smart-admin-web 10.10.0 → 10.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +1 -0
  2. package/cli/icon-subset.mjs +132 -0
  3. package/cli/smart-admin-icons.mjs +57 -0
  4. package/dist/api/index.d.ts +52 -1
  5. package/dist/api/index.js +151 -84
  6. package/dist/api/index.js.map +1 -1
  7. package/dist/api/schema.d.ts +936 -15
  8. package/dist/assets/icons/ph-subset.json.js +7 -7
  9. package/dist/createSmartAdmin.d.ts +3 -0
  10. package/dist/createSmartAdmin.js +36 -36
  11. package/dist/createSmartAdmin.js.map +1 -1
  12. package/dist/index.d.ts +1 -1
  13. package/dist/index.js +268 -252
  14. package/dist/index.js.map +1 -1
  15. package/dist/lib/icons.d.ts +5 -2
  16. package/dist/lib/icons.js +15 -13
  17. package/dist/lib/icons.js.map +1 -1
  18. package/dist/lib/runtime.js +1 -1
  19. package/dist/locales/en-US.d.ts +143 -0
  20. package/dist/locales/en-US.js +145 -0
  21. package/dist/locales/en-US.js.map +1 -1
  22. package/dist/locales/zh-CN.d.ts +143 -0
  23. package/dist/locales/zh-CN.js +145 -0
  24. package/dist/locales/zh-CN.js.map +1 -1
  25. package/dist/router/viewRegistry.js +1 -1
  26. package/dist/router/viewRegistry.js.map +1 -1
  27. package/dist/style.css +1 -1
  28. package/dist/types/api.d.ts +166 -0
  29. package/dist/types/api.js +12 -9
  30. package/dist/types/api.js.map +1 -1
  31. package/dist/views/system/ai-model/components/ProviderCard.vue.d.ts +13 -0
  32. package/dist/views/system/ai-model/components/ProviderCard.vue.js +8 -0
  33. package/dist/views/system/ai-model/components/ProviderCard.vue.js.map +1 -0
  34. package/dist/views/system/ai-model/components/ProviderCard2.vue.js +230 -0
  35. package/dist/views/system/ai-model/components/ProviderCard2.vue.js.map +1 -0
  36. package/dist/views/system/ai-model/components/ProviderForm.vue.d.ts +18 -0
  37. package/dist/views/system/ai-model/components/ProviderForm.vue.js +8 -0
  38. package/dist/views/system/ai-model/components/ProviderForm.vue.js.map +1 -0
  39. package/dist/views/system/ai-model/components/ProviderForm2.vue.js +507 -0
  40. package/dist/views/system/ai-model/components/ProviderForm2.vue.js.map +1 -0
  41. package/dist/views/system/ai-model/index.vue.d.ts +3 -0
  42. package/dist/views/system/ai-model/index.vue.js +8 -0
  43. package/dist/views/system/ai-model/index.vue.js.map +1 -0
  44. package/dist/views/system/ai-model/index2.vue.js +134 -0
  45. package/dist/views/system/ai-model/index2.vue.js.map +1 -0
  46. package/dist/views/system/ai-model/presets.d.ts +14 -0
  47. package/dist/views/system/ai-model/presets.js +22 -0
  48. package/dist/views/system/ai-model/presets.js.map +1 -0
  49. package/dist/views/system/ai-usage/index.vue.d.ts +3 -0
  50. package/dist/views/system/ai-usage/index.vue.js +8 -0
  51. package/dist/views/system/ai-usage/index.vue.js.map +1 -0
  52. package/dist/views/system/ai-usage/index2.vue.js +423 -0
  53. package/dist/views/system/ai-usage/index2.vue.js.map +1 -0
  54. package/package.json +6 -2
