chore: 将已合并的商务工作台同步回主工作区
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
@@ -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 代理的前缀处理。
|
||||
|
||||
@@ -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",
|
||||
|
||||
Generated
+25
-2
@@ -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 => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[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)">&'), '<img src=x onerror="alert(1)">&')
|
||||
})
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user