chore: 将已合并的商务工作台同步回主工作区

This commit is contained in:
Yvan
2026-11-05 10:57:06 +08:00
parent ee20692e95
commit 297a892518
10 changed files with 1304 additions and 121 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ VITE_CLI_PORT = 8080
VITE_SERVER_PORT = 8000
VITE_BASE_API = /api
VITE_FILE_API = /api
VITE_BASE_PATH = http://127.0.0.1
VITE_BASE_PATH = http://sw2.fuhua91.com
VITE_POSITION = open
VITE_EDITOR = code
// VITE_EDITOR = webstorm 如果使用webstorm开发且要使用dom定位到代码行功能 请先自定添加 webstorm到环境变量 再将VITE_EDITOR值修改为webstorm
+7
View File
@@ -6,3 +6,10 @@ bun.lockb
config.yaml
# 渲染产物:Jenkins 从 .docker-compose/nginx/conf.d/my.conf 生成
/nginx.k8s.conf
# Cursor Codegraph 本地索引
.codegraph/
# Vite 插件自动生成的类型声明
src/types/auto-imports.d.ts
src/types/components.d.ts
+7 -5
View File
@@ -1,16 +1,18 @@
# Kra 管理后台
Kra 的 Vue 3 管理端,与仓库中的 Kratos + Gin 后端配套使用。
Kra 的 Vue 3 商务管理端,本仓库使用 `kra` 分支。
Go 后端单独维护于 [java/sw](http://gitlab.fuhua91.com/java/sw.git) 的 `dev` 分支,本仓库不包含后端源代码。
```bash
npm install
npm run dev
pnpm install --frozen-lockfile
pnpm dev
```
生产构建:
```bash
npm run build
pnpm build
```
开发环境默认把 `/api` 代理到 `http://127.0.0.1:8000`。
开发环境沿用当前分支配置,将 `/api` 代理到 `http://sw2.fuhua91.com/api`。生产构建使用同域 `/api`;本地独立后端默认监听 `8000`,切换本地调试时需同步调整 `.env.development` 与 Vite 代理的前缀处理。
+1
View File
@@ -27,6 +27,7 @@
"axios": "1.8.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"echarts": "^6.1.0",
"element-plus": "^2.13.6",
"highlight.js": "^11.10.0",
"mitt": "^3.0.1",
+25 -2
View File
@@ -53,6 +53,9 @@ importers:
clsx:
specifier: ^2.1.1
version: 2.1.1
echarts:
specifier: ^6.1.0
version: 6.1.0
element-plus:
specifier: ^2.13.6
version: 2.14.4(vue@3.5.41)
@@ -1592,6 +1595,9 @@ packages:
duplexer@0.1.2:
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
echarts@6.1.0:
resolution: {integrity: sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==}
electron-to-chromium@1.5.405:
resolution: {integrity: sha512-bNglH7lPH5l+yHOes7Zr4VqxhOy4BQ9ZBUX4VdoFgxMpzJk7W1ZoO3Vgd9Pxa9PyjQ76sfm2aKH/nzEcCNRlew==}
@@ -1788,12 +1794,12 @@ packages:
glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
deprecated: Glob versions prior to v9 are no longer supported
glob@8.1.0:
resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
engines: {node: '>=12'}
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
deprecated: Glob versions prior to v9 are no longer supported
globals@13.24.0:
resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
@@ -2458,6 +2464,9 @@ packages:
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
engines: {node: '>=6'}
tslib@2.3.0:
resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
@@ -2662,6 +2671,9 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
zrender@6.1.0:
resolution: {integrity: sha512-oEGMDB6pOP2S6OwRR4PdVv610zrjnA3Bh+JnSG12fYJlBKjtNAoEb5fSUoCOOINlH96I2fU38/A2UpRKs67xYQ==}
snapshots:
'@babel/code-frame@7.29.7':
@@ -4244,6 +4256,11 @@ snapshots:
duplexer@0.1.2: {}
echarts@6.1.0:
dependencies:
tslib: 2.3.0
zrender: 6.1.0
electron-to-chromium@1.5.405: {}
element-plus@2.14.4(vue@3.5.41):
@@ -5069,6 +5086,8 @@ snapshots:
totalist@3.0.1: {}
tslib@2.3.0: {}
tslib@2.8.1: {}
type-check@0.4.0:
@@ -5255,3 +5274,7 @@ snapshots:
yallist@3.1.1: {}
yocto-queue@0.1.0: {}
zrender@6.1.0:
dependencies:
tslib: 2.3.0
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,32 @@
import { channelCount, channelMoney } from '../../sw/channel-display.js'
const definitions = [
{ key: 'todayRegister', label: '玩家注册' },
{ key: 'todayActive', label: '活跃玩家' },
{ key: 'todayPay', label: '充值金额', money: true },
{ key: 'effectivePromote', label: '有效端' },
{ key: 'activePromote', label: '活跃渠道数' },
{ key: 'newPromote', label: '新增渠道数' },
{ key: 'lockedPromote', label: '锁定渠道', event: true },
{ key: 'terminatedPromote', label: '解除合作渠道', event: true }
]
export const overviewCards = (data) => definitions.map(definition => {
const metric = data?.cards?.[definition.key]
const known = data?.available !== false && metric?.value != null
const changeRate = metric?.changeRate == null ? null : Number(metric.changeRate)
const label = metric?.compareLabel || '较上一同期'
const change = !known ? metric?.compareLabel || '—' : changeRate == null ? `${label}:无可比数据` : `${changeRate > 0 ? '+' : ''}${changeRate.toFixed(1)}% ${label}`
return {
...definition,
value: known ? (definition.money ? channelMoney(metric.value) : channelCount(String(metric.value).split('.')[0])) : !data ? '—' : data.available === false || definition.event ? '未接入' : '未获取',
change,
up: known && changeRate > 0,
down: known && changeRate < 0,
tip: definition.key === 'effectivePromote'
? `渠道注册后 ${data?.settings?.days ?? 30} 天内充值满 ¥${data?.settings?.amount ?? 3000},按达标日期统计。`
: definition.event ? '按所选区间内的状态变更事件统计。' : '所选时间区间内的数据,与上一等长周期比较。'
}
})
export const escapeChartText = value => String(value ?? '').replace(/[&<>"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]))
@@ -0,0 +1,24 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import { overviewCards, escapeChartText } from './dashboard-metrics.js'
test('overview preserves exact money, eight cards, and unavailable event data', () => {
const cards = overviewCards({ available: true, settings: { days: 15, amount: 500 }, cards: {
todayPay: { value: '9007199254740993.01', changeRate: null },
todayRegister: { value: '0.00', changeRate: '0' },
todayActive: { value: '5998.00', changeRate: '2.1' },
lockedPromote: { value: null, compareLabel: '事件数据未接入' }
} })
assert.equal(cards.length, 8)
assert.equal(cards.find(c => c.key === 'todayPay').value, '¥9,007,199,254,740,993.01')
assert.equal(cards.find(c => c.key === 'todayRegister').value, '0')
assert.equal(cards.find(c => c.key === 'todayActive').value, '5,998')
assert.equal(cards.find(c => c.key === 'lockedPromote').value, '未接入')
assert.match(cards.find(c => c.key === 'effectivePromote').tip, /15 天内充值满 ¥500/)
assert.ok(overviewCards(null).every(c => c.value === '—'))
assert.ok(overviewCards({ available: false }).every(c => c.value === '未接入'))
})
test('chart HTML tooltips escape provider-controlled names', () => {
assert.equal(escapeChartText('<img src=x onerror="alert(1)">&'), '&lt;img src=x onerror=&quot;alert(1)&quot;&gt;&amp;')
})
+1 -12
View File
@@ -1,23 +1,12 @@
<template>
<div class="h-full kra-container2 overflow-auto bg-main">
<div class="space-y-2 py-2">
<business-dashboard>
<template #heading>
<div>
<h1 class="text-xl font-semibold text-base-text">欢迎回来,{{ userStore.userInfo.nickName }}</h1>
<p class="mt-2 text-sm text-muted-foreground">{{ today }} · 商务工作台</p>
</div>
</template>
</business-dashboard>
<business-dashboard />
</div>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { useUserStore } from '@/pinia/modules/user'
import BusinessDashboard from './components/BusinessDashboard.vue'
defineOptions({ name: 'Dashboard' })
const userStore = useUserStore()
const today = computed(() => new Date().toLocaleDateString('zh-CN'))
</script>
+3 -5
View File
@@ -119,11 +119,9 @@ export default ({ mode }) => {
// 把key的路径代理到target位置
// detail: https://cli.vuejs.org/config/#devserver-proxy
[env.VITE_BASE_API]: {
// 需要代理的路径 例如 '/api'
target: `${env.VITE_BASE_PATH}:${env.VITE_SERVER_PORT}/`, // 代理到 目标路径
changeOrigin: true,
rewrite: (path) =>
path.replace(new RegExp('^' + env.VITE_BASE_API), '')
// 线上接口形如 http://sw2.fuhua91.com/api/sw/promote/list,保留 /api,不追加 8000 端口
target: env.VITE_BASE_PATH,
changeOrigin: true
}
}
},