npmapps 1.0.25 → 1.0.26

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 (212) hide show
  1. package/app/.codegraph/daemon.pid +6 -0
  2. package/app/.eslintrc.js +19 -0
  3. package/app/README.md +24 -0
  4. package/app/babel.config.js +5 -0
  5. package/app/devtool-windows-amd64.zip +0 -0
  6. package/app/docs/superpowers/plans/2026-05-29-quill-editor.md +836 -0
  7. package/app/docs/superpowers/specs/2026-05-29-quill-editor-design.md +210 -0
  8. package/app/docs/superpowers/specs/2026-06-06-lazy-cascader-design.md +400 -0
  9. package/app/jsconfig.json +19 -0
  10. package/app/package-lock.json +21347 -0
  11. package/app/package.json +63 -0
  12. package/app/postcss.config.js +10 -0
  13. package/app/public/favicon.ico +0 -0
  14. package/app/public/index.html +17 -0
  15. package/app/public//344/270/200/351/224/256/351/273/221/346/232/227.html +136 -0
  16. package/app/src/App.vue +110 -0
  17. package/app/src/assets/bpmn-camunda.jpg +0 -0
  18. package/app/src/assets/css/diagram.less +17 -0
  19. package/app/src/assets/icon/Icon.less +31 -0
  20. package/app/src/assets/icon/font/app-codes.css +26 -0
  21. package/app/src/assets/icon/font/app.eot +0 -0
  22. package/app/src/assets/icon/font/app.svg +60 -0
  23. package/app/src/assets/icon/font/app.ttf +0 -0
  24. package/app/src/assets/icon/font/app.woff +0 -0
  25. package/app/src/assets/icon/font/app.woff2 +0 -0
  26. package/app/src/assets/icon/font/config.json +248 -0
  27. package/app/src/assets/icon/font/source/raw/align-bottom-tool.svg +30 -0
  28. package/app/src/assets/icon/font/source/raw/align-horizontal-center-tool.svg +85 -0
  29. package/app/src/assets/icon/font/source/raw/align-left-tool.svg +84 -0
  30. package/app/src/assets/icon/font/source/raw/align-right-tool.svg +80 -0
  31. package/app/src/assets/icon/font/source/raw/align-top-tool.svg +84 -0
  32. package/app/src/assets/icon/font/source/raw/align-vertical-center-tool.svg +89 -0
  33. package/app/src/assets/icon/font/source/raw/distribute-horizontally-tool.svg +95 -0
  34. package/app/src/assets/icon/font/source/raw/distribute-vertically-tool.svg +99 -0
  35. package/app/src/assets/icon/font/source/raw/set-color-tool.svg +111 -0
  36. package/app/src/assets/icon/font/source/symbols/align-bottom-tool.svg +30 -0
  37. package/app/src/assets/icon/font/source/symbols/align-horizontal-center-tool.svg +30 -0
  38. package/app/src/assets/icon/font/source/symbols/align-left-tool.svg +30 -0
  39. package/app/src/assets/icon/font/source/symbols/align-right-tool.svg +30 -0
  40. package/app/src/assets/icon/font/source/symbols/align-top-tool.svg +30 -0
  41. package/app/src/assets/icon/font/source/symbols/align-vertical-center-tool.svg +30 -0
  42. package/app/src/assets/icon/font/source/symbols/distribute-horizontally-tool.svg +30 -0
  43. package/app/src/assets/icon/font/source/symbols/distribute-vertically-tool.svg +30 -0
  44. package/app/src/assets/icon/font/source/symbols/set-color-tool.svg +63 -0
  45. package/app/src/assets/logo.png +0 -0
  46. package/app/src/components/EllTable/README.md +70 -0
  47. package/app/src/components/EllTable/article.md +184 -0
  48. package/app/src/components/EllTable/index.js +213 -0
  49. package/app/src/components/FormulaEditor/FunctionSelector.vue +123 -0
  50. package/app/src/components/FormulaEditor/OperatorSelector.vue +184 -0
  51. package/app/src/components/FormulaEditor/ParameterSelector.vue +123 -0
  52. package/app/src/components/FormulaEditor/api.js +69 -0
  53. package/app/src/components/FormulaEditor/index.vue +435 -0
  54. package/app/src/components/HelloWorld.vue +58 -0
  55. package/app/src/components/PageHeader/index.vue +158 -0
  56. package/app/src/components/Splitter/README.md +144 -0
  57. package/app/src/components/Splitter/example.vue +88 -0
  58. package/app/src/components/Splitter/index.vue +203 -0
  59. package/app/src/components/diagram/ToolBar.vue +357 -0
  60. package/app/src/components/diagram/customTranslate/customTranslate.js +12 -0
  61. package/app/src/components/diagram/customTranslate/translationsGerman.js +241 -0
  62. package/app/src/components/diagram/index.vue +261 -0
  63. package/app/src/components/diagram/xmlData.js +29 -0
  64. package/app/src/directives/filldown.js +155 -0
  65. package/app/src/directives/filldownTable.js +291 -0
  66. package/app/src/main.js +40 -0
  67. package/app/src/router/index.js +63 -0
  68. package/app/src/store/index.js +23 -0
  69. package/app/src/utils/winBox.js +23 -0
  70. package/app/src/views/Extend/A.vue +12 -0
  71. package/app/src/views/Extend/B.vue +10 -0
  72. package/app/src/views/Extend/MagicalComponentsForELFormItem.vue +87 -0
  73. package/app/src/views/Extend/index.vue +59 -0
  74. package/app/src/views/Extend/tableMouseHorizontalWheel.vue +193 -0
  75. package/app/src/views/Home.vue +37 -0
  76. package/app/src/views/RouterJump.vue +155 -0
  77. package/app/src/views/css.vue +57 -0
  78. package/app/src/views/cssComponents/EllipsisText.vue +83 -0
  79. package/app/src/views/cssComponents/HoverCard.vue +79 -0
  80. package/app/src/views/cssComponents/TableHover.vue +140 -0
  81. package/app/src/views/cssComponents/inputSlo.vue +52 -0
  82. package/app/src/views/cssComponents/tableFixed.vue +158 -0
  83. package/app/src/views/echarts/echart-dome.vue +82 -0
  84. package/app/src/views/echarts/index.vue +118 -0
  85. package/app/src/views/echarts/pei3d.vue +667 -0
  86. package/app/src/views/element/bpmn/index.vue +18 -0
  87. package/app/src/views/element/components/attendanceCycle/index.vue +131 -0
  88. package/app/src/views/element/components/attendanceGroup/index.vue +147 -0
  89. package/app/src/views/element/components/attendancePersonnel/index.vue +158 -0
  90. package/app/src/views/element/components/companyCalendar/index.vue +147 -0
  91. package/app/src/views/element/components/shift/index.vue +147 -0
  92. package/app/src/views/element/components/shiftRotationSystem/index.vue +147 -0
  93. package/app/src/views/element/elTableJsx/columnManagement.vue +340 -0
  94. package/app/src/views/element/elTableJsx/dialogInput.vue +71 -0
  95. package/app/src/views/element/elTableJsx/elTableJsx.vue +1826 -0
  96. package/app/src/views/element/elTableJsx/formTable.vue +598 -0
  97. package/app/src/views/element/elTableJsx/index.vue +29 -0
  98. package/app/src/views/element/elTableJsx/simpleTable.vue +192 -0
  99. package/app/src/views/element/elTableJsx.zip +0 -0
  100. package/app/src/views/element/index.vue +44 -0
  101. package/app/src/views/element/lazyCascader/LazyCascader.vue +302 -0
  102. package/app/src/views/element/lazyCascader/data.js +205 -0
  103. package/app/src/views/element/lazyCascader/index.vue +315 -0
  104. package/app/src/views/element/quillEditor/README.md +163 -0
  105. package/app/src/views/element/quillEditor/example.vue +314 -0
  106. package/app/src/views/element/quillEditor/index.vue +409 -0
  107. package/app/src/views/element/quillEditor/toolbar.js +122 -0
  108. package/app/vue.config.js +15 -0
  109. package/package.json +1 -1
  110. package/app/wujie-vue3-child/.claude/settings.local.json +0 -8
  111. package/app/wujie-vue3-child/.vscode/extensions.json +0 -3
  112. package/app/wujie-vue3-child/PROJECT_MEMORY.md +0 -427
  113. package/app/wujie-vue3-child/README.md +0 -5
  114. package/app/wujie-vue3-child/index.html +0 -13
  115. package/app/wujie-vue3-child/package-lock.json +0 -5744
  116. package/app/wujie-vue3-child/package.json +0 -28
  117. package/app/wujie-vue3-child/public/vite.svg +0 -1
  118. package/app/wujie-vue3-child/src/App.vue +0 -130
  119. package/app/wujie-vue3-child/src/assets/vue.svg +0 -1
  120. package/app/wujie-vue3-child/src/components/HelloWorld.vue +0 -43
  121. package/app/wujie-vue3-child/src/components/SmartAnchorTabs/index.jsx +0 -224
  122. package/app/wujie-vue3-child/src/components/SmartAnchorTabs/style.css +0 -154
  123. package/app/wujie-vue3-child/src/components/tags-view.vue +0 -193
  124. package/app/wujie-vue3-child/src/components/tags-view1.vue +0 -131
  125. package/app/wujie-vue3-child/src/directives/aiLoading.js +0 -182
  126. package/app/wujie-vue3-child/src/hooks/useClickOutside.js +0 -11
  127. package/app/wujie-vue3-child/src/hooks/useTableDragSort.js +0 -28
  128. package/app/wujie-vue3-child/src/main.js +0 -18
  129. package/app/wujie-vue3-child/src/router/index.js +0 -104
  130. package/app/wujie-vue3-child/src/store/tagsViewStroe.js +0 -34
  131. package/app/wujie-vue3-child/src/style.css +0 -171
  132. package/app/wujie-vue3-child/src/views/aiCoach/collapseExpand/index.jsx +0 -108
  133. package/app/wujie-vue3-child/src/views/aiCoach/collapseExpand/index.module.scss +0 -97
  134. package/app/wujie-vue3-child/src/views/aiCoach/departmentPersonnel/README.md +0 -836
  135. package/app/wujie-vue3-child/src/views/aiCoach/departmentPersonnel/REFLEX_EXAMPLES.md +0 -728
  136. package/app/wujie-vue3-child/src/views/aiCoach/departmentPersonnel/components/DepartmentPersonnelSelector.jsx +0 -687
  137. package/app/wujie-vue3-child/src/views/aiCoach/departmentPersonnel/components/DepartmentPersonnelSelector.module.scss +0 -560
  138. package/app/wujie-vue3-child/src/views/aiCoach/departmentPersonnel/components/DepartmentSelector.jsx +0 -570
  139. package/app/wujie-vue3-child/src/views/aiCoach/departmentPersonnel/components/DepartmentSelector.module.scss +0 -330
  140. package/app/wujie-vue3-child/src/views/aiCoach/departmentPersonnel/components/DepartmentSelectorV2.jsx +0 -378
  141. package/app/wujie-vue3-child/src/views/aiCoach/departmentPersonnel/components/DepartmentSelectorV2.module.scss +0 -228
  142. package/app/wujie-vue3-child/src/views/aiCoach/departmentPersonnel/components/OptionsSelector.jsx +0 -399
  143. package/app/wujie-vue3-child/src/views/aiCoach/departmentPersonnel/components/OptionsSelector.module.scss +0 -252
  144. package/app/wujie-vue3-child/src/views/aiCoach/departmentPersonnel/components/PersonnelSelector.jsx +0 -585
  145. package/app/wujie-vue3-child/src/views/aiCoach/departmentPersonnel/components/PersonnelSelector.module.scss +0 -331
  146. package/app/wujie-vue3-child/src/views/aiCoach/departmentPersonnel/components/PopoverSelector.jsx +0 -392
  147. package/app/wujie-vue3-child/src/views/aiCoach/departmentPersonnel/components/PopoverSelector.module.scss +0 -39
  148. package/app/wujie-vue3-child/src/views/aiCoach/departmentPersonnel/components/README.md +0 -248
  149. package/app/wujie-vue3-child/src/views/aiCoach/departmentPersonnel/components/SelectorTrigger.jsx +0 -194
  150. package/app/wujie-vue3-child/src/views/aiCoach/departmentPersonnel/index.jsx +0 -1459
  151. package/app/wujie-vue3-child/src/views/aiCoach/departmentPersonnel/mockData.js +0 -301
  152. package/app/wujie-vue3-child/src/views/aiCoach/dialogueSegment/index.jsx +0 -182
  153. package/app/wujie-vue3-child/src/views/aiCoach/dialogueSegment/index.module.scss +0 -28
  154. package/app/wujie-vue3-child/src/views/aiCoach/index.jsx +0 -375
  155. package/app/wujie-vue3-child/src/views/aiCoach/practiceStatus/components/ChartsPanel/index.jsx +0 -121
  156. package/app/wujie-vue3-child/src/views/aiCoach/practiceStatus/components/ChartsPanel/index.module.scss +0 -76
  157. package/app/wujie-vue3-child/src/views/aiCoach/practiceStatus/components/DonutChart/index.jsx +0 -104
  158. package/app/wujie-vue3-child/src/views/aiCoach/practiceStatus/components/PracticeTable/index.jsx +0 -75
  159. package/app/wujie-vue3-child/src/views/aiCoach/practiceStatus/components/PracticeTable/index.module.scss +0 -12
  160. package/app/wujie-vue3-child/src/views/aiCoach/practiceStatus/components/RankBarChart/index.jsx +0 -62
  161. package/app/wujie-vue3-child/src/views/aiCoach/practiceStatus/components/RankBarChart/index.module.scss +0 -43
  162. package/app/wujie-vue3-child/src/views/aiCoach/practiceStatus/components/RankingGroup/index.jsx +0 -29
  163. package/app/wujie-vue3-child/src/views/aiCoach/practiceStatus/components/RankingGroup/index.module.scss +0 -5
  164. package/app/wujie-vue3-child/src/views/aiCoach/practiceStatus/components/RankingList/index.jsx +0 -58
  165. package/app/wujie-vue3-child/src/views/aiCoach/practiceStatus/components/RankingList/index.module.scss +0 -85
  166. package/app/wujie-vue3-child/src/views/aiCoach/practiceStatus/components/ScriptStatsPanel/index.jsx +0 -92
  167. package/app/wujie-vue3-child/src/views/aiCoach/practiceStatus/components/ScriptStatsPanel/index.module.scss +0 -56
  168. package/app/wujie-vue3-child/src/views/aiCoach/practiceStatus/components/StatCardsRow/index.jsx +0 -40
  169. package/app/wujie-vue3-child/src/views/aiCoach/practiceStatus/components/StatCardsRow/index.module.scss +0 -53
  170. package/app/wujie-vue3-child/src/views/aiCoach/practiceStatus/components/echarts/EchartsDonut.jsx +0 -106
  171. package/app/wujie-vue3-child/src/views/aiCoach/practiceStatus/components/echarts/EchartsRankBar.jsx +0 -132
  172. package/app/wujie-vue3-child/src/views/aiCoach/practiceStatus/index.jsx +0 -176
  173. package/app/wujie-vue3-child/src/views/aiCoach/practiceStatus/index.module.scss +0 -96
  174. package/app/wujie-vue3-child/src/views/aiCoach/reportDetail/components/CoachReport/index.jsx +0 -162
  175. package/app/wujie-vue3-child/src/views/aiCoach/reportDetail/components/CoachReport/index.module.scss +0 -16
  176. package/app/wujie-vue3-child/src/views/aiCoach/reportDetail/components/ComprehensiveEvaluation/index.jsx +0 -29
  177. package/app/wujie-vue3-child/src/views/aiCoach/reportDetail/components/ComprehensiveEvaluation/index.module.scss +0 -25
  178. package/app/wujie-vue3-child/src/views/aiCoach/reportDetail/components/DialogueBubble/index.jsx +0 -106
  179. package/app/wujie-vue3-child/src/views/aiCoach/reportDetail/components/DialogueBubble/index.module.scss +0 -164
  180. package/app/wujie-vue3-child/src/views/aiCoach/reportDetail/components/DialogueRecord/index.jsx +0 -182
  181. package/app/wujie-vue3-child/src/views/aiCoach/reportDetail/components/DialogueRecord/index.module.scss +0 -203
  182. package/app/wujie-vue3-child/src/views/aiCoach/reportDetail/components/DimensionDetail/index.jsx +0 -145
  183. package/app/wujie-vue3-child/src/views/aiCoach/reportDetail/components/DimensionDetail/index.module.scss +0 -126
  184. package/app/wujie-vue3-child/src/views/aiCoach/reportDetail/components/DimensionScores/index.jsx +0 -67
  185. package/app/wujie-vue3-child/src/views/aiCoach/reportDetail/components/DimensionScores/index.module.scss +0 -105
  186. package/app/wujie-vue3-child/src/views/aiCoach/reportDetail/components/ReportHeader/index.jsx +0 -81
  187. package/app/wujie-vue3-child/src/views/aiCoach/reportDetail/components/ReportHeader/index.module.scss +0 -47
  188. package/app/wujie-vue3-child/src/views/aiCoach/reportDetail/components/RoleInfo/index.jsx +0 -64
  189. package/app/wujie-vue3-child/src/views/aiCoach/reportDetail/components/RoleInfo/index.module.scss +0 -85
  190. package/app/wujie-vue3-child/src/views/aiCoach/reportDetail/components/ScoreBadge/index.jsx +0 -39
  191. package/app/wujie-vue3-child/src/views/aiCoach/reportDetail/components/ScoreBadge/index.module.scss +0 -44
  192. package/app/wujie-vue3-child/src/views/aiCoach/reportDetail/components/SubDimensionItem/index.jsx +0 -83
  193. package/app/wujie-vue3-child/src/views/aiCoach/reportDetail/components/SubDimensionItem/index.module.scss +0 -101
  194. package/app/wujie-vue3-child/src/views/aiCoach/reportDetail/index.jsx +0 -50
  195. package/app/wujie-vue3-child/src/views/aiCoach/reportDetail/index.module.scss +0 -25
  196. package/app/wujie-vue3-child/src/views/aiCoach/scriptTable/index.jsx +0 -196
  197. package/app/wujie-vue3-child/src/views/aiCoach/scriptTable/index.module.scss +0 -41
  198. package/app/wujie-vue3-child/src/views/aiCoach/scriptTable/inputColumn/index.jsx +0 -183
  199. package/app/wujie-vue3-child/src/views/aiCoach/scriptTable/inputColumn/index.module.scss +0 -115
  200. package/app/wujie-vue3-child/src/views/child-to-parent.vue +0 -117
  201. package/app/wujie-vue3-child/src/views/home.vue +0 -53
  202. package/app/wujie-vue3-child/src/views/jsx/btnSelect/btnSelect.vue +0 -169
  203. package/app/wujie-vue3-child/src/views/jsx/btnSelect/index.vue +0 -69
  204. package/app/wujie-vue3-child/src/views/jsx/com.vue +0 -44
  205. package/app/wujie-vue3-child/src/views/jsx/dialog.jsx +0 -66
  206. package/app/wujie-vue3-child/src/views/jsx/index.vue +0 -72
  207. package/app/wujie-vue3-child/src/views/jsx/props.vue +0 -33
  208. package/app/wujie-vue3-child/src/views/parent-to-child.vue +0 -225
  209. package/app/wujie-vue3-child/src/views/phone-code.vue +0 -318
  210. package/app/wujie-vue3-child/src/views/router-jump.vue +0 -123
  211. package/app/wujie-vue3-child/src/views/test.vue +0 -192
  212. package/app/wujie-vue3-child/vite.config.js +0 -15
