@wycm9527/morganite 0.0.0-stage → 0.3.3

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 (61) hide show
  1. package/CHANGELOG.md +165 -0
  2. package/LICENSE +33 -0
  3. package/README.md +58 -2
  4. package/bridge/base.css +138 -0
  5. package/bridge/echarts.d.ts +44 -0
  6. package/bridge/echarts.js +217 -0
  7. package/bridge/element-plus.css +785 -0
  8. package/bridge/fonts.css +124 -0
  9. package/bridge/icon-merge.d.ts +5 -0
  10. package/bridge/icon-merge.js +103 -0
  11. package/bridge/iconpark.config.ts +75 -0
  12. package/bridge/iconpark.css +29 -0
  13. package/bridge/react/ConfirmBar.tsx +43 -0
  14. package/bridge/react/EChart.tsx +45 -0
  15. package/bridge/react/PageHeader.tsx +24 -0
  16. package/bridge/react/SectionHeader.tsx +17 -0
  17. package/bridge/react/StatCard.tsx +33 -0
  18. package/bridge/react/TableSkeleton.tsx +11 -0
  19. package/bridge/react/TrendChart.tsx +8 -0
  20. package/bridge/recipes.css +444 -0
  21. package/bridge/shadcn-globals.css +6 -0
  22. package/bridge/shadcn.css +238 -0
  23. package/bridge/vue/ConfirmBar.vue +23 -0
  24. package/bridge/vue/EChart.vue +43 -0
  25. package/bridge/vue/KitchenSink.vue +507 -0
  26. package/bridge/vue/PageHeader.vue +23 -0
  27. package/bridge/vue/SectionHeader.vue +15 -0
  28. package/bridge/vue/StatCard.vue +33 -0
  29. package/bridge/vue/TableSkeleton.vue +14 -0
  30. package/bridge/vue/TrendChart.vue +11 -0
  31. package/bridge/vue/confirm.js +18 -0
  32. package/bridge/vue/inlineConfirm.js +21 -0
  33. package/design-system/AUDIT.md +277 -0
  34. package/design-system/DESIGN.md +176 -0
  35. package/design-system/contrast.md +71 -0
  36. package/design-system/preview/drawer.html +38 -0
  37. package/design-system/preview/form.html +40 -0
  38. package/design-system/preview/index.html +21 -0
  39. package/design-system/preview/preview.css +190 -0
  40. package/design-system/preview/preview.local.css +17 -0
  41. package/design-system/preview/shell.html +50 -0
  42. package/design-system/scope-map.json +4 -0
  43. package/design-system/style-dictionary.config.mjs +172 -0
  44. package/design-system/theme-map.json +16 -0
  45. package/design-system/themes/dark/THEME.md +29 -0
  46. package/design-system/themes/dark/tokens/semantic.tokens.json +354 -0
  47. package/design-system/token-board.html +137 -0
  48. package/design-system/tokens/primitives.tokens.json +1898 -0
  49. package/design-system/tokens/semantic.tokens.json +1080 -0
  50. package/design-system.json +217 -0
  51. package/migration/roles.json +743 -0
  52. package/package.json +82 -4
  53. package/templates/AGENTS.md +13 -0
  54. package/templates/KitchenSink.tsx +179 -0
  55. package/templates/accept.config.mjs +28 -0
  56. package/templates/entry-css.css +6 -0
  57. package/templates/entry-element-plus.css +9 -0
  58. package/templates/entry-shadcn.css +7 -0
  59. package/templates/notes-css.md +4 -0
  60. package/templates/notes-element-plus.md +7 -0
  61. package/templates/notes-shadcn.md +7 -0
