按用户要求,把 NoticeFront 里这两块 UI 原样搬到助手表单(不再自制近似控件):
- 正文 → wangEditor v5(@wangeditor/editor + @wangeditor/editor-for-vue)。
提交给后端的是 **HTML 字符串**,与模块页 NoticeRequest.content 一致;
插图上传走 /api/file/UploadImage、预览走 /api/file/Preview?id=(地址由后端下发,
统一由编排层拼模块前缀)。异步 chunk 加载,避免把 800KB 编辑器塞进首屏。
- 通知对象 → 1:1 搬运模块页 components/UserCheckInput.vue + UserCheckModal.vue:
radio「全部用户 / 指定用户」+ 只读回显 + +/清空 + 选人弹窗(左 zTree 组织树勾选级联 /
右已选列表 / 全选 / 删除选中 / 按名字搜索回勾);v-model 值是字符串("all" 或 id 逗号串)。
- 全局依赖 jQuery + zTree 按模块页的方式由 index.html 全局加载(public/libs/),
不用 npm 引入 —— 保证加载顺序(zTree 必须在 jQuery 之后)与样式/图标与模块页完全一致。
已确认模块页的 lodash 实际未被使用,故不引入。
FormCard 新增两个 widget 分支,每一处都对齐模块页口径:
- richtext:初值取字符串、提交原 HTML,只读摘要用剥标签后的纯文本;
- userpicker:初值数组 join / 字符串原样(空则 allToken)、提交字符串;
★ 跳过下拉字典的「拍平 + 取值对齐」—— 那条路会把人员 id 串判成「认不出的值」清掉,
等于把模型/用户已经选好的人悄悄抹掉;
- 两者都独占整行(自适应多列会把单选组挤到换行)。
验证:.scratch/verify-notice-publish-widgets.mjs 真机 38/38(含正文真实打字、
两个数据源页签、勾选回显、以及提交体 receiveList 必须是字符串 "101"、content 必须是 <p>…)。
npm run build 通过(RichTextEditor 单独成 chunk)。
@@ -26,6 +26,14 @@
|
||||
})();
|
||||
</script>
|
||||
<title><%- title %></title>
|
||||
<!-- 业务模块组件的全局依赖(照搬 NoticeFront/index.html 的加载方式)。
|
||||
★ zTree 是 jQuery 插件,**必须**在 jquery 之后、且在应用脚本之前加载:
|
||||
组件里的 `$.fn.zTree` 是在方法调用期求值的,晚一步就会 `undefined.init`。
|
||||
为什么不用 npm 引入:模块前端本来就是用 public/libs 下的静态文件全局加载的,
|
||||
搬过来保持同一种加载方式,行为与模块页完全一致(含 zTree 自带样式与图标)。 -->
|
||||
<link rel="stylesheet" href="./libs/ztree/zTreeStyle.css" />
|
||||
<script src="./libs/jquery.min.js"></script>
|
||||
<script src="./libs/ztree/jquery.ztree.all.min.js"></script>
|
||||
<%- injectScript %>
|
||||
</head>
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
"name": "agent-front",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||
"alova": "^3.4.0",
|
||||
"lodash": "^4.17.21",
|
||||
"mitt": "^3.0.1",
|
||||
@@ -112,6 +114,15 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.29.10",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.29.10.tgz",
|
||||
"integrity": "sha512-NpugTzbKgGrNe45Ui32cN8vCFFMxVtP/9rtfwv0nP5FRDAjvYBxJcLVpYA9mr1jxP6cnIT7x9LUzuDTbWYYNRA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.29.8",
|
||||
"license": "MIT",
|
||||
@@ -1338,11 +1349,23 @@
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@transloadit/prettier-bytes": {
|
||||
"version": "0.0.7",
|
||||
"resolved": "https://registry.npmmirror.com/@transloadit/prettier-bytes/-/prettier-bytes-0.0.7.tgz",
|
||||
"integrity": "sha512-VeJbUb0wEKbcwaSlj5n+LscBl9IPgLPkHVGBkh00cztv6X4L/TJXK58LzFuBKX7/GAfiGhIwH67YTLTlzvIzBA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.9",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/event-emitter": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmmirror.com/@types/event-emitter/-/event-emitter-0.3.5.tgz",
|
||||
"integrity": "sha512-zx2/Gg0Eg7gwEiOIIh5w9TrhKKTeQh7CPCOPNc0el4pLSwzebA8SmnHwZs2dWlLONvyulykSwGSQxQHLhjGLvQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.17.26",
|
||||
"dev": true,
|
||||
@@ -1680,6 +1703,61 @@
|
||||
"vite": "^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@uppy/companion-client": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmmirror.com/@uppy/companion-client/-/companion-client-2.2.2.tgz",
|
||||
"integrity": "sha512-5mTp2iq97/mYSisMaBtFRry6PTgZA6SIL7LePteOV5x0/DxKfrZW3DEiQERJmYpHzy7k8johpm2gHnEKto56Og==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@uppy/utils": "^4.1.2",
|
||||
"namespace-emitter": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@uppy/core": {
|
||||
"version": "2.3.4",
|
||||
"resolved": "https://registry.npmmirror.com/@uppy/core/-/core-2.3.4.tgz",
|
||||
"integrity": "sha512-iWAqppC8FD8mMVqewavCz+TNaet6HPXitmGXpGGREGrakZ4FeuWytVdrelydzTdXx6vVKkOmI2FLztGg73sENQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@transloadit/prettier-bytes": "0.0.7",
|
||||
"@uppy/store-default": "^2.1.1",
|
||||
"@uppy/utils": "^4.1.3",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"mime-match": "^1.0.2",
|
||||
"namespace-emitter": "^2.0.1",
|
||||
"nanoid": "^3.1.25",
|
||||
"preact": "^10.5.13"
|
||||
}
|
||||
},
|
||||
"node_modules/@uppy/store-default": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/@uppy/store-default/-/store-default-2.1.1.tgz",
|
||||
"integrity": "sha512-xnpTxvot2SeAwGwbvmJ899ASk5tYXhmZzD/aCFsXePh/v8rNvR2pKlcQUH7cF/y4baUGq3FHO/daKCok/mpKqQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@uppy/utils": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/@uppy/utils/-/utils-4.1.3.tgz",
|
||||
"integrity": "sha512-nTuMvwWYobnJcytDO3t+D6IkVq/Qs4Xv3vyoEZ+Iaf8gegZP+rEyoaFT2CK5XLRMienPyqRqNbIfRuFaOWSIFw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lodash.throttle": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@uppy/xhr-upload": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/@uppy/xhr-upload/-/xhr-upload-2.1.3.tgz",
|
||||
"integrity": "sha512-YWOQ6myBVPs+mhNjfdWsQyMRWUlrDLMoaG7nvf/G6Y3GKZf8AyjFDjvvJ49XWQ+DaZOftGkHmF1uh/DBeGivJQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@uppy/companion-client": "^2.2.2",
|
||||
"@uppy/utils": "^4.1.2",
|
||||
"nanoid": "^3.1.25"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@uppy/core": "^2.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitejs/plugin-vue": {
|
||||
"version": "5.2.4",
|
||||
"resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
|
||||
@@ -1879,6 +1957,165 @@
|
||||
"version": "3.5.43",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@wangeditor/basic-modules": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor/basic-modules/-/basic-modules-1.1.7.tgz",
|
||||
"integrity": "sha512-cY9CPkLJaqF05STqfpZKWG4LpxTMeGSIIF1fHvfm/mz+JXatCagjdkbxdikOuKYlxDdeqvOeBmsUBItufDLXZg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-url": "^1.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@wangeditor/core": "1.x",
|
||||
"dom7": "^3.0.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"nanoid": "^3.2.0",
|
||||
"slate": "^0.72.0",
|
||||
"snabbdom": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor/code-highlight": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor/code-highlight/-/code-highlight-1.0.3.tgz",
|
||||
"integrity": "sha512-iazHwO14XpCuIWJNTQTikqUhGKyqj+dUNWJ9288Oym9M2xMVHvnsOmDU2sgUDWVy+pOLojReMPgXCsvvNlOOhw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prismjs": "^1.23.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@wangeditor/core": "1.x",
|
||||
"dom7": "^3.0.0",
|
||||
"slate": "^0.72.0",
|
||||
"snabbdom": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor/core": {
|
||||
"version": "1.1.19",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor/core/-/core-1.1.19.tgz",
|
||||
"integrity": "sha512-KevkB47+7GhVszyYF2pKGKtCSj/YzmClsD03C3zTt+9SR2XWT5T0e3yQqg8baZpcMvkjs1D8Dv4fk8ok/UaS2Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/event-emitter": "^0.3.3",
|
||||
"event-emitter": "^0.3.5",
|
||||
"html-void-elements": "^2.0.0",
|
||||
"i18next": "^20.4.0",
|
||||
"scroll-into-view-if-needed": "^2.2.28",
|
||||
"slate-history": "^0.66.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@uppy/core": "^2.1.1",
|
||||
"@uppy/xhr-upload": "^2.0.3",
|
||||
"dom7": "^3.0.0",
|
||||
"is-hotkey": "^0.2.0",
|
||||
"lodash.camelcase": "^4.3.0",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.foreach": "^4.5.0",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"lodash.toarray": "^4.4.0",
|
||||
"nanoid": "^3.2.0",
|
||||
"slate": "^0.72.0",
|
||||
"snabbdom": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor/editor": {
|
||||
"version": "5.1.23",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor/editor/-/editor-5.1.23.tgz",
|
||||
"integrity": "sha512-0RxfeVTuK1tktUaPROnCoFfaHVJpRAIE2zdS0mpP+vq1axVQpLjM8+fCvKzqYIkH0Pg+C+44hJpe3VVroSkEuQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@uppy/core": "^2.1.1",
|
||||
"@uppy/xhr-upload": "^2.0.3",
|
||||
"@wangeditor/basic-modules": "^1.1.7",
|
||||
"@wangeditor/code-highlight": "^1.0.3",
|
||||
"@wangeditor/core": "^1.1.19",
|
||||
"@wangeditor/list-module": "^1.0.5",
|
||||
"@wangeditor/table-module": "^1.1.4",
|
||||
"@wangeditor/upload-image-module": "^1.0.2",
|
||||
"@wangeditor/video-module": "^1.1.4",
|
||||
"dom7": "^3.0.0",
|
||||
"is-hotkey": "^0.2.0",
|
||||
"lodash.camelcase": "^4.3.0",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.foreach": "^4.5.0",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"lodash.toarray": "^4.4.0",
|
||||
"nanoid": "^3.2.0",
|
||||
"slate": "^0.72.0",
|
||||
"snabbdom": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor/editor-for-vue": {
|
||||
"version": "5.1.12",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor/editor-for-vue/-/editor-for-vue-5.1.12.tgz",
|
||||
"integrity": "sha512-0Ds3D8I+xnpNWezAeO7HmPRgTfUxHLMd9JKcIw+QzvSmhC5xUHbpCcLU+KLmeBKTR/zffnS5GQo6qi3GhTMJWQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@wangeditor/editor": ">=5.1.0",
|
||||
"vue": "^3.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor/list-module": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor/list-module/-/list-module-1.0.5.tgz",
|
||||
"integrity": "sha512-uDuYTP6DVhcYf7mF1pTlmNn5jOb4QtcVhYwSSAkyg09zqxI1qBqsfUnveeDeDqIuptSJhkh81cyxi+MF8sEPOQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@wangeditor/core": "1.x",
|
||||
"dom7": "^3.0.0",
|
||||
"slate": "^0.72.0",
|
||||
"snabbdom": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor/table-module": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor/table-module/-/table-module-1.1.4.tgz",
|
||||
"integrity": "sha512-5saanU9xuEocxaemGdNi9t8MCDSucnykEC6jtuiT72kt+/Hhh4nERYx1J20OPsTCCdVr7hIyQenFD1iSRkIQ6w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@wangeditor/core": "1.x",
|
||||
"dom7": "^3.0.0",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"nanoid": "^3.2.0",
|
||||
"slate": "^0.72.0",
|
||||
"snabbdom": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor/upload-image-module": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor/upload-image-module/-/upload-image-module-1.0.2.tgz",
|
||||
"integrity": "sha512-z81lk/v71OwPDYeQDxj6cVr81aDP90aFuywb8nPD6eQeECtOymrqRODjpO6VGvCVxVck8nUxBHtbxKtjgcwyiA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@uppy/core": "^2.0.3",
|
||||
"@uppy/xhr-upload": "^2.0.3",
|
||||
"@wangeditor/basic-modules": "1.x",
|
||||
"@wangeditor/core": "1.x",
|
||||
"dom7": "^3.0.0",
|
||||
"lodash.foreach": "^4.5.0",
|
||||
"slate": "^0.72.0",
|
||||
"snabbdom": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor/video-module": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor/video-module/-/video-module-1.1.4.tgz",
|
||||
"integrity": "sha512-ZdodDPqKQrgx3IwWu4ZiQmXI8EXZ3hm2/fM6E3t5dB8tCaIGWQZhmqd6P5knfkRAd3z2+YRSRbxOGfoRSp/rLg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@uppy/core": "^2.1.4",
|
||||
"@uppy/xhr-upload": "^2.0.7",
|
||||
"@wangeditor/core": "1.x",
|
||||
"dom7": "^3.0.0",
|
||||
"nanoid": "^3.2.0",
|
||||
"slate": "^0.72.0",
|
||||
"snabbdom": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.19.0",
|
||||
"dev": true,
|
||||
@@ -2456,6 +2693,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/compute-scroll-into-view": {
|
||||
"version": "1.0.20",
|
||||
"resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz",
|
||||
"integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/computeds": {
|
||||
"version": "0.0.1",
|
||||
"dev": true,
|
||||
@@ -2606,6 +2849,19 @@
|
||||
"version": "3.2.3",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/d": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/d/-/d-1.0.2.tgz",
|
||||
"integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"es5-ext": "^0.10.64",
|
||||
"type": "^2.7.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
}
|
||||
},
|
||||
"node_modules/data-view-buffer": {
|
||||
"version": "1.0.2",
|
||||
"dev": true,
|
||||
@@ -2813,6 +3069,15 @@
|
||||
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/dom7": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/dom7/-/dom7-3.0.0.tgz",
|
||||
"integrity": "sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ssr-window": "^3.0.0-alpha.1"
|
||||
}
|
||||
},
|
||||
"node_modules/domelementtype": {
|
||||
"version": "2.3.0",
|
||||
"dev": true,
|
||||
@@ -3078,11 +3343,51 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/es5-ext": {
|
||||
"version": "0.10.64",
|
||||
"resolved": "https://registry.npmmirror.com/es5-ext/-/es5-ext-0.10.64.tgz",
|
||||
"integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"es6-iterator": "^2.0.3",
|
||||
"es6-symbol": "^3.1.3",
|
||||
"esniff": "^2.0.1",
|
||||
"next-tick": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/es6-iterator": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/es6-iterator/-/es6-iterator-2.0.3.tgz",
|
||||
"integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"d": "1",
|
||||
"es5-ext": "^0.10.35",
|
||||
"es6-symbol": "^3.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/es6-promise": {
|
||||
"version": "3.3.1",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/es6-symbol": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmmirror.com/es6-symbol/-/es6-symbol-3.1.4.tgz",
|
||||
"integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d": "^1.0.2",
|
||||
"ext": "^1.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.12",
|
||||
"dev": true,
|
||||
@@ -3499,6 +3804,21 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/esniff": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/esniff/-/esniff-2.0.1.tgz",
|
||||
"integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d": "^1.0.1",
|
||||
"es5-ext": "^0.10.62",
|
||||
"event-emitter": "^0.3.5",
|
||||
"type": "^2.7.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/estree-walker": {
|
||||
"version": "3.0.3",
|
||||
"dev": true,
|
||||
@@ -3515,6 +3835,16 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/event-emitter": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmmirror.com/event-emitter/-/event-emitter-0.3.5.tgz",
|
||||
"integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"d": "1",
|
||||
"es5-ext": "~0.10.14"
|
||||
}
|
||||
},
|
||||
"node_modules/expand-brackets": {
|
||||
"version": "2.1.4",
|
||||
"dev": true,
|
||||
@@ -3573,6 +3903,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ext": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmmirror.com/ext/-/ext-1.7.0.tgz",
|
||||
"integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"type": "^2.7.2"
|
||||
}
|
||||
},
|
||||
"node_modules/extend-shallow": {
|
||||
"version": "2.0.1",
|
||||
"dev": true,
|
||||
@@ -4190,6 +4529,16 @@
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/html-void-elements": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/html-void-elements/-/html-void-elements-2.0.1.tgz",
|
||||
"integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/htmlparser2": {
|
||||
"version": "3.10.1",
|
||||
"dev": true,
|
||||
@@ -4263,6 +4612,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "20.6.1",
|
||||
"resolved": "https://registry.npmmirror.com/i18next/-/i18next-20.6.1.tgz",
|
||||
"integrity": "sha512-yCMYTMEJ9ihCwEQQ3phLo7I/Pwycf8uAx+sRHwwk5U9Aui/IZYgQRyMqXafQOw5QQ7DM1Z+WyEXWIqSuJHhG2A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"dev": true,
|
||||
@@ -4293,6 +4651,16 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/immer": {
|
||||
"version": "9.0.21",
|
||||
"resolved": "https://registry.npmmirror.com/immer/-/immer-9.0.21.tgz",
|
||||
"integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/immer"
|
||||
}
|
||||
},
|
||||
"node_modules/immutable": {
|
||||
"version": "5.1.9",
|
||||
"license": "MIT"
|
||||
@@ -4564,6 +4932,12 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-hotkey": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/is-hotkey/-/is-hotkey-0.2.0.tgz",
|
||||
"integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-interactive": {
|
||||
"version": "1.0.0",
|
||||
"dev": true,
|
||||
@@ -4742,6 +5116,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-url": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmmirror.com/is-url/-/is-url-1.2.4.tgz",
|
||||
"integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-weakmap": {
|
||||
"version": "2.0.2",
|
||||
"dev": true,
|
||||
@@ -4946,14 +5326,51 @@
|
||||
"version": "4.18.1",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.camelcase": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
||||
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.chunk": {
|
||||
"version": "4.2.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.clonedeep": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.debounce": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.foreach": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz",
|
||||
"integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isequal": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
|
||||
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.throttle": {
|
||||
"version": "4.1.1",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.toarray": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz",
|
||||
"integrity": "sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/log-symbols": {
|
||||
"version": "4.1.0",
|
||||
"dev": true,
|
||||
@@ -5089,6 +5506,15 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/mime-match/-/mime-match-1.0.2.tgz",
|
||||
"integrity": "sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"wildcard": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-fn": {
|
||||
"version": "2.1.0",
|
||||
"dev": true,
|
||||
@@ -5177,6 +5603,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/namespace-emitter": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/namespace-emitter/-/namespace-emitter-2.0.1.tgz",
|
||||
"integrity": "sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.20",
|
||||
"funding": [
|
||||
@@ -5270,6 +5702,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/next-tick": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/next-tick/-/next-tick-1.1.0.tgz",
|
||||
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/no-case": {
|
||||
"version": "3.0.4",
|
||||
"dev": true,
|
||||
@@ -5913,6 +6351,24 @@
|
||||
"posthtml-render": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/preact": {
|
||||
"version": "10.29.8",
|
||||
"resolved": "https://registry.npmmirror.com/preact/-/preact-10.29.8.tgz",
|
||||
"integrity": "sha512-ej2aVZ+vZ8WO7tvlQWRM9N63A0KzF9q4mWJfDUHgYaIofWY9hu74QdnQrjoPMmZi2/nZ5gN0bJCQF49xQqx09Q==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/preact"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"preact-render-to-string": ">=5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"preact-render-to-string": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.9.9",
|
||||
"dev": true,
|
||||
@@ -5927,6 +6383,15 @@
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/prismjs": {
|
||||
"version": "1.30.0",
|
||||
"resolved": "https://registry.npmmirror.com/prismjs/-/prismjs-1.30.0.tgz",
|
||||
"integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/quansync": {
|
||||
"version": "1.0.0",
|
||||
"dev": true,
|
||||
@@ -6371,6 +6836,15 @@
|
||||
"node": ">=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/scroll-into-view-if-needed": {
|
||||
"version": "2.2.31",
|
||||
"resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz",
|
||||
"integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"compute-scroll-into-view": "^1.0.20"
|
||||
}
|
||||
},
|
||||
"node_modules/scule": {
|
||||
"version": "1.3.0",
|
||||
"dev": true,
|
||||
@@ -6594,6 +7068,56 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/slate": {
|
||||
"version": "0.72.8",
|
||||
"resolved": "https://registry.npmmirror.com/slate/-/slate-0.72.8.tgz",
|
||||
"integrity": "sha512-/nJwTswQgnRurpK+bGJFH1oM7naD5qDmHd89JyiKNT2oOKD8marW0QSBtuFnwEbL5aGCS8AmrhXQgNOsn4osAw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"immer": "^9.0.6",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"tiny-warning": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/slate-history": {
|
||||
"version": "0.66.0",
|
||||
"resolved": "https://registry.npmmirror.com/slate-history/-/slate-history-0.66.0.tgz",
|
||||
"integrity": "sha512-6MWpxGQZiMvSINlCbMW43E2YBSVMCMCIwQfBzGssjWw4kb0qfvj0pIdblWNRQZD0hR6WHP+dHHgGSeVdMWzfng==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-plain-object": "^5.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"slate": ">=0.65.3"
|
||||
}
|
||||
},
|
||||
"node_modules/slate-history/node_modules/is-plain-object": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-5.1.0.tgz",
|
||||
"integrity": "sha512-bUi/yjmtKYcRVUtWRGr0UA6xEFh2I6zWUwMrUXB3s7bmYCaZ8a+0ZsTRkrawh/mzlSD1Y0Ph8bp/U+TvBpWDNw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/slate/node_modules/is-plain-object": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-5.1.0.tgz",
|
||||
"integrity": "sha512-bUi/yjmtKYcRVUtWRGr0UA6xEFh2I6zWUwMrUXB3s7bmYCaZ8a+0ZsTRkrawh/mzlSD1Y0Ph8bp/U+TvBpWDNw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/snabbdom": {
|
||||
"version": "3.6.4",
|
||||
"resolved": "https://registry.npmmirror.com/snabbdom/-/snabbdom-3.6.4.tgz",
|
||||
"integrity": "sha512-VmxEfuw1/Y/eFj5VtMhYnukExpYiPkNzoo3+N3qwAOUDMl8wXgbli5ebR+j0knE3lZ/0eYskLxNcX64uy10N9w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/snapdragon": {
|
||||
"version": "0.8.2",
|
||||
"dev": true,
|
||||
@@ -6785,6 +7309,12 @@
|
||||
"node": ">=20.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ssr-window": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/ssr-window/-/ssr-window-3.0.0.tgz",
|
||||
"integrity": "sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/stable": {
|
||||
"version": "0.1.8",
|
||||
"dev": true,
|
||||
@@ -7264,6 +7794,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tiny-warning": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/tiny-warning/-/tiny-warning-1.0.3.tgz",
|
||||
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tinycolor2": {
|
||||
"version": "1.6.0",
|
||||
"license": "MIT"
|
||||
@@ -7409,6 +7945,12 @@
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/type": {
|
||||
"version": "2.7.3",
|
||||
"resolved": "https://registry.npmmirror.com/type/-/type-2.7.3.tgz",
|
||||
"integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/type-level-regexp": {
|
||||
"version": "0.1.17",
|
||||
"dev": true,
|
||||
@@ -8369,6 +8911,12 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/wildcard": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/wildcard/-/wildcard-1.1.2.tgz",
|
||||
"integrity": "sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/wordwrap": {
|
||||
"version": "1.0.0",
|
||||
"dev": true,
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
"api:gen": "alova gen -f"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||
"alova": "^3.4.0",
|
||||
"lodash": "^4.17.21",
|
||||
"mitt": "^3.0.1",
|
||||
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 933 B |
|
After Width: | Height: | Size: 5.4 KiB |
@@ -0,0 +1,378 @@
|
||||
/*-------------------------------------
|
||||
zTree Style
|
||||
|
||||
version: 3.4
|
||||
author: Hunter.z
|
||||
email: hunter.z@263.net
|
||||
website: http://code.google.com/p/jquerytree/
|
||||
|
||||
-------------------------------------*/
|
||||
.ztree * {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-family: Verdana, Arial, Helvetica, AppleGothic, sans-serif
|
||||
}
|
||||
|
||||
.ztree {
|
||||
margin: 0;
|
||||
padding: 5px;
|
||||
color: #333
|
||||
}
|
||||
|
||||
.ztree li {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
line-height: 17px;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
outline: 0
|
||||
}
|
||||
|
||||
.ztree li ul {
|
||||
margin: 0;
|
||||
padding: 0 0 0 18px
|
||||
}
|
||||
|
||||
.ztree li ul.line {
|
||||
background: url(./img/line_conn.png) 0 0 repeat-y;
|
||||
}
|
||||
|
||||
.ztree li a {
|
||||
padding-right: 3px;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
height: 21px;
|
||||
color: #333;
|
||||
background-color: transparent;
|
||||
text-decoration: none;
|
||||
vertical-align: top;
|
||||
display: inline-block
|
||||
}
|
||||
|
||||
.ztree li a:hover {
|
||||
text-decoration: underline
|
||||
}
|
||||
|
||||
.ztree li a.curSelectedNode {
|
||||
padding-top: 0px;
|
||||
background-color: #e5e5e5;
|
||||
color: black;
|
||||
height: 21px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.ztree li a.curSelectedNode_Edit {
|
||||
padding-top: 0px;
|
||||
background-color: #e5e5e5;
|
||||
color: black;
|
||||
height: 21px;
|
||||
border: 1px #666 solid;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.ztree li a.tmpTargetNode_inner {
|
||||
padding-top: 0px;
|
||||
background-color: #aaa;
|
||||
color: white;
|
||||
height: 21px;
|
||||
border: 1px #666 solid;
|
||||
opacity: 0.8;
|
||||
filter: alpha(opacity=80)
|
||||
}
|
||||
|
||||
.ztree li a.tmpTargetNode_prev {}
|
||||
|
||||
.ztree li a.tmpTargetNode_next {}
|
||||
|
||||
.ztree li a input.rename {
|
||||
height: 14px;
|
||||
width: 80px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
border: 1px #585956 solid;
|
||||
*border: 0px
|
||||
}
|
||||
|
||||
.ztree li span {
|
||||
line-height: 21px;
|
||||
margin-right: 2px
|
||||
}
|
||||
|
||||
.ztree li span.button {
|
||||
line-height: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
border: 0 none;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
background-color: transparent;
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: scroll;
|
||||
background-image: url("./img/bootstrap.png");
|
||||
}
|
||||
|
||||
.ztree li span.button.chk {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
margin: 0 2px;
|
||||
cursor: auto
|
||||
}
|
||||
|
||||
.ztree li span.button.chk.checkbox_false_full {
|
||||
background-position: -5px -5px;
|
||||
}
|
||||
|
||||
.ztree li span.button.chk.checkbox_false_full_focus {
|
||||
background-position: -5px -26px;
|
||||
}
|
||||
|
||||
.ztree li span.button.chk.checkbox_false_part {
|
||||
background-position: -5px -48px;
|
||||
}
|
||||
|
||||
.ztree li span.button.chk.checkbox_false_part_focus {
|
||||
background-position: -5px -68px;
|
||||
}
|
||||
|
||||
.ztree li span.button.chk.checkbox_false_disable {
|
||||
background-position: -5px -89px;
|
||||
}
|
||||
|
||||
.ztree li span.button.chk.checkbox_true_full {
|
||||
background-position: -26px -5px;
|
||||
}
|
||||
|
||||
.ztree li span.button.chk.checkbox_true_full_focus {
|
||||
background-position: -26px -26px;
|
||||
}
|
||||
|
||||
.ztree li span.button.chk.checkbox_true_part {
|
||||
background-position: -26px -48px;
|
||||
}
|
||||
|
||||
.ztree li span.button.chk.checkbox_true_part_focus {
|
||||
background-position: -26px -68px;
|
||||
}
|
||||
|
||||
.ztree li span.button.chk.checkbox_true_disable {
|
||||
background-position: -26px -89px;
|
||||
}
|
||||
|
||||
.ztree li span.button.chk.radio_false_full {
|
||||
background-position: -47px -5px;
|
||||
}
|
||||
|
||||
.ztree li span.button.chk.radio_false_full_focus {
|
||||
background-position: -47px -26px;
|
||||
}
|
||||
|
||||
.ztree li span.button.chk.radio_false_part {
|
||||
background-position: -47px -47px;
|
||||
}
|
||||
|
||||
.ztree li span.button.chk.radio_false_part_focus {
|
||||
background-position: -47px -68px;
|
||||
}
|
||||
|
||||
.ztree li span.button.chk.radio_false_disable {
|
||||
background-position: -47px -89px;
|
||||
}
|
||||
|
||||
.ztree li span.button.chk.radio_true_full {
|
||||
background-position: -68px -5px;
|
||||
}
|
||||
|
||||
.ztree li span.button.chk.radio_true_full_focus {
|
||||
background-position: -68px -26px;
|
||||
}
|
||||
|
||||
.ztree li span.button.chk.radio_true_part {
|
||||
background-position: -68px -47px;
|
||||
}
|
||||
|
||||
.ztree li span.button.chk.radio_true_part_focus {
|
||||
background-position: -68px -68px;
|
||||
}
|
||||
|
||||
.ztree li span.button.chk.radio_true_disable {
|
||||
background-position: -68px -89px;
|
||||
}
|
||||
|
||||
.ztree li span.button.switch {
|
||||
width: 21px;
|
||||
height: 21px
|
||||
}
|
||||
|
||||
.ztree li span.button.root_open {
|
||||
background-position: -105px -63px
|
||||
}
|
||||
|
||||
.ztree li span.button.root_close {
|
||||
background-position: -126px -63px
|
||||
}
|
||||
|
||||
.ztree li span.button.roots_open {
|
||||
background-position: -105px 0;
|
||||
}
|
||||
|
||||
.ztree li span.button.roots_close {
|
||||
background-position: -126px 0;
|
||||
}
|
||||
|
||||
.ztree li span.button.center_open {
|
||||
background-position: -105px -21px;
|
||||
}
|
||||
|
||||
.ztree li span.button.center_close {
|
||||
background-position: -126px -21px;
|
||||
}
|
||||
|
||||
.ztree li span.button.bottom_open {
|
||||
background-position: -105px -42px;
|
||||
}
|
||||
|
||||
.ztree li span.button.bottom_close {
|
||||
background-position: -126px -42px;
|
||||
}
|
||||
|
||||
.ztree li span.button.noline_open {
|
||||
background-position: -105px -84px;
|
||||
}
|
||||
|
||||
.ztree li span.button.noline_close {
|
||||
background-position: -126px -84px;
|
||||
}
|
||||
|
||||
.ztree li span.button.root_docu {
|
||||
background-position: -105px -63px;
|
||||
}
|
||||
|
||||
.ztree li span.button.roots_docu {
|
||||
background-position: -84px 0;
|
||||
}
|
||||
|
||||
.ztree li span.button.center_docu {
|
||||
background-position: -84px -21px;
|
||||
}
|
||||
|
||||
.ztree li span.button.bottom_docu {
|
||||
background-position: -84px -42px;
|
||||
}
|
||||
|
||||
.ztree li span.button.noline_docu {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.ztree li span.button.ico_open {
|
||||
margin-right: 2px;
|
||||
background-position: -147px -21px;
|
||||
vertical-align: top;
|
||||
*vertical-align: middle
|
||||
}
|
||||
|
||||
.ztree li span.button.ico_close {
|
||||
margin-right: 2px;
|
||||
margin-right: 2px;
|
||||
background-position: -147px 0;
|
||||
vertical-align: top;
|
||||
*vertical-align: middle
|
||||
}
|
||||
|
||||
.ztree li span.button.ico_docu {
|
||||
margin-right: 2px;
|
||||
background-position: -147px -43px;
|
||||
vertical-align: top;
|
||||
*vertical-align: middle
|
||||
}
|
||||
|
||||
.ztree li span.button.edit {
|
||||
margin-left: 2px;
|
||||
margin-right: -1px;
|
||||
background-position: -189px -21px;
|
||||
vertical-align: top;
|
||||
*vertical-align: middle
|
||||
}
|
||||
|
||||
.ztree li span.button.edit:hover {
|
||||
background-position: -168px -21px;
|
||||
}
|
||||
|
||||
.ztree li span.button.remove {
|
||||
margin-left: 2px;
|
||||
margin-right: -1px;
|
||||
background-position: -189px -42px;
|
||||
vertical-align: top;
|
||||
*vertical-align: middle
|
||||
}
|
||||
|
||||
.ztree li span.button.remove:hover {
|
||||
background-position: -168px -42px;
|
||||
}
|
||||
|
||||
.ztree li span.button.add {
|
||||
margin-left: 2px;
|
||||
margin-right: -1px;
|
||||
background-position: -189px 0;
|
||||
vertical-align: top;
|
||||
*vertical-align: middle
|
||||
}
|
||||
|
||||
.ztree li span.button.add:hover {
|
||||
background-position: -168px 0;
|
||||
}
|
||||
|
||||
ul.tmpTargetzTree {
|
||||
background-color: #FFE6B0;
|
||||
opacity: 0.8;
|
||||
filter: alpha(opacity=80)
|
||||
}
|
||||
|
||||
span.tmpzTreeMove_arrow {
|
||||
width: 16px;
|
||||
height: 21px;
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
margin: 2px 0 0 1px;
|
||||
border: 0 none;
|
||||
position: absolute;
|
||||
background-color: transparent;
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: scroll;
|
||||
background-position: -168px -84px;
|
||||
background-image: url("./img/bootstrap.png");
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
span.tmpzTreeMove_prev {
|
||||
background: red;
|
||||
}
|
||||
|
||||
|
||||
|
||||
ul.ztree.zTreeDragUL {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: auto;
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
background-color: #cfcfcf;
|
||||
border: 1px #00B83F dotted;
|
||||
opacity: 0.8;
|
||||
filter: alpha(opacity=80)
|
||||
}
|
||||
|
||||
.ztreeMask {
|
||||
z-index: 10000;
|
||||
background-color: #cfcfcf;
|
||||
opacity: 0.0;
|
||||
filter: alpha(opacity=0);
|
||||
position: absolute
|
||||
}
|
||||
|
After Width: | Height: | Size: 254 B |
|
After Width: | Height: | Size: 524 B |
@@ -13,11 +13,20 @@
|
||||
* 注意:view-ui-plus 组件在模板里必须用 PascalCase(Select / DatePicker / Input / Option),
|
||||
* 写成 i-select / i-date-picker 不会被解析。
|
||||
*/
|
||||
import { reactive, computed, ref, watch } from "vue";
|
||||
import { reactive, computed, ref, watch, defineAsyncComponent } from "vue";
|
||||
import { Message } from "view-ui-plus";
|
||||
import { callModuleApi, parseOptionItems, isLoginRedirect, warmModuleSession } from "@/utils/module-session";
|
||||
import UserCheckInput from "@/components/UserCheckInput.vue";
|
||||
import type { FormField, FormRequest } from "@/types/agent";
|
||||
|
||||
/**
|
||||
* 富文本正文编辑器(wangEditor)——**异步加载**。
|
||||
*
|
||||
* <p>`@wangeditor/*` 是重量级依赖(编辑器 + 工具栏 + 样式)。整个助手只有「通知公告」
|
||||
* 的发布表单用得上,静态引入等于让所有用户都背上这份体积,所以拆包按需加载。
|
||||
*/
|
||||
const RichTextEditor = defineAsyncComponent(() => import("@/components/RichTextEditor.vue"));
|
||||
|
||||
const props = defineProps<{
|
||||
form: FormRequest;
|
||||
submitted?: boolean;
|
||||
@@ -410,6 +419,28 @@ function approverNames(f: FormField, ids: any[]): string {
|
||||
return ids.map(id => opts.find(o => String(o.value) === String(id))?.label || String(id)).join("、");
|
||||
}
|
||||
|
||||
/* ==================== 通知对象(widget=userpicker,搬运自通知公告模块页) ==================== */
|
||||
/**
|
||||
* 「通知对象」的显示文本(按字段名)。
|
||||
*
|
||||
* <p>为什么要在外面存一份:控件内部只持有 id 串(`"101,102"`),提交后只读摘要里
|
||||
* 直接摆一串 id 是没法看的。控件在选中/清空时会 `@names` 回传它自己的可读文本
|
||||
* (如「张三、李四」),这里接住用于摘要。
|
||||
*/
|
||||
const pickerText = reactive<Record<string, string>>({});
|
||||
|
||||
/** 选人弹窗的页签(后端下发;没有时退回 optionCall 的单页签) */
|
||||
function pickerSourcesOf(f: FormField) {
|
||||
return f.pickerSources && f.pickerSources.length ? f.pickerSources : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 本表单是否是「编辑」场景 —— 由是否有 `sourceId`(要改的那条记录 id)判断。
|
||||
* <p>编辑态下「全部用户」要显示成「现有用户」(模块页 `:isEdit` 同款口径):
|
||||
* 定向发送过的通知不能再改成全校广播。
|
||||
*/
|
||||
const isEditForm = computed(() => (props.form?.fields || []).some(f => f.name === "sourceId"));
|
||||
|
||||
/* ==================== 模块页同款呈现:自动选中 / 单选项隐藏 / 快捷时段 / 逐天时段 ==================== */
|
||||
|
||||
/** widget=switch 的勾选态(真正提交的是 switchOn / switchOff 映射后的值) */
|
||||
@@ -598,7 +629,7 @@ function widgetOf(f: FormField) {
|
||||
* 附件与审批流更是必须整行。时间类字段保持并排(「开始 ~ 结束」与模块页一致)。
|
||||
*/
|
||||
function isWide(f: FormField) {
|
||||
if (["flow", "radio", "switch", "textarea", "file"].includes(f.widget)) return true;
|
||||
if (["flow", "radio", "switch", "textarea", "file", "richtext", "userpicker"].includes(f.widget)) return true;
|
||||
// 「连续天数」是「连续请假」按天展开的从属字段,跟着独占一行
|
||||
return (props.form?.fields || []).some(x => x.dailyExpand?.count === f.name);
|
||||
}
|
||||
@@ -677,6 +708,11 @@ async function reloadOptions(f: FormField) {
|
||||
function syncOptions() {
|
||||
for (const f of props.form?.fields || []) {
|
||||
if (!f.optionCall) continue;
|
||||
// ★ userpicker(通知对象)跳过:它的 optionCall 是给**选人弹窗**用的原始组织树,
|
||||
// 弹窗自己取、自己用嵌套结构。这里再按「下拉字典」取一遍会把树拍平成叶子,
|
||||
// 而且 alignToOptions 会把「具体人员 id 串」判成「认不出的值」直接清掉 ——
|
||||
// 那等于把模型已经识别出来的人悄悄抹掉。
|
||||
if (f.widget === "userpicker") continue;
|
||||
const sig = depSignature(f);
|
||||
if (depSig[f.name] === sig) continue;
|
||||
const hadSig = depSig[f.name] !== undefined;
|
||||
@@ -718,6 +754,7 @@ function initValues() {
|
||||
Object.keys(switchOn).forEach(k => delete switchOn[k]);
|
||||
Object.keys(dailyRows).forEach(k => delete dailyRows[k]);
|
||||
Object.keys(hiddenSingle).forEach(k => delete hiddenSingle[k]);
|
||||
Object.keys(pickerText).forEach(k => delete pickerText[k]);
|
||||
// 栏目动态字段:换表单就整体丢弃(取值、区划树缓存都不跨表单复用)
|
||||
extraFields.value = [];
|
||||
extraState.value = "idle";
|
||||
@@ -748,6 +785,20 @@ function initValues() {
|
||||
values[f.name] = alignToOptions(f, arr.length ? arr : f.allToken ? [f.allToken] : []);
|
||||
} else if (f.widget === "file") {
|
||||
values[f.name] = Array.isArray(seed) ? [...seed] : [];
|
||||
} else if (f.widget === "userpicker") {
|
||||
// 通知对象:v-model 是**字符串**(`"all"` 或用户 id 逗号串),不是数组 ——
|
||||
// 虽然声明成 array(wire 上要逗号串),但模块页 form.rdoname 初值就是 "all"。
|
||||
// 模型已经点出具体人时可能是数组,join 成模块要的形态。
|
||||
const token = f.allToken || "all";
|
||||
const raw = Array.isArray(seed) ? seed.join(",") : seed === undefined || seed === null ? "" : String(seed);
|
||||
values[f.name] = raw || token;
|
||||
pickerText[f.name] =
|
||||
raw && raw !== token
|
||||
? `已选 ${raw.split(",").filter(x => String(x).trim() !== "").length} 人`
|
||||
: f.allTokenLabel || "全部用户";
|
||||
} else if (f.widget === "richtext") {
|
||||
// 正文:HTML 字符串(与模块页 NoticeRequest.content 一致)
|
||||
values[f.name] = seed === undefined || seed === null ? "" : String(seed);
|
||||
} else if (f.widget === "select" || f.widget === "number" || f.widget === "radio") {
|
||||
// ★ 对齐选项取值:模型按 schema 给的是数字(integer)或名称(string),
|
||||
// 这里统一成「选项本身的值」,否则预填了也选不上,用户以为要再选一次
|
||||
@@ -876,6 +927,22 @@ function labelOfName(name: string): string {
|
||||
return f?.label || NAME_LABEL[name] || name;
|
||||
}
|
||||
|
||||
/** 富文本 HTML → 摘要里可读的纯文本(只用于展示,提交的仍是原 HTML) */
|
||||
function plainTextOfHtml(html: string): string {
|
||||
const text = String(html || "")
|
||||
.replace(/<br\s*\/?>/gi, " ")
|
||||
.replace(/<\/(p|div|li|h[1-6])>/gi, " ")
|
||||
.replace(/<[^>]*>/g, "")
|
||||
.replace(/ /gi, " ")
|
||||
.replace(/</gi, "<")
|
||||
.replace(/>/gi, ">")
|
||||
.replace(/&/gi, "&")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
if (!text) return "(空)";
|
||||
return text.length > 60 ? `${text.slice(0, 60)}…` : text;
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态下拉的状态提示:等待上级字段 / 正在读取 / 取不到(可手填编号)。
|
||||
* 有了它,用户不会对着一个空下拉框猜「为什么点不开」。
|
||||
@@ -959,6 +1026,15 @@ function onSubmit() {
|
||||
});
|
||||
}
|
||||
}
|
||||
// 「通知对象」:值是**字符串**(`"all"` 或 id 逗号串),不能走下面的数组/空值分支 ——
|
||||
// 空值在这里补回「全部」(模块页 form.rdoname 从不留空)。
|
||||
if (f.widget === "userpicker") {
|
||||
const token = f.allToken || "all";
|
||||
const s = values[f.name] === undefined || values[f.name] === null ? "" : String(values[f.name]).trim();
|
||||
args[f.name] = s || token;
|
||||
rows.push({ label: f.label, text: pickerText[f.name] || s || f.allTokenLabel || "全部用户" });
|
||||
continue;
|
||||
}
|
||||
const v = values[f.name];
|
||||
// 数组类字段即使是空数组也要提交(模块页就是恒带 fileIds:"" / attachments:[]),
|
||||
// 否则「没传」与「传空」在 wire 上不一致,后端拿到的可能是 null 而不是空集合。
|
||||
@@ -989,6 +1065,13 @@ function onSubmit() {
|
||||
rows.push({ label: f.label, text: objs.map(o => o.fileName).join("、") || `${objs.length} 个附件` });
|
||||
continue;
|
||||
}
|
||||
// 富文本正文:提交的是**完整 HTML**(与模块页 NoticeRequest.content 一致);
|
||||
// 但摘要里不能摆原始标签,剥成纯文本给人看。
|
||||
if (f.widget === "richtext") {
|
||||
args[f.name] = String(v);
|
||||
rows.push({ label: f.label, text: plainTextOfHtml(String(v)) });
|
||||
continue;
|
||||
}
|
||||
const normalized = normalize(f, v);
|
||||
args[f.name] = normalized;
|
||||
const opt = fieldOptions(f).find(o => String(o.value) === String(normalized));
|
||||
@@ -1161,6 +1244,32 @@ function reset() {
|
||||
<span v-else class="file-tip">{{ f.placeholder || "可选,支持图片 / 文档 / 压缩包" }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 通知对象:搬自通知公告模块页的 UserCheckInput(radio 全部/指定 + 选人弹窗,
|
||||
左组织树 / 右已选列表 / 全选 / 按名字搜索回勾)。值是 "all" 或 id 逗号串。 -->
|
||||
<div v-else-if="widgetOf(f) === 'userpicker'" class="field__picker">
|
||||
<UserCheckInput
|
||||
v-model="values[f.name]"
|
||||
:sources="pickerSourcesOf(f)"
|
||||
:fallback-call="f.optionCall"
|
||||
:all-token="f.allToken || 'all'"
|
||||
:all-label="f.allTokenLabel || '全部用户'"
|
||||
:is-edit="isEditForm"
|
||||
:disabled="disabled"
|
||||
@names="(t: string) => (pickerText[f.name] = t)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 富文本正文:搬自通知公告模块页的 wangEditor(提交的是 HTML 字符串) -->
|
||||
<div v-else-if="widgetOf(f) === 'richtext'" class="field__richtext">
|
||||
<RichTextEditor
|
||||
v-model="values[f.name]"
|
||||
:upload-path="f.richTextUploadPath"
|
||||
:preview-path="f.richTextPreviewPath"
|
||||
:placeholder="f.placeholder || '请输入通知内容……'"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-else-if="widgetOf(f) === 'flow'" class="field__flow">
|
||||
<Checkbox v-model="flowEnabled[f.name]" :disabled="disabled">
|
||||
选择审批人后走审批流程(不勾选 = 直接发布)
|
||||
@@ -1469,6 +1578,16 @@ function reset() {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 通知对象(搬自模块页):radio + 只读回显 + +/清空 一行排开 */
|
||||
.field__picker {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 富文本正文:编辑器自己带边框,这里只管占满整行 */
|
||||
.field__richtext {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.field__label {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 富文本正文编辑器 —— 通知公告模块页(`NoticeFront/src/views/notice/NewNotice.vue`)
|
||||
* 里那块 wangEditor 的封装,供助手的「发布通知」表单复用。
|
||||
*
|
||||
* <p>为什么是**封装**而不是把 wangEditor 直接写进 FormCard:
|
||||
* 编辑器要自己管实例(创建 / 销毁 / 只读态)与图片上传,塞进表单组件会把 FormCard
|
||||
* 已经够长的逻辑再撑大一截;而且 `@wangeditor/*` 是重量级依赖,单独成一个组件后
|
||||
* FormCard 可以**异步加载**它,只有出现富文本字段的表单才付这份体积。
|
||||
*
|
||||
* <p>契约:提交给后端的 `content` 是 **HTML 字符串**,与模块页
|
||||
* `NoticeRequest.content` 完全一致(模块页也是 `v-model="form.content"`)。
|
||||
*
|
||||
* <p>图片上传的两点对齐(两处都容易踩):
|
||||
* <ol>
|
||||
* <li>接口是 {@code /api/file/UploadImage}(**不是**附件的 {@code /api/base/FileUpload}),
|
||||
* 返回 {@code {code,data:[{id,fileTrueName,fileName}]}};</li>
|
||||
* <li>插进正文的 `src` 必须用 {@code /api/file/Preview?id=<id>} —— inline 输出图片。
|
||||
* {@code /api/file/Down} 是 attachment,放进 {@code <img src>} 显示不出来。</li>
|
||||
* </ol>
|
||||
* 两个地址都由后端下发(已带模块前缀),这里不拼前缀。
|
||||
*
|
||||
* <p>上传同样带**用户自己的登录态**:模块接口只认模块容器的 CAS 会话,服务端代调必 302。
|
||||
* 遇 {@code code=302} 先预热模块会话再重试一次(与附件上传、下拉代取同一条链路)。
|
||||
*/
|
||||
import { ref, shallowRef, onBeforeUnmount, watch } from "vue";
|
||||
import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
|
||||
import "@wangeditor/editor/dist/css/style.css";
|
||||
import { Message } from "view-ui-plus";
|
||||
import { isLoginRedirect, warmModuleSession } from "@/utils/module-session";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue?: string;
|
||||
/** 正文内图片的上传地址(后端下发,**已带模块前缀**) */
|
||||
uploadPath?: string;
|
||||
/** 正文内图片的预览地址模板(后端下发,含 `{id}` 占位) */
|
||||
previewPath?: string;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
}>(),
|
||||
{ modelValue: "", uploadPath: "", previewPath: "", placeholder: "请输入通知内容……", disabled: false }
|
||||
);
|
||||
|
||||
const emit = defineEmits<{ (e: "update:modelValue", html: string): void }>();
|
||||
|
||||
/** wangEditor 实例(必须 shallowRef —— 深度响应式会把编辑器内部状态搞坏) */
|
||||
const editorRef = shallowRef<any>(null);
|
||||
|
||||
/**
|
||||
* 工具栏与模块页一致:去掉「上传视频」和「全屏」。
|
||||
* <p>去掉视频是本模块的既有选择;全屏在对话面板里会把整个助手界面顶飞。
|
||||
*/
|
||||
const toolbarConfig = { excludeKeys: ["group-video", "fullScreen"] };
|
||||
|
||||
/** 图片预览地址(把后端给的模板里的 `{id}` 换掉) */
|
||||
function previewUrl(id: any): string {
|
||||
return String(props.previewPath || "").split("{id}").join(String(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 正文图片上传。
|
||||
* <p>注意 {@code customUpload} 里**不能**用 `this`,且必须同步调用 `insertFn` —— 这是 wangEditor 的约定。
|
||||
*/
|
||||
async function customUpload(file: File, insertFn: (url: string, alt: string, href: string) => void) {
|
||||
if (!props.uploadPath) {
|
||||
Message.error("正文图片上传地址缺失,请稍后重试");
|
||||
return;
|
||||
}
|
||||
if (!props.previewPath) {
|
||||
Message.error("正文图片预览地址缺失,请稍后重试");
|
||||
return;
|
||||
}
|
||||
const doSend = async () => {
|
||||
const fd = new FormData();
|
||||
fd.append("file", file);
|
||||
const res = await fetch(`${location.origin}${props.uploadPath}`, {
|
||||
method: "POST",
|
||||
headers: { yschapirequest: "1" },
|
||||
body: fd,
|
||||
credentials: "same-origin",
|
||||
});
|
||||
return { status: res.status, body: await res.text() };
|
||||
};
|
||||
|
||||
try {
|
||||
let r = await doSend();
|
||||
if (isLoginRedirect(r.status, r.body)) {
|
||||
await warmModuleSession(props.uploadPath);
|
||||
r = await doSend();
|
||||
}
|
||||
const j = JSON.parse(r.body);
|
||||
const data = j && typeof j === "object" ? j.data : null;
|
||||
const first = Array.isArray(data) ? data[0] : data;
|
||||
if (Number(j?.code) === 0 && first) {
|
||||
const id = first.id ?? first.fileid ?? first.fileId;
|
||||
if (id === undefined || id === null) {
|
||||
Message.error("图片上传失败:没拿回文件编号");
|
||||
return;
|
||||
}
|
||||
const url = previewUrl(id);
|
||||
// 三项都要给:url 是图片地址,href 是点击图片要跳的地址(模块页也指向自己)
|
||||
insertFn(url, first.fileTrueName || first.fileName || file.name || "", url);
|
||||
} else {
|
||||
Message.error(j?.msg || "图片上传失败");
|
||||
}
|
||||
} catch {
|
||||
Message.error(`「${file.name}」上传失败`);
|
||||
}
|
||||
}
|
||||
|
||||
const editorConfig = {
|
||||
placeholder: props.placeholder,
|
||||
MENU_CONF: {
|
||||
uploadImage: { customUpload },
|
||||
},
|
||||
};
|
||||
|
||||
function handleCreated(editor: any) {
|
||||
editorRef.value = editor;
|
||||
// 首屏就是只读(历史消息里的表单)时立刻置灰
|
||||
if (props.disabled) editor.disable();
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.disabled,
|
||||
v => {
|
||||
const editor = editorRef.value;
|
||||
if (!editor) return;
|
||||
if (v) editor.disable();
|
||||
else editor.enable();
|
||||
}
|
||||
);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
const editor = editorRef.value;
|
||||
editorRef.value = null;
|
||||
try {
|
||||
editor?.destroy();
|
||||
} catch {
|
||||
/* 吞掉:切换会话时组件与编辑器可能已被回收,重复 destroy 不该打断页面 */
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="rt-editor" :class="{ 'rt-editor--readonly': disabled }">
|
||||
<Toolbar class="rt-editor__toolbar" :editor="editorRef" :defaultConfig="toolbarConfig" mode="default" />
|
||||
<Editor
|
||||
class="rt-editor__content"
|
||||
:model-value="modelValue"
|
||||
:defaultConfig="editorConfig"
|
||||
mode="default"
|
||||
@onCreated="handleCreated"
|
||||
@update:modelValue="(html: string) => emit('update:modelValue', html)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.rt-editor {
|
||||
width: 100%;
|
||||
border: 1px solid var(--agent-border-strong, #dcdee2);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
/* 工具栏的弹出层要盖在下面的字段上 */
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.rt-editor--readonly {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.rt-editor__toolbar {
|
||||
border-bottom: 1px solid var(--agent-border, #e8e8e8);
|
||||
}
|
||||
|
||||
.rt-editor__content {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
/* 正文编辑区高度:模块页是 320px,对话面板里略矮一点,避免表单把整屏占满 */
|
||||
.rt-editor__content :deep(.w-e-text-container) {
|
||||
height: 240px !important;
|
||||
}
|
||||
|
||||
/* wangEditor 的工具栏按钮自带行高,压一压免得两行按钮把小面板撑高 */
|
||||
.rt-editor__toolbar :deep(.w-e-bar) {
|
||||
padding: 2px 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,241 @@
|
||||
<template>
|
||||
<!-- 通知对象选择:全部用户 / 指定用户(自业务模块 NoticeFront 的 components/UserCheckInput.vue 照搬) -->
|
||||
<div class="check-user-wrap">
|
||||
<div class="check-user-box">
|
||||
<RadioGroup v-model="isCheckUserAll" class="radio" @on-change="userAllCheck">
|
||||
<Radio :disabled="disabled || editDisabled" :label="0">{{ allText }}</Radio>
|
||||
<Radio :disabled="disabled" :label="1">指定用户</Radio>
|
||||
</RadioGroup>
|
||||
<Input
|
||||
type="text"
|
||||
v-model="userNameStr"
|
||||
readonly
|
||||
:disabled="disabled || !isCheckUserAll"
|
||||
placeholder="选择指定的用户"
|
||||
class="input"
|
||||
></Input>
|
||||
<Button icon="md-add" :disabled="disabled || !isCheckUserAll" @click="checkUserShow"></Button>
|
||||
<Button icon="md-trash" :disabled="disabled || !isCheckUserAll" @click="resetUser"></Button>
|
||||
</div>
|
||||
<UserCheckModal ref="userCheckModal" :sources="tabs" :userids="tempUserIds" @on-submit="submitUser" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
/**
|
||||
* 「通知对象」输入控件 —— 自业务模块 NoticeFront 的 `components/UserCheckInput.vue` **照搬**。
|
||||
*
|
||||
* <p>v-model 值是**字符串**:`"all"` 或用户 id 逗号串(与模块页 `form.rdoname` 一致);
|
||||
* 切到「指定用户」会直接弹出选人弹窗(左组织树 / 右已选列表)。
|
||||
* 提交时由后端按 `joinSeparator` 拼串,这里原样给出字符串即可。
|
||||
*
|
||||
* <p>与本体的**唯一区别是数据从哪儿来**:模块里是 alova 直连自己的 `/api/*`,
|
||||
* 这里改为后端下发的 `sources[].call`(`FormField.pickerSources`),
|
||||
* 由 `callModuleApi` 带**用户登录态**去取 —— 模块接口只认模块容器的 CAS 会话,服务端代调必 302。
|
||||
* 取回的是**原始嵌套树**,不做拍平(拍平会丢层级,树就画不出来)。
|
||||
*/
|
||||
import UserCheckModal from "@/components/UserCheckModal.vue";
|
||||
import { callModuleApi } from "@/utils/module-session";
|
||||
import type { BrowserToolCall } from "@/types/agent";
|
||||
|
||||
/** 后端下发的数据源页签(每个 call 都已带模块前缀) */
|
||||
interface PickerSource {
|
||||
name: string;
|
||||
call: BrowserToolCall;
|
||||
}
|
||||
|
||||
/**
|
||||
* 把模块返回的原始响应解成**组织树数组**。
|
||||
*
|
||||
* <p>与 `parseOptionItems` 分开写:那个是给下拉用的,会把树**拍平成叶子**
|
||||
* (下拉里当然不该出现「年级」这一层)。选人弹窗要的恰恰是层级本身,
|
||||
* 所以这里只做「剥掉 {code,msg,data} 外壳」,不动结构。
|
||||
*/
|
||||
function parseTree(body?: string): any[] {
|
||||
if (!body) return [];
|
||||
let payload: any;
|
||||
try {
|
||||
payload = JSON.parse(body);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
if (payload && typeof payload === "object" && !Array.isArray(payload) && "code" in payload) {
|
||||
if (Number(payload.code) !== 0) return [];
|
||||
payload = payload.data;
|
||||
}
|
||||
if (Array.isArray(payload)) return payload;
|
||||
// 少数版本把数组包在 data.list / data.records 里,容错取第一个数组
|
||||
if (payload && typeof payload === "object") {
|
||||
const arr = Object.values(payload).find(v => Array.isArray(v));
|
||||
if (arr) return arr as any[];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export default {
|
||||
name: "UserCheckInput",
|
||||
components: { UserCheckModal },
|
||||
props: {
|
||||
/** 当前值:`"all"` 或用户 id 逗号串 */
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "all",
|
||||
},
|
||||
/** 选人弹窗的数据源页签(后端下发的 pickerSources,已带模块前缀) */
|
||||
sources: {
|
||||
type: Array as () => PickerSource[],
|
||||
default: () => [],
|
||||
},
|
||||
/** 兜底数据源(后端只下发了 optionCall 时用;页签名固定「选择用户」) */
|
||||
fallbackCall: {
|
||||
type: Object as () => BrowserToolCall | undefined,
|
||||
default: undefined,
|
||||
},
|
||||
/** 「全部」哨兵值(后端下发,默认 all) */
|
||||
allToken: {
|
||||
type: String,
|
||||
default: "all",
|
||||
},
|
||||
/** 「全部」选项的显示名(后端下发,默认「全部用户」) */
|
||||
allLabel: {
|
||||
type: String,
|
||||
default: "全部用户",
|
||||
},
|
||||
/** 编辑态:把「全部用户」显示成「现有用户」(模块页同款) */
|
||||
isEdit: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue", "names"],
|
||||
data() {
|
||||
return {
|
||||
isCheckUserAll: 0 as number,
|
||||
tempUserIds: [] as any[],
|
||||
userNameStr: "",
|
||||
editDisabled: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
/** 弹窗的页签:{ name, loader }(loader 返回原始组织树) */
|
||||
tabs(): { name: string; loader: () => Promise<any[]> }[] {
|
||||
const srcs: PickerSource[] = this.sources || [];
|
||||
if (srcs.length) {
|
||||
return srcs.map(s => ({
|
||||
name: s.name,
|
||||
loader: async () => parseTree((await callModuleApi(s.call)).body),
|
||||
}));
|
||||
}
|
||||
const fb = this.fallbackCall;
|
||||
if (fb) {
|
||||
return [{ name: "选择用户", loader: async () => parseTree((await callModuleApi(fb)).body) }];
|
||||
}
|
||||
return [];
|
||||
},
|
||||
allText(): string {
|
||||
return this.isEdit ? "现有用户" : this.allLabel;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.syncFromValue();
|
||||
},
|
||||
methods: {
|
||||
/** 按当前 v-model 值同步界面(初值与外部改动共用一份口径) */
|
||||
syncFromValue() {
|
||||
const v = this.modelValue;
|
||||
if (v === this.allToken || !v) {
|
||||
this.isCheckUserAll = 0;
|
||||
this.userNameStr = v === this.allToken ? this.allText : "";
|
||||
} else {
|
||||
this.isCheckUserAll = 1;
|
||||
this.tempUserIds = parseIntList(v);
|
||||
}
|
||||
},
|
||||
checkUserShow() {
|
||||
(this.$refs.userCheckModal as any)?.show();
|
||||
},
|
||||
resetUser() {
|
||||
// 清空指定用户 → 回到「全部用户」
|
||||
this.tempUserIds = [];
|
||||
this.userNameStr = "";
|
||||
this.$emit("update:modelValue", this.allToken);
|
||||
this.$emit("names", this.allText);
|
||||
},
|
||||
userAllCheck(type: number) {
|
||||
const tempIds = parseIntList(this.modelValue);
|
||||
if (type == 0) {
|
||||
if (this.isEdit) {
|
||||
this.tempUserIds = tempIds;
|
||||
this.userNameStr = "现有用户";
|
||||
this.$emit("update:modelValue", String(tempIds));
|
||||
this.$emit("names", "现有用户");
|
||||
} else {
|
||||
this.tempUserIds = [];
|
||||
this.userNameStr = this.allLabel;
|
||||
this.$emit("update:modelValue", this.allToken);
|
||||
this.$emit("names", this.allLabel);
|
||||
}
|
||||
} else {
|
||||
if (this.isEdit) {
|
||||
this.editDisabled = true;
|
||||
}
|
||||
this.tempUserIds = tempIds;
|
||||
this.userNameStr = "";
|
||||
this.checkUserShow();
|
||||
}
|
||||
},
|
||||
submitUser(arr: any[]) {
|
||||
// 确认用户
|
||||
this.userNameStr = arr.map((x: any) => x.name).join("、");
|
||||
const ids = arr.map((x: any) => x.id).join(",");
|
||||
this.$emit("update:modelValue", ids || this.allToken);
|
||||
this.$emit("names", this.userNameStr || this.allText);
|
||||
if (!arr.length) {
|
||||
// 一个人都没选 → 回到「全部用户」(模块页同款)
|
||||
this.isCheckUserAll = 0;
|
||||
this.userNameStr = this.allLabel;
|
||||
this.$emit("update:modelValue", this.allToken);
|
||||
this.$emit("names", this.allLabel);
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
modelValue() {
|
||||
this.syncFromValue();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** `"101,102"` → `[101,102]`(非数字段丢掉,与模块页 `split().map(parseInt).filter` 同口径) */
|
||||
function parseIntList(v: any): number[] {
|
||||
return String(v || "")
|
||||
.split(",")
|
||||
.map(x => parseInt(x))
|
||||
.filter(x => !isNaN(x));
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.check-user-wrap {
|
||||
width: 100%;
|
||||
}
|
||||
.check-user-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.check-user-box .radio {
|
||||
width: 180px;
|
||||
flex: none;
|
||||
font-size: 12px;
|
||||
vertical-align: middle;
|
||||
line-height: 32px;
|
||||
}
|
||||
.check-user-box .input {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,437 @@
|
||||
<template>
|
||||
<Modal v-model="modal" title="选择用户" width="720px" :mask-closable="false" :closable="false">
|
||||
<Tabs v-if="sources.length > 1" :key="tabKey" type="card" :value="String(tabIndex)" @on-click="tabChange">
|
||||
<TabPane v-for="(item, index) in sources" :key="index" :label="item.name" :name="index + ''"></TabPane>
|
||||
</Tabs>
|
||||
<AutoComplete
|
||||
@on-search="handleSearch"
|
||||
@on-select="handleSelect"
|
||||
placeholder="输入名字搜索"
|
||||
v-model="searchTreeName"
|
||||
:clearable="true"
|
||||
class="auto-box"
|
||||
>
|
||||
<Option v-for="item in searchResult" :key="item.id" :value="item.name">
|
||||
<span>{{ item.name }}</span>
|
||||
<span class="fr info">{{ item.group }}</span>
|
||||
</Option>
|
||||
</AutoComplete>
|
||||
<div class="tree-wrap fix">
|
||||
<div class="tree-panel fl">
|
||||
<div class="tree-box">
|
||||
<div :id="ztreeDomId" class="ztree"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="arrow-box fl">
|
||||
<svg
|
||||
t="1626916477711"
|
||||
class="icon"
|
||||
viewBox="0 0 1024 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="36"
|
||||
height="36"
|
||||
>
|
||||
<path
|
||||
d="M138.935 256.078v511.844h63.42V256.078h-63.42z m126.842 0v511.844h127.215V256.078H265.777zM885.064 512L583.254 64.323v191.755H456.414v511.844h126.842v191.755L885.064 512z"
|
||||
fill="#2d8cf0"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="user-panel fl">
|
||||
<div class="title-box fix">
|
||||
<Checkbox @on-change="checkAllUser" v-model="isCheckUserAll">全选</Checkbox>
|
||||
<div class="fr">{{ tempUserList.length }}</div>
|
||||
<a href="javascript:;" @click="delCheckUser" v-if="tempUserList.findIndex((x: any) => x.isCheck) > -1"
|
||||
>删除选中</a
|
||||
>
|
||||
</div>
|
||||
<ul class="user-box">
|
||||
<li
|
||||
v-for="item in tempUserList"
|
||||
:key="item.id"
|
||||
:class="{ active: item.isCheck }"
|
||||
@click="checkUserOne(item)"
|
||||
>
|
||||
{{ item.name }}
|
||||
<span class="info">{{ item.group }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button type="primary" @click="submit">提交</Button>
|
||||
<Button @click="modal = false" style="margin-left: 8px">取消</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
/**
|
||||
* 选择用户弹窗 —— 自业务模块 NoticeFront 的 `components/UserCheckModal.vue` 照搬。
|
||||
*
|
||||
* <p>为什么整块搬过来而不是自己重写:这个弹窗的交互(左组织树勾选级联 / 右已选列表 /
|
||||
* 全选 / 删除选中 / 按名字搜索回勾)细节很多,自己重写很难做到「和系统里看到的一样」。
|
||||
*
|
||||
* <p>与本体的**唯一区别是数据从哪儿来**:模块里是 alova 客户端直连自己的 `/api/*`,
|
||||
* 这里改成由父组件传进来的 `sources[].loader` —— 助手侧统一走
|
||||
* `callModuleApi`(带用户登录态 + 遇 302 先预热模块会话再重试),
|
||||
* 因为助手的服务端**无法**代持模块会话。
|
||||
*
|
||||
* <p>依赖:jQuery + zTree 由 `index.html` 全局加载(模块页也是这个加载方式)。
|
||||
* zTree 的数据预处理、`id` 形如 `"1,301"`(原 id, 父 id)等约定**原样保留**,
|
||||
* 否则回显与去重都会错。
|
||||
*/
|
||||
import treeIconGroup from "@/assets/image/tree-icon-group.png";
|
||||
import treeIconItem from "@/assets/image/tree-icon-item.png";
|
||||
import { Message } from "view-ui-plus";
|
||||
|
||||
interface SelectSource {
|
||||
name: string;
|
||||
loader: () => Promise<any[]>;
|
||||
}
|
||||
|
||||
/** 全局 jQuery(由 index.html 的 script 提供)。取用时机放在方法内,避免模块求值早于脚本加载。 */
|
||||
function jq(): any {
|
||||
return (window as any).$;
|
||||
}
|
||||
|
||||
export default {
|
||||
name: "UserCheckModal",
|
||||
props: {
|
||||
/** 数据源 tab:{ name, loader },loader 返回 ztree 树数据(children 嵌套,叶子=人员) */
|
||||
sources: {
|
||||
type: Array as () => SelectSource[],
|
||||
default: () => [],
|
||||
},
|
||||
/** 已选用户 id(打开时自动勾选) */
|
||||
userids: {
|
||||
type: Array as () => number[],
|
||||
default: () => [],
|
||||
},
|
||||
isDisabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ["update:userids", "on-submit"],
|
||||
data() {
|
||||
return {
|
||||
ztreeDomId: "user_check_tree_" + Math.floor(Math.random() * 1e8),
|
||||
modal: false,
|
||||
tabIndex: 0,
|
||||
/** 每次打开弹窗自增,强制重建 Tabs,避免上次 tab 高亮残留与数据错位 */
|
||||
tabKey: 0,
|
||||
/** 树加载序号:tab 快速切换时只渲染最后一次请求的结果 */
|
||||
treeSeq: 0,
|
||||
isCheckUserAll: false,
|
||||
cacheMap: new Map(),
|
||||
currentTree: [] as any[],
|
||||
searchTreeName: "",
|
||||
searchResult: [] as any[],
|
||||
tempUserList: [] as any[],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
/** 当前树扁平化(含父级名称,供搜索展示) */
|
||||
currentTreeFlatten(): any[] {
|
||||
const flatten = (data: any[], pid: number, pname: string): any[] =>
|
||||
data.reduce(
|
||||
(arr: any[], { id, name, children = [], type }: any) =>
|
||||
arr.concat([{ id, name, pid, type, group: pname }], flatten(children, id, name)),
|
||||
[]
|
||||
);
|
||||
return flatten(this.currentTree, 0, "");
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
show() {
|
||||
this.modal = true;
|
||||
// 重置到第一个 tab:显式赋值 + key 重建 Tabs,保证高亮与数据一致
|
||||
this.tabIndex = 0;
|
||||
this.tabKey++;
|
||||
this.changeEmpty();
|
||||
this.initTree();
|
||||
},
|
||||
tabChange(index: string) {
|
||||
const idx = Number(index);
|
||||
// 严格比较:v-model 场景下 iview 会把值写成字符串 name,
|
||||
// 宽松相等("1" == 1)会误判"未切换"导致 tab 永远不生效
|
||||
if (idx !== 0 && this.tabIndex === idx) return;
|
||||
this.tabIndex = idx;
|
||||
this.changeEmpty();
|
||||
this.initTree();
|
||||
},
|
||||
handleSearch(query: string) {
|
||||
let arr: any[] = [];
|
||||
const searchArr = this.currentTreeFlatten.filter(
|
||||
(x: any) => x.type == 2 && x.name.indexOf(query) != -1
|
||||
);
|
||||
for (const item of searchArr) {
|
||||
!arr.map(x => this.getSplitId(x.id)).includes(this.getSplitId(item.id)) && arr.push(item);
|
||||
}
|
||||
this.searchResult = arr;
|
||||
},
|
||||
handleSelect(query: string) {
|
||||
if (query.indexOf(",") != -1) query = query.split(",")[0];
|
||||
const treeObj = jq().fn.zTree.getZTreeObj(this.ztreeDomId);
|
||||
const node = treeObj.getNodeByParam("name", query, null);
|
||||
if (!!node) treeObj.checkNode(node, true, true, true);
|
||||
},
|
||||
/**
|
||||
* ztree 数据预处理:拼接 pid 保证同人多组时 id 可区分;叶子标记 type=2。
|
||||
* 组/用户判定优先用接口 isParent(组=文件夹),缺失时按有无子节点兜底;
|
||||
* 并设置组/用户区分图标。
|
||||
*/
|
||||
formatTree(tree: any[], level = 0, pid: number = 0) {
|
||||
tree.forEach((element: any) => {
|
||||
element.open = level == 0;
|
||||
element.pid = pid;
|
||||
element.id = element.id + "," + pid;
|
||||
element.checked = false;
|
||||
if (typeof element.isParent !== "undefined") {
|
||||
element.type = element.isParent ? 1 : 2;
|
||||
} else {
|
||||
element.type = element.children && element.children.length > 0 ? 1 : 2;
|
||||
}
|
||||
element.icon = element.type == 1 ? treeIconGroup : treeIconItem;
|
||||
if (typeof element.children !== "undefined" && element.children.length > 0) {
|
||||
element.children = this.formatTree(element.children, level + 1, this.getSplitId(element.id));
|
||||
}
|
||||
});
|
||||
return tree;
|
||||
},
|
||||
initTree() {
|
||||
const _this = this;
|
||||
// 竞态守卫:慢请求返回时若已切换 tab,丢弃结果
|
||||
const seq = ++this.treeSeq;
|
||||
const cacheKey = this.tabIndex;
|
||||
const source = this.sources[this.tabIndex];
|
||||
if (!source) return;
|
||||
const cached = this.cacheMap.get(cacheKey);
|
||||
if (cached) {
|
||||
this.createTree(cached);
|
||||
return;
|
||||
}
|
||||
source
|
||||
.loader()
|
||||
.then((data: any[]) => {
|
||||
if (seq !== _this.treeSeq) return;
|
||||
_this.cacheMap.set(cacheKey, data || []);
|
||||
_this.createTree(data || []);
|
||||
})
|
||||
.catch((e: any) => {
|
||||
if (seq === _this.treeSeq) Message.error(e?.message || "加载组织数据失败");
|
||||
});
|
||||
},
|
||||
createTree(rawData: any[]) {
|
||||
const _this = this;
|
||||
const jtree = JSON.parse(JSON.stringify(rawData));
|
||||
const ftree = _this.formatTree(jtree);
|
||||
_this.currentTree = ftree;
|
||||
|
||||
const treeSetting: any = {
|
||||
view: {
|
||||
dblClickExpand: false,
|
||||
showIcon: true,
|
||||
},
|
||||
check: {
|
||||
enable: true,
|
||||
chkboxType: { Y: "ps", N: "ps" },
|
||||
},
|
||||
callback: {},
|
||||
};
|
||||
/** 勾选变化后同步右侧已选列表:收集勾选节点子树内的所有叶子 */
|
||||
function formatUser(treeNode: any, checked: boolean) {
|
||||
const recursionCheckIds = (tree: any): any[] => {
|
||||
let arrIds: any[] = [];
|
||||
for (const key in tree) {
|
||||
const element = tree[key];
|
||||
if (element.type == 2 && !element.chkDisabled) {
|
||||
arrIds.push({ id: element.id, tid: element.tId });
|
||||
}
|
||||
if (typeof element.children !== "undefined" && element.children) {
|
||||
arrIds = [...arrIds, ...recursionCheckIds(element.children)];
|
||||
}
|
||||
}
|
||||
return arrIds;
|
||||
};
|
||||
const checkIds = recursionCheckIds([treeNode]);
|
||||
const checkUserList = _this.currentTreeFlatten.filter((x: any) => checkIds.map(x => x.id).includes(x.id));
|
||||
const checkUserArr: any[] = [];
|
||||
for (const element of checkUserList) {
|
||||
const findObj = checkIds.find((x: any) => x.id == element.id);
|
||||
if (
|
||||
!!findObj &&
|
||||
checkUserArr.findIndex((x: any) => x.id == element.id) === -1 &&
|
||||
_this.tempUserList.findIndex((x: any) => x.id == element.id) === -1
|
||||
) {
|
||||
checkUserArr.push({ ...element, isCheck: false, tid: findObj.tid });
|
||||
}
|
||||
}
|
||||
if (checked) {
|
||||
_this.tempUserList = _this.tempUserList.concat(checkUserArr);
|
||||
} else {
|
||||
const ids = checkUserList.map((x: any) => x.id);
|
||||
_this.tempUserList = _this.tempUserList.filter((x: any) => !ids.includes(x.id));
|
||||
}
|
||||
}
|
||||
treeSetting.callback.onClick = (event: any, treeId: string, treeNode: any) => {
|
||||
const check = treeNode.checked;
|
||||
const treeObj = jq().fn.zTree.getZTreeObj(treeId);
|
||||
treeObj.checkNode(treeNode, !check, true);
|
||||
formatUser(treeNode, treeNode.checked);
|
||||
};
|
||||
treeSetting.callback.onCheck = (event: any, treeId: string, treeNode: any) => {
|
||||
formatUser(treeNode, treeNode.checked);
|
||||
};
|
||||
const treeObj = jq().fn.zTree.init(jq()("#" + _this.ztreeDomId), treeSetting, ftree);
|
||||
// 回显:已选用户打勾(callbackFlag=true 会触发 onCheck,同步右侧列表)
|
||||
const nodes = treeObj.getNodesByFilter((x: any) => _this.userids.includes(_this.getSplitId(x.id)));
|
||||
const arr: any[] = [];
|
||||
nodes.forEach((element: any) => {
|
||||
const splitId = _this.getSplitId(element.id); // 重复 id 只选择第一个
|
||||
if (!arr.includes(splitId)) treeObj.checkNode(element, true, true, true);
|
||||
if (_this.isDisabled) treeObj.setChkDisabled(element, true);
|
||||
arr.push(splitId);
|
||||
});
|
||||
},
|
||||
checkAllUser(check: boolean) {
|
||||
this.tempUserList.forEach((element: any) => {
|
||||
element.isCheck = check;
|
||||
});
|
||||
},
|
||||
checkUserOne(item: any) {
|
||||
item.isCheck = !item.isCheck;
|
||||
const tlist = this.tempUserList;
|
||||
this.isCheckUserAll = tlist.length > 0 && tlist.filter((x: any) => x.isCheck).length == tlist.length;
|
||||
},
|
||||
delCheckUser() {
|
||||
const _this = this;
|
||||
const tlist = _this.tempUserList;
|
||||
const delArr = tlist.filter((item: any) => item.isCheck);
|
||||
const treeObj = jq().fn.zTree.getZTreeObj(_this.ztreeDomId);
|
||||
delArr.forEach((element: any) => {
|
||||
const node = treeObj.getNodeByTId(element.tid);
|
||||
treeObj.checkNode(node, false, true, true);
|
||||
});
|
||||
_this.isCheckUserAll = false;
|
||||
},
|
||||
changeEmpty() {
|
||||
this.tempUserList = [];
|
||||
this.searchTreeName = "";
|
||||
this.searchResult = [];
|
||||
},
|
||||
submit() {
|
||||
const _this = this;
|
||||
let idsArr = this.tempUserList.map((x: any) => _this.getSplitId(x.id));
|
||||
idsArr = Array.from(new Set(idsArr));
|
||||
this.$emit("update:userids", idsArr);
|
||||
function delKey(obj: any, ...args: string[]) {
|
||||
args.forEach(v => delete obj[v]);
|
||||
return obj;
|
||||
}
|
||||
const objArr: any[] = [];
|
||||
idsArr.forEach(element => {
|
||||
let findObj = _this.tempUserList.find((x: any) => _this.getSplitId(x.id) == element);
|
||||
findObj = delKey({ ...findObj }, "id", "isCheck", "tid", "pid", "type");
|
||||
objArr.push({ ...findObj, id: element });
|
||||
});
|
||||
this.$emit("on-submit", objArr);
|
||||
this.modal = false;
|
||||
},
|
||||
/** 树节点 id 形如 "1,301"(原id,父id),取真实 id */
|
||||
getSplitId(strId: any) {
|
||||
if (typeof strId === "string" && strId.indexOf(",") != -1) return parseInt(strId.split(",")[0]);
|
||||
return strId;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tree-wrap {
|
||||
height: 430px;
|
||||
position: relative;
|
||||
}
|
||||
.tree-panel {
|
||||
border: 1px solid #eee;
|
||||
width: 280px;
|
||||
height: 430px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.tree-box {
|
||||
padding: 4px 0;
|
||||
height: 428px;
|
||||
width: auto;
|
||||
overflow-y: auto;
|
||||
border: none;
|
||||
}
|
||||
.user-panel {
|
||||
border: 1px solid #eee;
|
||||
width: 280px;
|
||||
.title-box {
|
||||
background: #f7f7f7;
|
||||
height: 34px;
|
||||
line-height: 34px;
|
||||
padding: 0 12px;
|
||||
a {
|
||||
text-decoration: none;
|
||||
float: right;
|
||||
margin-right: 20px;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
.user-box {
|
||||
height: 396px;
|
||||
overflow-y: auto;
|
||||
li {
|
||||
line-height: 32px;
|
||||
padding-left: 12px;
|
||||
cursor: pointer;
|
||||
.info {
|
||||
float: right;
|
||||
margin-right: 12px;
|
||||
font-size: 12px;
|
||||
color: gray;
|
||||
}
|
||||
}
|
||||
li:hover {
|
||||
background: #f3f3f3;
|
||||
}
|
||||
li.active {
|
||||
background: #e4edf6;
|
||||
}
|
||||
}
|
||||
}
|
||||
.arrow-box {
|
||||
width: 128px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
top: 50%;
|
||||
margin-top: -17px;
|
||||
}
|
||||
.auto-box {
|
||||
width: 200px;
|
||||
margin-bottom: 10px;
|
||||
:deep(.ivu-select-item .info) {
|
||||
font-size: 12px;
|
||||
color: gray;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
}
|
||||
:deep(.ztree li) {
|
||||
overflow: hidden;
|
||||
}
|
||||
:deep(.ztree li a) {
|
||||
width: 100%;
|
||||
}
|
||||
:deep(.ztree li a:hover) {
|
||||
text-decoration: none;
|
||||
}
|
||||
:deep(.ivu-auto-complete .ivu-select-dropdown-list) {
|
||||
max-height: 310px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -52,7 +52,18 @@ export interface FormField {
|
||||
/** 勾选框(对齐模块页的 Checkbox,如「连续请假」) */
|
||||
| "switch"
|
||||
/** 级联选择(栏目动态字段的 province_city:省市区,值存编码数组) */
|
||||
| "cascader";
|
||||
| "cascader"
|
||||
/**
|
||||
* 富文本正文(wangEditor,对齐通知公告模块页的编辑器)。
|
||||
* 提交给后端的是 **HTML 字符串**,与模块页 NoticeRequest.content 一致。
|
||||
*/
|
||||
| "richtext"
|
||||
/**
|
||||
* 「通知对象」自绘控件(模块页 components/UserCheckInput.vue 的同款):
|
||||
* radio「全部用户 / 指定用户」+ 只读回显 + +/清空 + 选人弹窗(左组织树右已选列表)。
|
||||
* v-model 值是 **字符串**:`"all"` 或用户 id 逗号串。
|
||||
*/
|
||||
| "userpicker";
|
||||
type?: string;
|
||||
required: boolean;
|
||||
/** 字段说明(含义与格式) */
|
||||
@@ -71,6 +82,21 @@ export interface FormField {
|
||||
uploadPath?: string;
|
||||
/** widget=file:是否允许多个附件 */
|
||||
multiple?: boolean;
|
||||
/**
|
||||
* widget=richtext:正文里「插入图片」的上传地址(已带模块前缀的绝对相对路径)。
|
||||
* 与 uploadPath 不是同一个接口(正文图片走 /api/file/UploadImage)。
|
||||
*/
|
||||
richTextUploadPath?: string;
|
||||
/** widget=richtext:正文内图片的预览地址模板(已带模块前缀,含 {id} 占位) */
|
||||
richTextPreviewPath?: string;
|
||||
/**
|
||||
* widget=userpicker:选人弹窗的**数据源页签**(后端下发,每个 call 都已带模块前缀)。
|
||||
*
|
||||
* <p>模块页的弹窗有两个页签(「基础平台」组织树 / 「自定义组」);
|
||||
* 未下发时前端退回 `optionCall`(单页签)。取回的是**原始嵌套树**,不做拍平 ——
|
||||
* 拍平会丢掉层级,树就画不出来。
|
||||
*/
|
||||
pickerSources?: { name: string; call: BrowserToolCall }[];
|
||||
/** 「全部」哨兵值(如 ALL):作为多选的第一项,可选中代表「全部班级 / 全部宿舍」 */
|
||||
allToken?: string;
|
||||
/** 「全部」选项的显示名 */
|
||||
|
||||