vome-core 0.3.68 → 0.3.70

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 (280) hide show
  1. package/dist/admin/components/ui/avatar/Avatar.vue +18 -18
  2. package/dist/admin/components/ui/avatar/AvatarFallback.vue +21 -21
  3. package/dist/admin/components/ui/avatar/AvatarImage.vue +16 -16
  4. package/dist/admin/components/ui/avatar/index.js +1 -1
  5. package/dist/admin/components/ui/badge/Badge.vue +26 -26
  6. package/dist/admin/components/ui/button/Button.vue +31 -31
  7. package/dist/admin/components/ui/button/index.d.ts +1 -1
  8. package/dist/admin/components/ui/checkbox/Checkbox.vue +35 -35
  9. package/dist/admin/components/ui/checkbox/index.js +1 -1
  10. package/dist/admin/components/ui/dialog/Dialog.vue +19 -19
  11. package/dist/admin/components/ui/dialog/DialogClose.vue +15 -15
  12. package/dist/admin/components/ui/dialog/DialogContent.vue +267 -267
  13. package/dist/admin/components/ui/dialog/DialogDescription.vue +23 -23
  14. package/dist/admin/components/ui/dialog/DialogFooter.vue +27 -27
  15. package/dist/admin/components/ui/dialog/DialogHeader.vue +17 -17
  16. package/dist/admin/components/ui/dialog/DialogOverlay.vue +21 -21
  17. package/dist/admin/components/ui/dialog/DialogScrollContent.vue +60 -60
  18. package/dist/admin/components/ui/dialog/DialogTitle.vue +23 -23
  19. package/dist/admin/components/ui/dialog/DialogTrigger.vue +15 -15
  20. package/dist/admin/components/ui/dialog/index.js +1 -1
  21. package/dist/admin/components/ui/dropdown-menu/DropdownMenu.vue +19 -19
  22. package/dist/admin/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +39 -39
  23. package/dist/admin/components/ui/dropdown-menu/DropdownMenuContent.vue +39 -39
  24. package/dist/admin/components/ui/dropdown-menu/DropdownMenuGroup.vue +15 -15
  25. package/dist/admin/components/ui/dropdown-menu/DropdownMenuItem.vue +31 -31
  26. package/dist/admin/components/ui/dropdown-menu/DropdownMenuLabel.vue +23 -23
  27. package/dist/admin/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +21 -21
  28. package/dist/admin/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +40 -40
  29. package/dist/admin/components/ui/dropdown-menu/DropdownMenuSeparator.vue +23 -23
  30. package/dist/admin/components/ui/dropdown-menu/DropdownMenuShortcut.vue +17 -17
  31. package/dist/admin/components/ui/dropdown-menu/DropdownMenuSub.vue +18 -18
  32. package/dist/admin/components/ui/dropdown-menu/DropdownMenuSubContent.vue +27 -27
  33. package/dist/admin/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +31 -31
  34. package/dist/admin/components/ui/dropdown-menu/DropdownMenuTrigger.vue +17 -17
  35. package/dist/admin/components/ui/dropdown-menu/index.js +1 -1
  36. package/dist/admin/components/ui/input/Input.vue +183 -183
  37. package/dist/admin/components/ui/input/index.js +1 -1
  38. package/dist/admin/components/ui/label/Label.vue +26 -26
  39. package/dist/admin/components/ui/label/index.js +1 -1
  40. package/dist/admin/components/ui/scroll-area/ScrollArea.vue +33 -33
  41. package/dist/admin/components/ui/scroll-area/ScrollBar.vue +32 -32
  42. package/dist/admin/components/ui/scroll-area/index.js +1 -1
  43. package/dist/admin/components/ui/select/Select.vue +19 -19
  44. package/dist/admin/components/ui/select/SelectContent.vue +51 -51
  45. package/dist/admin/components/ui/select/SelectGroup.vue +15 -15
  46. package/dist/admin/components/ui/select/SelectItem.vue +53 -53
  47. package/dist/admin/components/ui/select/SelectItemText.vue +15 -15
  48. package/dist/admin/components/ui/select/SelectLabel.vue +17 -17
  49. package/dist/admin/components/ui/select/SelectScrollDownButton.vue +26 -26
  50. package/dist/admin/components/ui/select/SelectScrollUpButton.vue +26 -26
  51. package/dist/admin/components/ui/select/SelectSeparator.vue +19 -19
  52. package/dist/admin/components/ui/select/SelectTrigger.vue +44 -44
  53. package/dist/admin/components/ui/select/SelectValue.vue +15 -15
  54. package/dist/admin/components/ui/select/index.js +1 -1
  55. package/dist/admin/components/ui/separator/Separator.vue +29 -29
  56. package/dist/admin/components/ui/separator/index.js +1 -1
  57. package/dist/admin/components/ui/sheet/Sheet.vue +19 -19
  58. package/dist/admin/components/ui/sheet/SheetClose.vue +15 -15
  59. package/dist/admin/components/ui/sheet/SheetContent.vue +63 -63
  60. package/dist/admin/components/ui/sheet/SheetDescription.vue +21 -21
  61. package/dist/admin/components/ui/sheet/SheetFooter.vue +16 -16
  62. package/dist/admin/components/ui/sheet/SheetHeader.vue +15 -15
  63. package/dist/admin/components/ui/sheet/SheetOverlay.vue +21 -21
  64. package/dist/admin/components/ui/sheet/SheetTitle.vue +21 -21
  65. package/dist/admin/components/ui/sheet/SheetTrigger.vue +15 -15
  66. package/dist/admin/components/ui/sheet/index.js +1 -1
  67. package/dist/admin/components/ui/sonner/Toaster.vue +117 -117
  68. package/dist/admin/components/ui/sonner/index.js +1 -1
  69. package/dist/admin/components/ui/table/Table.vue +16 -16
  70. package/dist/admin/components/ui/table/TableBody.vue +17 -17
  71. package/dist/admin/components/ui/table/TableCaption.vue +17 -17
  72. package/dist/admin/components/ui/table/TableCell.vue +22 -22
  73. package/dist/admin/components/ui/table/TableEmpty.vue +34 -34
  74. package/dist/admin/components/ui/table/TableFooter.vue +17 -17
  75. package/dist/admin/components/ui/table/TableHead.vue +17 -17
  76. package/dist/admin/components/ui/table/TableHeader.vue +17 -17
  77. package/dist/admin/components/ui/table/TableRow.vue +17 -17
  78. package/dist/admin/components/ui/table/index.js +1 -1
  79. package/dist/admin/components/ui/tabs/Tabs.vue +24 -24
  80. package/dist/admin/components/ui/tabs/TabsContent.vue +21 -21
  81. package/dist/admin/components/ui/tabs/TabsList.vue +24 -24
  82. package/dist/admin/components/ui/tabs/TabsTrigger.vue +26 -26
  83. package/dist/admin/components/ui/tabs/index.js +1 -1
  84. package/dist/admin/components/ui/tooltip/Tooltip.vue +19 -19
  85. package/dist/admin/components/ui/tooltip/TooltipContent.vue +34 -34
  86. package/dist/admin/components/ui/tooltip/TooltipProvider.vue +14 -14
  87. package/dist/admin/components/ui/tooltip/TooltipTrigger.vue +15 -15
  88. package/dist/admin/components/ui/tooltip/index.js +1 -1
  89. package/dist/admin/components/vm-action-btn.vue +136 -136
  90. package/dist/admin/components/vm-aside-list-item.vue +136 -136
  91. package/dist/admin/components/vm-aside.vue +229 -229
  92. package/dist/admin/components/vm-auto-perm-dialog.vue +371 -371
  93. package/dist/admin/components/vm-card-grid.vue +37 -37
  94. package/dist/admin/components/vm-card-head.vue +45 -45
  95. package/dist/admin/components/vm-card-meta.vue +22 -22
  96. package/dist/admin/components/vm-card-title.vue +56 -56
  97. package/dist/admin/components/vm-card.vue +176 -176
  98. package/dist/admin/components/vm-check-tree.vue +340 -340
  99. package/dist/admin/components/vm-clamp-text.vue +39 -39
  100. package/dist/admin/components/vm-dept-tree.vue +497 -497
  101. package/dist/admin/components/vm-empty.vue +68 -68
  102. package/dist/admin/components/vm-eps-perm-picker.vue +123 -123
  103. package/dist/admin/components/vm-form-dialog.vue +632 -632
  104. package/dist/admin/components/vm-group-cascader.vue +560 -560
  105. package/dist/admin/components/vm-icon-btn.vue +88 -88
  106. package/dist/admin/components/vm-icon-picker.vue +647 -647
  107. package/dist/admin/components/vm-inline-search.vue +61 -61
  108. package/dist/admin/components/vm-loading.vue +114 -114
  109. package/dist/admin/components/vm-markdown.vue +405 -405
  110. package/dist/admin/components/vm-marquee.vue +265 -265
  111. package/dist/admin/components/vm-page-tabs.vue +94 -94
  112. package/dist/admin/components/vm-perm-section.vue +57 -57
  113. package/dist/admin/components/vm-ri-icon.vue +23 -23
  114. package/dist/admin/components/vm-role-picker.vue +314 -314
  115. package/dist/admin/components/vm-split-layout.vue +106 -106
  116. package/dist/admin/components/vm-thumb.vue +49 -49
  117. package/dist/admin/components/vm-view-path-picker.vue +59 -59
  118. package/dist/admin/crud/auto-fields.d.ts +7 -0
  119. package/dist/admin/crud/auto-fields.js +1 -1
  120. package/dist/admin/crud/components/vm-avatar.vue +104 -104
  121. package/dist/admin/crud/components/vm-color-picker.vue +1626 -1626
  122. package/dist/admin/crud/components/vm-column-custom.vue +193 -193
  123. package/dist/admin/crud/components/vm-context-menu.vue +105 -105
  124. package/dist/admin/crud/components/vm-copy-btn.vue +102 -102
  125. package/dist/admin/crud/components/vm-date-calendar-panel.vue +537 -537
  126. package/dist/admin/crud/components/vm-date-picker.vue +75 -75
  127. package/dist/admin/crud/components/vm-date-range.vue +88 -88
  128. package/dist/admin/crud/components/vm-date-text.vue +42 -42
  129. package/dist/admin/crud/components/vm-datetime-field.vue +215 -215
  130. package/dist/admin/crud/components/vm-datetime-picker.vue +9 -9
  131. package/dist/admin/crud/components/vm-dict-tag.vue +112 -112
  132. package/dist/admin/crud/components/vm-ellipsis-text.vue +34 -34
  133. package/dist/admin/crud/components/vm-form-hint.vue +81 -81
  134. package/dist/admin/crud/components/vm-json-code.vue +41 -41
  135. package/dist/admin/crud/components/vm-json-editor.vue +161 -161
  136. package/dist/admin/crud/components/vm-json-kv-editor.vue +318 -318
  137. package/dist/admin/crud/components/vm-kv-row.vue +51 -51
  138. package/dist/admin/crud/components/vm-multi-select.vue +279 -279
  139. package/dist/admin/crud/components/vm-number-range.vue +204 -204
  140. package/dist/admin/crud/components/vm-preview-viewer.vue +894 -894
  141. package/dist/admin/crud/components/vm-radio.vue +87 -87
  142. package/dist/admin/crud/components/vm-richtext.vue +313 -313
  143. package/dist/admin/crud/components/vm-select.vue +570 -570
  144. package/dist/admin/crud/components/vm-status-tag.vue +158 -158
  145. package/dist/admin/crud/components/vm-switch.vue +158 -158
  146. package/dist/admin/crud/components/vm-tag-list.vue +108 -108
  147. package/dist/admin/crud/components/vm-text-link.vue +52 -52
  148. package/dist/admin/crud/components/vm-toggle-switch.vue +84 -84
  149. package/dist/admin/crud/components/vm-tree-select.vue +450 -450
  150. package/dist/admin/crud/components/vm-upload-item.vue +298 -298
  151. package/dist/admin/crud/components/vm-upload.vue +436 -436
  152. package/dist/admin/crud/config.js +1 -1
  153. package/dist/admin/crud/confirm.js +1 -1
  154. package/dist/admin/crud/crud-page-request.js +1 -1
  155. package/dist/admin/crud/data-i18n.js +1 -1
  156. package/dist/admin/crud/dict.js +1 -1
  157. package/dist/admin/crud/export-fetch.js +1 -1
  158. package/dist/admin/crud/index.d.ts +1 -1
  159. package/dist/admin/crud/index.js +1 -0
  160. package/dist/admin/crud/key.js +1 -1
  161. package/dist/admin/crud/plugins.js +1 -1
  162. package/dist/admin/crud/span.js +1 -1
  163. package/dist/admin/crud/style.js +1 -1
  164. package/dist/admin/crud/validate.js +1 -1
  165. package/dist/admin/crud/vm-add-btn.vue +70 -70
  166. package/dist/admin/crud/vm-adv-search.vue +83 -83
  167. package/dist/admin/crud/vm-crud.vue +711 -711
  168. package/dist/admin/crud/vm-data-i18n-btn.vue +456 -456
  169. package/dist/admin/crud/vm-data-i18n-lang.vue +183 -183
  170. package/dist/admin/crud/vm-export-btn.vue +183 -183
  171. package/dist/admin/crud/vm-flex.vue +14 -14
  172. package/dist/admin/crud/vm-import-btn.vue +747 -747
  173. package/dist/admin/crud/vm-multi-delete-btn.vue +49 -49
  174. package/dist/admin/crud/vm-pagination.vue +212 -212
  175. package/dist/admin/crud/vm-refresh-btn.vue +35 -35
  176. package/dist/admin/crud/vm-row.vue +55 -55
  177. package/dist/admin/crud/vm-search-key.vue +78 -78
  178. package/dist/admin/crud/vm-search.vue +466 -466
  179. package/dist/admin/crud/vm-table.vue +1390 -1390
  180. package/dist/admin/crud/vm-tabs.vue +94 -94
  181. package/dist/admin/crud/vm-toolbar.vue +357 -357
  182. package/dist/admin/crud/vm-upsert.vue +765 -765
  183. package/dist/admin/directives/perm.js +1 -1
  184. package/dist/admin/env.d.ts +1 -1
  185. package/dist/admin/hooks/useUpload.js +1 -1
  186. package/dist/admin/hooks/useVome.d.ts +2 -2
  187. package/dist/admin/lib/browser.js +1 -1
  188. package/dist/admin/lib/cn.js +1 -1
  189. package/dist/admin/lib/dialog-float.js +1 -1
  190. package/dist/admin/lib/export-excel.js +1 -1
  191. package/dist/admin/lib/import-excel.js +1 -1
  192. package/dist/admin/lib/json.js +1 -1
  193. package/dist/admin/lib/menu.js +1 -1
  194. package/dist/admin/lib/micro-locale.js +1 -1
  195. package/dist/admin/lib/micro-theme.js +1 -1
  196. package/dist/admin/lib/shell.js +1 -1
  197. package/dist/admin/lib/theme-color-options.js +1 -1
  198. package/dist/admin/lib/tree.js +1 -1
  199. package/dist/admin/lib/upload.js +1 -1
  200. package/dist/admin/lib/video-frame.js +1 -1
  201. package/dist/admin/router/create.js +1 -1
  202. package/dist/admin/router/guards.js +1 -1
  203. package/dist/admin/stores/app.d.ts +13 -1
  204. package/dist/admin/stores/tags.d.ts +14 -1
  205. package/dist/admin/stores/user.d.ts +48 -1
  206. package/dist/admin/styles/base.css +121 -121
  207. package/dist/admin/vendor/browser-image-compression.js +1144 -1144
  208. package/dist/admin/vendor/decimal-js.js +12 -12
  209. package/dist/admin/vendor/xlsx.js +263 -263
  210. package/dist/ai/index.js +1 -1
  211. package/dist/client/ai-model-registry.js +1 -1
  212. package/dist/client/base.js +1 -1
  213. package/dist/client/configure.js +1 -1
  214. package/dist/client/dict.js +1 -1
  215. package/dist/client/eps-payload.js +1 -1
  216. package/dist/client/eps.d.ts +7 -4
  217. package/dist/client/eps.js +1 -1
  218. package/dist/client/index.d.ts +1 -1
  219. package/dist/client/index.js +1 -1
  220. package/dist/client/locale-change.js +1 -1
  221. package/dist/client/locale-store.d.ts +45 -1
  222. package/dist/client/locale-store.js +1 -1
  223. package/dist/client/path.js +1 -1
  224. package/dist/client/service.d.ts +8 -0
  225. package/dist/client/service.js +1 -1
  226. package/dist/client/vite-admin.js +1 -1
  227. package/dist/client/vite-auto-import.js +1 -1
  228. package/dist/client/vite-micro-proxy.js +1 -1
  229. package/dist/client/vite-plugin-eps.d.ts +5 -0
  230. package/dist/client/vite-plugin-eps.js +1 -1
  231. package/dist/client/vite-resolve.js +1 -1
  232. package/dist/config/index.js +1 -1
  233. package/dist/core/index.d.ts +2 -2
  234. package/dist/index.js +1 -1
  235. package/dist/orm/entity-cache.d.ts +1 -1
  236. package/dist/orm/service.d.ts +5 -5
  237. package/dist/routing/context.d.ts +12 -2
  238. package/dist/routing/eps.d.ts +4 -1
  239. package/dist/routing/index.d.ts +2 -1
  240. package/dist/routing/metadata.d.ts +7 -3
  241. package/dist/server/index.d.ts +2 -2
  242. package/dist/server/index.js +1 -1
  243. package/dist/shared/datetime-picker.js +1 -1
  244. package/dist/shared/decimal.js +1 -1
  245. package/dist/shared/excel.js +1 -1
  246. package/dist/shared/index.js +1 -1
  247. package/dist/shared/locale-json.js +1 -1
  248. package/dist/shared/tree.js +1 -1
  249. package/dist/typings/routing/eps.d.ts +3 -1
  250. package/dist/typings/routing/meta.d.ts +15 -0
  251. package/package.json +1 -1
  252. package/typings/admin/base/auth.ts +24 -24
  253. package/typings/admin/base/permission.ts +19 -19
  254. package/typings/admin/comm/cascader.ts +7 -7
  255. package/typings/admin/comm/check-tree.ts +7 -7
  256. package/typings/admin/comm/crud.ts +427 -427
  257. package/typings/admin/comm/eps.ts +88 -86
  258. package/typings/admin/comm/request.ts +16 -16
  259. package/typings/admin/comm/service.ts +43 -43
  260. package/typings/admin/comm/upload.ts +39 -39
  261. package/typings/admin/theme.ts +10 -10
  262. package/typings/admin/vome/browser.ts +11 -11
  263. package/typings/admin/vome/module.ts +13 -13
  264. package/typings/admin/vome/tags.ts +9 -9
  265. package/typings/comm/crud.ts +83 -83
  266. package/typings/comm/page.ts +18 -18
  267. package/typings/comm/scan.ts +6 -6
  268. package/typings/common.ts +4 -4
  269. package/typings/context/data.ts +10 -10
  270. package/typings/ext/market-seat.ts +42 -42
  271. package/typings/ext/market.ts +55 -55
  272. package/typings/ext/module.ts +64 -64
  273. package/typings/ext/plugin.ts +38 -38
  274. package/typings/ioc/metadata.ts +34 -34
  275. package/typings/logger/config.ts +6 -6
  276. package/typings/routing/crud.ts +199 -199
  277. package/typings/routing/eps.ts +90 -88
  278. package/typings/routing/meta.ts +80 -63
  279. package/typings/routing/register.ts +21 -21
  280. package/typings/routing/scan-context.ts +7 -7
