From 1639cf35a094bc4b1a77f8a5bf4ca0885c0290c6 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Tue, 18 Aug 2026 19:49:37 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 29 +- apps/admin/app/components/SvcAlertBanner.vue | 116 -- apps/admin/app/components/SvcAlertBell.vue | 142 +++ apps/admin/app/composables/useTableSort.ts | 44 + apps/admin/app/layouts/default.vue | 87 +- apps/admin/app/pages/agents.vue | 1040 ++++++++++++----- apps/admin/app/pages/appgoods.vue | 416 +++++++ apps/admin/app/pages/dashboard.vue | 33 +- apps/admin/app/pages/device.vue | 19 +- apps/admin/app/pages/factory.vue | 63 +- apps/admin/app/pages/product.vue | 434 ++++++- apps/admin/app/pages/provider.vue | 142 ++- apps/admin/app/stores/auth.ts | 69 +- apps/admin/app/utils/chipvendors.ts | 23 + apps/admin/app/utils/menus.ts | 1 + apps/admin/app/utils/svchealth.ts | 2 +- apps/proto/db.proto | 21 +- apps/services/comm/appgoods.go | 37 + apps/services/comm/const.go | 1 + apps/services/modules/api/api_addbrand.go | 5 +- apps/services/modules/api/api_addproduct.go | 5 +- apps/services/modules/api/api_updatebrand.go | 2 +- .../services/modules/api/api_updateproduct.go | 12 +- apps/services/modules/api/model.go | 16 + apps/services/modules/console/api_agent.go | 67 +- apps/services/modules/console/api_config.go | 4 + apps/services/modules/console/api_device.go | 76 +- apps/services/modules/console/api_goods.go | 262 +++++ apps/services/modules/console/api_inspect.go | 27 +- apps/services/modules/console/model_goods.go | 67 ++ apps/services/modules/console/registry.go | 18 + apps/services/modules/console/scope_app.go | 28 + apps/services/modules/console/server.go | 29 +- apps/services/modules/pay/api.go | 121 +- .../modules/pay/api_paypalnotify_v2.go | 196 ++++ apps/services/modules/pay/model.go | 21 + .../modules/user/api_getchannelapp_test.go | 57 - .../modules/user/api_live_probe_test.go | 88 -- .../modules/user/api_v3_getagents_test.go | 71 -- .../modules/user/api_v3_getappconfig.go | 26 +- apps/services/modules/user/compat_test.go | 67 -- apps/services/modules/user/module_test.go | 579 ++++++++- apps/services/pb/db.pb.go | 90 +- apps/services/sys/pay/paypal/paypal.go | 125 +- deploy/admin/dev-deploy.sh | 4 +- deploy/admin/prod-deploy.sh | 2 +- deploy/app/README.md | 25 +- deploy/app/confs/home.yaml.example | 40 +- deploy/app/dev-deploy.sh | 11 +- deploy/app/docker-compose.yml | 14 +- deploy/app/env/env.example | 4 + deploy/app/prod-deploy.sh | 284 ++++- deploy/console/dev-deploy.sh | 4 +- deploy/console/prod-deploy.sh | 2 +- .../2026-08-18-product-provider.sql | 38 + 55 files changed, 4098 insertions(+), 1108 deletions(-) delete mode 100644 apps/admin/app/components/SvcAlertBanner.vue create mode 100644 apps/admin/app/components/SvcAlertBell.vue create mode 100644 apps/admin/app/composables/useTableSort.ts create mode 100644 apps/admin/app/pages/appgoods.vue create mode 100644 apps/admin/app/utils/chipvendors.ts create mode 100644 apps/services/comm/appgoods.go create mode 100644 apps/services/modules/console/api_goods.go create mode 100644 apps/services/modules/console/model_goods.go create mode 100644 apps/services/modules/pay/api_paypalnotify_v2.go delete mode 100644 apps/services/modules/user/api_getchannelapp_test.go delete mode 100644 apps/services/modules/user/api_live_probe_test.go delete mode 100644 apps/services/modules/user/api_v3_getagents_test.go delete mode 100644 apps/services/modules/user/compat_test.go create mode 100644 docs/migrations/2026-08-18-product-provider.sql diff --git a/CLAUDE.md b/CLAUDE.md index a79c7f5b..103a2dfe 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -74,7 +74,7 @@ cd deploy/console && ./prod-deploy.sh 0.2.1 yunyan # 第 2 个参数选生产 - `along` → `registry.ymaikj.com`(仓库实体就在阿龙测试机 8.133.166.29 上,**账号是 `admin` 不是 `liwei`**);阿龙测试机与正式机 47.116.104.181 都从这里拉镜像 测试环境档案在各 `dev-deploy.sh` 的 `env_profile()`: -- `along` → 8.133.166.29(阿里云,x86_64/Docker 24.0.9),目录 `/home/work/starpivot/{app,admin,console}`,密钥 `~/.ssh/along.pem` +- `along` → 8.133.166.29(阿里云,x86_64/Docker 24.0.9),目录 `/home/work/starpivot/{app,admin,console}`,密钥 `/Users/liwei1dao/liwei/密钥/along/releae-guangzhou.pem` 该机已自带 postgres:16 / redis:7 / mysql / NPM / gitea / docker-registry,配 `.env` 时优先复用,别另起一套 **生产镜像名统一为 `starpivot-<服务>`**(admin/console/app 三个服务、各家公司都一样),映射在 @@ -92,11 +92,32 @@ admin/console 的 `prod-deploy.sh` 已改为**多生产环境档案**(`env_pro - `yunyan` → 139.224.212.237(上海阿里云,与业务后端 app 同机),目录 `/home/work/starpivot/{admin,console}`,密钥 `~/Documents/key/aliyun_shanghai.pem` (2026-08-05 由美国机 47.253.88.66 迁来;美国机上的 `yunyan-admin`/`console-a11` 容器仍在跑,待验证后停,脚本已不再指向它) - `lingpu` → 8.166.114.65(广州阿里云),目录 `/home/work/starpivot/{admin,console}`,密钥 `~/.ssh/lingpu-gz.pem` -- `along` → 47.116.104.181(阿龙正式机,2026-08-16 补),目录 `/home/work/starpivot/{admin,console}`,密钥 `~/.ssh/along.pem`(与测试机同一把);镜像走阿龙仓库 `registry.ymaikj.com` +- `along` → 47.116.104.181(阿龙正式机,2026-08-16 补),目录 `/home/work/starpivot/{admin,console}`,密钥 `/Users/liwei1dao/liwei/密钥/along/releae-guangzhou.pem`(与测试机同一把);镜像走阿龙仓库 `registry.ymaikj.com` app 的 `prod-deploy.sh` 是**按区域**(`region_profile()`):`shanghai|japan|singapore` 是云雁三地,新增 `along` → 47.116.104.181。 -该函数除服务器连接信息外还给两项:`REGION_COMPANY`(这个区域拉哪家仓库,云雁三地=yunyan、阿龙=along;可用环境变量 `COMPANY` 覆盖)、 -`DEPLOY_DIR_OVERRIDE`(阿龙用 `/home/work/starpivot/app`,不走 `APPS` 里的 `/home/work/<应用>-a11`)。 +该函数除服务器连接信息外还给 `REGION_COMPANY`(这个区域拉哪家仓库,云雁三地=yunyan、阿龙=along;可用环境变量 `COMPANY` 覆盖)。 + +**远端目录与容器名一律按「应用」区分**(2026-08-18 起):目录只由 `APPS` 表给(`ym` → `/home/work/ym-a11`), +区域里**不再有 `DEPLOY_DIR_OVERRIDE`**(阿龙原先覆盖成 `/home/work/starpivot/app`,导致部 ym 时落错目录、还和已有容器撞名,已移除; +阿龙机上那份旧的 `/home/work/starpivot/app` 部署保持原样、不再由脚本管理)。容器名由应用 key 推导为 `<应用>-a11`, +经 `CONTAINER_NAME`(同时作 `COMPOSE_PROJECT_NAME`)注入 `docker-compose.yml` 的 `container_name: ${CONTAINER_NAME:-starpivot-app}`, +部署后写回服务器 `.env`。compose 里的 service 名固定为 `app`,别写成具体应用名。 +dev(`dev-deploy.sh`)仍是每台机单份部署,走默认容器名 `starpivot-app`;admin/console 的 compose 也尚未参数化。 +`docker-compose.yml` 由 `prod-deploy.sh` **按应用现生成再下发**:模板里 service 名是占位的 `app`,下发时 +`sed` 改写成本应用名(`ym` → `ym-a11:`),于是服务器上 service 名 = 容器名 = `<应用>-a11`。 +下发前与远端文件逐字节比对,一致就跳过、不一致才备份 `.bak` 后覆盖(纯"存在就不碰"会让旧 compose 永不更新—— +ym 首次部署容器一直叫 `starpivot-app` 正是如此)。service 名变更后旧容器占着 container_name, +远端脚本会按 compose 标签识别并 `docker rm -f` 掉再重建。服务器上只需维护 `.env` 与 `confs/*.yaml`。 + +**新应用首次部署 checklist**(2026-08-18 部署 ym 时逐条踩出来的): +1. `confs/home.yaml` 的 `wordfilter.WorldFile` 若列了词库 txt,服务器上必须真有这些文件,否则 home + `panic: init sys.wordfilter err: no found file:...` → entrypoint 杀容器 → 无限重启。词库不在 git 里, + 不需要就保持注释(模板已默认注释)。**表象是 api 报 `Table '<库>.userdevice' doesn't exist`**—— + userdevice/user 等表由 home 里的 user 模块启动时 `CreateTable` 建,home 起不来就永远没表,别去查数据库,先看 panic。 +2. `confs/ip2region_v4.xdb` / `v6.xdb`:`sys.ipinfo` 启动必需,缺了同样 panic。现在 prod-deploy 会自动下发(仅远端缺失时)。 +3. `.env` 别照抄模板:`CLUSTER_TAG`、`ANALYZE_APP_NAME`、`REDIS_KEY_PREFIX` 都要换成本应用的身份, + 其中 `ANALYZE_APP_NAME` 必须等于 console 注册表 `app_registry.app_name`,否则按应用作用域的配置全部落到全局默认。 +4. console 后台要先把该应用登记进 `app_registry`(阿龙正式机 2026-08-18 时该表还是空的)。 `dev-deploy.sh <环境>` 只做镜像部署:构建推送 → 远端 pull 重启,**不上传** 服务器上的真实 `.env` / `confs/*.yaml`(只下发 `*.example` 模板)。 diff --git a/apps/admin/app/components/SvcAlertBanner.vue b/apps/admin/app/components/SvcAlertBanner.vue deleted file mode 100644 index c41fc0bd..00000000 --- a/apps/admin/app/components/SvcAlertBanner.vue +++ /dev/null @@ -1,116 +0,0 @@ - - - - - diff --git a/apps/admin/app/components/SvcAlertBell.vue b/apps/admin/app/components/SvcAlertBell.vue new file mode 100644 index 00000000..6ee4d61f --- /dev/null +++ b/apps/admin/app/components/SvcAlertBell.vue @@ -0,0 +1,142 @@ + + + + + diff --git a/apps/admin/app/composables/useTableSort.ts b/apps/admin/app/composables/useTableSort.ts new file mode 100644 index 00000000..f36497a8 --- /dev/null +++ b/apps/admin/app/composables/useTableSort.ts @@ -0,0 +1,44 @@ +import { ref } from 'vue' + +// 列表表头按字段排序。各页传一组「升序语义」的比较器,降序由这里统一取反; +// 比较器里用 `||` 串一个兜底字段(一般是 id),值相等时顺序才稳定、不会每次渲染乱跳。 +// +// 用法: +// const { toggleSort, sortMark, applySort } = useTableSort({ +// id: (a, b) => a.id - b.id, +// name: (a, b) => a.name.localeCompare(b.name, 'zh') || a.id - b.id, +// }, 'id') +// const viewRows = computed(() => applySort(rows.value.filter(...))) +// 名称{{ sortMark('name') }} +export type SortDir = 'asc' | 'desc' +export type Comparator = (a: T, b: T) => number + +export function useTableSort( + comparators: Record>, + defaultKey: string, + defaultDir: SortDir = 'asc' +) { + const sortKey = ref(defaultKey) + const sortDir = ref(defaultDir) + + function toggleSort(key: string) { + if (!comparators[key]) return + if (sortKey.value === key) sortDir.value = sortDir.value === 'asc' ? 'desc' : 'asc' + else { sortKey.value = key; sortDir.value = 'asc' } + } + + // 表头后缀的排序箭头;非当前排序列返回空串 + function sortMark(key: string) { + return sortKey.value === key ? (sortDir.value === 'asc' ? ' ↑' : ' ↓') : '' + } + + // 返回新数组,不改原列表(原列表常是 ref 里的接口数据) + function applySort(list: T[]): T[] { + const cmp = comparators[sortKey.value] + if (!cmp) return list + const dir = sortDir.value === 'asc' ? 1 : -1 + return [...list].sort((a, b) => cmp(a, b) * dir) + } + + return { sortKey, sortDir, toggleSort, sortMark, applySort } +} diff --git a/apps/admin/app/layouts/default.vue b/apps/admin/app/layouts/default.vue index f176d3a1..6fc3cfd6 100644 --- a/apps/admin/app/layouts/default.vue +++ b/apps/admin/app/layouts/default.vue @@ -41,17 +41,35 @@ - -
-
+ + - +
- - - - - + +
@@ -157,7 +144,13 @@ const route = useRoute() const { consoleApi } = useApi() const { success, error } = useToast() +// DaisyUI 的 dropdown 靠 focus 展开,点完菜单项要主动失焦才会收起 +function closeMenu() { + if (import.meta.client) (document.activeElement as HTMLElement | null)?.blur() +} + function logout() { + closeMenu() auth.logout() router.push('/') } @@ -168,6 +161,7 @@ const pwdSubmitting = ref(false) const pwd = reactive({ old: '', next: '', confirm: '' }) function openPwd() { + closeMenu() pwd.old = '' pwd.next = '' pwd.confirm = '' @@ -192,14 +186,13 @@ async function submitPwd() { } } -// 身份标签(侧栏用全称,顶栏用简称,与 common.js 一致) -const identityLabel = computed(() => { - const map: Record = { 1: '超级管理员', 2: '管理员', 3: '代理', 4: '运营' } - return map[auth.identity] ?? '' -}) -const identityLabelShort = computed(() => { - const map: Record = { 1: '超管', 2: '管理员', 3: '代理', 4: '运营' } - return map[auth.identity] ?? '' +// 身份标签统一取 store 的 IDENTITY_LABELS(含品牌商/渠道商),避免各处再各写一份 map +const identityLabel = computed(() => auth.identityLabel || '未知身份') +// 账号 id:登录时下发。0 分两种情况——yaml 里的引导超管(库里本就没有记录), +// 或者本次登录态是加这个字段之前存下的(重新登录一次即可显示)。 +const accountIdLabel = computed(() => { + if (auth.accountId) return 'ID ' + auth.accountId + return auth.identity === 1 ? '内置账号' : 'ID -' }) const avatarChar = computed(() => (auth.account || 'A').charAt(0).toUpperCase()) diff --git a/apps/admin/app/pages/agents.vue b/apps/admin/app/pages/agents.vue index a0701c57..ee5bf918 100644 --- a/apps/admin/app/pages/agents.vue +++ b/apps/admin/app/pages/agents.vue @@ -1,278 +1,443 @@