package/README.md CHANGED
@@ -49,6 +49,7 @@ createSmartAdmin({
49
49
  | `locales` | 文案。`locales/ext/<locale>/<命名空间>.ts` 默认导出该命名空间的键,按命名空间深合并进内置文案 |
50
50
  | `routes` | 布局壳之外的顶级静态路由,比如整屏看板、打印页 |
51
51
  | `icons` | 本地 SVG:`import.meta.glob('./assets/svg/*.svg', { query: '?raw', import: 'default', eager: true })` |
52
+ | `iconSets` | 应用自己的 `ph` 图标子集,启动时同步注册。用包带的命令生成:`npx smart-admin-icons` 扫描 `src` 里的 `ph:*` 名字,写出 `src/assets/icons/ph-subset.json`;`--check` 给 CI 用 |
52
53
  | `menuTitles` | 菜单 path → i18n key,存量库的菜单标题是中文时用 |
53
54
  | `install(app)` | 需要 app 实例的注册:`registerHeaderTool`、`registerMenuBadge`、自己的 `app.use` |
54
55
  | `plugins` | 与上面同形的插件对象。覆盖顺序:内核 < 插件(按数组顺序)< 应用 |
@@ -0,0 +1,132 @@
1
+ // Phosphor 图标子集:扫描源码里的 ph:* 名字,从 @iconify-json/ph 整集裁出用到的那几个。
2
+ // 内核的 scripts/gen-icon-subset.mjs 与随包发布的 smart-admin-icons 命令共用这一份,本模块没有副作用。
3
+ //
4
+ // 为什么要子集:整套 ph 有 9000+ 个图标(4.5 MB / 946 KB gz),一个应用的静态代码只用到几十个。
5
+ // 启动时同步注册子集,首帧即可离线渲染;子集外的名字(菜单管理里配的图标)由 AppIcon 懒加载整集兜底。
6
+ //
7
+ // 裁剪不引 @iconify/utils:这里只用得到它的 getIcons,它却会给每个消费方多装三个传递依赖。
8
+ // buildSubset 与 getIcons(data, names, true) 的产物逐字节一致,由 src/lib/iconSubset.spec.ts 拿 getIcons 对照锁住。
9
+
10
+ import { readFile, readdir, writeFile, mkdir } from 'node:fs/promises'
11
+ import { createRequire } from 'node:module'
12
+ import path from 'node:path'
13
+
14
+ const require = createRequire(import.meta.url)
15
+
16
+ export const PREFIX = 'ph'
17
+
18
+ const SCAN_EXT = new Set(['.vue', '.ts', '.tsx', '.js', '.jsx', '.mjs'])
19
+ // 测试文件里的假图标名不进产物
20
+ const TEST_FILE = /\.(spec|test)\.[^.]+$/
21
+ const ICON_RE = /\bph:[a-z0-9]+(?:-[a-z0-9]+)*\b/g
22
+
23
+ // 整集级的尺寸与 provider 字段,裁出的子集原样带上(同 getIcons 的 propsToCopy)
24
+ const ROOT_PROPS = ['left', 'top', 'width', 'height', 'provider']
25
+
26
+ /** 递归扫描目录,收集全部 `ph:xxx` 引用并入 `extra`,去重后升序返回。跳过 node_modules 与 . 开头的目录。 */
27
+ export async function scanIconNames(srcDir, extra = []) {
28
+ const found = new Set(extra)
29
+ async function walk(dir) {
30
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
31
+ const full = path.join(dir, entry.name)
32
+ if (entry.isDirectory()) {
33
+ if (entry.name !== 'node_modules' && !entry.name.startsWith('.')) await walk(full)
34
+ continue
35
+ }
36
+ if (!SCAN_EXT.has(path.extname(entry.name)) || TEST_FILE.test(entry.name)) continue
37
+ const text = await readFile(full, 'utf8')
38
+ for (const m of text.matchAll(ICON_RE)) found.add(m[0])
39
+ }
40
+ }
41
+ await walk(srcDir)
42
+ return [...found].toSorted()
43
+ }
44
+
45
+ /** 读取完整的 @iconify-json/ph 图标集。它是本包的依赖,从本模块所在位置解析,消费方不必自己装。 */
46
+ export async function loadFullSet() {
47
+ return JSON.parse(await readFile(require.resolve('@iconify-json/ph/icons.json'), 'utf8'))
48
+ }
49
+
50
+ /**
51
+ * 按名字裁出子集;`missing` 是 ph 里查无此名的(基本都是拼错)。别名沿 parent 链追到真图标,
52
+ * 链上每一环都进子集。结果的键序、别名处理与 getIcons 相同:prefix、icons、lastModified、aliases,最后是整集级字段。
53
+ */
54
+ export function buildSubset(full, names) {
55
+ const bare = names.map(n => (n.startsWith(`${PREFIX}:`) ? n.slice(PREFIX.length + 1) : n))
56
+ const sourceIcons = full.icons
57
+ const sourceAliases = full.aliases || Object.create(null)
58
+
59
+ // 名字 → 追到真图标所经的 parent 链([] = 本身就是图标,null = 查无此名或链断了)
60
+ const resolved = Object.create(null)
61
+ const resolve = name => {
62
+ if (sourceIcons[name]) return (resolved[name] = [])
63
+ if (!(name in resolved)) {
64
+ resolved[name] = null
65
+ const parent = sourceAliases[name] && sourceAliases[name].parent
66
+ const chain = parent && resolve(parent)
67
+ if (chain) resolved[name] = [parent, ...chain]
68
+ }
69
+ return resolved[name]
70
+ }
71
+ bare.forEach(resolve)
72
+
73
+ const subset = { prefix: full.prefix, icons: Object.create(null) }
74
+ if (full.lastModified) subset.lastModified = full.lastModified
75
+ const missing = []
76
+ for (const name in resolved) {
77
+ if (!resolved[name]) {
78
+ if (bare.includes(name)) missing.push(`${PREFIX}:${name}`)
79
+ } else if (sourceIcons[name]) {
80
+ subset.icons[name] = { ...sourceIcons[name] }
81
+ } else {
82
+ subset.aliases ??= Object.create(null)
83
+ subset.aliases[name] = { ...sourceAliases[name] }
84
+ }
85
+ }
86
+ for (const key of ROOT_PROPS) if (key in full) subset[key] = full[key]
87
+ return { subset, missing }
88
+ }
89
+
90
+ /** 与写盘一致的序列化(--check 靠逐字节比对)。 */
91
+ export function serialize(subset) {
92
+ return `${JSON.stringify(subset, null, 2)}\n`
93
+ }
94
+
95
+ /**
96
+ * 扫描 `src`、裁出子集后写进 `out`;`check` 为真时只比对不写盘。`extra` 是源码里没有、也要进子集的名字,
97
+ * `staleHint` 接在「已过期」提示后面告诉人怎么重生成。返回进程退出码:0 成功,1 有拼错的名字或产物已过期。
98
+ */
99
+ export async function generate({ src, out, check = false, extra = [], staleHint }) {
100
+ let names
101
+ try {
102
+ names = await scanIconNames(src, extra)
103
+ } catch (e) {
104
+ if (e?.code !== 'ENOENT') throw e
105
+ console.error(`扫描目录不存在:${src}`)
106
+ return 1
107
+ }
108
+
109
+ const { subset, missing } = buildSubset(await loadFullSet(), names)
110
+ if (missing.length) {
111
+ console.error(`图标名在 Phosphor 里不存在(拼错?):\n ${missing.join('\n ')}`)
112
+ return 1
113
+ }
114
+
115
+ const text = serialize(subset)
116
+ const count = Object.keys(subset.icons).length
117
+ const shown = path.relative(process.cwd(), out) || out
118
+ if (check) {
119
+ const current = await readFile(out, 'utf8').catch(() => '')
120
+ if (current !== text) {
121
+ console.error(`${shown} 已过期,${staleHint}`)
122
+ return 1
123
+ }
124
+ console.log(`图标子集已是最新(${count} 个)`)
125
+ return 0
126
+ }
127
+
128
+ await mkdir(path.dirname(out), { recursive: true })
129
+ await writeFile(out, text, 'utf8')
130
+ console.log(`已写出 ${shown}(${count} 个图标)`)
131
+ return 0
132
+ }
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ // smart-admin-icons:给应用生成自己的 ph 离线图标子集,交给 createSmartAdmin({ iconSets }) 启动时同步注册。
3
+ // 扫描、裁剪、--check 的逻辑在 icon-subset.mjs,本文件只解析参数。退出码:0 成功;1 图标名拼错或 --check 下产物过期;2 参数错误。
4
+ //
5
+ // 不加「被 import 时不执行」的守卫:npm 在 POSIX 上把 node_modules/.bin 里的命令做成符号链接,
6
+ // process.argv[1] 是链接路径而不是本文件,拿它和 import.meta.url 比对,命令会静默什么都不做。
7
+
8
+ import path from 'node:path'
9
+ import { generate } from './icon-subset.mjs'
10
+
11
+ const USAGE = `用法:smart-admin-icons [--src <目录>] [--out <文件>] [--check]
12
+
13
+ 扫描 --src 下 .vue/.ts/.tsx/.js/.jsx/.mjs 里的 ph:* 图标名(跳过 *.spec.*、*.test.*、node_modules),
14
+ 从 Phosphor 整集裁出这些图标写进 --out,交给 createSmartAdmin({ iconSets }) 启动时同步注册。
15
+ 源码里没有、却要离线可用的名字(比如种子菜单的图标),写进 --src 下任意一个 .ts 就会被扫进来。
16
+
17
+ --src <目录> 扫描根,默认 src
18
+ --out <文件> 产物路径,默认 src/assets/icons/ph-subset.json
19
+ --check 只比对不写盘:产物过期或有拼错的名字时非 0 退出(CI 用)
20
+ --help 显示本说明`
21
+
22
+ function parseArgs(argv) {
23
+ const opts = { src: 'src', out: 'src/assets/icons/ph-subset.json', check: false, help: false }
24
+ for (let i = 0; i < argv.length; i++) {
25
+ const arg = argv[i]
26
+ if (arg === '--check') opts.check = true
27
+ else if (arg === '--help' || arg === '-h') opts.help = true
28
+ else {
29
+ // --src x 与 --src=x 两种写法都认
30
+ const m = /^--(src|out)(?:=(.*))?$/.exec(arg)
31
+ if (!m) throw new Error(`未知参数:${arg}`)
32
+ const value = m[2] ?? argv[++i]
33
+ if (!value || value.startsWith('--')) throw new Error(`--${m[1]} 缺少值`)
34
+ opts[m[1]] = value
35
+ }
36
+ }
37
+ return opts
38
+ }
39
+
40
+ let opts
41
+ try {
42
+ opts = parseArgs(process.argv.slice(2))
43
+ } catch (e) {
44
+ console.error(`${e.message}\n\n${USAGE}`)
45
+ process.exitCode = 2
46
+ }
47
+
48
+ if (opts?.help) {
49
+ console.log(USAGE)
50
+ } else if (opts) {
51
+ process.exitCode = await generate({
52
+ src: path.resolve(opts.src),
53
+ out: path.resolve(opts.out),
54
+ check: opts.check,
55
+ staleHint: '去掉 --check 再跑一次即可更新',
56
+ })
57
+ }
@@ -1,5 +1,5 @@
1
1
  import { components } from './schema';
2
- import { AddUserInput, AddUserOutput, ChunkInitOutput, ConfigInput, CronPreviewOutput, DashboardSummary, DataScopeType, DictItem, DictItemInput, DictTypeInput, DuplicateStrategy, FileUploadOutput, ImportCommitResult, ImportPreview, ImportRow, JobDashboard, JobHandlersOutput, JobInput, LoginOutput, ModuleInput, ModuleRow, MyModulesOutput, MySessionItem, NoticeMineItem, NoticePublishInput, OnlineSessionItem, OrgInput, PermissionRouteItem, PositionInput, RoleInput, ServerInfoOutput, SysConfig, SysDictItem, SysDictType, SysExceptionLog, SysFile, SysJob, SysJobLog, SysLoginLog, SysNotice, SysOpLog, SysOrg, SysPosition, SysRole, SysRoleDataScope, UpdateUserInput, UserDetail, UserItem, UserProfile } from '../types/api';
2
+ import { AddUserInput, AddUserOutput, AiProviderAddInput, AiProviderPreset, AiProviderUpdateInput, AiProviderView, AiTestResult, AiUsageGroupBy, AiUsageSummary, AiUsageTrendPoint, ChunkInitOutput, ConfigInput, CronPreviewOutput, DashboardSummary, DataScopeType, DictItem, DictItemInput, DictTypeInput, DuplicateStrategy, FileUploadOutput, ImportCommitResult, ImportPreview, ImportRow, JobDashboard, JobHandlersOutput, JobInput, LoginOutput, ModuleInput, ModuleRow, MyModulesOutput, MySessionItem, NoticeMineItem, NoticePublishInput, OnlineSessionItem, OrgInput, PermissionRouteItem, PositionInput, RoleInput, ServerInfoOutput, SysAiUsageLog, SysConfig, SysDictItem, SysDictType, SysExceptionLog, SysFile, SysJob, SysJobLog, SysLoginLog, SysNotice, SysOpLog, SysOrg, SysPosition, SysRole, SysRoleDataScope, UpdateUserInput, UserDetail, UserItem, UserProfile } from '../types/api';
3
3
  import { MenuInput, MenuNode, MenuTreeNode } from '../types/menu';
4
4
  /** 业务错误(含后端 code / msgKey);视图 catch 后经 translateError 展示。 */
5
5
  export declare class ApiError extends Error {
@@ -670,3 +670,54 @@ export declare const recycleApi: {
670
670
  restore: (type: string, id: number) => Promise<boolean>;
671
671
  purge: (type: string, id: number) => Promise<boolean>;
672
672
  };
673
+ export declare const aiProviderApi: {
674
+ /** 厂商分页(含模型清单);搜索键 keyword/enabled → PascalCase 查询参。 */
675
+ page: (params: {
676
+ page: number;
677
+ pageSize: number;
678
+ keyword?: string;
679
+ enabled?: boolean;
680
+ }) => Promise<{
681
+ items: AiProviderView[];
682
+ total: number;
683
+ }>;
684
+ get: (id: number) => Promise<AiProviderView>;
685
+ /** 厂商预置清单(协议/BaseUrl/鉴权方式/起步模型),新增抽屉选预设用。 */
686
+ presets: () => Promise<AiProviderPreset[]>;
687
+ add: (body: AiProviderAddInput) => Promise<number>;
688
+ /** 更新入参没有 preset/protocol 字段——这两项创建后不可改,类型层面就不给改的机会。 */
689
+ update: (id: number, body: AiProviderUpdateInput) => Promise<boolean>;
690
+ /** 启用时若未配置 Key 会被后端拒绝(49004),调用方按 StatusSwitch 模式处理失败回弹。 */
691
+ setEnabled: (id: number, enabled: boolean) => Promise<boolean>;
692
+ remove: (id: number) => Promise<boolean>;
693
+ /** 测试连接:用库里当前配置发一条最小对话;失败原因在返回体里,不抛异常。 */
694
+ test: (id: number) => Promise<AiTestResult>;
695
+ };
696
+ interface AiUsageFilter {
697
+ from: string;
698
+ to: string;
699
+ providerCode?: string;
700
+ model?: string;
701
+ scene?: string;
702
+ userId?: number;
703
+ }
704
+ export declare const aiUsageApi: {
705
+ /** 区间总计 + 按维度分组聚合(groupBy 切换四个 Tab 共用这一个接口)。 */
706
+ summary: (params: AiUsageFilter & {
707
+ groupBy: AiUsageGroupBy;
708
+ }) => Promise<AiUsageSummary>;
709
+ /** 按天趋势;区间内无数据的日期后端已补 0,前端直接画连续折线。 */
710
+ trend: (params: AiUsageFilter) => Promise<AiUsageTrendPoint[]>;
711
+ /** 明细分页(不聚合);from/to 在这里是可选的(不筛日期就是全量分页)。 */
712
+ page: (params: Omit<AiUsageFilter, "from" | "to"> & {
713
+ page: number;
714
+ pageSize: number;
715
+ from?: string;
716
+ to?: string;
717
+ success?: boolean;
718
+ }) => Promise<{
719
+ items: SysAiUsageLog[];
720
+ total: number;
721
+ }>;
722
+ };
723
+ export {};