@@ -0,0 +1,507 @@
1
+ <script setup>
2
+ // 组件走查页(种子配方组件,可直接嵌入任何 Element Plus 项目):把 Element Plus 的全部组件铺在一页,供桥接覆盖率扫描(默认 / hover / focus / 选中 / 禁用 / 出错)。
3
+ // 挂法:路由 `{ path: 'kitchen', component: () => import('@wycm9527/morganite/vue/KitchenSink.vue') }`;项目有验收脚本时(如 citrine-accept components,KITCHEN 填 '#/kitchen')可对页内 600+ 个交互元素做 hover / focus 走查。
4
+ // 依赖:Element Plus 全局注册(app.use(ElementPlus));不是业务页面,不进侧栏;`data-ks-open` 标记的浮层由扫描脚本逐个点开,`data-ks-modal` 标记的弹层逐个触发。
5
+ // `data-ks-ignore` 标记的元素承载用户数据颜色(取色器、图片),不参与颜色规则判定。
6
+ import { ref, reactive, onMounted, h } from 'vue'
7
+ import { ElMessage, ElNotification } from 'element-plus'
8
+ import { confirmBox } from './confirm.js'
9
+ import PageHeader from './PageHeader.vue'
10
+ import SectionHeader from './SectionHeader.vue'
11
+
12
+ const input = ref('示例文本')
13
+ const num = ref(3)
14
+ const textarea = ref('多行文本\n第二行')
15
+ const tags = ref(['办公设备', '耗材'])
16
+ const mention = ref('@王芳 请复核')
17
+ const select = ref('pending')
18
+ const multi = ref(['pending', 'done'])
19
+ const selectV2 = ref('a2')
20
+ const cascader = ref(['sh', 'pd'])
21
+ const treeSelect = ref('n2')
22
+ const date = ref(new Date(2026, 8, 10))
23
+ const range = ref([new Date(2026, 8, 1), new Date(2026, 8, 10)])
24
+ const datetime = ref(new Date(2026, 8, 10, 9, 30))
25
+ const time = ref(new Date(2026, 8, 10, 9, 30))
26
+ const timeSelect = ref('09:30')
27
+ const on = ref(true)
28
+ const off = ref(false)
29
+ const checks = ref(['a'])
30
+ const checkBtns = ref(['b'])
31
+ const radio = ref('a')
32
+ const radioBtn = ref('week')
33
+ const period = ref('week')
34
+ const rate = ref(3)
35
+ const slider = ref(40)
36
+ const sliderRange = ref([20, 60])
37
+ const color = ref('') // 取色器初值留空:颜色字面量会被 guard / status 记为 Drift,而这只是演示数据
38
+ const transfer = ref([1, 3])
39
+ const segmented = ref('列表')
40
+ const collapse = ref(['1'])
41
+ const tab = ref('a')
42
+ const tabCard = ref('c1')
43
+ const tabBorder = ref('b1')
44
+ const tabLeft = ref('l1')
45
+ const treeRef = ref()
46
+ const tableRef = ref()
47
+ const page = ref(3)
48
+ const pageSize = ref(20)
49
+ const checkTag = ref(true)
50
+ const dialog = ref(false)
51
+ const drawer = ref(false)
52
+ const popoverVisible = ref(true)
53
+ const tooltipVisible = ref(true)
54
+
55
+ const statusOptions = [
56
+ { value: 'pending', label: '待审批' },
57
+ { value: 'doing', label: '采购中' },
58
+ { value: 'done', label: '已完成' },
59
+ { value: 'off', label: '已停用', disabled: true }
60
+ ]
61
+ const v2Options = Array.from({ length: 40 }, (_, i) => ({ value: `a${i}`, label: `选项 ${i + 1}` }))
62
+ const cascaderOptions = [
63
+ { value: 'sh', label: '上海', children: [{ value: 'pd', label: '浦东新区' }, { value: 'xh', label: '徐汇区' }] },
64
+ { value: 'hz', label: '杭州', children: [{ value: 'xs', label: '西湖区' }, { value: 'bj', label: '滨江区', disabled: true }] }
65
+ ]
66
+ const treeData = [
67
+ { id: 'n1', label: '华东区', children: [{ id: 'n2', label: '上海' }, { id: 'n3', label: '杭州' }] },
68
+ { id: 'n4', label: '华南区', children: [{ id: 'n5', label: '深圳', disabled: true }] }
69
+ ]
70
+ const transferData = Array.from({ length: 6 }, (_, i) => ({ key: i, label: `供应商 ${i + 1}`, disabled: i === 4 }))
71
+ const rows = [
72
+ { id: 'CG-20260910-0001', merchant: '蓝海电子', amount: 156.7, status: 'done', statusText: '已完成' },
73
+ { id: 'CG-20260910-0002', merchant: '云帆物流', amount: 47.8, status: 'pending', statusText: '待审批' },
74
+ { id: 'CG-20260910-0003', merchant: '恒信办公', amount: 1225.9, status: 'cancel', statusText: '已取消' },
75
+ { id: 'CG-20260910-0004', merchant: '星辰科技', amount: 88, status: 'doing', statusText: '采购中' }
76
+ ]
77
+ const form = reactive({ name: '', phone: '138' })
78
+ const rules = { name: [{ required: true, message: '请输入名称', trigger: 'blur' }], phone: [{ min: 11, message: '手机号需 11 位', trigger: 'blur' }] }
79
+ const formRef = ref()
80
+ const querySearch = (q, cb) => cb(['蓝海电子', '云帆物流', '恒信办公'].filter((s) => s.includes(q)).map((value) => ({ value })))
81
+ const fileList = ref([{ name: '营业执照.jpg', url: '#' }, { name: '门头照.jpg', url: '#', status: 'fail' }])
82
+ const statusClass = (s) => ({ done: 'success', pending: 'warning', cancel: 'error', doing: 'info' })[s]
83
+
84
+ function openMessage() { ElMessage({ message: '已保存', type: 'success', duration: 0, showClose: true }) }
85
+ function openMessageWarn() { ElMessage({ message: '库存不足', type: 'warning', duration: 0, showClose: true }) }
86
+ function openMessageErr() { ElMessage({ message: '保存失败', type: 'error', duration: 0, showClose: true }) }
87
+ function openNotification() { ElNotification({ title: '新申请', message: 'CG-20260910-0005 已提交', type: 'success', duration: 0 }) }
88
+ function openMessageBox() { confirmBox('确认删除供应商「恒信办公」?此操作不可撤销。', '删除确认', { type: 'warning', confirmButtonText: '删除', cancelButtonText: '取消', confirmButtonClass: 'el-button--danger' }).catch(() => {}) }
89
+
90
+ onMounted(() => {
91
+ formRef.value?.validate(() => {})
92
+ tableRef.value?.setCurrentRow(rows[1])
93
+ tableRef.value?.toggleRowSelection(rows[0], true)
94
+ })
95
+ </script>
96
+
97
+ <template>
98
+ <div class="ks-page">
99
+ <PageHeader class="head" title="组件走查" :subtitle="`Element Plus ${'2.14'} 全部组件的静息 / 悬停 / 聚焦 / 选中 / 禁用 / 出错状态,供桥接覆盖率扫描;不是业务页面。`" />
100
+
101
+ <section class="ks" data-ks="page-head">
102
+ <h2>页头与板块标题(配方组件 PageHeader / SectionHeader)</h2>
103
+ <PageHeader title="采购申请" subtitle="全部部门的申请,默认按提交时间倒序">
104
+ <template #actions><el-button>导出</el-button><el-button type="primary">新建申请</el-button></template>
105
+ </PageHeader>
106
+ <PageHeader class="mt" title="CG-20260910-0001">
107
+ <template #status><span class="status info">采购中</span></template>
108
+ <template #actions><el-button>返回</el-button></template>
109
+ </PageHeader>
110
+ <SectionHeader class="mt" title="经营概览">
111
+ <template #actions>
112
+ <el-radio-group v-model="period" aria-label="统计周期"><el-radio-button value="day">今日</el-radio-button><el-radio-button value="week">本周</el-radio-button><el-radio-button value="month">本月</el-radio-button></el-radio-group>
113
+ <el-button>导出日报</el-button>
114
+ </template>
115
+ </SectionHeader>
116
+ </section>
117
+
118
+ <section class="ks" data-ks="button">
119
+ <h2>按钮</h2>
120
+ <el-space wrap>
121
+ <el-button type="primary">主操作</el-button>
122
+ <el-button>次要</el-button>
123
+ <el-button type="danger">删除</el-button>
124
+ <el-button type="primary" plain>朴素主色</el-button>
125
+ <el-button plain>朴素</el-button>
126
+ <el-button type="danger" plain>朴素危险</el-button>
127
+ <el-button type="primary" disabled>禁用主色</el-button>
128
+ <el-button disabled>禁用</el-button>
129
+ <el-button type="primary" loading>加载中</el-button>
130
+ <el-button text>文字按钮</el-button>
131
+ <el-button text bg>文字带底</el-button>
132
+ <el-button link>链接按钮</el-button>
133
+ <el-button link type="primary">主色链接按钮</el-button>
134
+ <el-button type="primary" round>圆角</el-button>
135
+ <el-button type="primary" circle aria-label="搜索">Q</el-button>
136
+ <el-button type="success">成功</el-button>
137
+ <el-button type="warning">警告</el-button>
138
+ <el-button type="info">信息</el-button>
139
+ <el-button size="small">小号</el-button>
140
+ <el-button size="large" type="primary">大号</el-button>
141
+ </el-space>
142
+ <el-button-group class="mt">
143
+ <el-button>左</el-button><el-button>中</el-button><el-button>右</el-button>
144
+ </el-button-group>
145
+ <el-button-group class="mt">
146
+ <el-button type="primary">上一页</el-button><el-button type="primary">下一页</el-button>
147
+ </el-button-group>
148
+ </section>
149
+
150
+ <section class="ks" data-ks="typography">
151
+ <h2>链接与文字</h2>
152
+ <el-space wrap>
153
+ <el-link href="#">默认链接</el-link>
154
+ <el-link type="primary" href="#">主色链接</el-link>
155
+ <el-link type="danger" href="#">危险链接</el-link>
156
+ <el-link disabled>禁用链接</el-link>
157
+ <el-link underline="never" href="#">无下划线</el-link>
158
+ <el-text>正文</el-text>
159
+ <el-text type="primary">主色文字</el-text>
160
+ <el-text type="success">成功</el-text>
161
+ <el-text type="warning">警告</el-text>
162
+ <el-text type="danger">危险</el-text>
163
+ <el-text type="info">弱化</el-text>
164
+ <el-text size="small">小字</el-text>
165
+ <el-text tag="b">加粗</el-text>
166
+ <el-text line-clamp="2" class="clamp">多行省略:说明文字超过两行时在第二行末尾出现省略号,单行的标签与按钮文字不做截断,验收的截断检查只看单行文字</el-text>
167
+ </el-space>
168
+ </section>
169
+
170
+ <section class="ks" data-ks="input">
171
+ <h2>输入</h2>
172
+ <div class="grid">
173
+ <el-input v-model="input" placeholder="请输入" clearable />
174
+ <el-input v-model="input" disabled />
175
+ <el-input v-model="input" readonly />
176
+ <el-input placeholder="带前后缀"><template #prefix>¥</template><template #suffix>元</template></el-input>
177
+ <el-input placeholder="带前后置"><template #prepend>https://</template><template #append>.com</template></el-input>
178
+ <el-input type="password" model-value="secret" show-password />
179
+ <el-input v-model="input" maxlength="10" show-word-limit />
180
+ <el-input size="small" placeholder="小号" />
181
+ <el-input size="large" placeholder="大号" />
182
+ <el-input-number v-model="num" :min="1" :max="10" />
183
+ <el-input-number v-model="num" controls-position="right" />
184
+ <el-input-number v-model="num" disabled />
185
+ <span data-ks-open data-ks-type="autocomplete"><el-autocomplete model-value="" :fetch-suggestions="querySearch" placeholder="自动补全" /></span>
186
+ <el-input-tag v-model="tags" placeholder="回车添加标签" />
187
+ <el-mention v-model="mention" :options="[{ value: '王芳' }, { value: '赵磊' }]" placeholder="输入 @ 提及" />
188
+ </div>
189
+ <el-input v-model="textarea" type="textarea" :rows="2" class="mt" />
190
+ </section>
191
+
192
+ <section class="ks" data-ks="select">
193
+ <h2>选择</h2>
194
+ <div class="grid">
195
+ <el-select v-model="select" placeholder="状态" data-ks-open>
196
+ <el-option v-for="o in statusOptions" :key="o.value" :label="o.label" :value="o.value" :disabled="o.disabled" />
197
+ </el-select>
198
+ <el-select v-model="multi" multiple placeholder="多选" data-ks-open>
199
+ <el-option v-for="o in statusOptions" :key="o.value" :label="o.label" :value="o.value" :disabled="o.disabled" />
200
+ </el-select>
201
+ <el-select v-model="multi" multiple collapse-tags collapse-tags-tooltip placeholder="折叠标签">
202
+ <el-option v-for="o in statusOptions" :key="o.value" :label="o.label" :value="o.value" />
203
+ </el-select>
204
+ <el-select v-model="select" disabled placeholder="禁用" />
205
+ <el-select model-value="" filterable placeholder="可搜索" clearable>
206
+ <el-option v-for="o in statusOptions" :key="o.value" :label="o.label" :value="o.value" />
207
+ </el-select>
208
+ <el-select-v2 v-model="selectV2" :options="v2Options" placeholder="虚拟列表" data-ks-open />
209
+ <el-cascader v-model="cascader" :options="cascaderOptions" placeholder="级联" data-ks-open />
210
+ <span data-ks-open><el-tree-select v-model="treeSelect" :data="treeData" node-key="id" check-strictly placeholder="树选择" /></span>
211
+ </div>
212
+ <el-cascader-panel v-model="cascader" :options="cascaderOptions" class="mt" />
213
+ </section>
214
+
215
+ <section class="ks" data-ks="date">
216
+ <h2>日期与时间</h2>
217
+ <div class="grid">
218
+ <el-date-picker v-model="date" type="date" placeholder="日期" data-ks-open />
219
+ <el-date-picker v-model="range" type="daterange" range-separator="至" start-placeholder="开始" end-placeholder="结束" data-ks-open />
220
+ <el-date-picker v-model="datetime" type="datetime" placeholder="日期时间" data-ks-open />
221
+ <el-date-picker v-model="date" type="month" placeholder="月份" data-ks-open />
222
+ <el-date-picker v-model="date" type="year" placeholder="年份" data-ks-open />
223
+ <el-date-picker v-model="date" type="week" placeholder="周" data-ks-open />
224
+ <el-time-picker v-model="time" placeholder="时间" data-ks-open />
225
+ <el-time-select v-model="timeSelect" start="08:30" step="00:15" end="18:30" placeholder="时间段" data-ks-open />
226
+ <el-date-picker v-model="date" type="date" disabled />
227
+ </div>
228
+ </section>
229
+
230
+ <section class="ks" data-ks="choice">
231
+ <h2>开关、勾选、单选、评分、滑杆</h2>
232
+ <el-space wrap size="large">
233
+ <el-switch v-model="on" />
234
+ <el-switch v-model="off" />
235
+ <el-switch v-model="on" disabled />
236
+ <el-switch v-model="on" active-text="开" inactive-text="关" />
237
+ <el-switch v-model="on" loading />
238
+ </el-space>
239
+ <div class="mt">
240
+ <el-checkbox-group v-model="checks">
241
+ <el-checkbox value="a" label="已选" />
242
+ <el-checkbox value="b" label="未选" />
243
+ <el-checkbox value="c" label="禁用" disabled />
244
+ <el-checkbox value="d" label="禁用已选" disabled model-value />
245
+ <el-checkbox value="e" label="半选" indeterminate />
246
+ <el-checkbox value="f" label="带边框" border />
247
+ </el-checkbox-group>
248
+ <el-checkbox-group v-model="checkBtns" class="mt">
249
+ <el-checkbox-button value="a">周一</el-checkbox-button>
250
+ <el-checkbox-button value="b">周二</el-checkbox-button>
251
+ <el-checkbox-button value="c" disabled>周三</el-checkbox-button>
252
+ </el-checkbox-group>
253
+ </div>
254
+ <div class="mt">
255
+ <el-radio-group v-model="radio">
256
+ <el-radio value="a">已选</el-radio>
257
+ <el-radio value="b">未选</el-radio>
258
+ <el-radio value="c" disabled>禁用</el-radio>
259
+ <el-radio value="d" border>带边框</el-radio>
260
+ </el-radio-group>
261
+ <el-radio-group v-model="radioBtn" class="mt">
262
+ <el-radio-button value="day">今日</el-radio-button>
263
+ <el-radio-button value="week">近 7 天</el-radio-button>
264
+ <el-radio-button value="month">近 30 天</el-radio-button>
265
+ <el-radio-button value="year" disabled>本年</el-radio-button>
266
+ </el-radio-group>
267
+ <el-radio-group v-model="radioBtn" size="small" class="mt">
268
+ <el-radio-button value="day">小号</el-radio-button>
269
+ <el-radio-button value="week">分段</el-radio-button>
270
+ </el-radio-group>
271
+ </div>
272
+ <el-space wrap size="large" class="mt">
273
+ <el-rate v-model="rate" />
274
+ <el-rate v-model="rate" disabled show-score />
275
+ <el-rate :model-value="4" allow-half show-text :texts="['极差', '失望', '一般', '满意', '惊喜']" />
276
+ </el-space>
277
+ <div class="mt slider-row">
278
+ <el-slider v-model="slider" />
279
+ <el-slider v-model="sliderRange" range :marks="{ 0: '0', 50: '50%', 100: '100%' }" />
280
+ <el-slider v-model="slider" disabled />
281
+ <el-slider v-model="slider" show-input />
282
+ </div>
283
+ <el-space wrap size="large" class="mt">
284
+ <el-color-picker v-model="color" data-ks-ignore />
285
+ <el-color-picker v-model="color" show-alpha data-ks-ignore />
286
+ <el-color-picker v-model="color" disabled data-ks-ignore />
287
+ </el-space>
288
+ </section>
289
+
290
+ <section class="ks" data-ks="transfer-upload">
291
+ <h2>穿梭框与上传</h2>
292
+ <el-transfer v-model="transfer" :data="transferData" filterable :titles="['未选供应商', '已选供应商']" :button-texts="['移出', '移入']" />
293
+ <div class="grid mt">
294
+ <el-upload action="#" :auto-upload="false" :file-list="fileList" list-type="text">
295
+ <el-button>选择文件</el-button>
296
+ <template #tip><div class="el-upload__tip">jpg / png,不超过 2MB</div></template>
297
+ </el-upload>
298
+ <el-upload action="#" :auto-upload="false" drag>
299
+ <div class="el-upload__text">拖拽文件到此处,或<em>点击上传</em></div>
300
+ </el-upload>
301
+ <el-upload action="#" :auto-upload="false" list-type="picture-card" :file-list="fileList">
302
+ <span aria-hidden="true">+</span>
303
+ </el-upload>
304
+ </div>
305
+ </section>
306
+
307
+ <section class="ks" data-ks="form">
308
+ <h2>表单(含校验出错)</h2>
309
+ <el-form ref="formRef" :model="form" :rules="rules" label-width="112px" class="form">
310
+ <el-form-item label="供应商名称" prop="name" required><el-input v-model="form.name" placeholder="必填" /></el-form-item>
311
+ <el-form-item label="联系电话" prop="phone"><el-input v-model="form.phone" /></el-form-item>
312
+ <el-form-item label="供应类目"><el-select v-model="select" placeholder="请选择"><el-option label="办公设备" value="pending" /></el-select></el-form-item>
313
+ <el-form-item label="说明"><el-input type="textarea" :rows="2" /></el-form-item>
314
+ <el-form-item><el-button type="primary">保存</el-button><el-button>取消</el-button></el-form-item>
315
+ </el-form>
316
+ <el-form inline class="mt"><el-form-item label="关键词"><el-input placeholder="行内表单" /></el-form-item><el-form-item><el-button type="primary">查询</el-button><el-button text>重置</el-button></el-form-item></el-form>
317
+ </section>
318
+
319
+ <section class="ks" data-ks="tag-badge">
320
+ <h2>标签、徽标、头像</h2>
321
+ <el-space wrap>
322
+ <el-tag>默认</el-tag>
323
+ <el-tag type="success">成功</el-tag>
324
+ <el-tag type="warning">警告</el-tag>
325
+ <el-tag type="danger">错误</el-tag>
326
+ <el-tag type="info">信息</el-tag>
327
+ <el-tag closable>可关闭</el-tag>
328
+ <el-tag effect="dark">深色</el-tag>
329
+ <el-tag effect="dark" type="success">深色成功</el-tag>
330
+ <el-tag effect="plain">朴素</el-tag>
331
+ <el-tag round>圆角</el-tag>
332
+ <el-tag size="small">小号</el-tag>
333
+ <el-check-tag v-model:checked="checkTag">可勾选标签</el-check-tag>
334
+ <el-check-tag :checked="false">未勾选</el-check-tag>
335
+ <el-badge :value="12"><el-button>消息</el-button></el-badge>
336
+ <el-badge :value="120" :max="99"><el-button>待办</el-button></el-badge>
337
+ <el-badge is-dot><el-button>新</el-button></el-badge>
338
+ <el-badge value="new" type="primary"><el-button>主色徽标</el-button></el-badge>
339
+ <el-avatar>王</el-avatar>
340
+ <el-avatar shape="square" size="small">赵</el-avatar>
341
+ <el-avatar size="large">李</el-avatar>
342
+ </el-space>
343
+ </section>
344
+
345
+ <section class="ks" data-ks="table">
346
+ <h2>表格</h2>
347
+ <el-table ref="tableRef" :data="rows" row-key="id" stripe border highlight-current-row default-expand-all>
348
+ <el-table-column type="selection" width="44" />
349
+ <el-table-column type="expand"><template #default>展开行内容</template></el-table-column>
350
+ <el-table-column prop="id" label="申请编号" sortable width="160"><template #default="{ row }"><a class="link mono" href="#">{{ row.id }}</a></template></el-table-column>
351
+ <el-table-column prop="merchant" label="供应商" :filters="[{ text: '蓝海电子', value: '蓝海电子' }]" :filter-method="() => true" />
352
+ <el-table-column prop="amount" label="金额" align="right" sortable><template #default="{ row }"><span class="num">¥ {{ row.amount.toFixed(2) }}</span></template></el-table-column>
353
+ <el-table-column label="状态"><template #default="{ row }"><span class="status" :class="statusClass(row.status)">{{ row.statusText }}</span></template></el-table-column>
354
+ <el-table-column label="操作" fixed="right" width="140"><template #default><el-button text size="small">查看</el-button><el-button text size="small" class="danger">取消</el-button></template></el-table-column>
355
+ </el-table>
356
+ <el-table :data="[]" class="mt"><el-table-column label="空表格" /></el-table>
357
+ <div class="mt row">
358
+ <el-pagination v-model:current-page="page" v-model:page-size="pageSize" :total="400" layout="total, sizes, prev, pager, next, jumper" background />
359
+ </div>
360
+ <el-pagination v-model:current-page="page" :page-size="20" :total="400" layout="prev, pager, next" size="small" class="mt" />
361
+ </section>
362
+
363
+ <section class="ks" data-ks="data">
364
+ <h2>数据展示</h2>
365
+ <div class="grid">
366
+ <el-card><template #header>卡片标题</template>卡片内容</el-card>
367
+ <el-card shadow="hover">悬停出阴影的卡片</el-card>
368
+ <el-card v-loading="true" element-loading-text="加载中">加载中的卡片</el-card>
369
+ <el-statistic title="本月申请" :value="12480" />
370
+ <el-statistic title="驳回率" :value="1.8" suffix="%" :precision="1" />
371
+ <el-countdown title="活动倒计时" :value="Date.now() + 1000 * 60 * 60 * 7" />
372
+ </div>
373
+ <el-descriptions title="供应商信息" :column="3" border class="mt">
374
+ <el-descriptions-item label="名称">蓝海电子</el-descriptions-item>
375
+ <el-descriptions-item label="电话">138****0000</el-descriptions-item>
376
+ <el-descriptions-item label="状态"><span class="status success">营业中</span></el-descriptions-item>
377
+ <el-descriptions-item label="地址" :span="3">上海市浦东新区张江路 100 号</el-descriptions-item>
378
+ </el-descriptions>
379
+ <div class="grid mt">
380
+ <el-progress :percentage="42" />
381
+ <el-progress :percentage="100" status="success" />
382
+ <el-progress :percentage="30" status="exception" />
383
+ <el-progress :percentage="70" status="warning" />
384
+ <el-progress :percentage="60" :stroke-width="12" striped striped-flow />
385
+ <el-progress type="circle" :percentage="42" />
386
+ <el-progress type="dashboard" :percentage="64" />
387
+ <el-progress :percentage="50" :text-inside="true" :stroke-width="20" />
388
+ </div>
389
+ <div class="grid mt">
390
+ <el-timeline>
391
+ <el-timeline-item timestamp="09:30" type="primary">下单</el-timeline-item>
392
+ <el-timeline-item timestamp="09:35" type="success">部门负责人已审批</el-timeline-item>
393
+ <el-timeline-item timestamp="09:50" type="warning">供应商发货超时</el-timeline-item>
394
+ <el-timeline-item timestamp="10:20" type="danger">用户取消</el-timeline-item>
395
+ <el-timeline-item timestamp="10:21" type="info" hollow>系统关单</el-timeline-item>
396
+ </el-timeline>
397
+ <el-tree ref="treeRef" :data="treeData" node-key="id" show-checkbox default-expand-all highlight-current current-node-key="n2" :default-checked-keys="['n3']" />
398
+ <el-tree-v2 :data="treeData" :props="{ value: 'id', label: 'label', children: 'children' }" show-checkbox :default-checked-keys="['n2']" :height="160" />
399
+ </div>
400
+ <div class="grid mt">
401
+ <el-empty description="暂无数据" />
402
+ <el-result icon="success" title="提交成功" sub-title="审核将在 1 个工作日内完成"><template #extra><el-button type="primary">返回</el-button></template></el-result>
403
+ <el-result icon="error" title="提交失败" />
404
+ <el-result icon="warning" title="需要确认" />
405
+ <el-result icon="info" title="提示" />
406
+ <el-skeleton :rows="3" animated />
407
+ </div>
408
+ <div class="grid mt">
409
+ <el-segmented v-model="segmented" :options="['列表', '卡片', '看板']" />
410
+ <el-segmented v-model="segmented" :options="['列表', '卡片']" disabled />
411
+ <el-segmented v-model="segmented" :options="['列表', '卡片', '看板']" size="small" />
412
+ </div>
413
+ <el-calendar v-model="date" class="mt" />
414
+ <el-carousel height="80px" class="mt" indicator-position="outside" :autoplay="false" data-ks-ignore>
415
+ <el-carousel-item v-for="i in 3" :key="i"><div class="slide">轮播 {{ i }}</div></el-carousel-item>
416
+ </el-carousel>
417
+ <el-collapse v-model="collapse" class="mt">
418
+ <el-collapse-item title="已展开的面板" name="1">面板内容</el-collapse-item>
419
+ <el-collapse-item title="折叠的面板" name="2">面板内容</el-collapse-item>
420
+ <el-collapse-item title="禁用的面板" name="3" disabled>面板内容</el-collapse-item>
421
+ </el-collapse>
422
+ <div class="grid mt">
423
+ <el-image src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" style="width: 120px; height: 80px" fit="cover" alt="示意图" data-ks-ignore />
424
+ <el-image src="" style="width: 120px; height: 80px"><template #error><div class="img-err">加载失败</div></template></el-image>
425
+ </div>
426
+ </section>
427
+
428
+ <section class="ks" data-ks="nav">
429
+ <h2>导航</h2>
430
+ <el-page-header title="返回" content="申请详情" class="mt" />
431
+ <el-breadcrumb separator="/" class="mt"><el-breadcrumb-item to="/">工作台</el-breadcrumb-item><el-breadcrumb-item><a href="#">采购申请</a></el-breadcrumb-item><el-breadcrumb-item>申请详情</el-breadcrumb-item></el-breadcrumb>
432
+ <el-menu mode="horizontal" default-active="2" class="mt" :ellipsis="false">
433
+ <el-menu-item index="1">工作台</el-menu-item>
434
+ <el-menu-item index="2">申请</el-menu-item>
435
+ <el-sub-menu index="3"><template #title>更多</template><el-menu-item index="3-1">供应商</el-menu-item><el-menu-item index="3-2">资产</el-menu-item></el-sub-menu>
436
+ <el-menu-item index="4" disabled>禁用</el-menu-item>
437
+ </el-menu>
438
+ <el-steps :active="1" class="mt"><el-step title="提交" description="09:30" /><el-step title="审批" /><el-step title="采购" /><el-step title="完成" /></el-steps>
439
+ <el-steps :active="2" process-status="error" finish-status="success" class="mt"><el-step title="提交" /><el-step title="审批" /><el-step title="申请人撤回" /><el-step title="完成" /></el-steps>
440
+ <el-steps direction="vertical" :active="1" class="mt" style="height: 150px"><el-step title="提交" /><el-step title="审核" /><el-step title="发布" /></el-steps>
441
+ <el-steps :active="1" simple class="mt"><el-step title="提交" /><el-step title="审批" /><el-step title="完成" /></el-steps>
442
+ <el-tabs v-model="tab" class="mt"><el-tab-pane label="概览" name="a">概览内容</el-tab-pane><el-tab-pane label="申请" name="b">申请内容</el-tab-pane><el-tab-pane label="禁用" name="c" disabled>—</el-tab-pane></el-tabs>
443
+ <el-tabs v-model="tabCard" type="card" class="mt" closable><el-tab-pane label="卡片页签" name="c1">内容</el-tab-pane><el-tab-pane label="第二页" name="c2">内容</el-tab-pane></el-tabs>
444
+ <el-tabs v-model="tabBorder" type="border-card" class="mt"><el-tab-pane label="边框卡片" name="b1">内容</el-tab-pane><el-tab-pane label="第二页" name="b2">内容</el-tab-pane></el-tabs>
445
+ <el-tabs v-model="tabLeft" tab-position="left" class="mt" style="height: 120px"><el-tab-pane label="左侧页签" name="l1">内容</el-tab-pane><el-tab-pane label="第二页" name="l2">内容</el-tab-pane></el-tabs>
446
+ <el-anchor class="mt" :offset="80"><el-anchor-link href="#ks-a" title="锚点一" /><el-anchor-link href="#ks-b" title="锚点二" /></el-anchor>
447
+ <el-space wrap class="mt">
448
+ <el-dropdown trigger="click" data-ks-open data-ks-type="dropdown">
449
+ <el-button>更多操作</el-button>
450
+ <template #dropdown><el-dropdown-menu><el-dropdown-item>编辑</el-dropdown-item><el-dropdown-item disabled>复制</el-dropdown-item><el-dropdown-item divided class="is-danger">删除</el-dropdown-item></el-dropdown-menu></template>
451
+ </el-dropdown>
452
+ <el-dropdown split-button type="primary" trigger="click" data-ks-open data-ks-type="dropdown">导出<template #dropdown><el-dropdown-menu><el-dropdown-item>导出 Excel</el-dropdown-item><el-dropdown-item>导出 CSV</el-dropdown-item></el-dropdown-menu></template></el-dropdown>
453
+ </el-space>
454
+ </section>
455
+
456
+ <section class="ks" data-ks="feedback">
457
+ <h2>反馈</h2>
458
+ <div class="stack">
459
+ <el-alert title="成功提示" type="success" show-icon />
460
+ <el-alert title="警告提示" type="warning" show-icon description="这是一段说明文字。" />
461
+ <el-alert title="错误提示" type="error" show-icon closable />
462
+ <el-alert title="信息提示" type="info" show-icon :closable="false" />
463
+ <el-alert title="深色成功" type="success" effect="dark" />
464
+ <el-alert title="深色错误" type="error" effect="dark" />
465
+ </div>
466
+ <el-space wrap class="mt" size="large">
467
+ <el-tooltip content="这是一个 Tooltip" :visible="tooltipVisible" :teleported="false" persistent placement="bottom"><el-button>Tooltip 常显</el-button></el-tooltip>
468
+ <el-tooltip content="浅色 Tooltip" effect="light" :visible="tooltipVisible" :teleported="false" persistent placement="bottom"><el-button>浅色 Tooltip</el-button></el-tooltip>
469
+ <el-popover :visible="popoverVisible" :teleported="false" persistent title="Popover 标题" content="这是 Popover 的内容。" placement="bottom" :width="220"><template #reference><el-button>Popover 常显</el-button></template></el-popover>
470
+ <el-popconfirm title="确认撤回该申请?" confirm-button-text="确认" cancel-button-text="再想想" icon-color="var(--color-status-warning)" :teleported="false" persistent><template #reference><el-button data-ks-open>Popconfirm</el-button></template></el-popconfirm>
471
+ </el-space>
472
+ <el-space wrap class="mt" style="margin-top: 96px">
473
+ <el-button data-ks-modal="dialog" @click="dialog = true">打开弹窗</el-button>
474
+ <el-button data-ks-modal="drawer" @click="drawer = true">打开抽屉</el-button>
475
+ <el-button data-ks-modal="messagebox" @click="openMessageBox">MessageBox</el-button>
476
+ <el-button data-ks-modal="message" @click="openMessage">Message 成功</el-button>
477
+ <el-button data-ks-modal="message" @click="openMessageWarn">Message 警告</el-button>
478
+ <el-button data-ks-modal="message" @click="openMessageErr">Message 错误</el-button>
479
+ <el-button data-ks-modal="notification" @click="openNotification">Notification</el-button>
480
+ </el-space>
481
+ <el-dialog v-model="dialog" title="编辑供应商" width="480px">
482
+ <el-form label-width="112px"><el-form-item label="名称"><el-input model-value="蓝海电子" /></el-form-item></el-form>
483
+ <template #footer><el-button @click="dialog = false">取消</el-button><el-button type="primary" @click="dialog = false">保存</el-button></template>
484
+ </el-dialog>
485
+ <el-drawer v-model="drawer" title="申请详情" size="480px"><p>抽屉内容</p><el-button type="primary">主操作</el-button></el-drawer>
486
+ <el-divider content-position="left">分隔线</el-divider>
487
+ <el-divider />
488
+ </section>
489
+ </div>
490
+ </template>
491
+
492
+ <style scoped>
493
+ .ks-page { max-width: 1120px; }
494
+ .ks-page .head { margin-bottom: var(--space-stack); } /* 页头用配方组件;走查页不在内容区的 gap 里排,自己留出到第一组的距离 */
495
+ .ks { background: var(--color-bg-surface); border: var(--border-width-default) solid var(--color-border-default); border-radius: var(--radius-lg); padding: var(--space-card); margin-bottom: var(--space-stack); box-shadow: 0 var(--elevation-card-y) var(--elevation-card-blur) var(--elevation-card-color); }
496
+ .ks h2 { margin: 0 0 var(--spacing-4); font-size: var(--text-title-size); font-weight: var(--text-weight-label); }
497
+ .mt { margin-top: var(--spacing-4); }
498
+ .grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--spacing-4); align-items: start; }
499
+ .stack { display: grid; gap: var(--spacing-3); }
500
+ .row { display: flex; flex-wrap: wrap; gap: var(--spacing-4); }
501
+ .slider-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--spacing-6) var(--spacing-8); }
502
+ .form { max-width: var(--layout-form-max-width); }
503
+ .clamp { max-width: var(--layout-search-width); }
504
+ .slide { height: 100%; display: grid; place-items: center; background: var(--color-bg-subtle); color: var(--color-text-secondary); }
505
+ .img-err { display: grid; place-items: center; height: 100%; background: var(--color-bg-subtle); color: var(--color-text-muted); font-size: var(--text-caption-size); }
506
+ .num { font-family: var(--text-numeric-family); font-variant-numeric: var(--text-numeric-variant); }
507
+ </style>
@@ -0,0 +1,23 @@
1
+ <script setup>
2
+ // 页头(DESIGN「页面骨架」:顶栏 → 页头 → 可选标签 / 筛选 → 业务内容):每个一级页面一个 H1,副标题可省(没必要就不写)。
3
+ // 种子配方组件(bridge/vue),样式来自 recipes.css 的 .page-head / .title-row / .actions(与 React 版共用),不依赖 Element。
4
+ // #status 放标题旁的状态胶囊(详情页「处理中」),#actions 放页面级操作(主操作金底),与标题顶部对齐。
5
+ // 面包屑不在这里——它在壳层顶栏(el-breadcrumb / .crumbs);表格、图表自己的筛选也不放这里,留在各自的卡片里。
6
+ // 标题要带行内标记(单号包 .mono)时用 #title 插槽,副标题同理用 #subtitle;#leading 放标题左侧的头像(客户详情),与 H1 行顶对齐。
7
+ defineProps({
8
+ title: String,
9
+ subtitle: String
10
+ })
11
+ </script>
12
+
13
+ <template>
14
+ <div class="page-head">
15
+ <slot name="leading" />
16
+ <div>
17
+ <div v-if="$slots.status" class="title-row"><h1><slot name="title">{{ title }}</slot></h1><slot name="status" /></div>
18
+ <h1 v-else><slot name="title">{{ title }}</slot></h1>
19
+ <p v-if="subtitle || $slots.subtitle"><slot name="subtitle">{{ subtitle }}</slot></p>
20
+ </div>
21
+ <div v-if="$slots.actions" class="actions"><slot name="actions" /></div>
22
+ </div>
23
+ </template>
@@ -0,0 +1,15 @@
1
+ <script setup>
2
+ // 板块标题(DESIGN「页面骨架」):看板页在 H1 下分板块的二级标题(工作台「经营概览」),右侧 #actions 放只作用于这一板块的筛选与操作(周期切换、导出日报)。
3
+ // 种子配方组件(bridge/vue),样式来自 recipes.css 的 .section-head(与 React 版共用),不依赖 Element。
4
+ // 页面级操作放 PageHeader 的 #actions,不放这里;不值一个标题的次要分组用配方类 .section-label。
5
+ defineProps({
6
+ title: String
7
+ })
8
+ </script>
9
+
10
+ <template>
11
+ <div class="section-head">
12
+ <h2><slot name="title">{{ title }}</slot></h2>
13
+ <div v-if="$slots.actions" class="right"><slot name="actions" /></div>
14
+ </div>
15
+ </template>
@@ -0,0 +1,33 @@
1
+ <script setup>
2
+ // 统计卡(DESIGN 页面骨架「统计卡 vs 状态条」):数据大字 + 涨跌。种子配方组件(bridge/vue),样式来自 recipes.css 的 .stat-card(与 React 版共用),不依赖 el-card。
3
+ // up 是箭头方向,positive 是颜色(好坏)——取消率下降是"向下箭头 + 绿色",两个维度分开(DESIGN 财务涨跌规则)。
4
+ // 数据卡与普通卡片同一形态(细边线 + 柔阴影),不加品牌指示条,也不铺大面积金色。
5
+ // hint:口径说明(报表「已通过金额」与工作台「本月获批」口径不同这类),标签右侧一个带 aria-label 的信息图标按钮,悬停 / 聚焦出 tooltip;
6
+ // note:没有 delta 时也能单独显示的副文案。#label-extra 插槽可放自定义内容。
7
+ import { Info } from '@icon-park/vue-next'
8
+ const props = defineProps({
9
+ label: { type: String, required: true },
10
+ value: { type: [String, Number], required: true },
11
+ delta: String,
12
+ up: { type: Boolean, default: true },
13
+ positive: { type: Boolean, default: undefined },
14
+ note: String,
15
+ hint: String
16
+ })
17
+ const good = () => (props.positive === undefined ? props.up : props.positive)
18
+ </script>
19
+
20
+ <template>
21
+ <div class="stat-card">
22
+ <div class="stat-card__label">
23
+ <span>{{ label }}</span>
24
+ <el-tooltip v-if="hint" :content="hint" placement="top">
25
+ <button type="button" class="stat-card__hint" :aria-label="`${label}的统计口径说明`"><Info class="i-icon--sm" /></button>
26
+ </el-tooltip>
27
+ <slot name="label-extra" />
28
+ </div>
29
+ <div class="stat-card__value num">{{ value }}</div>
30
+ <div v-if="delta" class="stat-card__delta num" :class="good() ? 'up' : 'down'">{{ up ? '▲' : '▼' }} {{ delta }}<span v-if="note" class="stat-card__note"> · {{ note }}</span></div>
31
+ <div v-else-if="note" class="stat-card__note">{{ note }}</div>
32
+ </div>
33
+ </template>
@@ -0,0 +1,14 @@
1
+ <script setup>
2
+ // 表格骨架(种子配方组件):按目标表格的形状占位(表头 + 等高行 + 列宽),加载完成后不发生版面跳动。样式来自 recipes.css 的 .tskel(与 React 版共用)。
3
+ // 见 DESIGN.md「组件配方 · 空状态 / 骨架屏」:骨架要按目标形状,不用通用段落骨架顶替表格。
4
+ defineProps({ rows: { type: Number, default: 6 }, cols: { type: Array, default: () => [200, 160, 120, 120, 100] } })
5
+ </script>
6
+
7
+ <template>
8
+ <div class="tskel" role="status" aria-live="polite" aria-label="正在加载">
9
+ <div class="tskel-head"><span v-for="(w, i) in cols" :key="i" class="cell" :style="{ flex: `0 0 ${w}px` }"><i class="bar head" /></span></div>
10
+ <div v-for="r in rows" :key="r" class="tskel-row">
11
+ <span v-for="(w, i) in cols" :key="i" class="cell" :style="{ flex: `0 0 ${w}px` }"><i class="bar" :class="{ short: (r + i) % 3 === 0 }" /></span>
12
+ </div>
13
+ </div>
14
+ </template>
@@ -0,0 +1,11 @@
1
+ <script setup>
2
+ // 趋势折线(种子配方组件):直接用桥接的 trendLine 配方——y 轴贴数据范围、平滑、不显示数据点、多序列不填面积、单序列渐变面积。
3
+ import EChart from './EChart.vue'
4
+ import { trendLine, areaGradient } from '../echarts.js'
5
+
6
+ // count:计数型趋势(整数)y 轴从 0 起、整数刻度;不传时按数据自动判断
7
+ const props = defineProps({ days: Array, series: Array, count: { type: Boolean, default: undefined } })
8
+ const option = (t, echarts) => trendLine({ days: props.days, series: props.series, count: props.count, area: props.series.length === 1 ? areaGradient(echarts) : undefined })
9
+ </script>
10
+
11
+ <template><EChart :option="option" /></template>