@@ -1,456 +1,456 @@
1
- <template>
2
- <div v-if="visible" class="vm-data-i18n-btn">
3
- <button
4
- type="button"
5
- class="vm-crud-toolbar__btn is-ghost"
6
- :disabled="disabled || submitting"
7
- @click="openDialog"
8
- >
9
- <i class="ri-translate-2" />
10
- {{ text || crudLabel('dataI18nTranslate', crud.dict.label) }}
11
- </button>
12
-
13
- <Dialog :open="open" @update:open="onOpenChange">
14
- <DialogContent
15
- shell="crud"
16
- :show-close-button="false"
17
- class="vm-data-i18n-btn__dialog is-fill"
18
- @pointer-down-outside="preventDialogOutsideClose"
19
- @focus-outside="preventDialogOutsideClose"
20
- @interact-outside="preventDialogOutsideClose"
21
- >
22
- <DialogHeader class="vm-crud-shell__head">
23
- <DialogTitle class="vm-crud-shell__title">
24
- {{ crudLabel('dataI18nTranslateTitle', crud.dict.label) }}
25
- </DialogTitle>
26
- <DialogClose class="vm-crud-shell__close">
27
- <i class="ri-close-line" />
28
- <span class="sr-only">Close</span>
29
- </DialogClose>
30
- </DialogHeader>
31
-
32
- <!-- 与 vm-upsert fill 同壳:表单 + 流式输出占满剩余高度 -->
33
- <div class="vm-crud-shell__body vm-data-i18n-btn__body">
34
- <div
35
- class="vm-data-i18n-btn__field"
36
- :style="{ gridColumn: `span ${fieldSpan}` }"
37
- >
38
- <Label class="vm-data-i18n-btn__label">
39
- {{ crudLabel('dataI18nTable', crud.dict.label) }}
40
- </Label>
41
- <Input
42
- class="vm-data-i18n-btn__control is-readonly"
43
- :model-value="tableName"
44
- disabled
45
- />
46
- </div>
47
-
48
- <div
49
- class="vm-data-i18n-btn__field"
50
- :style="{ gridColumn: `span ${fieldSpan}` }"
51
- >
52
- <Label class="vm-data-i18n-btn__label">
53
- {{ crudLabel('dataI18nLang', crud.dict.label) }}
54
- <span class="vm-data-i18n-btn__req">*</span>
55
- </Label>
56
- <vm-select
57
- v-model="form.langCode"
58
- class="vm-data-i18n-btn__control is-select"
59
- :options="langOptions"
60
- :refresh-on-change="false"
61
- width="100%"
62
- :placeholder="crudLabel('dataI18nPickLang', crud.dict.label)"
63
- />
64
- </div>
65
-
66
- <div
67
- class="vm-data-i18n-btn__field"
68
- :style="{ gridColumn: `span ${fieldSpan}` }"
69
- >
70
- <Label class="vm-data-i18n-btn__label">
71
- {{ crudLabel('dataI18nModel', crud.dict.label) }}
72
- <span class="vm-data-i18n-btn__req">*</span>
73
- </Label>
74
- <vm-select
75
- v-model="form.model"
76
- class="vm-data-i18n-btn__control is-select"
77
- :options="modelOptions"
78
- :refresh-on-change="false"
79
- width="100%"
80
- :placeholder="crudLabel('dataI18nPickModel', crud.dict.label)"
81
- />
82
- </div>
83
-
84
- <div
85
- class="vm-data-i18n-btn__field"
86
- :style="{ gridColumn: `span ${fieldSpan}` }"
87
- >
88
- <Label class="vm-data-i18n-btn__label">
89
- {{ crudLabel('dataI18nMode', crud.dict.label) }}
90
- <span class="vm-data-i18n-btn__req">*</span>
91
- </Label>
92
- <vm-select
93
- v-model="form.mode"
94
- class="vm-data-i18n-btn__control is-select"
95
- :options="modeOptions"
96
- :refresh-on-change="false"
97
- width="100%"
98
- />
99
- </div>
100
-
101
- <div class="vm-data-i18n-btn__stream">
102
- <vm-json-editor
103
- ref="StreamEditor"
104
- fill
105
- :disabled="submitting"
106
- :format-on-blur="false"
107
- :placeholder="crudLabel('jsonPlaceholder')"
108
- />
109
- </div>
110
- </div>
111
-
112
- <DialogFooter class="vm-crud-shell__foot">
113
- <button
114
- type="button"
115
- class="vm-crud-shell__btn is-ghost"
116
- :disabled="submitting"
117
- @click="open = false"
118
- >
119
- {{ crudLabel('close', crud.dict.label) }}
120
- </button>
121
- <button
122
- type="button"
123
- class="vm-crud-shell__btn is-primary"
124
- :disabled="submitting || !form.model || !form.langCode"
125
- @click="runTranslate"
126
- >
127
- {{
128
- submitting
129
- ? crudLabel('dataI18nTranslating', crud.dict.label)
130
- : crudLabel('dataI18nTranslate', crud.dict.label)
131
- }}
132
- </button>
133
- </DialogFooter>
134
- </DialogContent>
135
- </Dialog>
136
- </div>
137
- </template>
138
-
139
- <script setup lang="ts">
140
- import { computed, reactive, ref, watch } from 'vue'
141
- import { toast } from 'vue-sonner'
142
- import { isAiStreamResult } from '../api/client'
143
- import { preventDialogOutsideClose } from '../lib/dialog-float'
144
- import { useVome } from '../hooks/useVome'
145
- import { useCrud } from './useCrud'
146
- import VmSelect from './components/vm-select.vue'
147
- import VmJsonEditor from './components/vm-json-editor.vue'
148
- import { resolveFormSpan, useFormCols } from './span'
149
- import { crudFormat, crudLabel } from './style'
150
- import {
151
- canUseDataI18n,
152
- dataPackFromService,
153
- loadEnabledLangOptions,
154
- } from './data-i18n'
155
-
156
- defineOptions({ name: 'vm-data-i18n-btn' })
157
-
158
- const props = withDefaults(
159
- defineProps<{
160
- disabled?: boolean
161
- text?: string
162
- tableName?: string
163
- }>(),
164
- {
165
- disabled: false,
166
- },
167
- )
168
-
169
- const crud = useCrud()
170
- const { service } = useVome()
171
- const open = ref(false)
172
- const submitting = ref(false)
173
- const StreamEditor = ref<{
174
- beginStream: () => void
175
- endStream: () => void
176
- setText: (v: unknown) => void
177
- } | null>(null)
178
- const langOptions = ref<Array<{ label: string; value: string }>>([])
179
- const modelOptions = ref<Array<{ label: string; value: string }>>([])
180
- const formCols = useFormCols()
181
- const fieldSpan = computed(() => resolveFormSpan(12, formCols.value))
182
-
183
- const form = reactive({
184
- langCode: 'en-US',
185
- model: '',
186
- mode: 'incremental' as 'incremental' | 'full',
187
- })
188
-
189
- const modeOptions = computed(() => [
190
- {
191
- label: crudLabel('dataI18nModeIncremental', crud.dict.label),
192
- value: 'incremental',
193
- },
194
- {
195
- label: crudLabel('dataI18nModeFull', crud.dict.label),
196
- value: 'full',
197
- },
198
- ])
199
-
200
- const meta = computed(() =>
201
- canUseDataI18n(crud.service, service, props.tableName),
202
- )
203
-
204
- const tableName = computed(() => meta.value.tableName)
205
- const visible = computed(() => meta.value.ok)
206
-
207
- watch(open, (v) => {
208
- if (v) {
209
- StreamEditor.value?.endStream()
210
- StreamEditor.value?.setText('')
211
- void loadDialogData()
212
- }
213
- })
214
-
215
- function onOpenChange(v: boolean) {
216
- if (submitting.value && !v) return
217
- open.value = v
218
- }
219
-
220
- function openDialog() {
221
- if (props.disabled || submitting.value) return
222
- open.value = true
223
- }
224
-
225
- async function loadDialogData() {
226
- await Promise.all([loadLangs(), loadModels()])
227
- const viewing = crud.dataI18nLangCode?.value
228
- if (viewing && langOptions.value.some((o) => o.value === viewing)) {
229
- form.langCode = viewing
230
- }
231
- }
232
-
233
- async function loadLangs() {
234
- const rows = await loadEnabledLangOptions(service, { excludeSource: true })
235
- langOptions.value = rows.map((r) => ({ label: r.label, value: r.value }))
236
- if (!langOptions.value.some((o) => o.value === form.langCode)) {
237
- form.langCode = langOptions.value[0]?.value || 'en-US'
238
- }
239
- }
240
-
241
- async function loadModels() {
242
- const dataPack = dataPackFromService(service)
243
- if (!dataPack?.chatModels) {
244
- modelOptions.value = []
245
- form.model = ''
246
- toast.error(crudLabel('noChatModels'))
247
- return
248
- }
249
- try {
250
- const list = (await dataPack.chatModels()) as Array<{ code?: string }>
251
- modelOptions.value = (list || [])
252
- .filter((m) => m.code)
253
- .map((m) => ({ label: String(m.code), value: String(m.code) }))
254
- if (!modelOptions.value.some((o) => o.value === form.model)) {
255
- form.model = modelOptions.value[0]?.value || ''
256
- }
257
- } catch {
258
- modelOptions.value = []
259
- form.model = ''
260
- }
261
- }
262
-
263
- async function runTranslate() {
264
- if (submitting.value || props.disabled) return
265
- const table = tableName.value
266
- if (!table) {
267
- toast.error(crudLabel('dataI18nNoTable', crud.dict.label))
268
- return
269
- }
270
- if (!form.langCode) {
271
- toast.error(crudLabel('dataI18nNeedLang', crud.dict.label))
272
- return
273
- }
274
- if (!form.model) {
275
- toast.error(crudLabel('dataI18nNeedModel', crud.dict.label))
276
- return
277
- }
278
- const dataPack = dataPackFromService(service)
279
- if (!dataPack?.translateTable) {
280
- toast.error(crudLabel('dataI18nNoService', crud.dict.label))
281
- return
282
- }
283
- submitting.value = true
284
- StreamEditor.value?.beginStream()
285
- StreamEditor.value?.setText('')
286
- try {
287
- const langName =
288
- langOptions.value.find((o) => o.value === form.langCode)?.label ||
289
- form.langCode
290
- const out = await dataPack.translateTable({
291
- tableName: table,
292
- langCode: form.langCode,
293
- langName,
294
- mode: form.mode,
295
- model: form.model,
296
- })
297
- let skipped = false
298
- let message = ''
299
- let rowCount = 0
300
- let fullText = ''
301
- if (isAiStreamResult(out)) {
302
- for await (const chunk of out.stream) {
303
- if (chunk.type === 'error') {
304
- throw new Error(
305
- chunk.error?.message || crudLabel('dataI18nFail', crud.dict.label),
306
- )
307
- }
308
- if (chunk.type === 'delta') {
309
- const next =
310
- typeof chunk.data?.fullText === 'string'
311
- ? chunk.data.fullText
312
- : fullText + (chunk.text || '')
313
- fullText = next
314
- StreamEditor.value?.setText(fullText)
315
- }
316
- if (chunk.type === 'done') {
317
- skipped = Boolean(chunk.data?.skipped)
318
- message = String(chunk.data?.message || '')
319
- rowCount = Number(chunk.data?.rowCount ?? 0)
320
- if (typeof chunk.data?.fullText === 'string') {
321
- fullText = chunk.data.fullText
322
- StreamEditor.value?.setText(fullText)
323
- }
324
- }
325
- }
326
- } else {
327
- const res = out as {
328
- skipped?: boolean
329
- message?: string
330
- rowCount?: number
331
- }
332
- skipped = Boolean(res?.skipped)
333
- message = String(res?.message || '')
334
- rowCount = Number(res?.rowCount ?? 0)
335
- }
336
- StreamEditor.value?.endStream()
337
- if (skipped) {
338
- toast.message(message || crudLabel('dataI18nSkipped', crud.dict.label))
339
- } else {
340
- toast.success(
341
- crudFormat(crudLabel('dataI18nDone', crud.dict.label), rowCount),
342
- )
343
- }
344
- if (crud.dataI18nLangCode) {
345
- crud.dataI18nLangCode.value = form.langCode
346
- }
347
- if (crud.trashMode.value !== 'dataI18n') {
348
- crud.trashMode.value = 'dataI18n'
349
- }
350
- await crud.refresh()
351
- } catch (e) {
352
- StreamEditor.value?.endStream()
353
- toast.error(
354
- e instanceof Error
355
- ? e.message
356
- : crudLabel('dataI18nFail', crud.dict.label),
357
- )
358
- } finally {
359
- submitting.value = false
360
- }
361
- }
362
- </script>
363
-
364
- <style lang="scss" scoped>
365
- .vm-data-i18n-btn {
366
- display: inline-flex;
367
- }
368
-
369
- .vm-crud-toolbar__btn {
370
- display: inline-flex;
371
- height: 36px;
372
- align-items: center;
373
- gap: 4px;
374
- padding: 0 14px;
375
- border: none;
376
- border-radius: 12px;
377
- font-size: 13px;
378
- font-weight: 600;
379
- cursor: pointer;
380
- background: var(--muted);
381
- color: var(--foreground);
382
- transition: background 0.15s ease, opacity 0.15s ease;
383
-
384
- &:disabled {
385
- cursor: not-allowed;
386
- opacity: 0.45;
387
- }
388
-
389
- &:hover:not(:disabled) {
390
- background: color-mix(in srgb, var(--muted) 70%, var(--foreground) 8%);
391
- }
392
- }
393
-
394
- .vm-data-i18n-btn__body {
395
- /* 覆盖 shell body 的 flex-column:上表单网格 + 下流式输出 1fr */
396
- display: grid;
397
- grid-template-columns: repeat(24, minmax(0, 1fr));
398
- grid-template-rows: auto auto minmax(0, 1fr);
399
- gap: 12px 16px;
400
- align-content: stretch;
401
- flex: 1 1 auto;
402
- min-height: 0;
403
- padding: 18px 22px 16px;
404
-
405
- @media (max-width: 768px) {
406
- grid-template-columns: repeat(12, minmax(0, 1fr));
407
- grid-template-rows: auto auto auto auto minmax(0, 1fr);
408
- gap: 10px;
409
- padding: 12px;
410
- }
411
- }
412
-
413
- .vm-data-i18n-btn__label {
414
- display: inline-flex;
415
- align-items: center;
416
- gap: 2px;
417
- margin-bottom: 6px;
418
- font-size: 12px;
419
- color: var(--muted-foreground);
420
- }
421
-
422
- .vm-data-i18n-btn__req {
423
- color: var(--destructive);
424
- }
425
-
426
- .vm-data-i18n-btn__field {
427
- min-width: 0;
428
- }
429
-
430
- .vm-data-i18n-btn__stream {
431
- grid-column: 1 / -1;
432
- display: flex;
433
- flex-direction: column;
434
- min-width: 0;
435
- min-height: 0;
436
- }
437
-
438
- .vm-data-i18n-btn__control {
439
- width: 100%;
440
- height: var(--vm-control-height) !important;
441
- min-height: var(--vm-control-height);
442
- border-radius: var(--vm-control-radius) !important;
443
- font-size: var(--vm-control-font-size) !important;
444
-
445
- &.is-readonly {
446
- background: var(--muted) !important;
447
- color: var(--muted-foreground) !important;
448
- opacity: 1 !important;
449
- cursor: default;
450
- }
451
-
452
- &.is-select {
453
- background: var(--muted) !important;
454
- }
455
- }
456
- </style>
1
+ <template>
2
+ <div v-if="visible" class="vm-data-i18n-btn">
3
+ <button
4
+ type="button"
5
+ class="vm-crud-toolbar__btn is-ghost"
6
+ :disabled="disabled || submitting"
7
+ @click="openDialog"
8
+ >
9
+ <i class="ri-translate-2" />
10
+ {{ text || crudLabel('dataI18nTranslate', crud.dict.label) }}
11
+ </button>
12
+
13
+ <Dialog :open="open" @update:open="onOpenChange">
14
+ <DialogContent
15
+ shell="crud"
16
+ :show-close-button="false"
17
+ class="vm-data-i18n-btn__dialog is-fill"
18
+ @pointer-down-outside="preventDialogOutsideClose"
19
+ @focus-outside="preventDialogOutsideClose"
20
+ @interact-outside="preventDialogOutsideClose"
21
+ >
22
+ <DialogHeader class="vm-crud-shell__head">
23
+ <DialogTitle class="vm-crud-shell__title">
24
+ {{ crudLabel('dataI18nTranslateTitle', crud.dict.label) }}
25
+ </DialogTitle>
26
+ <DialogClose class="vm-crud-shell__close">
27
+ <i class="ri-close-line" />
28
+ <span class="sr-only">Close</span>
29
+ </DialogClose>
30
+ </DialogHeader>
31
+
32
+ <!-- 与 vm-upsert fill 同壳:表单 + 流式输出占满剩余高度 -->
33
+ <div class="vm-crud-shell__body vm-data-i18n-btn__body">
34
+ <div
35
+ class="vm-data-i18n-btn__field"
36
+ :style="{ gridColumn: `span ${fieldSpan}` }"
37
+ >
38
+ <Label class="vm-data-i18n-btn__label">
39
+ {{ crudLabel('dataI18nTable', crud.dict.label) }}
40
+ </Label>
41
+ <Input
42
+ class="vm-data-i18n-btn__control is-readonly"
43
+ :model-value="tableName"
44
+ disabled
45
+ />
46
+ </div>
47
+
48
+ <div
49
+ class="vm-data-i18n-btn__field"
50
+ :style="{ gridColumn: `span ${fieldSpan}` }"
51
+ >
52
+ <Label class="vm-data-i18n-btn__label">
53
+ {{ crudLabel('dataI18nLang', crud.dict.label) }}
54
+ <span class="vm-data-i18n-btn__req">*</span>
55
+ </Label>
56
+ <vm-select
57
+ v-model="form.langCode"
58
+ class="vm-data-i18n-btn__control is-select"
59
+ :options="langOptions"
60
+ :refresh-on-change="false"
61
+ width="100%"
62
+ :placeholder="crudLabel('dataI18nPickLang', crud.dict.label)"
63
+ />
64
+ </div>
65
+
66
+ <div
67
+ class="vm-data-i18n-btn__field"
68
+ :style="{ gridColumn: `span ${fieldSpan}` }"
69
+ >
70
+ <Label class="vm-data-i18n-btn__label">
71
+ {{ crudLabel('dataI18nModel', crud.dict.label) }}
72
+ <span class="vm-data-i18n-btn__req">*</span>
73
+ </Label>
74
+ <vm-select
75
+ v-model="form.model"
76
+ class="vm-data-i18n-btn__control is-select"
77
+ :options="modelOptions"
78
+ :refresh-on-change="false"
79
+ width="100%"
80
+ :placeholder="crudLabel('dataI18nPickModel', crud.dict.label)"
81
+ />
82
+ </div>
83
+
84
+ <div
85
+ class="vm-data-i18n-btn__field"
86
+ :style="{ gridColumn: `span ${fieldSpan}` }"
87
+ >
88
+ <Label class="vm-data-i18n-btn__label">
89
+ {{ crudLabel('dataI18nMode', crud.dict.label) }}
90
+ <span class="vm-data-i18n-btn__req">*</span>
91
+ </Label>
92
+ <vm-select
93
+ v-model="form.mode"
94
+ class="vm-data-i18n-btn__control is-select"
95
+ :options="modeOptions"
96
+ :refresh-on-change="false"
97
+ width="100%"
98
+ />
99
+ </div>
100
+
101
+ <div class="vm-data-i18n-btn__stream">
102
+ <vm-json-editor
103
+ ref="StreamEditor"
104
+ fill
105
+ :disabled="submitting"
106
+ :format-on-blur="false"
107
+ :placeholder="crudLabel('jsonPlaceholder')"
108
+ />
109
+ </div>
110
+ </div>
111
+
112
+ <DialogFooter class="vm-crud-shell__foot">
113
+ <button
114
+ type="button"
115
+ class="vm-crud-shell__btn is-ghost"
116
+ :disabled="submitting"
117
+ @click="open = false"
118
+ >
119
+ {{ crudLabel('close', crud.dict.label) }}
120
+ </button>
121
+ <button
122
+ type="button"
123
+ class="vm-crud-shell__btn is-primary"
124
+ :disabled="submitting || !form.model || !form.langCode"
125
+ @click="runTranslate"
126
+ >
127
+ {{
128
+ submitting
129
+ ? crudLabel('dataI18nTranslating', crud.dict.label)
130
+ : crudLabel('dataI18nTranslate', crud.dict.label)
131
+ }}
132
+ </button>
133
+ </DialogFooter>
134
+ </DialogContent>
135
+ </Dialog>
136
+ </div>
137
+ </template>
138
+
139
+ <script setup lang="ts">
140
+ import { computed, reactive, ref, watch } from 'vue'
141
+ import { toast } from 'vue-sonner'
142
+ import { isAiStreamResult } from '../api/client'
143
+ import { preventDialogOutsideClose } from '../lib/dialog-float'
144
+ import { useVome } from '../hooks/useVome'
145
+ import { useCrud } from './useCrud'
146
+ import VmSelect from './components/vm-select.vue'
147
+ import VmJsonEditor from './components/vm-json-editor.vue'
148
+ import { resolveFormSpan, useFormCols } from './span'
149
+ import { crudFormat, crudLabel } from './style'
150
+ import {
151
+ canUseDataI18n,
152
+ dataPackFromService,
153
+ loadEnabledLangOptions,
154
+ } from './data-i18n'
155
+
156
+ defineOptions({ name: 'vm-data-i18n-btn' })
157
+
158
+ const props = withDefaults(
159
+ defineProps<{
160
+ disabled?: boolean
161
+ text?: string
162
+ tableName?: string
163
+ }>(),
164
+ {
165
+ disabled: false,
166
+ },
167
+ )
168
+
169
+ const crud = useCrud()
170
+ const { service } = useVome()
171
+ const open = ref(false)
172
+ const submitting = ref(false)
173
+ const StreamEditor = ref<{
174
+ beginStream: () => void
175
+ endStream: () => void
176
+ setText: (v: unknown) => void
177
+ } | null>(null)
178
+ const langOptions = ref<Array<{ label: string; value: string }>>([])
179
+ const modelOptions = ref<Array<{ label: string; value: string }>>([])
180
+ const formCols = useFormCols()
181
+ const fieldSpan = computed(() => resolveFormSpan(12, formCols.value))
182
+
183
+ const form = reactive({
184
+ langCode: 'en-US',
185
+ model: '',
186
+ mode: 'incremental' as 'incremental' | 'full',
187
+ })
188
+
189
+ const modeOptions = computed(() => [
190
+ {
191
+ label: crudLabel('dataI18nModeIncremental', crud.dict.label),
192
+ value: 'incremental',
193
+ },
194
+ {
195
+ label: crudLabel('dataI18nModeFull', crud.dict.label),
196
+ value: 'full',
197
+ },
198
+ ])
199
+
200
+ const meta = computed(() =>
201
+ canUseDataI18n(crud.service, service, props.tableName),
202
+ )
203
+
204
+ const tableName = computed(() => meta.value.tableName)
205
+ const visible = computed(() => meta.value.ok)
206
+
207
+ watch(open, (v) => {
208
+ if (v) {
209
+ StreamEditor.value?.endStream()
210
+ StreamEditor.value?.setText('')
211
+ void loadDialogData()
212
+ }
213
+ })
214
+
215
+ function onOpenChange(v: boolean) {
216
+ if (submitting.value && !v) return
217
+ open.value = v
218
+ }
219
+
220
+ function openDialog() {
221
+ if (props.disabled || submitting.value) return
222
+ open.value = true
223
+ }
224
+
225
+ async function loadDialogData() {
226
+ await Promise.all([loadLangs(), loadModels()])
227
+ const viewing = crud.dataI18nLangCode?.value
228
+ if (viewing && langOptions.value.some((o) => o.value === viewing)) {
229
+ form.langCode = viewing
230
+ }
231
+ }
232
+
233
+ async function loadLangs() {
234
+ const rows = await loadEnabledLangOptions(service, { excludeSource: true })
235
+ langOptions.value = rows.map((r) => ({ label: r.label, value: r.value }))
236
+ if (!langOptions.value.some((o) => o.value === form.langCode)) {
237
+ form.langCode = langOptions.value[0]?.value || 'en-US'
238
+ }
239
+ }
240
+
241
+ async function loadModels() {
242
+ const dataPack = dataPackFromService(service)
243
+ if (!dataPack?.chatModels) {
244
+ modelOptions.value = []
245
+ form.model = ''
246
+ toast.error(crudLabel('noChatModels'))
247
+ return
248
+ }
249
+ try {
250
+ const list = (await dataPack.chatModels()) as Array<{ code?: string }>
251
+ modelOptions.value = (list || [])
252
+ .filter((m) => m.code)
253
+ .map((m) => ({ label: String(m.code), value: String(m.code) }))
254
+ if (!modelOptions.value.some((o) => o.value === form.model)) {
255
+ form.model = modelOptions.value[0]?.value || ''
256
+ }
257
+ } catch {
258
+ modelOptions.value = []
259
+ form.model = ''
260
+ }
261
+ }
262
+
263
+ async function runTranslate() {
264
+ if (submitting.value || props.disabled) return
265
+ const table = tableName.value
266
+ if (!table) {
267
+ toast.error(crudLabel('dataI18nNoTable', crud.dict.label))
268
+ return
269
+ }
270
+ if (!form.langCode) {
271
+ toast.error(crudLabel('dataI18nNeedLang', crud.dict.label))
272
+ return
273
+ }
274
+ if (!form.model) {
275
+ toast.error(crudLabel('dataI18nNeedModel', crud.dict.label))
276
+ return
277
+ }
278
+ const dataPack = dataPackFromService(service)
279
+ if (!dataPack?.translateTable) {
280
+ toast.error(crudLabel('dataI18nNoService', crud.dict.label))
281
+ return
282
+ }
283
+ submitting.value = true
284
+ StreamEditor.value?.beginStream()
285
+ StreamEditor.value?.setText('')
286
+ try {
287
+ const langName =
288
+ langOptions.value.find((o) => o.value === form.langCode)?.label ||
289
+ form.langCode
290
+ const out = await dataPack.translateTable({
291
+ tableName: table,
292
+ langCode: form.langCode,
293
+ langName,
294
+ mode: form.mode,
295
+ model: form.model,
296
+ })
297
+ let skipped = false
298
+ let message = ''
299
+ let rowCount = 0
300
+ let fullText = ''
301
+ if (isAiStreamResult(out)) {
302
+ for await (const chunk of out.stream) {
303
+ if (chunk.type === 'error') {
304
+ throw new Error(
305
+ chunk.error?.message || crudLabel('dataI18nFail', crud.dict.label),
306
+ )
307
+ }
308
+ if (chunk.type === 'delta') {
309
+ const next =
310
+ typeof chunk.data?.fullText === 'string'
311
+ ? chunk.data.fullText
312
+ : fullText + (chunk.text || '')
313
+ fullText = next
314
+ StreamEditor.value?.setText(fullText)
315
+ }
316
+ if (chunk.type === 'done') {
317
+ skipped = Boolean(chunk.data?.skipped)
318
+ message = String(chunk.data?.message || '')
319
+ rowCount = Number(chunk.data?.rowCount ?? 0)
320
+ if (typeof chunk.data?.fullText === 'string') {
321
+ fullText = chunk.data.fullText
322
+ StreamEditor.value?.setText(fullText)
323
+ }
324
+ }
325
+ }
326
+ } else {
327
+ const res = out as {
328
+ skipped?: boolean
329
+ message?: string
330
+ rowCount?: number
331
+ }
332
+ skipped = Boolean(res?.skipped)
333
+ message = String(res?.message || '')
334
+ rowCount = Number(res?.rowCount ?? 0)
335
+ }
336
+ StreamEditor.value?.endStream()
337
+ if (skipped) {
338
+ toast.message(message || crudLabel('dataI18nSkipped', crud.dict.label))
339
+ } else {
340
+ toast.success(
341
+ crudFormat(crudLabel('dataI18nDone', crud.dict.label), rowCount),
342
+ )
343
+ }
344
+ if (crud.dataI18nLangCode) {
345
+ crud.dataI18nLangCode.value = form.langCode
346
+ }
347
+ if (crud.trashMode.value !== 'dataI18n') {
348
+ crud.trashMode.value = 'dataI18n'
349
+ }
350
+ await crud.refresh()
351
+ } catch (e) {
352
+ StreamEditor.value?.endStream()
353
+ toast.error(
354
+ e instanceof Error
355
+ ? e.message
356
+ : crudLabel('dataI18nFail', crud.dict.label),
357
+ )
358
+ } finally {
359
+ submitting.value = false
360
+ }
361
+ }
362
+ </script>
363
+
364
+ <style lang="scss" scoped>
365
+ .vm-data-i18n-btn {
366
+ display: inline-flex;
367
+ }
368
+
369
+ .vm-crud-toolbar__btn {
370
+ display: inline-flex;
371
+ height: 36px;
372
+ align-items: center;
373
+ gap: 4px;
374
+ padding: 0 14px;
375
+ border: none;
376
+ border-radius: 12px;
377
+ font-size: 13px;
378
+ font-weight: 600;
379
+ cursor: pointer;
380
+ background: var(--muted);
381
+ color: var(--foreground);
382
+ transition: background 0.15s ease, opacity 0.15s ease;
383
+
384
+ &:disabled {
385
+ cursor: not-allowed;
386
+ opacity: 0.45;
387
+ }
388
+
389
+ &:hover:not(:disabled) {
390
+ background: color-mix(in srgb, var(--muted) 70%, var(--foreground) 8%);
391
+ }
392
+ }
393
+
394
+ .vm-data-i18n-btn__body {
395
+ /* 覆盖 shell body 的 flex-column:上表单网格 + 下流式输出 1fr */
396
+ display: grid;
397
+ grid-template-columns: repeat(24, minmax(0, 1fr));
398
+ grid-template-rows: auto auto minmax(0, 1fr);
399
+ gap: 12px 16px;
400
+ align-content: stretch;
401
+ flex: 1 1 auto;
402
+ min-height: 0;
403
+ padding: 18px 22px 16px;
404
+
405
+ @media (max-width: 768px) {
406
+ grid-template-columns: repeat(12, minmax(0, 1fr));
407
+ grid-template-rows: auto auto auto auto minmax(0, 1fr);
408
+ gap: 10px;
409
+ padding: 12px;
410
+ }
411
+ }
412
+
413
+ .vm-data-i18n-btn__label {
414
+ display: inline-flex;
415
+ align-items: center;
416
+ gap: 2px;
417
+ margin-bottom: 6px;
418
+ font-size: 12px;
419
+ color: var(--muted-foreground);
420
+ }
421
+
422
+ .vm-data-i18n-btn__req {
423
+ color: var(--destructive);
424
+ }
425
+
426
+ .vm-data-i18n-btn__field {
427
+ min-width: 0;
428
+ }
429
+
430
+ .vm-data-i18n-btn__stream {
431
+ grid-column: 1 / -1;
432
+ display: flex;
433
+ flex-direction: column;
434
+ min-width: 0;
435
+ min-height: 0;
436
+ }
437
+
438
+ .vm-data-i18n-btn__control {
439
+ width: 100%;
440
+ height: var(--vm-control-height) !important;
441
+ min-height: var(--vm-control-height);
442
+ border-radius: var(--vm-control-radius) !important;
443
+ font-size: var(--vm-control-font-size) !important;
444
+
445
+ &.is-readonly {
446
+ background: var(--muted) !important;
447
+ color: var(--muted-foreground) !important;
448
+ opacity: 1 !important;
449
+ cursor: default;
450
+ }
451
+
452
+ &.is-select {
453
+ background: var(--muted) !important;
454
+ }
455
+ }
456
+ </style>