@@ -0,0 +1,205 @@
1
+ /**
2
+ * 懒加载级联 - 模拟数据 & 工具
3
+ *
4
+ * 数据约定:扁平数组 [{ id, parentId, label }]
5
+ * - parentId 为空字符串 / null / undefined 即根
6
+ * - 实际工程里这是后端一次性给到的原始数据
7
+ */
8
+
9
+ // 1. 扁平原始数据(3 省 × 3~4 市 × 2~3 区 ≈ 30 节点)
10
+ // eslint-disable-next-line max-lines
11
+ export const flatRegions = [
12
+ // ---- 河北省 ----
13
+ { id: 'hebei', parentId: '', label: '河北省' },
14
+
15
+ { id: 'hebei-sjz', parentId: 'hebei', label: '石家庄市' },
16
+ { id: 'hebei-cd', parentId: 'hebei-sjz', label: '长安区' },
17
+ { id: 'hebei-qx', parentId: 'hebei-sjz', label: '桥西区' },
18
+ { id: 'hebei-yh', parentId: 'hebei-sjz', label: '裕华区' },
19
+
20
+ { id: 'hebei-ts', parentId: 'hebei', label: '唐山市' },
21
+ { id: 'hebei-ln', parentId: 'hebei-ts', label: '路北区' },
22
+ { id: 'hebei-gy', parentId: 'hebei-ts', label: '古冶区' },
23
+ { id: 'hebei-ka', parentId: 'hebei-ts', label: '开平区' },
24
+
25
+ { id: 'hebei-hd', parentId: 'hebei', label: '邯郸市' },
26
+ { id: 'hebei-hsh', parentId: 'hebei-hd', label: '邯山区' },
27
+ { id: 'hebei-cf', parentId: 'hebei-hd', label: '丛台区' },
28
+
29
+ // ---- 山东省 ----
30
+ { id: 'shandong', parentId: '', label: '山东省' },
31
+
32
+ { id: 'shandong-jn', parentId: 'shandong', label: '济南市' },
33
+ { id: 'shandong-lx', parentId: 'shandong-jn', label: '历下区' },
34
+ { id: 'shandong-shz', parentId: 'shandong-jn', label: '市中区' },
35
+ { id: 'shandong-hy', parentId: 'shandong-jn', label: '槐荫区' },
36
+
37
+ { id: 'shandong-qd', parentId: 'shandong', label: '青岛市' },
38
+ { id: 'shandong-shan', parentId: 'shandong-qd', label: '市南区' },
39
+ { id: 'shandong-shib', parentId: 'shandong-qd', label: '市北区' },
40
+ { id: 'shandong-ly', parentId: 'shandong-qd', label: '李沧区' },
41
+
42
+ { id: 'shandong-yk', parentId: 'shandong', label: '烟台市' },
43
+ { id: 'shandong-zs', parentId: 'shandong-yk', label: '芝罘区' },
44
+ { id: 'shandong-fs', parentId: 'shandong-yk', label: '福山区' },
45
+
46
+ // ---- 江苏省 ----
47
+ { id: 'jiangsu', parentId: '', label: '江苏省' },
48
+
49
+ { id: 'jiangsu-nj', parentId: 'jiangsu', label: '南京市' },
50
+ { id: 'jiangsu-xw', parentId: 'jiangsu-nj', label: '玄武区' },
51
+ { id: 'jiangsu-qh', parentId: 'jiangsu-nj', label: '秦淮区' },
52
+ { id: 'jiangsu-jb', parentId: 'jiangsu-nj', label: '建邺区' },
53
+
54
+ { id: 'jiangsu-sz', parentId: 'jiangsu', label: '苏州市' },
55
+ { id: 'jiangsu-gyq', parentId: 'jiangsu-sz', label: '姑苏区' },
56
+ { id: 'jiangsu-wzq', parentId: 'jiangsu-sz', label: '吴中区' },
57
+ { id: 'jiangsu-icq', parentId: 'jiangsu-sz', label: '相城区' },
58
+
59
+ { id: 'jiangsu-wx', parentId: 'jiangsu', label: '无锡市' },
60
+ { id: 'jiangsu-cs', parentId: 'jiangsu-wx', label: '梁溪区' },
61
+ { id: 'jiangsu-bh', parentId: 'jiangsu-wx', label: '滨湖区' },
62
+ ]
63
+
64
+ // 2. 树形数据(同一份数据,只是嵌套形式;用于演示"后端返树形"的情况)
65
+ export const treeRegions = [
66
+ {
67
+ id: 'hebei',
68
+ label: '河北省',
69
+ children: [
70
+ {
71
+ id: 'hebei-sjz',
72
+ label: '石家庄市',
73
+ children: [
74
+ { id: 'hebei-cd', label: '长安区' },
75
+ { id: 'hebei-qx', label: '桥西区' },
76
+ { id: 'hebei-yh', label: '裕华区' },
77
+ ],
78
+ },
79
+ {
80
+ id: 'hebei-ts',
81
+ label: '唐山市',
82
+ children: [
83
+ { id: 'hebei-ln', label: '路北区' },
84
+ { id: 'hebei-gy', label: '古冶区' },
85
+ { id: 'hebei-ka', label: '开平区' },
86
+ ],
87
+ },
88
+ {
89
+ id: 'hebei-hd',
90
+ label: '邯郸市',
91
+ children: [
92
+ { id: 'hebei-hsh', label: '邯山区' },
93
+ { id: 'hebei-cf', label: '丛台区' },
94
+ ],
95
+ },
96
+ ],
97
+ },
98
+ {
99
+ id: 'shandong',
100
+ label: '山东省',
101
+ children: [
102
+ {
103
+ id: 'shandong-jn',
104
+ label: '济南市',
105
+ children: [
106
+ { id: 'shandong-lx', label: '历下区' },
107
+ { id: 'shandong-shz', label: '市中区' },
108
+ { id: 'shandong-hy', label: '槐荫区' },
109
+ ],
110
+ },
111
+ {
112
+ id: 'shandong-qd',
113
+ label: '青岛市',
114
+ children: [
115
+ { id: 'shandong-shan', label: '市南区' },
116
+ { id: 'shandong-shib', label: '市北区' },
117
+ { id: 'shandong-ly', label: '李沧区' },
118
+ ],
119
+ },
120
+ {
121
+ id: 'shandong-yk',
122
+ label: '烟台市',
123
+ children: [
124
+ { id: 'shandong-zs', label: '芝罘区' },
125
+ { id: 'shandong-fs', label: '福山区' },
126
+ ],
127
+ },
128
+ ],
129
+ },
130
+ {
131
+ id: 'jiangsu',
132
+ label: '江苏省',
133
+ children: [
134
+ {
135
+ id: 'jiangsu-nj',
136
+ label: '南京市',
137
+ children: [
138
+ { id: 'jiangsu-xw', label: '玄武区' },
139
+ { id: 'jiangsu-qh', label: '秦淮区' },
140
+ { id: 'jiangsu-jb', label: '建邺区' },
141
+ ],
142
+ },
143
+ {
144
+ id: 'jiangsu-sz',
145
+ label: '苏州市',
146
+ children: [
147
+ { id: 'jiangsu-gyq', label: '姑苏区' },
148
+ { id: 'jiangsu-wzq', label: '吴中区' },
149
+ { id: 'jiangsu-icq', label: '相城区' },
150
+ ],
151
+ },
152
+ {
153
+ id: 'jiangsu-wx',
154
+ label: '无锡市',
155
+ children: [
156
+ { id: 'jiangsu-cs', label: '梁溪区' },
157
+ { id: 'jiangsu-bh', label: '滨湖区' },
158
+ ],
159
+ },
160
+ ],
161
+ },
162
+ ]
163
+
164
+ /**
165
+ * 树 → 扁平
166
+ * @param {Array<{ id, label, children? }>} tree
167
+ * @returns {Array<{ id: string, parentId: string, label: string }>}
168
+ */
169
+ export function flattenTree(tree) {
170
+ const out = []
171
+ const walk = (nodes, parentId) => {
172
+ if (!Array.isArray(nodes)) return
173
+ nodes.forEach(n => {
174
+ if (!n || !n.id) return
175
+ out.push({ id: n.id, parentId: parentId || '', label: n.label })
176
+ if (Array.isArray(n.children) && n.children.length) {
177
+ walk(n.children, n.id)
178
+ }
179
+ })
180
+ }
181
+ walk(tree, '')
182
+ return out
183
+ }
184
+
185
+ /**
186
+ * 模拟接口:返回某个 parentId 的子节点
187
+ * - 真实工程里这会走 axios 请求后端
188
+ * - 这里直接查 flat,加 0~50ms 延时让 loading 状态可见
189
+ */
190
+ export function fetchChildren(parentId, flat = flatRegions) {
191
+ return new Promise(resolve => {
192
+ setTimeout(() => {
193
+ const list = flat.filter(i => (i.parentId || '') === (parentId || ''))
194
+ resolve(list)
195
+ }, Math.random() * 50)
196
+ })
197
+ }
198
+
199
+ // 一些"默认值回显"用的预设叶子 id
200
+ // (新设计:v-model 是单字符串,所以存叶子 id 即可)
201
+ export const DEFAULT_LEAF_QINGDAO_SHIBEI = 'shandong-shib'
202
+ export const DEFAULT_LEAF_JINAN_LIXIA = 'shandong-lx'
203
+ export const DEFAULT_LEAF_SUZHOU_WUZHONG = 'jiangsu-wzq'
204
+ export const DEFAULT_LEAF_NANJING_QINHUAI = 'jiangsu-qh'
205
+ export const DEFAULT_LEAF_HANDAN_CONGTAI = 'hebei-cf'
@@ -0,0 +1,315 @@
1
+ <template>
2
+ <div class="lazy-cascader-demo">
3
+ <PageHeader
4
+ title="级联懒加载(前端拆分)"
5
+ description="模拟后端一次性返回大量扁平数据,前端按层拆分成懒加载体验;v-model 绑定叶子 id 字符串(单值);支持默认值回显、v-if 销毁重建、先有值后到数据、树形转扁平等场景"
6
+ usage="&lt;LazyCascader v-model=&quot;leafId&quot; :data=&quot;flat&quot; :check-strictly=&quot;false&quot; :lazy-delay=&quot;50&quot; /&gt; data 必传扁平数组;checkStrictly 控制是否可选任意级;lazyDelay 是模拟接口延时(ms),0 即同步"
7
+ />
8
+
9
+ <!-- 案例 1: 基础 - 可选任意级 -->
10
+ <el-card class="case" header="案例 1: 基础 - 可选任意级(checkStrictly=true)">
11
+ <LazyCascader
12
+ v-model="case1.value"
13
+ :data="flatRegions"
14
+ :check-strictly="true"
15
+ :lazy-delay="50"
16
+ placeholder="请选择(任意级)"
17
+ />
18
+ <div class="ops">
19
+ <el-button size="mini" @click="showValue('case1')">查看当前值</el-button>
20
+ <el-button size="mini" @click="reset('case1')">重置</el-button>
21
+ <el-button size="mini" type="primary" @click="setDefault('case1')">
22
+ 重置为默认(青岛市市北区)
23
+ </el-button>
24
+ </div>
25
+ <div class="hint">checkStrictly=true 时,可选省/市/区任意一级;v-model 仍是单个 id 字符串</div>
26
+ <pre v-if="case1.display">{{ JSON.stringify(case1.value, null, 2) }}</pre>
27
+ </el-card>
28
+
29
+ <!-- 案例 2: 基础 - 只选最后一级(默认) -->
30
+ <el-card class="case" header="案例 2: 基础 - 只选最后一级(默认 checkStrictly=false)">
31
+ <LazyCascader
32
+ v-model="case2.value"
33
+ :data="flatRegions"
34
+ :check-strictly="false"
35
+ :lazy-delay="50"
36
+ placeholder="请选择(只选叶子)"
37
+ />
38
+ <div class="ops">
39
+ <el-button size="mini" @click="showValue('case2')">查看当前值</el-button>
40
+ <el-button size="mini" @click="reset('case2')">重置</el-button>
41
+ <el-button size="mini" type="primary" @click="setDefault('case2')">
42
+ 重置为默认(青岛市市北区)
43
+ </el-button>
44
+ </div>
45
+ <div class="hint">只能选叶子级;v-model 是叶子 id 字符串;输入框只显示叶子 label</div>
46
+ <pre v-if="case2.display">{{ JSON.stringify(case2.value, null, 2) }}</pre>
47
+ </el-card>
48
+
49
+ <!-- 案例 3: 默认值回显 -->
50
+ <el-card class="case" header="案例 3: 默认值回显(组件挂载时即展开)">
51
+ <LazyCascader
52
+ v-model="case3.value"
53
+ :data="flatRegions"
54
+ :check-strictly="false"
55
+ :lazy-delay="50"
56
+ />
57
+ <div class="ops">
58
+ <el-button size="mini" @click="showValue('case3')">查看当前值</el-button>
59
+ <el-button size="mini" @click="reset('case3')">重置</el-button>
60
+ <el-button size="mini" type="primary" @click="setDefault('case3')">
61
+ 重置为默认(济南市历下区)
62
+ </el-button>
63
+ </div>
64
+ <div class="hint">组件挂载后,应自动展开到"山东省 / 济南市 / 历下区",输入框显示"历下区",可继续点开重选</div>
65
+ <pre v-if="case3.display">{{ JSON.stringify(case3.value, null, 2) }}</pre>
66
+ </el-card>
67
+
68
+ <!-- 案例 4: v-if 销毁/重建 -->
69
+ <el-card class="case" header="案例 4: v-if 控制显示/隐藏(销毁重建后仍能回显)">
70
+ <div class="ops">
71
+ <el-button size="mini" :type="case4.visible ? 'danger' : 'primary'" @click="case4.visible = !case4.visible">
72
+ {{ case4.visible ? '隐藏组件' : '显示组件' }}
73
+ </el-button>
74
+ <el-button size="mini" type="primary" @click="setDefault('case4')">
75
+ 重置为默认(苏州市吴中区)
76
+ </el-button>
77
+ </div>
78
+ <LazyCascader
79
+ v-if="case4.visible"
80
+ v-model="case4.value"
81
+ :data="flatRegions"
82
+ :check-strictly="false"
83
+ :lazy-delay="50"
84
+ />
85
+ <div class="hint">点"隐藏"再点"显示",默认值仍能正确回显(说明 v-if 销毁重建不影响)</div>
86
+ <pre v-if="case4.display">{{ JSON.stringify(case4.value, null, 2) }}</pre>
87
+ </el-card>
88
+
89
+ <!-- 案例 5: 先有默认值,后到数据 -->
90
+ <el-card class="case" header="案例 5: 先有默认值,后到数据(模拟接口延迟)">
91
+ <LazyCascader
92
+ ref="case5Ref"
93
+ v-model="case5.value"
94
+ :data="case5.data"
95
+ :check-strictly="false"
96
+ :lazy-delay="50"
97
+ />
98
+ <div class="ops">
99
+ <el-button size="mini" @click="showValue('case5')">查看当前值</el-button>
100
+ <el-button size="mini" @click="reset('case5')">重置</el-button>
101
+ <el-button size="mini" type="primary" @click="setDefault('case5')">
102
+ 重置为默认(青岛市市北区)
103
+ </el-button>
104
+ <el-button size="mini" type="success" :loading="case5.loading" @click="loadCase5Data">
105
+ 模拟接口返回(1s 后)
106
+ </el-button>
107
+ </div>
108
+ <div class="hint">初始 data=[] 渲染 → 默认值暂时不显示 → 点"模拟接口返回" → data 填上 + 触发组件 reload() → 默认值正确回显</div>
109
+ <pre v-if="case5.display">{{ JSON.stringify(case5.value, null, 2) }}</pre>
110
+ </el-card>
111
+
112
+ <!-- 案例 6: 后端返回的是树形结构 -->
113
+ <el-card class="case" header="案例 6: 后端返的是树形结构(用 flattenTree 转扁平)">
114
+ <LazyCascader
115
+ v-model="case6.value"
116
+ :data="case6.flatData"
117
+ :check-strictly="false"
118
+ :lazy-delay="50"
119
+ />
120
+ <div class="ops">
121
+ <el-button size="mini" @click="showValue('case6')">查看当前值</el-button>
122
+ <el-button size="mini" @click="reset('case6')">重置</el-button>
123
+ <el-button size="mini" type="primary" @click="setDefault('case6')">
124
+ 重置为默认(南京市秦淮区)
125
+ </el-button>
126
+ <el-button size="mini" type="warning" @click="rebuildCase6FromTree">
127
+ 重新调用 flattenTree(演示树 → 扁平)
128
+ </el-button>
129
+ </div>
130
+ <div class="hint">case6.flatData 初始是 flattenTree(treeRegions) 的结果;点"重新调用"再走一遍,组件无感知,继续工作</div>
131
+ <pre v-if="case6.display">{{ JSON.stringify(case6.value, null, 2) }}</pre>
132
+ </el-card>
133
+
134
+ <!-- 案例 7: lazyDelay 控制 -->
135
+ <el-card class="case" header="案例 7: lazyDelay 控制模拟接口延时(默认 50ms)">
136
+ <div class="ops">
137
+ <el-button size="mini" @click="case7.delay = 0">同步(lazyDelay=0)</el-button>
138
+ <el-button size="mini" @click="case7.delay = 200">慢(lazyDelay=200)</el-button>
139
+ <el-button size="mini" @click="case7.delay = 1000">很慢(lazyDelay=1000)</el-button>
140
+ <el-button size="mini" type="primary" @click="setDefault('case7')">
141
+ 重置为默认(邯郸市丛台区)
142
+ </el-button>
143
+ </div>
144
+ <LazyCascader
145
+ v-model="case7.value"
146
+ :data="flatRegions"
147
+ :check-strictly="false"
148
+ :lazy-delay="case7.delay"
149
+ />
150
+ <div class="hint">点击不同按钮感受 loading 效果差异;lazyDelay=0 时完全同步无 loading</div>
151
+ <pre v-if="case7.display">{{ JSON.stringify(case7.value, null, 2) }}</pre>
152
+ </el-card>
153
+
154
+ <!-- 案例 8: showAllLevels 控制输入框显示内容 -->
155
+ <el-card class="case" header="案例 8: showAllLevels 控制输入框显示内容">
156
+ <div class="ops">
157
+ <el-button size="mini" :type="!case8.showAllLevels ? 'primary' : ''" @click="case8.showAllLevels = false">
158
+ 只显示叶子(默认)
159
+ </el-button>
160
+ <el-button size="mini" :type="case8.showAllLevels ? 'primary' : ''" @click="case8.showAllLevels = true">
161
+ 显示完整路径
162
+ </el-button>
163
+ <el-button size="mini" type="primary" @click="setDefault('case8')">
164
+ 重置为默认(唐山市开平区)
165
+ </el-button>
166
+ </div>
167
+ <LazyCascader
168
+ v-model="case8.value"
169
+ :data="flatRegions"
170
+ :check-strictly="false"
171
+ :show-all-levels="case8.showAllLevels"
172
+ :separator="case8.separator"
173
+ />
174
+ <div class="hint">切换按钮,看输入框显示"开平区"还是"河北省 / 唐山市 / 开平区";separator 可自定义分隔符</div>
175
+ <pre v-if="case8.display">{{ JSON.stringify({ value: case8.value, showAllLevels: case8.showAllLevels }, null, 2) }}</pre>
176
+ </el-card>
177
+ </div>
178
+ </template>
179
+
180
+ <script>
181
+ import PageHeader from '@/components/PageHeader/index.vue'
182
+ import LazyCascader from './LazyCascader.vue'
183
+ import {
184
+ flatRegions,
185
+ treeRegions,
186
+ flattenTree,
187
+ DEFAULT_LEAF_JINAN_LIXIA,
188
+ DEFAULT_LEAF_QINGDAO_SHIBEI,
189
+ DEFAULT_LEAF_SUZHOU_WUZHONG,
190
+ DEFAULT_LEAF_NANJING_QINHUAI,
191
+ DEFAULT_LEAF_HANDAN_CONGTAI,
192
+ } from './data.js'
193
+
194
+ const newCase = (leafId) => ({
195
+ value: leafId || '',
196
+ display: false,
197
+ })
198
+
199
+ const newCaseEmpty = () => ({
200
+ value: '',
201
+ data: [],
202
+ loading: false,
203
+ display: false,
204
+ })
205
+
206
+ const newCaseFlat = (initialFlat, defaultLeaf) => ({
207
+ value: defaultLeaf,
208
+ flatData: initialFlat,
209
+ display: false,
210
+ })
211
+
212
+ export default {
213
+ name: 'LazyCascaderDemo',
214
+
215
+ components: { PageHeader, LazyCascader },
216
+
217
+ data() {
218
+ return {
219
+ flatRegions,
220
+ case1: newCase(DEFAULT_LEAF_QINGDAO_SHIBEI),
221
+ case2: newCase(DEFAULT_LEAF_QINGDAO_SHIBEI),
222
+ case3: newCase(DEFAULT_LEAF_JINAN_LIXIA),
223
+ case4: { ...newCase(DEFAULT_LEAF_SUZHOU_WUZHONG), visible: true },
224
+ case5: newCaseEmpty(),
225
+ case6: newCaseFlat(flattenTree(treeRegions), DEFAULT_LEAF_NANJING_QINHUAI),
226
+ case7: { value: DEFAULT_LEAF_HANDAN_CONGTAI, delay: 50, display: false },
227
+ case8: { value: 'hebei-ka', showAllLevels: false, separator: ' / ', display: false },
228
+ }
229
+ },
230
+
231
+ methods: {
232
+ showValue(key) {
233
+ this[key].display = !this[key].display
234
+ },
235
+ reset(key) {
236
+ this[key].value = ''
237
+ },
238
+ setDefault(key) {
239
+ switch (key) {
240
+ case 'case1':
241
+ this.case1.value = DEFAULT_LEAF_QINGDAO_SHIBEI
242
+ break
243
+ case 'case2':
244
+ this.case2.value = DEFAULT_LEAF_QINGDAO_SHIBEI
245
+ break
246
+ case 'case3':
247
+ this.case3.value = DEFAULT_LEAF_JINAN_LIXIA
248
+ break
249
+ case 'case4':
250
+ this.case4.value = DEFAULT_LEAF_SUZHOU_WUZHONG
251
+ break
252
+ case 'case5':
253
+ this.case5.value = DEFAULT_LEAF_QINGDAO_SHIBEI
254
+ break
255
+ case 'case6':
256
+ this.case6.value = DEFAULT_LEAF_NANJING_QINHUAI
257
+ break
258
+ case 'case7':
259
+ this.case7.value = DEFAULT_LEAF_HANDAN_CONGTAI
260
+ break
261
+ case 'case8':
262
+ this.case8.value = 'hebei-ka'
263
+ break
264
+ default:
265
+ break
266
+ }
267
+ },
268
+ loadCase5Data() {
269
+ this.case5.loading = true
270
+ setTimeout(() => {
271
+ this.case5.data = flatRegions
272
+ this.case5.loading = false
273
+ this.$nextTick(() => {
274
+ const ref = this.$refs.case5Ref
275
+ if (ref && typeof ref.reload === 'function') ref.reload()
276
+ })
277
+ }, 1000)
278
+ },
279
+ rebuildCase6FromTree() {
280
+ this.case6.flatData = flattenTree(treeRegions)
281
+ this.$message.success('已重新调用 flattenTree,数据条数 = ' + this.case6.flatData.length)
282
+ },
283
+ },
284
+ }
285
+ </script>
286
+
287
+ <style lang="less" scoped>
288
+ .lazy-cascader-demo {
289
+ padding: 16px;
290
+ .case {
291
+ margin-bottom: 16px;
292
+ }
293
+ .ops {
294
+ margin-top: 12px;
295
+ display: flex;
296
+ flex-wrap: wrap;
297
+ gap: 8px;
298
+ }
299
+ .hint {
300
+ margin-top: 8px;
301
+ color: #909399;
302
+ font-size: 12px;
303
+ line-height: 1.5;
304
+ }
305
+ pre {
306
+ margin-top: 8px;
307
+ padding: 8px 12px;
308
+ background: #f5f7fa;
309
+ border-radius: 4px;
310
+ font-size: 12px;
311
+ color: #303133;
312
+ line-height: 1.6;
313
+ }
314
+ }
315
+ </style>
@@ -0,0 +1,163 @@
1
+ # QuillEditor
2
+
3
+ 基于 Quill2.x + quill-table-better 的 Vue2 富文本编辑器组件,支持表格、图片、全部基础格式。
4
+
5
+ ## 安装依赖
6
+
7
+ 依赖已添加到 `package.json`,首次使用需安装:
8
+
9
+ ```bash
10
+ npm install
11
+ ```
12
+
13
+ 依赖列表:
14
+ - `quill@2.0.2`
15
+ - `quill-table-better@1.2.3`
16
+
17
+ ##快速使用
18
+
19
+ ```vue
20
+ <template>
21
+ <QuillEditor v-model="content" :height="400" />
22
+ </template>
23
+
24
+ <script>
25
+ import QuillEditor from '@/views/element/quillEditor/index.vue';
26
+
27
+ export default {
28
+ components: { QuillEditor },
29
+ data() {
30
+ return { content: '<p>Hello Quill!</p>' };
31
+ },
32
+ };
33
+ </script>
34
+ ```
35
+
36
+ ## Props
37
+
38
+ | Prop | 类型 | 默认值 | 说明 |
39
+ |------|------|--------|------|
40
+ | `value` | String | `''` | 编辑器 HTML 内容,支持 v-model |
41
+ | `placeholder` | String | `'请输入内容...'` | 占位文字 |
42
+ | `readonly` | Boolean | `false` | 是否只读(可看不可改) |
43
+ | `disabled` | Boolean | `false` | 是否禁用(工具栏灰掉,不可编辑) |
44
+ | `height` | String \| Number | `300` | 编辑区高度,数字按 px,字符串按原值 |
45
+ | `toolbarPreset` | String | `'default'` | 工具栏预设:`'simple'`(精简)/ `'default'`(默认)/ `'full'`(全部) |
46
+ | `toolbar` | Array \| Object | `null` | 自定义工具栏配置;传了则**覆盖** `toolbarPreset` |
47
+ | `imageHandler` | Function | `null` | 自定义图片处理函数;不传则走 base64 内联 |
48
+ | `theme` | String | `'snow'` |主题,目前只支持 snow |
49
+
50
+ ## Events
51
+
52
+ |事件 |触发时机 | 参数 |
53
+ |------|---------|------|
54
+ | `input` | 内容变化 | `(html: String)` |
55
+ | `change` | 内容变化(去抖300ms) | `(html: String)` |
56
+ | `ready` | Quill 实例初始化完成 | `(quill: Quill)` |
57
+ | `focus` | 编辑器获得焦点 | `()` |
58
+ | `blur` | 编辑器失去焦点 | `()` |
59
+
60
+ ## Methods(通过 ref 调用)
61
+
62
+ | 方法 | 返回值 | 说明 |
63
+ |------|--------|------|
64
+ | `getHtml()` | String | 获取当前 HTML |
65
+ | `getText()` | String | 获取纯文本 |
66
+ | `setHtml(html)` | - | 设置内容 |
67
+ | `clear()` | - | 清空内容 |
68
+ | `focus()` | - |聚焦 |
69
+ | `getQuill()` | Quill | 获取底层 Quill 实例(高级用法) |
70
+
71
+ ## 自定义图片上传示例
72
+
73
+ ```vue
74
+ <QuillEditor
75
+ v-model="content"
76
+ :image-handler="uploadImage"
77
+ />
78
+
79
+ <script>
80
+ methods: {
81
+ async uploadImage(file, range, quill) {
82
+ const form = new FormData();
83
+ form.append('file', file);
84
+ const res = await fetch('/api/upload', { method: 'POST', body: form });
85
+ const { url } = await res.json();
86
+ quill.insertEmbed(range.index, 'image', url);
87
+ return true; // 返回 true 表示已处理,跳过 base64 回退
88
+ },
89
+ }
90
+ </script>
91
+ ```
92
+
93
+ ## 工具栏预设(toolbarPreset)
94
+
95
+ 通过 `toolbarPreset` 选择内置工具栏,三档从简到全:
96
+
97
+ | 预设 | 适用场景 | 包含按钮 |
98
+ |------|---------|----------|
99
+ | `simple` 精简 | 评论、备注等轻量输入 | 加粗/斜体/下划线、标题(1-3)、有序/无序列表、链接、清除格式 |
100
+ | `default` 默认 | 大多数业务表单(默认值) | 标题/字号、基础格式+删除线、颜色/背景、引用/代码、列表、缩进、对齐、链接/图片、清除、撤销/重做 |
101
+ | `full` 全部 | 文档/文章等富内容 | 在默认基础上再加 上标/下标、视频、表格 |
102
+
103
+ ```vue
104
+ <QuillEditor :toolbar-preset="'simple'" /> <!-- 精简 -->
105
+ <QuillEditor /> <!-- 默认 -->
106
+ <QuillEditor :toolbar-preset="'full'" /> <!-- 全部 -->
107
+ ```
108
+
109
+ > 运行时切换 `toolbarPreset` 需重新挂载组件(给组件加 `:key="preset"`),因为 Quill 工具栏在初始化时一次性构建。
110
+
111
+ 各按钮 token 的功能对照、以及为何预设不含 `font` 字体下拉,见 `toolbar.js` 顶部注释。
112
+
113
+ ### 完全自定义工具栏
114
+
115
+ 传 `toolbar` 数组(优先级高于 `toolbarPreset`):
116
+
117
+ ```javascript
118
+ [
119
+ [{ header: [1, 2, 3, false] }],
120
+ ['bold', 'italic', 'underline'],
121
+ [{ list: 'ordered' }, { list: 'bullet' }],
122
+ ['link', 'clean'],
123
+ ]
124
+ ```
125
+
126
+ ## HTML 源码双向联动
127
+
128
+ 内容用 v-model 双向绑定,可同时在一个 textarea 里编辑 HTML 源码并写回编辑器:
129
+
130
+ ```vue
131
+ <template>
132
+ <QuillEditor v-model="html" />
133
+ <textarea v-model="source" />
134
+ <button @click="html = source">应用源码到编辑器</button>
135
+ </template>
136
+
137
+ <script>
138
+ export default {
139
+ data() {
140
+ return { html: '<p>hello</p>', source: '<p>hello</p>' };
141
+ },
142
+ watch: {
143
+ html(val) { this.source = val; }, // 编辑器变化实时同步到源码框
144
+ },
145
+ };
146
+ </script>
147
+ ```
148
+
149
+ > 写回时 `html = source` 会触发编辑器 `setHtml`,Quill 会对 HTML 做规范化。完整示例见 `example.vue` 卡片②。
150
+
151
+ ##表格功能
152
+
153
+ 点击工具栏的 `table-better`按钮可插入表格,右键单元格可弹出操作菜单(增删行列、合并/拆分、单元格背景色、复制/删除)。
154
+
155
+ 表格配置见 `toolbar.js` 中 `defaultTableBetterConfig`:
156
+ - `language: 'zh_CN'` 中文界面
157
+ - `toolbarTable: true` 显示表格工具栏
158
+ - `menus`包含 column/row/merge/table/cell/wrap/copy/delete全部操作
159
+
160
+ ##主题与样式
161
+
162
+ 组件内部已 `import 'quill/dist/quill.snow.css'` 和 `quill-table-better/dist/quill-table-better.css`,
163
+ **不需要在 main.js 全局引入**。外层容器样式 scoped,不污染全局。