zj-plugin-intelligent 1.2.12-beta.6 → 1.2.12-beta.7
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.
|
@@ -916,7 +916,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n
|
|
|
916
916
|
/***/ (function(module, exports, __webpack_require__) {
|
|
917
917
|
|
|
918
918
|
"use strict";
|
|
919
|
-
eval("\n\nvar _interopRequireDefault = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/interopRequireDefault */ \"./node_modules/@babel/runtime/helpers/interopRequireDefault.js\").default;\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _toConsumableArray2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/toConsumableArray */ \"./node_modules/@babel/runtime/helpers/toConsumableArray.js\"));\n\nvar _objectSpread2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/objectSpread2 */ \"./node_modules/@babel/runtime/helpers/objectSpread2.js\"));\n\nvar _createForOfIteratorHelper2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/createForOfIteratorHelper */ \"./node_modules/@babel/runtime/helpers/createForOfIteratorHelper.js\"));\n\nvar _asyncToGenerator2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/asyncToGenerator */ \"./node_modules/@babel/runtime/helpers/asyncToGenerator.js\"));\n\nvar _markdownIt = _interopRequireDefault(__webpack_require__(/*! markdown-it */ \"./node_modules/markdown-it/index.js\"));\n\n__webpack_require__(/*! @jboltai/tokui/dist/tokui.css */ \"./node_modules/@jboltai/tokui/dist/tokui.css\");\n\nvar _apichat = __webpack_require__(/*! @lib/api/apichat */ \"./src/lib/api/apichat.js\");\n\nvar _index = __webpack_require__(/*! @lib/socket/fileManager/obs/index */ \"./src/lib/socket/fileManager/obs/index.js\");\n\nvar _index2 = _interopRequireDefault(__webpack_require__(/*! @lib/socket/utils/index */ \"./src/lib/socket/utils/index.js\"));\n\nvar _cookie = __webpack_require__(/*! @lib/utils/cookie.js */ \"./src/lib/utils/cookie.js\");\n\nvar _request = __webpack_require__(/*! @lib/utils/request */ \"./src/lib/utils/request.js\");\n\nvar _apiObs = _interopRequireDefault(__webpack_require__(/*! @lib/api/apiObs */ \"./src/lib/api/apiObs.js\"));\n\nvar _html2canvas = _interopRequireDefault(__webpack_require__(/*! html2canvas */ \"./node_modules/html2canvas/dist/html2canvas.esm.js\"));\n\nvar _tokuiValidator = __webpack_require__(/*! @lib/tokui-validator */ \"./src/lib/tokui-validator/index.js\");\n\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\nvar resolveTokUIConstructor = function resolveTokUIConstructor() {\n if (typeof window !== 'undefined') {\n var _window$globalThis;\n\n var runtime = window.TokUI || ((_window$globalThis = window.globalThis) === null || _window$globalThis === void 0 ? void 0 : _window$globalThis.TokUI); // 仅当全局 TokUI 具备 _internal 内部结构时才优先使用(校验补丁与事件总线依赖它),\n // 否则回落到 lib 内置打包的 @jboltai/tokui,保证补丁与实例同源\n\n if (runtime && runtime._internal) {\n var _runtime$default;\n\n return runtime.TokUI || ((_runtime$default = runtime.default) === null || _runtime$default === void 0 ? void 0 : _runtime$default.TokUI) || runtime.default || runtime;\n }\n }\n\n try {\n if (true) {\n var _runtime = __webpack_require__(/*! @jboltai/tokui/dist/tokui.umd.js */ \"./node_modules/@jboltai/tokui/dist/tokui.umd.js\");\n\n if (_runtime) {\n var _runtime$default2;\n\n return _runtime.TokUI || ((_runtime$default2 = _runtime.default) === null || _runtime$default2 === void 0 ? void 0 : _runtime$default2.TokUI) || _runtime.default || _runtime;\n }\n }\n } catch (e) {// ignore and continue to package entry fallback\n }\n\n try {\n if (true) {\n var _runtime2 = __webpack_require__(/*! @jboltai/tokui */ \"./node_modules/@jboltai/tokui/dist/tokui.mjs\");\n\n if (_runtime2) {\n var _runtime2$default;\n\n return _runtime2.TokUI || ((_runtime2$default = _runtime2.default) === null || _runtime2$default === void 0 ? void 0 : _runtime2$default.TokUI) || _runtime2.default || _runtime2;\n }\n }\n } catch (e) {// ignore and leave unresolved\n }\n\n return null;\n}; // TokUI 校验补丁:全局只打一次(依赖 window.TokUI._internal,须在 TokUI 模块加载完成后调用)\n\n\nvar _tokuiValidatorPatched = false;\n\nvar ensureTokuiValidatorPatched = function ensureTokuiValidatorPatched() {\n if (_tokuiValidatorPatched) return;\n if (typeof window === 'undefined' || !window.TokUI || !window.TokUI._internal) return;\n (0, _tokuiValidator.patchTokUI)();\n _tokuiValidatorPatched = true;\n}; // TokUI 实例及其 bridge/DOM 引用可能包含循环结构,避免被 messages 的 deep watcher 遍历。\n\n\nvar setTokuiRuntimeProperty = function setTokuiRuntimeProperty(message, key, value) {\n Object.defineProperty(message, key, {\n configurable: true,\n enumerable: false,\n writable: true,\n value: value\n });\n};\n\nvar md = new _markdownIt.default({\n breaks: true,\n html: false,\n linkify: true,\n typographer: true\n});\nmd.enable('table');\nvar _default2 = {\n name: 'ChatDetail',\n props: {\n isMobile: {\n type: Boolean,\n default: false\n },\n activeSessionId: {\n type: String,\n default: ''\n },\n currentAgent: {\n type: String,\n default: ''\n },\n agentInfo: {\n type: Object,\n default: function _default() {\n return {};\n }\n },\n currentAgentDesc: {\n type: String,\n default: ''\n },\n showSidebar: {\n type: Boolean,\n default: false\n },\n showH5List: {\n type: Boolean,\n default: false\n },\n showAgentSelect: {\n type: Boolean,\n default: false\n },\n agentList: {\n type: Array,\n default: function _default() {\n return [];\n }\n },\n nickName: {\n type: String,\n default: ''\n }\n },\n data: function data() {\n return {\n messages: [],\n inputText: '',\n sending: false,\n waitTimer: null,\n waitSeconds: 0,\n canceling: false,\n msgPage: 1,\n msgMaxPage: 1,\n msgTotal: 0,\n eventSource: null,\n showToolInfo: false,\n showDownBtn: false,\n lastScrollTop: 0,\n scrollTimer: null,\n contentCache: {},\n loadingSessionMsg: false,\n loadingMore: false,\n greeting: '',\n _scrollTimer: null,\n _userScrolledUp: false,\n voiceMode: false,\n isRecording: false,\n recordDuration: 0,\n recordingTimer: null,\n audioContext: null,\n audioStream: null,\n audioProcessor: null,\n audioChunks: [],\n audioSampleRate: 16000,\n isCancelSwipe: false,\n recordStartY: 0,\n isAndroid: false,\n _pendingSendText: null\n };\n },\n computed: {\n waitClass: function waitClass() {\n if (this.waitSeconds > 15) return 'wait-15';\n if (this.waitSeconds > 10) return 'wait-10';\n if (this.waitSeconds > 5) return 'wait-5';\n return '';\n },\n currentAgentName: function currentAgentName() {\n var _this = this;\n\n if (!this.currentAgent) return '';\n var a = this.agentList.find(function (x) {\n return x.agentCode === _this.currentAgent;\n });\n return a ? a.agentName || a.agentCode : this.currentAgent;\n }\n },\n watch: {\n contentCache: {\n handler: function handler(val) {// 保持响应性\n },\n immediate: true\n },\n activeSessionId: function activeSessionId(val) {\n var _this2 = this;\n\n if (val && this._pendingSendText) {\n var text = this._pendingSendText;\n this._pendingSendText = null;\n this.$nextTick(function () {\n return _this2._sendMessage(text);\n });\n }\n },\n messages: {\n handler: function handler() {\n var _this3 = this;\n\n this.$nextTick(function () {\n return _this3._renderTokuiMessages();\n });\n },\n deep: true\n }\n },\n mounted: function mounted() {\n var _this4 = this;\n\n if (window.visualViewport) {\n window.visualViewport.addEventListener('resize', this.onViewportResize, {\n passive: true\n });\n }\n\n if (typeof navigator !== 'undefined') {\n this.isAndroid = /android/i.test(navigator.userAgent);\n }\n\n this.$nextTick(function () {\n _this4._renderTokuiMessages();\n });\n },\n beforeDestroy: function beforeDestroy() {\n if (window.visualViewport) {\n window.visualViewport.removeEventListener('resize', this.onViewportResize);\n }\n\n this.releaseAudio();\n this.contentCache = {};\n clearTimeout(this._scrollTimer);\n\n if (this.eventSource) {\n this.eventSource.onerror = null;\n this.eventSource.close();\n this.eventSource = null;\n }\n\n if (this.waitTimer) {\n clearInterval(this.waitTimer);\n this.waitTimer = null;\n }\n\n if (this._typeTimer) {\n clearInterval(this._typeTimer);\n this._typeTimer = null;\n }\n\n this.messages.forEach(function (msg) {\n if (msg && msg._tokuiValidator && typeof msg._tokuiValidator.destroy === 'function') {\n msg._tokuiValidator.destroy();\n\n msg._tokuiValidator = null;\n }\n });\n },\n methods: {\n // 构建传入 setupValidator 的公共选项(含补卡日历所需的 user / api 信息)\n _buildTokuiBridgeOptions: function _buildTokuiBridgeOptions(extra) {\n var auth = (0, _request.getAuth)();\n var opts = Object.assign({\n user: {\n no: auth.userno || auth.user,\n cno: auth.companyNo\n },\n api: {\n patchQueryMonthClockStatus: '/api/v1/open/attendance/patchQueryMonthClockStatus'\n }\n }, extra || {});\n return opts;\n },\n getInitial: function getInitial(name) {\n if (!name) return '?';\n return name.charAt(0);\n },\n formatTime: function formatTime(t) {\n if (!t) return '';\n var d = new Date(t.replace(' ', 'T'));\n var now = new Date();\n var diff = (now - d) / 1000;\n if (diff < 60) return '刚刚';\n if (diff < 3600) return Math.floor(diff / 60) + '分钟前';\n if (diff < 86400) return Math.floor(diff / 3600) + '小时前';\n return d.getMonth() + 1 + '/' + d.getDate();\n },\n escapeHtml: function escapeHtml(text) {\n if (!text) return '';\n return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n },\n renderMarkdown: function renderMarkdown(text) {\n if (!text) return '';\n\n try {\n return md.render(text);\n } catch (err) {\n return this.escapeHtml(text);\n }\n },\n renderStreaming: function renderStreaming(text, msgKey) {\n if (!text) return '';\n var cache = this.contentCache[msgKey];\n var lines = text.split('\\n');\n var lastLineIdx = lines.length - 1;\n\n if (!cache) {\n try {\n var html = md.render(text);\n this.$set(this.contentCache, msgKey, {\n renderedLines: lastLineIdx,\n html: html,\n fullText: text\n });\n return html;\n } catch (_unused) {\n return this.escapeHtml(text);\n }\n }\n\n if (text === cache.fullText) return cache.html;\n cache.fullText = text; // 行数没变,仅末尾追加字符,直接拼接尾行\n\n if (lastLineIdx === cache.renderedLines) {\n return cache.html + this.escapeHtml(lines[lastLineIdx]);\n } // 场景1:行数变少/文本回退截断,全量重渲染\n\n\n if (lastLineIdx < cache.renderedLines) {\n try {\n var fullLines = lines.slice(0, lastLineIdx);\n var newBaseHtml = '';\n\n if (fullLines.length > 0) {\n newBaseHtml = md.render(fullLines.join('\\n'));\n }\n\n cache.html = newBaseHtml;\n cache.renderedLines = lastLineIdx;\n\n var _lastLine = this.escapeHtml(lines[lastLineIdx]);\n\n return cache.html + (_lastLine || '');\n } catch (_unused2) {\n return this.escapeHtml(text);\n }\n } // 场景2:新增完整行\n\n\n if (cache.renderedLines < lastLineIdx) {\n var allFullLines = lines.slice(0, lastLineIdx);\n var _newBaseHtml = '';\n\n if (allFullLines.length > 0) {\n _newBaseHtml = md.render(allFullLines.join('\\n'));\n }\n\n cache.html = _newBaseHtml;\n cache.renderedLines = lastLineIdx;\n }\n\n var lastLine = this.escapeHtml(lines[lastLineIdx]);\n cache.renderedLines = lastLineIdx;\n return cache.html + (lastLine || '');\n },\n getChatAvatarStyle: function getChatAvatarStyle(agent, avatar) {\n if (avatar) {\n return {\n backgroundImage: \"url(\".concat(avatar, \")\"),\n backgroundSize: 'cover',\n backgroundPosition: 'center',\n backgroundRepeat: 'no-repeat'\n };\n }\n\n var _agent = this.agentList.find(function (x) {\n return x.agentCode === agent;\n });\n\n if (_agent && _agent.avatar) {\n return {\n backgroundImage: \"url(\".concat(_agent.avatar, \")\"),\n backgroundSize: 'cover',\n backgroundPosition: 'center',\n backgroundRepeat: 'no-repeat'\n };\n } // 没有网络图片时,根据智能体在 agentList 中的索引位置决定颜色\n\n\n var index = this.agentList.findIndex(function (item) {\n return item.agentCode === agent;\n });\n\n if (index !== -1) {\n var colorIndex = index % 4 + 1;\n return {\n background: this.getGradientByColorIndex(colorIndex)\n };\n }\n\n return {\n background: 'linear-gradient(135deg, #1677ff, #4096ff)'\n };\n },\n getGradientByColorIndex: function getGradientByColorIndex(index) {\n var gradients = {\n 1: 'linear-gradient(135deg, #1677ff, #4096ff)',\n 2: 'linear-gradient(135deg, #52c41a, #73d13d)',\n 3: 'linear-gradient(135deg, #fa8c16, #ffa940)',\n 4: 'linear-gradient(135deg, #722ed1, #b37feb)'\n };\n return gradients[index] || gradients[1];\n },\n sendQuickMsg: function sendQuickMsg(message) {\n if (message) {\n this.inputText = message;\n this.sendMessage();\n }\n },\n formatMixText: function formatMixText(str) {\n if (!str) return '';\n var res = str.replace(/([a-zA-Z0-9])([\\u4e00-\\u9fff])/g, '$1 $2').replace(/([\\u4e00-\\u9fff])([a-zA-Z0-9])/g, '$1 $2');\n res = res.replace(/([a-z])([A-Z])/g, '$1 $2');\n return res;\n },\n // 检测是否滚动到底部,控制向下箭头显示隐藏\n checkScrollBottom: function checkScrollBottom() {\n var forceShowWhenNotBottom = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n var el = this.$refs.messagesRef;\n\n if (!el) {\n this.showDownBtn = false;\n return;\n }\n\n var scrollHeight = el.scrollHeight,\n scrollTop = el.scrollTop,\n clientHeight = el.clientHeight;\n var remain = scrollHeight - scrollTop - clientHeight;\n var atBottom = remain <= 80; // 检测用户是否主动向上滚动(远离底部)\n\n if (remain > 80 && scrollTop < this.lastScrollTop) {\n this._userScrolledUp = true;\n }\n\n if (atBottom) {\n this._userScrolledUp = false;\n this.showDownBtn = false;\n } else if (this._userScrolledUp) {\n // 用户已主动上滚,保持显示\"到底部\"按钮,不自动回滚\n this.showDownBtn = true;\n } else if (this.sending && scrollTop >= this.lastScrollTop && !forceShowWhenNotBottom) {\n this.showDownBtn = false;\n } else {\n this.showDownBtn = true;\n }\n\n this.lastScrollTop = scrollTop;\n this.$emit('scroll-bottom-check', {\n showDownBtn: this.showDownBtn,\n lastScrollTop: this.lastScrollTop\n });\n },\n // H5 输入框获取焦点 - 滚动到底部让键盘不遮挡\n onInputFocus: function onInputFocus() {\n if (!this.isMobile) return;\n setTimeout(function () {\n window.scrollTo(0, document.body.scrollHeight);\n }, 300);\n },\n // visualViewport 变化时(如键盘弹出)调整滚动位置\n onViewportResize: function onViewportResize() {\n if (!this.isMobile) return;\n var activeEl = document.activeElement;\n\n if (activeEl && (activeEl.tagName === 'TEXTAREA' || activeEl.tagName === 'INPUT')) {\n window.scrollTo(0, document.body.scrollHeight);\n }\n },\n autoResizeTextarea: function autoResizeTextarea() {\n var _this5 = this;\n\n var ta = this.$refs.taRef;\n if (!ta) return;\n var isMobile = this.isMobile;\n var baseH = isMobile ? 36 : 100;\n var maxH = isMobile ? 100 : 180;\n ta.style.height = \"\".concat(baseH, \"px\");\n var realH = ta.scrollHeight;\n\n if (realH > baseH) {\n ta.style.height = \"\".concat(Math.min(realH, maxH), \"px\");\n }\n\n this.$nextTick(function () {\n _this5.checkScrollBottom(true);\n });\n },\n // Ctrl+Enter换行\n newLine: function newLine(e) {\n var _this6 = this;\n\n e.preventDefault();\n var ta = this.$refs.taRef;\n var start = ta.selectionStart;\n var end = ta.selectionEnd;\n this.inputText = this.inputText.slice(0, start) + '\\n' + this.inputText.slice(end);\n this.$nextTick(function () {\n ta.selectionStart = ta.selectionEnd = start + 1;\n\n _this6.autoResizeTextarea();\n });\n },\n toggleVoiceMode: function toggleVoiceMode() {\n var _this7 = this;\n\n if (this.voiceMode) {\n this.voiceMode = false;\n this.releaseAudio();\n return;\n } // 进入语音模式前提前请求麦克风权限,避免触摸时异步弹窗导致 touchend 丢失\n\n\n this.ensureAudioReady().then(function (ready) {\n if (ready) {\n _this7.voiceMode = true;\n } else {\n _this7.voiceMode = false;\n }\n });\n },\n // 确保音频设备已就绪\n ensureAudioReady: function ensureAudioReady() {\n var _this8 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {\n var stream, audioContext, source, processor;\n return regeneratorRuntime.wrap(function _callee$(_context) {\n while (1) {\n switch (_context.prev = _context.next) {\n case 0:\n if (!(_this8.audioContext && _this8.audioStream)) {\n _context.next = 2;\n break;\n }\n\n return _context.abrupt(\"return\", true);\n\n case 2:\n if (!(!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia)) {\n _context.next = 5;\n break;\n }\n\n alert('当前环境不支持麦克风访问');\n return _context.abrupt(\"return\", false);\n\n case 5:\n _context.prev = 5;\n _context.next = 8;\n return navigator.mediaDevices.getUserMedia({\n audio: true\n });\n\n case 8:\n stream = _context.sent;\n _this8.audioStream = stream;\n audioContext = new (window.AudioContext || window.webkitAudioContext)(); // iOS 上 AudioContext 默认 suspended,需要手动 resume\n\n if (!(audioContext.state === 'suspended')) {\n _context.next = 14;\n break;\n }\n\n _context.next = 14;\n return audioContext.resume();\n\n case 14:\n _this8.audioContext = audioContext; // 采集实际采样率(iOS 会忽略 sampleRate 参数,使用硬件采样率)\n\n _this8.audioSampleRate = audioContext.sampleRate || 16000;\n source = audioContext.createMediaStreamSource(stream); // 保存 source 引用到实例,防止 iOS Safari 垃圾回收导致音频处理中断\n\n _this8.audioSource = source;\n processor = audioContext.createScriptProcessor(4096, 1, 1);\n _this8.audioProcessor = processor; // iOS 上 onaudioprocess 可能先于 isRecording=true 触发\n // 因此不依赖 isRecording 过滤,由外部控制 audioChunks 的清零时机\n\n processor.onaudioprocess = function (e) {\n var inputData = e.inputBuffer.getChannelData(0);\n\n _this8.audioChunks.push(new Float32Array(inputData));\n };\n\n source.connect(processor);\n processor.connect(audioContext.destination);\n return _context.abrupt(\"return\", true);\n\n case 26:\n _context.prev = 26;\n _context.t0 = _context[\"catch\"](5);\n console.error('麦克风访问失败:', _context.t0);\n alert('麦克风权限被拒绝');\n return _context.abrupt(\"return\", false);\n\n case 31:\n case \"end\":\n return _context.stop();\n }\n }\n }, _callee, null, [[5, 26]]);\n }))();\n },\n // H5语音录制 - 触摸开始\n onRecordTouchStart: function onRecordTouchStart(e) {\n var _this9 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() {\n var ready;\n return regeneratorRuntime.wrap(function _callee2$(_context2) {\n while (1) {\n switch (_context2.prev = _context2.next) {\n case 0:\n if (!_this9.sending) {\n _context2.next = 2;\n break;\n }\n\n return _context2.abrupt(\"return\");\n\n case 2:\n _context2.next = 4;\n return _this9.ensureAudioReady();\n\n case 4:\n ready = _context2.sent;\n\n if (ready) {\n _context2.next = 7;\n break;\n }\n\n return _context2.abrupt(\"return\");\n\n case 7:\n _this9.isCancelSwipe = false;\n _this9.recordStartY = e.touches[0].clientY;\n _this9.recordDuration = 0;\n _this9.audioChunks = [];\n _this9.isRecording = true;\n _this9.recordingTimer = setInterval(function () {\n _this9.recordDuration++;\n\n if (_this9.recordDuration >= 60) {\n _this9.onRecordTouchEnd();\n }\n }, 1000);\n\n case 13:\n case \"end\":\n return _context2.stop();\n }\n }\n }, _callee2);\n }))();\n },\n // H5语音录制 - 触摸移动\n onRecordTouchMove: function onRecordTouchMove(e) {\n if (!this.isRecording) return;\n var touch = e.touches[0];\n var moveY = touch.clientY;\n var cancelThreshold = 60;\n this.isCancelSwipe = this.recordStartY - moveY > cancelThreshold;\n },\n // H5语音录制 - 触摸结束\n onRecordTouchEnd: function onRecordTouchEnd() {\n if (!this.isRecording) return;\n this.isRecording = false;\n\n if (this.recordingTimer) {\n clearInterval(this.recordingTimer);\n this.recordingTimer = null;\n }\n\n this.stopAudioCapture(!this.isCancelSwipe);\n },\n // 停止音频采集并输出 WAV\n stopAudioCapture: function stopAudioCapture(shouldSend) {\n this.isRecording = false;\n\n if (shouldSend && this.audioChunks.length > 0) {\n var wavBlob = this.encodePCMToWav(this.audioChunks, this.audioSampleRate || 16000);\n this.sendAudioBlob(wavBlob);\n }\n\n this.audioChunks = [];\n },\n // 取消录音\n cancelRecording: function cancelRecording() {\n this.isRecording = false;\n\n if (this.recordingTimer) {\n clearInterval(this.recordingTimer);\n this.recordingTimer = null;\n }\n\n this.recordDuration = 0;\n this.isCancelSwipe = false;\n this.audioChunks = [];\n },\n // 释放音频资源\n releaseAudio: function releaseAudio() {\n this.cancelRecording();\n\n if (this.audioProcessor) {\n this.audioProcessor.disconnect();\n this.audioProcessor = null;\n }\n\n if (this.audioSource) {\n this.audioSource.disconnect();\n this.audioSource = null;\n }\n\n if (this.audioContext) {\n this.audioContext.close().catch(function () {});\n this.audioContext = null;\n }\n\n if (this.audioStream) {\n this.audioStream.getTracks().forEach(function (t) {\n return t.stop();\n });\n this.audioStream = null;\n }\n },\n // 将 Float32 PCM 数据编码为 WAV Blob\n encodePCMToWav: function encodePCMToWav(chunks, sampleRate) {\n var totalLength = 0;\n\n var _iterator = (0, _createForOfIteratorHelper2.default)(chunks),\n _step;\n\n try {\n for (_iterator.s(); !(_step = _iterator.n()).done;) {\n var chunk = _step.value;\n totalLength += chunk.length;\n }\n } catch (err) {\n _iterator.e(err);\n } finally {\n _iterator.f();\n }\n\n var pcmData = new Int16Array(totalLength);\n var offset = 0;\n\n var _iterator2 = (0, _createForOfIteratorHelper2.default)(chunks),\n _step2;\n\n try {\n for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {\n var _chunk = _step2.value;\n\n for (var i = 0; i < _chunk.length; i++) {\n var s = Math.max(-1, Math.min(1, _chunk[i]));\n pcmData[offset++] = s < 0 ? s * 0x8000 : s * 0x7FFF;\n }\n }\n } catch (err) {\n _iterator2.e(err);\n } finally {\n _iterator2.f();\n }\n\n var numChannels = 1;\n var bitsPerSample = 16;\n var byteRate = sampleRate * numChannels * bitsPerSample / 8;\n var blockAlign = numChannels * bitsPerSample / 8;\n var dataSize = pcmData.length * (bitsPerSample / 8);\n var headerSize = 44;\n var totalSize = headerSize + dataSize;\n var buffer = new ArrayBuffer(totalSize);\n var view = new DataView(buffer);\n\n this._wavWriteString(view, 0, 'RIFF');\n\n view.setUint32(4, totalSize - 8, true);\n\n this._wavWriteString(view, 8, 'WAVE');\n\n this._wavWriteString(view, 12, 'fmt ');\n\n view.setUint32(16, 16, true);\n view.setUint16(20, 1, true);\n view.setUint16(22, numChannels, true);\n view.setUint32(24, sampleRate, true);\n view.setUint32(28, byteRate, true);\n view.setUint16(32, blockAlign, true);\n view.setUint16(34, bitsPerSample, true);\n\n this._wavWriteString(view, 36, 'data');\n\n view.setUint32(40, dataSize, true);\n var pcmView = new Int16Array(buffer, headerSize, pcmData.length);\n pcmView.set(pcmData);\n return new Blob([buffer], {\n type: 'audio/wav'\n });\n },\n _wavWriteString: function _wavWriteString(view, offset, str) {\n for (var i = 0; i < str.length; i++) {\n view.setUint8(offset + i, str.charCodeAt(i));\n }\n },\n // 发送音频\n sendAudioBlob: function sendAudioBlob(blob) {\n var appSeqId = _index2.default.getAppSeqId();\n\n var name = appSeqId + '.wav';\n var file = new File([blob], name, {\n type: 'audio/wav'\n });\n this.sendAudioMessage(file);\n },\n sendAudioMessage: function sendAudioMessage(file) {\n var _this10 = this;\n\n var sttKey = 'stt_' + Date.now();\n var sttMsg = {\n role: 'user',\n content: '转化中...',\n _sttStatus: 'pending',\n _sttKey: sttKey,\n createTime: new Date().toISOString(),\n _key: sttKey\n };\n this.messages.push(sttMsg);\n this.scrollToBottom(true);\n (0, _index.fileUploadToObs)({\n _file: file,\n appSeqId: file.name\n }).then(function (resp) {\n if (resp.CommonMsg.Status === 200) {\n return _apiObs.default.aliAudioToText({\n url: resp.obs_upload_data.obsUrl\n }).then(function (result) {\n if (result.code === '200' && result.data) {\n var idx = _this10.messages.findIndex(function (m) {\n return m._sttKey === sttKey;\n });\n\n if (idx !== -1) {\n _this10.messages.splice(idx, 1);\n }\n\n _this10.sendQuickMsg(result.data);\n } else {\n throw new Error(result.msg || '语音转文字失败');\n }\n });\n } else {\n throw new Error(resp.CommonMsg.Message || '文件上传失败');\n }\n }).catch(function (err) {\n console.error('语音转文字失败:', err);\n\n var idx = _this10.messages.findIndex(function (m) {\n return m._sttKey === sttKey;\n });\n\n if (idx !== -1) {\n _this10.$set(_this10.messages, idx, (0, _objectSpread2.default)((0, _objectSpread2.default)({}, _this10.messages[idx]), {}, {\n _sttStatus: 'error',\n content: '语音转文字失败'\n }));\n }\n\n _this10.$message.error('语音转文字失败');\n });\n this.$message.success(\"\\u5F55\\u97F3\\u5B8C\\u6210 (\".concat(this.recordDuration, \"s)\"));\n },\n clearWaitTimer: function clearWaitTimer() {\n if (this.waitTimer) {\n clearInterval(this.waitTimer);\n this.waitTimer = null;\n }\n },\n loadMoreMessages: function loadMoreMessages() {\n var _this11 = this;\n\n var nextPage = this.msgPage + 1;\n var maxPage = Math.ceil(this.msgTotal / 10);\n\n if (nextPage > maxPage) {\n this.loadingMore = false;\n return;\n }\n\n (0, _apichat.getSessionMessages)(this.activeSessionId, nextPage, 10).then(function (res) {\n var list = Array.isArray(res) ? res : res.list || [];\n _this11.msgPage = nextPage;\n var asAsc = (list || []).slice().reverse();\n var prepend = asAsc.map(function (m) {\n return {\n id: m.id,\n role: m.role,\n content: m.content || '',\n thinking: m.thinking || '',\n toolCalls: [],\n agentName: '',\n createTime: m.createTime || null,\n _key: m.id != null ? 'msg_' + m.id : 'msg_' + Date.now() + '_' + Math.random(),\n msgKey: m.msgKey,\n formSubmitted: m.formSubmitted || 0,\n formData: m.formData || null,\n // 历史消息中如果包含 ```tokui 标记或以 TokUI 标签开头,自动启用 TokUI 渲染\n _hasTokui: m.role === 'assistant' && m.content && (/```tokui/.test(m.content) || /^\\s*\\[(card|row|list|table|callout|desc|dv|ft)\\b/.test(m.content.trim()))\n };\n });\n var container = _this11.$refs.messagesRef;\n var oldScrollHeight = container ? container.scrollHeight : 0;\n var oldScrollTop = container ? container.scrollTop : 0;\n _this11.messages = [].concat((0, _toConsumableArray2.default)(prepend), (0, _toConsumableArray2.default)(_this11.messages));\n\n _this11.$nextTick(function () {\n if (container) {\n container.scrollTop = oldScrollTop + (container.scrollHeight - oldScrollHeight);\n }\n\n _this11.loadingMore = false;\n });\n }).catch(function () {\n _this11.loadingMore = false;\n });\n },\n onScrollMessages: function onScrollMessages() {\n var container = this.$refs.messagesRef;\n if (!container || this.loadingMore) return;\n var isAtBottom = container.scrollTop + container.clientHeight >= container.scrollHeight - 100;\n this.checkScrollBottom();\n if (isAtBottom) return;\n\n if (container.scrollTop < 50 && this.msgPage < Math.ceil(this.msgTotal / 10)) {\n this.loadingMore = true;\n this.loadMoreMessages();\n }\n },\n // 检测内容是否为纯 TokUI DSL(无 ```tokui 围栏,直接以 TokUI 标签开头)\n // TokUI 标签名由字母开头,可包含字母、数字、连字符(如 hover-card、think-chain)\n _isPureTokuiDsl: function _isPureTokuiDsl(text) {\n if (!text) return false;\n return /^\\s*\\[[a-zA-Z][a-zA-Z0-9-]*(?=[\\s\\]])/.test(text.trim());\n },\n // 提取 ```tokui 代码块中的 DSL,返回混合内容片段数组\n // 每项: { type: 'md' | 'dsl', content: string }\n _extractTokuiDsl: function _extractTokuiDsl(text) {\n if (!text) return [{\n type: 'md',\n content: ''\n }]; // 纯 TokUI DSL(无 ```tokui 围栏):整个内容作为 DSL 处理\n\n if (this._isPureTokuiDsl(text)) {\n return [{\n type: 'dsl',\n content: text.trim()\n }];\n }\n\n var parts = [];\n var re = /```tokui\\s*([\\s\\S]*?)```/g;\n var lastIndex = 0;\n var match;\n\n while ((match = re.exec(text)) !== null) {\n if (match.index > lastIndex) {\n parts.push({\n type: 'md',\n content: text.substring(lastIndex, match.index)\n });\n }\n\n var dsl = match[1].trim();\n if (dsl) parts.push({\n type: 'dsl',\n content: dsl\n });\n lastIndex = match.index + match[0].length;\n }\n\n if (lastIndex < text.length) {\n parts.push({\n type: 'md',\n content: text.substring(lastIndex)\n });\n }\n\n return parts;\n },\n // 将“申请人”输入框的默认值(v:)替换为当前登录用户昵称\n _applyApplicantNickName: function _applyApplicantNickName(line) {\n if (!line || !this.nickName) return line; // 仅处理申请人字段所在行(id:applierName / l:申请人)\n\n if (!/applierName|申请人/.test(line)) return line;\n var val = String(this.nickName).replace(/\"/g, '');\n return line.replace(/\\bv\\s*:\\s*(?:\"[^\"]*\"|[^\\s\\]]+)/, \"v:\\\"\".concat(val, \"\\\"\"));\n },\n // ── 收尾对齐:实时渲染 endStream 后与历史记录渲染做统一对齐 ──\n // 解决实时渲染增量 feed 导致的初始化遗漏问题:\n // 1. 条件显示:重新扫描所有 data-visible-when / data-visible-when-not 规则\n // 2. 级联 select:重新检测未初始化的 select[data-load],补做 setupCascade\n // 3. 审批流程:重新检测 data-approval-type 表单\n // 4. data-default-label:未匹配的 select 尝试再次匹配\n _reconcileAfterStream: function _reconcileAfterStream(msg, tokuiDiv) {\n if (!tokuiDiv) return; // 延迟执行,确保 endStream 的 DOM 操作全部完成\n\n setTimeout(function () {\n try {\n var scope = tokuiDiv; // ① 条件显示:调用 bridge 的全局函数重新扫描(传入 tokuiDiv 作用域)\n\n if (typeof window !== 'undefined' && window.__tokuiValidatorBridge) {\n var bridge = window.__tokuiValidatorBridge;\n\n if (bridge.applyDefaultValues) {\n bridge.applyDefaultValues(scope);\n }\n\n if (bridge.fixDateTimeDefaults) {\n bridge.fixDateTimeDefaults(scope);\n }\n\n if (bridge.initConditionalVisibility) {\n bridge.initConditionalVisibility(scope);\n }\n\n if (bridge.updateConditionalVisibility) {\n bridge.updateConditionalVisibility(scope);\n }\n } // ② 级联 select:检测未初始化的 select[data-load],补做 setupCascade\n\n\n var selects = scope.querySelectorAll('select[data-load], [data-load] select');\n selects.forEach(function (sel) {\n if (!sel._tokuiCascadeSetup) {\n sel._tokuiCascadeSetup = true; // 触发 change 事件,让已有的级联逻辑生效\n\n try {\n sel.dispatchEvent(new Event('change', {\n bubbles: true\n }));\n } catch (e) {}\n }\n }); // ③ 审批流程:强制重新定位到提交按钮前面\n // 实时渲染中 initApprovalFlow 可能在流式传输中途被触发,\n // 此时 btngroup 尚未渲染,审批流程被 appendChild 到错误位置。\n // endStream 后 DOM 已完整,这里强制修正位置。\n\n var approvalContainer = scope.querySelector('.tv-approval-flow');\n\n if (approvalContainer) {\n // 找到提交按钮组\n var allBtns = scope.querySelectorAll('button');\n var btngroup = null;\n\n for (var bi = 0; bi < allBtns.length; bi++) {\n var btxt = (allBtns[bi].textContent || '').trim();\n\n if (btxt === '提交' || btxt === '暂存' || btxt === '重置') {\n btngroup = allBtns[bi].parentElement;\n break;\n }\n }\n\n if (btngroup) {\n // 检查是否已在 wrapper 中且顺序正确\n var btnParent = btngroup.parentNode;\n var _approvalDone = false;\n\n if (btnParent && btnParent.classList.contains('tv-approval-wrapper')) {\n var existingFlow = btnParent.querySelector('.tv-approval-flow');\n\n if (existingFlow === approvalContainer && btnParent.firstChild === approvalContainer) {\n // 已在正确位置,确保可见\n approvalContainer.classList.remove('tv-approval-flow--hidden');\n _approvalDone = true;\n } else {\n // wrapper 存在但顺序不对或有旧 flow,拆开重建\n var wp = btnParent.parentNode;\n\n if (wp) {\n while (btnParent.firstChild) {\n wp.insertBefore(btnParent.firstChild, btnParent);\n }\n\n btnParent.remove();\n }\n }\n }\n\n if (!_approvalDone) {\n // 创建新包裹层,确保审批流程紧挨在 btngroup 上方\n var newWrapper = document.createElement('div');\n newWrapper.className = 'tv-approval-wrapper';\n btngroup.parentNode.insertBefore(newWrapper, btngroup);\n newWrapper.appendChild(approvalContainer);\n newWrapper.appendChild(btngroup); // 定位完成,移除隐藏类\n\n approvalContainer.classList.remove('tv-approval-flow--hidden');\n console.log('[TokUI] _reconcileAfterStream: 审批流程已重新定位到按钮组前面');\n }\n }\n } // ④ data-default-label:未匹配的 select 再试一次\n\n\n var defaultSelects = scope.querySelectorAll('select[data-default-label]');\n defaultSelects.forEach(function (sel) {\n var label = sel.getAttribute('data-default-label');\n if (!label || !sel.options) return;\n if (label.indexOf('${') >= 0) return; // 未替换的占位符\n // 检查是否已选中\n\n var selected = sel.options[sel.selectedIndex];\n if (selected && (selected.textContent || '').trim() === label.trim()) return; // 尝试匹配\n\n for (var i = 0; i < sel.options.length; i++) {\n if ((sel.options[i].textContent || '').trim() === label.trim()) {\n sel.selectedIndex = i;\n\n try {\n sel.dispatchEvent(new Event('change', {\n bubbles: true\n }));\n } catch (e) {}\n\n break;\n }\n }\n }); // ⑤ 最终条件显示刷新\n\n if (typeof window !== 'undefined' && window.__tokuiValidatorBridge && window.__tokuiValidatorBridge.updateConditionalVisibility) {\n setTimeout(function () {\n window.__tokuiValidatorBridge.updateConditionalVisibility(scope);\n }, 100);\n setTimeout(function () {\n window.__tokuiValidatorBridge.updateConditionalVisibility(scope);\n }, 500);\n } // ⑥ 补卡日历初始化:SSE 流式渲染时表单字段是逐步生成的,\n // setupPatchCardCalendar 的固定延迟可能早于字段完整渲染导致提前 return。\n // endStream 后 DOM 已完整,这里显式触发补卡日历加载(含 patchQueryMonthClockStatus 接口调用)\n\n\n var patchForms = scope.querySelectorAll('[data-approval-type=\"patch\"]');\n\n if (patchForms.length > 0 && typeof window !== 'undefined' && window.__tokuiValidatorBridge && window.__tokuiValidatorBridge.setupPatchCardCalendar) {\n patchForms.forEach(function (el) {\n var form = el.closest('form') || el.closest('[data-form-id]') || el;\n\n if (form) {\n console.log('[TokUI] _reconcileAfterStream: 触发补卡日历初始化 for form:', form);\n\n window.__tokuiValidatorBridge.setupPatchCardCalendar(form);\n }\n });\n } // ⑦ 标记表单就绪:审批流程等依赖「表单完全渲染」的逻辑在此之后才允许初始化/预览,\n // 避免 SSE 增量渲染期间在半成品 DOM 上触发审批接口导致报错。\n\n\n if (typeof window !== 'undefined' && window.__tokuiValidatorBridge && window.__tokuiValidatorBridge.markFormReady) {\n window.__tokuiValidatorBridge.markFormReady(scope);\n }\n\n console.log('[TokUI] _reconcileAfterStream: 对齐完成');\n } catch (e) {\n console.warn('[TokUI] _reconcileAfterStream error:', e);\n }\n }, 200);\n },\n // 全文本增量流式 feed TokUI(参考项目方式)\n _streamTokui: function _streamTokui(msg, fullText) {\n if (!msg._tokuiStream) return; // 动态判断当前内容是否为纯 TokUI DSL\n\n var isPureDsl = this._isPureTokuiDsl(fullText); // 如果内容模式发生了变化,重置增量追踪\n\n\n if (msg._tokuiPureDsl !== isPureDsl) {\n msg._tokuiPureDsl = isPureDsl;\n msg._prevMdLen = 0;\n msg._prevDslLen = 0;\n msg._dslBuffer = '';\n }\n\n var dsl = '';\n var md = '';\n\n if (isPureDsl) {\n // 纯 DSL:整个内容为 DSL\n dsl = fullText;\n } else {\n // 混合内容:从 ```tokui 围栏中提取 DSL\n var dslParts = []; // 函数内定义,每次执行全新正则,杜绝lastIndex污染\n\n var re = /```tokui\\s*([\\s\\S]*?)```/g;\n var match;\n\n while ((match = re.exec(fullText)) !== null) {\n var inner = match[1].trim();\n if (inner) dslParts.push(inner);\n } // 处理末尾未闭合的 ```tokui(流式场景)\n\n\n var lastIdx = re.lastIndex;\n var remaining = fullText.substring(lastIdx);\n var openIdx = remaining.lastIndexOf('```tokui');\n\n if (openIdx >= 0) {\n var dslPart = remaining.substring(openIdx + 8);\n if (dslPart.charCodeAt(0) === 10) dslPart = dslPart.substring(1); // ✅ 关键防护:如果片段里面存在闭合标记 ```,说明是完整块,不要再追加!\n\n if (dslPart && !dslPart.includes('```')) {\n dslParts.push(dslPart);\n }\n }\n\n dsl = dslParts.join('\\n'); // 移除所有代码围栏得到纯 Markdown\n\n md = fullText.replace(/```[\\s\\S]*?(```|$)/g, '');\n } // Feed Markdown 增量\n\n\n if (md) {\n var prevMdLen = msg._prevMdLen || 0;\n\n if (md.length > prevMdLen) {\n var newMd = md.substring(prevMdLen);\n msg._prevMdLen = md.length;\n\n if (!msg._mdTagOpen) {\n msg._tokuiStream.feed('[md]\\n');\n\n msg._mdTagOpen = true;\n }\n\n msg._tokuiStream.feed(newMd);\n }\n } // Feed DSL 增量\n\n\n if (dsl) {\n var prevDslLen = msg._prevDslLen || 0;\n\n if (dsl.length > prevDslLen) {\n var newDsl = dsl.substring(prevDslLen);\n msg._prevDslLen = dsl.length;\n\n if (msg._mdTagOpen) {\n msg._tokuiStream.feed('[/md]\\n');\n\n msg._mdTagOpen = false;\n } // 逐行 feed DSL,缓冲未完成行\n\n\n msg._dslBuffer = (msg._dslBuffer || '') + newDsl;\n\n var lines = msg._dslBuffer.split('\\n');\n\n for (var i = 0; i < lines.length - 1; i++) {\n msg._tokuiStream.feed(this._applyApplicantNickName(lines[i]) + '\\n');\n }\n\n msg._dslBuffer = lines[lines.length - 1];\n }\n }\n },\n // 渲染历史消息中的 TokUI 内容(仅在 TokUI 可用时执行)\n _renderTokuiMessages: function _renderTokuiMessages() {\n var _this12 = this;\n\n var TokUIConstructor = resolveTokUIConstructor();\n if (!TokUIConstructor) return;\n ensureTokuiValidatorPatched();\n var container = this.$refs.messagesRef;\n if (!container) return;\n var allRows = container.querySelectorAll('.msg-row');\n allRows.forEach(function (row, idx) {\n var msg = _this12.messages[idx];\n if (!msg || !msg._hasTokui) return; // 已通过流式渲染完成的消息,跳过(防止 endStream 后 watcher 触发重复渲染)\n\n if (msg._tokuiRendered) return;\n if (msg.loading || !msg.content || msg._tokuiStream) return;\n var tokuiDiv = row.querySelector('.tokui-stream');\n if (!tokuiDiv) return; // 清除旧内容和状态(DOM 复用 + 流式残留)\n\n if (msg._tokuiValidator && typeof msg._tokuiValidator.destroy === 'function') {\n msg._tokuiValidator.destroy();\n\n msg._tokuiValidator = null;\n }\n\n tokuiDiv.innerHTML = '';\n tokuiDiv.classList.remove('tokui-streaming');\n tokuiDiv.style.display = 'block';\n\n try {\n var ui = new TokUIConstructor({\n container: tokuiDiv\n }); // 绑定校验引擎(data-rule / data-trigger / data-load / formSubmit)\n\n var validatorBridge = (0, _tokuiValidator.setupValidator)(ui, new _tokuiValidator.Validator(), _this12._buildTokuiBridgeOptions({\n container: tokuiDiv,\n scopeKey: msg.msgKey || msg._key,\n skipDataLoad: msg.formSubmitted === 1,\n onFormSuccess: function onFormSuccess(data) {\n return _this12._handleTokuiFormSuccess(msg, data);\n }\n }));\n setTokuiRuntimeProperty(msg, '_tokuiValidator', validatorBridge); // ── 与 _streamTokui 完全一致的提取逻辑(含未闭合围栏处理) ──\n\n var text = msg.content;\n\n var isPureDsl = _this12._isPureTokuiDsl(text);\n\n var dsl = '';\n var md = '';\n\n if (isPureDsl) {\n dsl = text;\n } else {\n var dslParts = []; // 每次执行新建正则!!不要提取到外层复用\n\n var re = /```tokui\\s*([\\s\\S]*?)```/g;\n var match;\n\n while ((match = re.exec(text)) !== null) {\n var inner = match[1].trim();\n if (inner) dslParts.push(inner);\n } // 处理未闭合的 ```tokui(流式逻辑)\n\n\n var lastIdx = re.lastIndex;\n var remaining = text.substring(lastIdx);\n var openIdx = remaining.lastIndexOf('```tokui');\n\n if (openIdx >= 0) {\n var dslPart = remaining.substring(openIdx + 8);\n if (dslPart.charCodeAt(0) === 10) dslPart = dslPart.substring(1); // 增强防护:如果这段dslPart包含闭合标记```,说明它其实是完整块,不要追加!\n\n if (dslPart && !dslPart.includes('```')) {\n dslParts.push(dslPart);\n }\n }\n\n dsl = dslParts.join('\\n');\n md = text.replace(/```[\\s\\S]*?(```|$)/g, '');\n } // ── 与流式完全一致的 feed 顺序和方式 ──\n\n\n ui.startStream(); // Feed Markdown\n\n if (md && md.trim()) {\n ui.feed('[md]\\n');\n ui.feed(md);\n ui.feed('[/md]\\n');\n } // Feed DSL 逐行(与流式 _streamTokui 完全一致)\n\n\n if (dsl) {\n var dslLines = dsl.split('\\n');\n\n for (var i = 0; i < dslLines.length; i++) {\n ui.feed(_this12._applyApplicantNickName(dslLines[i]) + '\\n');\n }\n }\n\n ui.endStream();\n tokuiDiv._tokuiRendered = true;\n\n _this12.$set(msg, '_tokuiRendered', true); // 已提交的表单:跳过收尾对齐(避免 applyDefaultValues 覆盖反显值 / change 触发校验),\n // 直接反显 formData 并恢复遮罩;未提交的表单走正常收尾对齐。\n\n\n if (msg.formSubmitted === 1) {\n _this12._restoreTokuiFormOverlay(msg, tokuiDiv);\n } else {\n _this12._reconcileAfterStream(msg, tokuiDiv);\n }\n } catch (e) {\n console.error('[TokUI] render error:', e);\n tokuiDiv.textContent = msg.content;\n }\n });\n },\n // 已提交表单的遮罩恢复:历史消息重渲染后重新锁定,防止二次提交\n _restoreTokuiFormOverlay: function _restoreTokuiFormOverlay(msg, tokuiDiv) {\n if (!msg || msg.formSubmitted !== 1 || msg.role !== 'assistant') return;\n if (!tokuiDiv) return;\n var bridge = window.__tokuiValidatorBridge;\n if (!bridge) return;\n console.log('[TokUI] _restoreTokuiFormOverlay: msgKey=' + (msg.msgKey || msg._key) + ', hasFormData=' + !!msg.formData + ', formData=' + (msg.formData ? JSON.stringify(msg.formData).substring(0, 200) : 'null')); // 1) 把提交时保存的 formData 反显回表单(不触发校验)\n\n if (bridge.applyFormData && msg.formData) {\n // 立即回填一次,并在 200ms 后再回填一次(兜底:TokUI 异步渲染可能导致首次回填时 DOM 未就绪)\n bridge.applyFormData(tokuiDiv, msg.formData);\n if (bridge.fixDateTimeDefaults) bridge.fixDateTimeDefaults(tokuiDiv);\n setTimeout(function () {\n if (tokuiDiv && tokuiDiv.parentNode) {\n bridge.applyFormData(tokuiDiv, msg.formData);\n if (bridge.fixDateTimeDefaults) bridge.fixDateTimeDefaults(tokuiDiv);\n }\n }, 200);\n } else {\n console.warn('[TokUI] _restoreTokuiFormOverlay: formData 为空,无法回填表单字段'); // 即使没有 formData,也尝试补全 datetimepicker 默认时间\n\n if (bridge.fixDateTimeDefaults) bridge.fixDateTimeDefaults(tokuiDiv);\n } // 2) 复用 lockFormWithOverlay 加遮罩(与提交按钮后的效果完全一致)\n\n\n var form = tokuiDiv.querySelector('form') || tokuiDiv;\n\n if (bridge.lockFormWithOverlay) {\n bridge.lockFormWithOverlay(form);\n }\n },\n // TokUI 表单提交成功回调(bridge 在校验通过且 fetch 成功后触发)\n // 1) 标记消息已提交 2) $emit 通知宿主(持久化/工作流由宿主决定) 3) 自动回传摘要驱动 AI 继续会话\n _handleTokuiFormSuccess: function _handleTokuiFormSuccess(msg, payload) {\n if (msg) this.$set(msg, 'formSubmitted', 1);\n var formData = payload && payload.formData || {};\n this.$emit('form-submit', {\n sessionId: this.activeSessionId,\n msgKey: msg && (msg.msgKey || msg._key),\n formId: payload && payload.formId || null,\n formData: formData,\n resultText: payload && payload.resultText || ''\n });\n if (!this.activeSessionId || this.sending) return; // const pairs = Object.keys(formData).map(k => k + '=' + formData[k])\n // const summary = '已提交表单' + (payload && payload.formId ? '「' + payload.formId + '」' : '') + (pairs.length ? ':' + pairs.join(',') : '')\n // this._sendMessage(summary)\n },\n sendMessage: function sendMessage() {\n var _this13 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee3() {\n var text;\n return regeneratorRuntime.wrap(function _callee3$(_context3) {\n while (1) {\n switch (_context3.prev = _context3.next) {\n case 0:\n text = _this13.inputText.trim();\n\n if (!(!text || _this13.sending)) {\n _context3.next = 3;\n break;\n }\n\n return _context3.abrupt(\"return\");\n\n case 3:\n if (!(text.length > 300)) {\n _context3.next = 6;\n break;\n }\n\n _this13.$message({\n message: '消息内容不能超过300字',\n type: 'warning',\n duration: 2000\n });\n\n return _context3.abrupt(\"return\");\n\n case 6:\n if (_this13.activeSessionId) {\n _context3.next = 10;\n break;\n }\n\n _this13.$emit('start-session');\n\n _this13._pendingSendText = text;\n return _context3.abrupt(\"return\");\n\n case 10:\n _this13._sendMessage(text);\n\n case 11:\n case \"end\":\n return _context3.stop();\n }\n }\n }, _callee3);\n }))();\n },\n _sendMessage: function _sendMessage(text) {\n var _this14 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee4() {\n var ta, isMobile, baseH, agentName, msgIdx, thinkingBuffer, finalContent, answerBuffer, thinkDisplayed, typeTimer, streamDone, streamFinalized, finalizeStreamMessage, url, es;\n return regeneratorRuntime.wrap(function _callee4$(_context4) {\n while (1) {\n switch (_context4.prev = _context4.next) {\n case 0:\n _this14.inputText = '';\n ta = _this14.$refs.taRef;\n isMobile = _this14.isMobile;\n baseH = isMobile ? 36 : 100;\n\n if (ta) {\n ta.style.height = baseH + 'px';\n }\n\n _this14.showDownBtn = false;\n\n _this14.messages.push({\n role: 'user',\n content: text,\n createTime: new Date().toISOString(),\n _key: 'user_' + Date.now() + '_' + Math.random()\n });\n\n agentName = _this14.currentAgentName;\n msgIdx = _this14.messages.push({\n role: \"assistant\",\n content: \"\",\n thinking: \"\",\n loading: true,\n toolCalls: [],\n agentName: agentName,\n createTime: new Date().toISOString(),\n _key: 'asst_' + Date.now() + '_' + Math.random()\n }) - 1;\n\n _this14.scrollToBottom(true);\n\n _this14.sending = true;\n _this14.waitSeconds = 0;\n if (_this14.waitTimer) clearInterval(_this14.waitTimer);\n _this14.waitTimer = setInterval(function () {\n _this14.waitSeconds++;\n }, 1000);\n thinkingBuffer = '';\n finalContent = '';\n answerBuffer = '';\n thinkDisplayed = 0;\n typeTimer = null;\n streamDone = false;\n streamFinalized = false;\n\n finalizeStreamMessage = function finalizeStreamMessage() {\n if (streamFinalized) return;\n var msg = _this14.messages[msgIdx];\n\n if (!msg) {\n streamFinalized = true;\n if (typeTimer) clearInterval(typeTimer);\n typeTimer = null;\n return;\n }\n\n streamFinalized = true;\n if (typeTimer) clearInterval(typeTimer);\n typeTimer = null;\n _this14._typeTimer = null;\n var key = msg._key;\n\n if (key) {\n _this14.$delete(_this14.contentCache, key);\n\n _this14.$delete(_this14.contentCache, key + '_t');\n }\n\n if (msg._tokuiStream) {\n try {\n if (msg._mdTagOpen) {\n msg._tokuiStream.feed('[/md]\\n');\n\n msg._mdTagOpen = false;\n }\n\n if (msg._dslBuffer) {\n msg._tokuiStream.feed(_this14._applyApplicantNickName(msg._dslBuffer) + '\\n');\n\n msg._dslBuffer = '';\n }\n\n msg._tokuiStream.endStream();\n } catch (e) {\n console.error('[TokUI] endStream error:', e);\n }\n\n _this14._reconcileAfterStream(msg, msg._tokuiDiv);\n\n if (msg._tokuiDiv) msg._tokuiDiv._tokuiRendered = true;\n msg._tokuiStream = null;\n\n _this14.$set(msg, '_tokuiRendered', true);\n }\n\n msg.content = finalContent || answerBuffer || thinkingBuffer || msg.content || '❌ 连接中断';\n msg.thinking = thinkingBuffer;\n msg.loading = false;\n _this14.sending = false;\n\n _this14.clearWaitTimer();\n\n _this14.scrollToBottom();\n\n _this14.$emit('session-refresh');\n\n _this14.$emit('message-sent');\n };\n\n typeTimer = setInterval(function () {\n if (!_this14.messages[msgIdx]) {\n finalizeStreamMessage();\n return;\n } // Phase 1: 逐字展示思考内容\n\n\n if (thinkDisplayed < thinkingBuffer.length) {\n thinkDisplayed += 50;\n if (thinkDisplayed > thinkingBuffer.length) thinkDisplayed = thinkingBuffer.length;\n _this14.messages[msgIdx].thinking = thinkingBuffer.substring(0, thinkDisplayed);\n\n _this14.scrollToBottom();\n } // Phase 2: 渲染答案内容\n\n\n var msg = _this14.messages[msgIdx]; // TokUI 活跃期间始终用 answerBuffer(避免 finalContent 带 ```tokui 围栏导致内容跳变)\n\n var target = msg._tokuiStream ? answerBuffer : finalContent || answerBuffer;\n var prevLen = msg._tokuiPrevLen || 0;\n\n if (target.length > prevLen) {\n msg._tokuiPrevLen = target.length; // 检测内容是否包含 TokUI 标记(```tokui 围栏 或 纯 DSL 标签开头)\n\n var hasTokuiMarker = msg._tokuiStream || _this14._isPureTokuiDsl(target) || target.indexOf('```tokui') !== -1;\n\n if (hasTokuiMarker && !msg._hasTokuiFailed) {\n // ── TokUI 路径 ──\n if (!msg._tokuiStream) {\n var container = _this14.$refs.messagesRef;\n\n if (container) {\n var rows = container.querySelectorAll('.msg-row.assistant');\n var lastRow = rows[rows.length - 1];\n var tokuiDiv = lastRow === null || lastRow === void 0 ? void 0 : lastRow.querySelector('.tokui-stream');\n var TokUIConstructor = resolveTokUIConstructor();\n\n if (tokuiDiv && TokUIConstructor) {\n try {\n ensureTokuiValidatorPatched();\n setTokuiRuntimeProperty(msg, '_tokuiDiv', tokuiDiv);\n setTokuiRuntimeProperty(msg, '_tokuiStream', new TokUIConstructor({\n container: tokuiDiv\n })); // 绑定校验引擎(data-rule / data-trigger / data-load / formSubmit)\n\n setTokuiRuntimeProperty(msg, '_tokuiValidator', (0, _tokuiValidator.setupValidator)(msg._tokuiStream, new _tokuiValidator.Validator(), _this14._buildTokuiBridgeOptions({\n container: tokuiDiv,\n scopeKey: msg.msgKey || msg._key,\n onFormSuccess: function onFormSuccess(data) {\n return _this14._handleTokuiFormSuccess(msg, data);\n }\n })));\n\n msg._tokuiStream.startStream();\n\n tokuiDiv.classList.add('tokui-streaming');\n msg._tokuiPureDsl = _this14._isPureTokuiDsl(target);\n msg._mdTagOpen = false;\n msg._prevMdLen = 0;\n msg._prevDslLen = 0;\n msg._dslBuffer = '';\n\n _this14.$set(msg, '_hasTokui', true);\n } catch (e) {\n console.error('[TokUI] init error:', e);\n msg._hasTokuiFailed = true;\n msg._tokuiStream = null;\n }\n } else {\n msg._hasTokuiFailed = true;\n }\n }\n }\n\n if (msg._tokuiStream) {\n try {\n _this14._streamTokui(msg, target);\n } catch (e) {\n console.error('[TokUI] feed error:', e);\n msg._hasTokuiFailed = true;\n msg._tokuiStream = null;\n msg.content = target;\n }\n } else if (msg._hasTokuiFailed) {\n // TokUI 初始化失败,回退到普通渲染\n msg.content = target;\n }\n } else {\n // ── 普通渲染路径(保持原有逻辑不变)──\n msg.content = target;\n }\n\n _this14.scrollToBottom();\n\n return;\n } // Phase 3: 流结束 → 收尾\n\n\n if (streamDone) {\n finalizeStreamMessage();\n }\n }, 30);\n _this14._typeTimer = typeTimer;\n url = (0, _apichat.createStreamUrl)(_this14.activeSessionId, text);\n\n if (_this14.eventSource) {\n _this14.eventSource.close();\n }\n\n _this14.eventSource = new EventSource(url);\n es = _this14.eventSource;\n es.addEventListener('reasoning', function (e) {\n if (e.data) {\n var chunk = e.data;\n var cleanChunk = chunk.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2').replace(/([a-zA-Z])([\\u4e00-\\u9fff])/g, '$1 $2').replace(/([\\u4e00-\\u9fff])([a-zA-Z])/g, '$1 $2').replace(/([a-zA-Z])(\\d)/g, '$1 $2').replace(/(\\d)([a-zA-Z])/g, '$1 $2').replace(/\\s+([:,.!?])/g, '$1').replace(/([:,.!?])\\s+/g, '$1 ');\n cleanChunk = cleanChunk.replace(/\\s+/g, ' ');\n var lastChar = thinkingBuffer.slice(-1);\n var firstChar = cleanChunk.slice(0, 1);\n\n var isLetter = function isLetter(c) {\n return /[a-zA-Z]/.test(c);\n };\n\n if (isLetter(lastChar) && isLetter(firstChar)) {\n thinkingBuffer += ' ' + cleanChunk;\n } else {\n thinkingBuffer += cleanChunk;\n }\n }\n });\n es.addEventListener('reasoning_result', function (e) {\n if (e.data) {\n thinkingBuffer = e.data;\n }\n });\n es.addEventListener('answer', function (e) {\n if (e.data) {\n answerBuffer += e.data.replace(/([a-zA-Z]+)([\\u4e00-\\u9fff])/g, '$1 $2').replace(/([\\u4e00-\\u9fff])([a-zA-Z]+)/g, '$1 $2').replace(/([a-z])([A-Z])/g, '$1 $2').replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2').replace(/([a-zA-Z])(\\d)/g, '$1 $2').replace(/(\\d)([a-zA-Z])/g, '$1 $2');\n }\n });\n es.addEventListener('tool_result', function (e) {\n if (e.data && _this14.messages[msgIdx]) {\n if (!_this14.messages[msgIdx].toolCalls) _this14.messages[msgIdx].toolCalls = [];\n\n _this14.messages[msgIdx].toolCalls.push(e.data);\n\n _this14.scrollToBottom();\n }\n });\n es.addEventListener('msgKey', function (e) {\n if (e.data && _this14.messages[msgIdx]) {\n _this14.$set(_this14.messages[msgIdx], 'msgKey', e.data);\n }\n });\n es.addEventListener('agent_result', function (e) {\n if (_this14.eventSource !== es && _this14.eventSource !== null) return;\n finalContent = e.data || '';\n streamDone = true;\n es.close();\n _this14.eventSource = null;\n finalizeStreamMessage();\n });\n\n es.onerror = function (err) {\n if (_this14.eventSource !== es && _this14.eventSource !== null) return;\n console.log('[SSE] onerror', err);\n if (thinkingBuffer.length > 0 && !streamDone) finalContent = thinkingBuffer;\n if (!streamDone) finalContent = '❌ 连接中断';\n streamDone = true;\n es.close();\n _this14.eventSource = null;\n finalizeStreamMessage();\n };\n\n case 35:\n case \"end\":\n return _context4.stop();\n }\n }\n }, _callee4);\n }))();\n },\n stopAgent: function stopAgent() {\n // 1) 停止打字机定时器,避免继续把半成品 DSL feed 进 TokUI 导致渲染报错\n if (this._typeTimer) {\n clearInterval(this._typeTimer);\n this._typeTimer = null;\n }\n\n if (this.eventSource) {\n this.eventSource.close();\n this.eventSource = null;\n }\n\n if (this.activeSessionId) {\n (0, _apichat.stopStream)(this.activeSessionId).catch(function () {});\n } // 2) 结束正在生成的消息:若为 TokUI 表单流,先 endStream 冻结已渲染部分,不再继续生成\n\n\n if (this.messages.length > 0) {\n var last = this.messages[this.messages.length - 1];\n\n if (last && last.loading) {\n if (last._tokuiStream) {\n try {\n if (last._mdTagOpen) {\n last._tokuiStream.feed('[/md]\\n');\n\n last._mdTagOpen = false;\n }\n\n last._tokuiStream.endStream();\n } catch (e) {\n console.error('[TokUI] stop endStream error:', e);\n } // 停止该表单的审批刷新/重试,避免暂停后仍输出 [Approval] 日志\n\n\n if (last._tokuiDiv && window.__tokuiValidatorBridge && window.__tokuiValidatorBridge.stopApprovalFlows) {\n try {\n window.__tokuiValidatorBridge.stopApprovalFlows(last._tokuiDiv);\n } catch (e2) {\n console.error('[Approval] stopApprovalFlows error:', e2);\n }\n }\n\n last._tokuiStream = null;\n }\n\n last.content = last.content || last.thinking || '⏹ 已停止';\n last.loading = false;\n this.$delete(this.contentCache, last._key);\n }\n }\n\n this.sending = false;\n this.clearWaitTimer();\n },\n scrollToTop: function scrollToTop() {\n var el = this.$refs.messagesRef;\n if (el) el.scrollTop = 0;\n },\n scrollToBottom: function scrollToBottom() {\n var _this15 = this;\n\n var force = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n if (this.showDownBtn && !force) return;\n this.$nextTick(function () {\n var el = _this15.$refs.messagesRef;\n\n if (el) {\n el.scrollTop = el.scrollHeight;\n _this15.lastScrollTop = el.scrollTop;\n _this15.showDownBtn = false;\n _this15._userScrolledUp = false;\n }\n });\n },\n scrollToBottomOnce: function scrollToBottomOnce() {\n var _this16 = this;\n\n clearTimeout(this._scrollTimer);\n this.$nextTick(function () {\n var tryScroll = function tryScroll() {\n var el = _this16.$refs.messagesRef;\n\n if (!el) {\n _this16._scrollTimer = setTimeout(tryScroll, 50);\n return;\n }\n\n var originalScrollBehavior = el.style.scrollBehavior;\n el.style.scrollBehavior = 'auto';\n el.scrollTop = el.scrollHeight - el.clientHeight;\n el.style.scrollBehavior = originalScrollBehavior;\n _this16.showDownBtn = false;\n _this16._userScrolledUp = false;\n };\n\n tryScroll();\n });\n },\n copyText: function copyText(content) {\n var _this17 = this;\n\n if (!content) return;\n var text = content.replace(/<[^>]+>/g, ''); // 优先使用 Clipboard API(HTTPS 环境)\n\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(text).then(function () {\n _this17.$message({\n message: '已复制到剪贴板',\n type: 'success',\n duration: 1500\n });\n }).catch(function () {\n _this17._fallbackCopyText(text);\n });\n } else {\n this._fallbackCopyText(text);\n }\n },\n _fallbackCopyText: function _fallbackCopyText(text) {\n try {\n var ta = document.createElement('textarea');\n ta.value = text;\n ta.style.cssText = 'position:fixed;left:-9999px;top:0;opacity:0';\n document.body.appendChild(ta);\n ta.focus();\n ta.select();\n document.execCommand('copy');\n document.body.removeChild(ta);\n this.$message({\n message: '已复制到剪贴板',\n type: 'success',\n duration: 1500\n });\n } catch (_unused3) {\n this.$message({\n message: '复制失败',\n type: 'error',\n duration: 2000\n });\n }\n },\n copyAsImage: function copyAsImage(content) {\n var _this18 = this;\n\n if (!content) return;\n var div = document.createElement('div');\n div.className = 'copy-image-render';\n div.style.cssText = 'position:fixed;left:-9999px;top:0;width:600px;padding:20px;background:#fff;font-size:14px;line-height:1.6;color:#333;border-radius:8px;font-family:-apple-system,BlinkMacSystemFont,sans-serif;'; // 内联表格样式(因 Vue 作用域样式不覆盖复制用的脱离文档流 div)\n\n div.innerHTML = this.renderMarkdown(content) + '<style>table{border-collapse:collapse;width:100%;margin:6px 0;font-size:14px}' + 'th,td{border:1px solid #e8e8e8;padding:5px 8px;text-align:left}' + 'th{background:#fafafa;font-weight:600}</style>';\n document.body.appendChild(div);\n this.$message({\n message: '正在生成图片...',\n type: 'info',\n duration: 2000\n });\n (0, _html2canvas.default)(div, {\n scale: 2,\n useCORS: true,\n backgroundColor: '#ffffff'\n }).then(function (canvas) {\n var base64 = canvas.toDataURL('image/png'); // 移动端:调用原生方法保存到本地相册\n\n if (_this18.isMobile) {\n if (_this18.$hybrid && typeof _this18.$hybrid.savePic2Library === 'function') {\n _this18.$hybrid.savePic2Library(base64, function () {\n _this18.$message({\n message: '图片已保存到本地相册',\n type: 'success',\n duration: 1500\n });\n });\n } else {\n // fallback:展示图片让用户长按保存\n _this18.previewImage = base64;\n\n _this18.$message({\n message: '长按图片可保存到本地',\n type: 'info',\n duration: 3000\n });\n }\n\n return;\n } // PC端:复制到剪贴板\n\n\n var canCopyImage = typeof ClipboardItem !== 'undefined' && navigator.clipboard && typeof navigator.clipboard.write === 'function';\n\n if (!canCopyImage) {\n _this18.$message({\n message: '当前浏览器不支持复制图片',\n type: 'warning',\n duration: 3000\n });\n\n return;\n }\n\n canvas.toBlob(function (blob) {\n if (!blob) {\n _this18.$message({\n message: '生成图片失败',\n type: 'error',\n duration: 2000\n });\n\n return;\n }\n\n navigator.clipboard.write([new ClipboardItem({\n 'image/png': blob\n })]).then(function () {\n _this18.$message({\n message: '图片已复制到剪贴板',\n type: 'success',\n duration: 1500\n });\n }).catch(function () {\n _this18.$message({\n message: '复制失败,请重试',\n type: 'error',\n duration: 2000\n });\n });\n }, 'image/png');\n }).catch(function () {\n _this18.$message({\n message: '生成图片失败',\n type: 'error',\n duration: 2000\n });\n }).finally(function () {\n document.body.removeChild(div);\n });\n }\n }\n};\nexports.default = _default2;\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/components/chatMain/ChatDetail.vue?./node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
919
|
+
eval("\n\nvar _interopRequireDefault = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/interopRequireDefault */ \"./node_modules/@babel/runtime/helpers/interopRequireDefault.js\").default;\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _toConsumableArray2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/toConsumableArray */ \"./node_modules/@babel/runtime/helpers/toConsumableArray.js\"));\n\nvar _objectSpread2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/objectSpread2 */ \"./node_modules/@babel/runtime/helpers/objectSpread2.js\"));\n\nvar _createForOfIteratorHelper2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/createForOfIteratorHelper */ \"./node_modules/@babel/runtime/helpers/createForOfIteratorHelper.js\"));\n\nvar _asyncToGenerator2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/asyncToGenerator */ \"./node_modules/@babel/runtime/helpers/asyncToGenerator.js\"));\n\nvar _markdownIt = _interopRequireDefault(__webpack_require__(/*! markdown-it */ \"./node_modules/markdown-it/index.js\"));\n\n__webpack_require__(/*! @jboltai/tokui/dist/tokui.css */ \"./node_modules/@jboltai/tokui/dist/tokui.css\");\n\nvar _apichat = __webpack_require__(/*! @lib/api/apichat */ \"./src/lib/api/apichat.js\");\n\nvar _index = __webpack_require__(/*! @lib/socket/fileManager/obs/index */ \"./src/lib/socket/fileManager/obs/index.js\");\n\nvar _index2 = _interopRequireDefault(__webpack_require__(/*! @lib/socket/utils/index */ \"./src/lib/socket/utils/index.js\"));\n\nvar _cookie = __webpack_require__(/*! @lib/utils/cookie.js */ \"./src/lib/utils/cookie.js\");\n\nvar _request = __webpack_require__(/*! @lib/utils/request */ \"./src/lib/utils/request.js\");\n\nvar _apiObs = _interopRequireDefault(__webpack_require__(/*! @lib/api/apiObs */ \"./src/lib/api/apiObs.js\"));\n\nvar _html2canvas = _interopRequireDefault(__webpack_require__(/*! html2canvas */ \"./node_modules/html2canvas/dist/html2canvas.esm.js\"));\n\nvar _tokuiValidator = __webpack_require__(/*! @lib/tokui-validator */ \"./src/lib/tokui-validator/index.js\");\n\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\nvar resolveTokUIConstructor = function resolveTokUIConstructor() {\n if (typeof window !== 'undefined') {\n var _window$globalThis;\n\n var runtime = window.TokUI || ((_window$globalThis = window.globalThis) === null || _window$globalThis === void 0 ? void 0 : _window$globalThis.TokUI); // 仅当全局 TokUI 具备 _internal 内部结构时才优先使用(校验补丁与事件总线依赖它),\n // 否则回落到 lib 内置打包的 @jboltai/tokui,保证补丁与实例同源\n\n if (runtime && runtime._internal) {\n var _runtime$default;\n\n return runtime.TokUI || ((_runtime$default = runtime.default) === null || _runtime$default === void 0 ? void 0 : _runtime$default.TokUI) || runtime.default || runtime;\n }\n }\n\n try {\n if (true) {\n var _runtime = __webpack_require__(/*! @jboltai/tokui/dist/tokui.umd.js */ \"./node_modules/@jboltai/tokui/dist/tokui.umd.js\");\n\n if (_runtime) {\n var _runtime$default2;\n\n return _runtime.TokUI || ((_runtime$default2 = _runtime.default) === null || _runtime$default2 === void 0 ? void 0 : _runtime$default2.TokUI) || _runtime.default || _runtime;\n }\n }\n } catch (e) {// ignore and continue to package entry fallback\n }\n\n try {\n if (true) {\n var _runtime2 = __webpack_require__(/*! @jboltai/tokui */ \"./node_modules/@jboltai/tokui/dist/tokui.mjs\");\n\n if (_runtime2) {\n var _runtime2$default;\n\n return _runtime2.TokUI || ((_runtime2$default = _runtime2.default) === null || _runtime2$default === void 0 ? void 0 : _runtime2$default.TokUI) || _runtime2.default || _runtime2;\n }\n }\n } catch (e) {// ignore and leave unresolved\n }\n\n return null;\n}; // TokUI 校验补丁:全局只打一次(依赖 window.TokUI._internal,须在 TokUI 模块加载完成后调用)\n\n\nvar _tokuiValidatorPatched = false;\n\nvar ensureTokuiValidatorPatched = function ensureTokuiValidatorPatched() {\n if (_tokuiValidatorPatched) return;\n if (typeof window === 'undefined' || !window.TokUI || !window.TokUI._internal) return;\n (0, _tokuiValidator.patchTokUI)();\n _tokuiValidatorPatched = true;\n}; // TokUI 实例及其 bridge/DOM 引用可能包含循环结构,避免被 messages 的 deep watcher 遍历。\n\n\nvar setTokuiRuntimeProperty = function setTokuiRuntimeProperty(message, key, value) {\n Object.defineProperty(message, key, {\n configurable: true,\n enumerable: false,\n writable: true,\n value: value\n });\n};\n\nvar md = new _markdownIt.default({\n breaks: true,\n html: false,\n linkify: true,\n typographer: true\n});\nmd.enable('table');\nvar _default2 = {\n name: 'ChatDetail',\n props: {\n isMobile: {\n type: Boolean,\n default: false\n },\n activeSessionId: {\n type: String,\n default: ''\n },\n currentAgent: {\n type: String,\n default: ''\n },\n agentInfo: {\n type: Object,\n default: function _default() {\n return {};\n }\n },\n currentAgentDesc: {\n type: String,\n default: ''\n },\n showSidebar: {\n type: Boolean,\n default: false\n },\n showH5List: {\n type: Boolean,\n default: false\n },\n showAgentSelect: {\n type: Boolean,\n default: false\n },\n agentList: {\n type: Array,\n default: function _default() {\n return [];\n }\n },\n nickName: {\n type: String,\n default: ''\n }\n },\n data: function data() {\n return {\n messages: [],\n inputText: '',\n sending: false,\n waitTimer: null,\n waitSeconds: 0,\n canceling: false,\n msgPage: 1,\n msgMaxPage: 1,\n msgTotal: 0,\n eventSource: null,\n showToolInfo: false,\n showDownBtn: false,\n lastScrollTop: 0,\n scrollTimer: null,\n contentCache: {},\n loadingSessionMsg: false,\n loadingMore: false,\n greeting: '',\n _scrollTimer: null,\n _userScrolledUp: false,\n voiceMode: false,\n isRecording: false,\n recordDuration: 0,\n recordingTimer: null,\n audioContext: null,\n audioStream: null,\n audioProcessor: null,\n audioChunks: [],\n audioSampleRate: 16000,\n isCancelSwipe: false,\n recordStartY: 0,\n isAndroid: false,\n _pendingSendText: null\n };\n },\n computed: {\n waitClass: function waitClass() {\n if (this.waitSeconds > 15) return 'wait-15';\n if (this.waitSeconds > 10) return 'wait-10';\n if (this.waitSeconds > 5) return 'wait-5';\n return '';\n },\n currentAgentName: function currentAgentName() {\n var _this = this;\n\n if (!this.currentAgent) return '';\n var a = this.agentList.find(function (x) {\n return x.agentCode === _this.currentAgent;\n });\n return a ? a.agentName || a.agentCode : this.currentAgent;\n }\n },\n watch: {\n contentCache: {\n handler: function handler(val) {// 保持响应性\n },\n immediate: true\n },\n activeSessionId: function activeSessionId(val) {\n var _this2 = this;\n\n if (val && this._pendingSendText) {\n var text = this._pendingSendText;\n this._pendingSendText = null;\n this.$nextTick(function () {\n return _this2._sendMessage(text);\n });\n }\n },\n messages: {\n handler: function handler() {\n var _this3 = this;\n\n this.$nextTick(function () {\n return _this3._renderTokuiMessages();\n });\n },\n deep: true\n }\n },\n mounted: function mounted() {\n var _this4 = this;\n\n if (window.visualViewport) {\n window.visualViewport.addEventListener('resize', this.onViewportResize, {\n passive: true\n });\n }\n\n if (typeof navigator !== 'undefined') {\n this.isAndroid = /android/i.test(navigator.userAgent);\n }\n\n this.$nextTick(function () {\n _this4._renderTokuiMessages();\n });\n },\n beforeDestroy: function beforeDestroy() {\n if (window.visualViewport) {\n window.visualViewport.removeEventListener('resize', this.onViewportResize);\n }\n\n this.releaseAudio();\n this.contentCache = {};\n clearTimeout(this._scrollTimer);\n\n if (this.eventSource) {\n this.eventSource.onerror = null;\n this.eventSource.close();\n this.eventSource = null;\n }\n\n if (this.waitTimer) {\n clearInterval(this.waitTimer);\n this.waitTimer = null;\n }\n\n if (this._typeTimer) {\n clearInterval(this._typeTimer);\n this._typeTimer = null;\n }\n\n this.messages.forEach(function (msg) {\n if (msg && msg._tokuiValidator && typeof msg._tokuiValidator.destroy === 'function') {\n msg._tokuiValidator.destroy();\n\n msg._tokuiValidator = null;\n }\n });\n },\n methods: {\n // 构建传入 setupValidator 的公共选项(含补卡日历所需的 user / api 信息)\n _buildTokuiBridgeOptions: function _buildTokuiBridgeOptions(extra) {\n var auth = (0, _request.getAuth)();\n var opts = Object.assign({\n user: {\n no: auth.userno || auth.user,\n cno: auth.companyNo\n },\n api: {\n patchQueryMonthClockStatus: '/api/v1/open/attendance/patchQueryMonthClockStatus'\n }\n }, extra || {});\n return opts;\n },\n getInitial: function getInitial(name) {\n if (!name) return '?';\n return name.charAt(0);\n },\n formatTime: function formatTime(t) {\n if (!t) return '';\n var d = new Date(t.replace(' ', 'T'));\n var now = new Date();\n var diff = (now - d) / 1000;\n if (diff < 60) return '刚刚';\n if (diff < 3600) return Math.floor(diff / 60) + '分钟前';\n if (diff < 86400) return Math.floor(diff / 3600) + '小时前';\n return d.getMonth() + 1 + '/' + d.getDate();\n },\n escapeHtml: function escapeHtml(text) {\n if (!text) return '';\n return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n },\n renderMarkdown: function renderMarkdown(text) {\n if (!text) return '';\n\n try {\n return md.render(text);\n } catch (err) {\n return this.escapeHtml(text);\n }\n },\n renderStreaming: function renderStreaming(text, msgKey) {\n if (!text) return '';\n var cache = this.contentCache[msgKey];\n var lines = text.split('\\n');\n var lastLineIdx = lines.length - 1;\n\n if (!cache) {\n try {\n var html = md.render(text);\n this.$set(this.contentCache, msgKey, {\n renderedLines: lastLineIdx,\n html: html,\n fullText: text\n });\n return html;\n } catch (_unused) {\n return this.escapeHtml(text);\n }\n }\n\n if (text === cache.fullText) return cache.html;\n cache.fullText = text; // 行数没变,仅末尾追加字符,直接拼接尾行\n\n if (lastLineIdx === cache.renderedLines) {\n return cache.html + this.escapeHtml(lines[lastLineIdx]);\n } // 场景1:行数变少/文本回退截断,全量重渲染\n\n\n if (lastLineIdx < cache.renderedLines) {\n try {\n var fullLines = lines.slice(0, lastLineIdx);\n var newBaseHtml = '';\n\n if (fullLines.length > 0) {\n newBaseHtml = md.render(fullLines.join('\\n'));\n }\n\n cache.html = newBaseHtml;\n cache.renderedLines = lastLineIdx;\n\n var _lastLine = this.escapeHtml(lines[lastLineIdx]);\n\n return cache.html + (_lastLine || '');\n } catch (_unused2) {\n return this.escapeHtml(text);\n }\n } // 场景2:新增完整行\n\n\n if (cache.renderedLines < lastLineIdx) {\n var allFullLines = lines.slice(0, lastLineIdx);\n var _newBaseHtml = '';\n\n if (allFullLines.length > 0) {\n _newBaseHtml = md.render(allFullLines.join('\\n'));\n }\n\n cache.html = _newBaseHtml;\n cache.renderedLines = lastLineIdx;\n }\n\n var lastLine = this.escapeHtml(lines[lastLineIdx]);\n cache.renderedLines = lastLineIdx;\n return cache.html + (lastLine || '');\n },\n getChatAvatarStyle: function getChatAvatarStyle(agent, avatar) {\n if (avatar) {\n return {\n backgroundImage: \"url(\".concat(avatar, \")\"),\n backgroundSize: 'cover',\n backgroundPosition: 'center',\n backgroundRepeat: 'no-repeat'\n };\n }\n\n var _agent = this.agentList.find(function (x) {\n return x.agentCode === agent;\n });\n\n if (_agent && _agent.avatar) {\n return {\n backgroundImage: \"url(\".concat(_agent.avatar, \")\"),\n backgroundSize: 'cover',\n backgroundPosition: 'center',\n backgroundRepeat: 'no-repeat'\n };\n } // 没有网络图片时,根据智能体在 agentList 中的索引位置决定颜色\n\n\n var index = this.agentList.findIndex(function (item) {\n return item.agentCode === agent;\n });\n\n if (index !== -1) {\n var colorIndex = index % 4 + 1;\n return {\n background: this.getGradientByColorIndex(colorIndex)\n };\n }\n\n return {\n background: 'linear-gradient(135deg, #1677ff, #4096ff)'\n };\n },\n getGradientByColorIndex: function getGradientByColorIndex(index) {\n var gradients = {\n 1: 'linear-gradient(135deg, #1677ff, #4096ff)',\n 2: 'linear-gradient(135deg, #52c41a, #73d13d)',\n 3: 'linear-gradient(135deg, #fa8c16, #ffa940)',\n 4: 'linear-gradient(135deg, #722ed1, #b37feb)'\n };\n return gradients[index] || gradients[1];\n },\n sendQuickMsg: function sendQuickMsg(message) {\n if (message) {\n this.inputText = message;\n this.sendMessage();\n }\n },\n formatMixText: function formatMixText(str) {\n if (!str) return '';\n var res = str.replace(/([a-zA-Z0-9])([\\u4e00-\\u9fff])/g, '$1 $2').replace(/([\\u4e00-\\u9fff])([a-zA-Z0-9])/g, '$1 $2');\n res = res.replace(/([a-z])([A-Z])/g, '$1 $2');\n return res;\n },\n // 检测是否滚动到底部,控制向下箭头显示隐藏\n checkScrollBottom: function checkScrollBottom() {\n var forceShowWhenNotBottom = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n var el = this.$refs.messagesRef;\n\n if (!el) {\n this.showDownBtn = false;\n return;\n }\n\n var scrollHeight = el.scrollHeight,\n scrollTop = el.scrollTop,\n clientHeight = el.clientHeight;\n var remain = scrollHeight - scrollTop - clientHeight;\n var atBottom = remain <= 80; // 检测用户是否主动向上滚动(远离底部)\n\n if (remain > 80 && scrollTop < this.lastScrollTop) {\n this._userScrolledUp = true;\n }\n\n if (atBottom) {\n this._userScrolledUp = false;\n this.showDownBtn = false;\n } else if (this._userScrolledUp) {\n // 用户已主动上滚,保持显示\"到底部\"按钮,不自动回滚\n this.showDownBtn = true;\n } else if (this.sending && scrollTop >= this.lastScrollTop && !forceShowWhenNotBottom) {\n this.showDownBtn = false;\n } else {\n this.showDownBtn = true;\n }\n\n this.lastScrollTop = scrollTop;\n this.$emit('scroll-bottom-check', {\n showDownBtn: this.showDownBtn,\n lastScrollTop: this.lastScrollTop\n });\n },\n // H5 输入框获取焦点 - 滚动到底部让键盘不遮挡\n onInputFocus: function onInputFocus() {\n if (!this.isMobile) return;\n setTimeout(function () {\n window.scrollTo(0, document.body.scrollHeight);\n }, 300);\n },\n // visualViewport 变化时(如键盘弹出)调整滚动位置\n onViewportResize: function onViewportResize() {\n if (!this.isMobile) return;\n var activeEl = document.activeElement;\n\n if (activeEl && (activeEl.tagName === 'TEXTAREA' || activeEl.tagName === 'INPUT')) {\n window.scrollTo(0, document.body.scrollHeight);\n }\n },\n autoResizeTextarea: function autoResizeTextarea() {\n var _this5 = this;\n\n var ta = this.$refs.taRef;\n if (!ta) return;\n var isMobile = this.isMobile;\n var baseH = isMobile ? 36 : 100;\n var maxH = isMobile ? 100 : 180;\n ta.style.height = \"\".concat(baseH, \"px\");\n var realH = ta.scrollHeight;\n\n if (realH > baseH) {\n ta.style.height = \"\".concat(Math.min(realH, maxH), \"px\");\n }\n\n this.$nextTick(function () {\n _this5.checkScrollBottom(true);\n });\n },\n // Ctrl+Enter换行\n newLine: function newLine(e) {\n var _this6 = this;\n\n e.preventDefault();\n var ta = this.$refs.taRef;\n var start = ta.selectionStart;\n var end = ta.selectionEnd;\n this.inputText = this.inputText.slice(0, start) + '\\n' + this.inputText.slice(end);\n this.$nextTick(function () {\n ta.selectionStart = ta.selectionEnd = start + 1;\n\n _this6.autoResizeTextarea();\n });\n },\n toggleVoiceMode: function toggleVoiceMode() {\n var _this7 = this;\n\n if (this.voiceMode) {\n this.voiceMode = false;\n this.releaseAudio();\n return;\n } // 进入语音模式前提前请求麦克风权限,避免触摸时异步弹窗导致 touchend 丢失\n\n\n this.ensureAudioReady().then(function (ready) {\n if (ready) {\n _this7.voiceMode = true;\n } else {\n _this7.voiceMode = false;\n }\n });\n },\n // 确保音频设备已就绪\n ensureAudioReady: function ensureAudioReady() {\n var _this8 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {\n var stream, audioContext, source, processor;\n return regeneratorRuntime.wrap(function _callee$(_context) {\n while (1) {\n switch (_context.prev = _context.next) {\n case 0:\n if (!(_this8.audioContext && _this8.audioStream)) {\n _context.next = 2;\n break;\n }\n\n return _context.abrupt(\"return\", true);\n\n case 2:\n if (!(!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia)) {\n _context.next = 5;\n break;\n }\n\n alert('当前环境不支持麦克风访问');\n return _context.abrupt(\"return\", false);\n\n case 5:\n _context.prev = 5;\n _context.next = 8;\n return navigator.mediaDevices.getUserMedia({\n audio: true\n });\n\n case 8:\n stream = _context.sent;\n _this8.audioStream = stream;\n audioContext = new (window.AudioContext || window.webkitAudioContext)(); // iOS 上 AudioContext 默认 suspended,需要手动 resume\n\n if (!(audioContext.state === 'suspended')) {\n _context.next = 14;\n break;\n }\n\n _context.next = 14;\n return audioContext.resume();\n\n case 14:\n _this8.audioContext = audioContext; // 采集实际采样率(iOS 会忽略 sampleRate 参数,使用硬件采样率)\n\n _this8.audioSampleRate = audioContext.sampleRate || 16000;\n source = audioContext.createMediaStreamSource(stream); // 保存 source 引用到实例,防止 iOS Safari 垃圾回收导致音频处理中断\n\n _this8.audioSource = source;\n processor = audioContext.createScriptProcessor(4096, 1, 1);\n _this8.audioProcessor = processor; // iOS 上 onaudioprocess 可能先于 isRecording=true 触发\n // 因此不依赖 isRecording 过滤,由外部控制 audioChunks 的清零时机\n\n processor.onaudioprocess = function (e) {\n var inputData = e.inputBuffer.getChannelData(0);\n\n _this8.audioChunks.push(new Float32Array(inputData));\n };\n\n source.connect(processor);\n processor.connect(audioContext.destination);\n return _context.abrupt(\"return\", true);\n\n case 26:\n _context.prev = 26;\n _context.t0 = _context[\"catch\"](5);\n console.error('麦克风访问失败:', _context.t0);\n alert('麦克风权限被拒绝');\n return _context.abrupt(\"return\", false);\n\n case 31:\n case \"end\":\n return _context.stop();\n }\n }\n }, _callee, null, [[5, 26]]);\n }))();\n },\n // H5语音录制 - 触摸开始\n onRecordTouchStart: function onRecordTouchStart(e) {\n var _this9 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() {\n var ready;\n return regeneratorRuntime.wrap(function _callee2$(_context2) {\n while (1) {\n switch (_context2.prev = _context2.next) {\n case 0:\n if (!_this9.sending) {\n _context2.next = 2;\n break;\n }\n\n return _context2.abrupt(\"return\");\n\n case 2:\n _context2.next = 4;\n return _this9.ensureAudioReady();\n\n case 4:\n ready = _context2.sent;\n\n if (ready) {\n _context2.next = 7;\n break;\n }\n\n return _context2.abrupt(\"return\");\n\n case 7:\n _this9.isCancelSwipe = false;\n _this9.recordStartY = e.touches[0].clientY;\n _this9.recordDuration = 0;\n _this9.audioChunks = [];\n _this9.isRecording = true;\n _this9.recordingTimer = setInterval(function () {\n _this9.recordDuration++;\n\n if (_this9.recordDuration >= 60) {\n _this9.onRecordTouchEnd();\n }\n }, 1000);\n\n case 13:\n case \"end\":\n return _context2.stop();\n }\n }\n }, _callee2);\n }))();\n },\n // H5语音录制 - 触摸移动\n onRecordTouchMove: function onRecordTouchMove(e) {\n if (!this.isRecording) return;\n var touch = e.touches[0];\n var moveY = touch.clientY;\n var cancelThreshold = 60;\n this.isCancelSwipe = this.recordStartY - moveY > cancelThreshold;\n },\n // H5语音录制 - 触摸结束\n onRecordTouchEnd: function onRecordTouchEnd() {\n if (!this.isRecording) return;\n this.isRecording = false;\n\n if (this.recordingTimer) {\n clearInterval(this.recordingTimer);\n this.recordingTimer = null;\n }\n\n this.stopAudioCapture(!this.isCancelSwipe);\n },\n // 停止音频采集并输出 WAV\n stopAudioCapture: function stopAudioCapture(shouldSend) {\n this.isRecording = false;\n\n if (shouldSend && this.audioChunks.length > 0) {\n var wavBlob = this.encodePCMToWav(this.audioChunks, this.audioSampleRate || 16000);\n this.sendAudioBlob(wavBlob);\n }\n\n this.audioChunks = [];\n },\n // 取消录音\n cancelRecording: function cancelRecording() {\n this.isRecording = false;\n\n if (this.recordingTimer) {\n clearInterval(this.recordingTimer);\n this.recordingTimer = null;\n }\n\n this.recordDuration = 0;\n this.isCancelSwipe = false;\n this.audioChunks = [];\n },\n // 释放音频资源\n releaseAudio: function releaseAudio() {\n this.cancelRecording();\n\n if (this.audioProcessor) {\n this.audioProcessor.disconnect();\n this.audioProcessor = null;\n }\n\n if (this.audioSource) {\n this.audioSource.disconnect();\n this.audioSource = null;\n }\n\n if (this.audioContext) {\n this.audioContext.close().catch(function () {});\n this.audioContext = null;\n }\n\n if (this.audioStream) {\n this.audioStream.getTracks().forEach(function (t) {\n return t.stop();\n });\n this.audioStream = null;\n }\n },\n // 将 Float32 PCM 数据编码为 WAV Blob\n encodePCMToWav: function encodePCMToWav(chunks, sampleRate) {\n var totalLength = 0;\n\n var _iterator = (0, _createForOfIteratorHelper2.default)(chunks),\n _step;\n\n try {\n for (_iterator.s(); !(_step = _iterator.n()).done;) {\n var chunk = _step.value;\n totalLength += chunk.length;\n }\n } catch (err) {\n _iterator.e(err);\n } finally {\n _iterator.f();\n }\n\n var pcmData = new Int16Array(totalLength);\n var offset = 0;\n\n var _iterator2 = (0, _createForOfIteratorHelper2.default)(chunks),\n _step2;\n\n try {\n for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {\n var _chunk = _step2.value;\n\n for (var i = 0; i < _chunk.length; i++) {\n var s = Math.max(-1, Math.min(1, _chunk[i]));\n pcmData[offset++] = s < 0 ? s * 0x8000 : s * 0x7FFF;\n }\n }\n } catch (err) {\n _iterator2.e(err);\n } finally {\n _iterator2.f();\n }\n\n var numChannels = 1;\n var bitsPerSample = 16;\n var byteRate = sampleRate * numChannels * bitsPerSample / 8;\n var blockAlign = numChannels * bitsPerSample / 8;\n var dataSize = pcmData.length * (bitsPerSample / 8);\n var headerSize = 44;\n var totalSize = headerSize + dataSize;\n var buffer = new ArrayBuffer(totalSize);\n var view = new DataView(buffer);\n\n this._wavWriteString(view, 0, 'RIFF');\n\n view.setUint32(4, totalSize - 8, true);\n\n this._wavWriteString(view, 8, 'WAVE');\n\n this._wavWriteString(view, 12, 'fmt ');\n\n view.setUint32(16, 16, true);\n view.setUint16(20, 1, true);\n view.setUint16(22, numChannels, true);\n view.setUint32(24, sampleRate, true);\n view.setUint32(28, byteRate, true);\n view.setUint16(32, blockAlign, true);\n view.setUint16(34, bitsPerSample, true);\n\n this._wavWriteString(view, 36, 'data');\n\n view.setUint32(40, dataSize, true);\n var pcmView = new Int16Array(buffer, headerSize, pcmData.length);\n pcmView.set(pcmData);\n return new Blob([buffer], {\n type: 'audio/wav'\n });\n },\n _wavWriteString: function _wavWriteString(view, offset, str) {\n for (var i = 0; i < str.length; i++) {\n view.setUint8(offset + i, str.charCodeAt(i));\n }\n },\n // 发送音频\n sendAudioBlob: function sendAudioBlob(blob) {\n var appSeqId = _index2.default.getAppSeqId();\n\n var name = appSeqId + '.wav';\n var file = new File([blob], name, {\n type: 'audio/wav'\n });\n this.sendAudioMessage(file);\n },\n sendAudioMessage: function sendAudioMessage(file) {\n var _this10 = this;\n\n var sttKey = 'stt_' + Date.now();\n var sttMsg = {\n role: 'user',\n content: '转化中...',\n _sttStatus: 'pending',\n _sttKey: sttKey,\n createTime: new Date().toISOString(),\n _key: sttKey\n };\n this.messages.push(sttMsg);\n this.scrollToBottom(true);\n (0, _index.fileUploadToObs)({\n _file: file,\n appSeqId: file.name\n }).then(function (resp) {\n if (resp.CommonMsg.Status === 200) {\n return _apiObs.default.aliAudioToText({\n url: resp.obs_upload_data.obsUrl\n }).then(function (result) {\n if (result.code === '200' && result.data) {\n var idx = _this10.messages.findIndex(function (m) {\n return m._sttKey === sttKey;\n });\n\n if (idx !== -1) {\n _this10.messages.splice(idx, 1);\n }\n\n _this10.sendQuickMsg(result.data);\n } else {\n throw new Error(result.msg || '语音转文字失败');\n }\n });\n } else {\n throw new Error(resp.CommonMsg.Message || '文件上传失败');\n }\n }).catch(function (err) {\n console.error('语音转文字失败:', err);\n\n var idx = _this10.messages.findIndex(function (m) {\n return m._sttKey === sttKey;\n });\n\n if (idx !== -1) {\n _this10.$set(_this10.messages, idx, (0, _objectSpread2.default)((0, _objectSpread2.default)({}, _this10.messages[idx]), {}, {\n _sttStatus: 'error',\n content: '语音转文字失败'\n }));\n }\n\n _this10.$message.error('语音转文字失败');\n });\n this.$message.success(\"\\u5F55\\u97F3\\u5B8C\\u6210 (\".concat(this.recordDuration, \"s)\"));\n },\n clearWaitTimer: function clearWaitTimer() {\n if (this.waitTimer) {\n clearInterval(this.waitTimer);\n this.waitTimer = null;\n }\n },\n loadMoreMessages: function loadMoreMessages() {\n var _this11 = this;\n\n var nextPage = this.msgPage + 1;\n var maxPage = Math.ceil(this.msgTotal / 10);\n\n if (nextPage > maxPage) {\n this.loadingMore = false;\n return;\n }\n\n (0, _apichat.getSessionMessages)(this.activeSessionId, nextPage, 10).then(function (res) {\n var list = Array.isArray(res) ? res : res.list || [];\n _this11.msgPage = nextPage;\n var asAsc = (list || []).slice().reverse();\n var prepend = asAsc.map(function (m) {\n return {\n id: m.id,\n role: m.role,\n content: m.content || '',\n thinking: m.thinking || '',\n toolCalls: [],\n agentName: '',\n createTime: m.createTime || null,\n _key: m.id != null ? 'msg_' + m.id : 'msg_' + Date.now() + '_' + Math.random(),\n msgKey: m.msgKey,\n formSubmitted: m.formSubmitted || 0,\n formData: m.formData || null,\n // 历史消息中如果包含 ```tokui 标记或以 TokUI 标签开头,自动启用 TokUI 渲染\n _hasTokui: m.role === 'assistant' && m.content && (/```tokui/.test(m.content) || /^\\s*\\[(card|row|list|table|callout|desc|dv|ft)\\b/.test(m.content.trim()))\n };\n });\n var container = _this11.$refs.messagesRef;\n var oldScrollHeight = container ? container.scrollHeight : 0;\n var oldScrollTop = container ? container.scrollTop : 0;\n _this11.messages = [].concat((0, _toConsumableArray2.default)(prepend), (0, _toConsumableArray2.default)(_this11.messages));\n\n _this11.$nextTick(function () {\n if (container) {\n container.scrollTop = oldScrollTop + (container.scrollHeight - oldScrollHeight);\n }\n\n _this11.loadingMore = false;\n });\n }).catch(function () {\n _this11.loadingMore = false;\n });\n },\n onScrollMessages: function onScrollMessages() {\n var container = this.$refs.messagesRef;\n if (!container || this.loadingMore) return;\n var isAtBottom = container.scrollTop + container.clientHeight >= container.scrollHeight - 100;\n this.checkScrollBottom();\n if (isAtBottom) return;\n\n if (container.scrollTop < 50 && this.msgPage < Math.ceil(this.msgTotal / 10)) {\n this.loadingMore = true;\n this.loadMoreMessages();\n }\n },\n // 检测内容是否为纯 TokUI DSL(无 ```tokui 围栏,直接以 TokUI 标签开头)\n // TokUI 标签名由字母开头,可包含字母、数字、连字符(如 hover-card、think-chain)\n _isPureTokuiDsl: function _isPureTokuiDsl(text) {\n if (!text) return false;\n return /^\\s*\\[[a-zA-Z][a-zA-Z0-9-]*(?=[\\s\\]])/.test(text.trim());\n },\n // 判断最终完整内容是否为 TokUI 表单(```tokui 围栏 或 纯 DSL 标签开头)\n _isTokuiFinalContent: function _isTokuiFinalContent(text) {\n if (!text) return false;\n return /```tokui/.test(text) || this._isPureTokuiDsl(text);\n },\n // 提取 ```tokui 代码块中的 DSL,返回混合内容片段数组\n // 每项: { type: 'md' | 'dsl', content: string }\n _extractTokuiDsl: function _extractTokuiDsl(text) {\n if (!text) return [{\n type: 'md',\n content: ''\n }]; // 纯 TokUI DSL(无 ```tokui 围栏):整个内容作为 DSL 处理\n\n if (this._isPureTokuiDsl(text)) {\n return [{\n type: 'dsl',\n content: text.trim()\n }];\n }\n\n var parts = [];\n var re = /```tokui\\s*([\\s\\S]*?)```/g;\n var lastIndex = 0;\n var match;\n\n while ((match = re.exec(text)) !== null) {\n if (match.index > lastIndex) {\n parts.push({\n type: 'md',\n content: text.substring(lastIndex, match.index)\n });\n }\n\n var dsl = match[1].trim();\n if (dsl) parts.push({\n type: 'dsl',\n content: dsl\n });\n lastIndex = match.index + match[0].length;\n }\n\n if (lastIndex < text.length) {\n parts.push({\n type: 'md',\n content: text.substring(lastIndex)\n });\n }\n\n return parts;\n },\n // 将“申请人”输入框的默认值(v:)替换为当前登录用户昵称\n _applyApplicantNickName: function _applyApplicantNickName(line) {\n if (!line || !this.nickName) return line; // 仅处理申请人字段所在行(id:applierName / l:申请人)\n\n if (!/applierName|申请人/.test(line)) return line;\n var val = String(this.nickName).replace(/\"/g, '');\n return line.replace(/\\bv\\s*:\\s*(?:\"[^\"]*\"|[^\\s\\]]+)/, \"v:\\\"\".concat(val, \"\\\"\"));\n },\n // ── 收尾对齐:实时渲染 endStream 后与历史记录渲染做统一对齐 ──\n // 解决实时渲染增量 feed 导致的初始化遗漏问题:\n // 1. 条件显示:重新扫描所有 data-visible-when / data-visible-when-not 规则\n // 2. 级联 select:重新检测未初始化的 select[data-load],补做 setupCascade\n // 3. 审批流程:重新检测 data-approval-type 表单\n // 4. data-default-label:未匹配的 select 尝试再次匹配\n _reconcileAfterStream: function _reconcileAfterStream(msg, tokuiDiv) {\n if (!tokuiDiv) return; // 延迟执行,确保 endStream 的 DOM 操作全部完成\n\n setTimeout(function () {\n try {\n var scope = tokuiDiv; // ① 条件显示:调用 bridge 的全局函数重新扫描(传入 tokuiDiv 作用域)\n\n if (typeof window !== 'undefined' && window.__tokuiValidatorBridge) {\n var bridge = window.__tokuiValidatorBridge;\n\n if (bridge.applyDefaultValues) {\n bridge.applyDefaultValues(scope);\n }\n\n if (bridge.fixDateTimeDefaults) {\n bridge.fixDateTimeDefaults(scope);\n }\n\n if (bridge.initConditionalVisibility) {\n bridge.initConditionalVisibility(scope);\n }\n\n if (bridge.updateConditionalVisibility) {\n bridge.updateConditionalVisibility(scope);\n }\n } // ② 级联 select:检测未初始化的 select[data-load],补做 setupCascade\n\n\n var selects = scope.querySelectorAll('select[data-load], [data-load] select');\n selects.forEach(function (sel) {\n if (!sel._tokuiCascadeSetup) {\n sel._tokuiCascadeSetup = true; // 触发 change 事件,让已有的级联逻辑生效\n\n try {\n sel.dispatchEvent(new Event('change', {\n bubbles: true\n }));\n } catch (e) {}\n }\n }); // ③ 审批流程:强制重新定位到提交按钮前面\n // 实时渲染中 initApprovalFlow 可能在流式传输中途被触发,\n // 此时 btngroup 尚未渲染,审批流程被 appendChild 到错误位置。\n // endStream 后 DOM 已完整,这里强制修正位置。\n\n var approvalContainer = scope.querySelector('.tv-approval-flow');\n\n if (approvalContainer) {\n // 找到提交按钮组\n var allBtns = scope.querySelectorAll('button');\n var btngroup = null;\n\n for (var bi = 0; bi < allBtns.length; bi++) {\n var btxt = (allBtns[bi].textContent || '').trim();\n\n if (btxt === '提交' || btxt === '暂存' || btxt === '重置') {\n btngroup = allBtns[bi].parentElement;\n break;\n }\n }\n\n if (btngroup) {\n // 检查是否已在 wrapper 中且顺序正确\n var btnParent = btngroup.parentNode;\n var _approvalDone = false;\n\n if (btnParent && btnParent.classList.contains('tv-approval-wrapper')) {\n var existingFlow = btnParent.querySelector('.tv-approval-flow');\n\n if (existingFlow === approvalContainer && btnParent.firstChild === approvalContainer) {\n // 已在正确位置,确保可见\n approvalContainer.classList.remove('tv-approval-flow--hidden');\n _approvalDone = true;\n } else {\n // wrapper 存在但顺序不对或有旧 flow,拆开重建\n var wp = btnParent.parentNode;\n\n if (wp) {\n while (btnParent.firstChild) {\n wp.insertBefore(btnParent.firstChild, btnParent);\n }\n\n btnParent.remove();\n }\n }\n }\n\n if (!_approvalDone) {\n // 创建新包裹层,确保审批流程紧挨在 btngroup 上方\n var newWrapper = document.createElement('div');\n newWrapper.className = 'tv-approval-wrapper';\n btngroup.parentNode.insertBefore(newWrapper, btngroup);\n newWrapper.appendChild(approvalContainer);\n newWrapper.appendChild(btngroup); // 定位完成,移除隐藏类\n\n approvalContainer.classList.remove('tv-approval-flow--hidden');\n console.log('[TokUI] _reconcileAfterStream: 审批流程已重新定位到按钮组前面');\n }\n }\n } // ④ data-default-label:未匹配的 select 再试一次\n\n\n var defaultSelects = scope.querySelectorAll('select[data-default-label]');\n defaultSelects.forEach(function (sel) {\n var label = sel.getAttribute('data-default-label');\n if (!label || !sel.options) return;\n if (label.indexOf('${') >= 0) return; // 未替换的占位符\n // 检查是否已选中\n\n var selected = sel.options[sel.selectedIndex];\n if (selected && (selected.textContent || '').trim() === label.trim()) return; // 尝试匹配\n\n for (var i = 0; i < sel.options.length; i++) {\n if ((sel.options[i].textContent || '').trim() === label.trim()) {\n sel.selectedIndex = i;\n\n try {\n sel.dispatchEvent(new Event('change', {\n bubbles: true\n }));\n } catch (e) {}\n\n break;\n }\n }\n }); // ⑤ 最终条件显示刷新\n\n if (typeof window !== 'undefined' && window.__tokuiValidatorBridge && window.__tokuiValidatorBridge.updateConditionalVisibility) {\n setTimeout(function () {\n window.__tokuiValidatorBridge.updateConditionalVisibility(scope);\n }, 100);\n setTimeout(function () {\n window.__tokuiValidatorBridge.updateConditionalVisibility(scope);\n }, 500);\n } // ⑥ 补卡日历初始化:SSE 流式渲染时表单字段是逐步生成的,\n // setupPatchCardCalendar 的固定延迟可能早于字段完整渲染导致提前 return。\n // endStream 后 DOM 已完整,这里显式触发补卡日历加载(含 patchQueryMonthClockStatus 接口调用)\n\n\n var patchForms = scope.querySelectorAll('[data-approval-type=\"patch\"]');\n\n if (patchForms.length > 0 && typeof window !== 'undefined' && window.__tokuiValidatorBridge && window.__tokuiValidatorBridge.setupPatchCardCalendar) {\n patchForms.forEach(function (el) {\n var form = el.closest('form') || el.closest('[data-form-id]') || el;\n\n if (form) {\n console.log('[TokUI] _reconcileAfterStream: 触发补卡日历初始化 for form:', form);\n\n window.__tokuiValidatorBridge.setupPatchCardCalendar(form);\n }\n });\n } // ⑦ 标记表单就绪:审批流程等依赖「表单完全渲染」的逻辑在此之后才允许初始化/预览,\n // 避免 SSE 增量渲染期间在半成品 DOM 上触发审批接口导致报错。\n\n\n if (typeof window !== 'undefined' && window.__tokuiValidatorBridge && window.__tokuiValidatorBridge.markFormReady) {\n window.__tokuiValidatorBridge.markFormReady(scope);\n }\n\n console.log('[TokUI] _reconcileAfterStream: 对齐完成');\n } catch (e) {\n console.warn('[TokUI] _reconcileAfterStream error:', e);\n }\n }, 200);\n },\n // 全文本增量流式 feed TokUI(参考项目方式)\n _streamTokui: function _streamTokui(msg, fullText) {\n if (!msg._tokuiStream) return; // 动态判断当前内容是否为纯 TokUI DSL\n\n var isPureDsl = this._isPureTokuiDsl(fullText); // 如果内容模式发生了变化,重置增量追踪\n\n\n if (msg._tokuiPureDsl !== isPureDsl) {\n msg._tokuiPureDsl = isPureDsl;\n msg._prevMdLen = 0;\n msg._prevDslLen = 0;\n msg._dslBuffer = '';\n }\n\n var dsl = '';\n var md = '';\n\n if (isPureDsl) {\n // 纯 DSL:整个内容为 DSL\n dsl = fullText;\n } else {\n // 混合内容:从 ```tokui 围栏中提取 DSL\n var dslParts = []; // 函数内定义,每次执行全新正则,杜绝lastIndex污染\n\n var re = /```tokui\\s*([\\s\\S]*?)```/g;\n var match;\n\n while ((match = re.exec(fullText)) !== null) {\n var inner = match[1].trim();\n if (inner) dslParts.push(inner);\n } // 处理末尾未闭合的 ```tokui(流式场景)\n\n\n var lastIdx = re.lastIndex;\n var remaining = fullText.substring(lastIdx);\n var openIdx = remaining.lastIndexOf('```tokui');\n\n if (openIdx >= 0) {\n var dslPart = remaining.substring(openIdx + 8);\n if (dslPart.charCodeAt(0) === 10) dslPart = dslPart.substring(1); // ✅ 关键防护:如果片段里面存在闭合标记 ```,说明是完整块,不要再追加!\n\n if (dslPart && !dslPart.includes('```')) {\n dslParts.push(dslPart);\n }\n }\n\n dsl = dslParts.join('\\n'); // 移除所有代码围栏得到纯 Markdown\n\n md = fullText.replace(/```[\\s\\S]*?(```|$)/g, '');\n } // Feed Markdown 增量\n\n\n if (md) {\n var prevMdLen = msg._prevMdLen || 0;\n\n if (md.length > prevMdLen) {\n var newMd = md.substring(prevMdLen);\n msg._prevMdLen = md.length;\n\n if (!msg._mdTagOpen) {\n msg._tokuiStream.feed('[md]\\n');\n\n msg._mdTagOpen = true;\n }\n\n msg._tokuiStream.feed(newMd);\n }\n } // Feed DSL 增量\n\n\n if (dsl) {\n var prevDslLen = msg._prevDslLen || 0;\n\n if (dsl.length > prevDslLen) {\n var newDsl = dsl.substring(prevDslLen);\n msg._prevDslLen = dsl.length;\n\n if (msg._mdTagOpen) {\n msg._tokuiStream.feed('[/md]\\n');\n\n msg._mdTagOpen = false;\n } // 逐行 feed DSL,缓冲未完成行\n\n\n msg._dslBuffer = (msg._dslBuffer || '') + newDsl;\n\n var lines = msg._dslBuffer.split('\\n');\n\n for (var i = 0; i < lines.length - 1; i++) {\n msg._tokuiStream.feed(this._applyApplicantNickName(lines[i]) + '\\n');\n }\n\n msg._dslBuffer = lines[lines.length - 1];\n }\n }\n },\n // 渲染历史消息中的 TokUI 内容(仅在 TokUI 可用时执行)\n _renderTokuiMessages: function _renderTokuiMessages() {\n var _this12 = this;\n\n var TokUIConstructor = resolveTokUIConstructor();\n if (!TokUIConstructor) return;\n ensureTokuiValidatorPatched();\n var container = this.$refs.messagesRef;\n if (!container) return;\n var allRows = container.querySelectorAll('.msg-row');\n allRows.forEach(function (row, idx) {\n var msg = _this12.messages[idx];\n if (!msg || !msg._hasTokui) return; // 已通过流式渲染完成的消息,跳过(防止 endStream 后 watcher 触发重复渲染)\n\n if (msg._tokuiRendered) return;\n if (msg.loading || !msg.content || msg._tokuiStream) return;\n var tokuiDiv = row.querySelector('.tokui-stream');\n if (!tokuiDiv) return; // 清除旧内容和状态(DOM 复用 + 流式残留)\n\n if (msg._tokuiValidator && typeof msg._tokuiValidator.destroy === 'function') {\n msg._tokuiValidator.destroy();\n\n msg._tokuiValidator = null;\n }\n\n tokuiDiv.innerHTML = '';\n tokuiDiv.classList.remove('tokui-streaming');\n tokuiDiv.style.display = 'block';\n\n try {\n var ui = new TokUIConstructor({\n container: tokuiDiv\n }); // 绑定校验引擎(data-rule / data-trigger / data-load / formSubmit)\n\n var validatorBridge = (0, _tokuiValidator.setupValidator)(ui, new _tokuiValidator.Validator(), _this12._buildTokuiBridgeOptions({\n container: tokuiDiv,\n scopeKey: msg.msgKey || msg._key,\n skipDataLoad: msg.formSubmitted === 1,\n onFormSuccess: function onFormSuccess(data) {\n return _this12._handleTokuiFormSuccess(msg, data);\n }\n }));\n setTokuiRuntimeProperty(msg, '_tokuiValidator', validatorBridge); // ── 与 _streamTokui 完全一致的提取逻辑(含未闭合围栏处理) ──\n\n var text = msg.content;\n\n var isPureDsl = _this12._isPureTokuiDsl(text);\n\n var dsl = '';\n var md = '';\n\n if (isPureDsl) {\n dsl = text;\n } else {\n var dslParts = []; // 每次执行新建正则!!不要提取到外层复用\n\n var re = /```tokui\\s*([\\s\\S]*?)```/g;\n var match;\n\n while ((match = re.exec(text)) !== null) {\n var inner = match[1].trim();\n if (inner) dslParts.push(inner);\n } // 处理未闭合的 ```tokui(流式逻辑)\n\n\n var lastIdx = re.lastIndex;\n var remaining = text.substring(lastIdx);\n var openIdx = remaining.lastIndexOf('```tokui');\n\n if (openIdx >= 0) {\n var dslPart = remaining.substring(openIdx + 8);\n if (dslPart.charCodeAt(0) === 10) dslPart = dslPart.substring(1); // 增强防护:如果这段dslPart包含闭合标记```,说明它其实是完整块,不要追加!\n\n if (dslPart && !dslPart.includes('```')) {\n dslParts.push(dslPart);\n }\n }\n\n dsl = dslParts.join('\\n');\n md = text.replace(/```[\\s\\S]*?(```|$)/g, '');\n } // ── 与流式完全一致的 feed 顺序和方式 ──\n\n\n ui.startStream(); // Feed Markdown\n\n if (md && md.trim()) {\n ui.feed('[md]\\n');\n ui.feed(md);\n ui.feed('[/md]\\n');\n } // Feed DSL 逐行(与流式 _streamTokui 完全一致)\n\n\n if (dsl) {\n var dslLines = dsl.split('\\n');\n\n for (var i = 0; i < dslLines.length; i++) {\n ui.feed(_this12._applyApplicantNickName(dslLines[i]) + '\\n');\n }\n }\n\n ui.endStream();\n tokuiDiv._tokuiRendered = true;\n\n _this12.$set(msg, '_tokuiRendered', true); // 已提交的表单:跳过收尾对齐(避免 applyDefaultValues 覆盖反显值 / change 触发校验),\n // 直接反显 formData 并恢复遮罩;未提交的表单走正常收尾对齐。\n\n\n if (msg.formSubmitted === 1) {\n _this12._restoreTokuiFormOverlay(msg, tokuiDiv);\n } else {\n _this12._reconcileAfterStream(msg, tokuiDiv);\n }\n } catch (e) {\n console.error('[TokUI] render error:', e);\n tokuiDiv.textContent = msg.content;\n }\n });\n },\n // 已提交表单的遮罩恢复:历史消息重渲染后重新锁定,防止二次提交\n _restoreTokuiFormOverlay: function _restoreTokuiFormOverlay(msg, tokuiDiv) {\n if (!msg || msg.formSubmitted !== 1 || msg.role !== 'assistant') return;\n if (!tokuiDiv) return;\n var bridge = window.__tokuiValidatorBridge;\n if (!bridge) return;\n console.log('[TokUI] _restoreTokuiFormOverlay: msgKey=' + (msg.msgKey || msg._key) + ', hasFormData=' + !!msg.formData + ', formData=' + (msg.formData ? JSON.stringify(msg.formData).substring(0, 200) : 'null')); // 1) 把提交时保存的 formData 反显回表单(不触发校验)\n\n if (bridge.applyFormData && msg.formData) {\n // 立即回填一次,并在 200ms 后再回填一次(兜底:TokUI 异步渲染可能导致首次回填时 DOM 未就绪)\n bridge.applyFormData(tokuiDiv, msg.formData);\n if (bridge.fixDateTimeDefaults) bridge.fixDateTimeDefaults(tokuiDiv);\n setTimeout(function () {\n if (tokuiDiv && tokuiDiv.parentNode) {\n bridge.applyFormData(tokuiDiv, msg.formData);\n if (bridge.fixDateTimeDefaults) bridge.fixDateTimeDefaults(tokuiDiv);\n }\n }, 200);\n } else {\n console.warn('[TokUI] _restoreTokuiFormOverlay: formData 为空,无法回填表单字段'); // 即使没有 formData,也尝试补全 datetimepicker 默认时间\n\n if (bridge.fixDateTimeDefaults) bridge.fixDateTimeDefaults(tokuiDiv);\n } // 2) 复用 lockFormWithOverlay 加遮罩(与提交按钮后的效果完全一致)\n\n\n var form = tokuiDiv.querySelector('form') || tokuiDiv;\n\n if (bridge.lockFormWithOverlay) {\n bridge.lockFormWithOverlay(form);\n }\n },\n // TokUI 表单提交成功回调(bridge 在校验通过且 fetch 成功后触发)\n // 1) 标记消息已提交 2) $emit 通知宿主(持久化/工作流由宿主决定) 3) 自动回传摘要驱动 AI 继续会话\n _handleTokuiFormSuccess: function _handleTokuiFormSuccess(msg, payload) {\n if (msg) this.$set(msg, 'formSubmitted', 1);\n var formData = payload && payload.formData || {};\n this.$emit('form-submit', {\n sessionId: this.activeSessionId,\n msgKey: msg && (msg.msgKey || msg._key),\n formId: payload && payload.formId || null,\n formData: formData,\n resultText: payload && payload.resultText || ''\n });\n if (!this.activeSessionId || this.sending) return; // const pairs = Object.keys(formData).map(k => k + '=' + formData[k])\n // const summary = '已提交表单' + (payload && payload.formId ? '「' + payload.formId + '」' : '') + (pairs.length ? ':' + pairs.join(',') : '')\n // this._sendMessage(summary)\n },\n sendMessage: function sendMessage() {\n var _this13 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee3() {\n var text;\n return regeneratorRuntime.wrap(function _callee3$(_context3) {\n while (1) {\n switch (_context3.prev = _context3.next) {\n case 0:\n text = _this13.inputText.trim();\n\n if (!(!text || _this13.sending)) {\n _context3.next = 3;\n break;\n }\n\n return _context3.abrupt(\"return\");\n\n case 3:\n if (!(text.length > 300)) {\n _context3.next = 6;\n break;\n }\n\n _this13.$message({\n message: '消息内容不能超过300字',\n type: 'warning',\n duration: 2000\n });\n\n return _context3.abrupt(\"return\");\n\n case 6:\n if (_this13.activeSessionId) {\n _context3.next = 10;\n break;\n }\n\n _this13.$emit('start-session');\n\n _this13._pendingSendText = text;\n return _context3.abrupt(\"return\");\n\n case 10:\n _this13._sendMessage(text);\n\n case 11:\n case \"end\":\n return _context3.stop();\n }\n }\n }, _callee3);\n }))();\n },\n _sendMessage: function _sendMessage(text) {\n var _this14 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee4() {\n var ta, isMobile, baseH, agentName, msgIdx, thinkingBuffer, finalContent, answerBuffer, thinkDisplayed, typeTimer, streamDone, streamFinalized, finalizeStreamMessage, url, es;\n return regeneratorRuntime.wrap(function _callee4$(_context4) {\n while (1) {\n switch (_context4.prev = _context4.next) {\n case 0:\n _this14.inputText = '';\n ta = _this14.$refs.taRef;\n isMobile = _this14.isMobile;\n baseH = isMobile ? 36 : 100;\n\n if (ta) {\n ta.style.height = baseH + 'px';\n }\n\n _this14.showDownBtn = false;\n\n _this14.messages.push({\n role: 'user',\n content: text,\n createTime: new Date().toISOString(),\n _key: 'user_' + Date.now() + '_' + Math.random()\n });\n\n agentName = _this14.currentAgentName;\n msgIdx = _this14.messages.push({\n role: \"assistant\",\n content: \"\",\n thinking: \"\",\n loading: true,\n toolCalls: [],\n agentName: agentName,\n createTime: new Date().toISOString(),\n _key: 'asst_' + Date.now() + '_' + Math.random()\n }) - 1;\n\n _this14.scrollToBottom(true);\n\n _this14.sending = true;\n _this14.waitSeconds = 0;\n if (_this14.waitTimer) clearInterval(_this14.waitTimer);\n _this14.waitTimer = setInterval(function () {\n _this14.waitSeconds++;\n }, 1000);\n thinkingBuffer = '';\n finalContent = '';\n answerBuffer = '';\n thinkDisplayed = 0;\n typeTimer = null;\n streamDone = false;\n streamFinalized = false;\n\n finalizeStreamMessage = function finalizeStreamMessage() {\n if (streamFinalized) return;\n var msg = _this14.messages[msgIdx];\n\n if (!msg) {\n streamFinalized = true;\n if (typeTimer) clearInterval(typeTimer);\n typeTimer = null;\n return;\n }\n\n streamFinalized = true;\n if (typeTimer) clearInterval(typeTimer);\n typeTimer = null;\n _this14._typeTimer = null;\n var key = msg._key;\n\n if (key) {\n _this14.$delete(_this14.contentCache, key);\n\n _this14.$delete(_this14.contentCache, key + '_t');\n }\n\n if (msg._tokuiStream) {\n try {\n if (msg._mdTagOpen) {\n msg._tokuiStream.feed('[/md]\\n');\n\n msg._mdTagOpen = false;\n }\n\n if (msg._dslBuffer) {\n msg._tokuiStream.feed(_this14._applyApplicantNickName(msg._dslBuffer) + '\\n');\n\n msg._dslBuffer = '';\n }\n\n msg._tokuiStream.endStream();\n } catch (e) {\n console.error('[TokUI] endStream error:', e);\n }\n\n _this14._reconcileAfterStream(msg, msg._tokuiDiv);\n\n if (msg._tokuiDiv) msg._tokuiDiv._tokuiRendered = true;\n msg._tokuiStream = null;\n\n _this14.$set(msg, '_tokuiRendered', true);\n }\n\n msg.content = finalContent || answerBuffer || thinkingBuffer || msg.content || '❌ 连接中断';\n msg.thinking = thinkingBuffer;\n msg.loading = false; // TokUI 兜底:流式期间解析失败回退成代码时,若最终完整内容确为 TokUI 表单,\n // 补上 _hasTokui 标记,由 messages watcher 在 nextTick 重新渲染完整的 TokUI 表单\n\n if (!msg._tokuiRendered && !msg._hasTokui && _this14._isTokuiFinalContent(msg.content)) {\n _this14.$set(msg, '_hasTokui', true);\n }\n\n _this14.sending = false;\n\n _this14.clearWaitTimer();\n\n _this14.scrollToBottom();\n\n _this14.$emit('session-refresh');\n\n _this14.$emit('message-sent');\n };\n\n typeTimer = setInterval(function () {\n if (!_this14.messages[msgIdx]) {\n finalizeStreamMessage();\n return;\n } // Phase 1: 逐字展示思考内容\n\n\n if (thinkDisplayed < thinkingBuffer.length) {\n thinkDisplayed += 50;\n if (thinkDisplayed > thinkingBuffer.length) thinkDisplayed = thinkingBuffer.length;\n _this14.messages[msgIdx].thinking = thinkingBuffer.substring(0, thinkDisplayed);\n\n _this14.scrollToBottom();\n } // Phase 2: 渲染答案内容\n\n\n var msg = _this14.messages[msgIdx]; // TokUI 活跃期间始终用 answerBuffer(避免 finalContent 带 ```tokui 围栏导致内容跳变)\n\n var target = msg._tokuiStream ? answerBuffer : finalContent || answerBuffer;\n var prevLen = msg._tokuiPrevLen || 0;\n\n if (target.length > prevLen) {\n msg._tokuiPrevLen = target.length; // 检测内容是否包含 TokUI 标记(```tokui 围栏 或 纯 DSL 标签开头)\n\n var hasTokuiMarker = msg._tokuiStream || _this14._isPureTokuiDsl(target) || target.indexOf('```tokui') !== -1;\n\n if (hasTokuiMarker && !msg._hasTokuiFailed) {\n // ── TokUI 路径 ──\n if (!msg._tokuiStream) {\n var container = _this14.$refs.messagesRef;\n\n if (container) {\n var rows = container.querySelectorAll('.msg-row.assistant');\n var lastRow = rows[rows.length - 1];\n var tokuiDiv = lastRow === null || lastRow === void 0 ? void 0 : lastRow.querySelector('.tokui-stream');\n var TokUIConstructor = resolveTokUIConstructor();\n\n if (tokuiDiv && TokUIConstructor) {\n try {\n ensureTokuiValidatorPatched();\n setTokuiRuntimeProperty(msg, '_tokuiDiv', tokuiDiv);\n setTokuiRuntimeProperty(msg, '_tokuiStream', new TokUIConstructor({\n container: tokuiDiv\n })); // 绑定校验引擎(data-rule / data-trigger / data-load / formSubmit)\n\n setTokuiRuntimeProperty(msg, '_tokuiValidator', (0, _tokuiValidator.setupValidator)(msg._tokuiStream, new _tokuiValidator.Validator(), _this14._buildTokuiBridgeOptions({\n container: tokuiDiv,\n scopeKey: msg.msgKey || msg._key,\n onFormSuccess: function onFormSuccess(data) {\n return _this14._handleTokuiFormSuccess(msg, data);\n }\n })));\n\n msg._tokuiStream.startStream();\n\n tokuiDiv.classList.add('tokui-streaming');\n msg._tokuiPureDsl = _this14._isPureTokuiDsl(target);\n msg._mdTagOpen = false;\n msg._prevMdLen = 0;\n msg._prevDslLen = 0;\n msg._dslBuffer = '';\n\n _this14.$set(msg, '_hasTokui', true);\n } catch (e) {\n console.error('[TokUI] init error:', e);\n msg._hasTokuiFailed = true;\n msg._tokuiStream = null;\n }\n } else {\n msg._hasTokuiFailed = true;\n }\n }\n }\n\n if (msg._tokuiStream) {\n try {\n _this14._streamTokui(msg, target);\n } catch (e) {\n console.error('[TokUI] feed error:', e);\n msg._hasTokuiFailed = true;\n msg._tokuiStream = null;\n msg.content = target;\n }\n } else if (msg._hasTokuiFailed) {\n // TokUI 初始化失败,回退到普通渲染\n msg.content = target;\n }\n } else {\n // ── 普通渲染路径(保持原有逻辑不变)──\n msg.content = target;\n }\n\n _this14.scrollToBottom();\n\n return;\n } // Phase 3: 流结束 → 收尾\n\n\n if (streamDone) {\n finalizeStreamMessage();\n }\n }, 30);\n _this14._typeTimer = typeTimer;\n url = (0, _apichat.createStreamUrl)(_this14.activeSessionId, text);\n\n if (_this14.eventSource) {\n _this14.eventSource.close();\n }\n\n _this14.eventSource = new EventSource(url);\n es = _this14.eventSource;\n es.addEventListener('reasoning', function (e) {\n if (e.data) {\n var chunk = e.data;\n var cleanChunk = chunk.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2').replace(/([a-zA-Z])([\\u4e00-\\u9fff])/g, '$1 $2').replace(/([\\u4e00-\\u9fff])([a-zA-Z])/g, '$1 $2').replace(/([a-zA-Z])(\\d)/g, '$1 $2').replace(/(\\d)([a-zA-Z])/g, '$1 $2').replace(/\\s+([:,.!?])/g, '$1').replace(/([:,.!?])\\s+/g, '$1 ');\n cleanChunk = cleanChunk.replace(/\\s+/g, ' ');\n var lastChar = thinkingBuffer.slice(-1);\n var firstChar = cleanChunk.slice(0, 1);\n\n var isLetter = function isLetter(c) {\n return /[a-zA-Z]/.test(c);\n };\n\n if (isLetter(lastChar) && isLetter(firstChar)) {\n thinkingBuffer += ' ' + cleanChunk;\n } else {\n thinkingBuffer += cleanChunk;\n }\n }\n });\n es.addEventListener('reasoning_result', function (e) {\n if (e.data) {\n thinkingBuffer = e.data;\n }\n });\n es.addEventListener('answer', function (e) {\n if (e.data) {\n answerBuffer += e.data.replace(/([a-zA-Z]+)([\\u4e00-\\u9fff])/g, '$1 $2').replace(/([\\u4e00-\\u9fff])([a-zA-Z]+)/g, '$1 $2').replace(/([a-z])([A-Z])/g, '$1 $2').replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2').replace(/([a-zA-Z])(\\d)/g, '$1 $2').replace(/(\\d)([a-zA-Z])/g, '$1 $2');\n }\n });\n es.addEventListener('tool_result', function (e) {\n if (e.data && _this14.messages[msgIdx]) {\n if (!_this14.messages[msgIdx].toolCalls) _this14.messages[msgIdx].toolCalls = [];\n\n _this14.messages[msgIdx].toolCalls.push(e.data);\n\n _this14.scrollToBottom();\n }\n });\n es.addEventListener('msgKey', function (e) {\n if (e.data && _this14.messages[msgIdx]) {\n _this14.$set(_this14.messages[msgIdx], 'msgKey', e.data);\n }\n });\n es.addEventListener('agent_result', function (e) {\n if (_this14.eventSource !== es && _this14.eventSource !== null) return;\n finalContent = e.data || '';\n streamDone = true;\n es.close();\n _this14.eventSource = null;\n finalizeStreamMessage();\n });\n\n es.onerror = function (err) {\n if (_this14.eventSource !== es && _this14.eventSource !== null) return;\n console.log('[SSE] onerror', err);\n if (thinkingBuffer.length > 0 && !streamDone) finalContent = thinkingBuffer;\n if (!streamDone) finalContent = '❌ 连接中断';\n streamDone = true;\n es.close();\n _this14.eventSource = null;\n finalizeStreamMessage();\n };\n\n case 35:\n case \"end\":\n return _context4.stop();\n }\n }\n }, _callee4);\n }))();\n },\n stopAgent: function stopAgent() {\n // 1) 停止打字机定时器,避免继续把半成品 DSL feed 进 TokUI 导致渲染报错\n if (this._typeTimer) {\n clearInterval(this._typeTimer);\n this._typeTimer = null;\n }\n\n if (this.eventSource) {\n this.eventSource.close();\n this.eventSource = null;\n }\n\n if (this.activeSessionId) {\n (0, _apichat.stopStream)(this.activeSessionId).catch(function () {});\n } // 2) 结束正在生成的消息:若为 TokUI 表单流,先 endStream 冻结已渲染部分,不再继续生成\n\n\n if (this.messages.length > 0) {\n var last = this.messages[this.messages.length - 1];\n\n if (last && last.loading) {\n if (last._tokuiStream) {\n try {\n if (last._mdTagOpen) {\n last._tokuiStream.feed('[/md]\\n');\n\n last._mdTagOpen = false;\n }\n\n last._tokuiStream.endStream();\n } catch (e) {\n console.error('[TokUI] stop endStream error:', e);\n } // 停止该表单的审批刷新/重试,避免暂停后仍输出 [Approval] 日志\n\n\n if (last._tokuiDiv && window.__tokuiValidatorBridge && window.__tokuiValidatorBridge.stopApprovalFlows) {\n try {\n window.__tokuiValidatorBridge.stopApprovalFlows(last._tokuiDiv);\n } catch (e2) {\n console.error('[Approval] stopApprovalFlows error:', e2);\n }\n }\n\n last._tokuiStream = null;\n }\n\n last.content = last.content || last.thinking || '⏹ 已停止';\n last.loading = false;\n this.$delete(this.contentCache, last._key);\n }\n }\n\n this.sending = false;\n this.clearWaitTimer();\n },\n scrollToTop: function scrollToTop() {\n var el = this.$refs.messagesRef;\n if (el) el.scrollTop = 0;\n },\n scrollToBottom: function scrollToBottom() {\n var _this15 = this;\n\n var force = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n if (this.showDownBtn && !force) return;\n this.$nextTick(function () {\n var el = _this15.$refs.messagesRef;\n\n if (el) {\n el.scrollTop = el.scrollHeight;\n _this15.lastScrollTop = el.scrollTop;\n _this15.showDownBtn = false;\n _this15._userScrolledUp = false;\n }\n });\n },\n scrollToBottomOnce: function scrollToBottomOnce() {\n var _this16 = this;\n\n clearTimeout(this._scrollTimer);\n this.$nextTick(function () {\n var tryScroll = function tryScroll() {\n var el = _this16.$refs.messagesRef;\n\n if (!el) {\n _this16._scrollTimer = setTimeout(tryScroll, 50);\n return;\n }\n\n var originalScrollBehavior = el.style.scrollBehavior;\n el.style.scrollBehavior = 'auto';\n el.scrollTop = el.scrollHeight - el.clientHeight;\n el.style.scrollBehavior = originalScrollBehavior;\n _this16.showDownBtn = false;\n _this16._userScrolledUp = false;\n };\n\n tryScroll();\n });\n },\n copyText: function copyText(content) {\n var _this17 = this;\n\n if (!content) return;\n var text = content.replace(/<[^>]+>/g, ''); // 优先使用 Clipboard API(HTTPS 环境)\n\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(text).then(function () {\n _this17.$message({\n message: '已复制到剪贴板',\n type: 'success',\n duration: 1500\n });\n }).catch(function () {\n _this17._fallbackCopyText(text);\n });\n } else {\n this._fallbackCopyText(text);\n }\n },\n _fallbackCopyText: function _fallbackCopyText(text) {\n try {\n var ta = document.createElement('textarea');\n ta.value = text;\n ta.style.cssText = 'position:fixed;left:-9999px;top:0;opacity:0';\n document.body.appendChild(ta);\n ta.focus();\n ta.select();\n document.execCommand('copy');\n document.body.removeChild(ta);\n this.$message({\n message: '已复制到剪贴板',\n type: 'success',\n duration: 1500\n });\n } catch (_unused3) {\n this.$message({\n message: '复制失败',\n type: 'error',\n duration: 2000\n });\n }\n },\n copyAsImage: function copyAsImage(content) {\n var _this18 = this;\n\n if (!content) return;\n var div = document.createElement('div');\n div.className = 'copy-image-render';\n div.style.cssText = 'position:fixed;left:-9999px;top:0;width:600px;padding:20px;background:#fff;font-size:14px;line-height:1.6;color:#333;border-radius:8px;font-family:-apple-system,BlinkMacSystemFont,sans-serif;'; // 内联表格样式(因 Vue 作用域样式不覆盖复制用的脱离文档流 div)\n\n div.innerHTML = this.renderMarkdown(content) + '<style>table{border-collapse:collapse;width:100%;margin:6px 0;font-size:14px}' + 'th,td{border:1px solid #e8e8e8;padding:5px 8px;text-align:left}' + 'th{background:#fafafa;font-weight:600}</style>';\n document.body.appendChild(div);\n this.$message({\n message: '正在生成图片...',\n type: 'info',\n duration: 2000\n });\n (0, _html2canvas.default)(div, {\n scale: 2,\n useCORS: true,\n backgroundColor: '#ffffff'\n }).then(function (canvas) {\n var base64 = canvas.toDataURL('image/png'); // 移动端:调用原生方法保存到本地相册\n\n if (_this18.isMobile) {\n if (_this18.$hybrid && typeof _this18.$hybrid.savePic2Library === 'function') {\n _this18.$hybrid.savePic2Library(base64, function () {\n _this18.$message({\n message: '图片已保存到本地相册',\n type: 'success',\n duration: 1500\n });\n });\n } else {\n // fallback:展示图片让用户长按保存\n _this18.previewImage = base64;\n\n _this18.$message({\n message: '长按图片可保存到本地',\n type: 'info',\n duration: 3000\n });\n }\n\n return;\n } // PC端:复制到剪贴板\n\n\n var canCopyImage = typeof ClipboardItem !== 'undefined' && navigator.clipboard && typeof navigator.clipboard.write === 'function';\n\n if (!canCopyImage) {\n _this18.$message({\n message: '当前浏览器不支持复制图片',\n type: 'warning',\n duration: 3000\n });\n\n return;\n }\n\n canvas.toBlob(function (blob) {\n if (!blob) {\n _this18.$message({\n message: '生成图片失败',\n type: 'error',\n duration: 2000\n });\n\n return;\n }\n\n navigator.clipboard.write([new ClipboardItem({\n 'image/png': blob\n })]).then(function () {\n _this18.$message({\n message: '图片已复制到剪贴板',\n type: 'success',\n duration: 1500\n });\n }).catch(function () {\n _this18.$message({\n message: '复制失败,请重试',\n type: 'error',\n duration: 2000\n });\n });\n }, 'image/png');\n }).catch(function () {\n _this18.$message({\n message: '生成图片失败',\n type: 'error',\n duration: 2000\n });\n }).finally(function () {\n document.body.removeChild(div);\n });\n }\n }\n};\nexports.default = _default2;\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/components/chatMain/ChatDetail.vue?./node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
920
920
|
|
|
921
921
|
/***/ }),
|
|
922
922
|
|
|
@@ -940,7 +940,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n
|
|
|
940
940
|
/***/ (function(module, exports, __webpack_require__) {
|
|
941
941
|
|
|
942
942
|
"use strict";
|
|
943
|
-
eval("\n\nvar _interopRequireDefault = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/interopRequireDefault */ \"./node_modules/@babel/runtime/helpers/interopRequireDefault.js\").default;\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _asyncToGenerator2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/asyncToGenerator */ \"./node_modules/@babel/runtime/helpers/asyncToGenerator.js\"));\n\nvar _apichat = __webpack_require__(/*! @lib/api/apichat */ \"./src/lib/api/apichat.js\");\n\nvar _cookie = __webpack_require__(/*! @lib/utils/cookie.js */ \"./src/lib/utils/cookie.js\");\n\nvar _message = __webpack_require__(/*! @lib/utils/message.js */ \"./src/lib/utils/message.js\");\n\nvar _guideHelper = __webpack_require__(/*! @lib/utils/guideHelper.js */ \"./src/lib/utils/guideHelper.js\");\n\nvar _guideConfig = __webpack_require__(/*! @lib/components/guide/guideConfig.js */ \"./src/lib/components/guide/guideConfig.js\");\n\nvar _guideMobileConfig = __webpack_require__(/*! @lib/components/guide/guideMobileConfig.js */ \"./src/lib/components/guide/guideMobileConfig.js\");\n\nvar _NewFeatureGuide = _interopRequireDefault(__webpack_require__(/*! @lib/components/guide/NewFeatureGuide.vue */ \"./src/lib/components/guide/NewFeatureGuide.vue\"));\n\nvar _ChatSidebar = _interopRequireDefault(__webpack_require__(/*! ./ChatSidebar.vue */ \"./src/lib/components/chatMain/ChatSidebar.vue\"));\n\nvar _AgentSelector = _interopRequireDefault(__webpack_require__(/*! ./AgentSelector.vue */ \"./src/lib/components/chatMain/AgentSelector.vue\"));\n\nvar _ChatDetail = _interopRequireDefault(__webpack_require__(/*! ./ChatDetail.vue */ \"./src/lib/components/chatMain/ChatDetail.vue\"));\n\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\nvar _default = {\n name: 'ChatMain',\n components: {\n ChatSidebar: _ChatSidebar.default,\n AgentSelector: _AgentSelector.default,\n ChatDetail: _ChatDetail.default,\n NewFeatureGuide: _NewFeatureGuide.default\n },\n props: {\n Ak: {\n type: String,\n default: ''\n },\n Sk: {\n type: String,\n default: ''\n },\n imAccount: {\n type: String,\n default: ''\n },\n companyNo: {\n type: String,\n default: ''\n },\n isIntelShow: {\n default: false,\n type: Boolean\n },\n chatImToken: {\n default: '',\n type: String\n },\n userno: {\n default: '',\n type: String\n },\n isPlugin: {\n default: false,\n type: Boolean\n }\n },\n data: function data() {\n // 首次渲染前检测窗口宽度,避免 H5 端先显示 PC 布局再切换导致闪烁\n var _isMobile = typeof window !== 'undefined' && window.innerWidth <= 768;\n\n return {\n agentList: [],\n currentAgent: '',\n currentAgentDesc: '',\n sessionsLoaded: false,\n sessionList: [],\n activeSessionId: '',\n loadingMore: false,\n showAgentSelect: !_isMobile,\n isMobile: _isMobile,\n showSidebar: false,\n showH5List: _isMobile,\n agentInfo: {},\n nickName: '',\n sessionsLoading: true,\n // 引导动画状态(PC & 移动端共用)\n showGuide: false,\n guideSteps: [],\n isGuideFirstStep: true,\n isGuideLastStep: false,\n // 移动端跨页面引导专用状态\n isMobileGuide: false,\n guidePageTransitioning: false,\n _swipeStartX: 0,\n _swipeStartY: 0,\n _swipeHandled: false\n };\n },\n created: function created() {\n this.getCurrentUserInfo();\n\n if (this.Ak || this.Sk) {\n (0, _apichat.setApiAuth)(this.Ak, this.Sk, this.imAccount, this.companyNo, this.chatImToken, this.userno);\n }\n },\n mounted: function mounted() {\n this.updateViewportState();\n window.addEventListener('resize', this.updateViewportState, {\n passive: true\n }); // Android WebView 兼容:在 document 级别监听 touchmove/touchend\n // 避免可滚动子元素消费事件后不冒泡到父级\n\n document.addEventListener('touchmove', this._onSwipeMove, {\n passive: true\n });\n document.addEventListener('touchend', this._onSwipeEnd, {\n passive: true\n }); // 拦截 Android 系统返回手势:通过 history.pushState + popstate\n\n window.addEventListener('popstate', this._onPopState); // 引导动画在 loadSessions 完成后初始化(见 loadSessions),\n // 以便依据会话数据判定新老用户,从正确的页面开始引导\n },\n watch: {},\n methods: {\n getCurrentUserInfo: function getCurrentUserInfo() {\n var _this = this;\n\n (0, _apichat.getCurrentUserInfo)().then(function (data) {\n _this.nickName = data && data.nickName || ''; // 单独用 getCurrentUserInfo 返回的 no 注入 userno,不重复调用 setApiAuth\n\n var no = data && (data.no !== undefined && data.no !== null ? data.no : data.userNo);\n\n if (no !== undefined && no !== null && no !== '') {\n (0, _apichat.setApiUserNo)(String(no));\n }\n\n _this.loadAgents();\n\n _this.loadSessions();\n }).catch(function () {});\n },\n handleFormSubmit: function handleFormSubmit(payload) {\n if (!payload || !payload.sessionId || !payload.msgKey) return;\n (0, _apichat.markFormSubmitted)(payload.sessionId, payload.msgKey, payload.formData).catch(function () {});\n },\n // ── H5 侧滑手势 ──\n onSwipeTouchStart: function onSwipeTouchStart(e) {\n if (!this.isMobile) return;\n var t = e.touches[0];\n this._swipeStartX = t.clientX;\n this._swipeStartY = t.clientY;\n this._swipeHandled = false; // 仅当从屏幕左/右边缘区域(约屏幕宽度的 9%,最大 56px)起手才算返回上一级手势,\n // 避免页面内部正常的横向滑动/滚动被误判成返回首页\n\n var edge = Math.min(56, Math.round(window.innerWidth * 0.09));\n this._swipeFromLeftEdge = t.clientX <= edge;\n this._swipeFromRightEdge = t.clientX >= window.innerWidth - edge;\n this._swipeFromEdge = this._swipeFromLeftEdge || this._swipeFromRightEdge;\n },\n // 在 touchmove 中实时检测\n // 从左边缘起手向右滑、或从右边缘起手向左滑才触发返回;页面内部滑动不受影响\n _swipeShouldTrigger: function _swipeShouldTrigger(diffX, diffY) {\n if (!this._swipeFromEdge) return false; // 从左边缘 → 右滑;从右边缘 → 左滑\n\n var directionOk = this._swipeFromLeftEdge && diffX >= 60 || this._swipeFromRightEdge && diffX <= -60;\n if (!directionOk) return false; // 水平距离明显大于垂直距离(排除上下滚动干扰)\n\n return Math.abs(diffX) >= Math.abs(diffY) * 2;\n },\n _onSwipeMove: function _onSwipeMove(e) {\n if (!this.isMobile || this._swipeHandled) return;\n var moveX = e.touches[0].clientX;\n var moveY = e.touches[0].clientY;\n var diffX = moveX - this._swipeStartX;\n var diffY = moveY - this._swipeStartY;\n if (!this._swipeShouldTrigger(diffX, diffY)) return;\n this._swipeHandled = true;\n\n this._triggerSwipeAction();\n },\n // touchend 检测(兜底,兼容不支持 touchmove 的场景)\n _onSwipeEnd: function _onSwipeEnd(e) {\n if (!this.isMobile || this._swipeHandled) return;\n var endX = e.changedTouches[0].clientX;\n var endY = e.changedTouches[0].clientY;\n var diffX = endX - this._swipeStartX;\n var diffY = endY - this._swipeStartY;\n if (!this._swipeShouldTrigger(diffX, diffY)) return;\n this._swipeHandled = true;\n\n this._triggerSwipeAction();\n },\n _triggerSwipeAction: function _triggerSwipeAction() {\n if (this.showH5List) {\n // 列表页 → 原生返回\n this.handleNativeBack();\n } else if (this.showAgentSelect) {\n // 智能体选择页 → 返回列表\n this.goBackToList();\n } else if (!this.showAgentSelect) {\n // 详情页/欢迎页 → 返回列表\n this.goBackToList();\n }\n },\n // 拦截 Android 系统返回手势(通过 history.pushState + popstate)\n // 当用户在详情页触发系统返回时,WebView 先回退历史栈触发 popstate,\n // 我们在这里拦截并导航到列表页,避免 Activity 被直接关闭\n _onPopState: function _onPopState() {\n if (!this.isMobile) return;\n\n if (this.showAgentSelect) {\n // 智能体选择页 → 返回列表\n this.goBackToList();\n history.pushState({\n page: 'chatList'\n }, '');\n } else if (!this.showH5List) {\n // 详情页/欢迎页 → 返回列表\n this.goBackToList();\n history.pushState({\n page: 'chatList'\n }, '');\n } else {\n // 列表页 → 直接关闭 WebView\n this.handleNativeBack();\n }\n },\n handleNewSessionInternal: function handleNewSessionInternal() {\n var _this2 = this;\n\n if (!this.currentAgent) return;\n if (this.activeSessionId) this.closeOldSession();\n (0, _apichat.startSession)(this.currentAgent).then(function (res) {\n _this2.activeSessionId = res.sessionId || '';\n\n _this2.loadSessions();\n\n var agent = _this2.agentList.find(function (a) {\n return a.agentCode === _this2.currentAgent;\n });\n\n if (agent) _this2.addWelcomeMessage(agent);\n }).catch(function () {\n _this2.sessionsLoading = false;\n });\n },\n updateViewportState: function updateViewportState() {\n if (typeof window === 'undefined') return;\n var nextIsMobile = window.innerWidth <= 768;\n var wasMobile = this.isMobile;\n this.isMobile = nextIsMobile;\n\n if (!nextIsMobile) {\n this.showSidebar = false;\n this.showH5List = false;\n } else if (!wasMobile && nextIsMobile) {\n if (this.sessionList.length > 0 && !this.activeSessionId) {\n this.showH5List = true;\n this.showAgentSelect = false;\n }\n }\n },\n toggleSidebar: function toggleSidebar() {\n if (!this.isMobile) return;\n this.showSidebar = !this.showSidebar;\n },\n closeSidebar: function closeSidebar() {\n this.showSidebar = false;\n },\n handleNativeBack: function handleNativeBack() {\n if (this.$hybrid && typeof this.$hybrid.closePage === 'function') {\n this.$hybrid.closePage();\n } else if (window.history.length > 1) {\n window.history.back();\n }\n },\n handleSidebarBack: function handleSidebarBack() {\n this.handleNativeBack();\n },\n agentjumpclick: function agentjumpclick() {\n var cd = this.$refs.chatDetail;\n\n if (cd && cd.sending) {\n this.$message.warning('AI正在生成内容,暂时请不要切换对话');\n return;\n }\n\n this.currentAgent = '';\n this.currentAgentDesc = '';\n this.activeSessionId = '';\n this.showAgentSelect = true;\n this.showH5List = false;\n this.closeSidebar();\n\n if (this.isMobile) {\n history.pushState({\n page: 'agentSelect'\n }, '');\n }\n },\n goBackToList: function goBackToList() {\n var cd = this.$refs.chatDetail;\n\n if (cd && cd.sending) {\n this.$message.warning('AI正在生成内容,暂时请不要切换对话');\n return;\n }\n\n if (cd) {\n if (cd.eventSource) {\n cd.eventSource.close();\n cd.eventSource = null;\n }\n\n cd.sending = false;\n cd.clearWaitTimer();\n cd.messages = [];\n cd.contentCache = {};\n }\n\n this.activeSessionId = '';\n this.currentAgent = '';\n this.currentAgentDesc = '';\n this.agentInfo = {};\n this.showH5List = true;\n this.showAgentSelect = false;\n this.closeSidebar();\n },\n loadAgents: function loadAgents() {\n var _this3 = this;\n\n (0, _apichat.getChatAgents)().then(function (list) {\n _this3.agentList = list || [];\n }).catch(function () {\n _this3.sessionsLoading = false;\n });\n },\n loadSessions: function loadSessions() {\n var _this4 = this;\n\n this.sessionsLoading = true;\n (0, _apichat.getSessionList)().then(function (list) {\n _this4.sessionsLoading = false;\n\n if (!_this4.activeSessionId) {\n _this4.sessionList = list || [];\n\n if (_this4.sessionList.length > 0) {\n if (_this4.isMobile) {\n _this4.showAgentSelect = false;\n _this4.showH5List = true;\n _this4.showSidebar = false;\n } else {\n _this4.switchSession(_this4.sessionList[0]);\n }\n }\n\n if (_this4.sessionList.length === 0) {\n _this4.showAgentSelect = true;\n _this4.showH5List = false;\n }\n } else {\n _this4.sessionList = list || [];\n\n var _item = list.find(function (res) {\n return res.sessionId == _this4.activeSessionId;\n });\n\n try {\n if (_item && _item.suggestedQuestions) {\n _item.suggestedQuestions = JSON.parse(_item.suggestedQuestions);\n }\n\n _item && (_this4.agentInfo = _item);\n } catch (err) {\n _this4.agentInfo.questionArr = [];\n console.warn('快捷提问解析失败', err);\n }\n } // 会话数据就绪后再初始化引导,保证新老用户判定准确\n\n\n _this4.initGuide();\n }).catch(function () {\n _this4.sessionsLoading = false; // 会话加载失败兜底:按新用户流程引导\n\n _this4.initGuide();\n });\n },\n startChatWithAgent: function startChatWithAgent(agent) {\n console.log('选择了助手', agent.agentCode, this.currentAgent);\n\n if (agent.agentCode == this.currentAgent) {\n return;\n }\n\n this.currentAgent = agent.agentCode;\n this.currentAgentDesc = agent.description || '';\n this.agentInfo = {};\n this.showAgentSelect = false;\n this.closeSidebar();\n\n if (this.isMobile) {\n this.showH5List = false;\n }\n\n var existing = this.sessionList.find(function (s) {\n return s.agentCode === agent.agentCode;\n });\n\n if (existing) {\n this.switchSession(existing);\n } else {\n if (this.isMobile) {\n history.pushState({\n page: 'chatWelcome'\n }, '');\n }\n\n this.handleNewSession();\n }\n },\n handleNewSession: function handleNewSession() {\n var _this5 = this;\n\n if (!this.currentAgent) return;\n if (this.activeSessionId) this.closeOldSession();\n (0, _apichat.startSession)(this.currentAgent).then(function (res) {\n _this5.activeSessionId = res.sessionId || '';\n\n if (_this5.isMobile) {\n history.replaceState({\n page: 'chatDetail'\n }, '');\n }\n\n _this5.loadSessions();\n\n var agent = _this5.agentList.find(function (a) {\n return a.agentCode === _this5.currentAgent;\n });\n\n if (agent) _this5.addWelcomeMessage(agent);\n }).catch(function () {});\n },\n addWelcomeMessage: function addWelcomeMessage(agent) {\n var _this6 = this;\n\n var cd = this.$refs.chatDetail;\n\n if (cd) {\n var _agent = this.agentList.find(function (x) {\n return x.agentCode === _this6.currentAgent;\n });\n\n cd.greeting = _agent ? _agent.greeting || '很高兴为您服务,请问有什么可以帮到您?' : '';\n }\n },\n switchSession: function switchSession(s) {\n var _this7 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {\n var cd, agent, res, list, asAsc, _agent;\n\n return regeneratorRuntime.wrap(function _callee$(_context) {\n while (1) {\n switch (_context.prev = _context.next) {\n case 0:\n cd = _this7.$refs.chatDetail;\n\n if (!(cd && cd.sending)) {\n _context.next = 4;\n break;\n }\n\n _this7.$message.warning('AI正在生成内容,暂时请不要切换对话');\n\n return _context.abrupt(\"return\");\n\n case 4:\n _this7.showAgentSelect = false;\n\n _this7.closeSidebar();\n\n if (_this7.isMobile) {\n _this7.showH5List = false; // 推入历史状态,拦截 Android 系统返回手势\n // 系统返回会触发 WebView 历史回退 → popstate → 我们拦截后 goBackToList\n\n history.pushState({\n page: 'chatDetail'\n }, '');\n }\n\n if (!(_this7.activeSessionId === s.sessionId)) {\n _context.next = 9;\n break;\n }\n\n return _context.abrupt(\"return\");\n\n case 9:\n if (cd) {\n _context.next = 13;\n break;\n }\n\n _context.next = 12;\n return _this7.$nextTick();\n\n case 12:\n cd = _this7.$refs.chatDetail;\n\n case 13:\n if (cd) {\n cd.contentCache = {};\n cd.messages = [];\n cd.msgPage = 1;\n cd.msgTotal = 0;\n cd.loadingMore = false;\n cd.loadingSessionMsg = true;\n }\n\n _this7.activeSessionId = s.sessionId;\n _this7.currentAgent = s.agentCode;\n _this7.agentInfo = s;\n\n try {\n if (s.suggestedQuestions) {\n _this7.agentInfo.suggestedQuestions = JSON.parse(s.suggestedQuestions);\n } else {\n _this7.agentInfo.suggestedQuestions = [];\n }\n } catch (err) {\n _this7.agentInfo.questionArr = [];\n console.warn('快捷提问解析失败', err);\n }\n\n agent = _this7.agentList.find(function (a) {\n return a.agentCode === s.agentCode;\n });\n _this7.currentAgentDesc = agent ? agent.description || '' : '';\n (0, _apichat.syncToken)((0, _cookie.getToken)(), s.sessionId);\n _context.prev = 21;\n _context.next = 24;\n return (0, _apichat.getSessionMessages)(s.sessionId, 1, 10);\n\n case 24:\n res = _context.sent;\n list = Array.isArray(res) ? res : res.list || [];\n\n if (!cd) {\n _context.next = 36;\n break;\n }\n\n cd.msgTotal = res.total || list.length;\n cd.msgPage = 1;\n asAsc = (list || []).slice().reverse();\n cd.messages = asAsc.map(function (m) {\n return {\n id: m.id,\n role: m.role,\n content: m.content || '',\n thinking: m.thinking || '',\n toolCalls: [],\n agentName: m.role === 'assistant' ? s.agentName || '' : '',\n createTime: m.createTime || null,\n _key: m.id != null ? 'msg_' + m.id : 'msg_' + Date.now() + '_' + Math.random(),\n msgKey: m.msgKey,\n formSubmitted: m.formSubmitted || 0,\n formData: m.formData || null,\n // 历史消息中如果包含 ```tokui 标记或以 TokUI 标签开头,自动启用 TokUI 渲染\n _hasTokui: m.role === 'assistant' && m.content && (/```tokui/.test(m.content) || /^\\s*\\[(card|row|list|table|callout|desc|dv|ft)\\b/.test(m.content.trim()))\n };\n });\n\n if (cd.messages.length === 0 && agent) {\n _agent = _this7.agentList.find(function (x) {\n return x.agentCode === _this7.currentAgent;\n });\n cd.greeting = _agent ? _agent.greeting || '很高兴为您服务,请问有什么可以帮到您?' : '';\n }\n\n cd.loadingSessionMsg = false;\n _context.next = 35;\n return _this7.$nextTick();\n\n case 35:\n _this7.$nextTick(function () {\n cd.scrollToBottomOnce();\n cd.checkScrollBottom();\n });\n\n case 36:\n _context.next = 41;\n break;\n\n case 38:\n _context.prev = 38;\n _context.t0 = _context[\"catch\"](21);\n\n if (cd) {\n cd.messages = [];\n cd.loadingSessionMsg = false;\n }\n\n case 41:\n case \"end\":\n return _context.stop();\n }\n }\n }, _callee, null, [[21, 38]]);\n }))();\n },\n handleDeleteSession: function handleDeleteSession(s) {\n var _this8 = this;\n\n var confirmFn = this.$confirm || _message.showConfirm;\n confirmFn(\"\\u786E\\u8BA4\\u5220\\u9664\\u300C\".concat(s.agentName || s.agentCode, \"\\u300D\\u7684\\u4F1A\\u8BDD\\uFF1F\"), '提示', {\n type: 'warning'\n }).then(function () {\n (0, _apichat.deleteSession)(s.sessionId).then(function () {\n if (_this8.activeSessionId === s.sessionId) {\n _this8.activeSessionId = '';\n _this8.currentAgent = '';\n _this8.currentAgentDesc = '';\n var cd = _this8.$refs.chatDetail;\n\n if (cd) {\n cd.messages = [];\n cd.contentCache = {};\n }\n\n if (_this8.isMobile) {\n _this8.showH5List = false;\n _this8.showAgentSelect = true;\n } else {\n _this8.showAgentSelect = true;\n }\n }\n\n _this8.loadSessions();\n });\n }).catch(function () {});\n },\n closeOldSession: function closeOldSession() {},\n // ── 重新播放引导(欢迎页用户点击 ? 按钮触发) ──\n replayGuide: function replayGuide() {\n var _this9 = this;\n\n if (this.showGuide) return;\n\n if (this.isMobile) {\n // 与 initGuide 相同的判定逻辑(见 initGuide 注释)\n var isNewUser = !this.sessionList.some(function (s) {\n return s.lastContent;\n });\n this.guideSteps = (0, _guideMobileConfig.generateMobileGuideSteps)({\n isNewUser: isNewUser\n });\n this.isMobileGuide = true;\n this.showAgentSelect = isNewUser;\n this.showH5List = !isNewUser;\n this.$nextTick(function () {\n setTimeout(function () {\n _this9.showGuide = true;\n }, 400);\n });\n } else {\n var _isNewUser = !this.sessionList.some(function (s) {\n return s.lastContent;\n });\n\n this.guideSteps = (0, _guideConfig.generateGuideSteps)({\n isMobile: false,\n isNewUser: _isNewUser\n });\n this.isMobileGuide = false;\n this.showGuide = true;\n }\n },\n // ── 引导动画初始化(需会话数据就绪后调用,以判定新老用户) ──\n initGuide: function initGuide() {\n var _this10 = this;\n\n if (this._guideInited) return;\n this._guideInited = true;\n\n if (this.isMobile) {\n // 以会话中是否已有聊天记录(lastContent)区分新老用户,\n // 不能用 sessionList.length:后端可能为新用户预建空会话。\n // 新用户(无记录)落在智能体选择页(欢迎页),引导猜你想问/停止按钮;\n // 老用户(有记录)落在列表页,仅引导进入会话与输入框\n var isNewUser = !this.sessionList.some(function (s) {\n return s.lastContent;\n });\n this.guideSteps = (0, _guideMobileConfig.generateMobileGuideSteps)({\n isNewUser: isNewUser\n });\n\n if ((0, _guideHelper.shouldShowMobileGuide)() && this.guideSteps.length > 0) {\n this.isMobileGuide = true;\n this.showAgentSelect = isNewUser;\n this.showH5List = !isNewUser;\n this.$nextTick(function () {\n setTimeout(function () {\n _this10.showGuide = true;\n }, 800);\n });\n }\n } else {\n // PC 端:同样以有无聊天记录区分新老用户。\n // 会话由引导中\"选择 Agent\"步骤点击穿透创建,此处不提前创建\n var _isNewUser2 = !this.sessionList.some(function (s) {\n return s.lastContent;\n });\n\n this.guideSteps = (0, _guideConfig.generateGuideSteps)({\n isMobile: false,\n isNewUser: _isNewUser2\n });\n\n if ((0, _guideHelper.shouldShowGuide)() && this.guideSteps.length > 0) {\n this.isMobileGuide = false;\n setTimeout(function () {\n _this10.showGuide = true;\n }, 600);\n }\n }\n },\n // ── 移动端跨页面导航 ──\n navigateToGuidePage: function navigateToGuidePage(page) {\n var _this11 = this;\n\n return new Promise(function (resolve) {\n switch (page) {\n case 'list':\n // 跳转到列表页\n _this11.showAgentSelect = false;\n _this11.showH5List = true;\n _this11.activeSessionId = '';\n break;\n\n case 'agentSelect':\n // 跳转到智能体选择页\n _this11.showAgentSelect = true;\n _this11.showH5List = false;\n break;\n\n case 'detail':\n // 跳转到详情页:老用户选中第一个会话;\n // 新用户选中第一个 Agent 发起真实会话,\n // 详情页才能加载出欢迎语与\"猜你想问\"等数据供后续步骤高亮\n _this11.showH5List = false;\n _this11.showAgentSelect = false;\n\n if (!_this11.activeSessionId) {\n if (_this11.sessionList.length > 0) {\n _this11.switchSession(_this11.sessionList[0]);\n } else if (_this11.agentList.length > 0) {\n _this11.startChatWithAgent(_this11.agentList[0]);\n }\n }\n\n break;\n\n default:\n break;\n } // 等待 DOM 更新\n\n\n _this11.$nextTick(function () {\n // 额外等待渲染完成\n setTimeout(resolve, 400);\n });\n });\n },\n // ── 引导步骤变化(包含跨页面切换) ──\n onGuideStepChange: function onGuideStepChange(index) {\n var _this12 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() {\n var step, currentPage, isNewUser;\n return regeneratorRuntime.wrap(function _callee2$(_context2) {\n while (1) {\n switch (_context2.prev = _context2.next) {\n case 0:\n step = _this12.guideSteps[index];\n\n if (step) {\n _context2.next = 3;\n break;\n }\n\n return _context2.abrupt(\"return\");\n\n case 3:\n if (!(_this12.isMobileGuide && step.page)) {\n _context2.next = 10;\n break;\n }\n\n currentPage = _this12.getCurrentPage();\n\n if (!(currentPage !== step.page)) {\n _context2.next = 10;\n break;\n }\n\n _this12.guidePageTransitioning = true;\n _context2.next = 9;\n return _this12.navigateToGuidePage(step.page);\n\n case 9:\n _this12.guidePageTransitioning = false;\n\n case 10:\n // PC 端:新用户引导时,欢迎步骤 → 选 Agent 步骤需要显示 AgentSelector\n if (!_this12.isMobileGuide && !_this12.isMobile) {\n isNewUser = !_this12.sessionList.some(function (s) {\n return s.lastContent;\n });\n\n if (isNewUser) {\n if (step.targetSelector && step.targetSelector.includes('agent-item')) {\n // 进入\"选择 Agent\"步骤,确保 AgentSelector 显示\n if (!_this12.showAgentSelect) {\n _this12.showAgentSelect = true;\n _this12.showH5List = false;\n }\n } else if (step.targetSelector && step.targetSelector.includes('guess-box')) {\n // 进入\"猜你想问\"步骤,隐藏 AgentSelector,显示 ChatDetail\n if (_this12.showAgentSelect) {\n _this12.showAgentSelect = false;\n }\n }\n }\n }\n\n case 11:\n case \"end\":\n return _context2.stop();\n }\n }\n }, _callee2);\n }))();\n },\n getCurrentPage: function getCurrentPage() {\n if (this.showH5List) return 'list';\n if (this.showAgentSelect) return 'agentSelect';\n return 'detail';\n },\n // ── 引导跳过 ──\n onGuideSkip: function onGuideSkip() {\n if (this.isMobileGuide) {\n (0, _guideHelper.markMobileGuideCompleted)();\n } else {\n (0, _guideHelper.markGuideCompleted)();\n }\n\n this.landAfterMobileGuide();\n },\n // ── 引导完成 ──\n onGuideComplete: function onGuideComplete() {\n if (this.isMobileGuide) {\n (0, _guideHelper.markMobileGuideCompleted)();\n } else {\n (0, _guideHelper.markGuideCompleted)();\n }\n\n this.showGuide = false;\n this.landAfterMobileGuide();\n },\n // ── 引导结束后的落地页:新用户(无聊天记录)停留在无会话的聊天页时\n // 没有可聊对象,带回智能体选择页,让其可以直接挑选 Agent 发起会话 ──\n landAfterMobileGuide: function landAfterMobileGuide() {\n if (!this.isMobileGuide) return;\n var isNewUser = !this.sessionList.some(function (s) {\n return s.lastContent;\n });\n\n if (isNewUser && !this.activeSessionId) {\n this.showAgentSelect = true;\n this.showH5List = false;\n }\n },\n // ── 引导关闭(动画结束后) ──\n handleGuideClosed: function handleGuideClosed() {\n this.showGuide = false;\n this.guidePageTransitioning = false; // 如果用户在引导期间被带到了详情页,但引导已完成,不需要额外操作\n }\n },\n beforeDestroy: function beforeDestroy() {\n window.removeEventListener('resize', this.updateViewportState);\n document.removeEventListener('touchmove', this._onSwipeMove);\n document.removeEventListener('touchend', this._onSwipeEnd);\n window.removeEventListener('popstate', this._onPopState);\n }\n};\nexports.default = _default;\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/components/chatMain/index.vue?./node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
943
|
+
eval("\n\nvar _interopRequireDefault = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/interopRequireDefault */ \"./node_modules/@babel/runtime/helpers/interopRequireDefault.js\").default;\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _asyncToGenerator2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/asyncToGenerator */ \"./node_modules/@babel/runtime/helpers/asyncToGenerator.js\"));\n\nvar _apichat = __webpack_require__(/*! @lib/api/apichat */ \"./src/lib/api/apichat.js\");\n\nvar _cookie = __webpack_require__(/*! @lib/utils/cookie.js */ \"./src/lib/utils/cookie.js\");\n\nvar _message = __webpack_require__(/*! @lib/utils/message.js */ \"./src/lib/utils/message.js\");\n\nvar _guideHelper = __webpack_require__(/*! @lib/utils/guideHelper.js */ \"./src/lib/utils/guideHelper.js\");\n\nvar _guideConfig = __webpack_require__(/*! @lib/components/guide/guideConfig.js */ \"./src/lib/components/guide/guideConfig.js\");\n\nvar _guideMobileConfig = __webpack_require__(/*! @lib/components/guide/guideMobileConfig.js */ \"./src/lib/components/guide/guideMobileConfig.js\");\n\nvar _NewFeatureGuide = _interopRequireDefault(__webpack_require__(/*! @lib/components/guide/NewFeatureGuide.vue */ \"./src/lib/components/guide/NewFeatureGuide.vue\"));\n\nvar _ChatSidebar = _interopRequireDefault(__webpack_require__(/*! ./ChatSidebar.vue */ \"./src/lib/components/chatMain/ChatSidebar.vue\"));\n\nvar _AgentSelector = _interopRequireDefault(__webpack_require__(/*! ./AgentSelector.vue */ \"./src/lib/components/chatMain/AgentSelector.vue\"));\n\nvar _ChatDetail = _interopRequireDefault(__webpack_require__(/*! ./ChatDetail.vue */ \"./src/lib/components/chatMain/ChatDetail.vue\"));\n\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\nvar _default = {\n name: 'ChatMain',\n components: {\n ChatSidebar: _ChatSidebar.default,\n AgentSelector: _AgentSelector.default,\n ChatDetail: _ChatDetail.default,\n NewFeatureGuide: _NewFeatureGuide.default\n },\n props: {\n Ak: {\n type: String,\n default: ''\n },\n Sk: {\n type: String,\n default: ''\n },\n imAccount: {\n type: String,\n default: ''\n },\n companyNo: {\n type: String,\n default: ''\n },\n isIntelShow: {\n default: false,\n type: Boolean\n },\n chatImToken: {\n default: '',\n type: String\n },\n userno: {\n default: '',\n type: String\n },\n isPlugin: {\n default: false,\n type: Boolean\n }\n },\n data: function data() {\n // 首次渲染前检测窗口宽度,避免 H5 端先显示 PC 布局再切换导致闪烁\n var _isMobile = typeof window !== 'undefined' && window.innerWidth <= 768;\n\n return {\n agentList: [],\n currentAgent: '',\n currentAgentDesc: '',\n sessionsLoaded: false,\n sessionList: [],\n activeSessionId: '',\n loadingMore: false,\n showAgentSelect: !_isMobile,\n isMobile: _isMobile,\n showSidebar: false,\n showH5List: _isMobile,\n agentInfo: {},\n nickName: '',\n sessionsLoading: true,\n // 引导动画状态(PC & 移动端共用)\n showGuide: false,\n guideSteps: [],\n isGuideFirstStep: true,\n isGuideLastStep: false,\n // 移动端跨页面引导专用状态\n isMobileGuide: false,\n guidePageTransitioning: false,\n _swipeStartX: 0,\n _swipeStartY: 0,\n _swipeHandled: false\n };\n },\n created: function created() {\n this.getCurrentUserInfo();\n\n if (this.Ak || this.Sk) {\n (0, _apichat.setApiAuth)(this.Ak, this.Sk, this.imAccount, this.companyNo, this.chatImToken, this.userno);\n }\n },\n mounted: function mounted() {\n this.updateViewportState();\n window.addEventListener('resize', this.updateViewportState, {\n passive: true\n }); // Android WebView 兼容:在 document 级别监听 touchmove/touchend\n // 避免可滚动子元素消费事件后不冒泡到父级\n\n document.addEventListener('touchmove', this._onSwipeMove, {\n passive: true\n });\n document.addEventListener('touchend', this._onSwipeEnd, {\n passive: true\n }); // 拦截 Android 系统返回手势:通过 history.pushState + popstate\n\n window.addEventListener('popstate', this._onPopState); // 引导动画在 loadSessions 完成后初始化(见 loadSessions),\n // 以便依据会话数据判定新老用户,从正确的页面开始引导\n },\n watch: {},\n methods: {\n getCurrentUserInfo: function getCurrentUserInfo() {\n var _this = this;\n\n (0, _apichat.getCurrentUserInfo)().then(function (data) {\n _this.nickName = data && data.nickName || ''; // 缓存 userinfo 接口数据到全局,供 TokUI 各表单组装统一读取登录用户名/头像\n // (tokui-validator-bridge 的 getLoginUserInfo 优先取 __tokuiUserInfo)\n\n if (typeof window !== 'undefined' && data) {\n window.__tokuiUserInfo = data;\n } // 单独用 getCurrentUserInfo 返回的 no 注入 userno,不重复调用 setApiAuth\n\n\n var no = data && (data.no !== undefined && data.no !== null ? data.no : data.userNo);\n\n if (no !== undefined && no !== null && no !== '') {\n (0, _apichat.setApiUserNo)(String(no));\n }\n\n _this.loadAgents();\n\n _this.loadSessions();\n }).catch(function () {});\n },\n handleFormSubmit: function handleFormSubmit(payload) {\n if (!payload || !payload.sessionId || !payload.msgKey) return;\n (0, _apichat.markFormSubmitted)(payload.sessionId, payload.msgKey, payload.formData).catch(function () {});\n },\n // ── H5 侧滑手势 ──\n onSwipeTouchStart: function onSwipeTouchStart(e) {\n if (!this.isMobile) return;\n var t = e.touches[0];\n this._swipeStartX = t.clientX;\n this._swipeStartY = t.clientY;\n this._swipeHandled = false; // 仅当从屏幕左/右边缘区域(约屏幕宽度的 9%,最大 56px)起手才算返回上一级手势,\n // 避免页面内部正常的横向滑动/滚动被误判成返回首页\n\n var edge = Math.min(56, Math.round(window.innerWidth * 0.09));\n this._swipeFromLeftEdge = t.clientX <= edge;\n this._swipeFromRightEdge = t.clientX >= window.innerWidth - edge;\n this._swipeFromEdge = this._swipeFromLeftEdge || this._swipeFromRightEdge;\n },\n // 在 touchmove 中实时检测\n // 从左边缘起手向右滑、或从右边缘起手向左滑才触发返回;页面内部滑动不受影响\n _swipeShouldTrigger: function _swipeShouldTrigger(diffX, diffY) {\n if (!this._swipeFromEdge) return false; // 从左边缘 → 右滑;从右边缘 → 左滑\n\n var directionOk = this._swipeFromLeftEdge && diffX >= 60 || this._swipeFromRightEdge && diffX <= -60;\n if (!directionOk) return false; // 水平距离明显大于垂直距离(排除上下滚动干扰)\n\n return Math.abs(diffX) >= Math.abs(diffY) * 2;\n },\n _onSwipeMove: function _onSwipeMove(e) {\n if (!this.isMobile || this._swipeHandled) return;\n var moveX = e.touches[0].clientX;\n var moveY = e.touches[0].clientY;\n var diffX = moveX - this._swipeStartX;\n var diffY = moveY - this._swipeStartY;\n if (!this._swipeShouldTrigger(diffX, diffY)) return;\n this._swipeHandled = true;\n\n this._triggerSwipeAction();\n },\n // touchend 检测(兜底,兼容不支持 touchmove 的场景)\n _onSwipeEnd: function _onSwipeEnd(e) {\n if (!this.isMobile || this._swipeHandled) return;\n var endX = e.changedTouches[0].clientX;\n var endY = e.changedTouches[0].clientY;\n var diffX = endX - this._swipeStartX;\n var diffY = endY - this._swipeStartY;\n if (!this._swipeShouldTrigger(diffX, diffY)) return;\n this._swipeHandled = true;\n\n this._triggerSwipeAction();\n },\n _triggerSwipeAction: function _triggerSwipeAction() {\n if (this.showH5List) {\n // 列表页 → 原生返回\n this.handleNativeBack();\n } else if (this.showAgentSelect) {\n // 智能体选择页 → 返回列表\n this.goBackToList();\n } else if (!this.showAgentSelect) {\n // 详情页/欢迎页 → 返回列表\n this.goBackToList();\n }\n },\n // 拦截 Android 系统返回手势(通过 history.pushState + popstate)\n // 当用户在详情页触发系统返回时,WebView 先回退历史栈触发 popstate,\n // 我们在这里拦截并导航到列表页,避免 Activity 被直接关闭\n _onPopState: function _onPopState() {\n if (!this.isMobile) return;\n\n if (this.showAgentSelect) {\n // 智能体选择页 → 返回列表\n this.goBackToList();\n history.pushState({\n page: 'chatList'\n }, '');\n } else if (!this.showH5List) {\n // 详情页/欢迎页 → 返回列表\n this.goBackToList();\n history.pushState({\n page: 'chatList'\n }, '');\n } else {\n // 列表页 → 直接关闭 WebView\n this.handleNativeBack();\n }\n },\n handleNewSessionInternal: function handleNewSessionInternal() {\n var _this2 = this;\n\n if (!this.currentAgent) return;\n if (this.activeSessionId) this.closeOldSession();\n (0, _apichat.startSession)(this.currentAgent).then(function (res) {\n _this2.activeSessionId = res.sessionId || '';\n\n _this2.loadSessions();\n\n var agent = _this2.agentList.find(function (a) {\n return a.agentCode === _this2.currentAgent;\n });\n\n if (agent) _this2.addWelcomeMessage(agent);\n }).catch(function () {\n _this2.sessionsLoading = false;\n });\n },\n updateViewportState: function updateViewportState() {\n if (typeof window === 'undefined') return;\n var nextIsMobile = window.innerWidth <= 768;\n var wasMobile = this.isMobile;\n this.isMobile = nextIsMobile;\n\n if (!nextIsMobile) {\n this.showSidebar = false;\n this.showH5List = false;\n } else if (!wasMobile && nextIsMobile) {\n if (this.sessionList.length > 0 && !this.activeSessionId) {\n this.showH5List = true;\n this.showAgentSelect = false;\n }\n }\n },\n toggleSidebar: function toggleSidebar() {\n if (!this.isMobile) return;\n this.showSidebar = !this.showSidebar;\n },\n closeSidebar: function closeSidebar() {\n this.showSidebar = false;\n },\n handleNativeBack: function handleNativeBack() {\n if (this.$hybrid && typeof this.$hybrid.closePage === 'function') {\n this.$hybrid.closePage();\n } else if (window.history.length > 1) {\n window.history.back();\n }\n },\n handleSidebarBack: function handleSidebarBack() {\n this.handleNativeBack();\n },\n agentjumpclick: function agentjumpclick() {\n var cd = this.$refs.chatDetail;\n\n if (cd && cd.sending) {\n this.$message.warning('AI正在生成内容,暂时请不要切换对话');\n return;\n }\n\n this.currentAgent = '';\n this.currentAgentDesc = '';\n this.activeSessionId = '';\n this.showAgentSelect = true;\n this.showH5List = false;\n this.closeSidebar();\n\n if (this.isMobile) {\n history.pushState({\n page: 'agentSelect'\n }, '');\n }\n },\n goBackToList: function goBackToList() {\n var cd = this.$refs.chatDetail;\n\n if (cd && cd.sending) {\n this.$message.warning('AI正在生成内容,暂时请不要切换对话');\n return;\n }\n\n if (cd) {\n if (cd.eventSource) {\n cd.eventSource.close();\n cd.eventSource = null;\n }\n\n cd.sending = false;\n cd.clearWaitTimer();\n cd.messages = [];\n cd.contentCache = {};\n }\n\n this.activeSessionId = '';\n this.currentAgent = '';\n this.currentAgentDesc = '';\n this.agentInfo = {};\n this.showH5List = true;\n this.showAgentSelect = false;\n this.closeSidebar();\n },\n loadAgents: function loadAgents() {\n var _this3 = this;\n\n (0, _apichat.getChatAgents)().then(function (list) {\n _this3.agentList = list || [];\n }).catch(function () {\n _this3.sessionsLoading = false;\n });\n },\n loadSessions: function loadSessions() {\n var _this4 = this;\n\n this.sessionsLoading = true;\n (0, _apichat.getSessionList)().then(function (list) {\n _this4.sessionsLoading = false;\n\n if (!_this4.activeSessionId) {\n _this4.sessionList = list || [];\n\n if (_this4.sessionList.length > 0) {\n if (_this4.isMobile) {\n _this4.showAgentSelect = false;\n _this4.showH5List = true;\n _this4.showSidebar = false;\n } else {\n _this4.switchSession(_this4.sessionList[0]);\n }\n }\n\n if (_this4.sessionList.length === 0) {\n _this4.showAgentSelect = true;\n _this4.showH5List = false;\n }\n } else {\n _this4.sessionList = list || [];\n\n var _item = list.find(function (res) {\n return res.sessionId == _this4.activeSessionId;\n });\n\n try {\n if (_item && _item.suggestedQuestions) {\n _item.suggestedQuestions = JSON.parse(_item.suggestedQuestions);\n }\n\n _item && (_this4.agentInfo = _item);\n } catch (err) {\n _this4.agentInfo.questionArr = [];\n console.warn('快捷提问解析失败', err);\n }\n } // 会话数据就绪后再初始化引导,保证新老用户判定准确\n\n\n _this4.initGuide();\n }).catch(function () {\n _this4.sessionsLoading = false; // 会话加载失败兜底:按新用户流程引导\n\n _this4.initGuide();\n });\n },\n startChatWithAgent: function startChatWithAgent(agent) {\n console.log('选择了助手', agent.agentCode, this.currentAgent);\n\n if (agent.agentCode == this.currentAgent) {\n return;\n }\n\n this.currentAgent = agent.agentCode;\n this.currentAgentDesc = agent.description || '';\n this.agentInfo = {};\n this.showAgentSelect = false;\n this.closeSidebar();\n\n if (this.isMobile) {\n this.showH5List = false;\n }\n\n var existing = this.sessionList.find(function (s) {\n return s.agentCode === agent.agentCode;\n });\n\n if (existing) {\n this.switchSession(existing);\n } else {\n if (this.isMobile) {\n history.pushState({\n page: 'chatWelcome'\n }, '');\n }\n\n this.handleNewSession();\n }\n },\n handleNewSession: function handleNewSession() {\n var _this5 = this;\n\n if (!this.currentAgent) return;\n if (this.activeSessionId) this.closeOldSession();\n (0, _apichat.startSession)(this.currentAgent).then(function (res) {\n _this5.activeSessionId = res.sessionId || '';\n\n if (_this5.isMobile) {\n history.replaceState({\n page: 'chatDetail'\n }, '');\n }\n\n _this5.loadSessions();\n\n var agent = _this5.agentList.find(function (a) {\n return a.agentCode === _this5.currentAgent;\n });\n\n if (agent) _this5.addWelcomeMessage(agent);\n }).catch(function () {});\n },\n addWelcomeMessage: function addWelcomeMessage(agent) {\n var _this6 = this;\n\n var cd = this.$refs.chatDetail;\n\n if (cd) {\n var _agent = this.agentList.find(function (x) {\n return x.agentCode === _this6.currentAgent;\n });\n\n cd.greeting = _agent ? _agent.greeting || '很高兴为您服务,请问有什么可以帮到您?' : '';\n }\n },\n switchSession: function switchSession(s) {\n var _this7 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {\n var cd, agent, res, list, asAsc, _agent;\n\n return regeneratorRuntime.wrap(function _callee$(_context) {\n while (1) {\n switch (_context.prev = _context.next) {\n case 0:\n cd = _this7.$refs.chatDetail;\n\n if (!(cd && cd.sending)) {\n _context.next = 4;\n break;\n }\n\n _this7.$message.warning('AI正在生成内容,暂时请不要切换对话');\n\n return _context.abrupt(\"return\");\n\n case 4:\n _this7.showAgentSelect = false;\n\n _this7.closeSidebar();\n\n if (_this7.isMobile) {\n _this7.showH5List = false; // 推入历史状态,拦截 Android 系统返回手势\n // 系统返回会触发 WebView 历史回退 → popstate → 我们拦截后 goBackToList\n\n history.pushState({\n page: 'chatDetail'\n }, '');\n }\n\n if (!(_this7.activeSessionId === s.sessionId)) {\n _context.next = 9;\n break;\n }\n\n return _context.abrupt(\"return\");\n\n case 9:\n if (cd) {\n _context.next = 13;\n break;\n }\n\n _context.next = 12;\n return _this7.$nextTick();\n\n case 12:\n cd = _this7.$refs.chatDetail;\n\n case 13:\n if (cd) {\n cd.contentCache = {};\n cd.messages = [];\n cd.msgPage = 1;\n cd.msgTotal = 0;\n cd.loadingMore = false;\n cd.loadingSessionMsg = true;\n }\n\n _this7.activeSessionId = s.sessionId;\n _this7.currentAgent = s.agentCode;\n _this7.agentInfo = s;\n\n try {\n if (s.suggestedQuestions) {\n _this7.agentInfo.suggestedQuestions = JSON.parse(s.suggestedQuestions);\n } else {\n _this7.agentInfo.suggestedQuestions = [];\n }\n } catch (err) {\n _this7.agentInfo.questionArr = [];\n console.warn('快捷提问解析失败', err);\n }\n\n agent = _this7.agentList.find(function (a) {\n return a.agentCode === s.agentCode;\n });\n _this7.currentAgentDesc = agent ? agent.description || '' : '';\n (0, _apichat.syncToken)((0, _cookie.getToken)(), s.sessionId);\n _context.prev = 21;\n _context.next = 24;\n return (0, _apichat.getSessionMessages)(s.sessionId, 1, 10);\n\n case 24:\n res = _context.sent;\n list = Array.isArray(res) ? res : res.list || [];\n\n if (!cd) {\n _context.next = 36;\n break;\n }\n\n cd.msgTotal = res.total || list.length;\n cd.msgPage = 1;\n asAsc = (list || []).slice().reverse();\n cd.messages = asAsc.map(function (m) {\n return {\n id: m.id,\n role: m.role,\n content: m.content || '',\n thinking: m.thinking || '',\n toolCalls: [],\n agentName: m.role === 'assistant' ? s.agentName || '' : '',\n createTime: m.createTime || null,\n _key: m.id != null ? 'msg_' + m.id : 'msg_' + Date.now() + '_' + Math.random(),\n msgKey: m.msgKey,\n formSubmitted: m.formSubmitted || 0,\n formData: m.formData || null,\n // 历史消息中如果包含 ```tokui 标记或以 TokUI 标签开头,自动启用 TokUI 渲染\n _hasTokui: m.role === 'assistant' && m.content && (/```tokui/.test(m.content) || /^\\s*\\[(card|row|list|table|callout|desc|dv|ft)\\b/.test(m.content.trim()))\n };\n });\n\n if (cd.messages.length === 0 && agent) {\n _agent = _this7.agentList.find(function (x) {\n return x.agentCode === _this7.currentAgent;\n });\n cd.greeting = _agent ? _agent.greeting || '很高兴为您服务,请问有什么可以帮到您?' : '';\n }\n\n cd.loadingSessionMsg = false;\n _context.next = 35;\n return _this7.$nextTick();\n\n case 35:\n _this7.$nextTick(function () {\n cd.scrollToBottomOnce();\n cd.checkScrollBottom();\n });\n\n case 36:\n _context.next = 41;\n break;\n\n case 38:\n _context.prev = 38;\n _context.t0 = _context[\"catch\"](21);\n\n if (cd) {\n cd.messages = [];\n cd.loadingSessionMsg = false;\n }\n\n case 41:\n case \"end\":\n return _context.stop();\n }\n }\n }, _callee, null, [[21, 38]]);\n }))();\n },\n handleDeleteSession: function handleDeleteSession(s) {\n var _this8 = this;\n\n var confirmFn = this.$confirm || _message.showConfirm;\n confirmFn(\"\\u786E\\u8BA4\\u5220\\u9664\\u300C\".concat(s.agentName || s.agentCode, \"\\u300D\\u7684\\u4F1A\\u8BDD\\uFF1F\"), '提示', {\n type: 'warning'\n }).then(function () {\n (0, _apichat.deleteSession)(s.sessionId).then(function () {\n if (_this8.activeSessionId === s.sessionId) {\n _this8.activeSessionId = '';\n _this8.currentAgent = '';\n _this8.currentAgentDesc = '';\n var cd = _this8.$refs.chatDetail;\n\n if (cd) {\n cd.messages = [];\n cd.contentCache = {};\n }\n\n if (_this8.isMobile) {\n _this8.showH5List = false;\n _this8.showAgentSelect = true;\n } else {\n _this8.showAgentSelect = true;\n }\n }\n\n _this8.loadSessions();\n });\n }).catch(function () {});\n },\n closeOldSession: function closeOldSession() {},\n // ── 重新播放引导(欢迎页用户点击 ? 按钮触发) ──\n replayGuide: function replayGuide() {\n var _this9 = this;\n\n if (this.showGuide) return;\n\n if (this.isMobile) {\n // 与 initGuide 相同的判定逻辑(见 initGuide 注释)\n var isNewUser = !this.sessionList.some(function (s) {\n return s.lastContent;\n });\n this.guideSteps = (0, _guideMobileConfig.generateMobileGuideSteps)({\n isNewUser: isNewUser\n });\n this.isMobileGuide = true;\n this.showAgentSelect = isNewUser;\n this.showH5List = !isNewUser;\n this.$nextTick(function () {\n setTimeout(function () {\n _this9.showGuide = true;\n }, 400);\n });\n } else {\n var _isNewUser = !this.sessionList.some(function (s) {\n return s.lastContent;\n });\n\n this.guideSteps = (0, _guideConfig.generateGuideSteps)({\n isMobile: false,\n isNewUser: _isNewUser\n });\n this.isMobileGuide = false;\n this.showGuide = true;\n }\n },\n // ── 引导动画初始化(需会话数据就绪后调用,以判定新老用户) ──\n initGuide: function initGuide() {\n var _this10 = this;\n\n if (this._guideInited) return;\n this._guideInited = true;\n\n if (this.isMobile) {\n // 以会话中是否已有聊天记录(lastContent)区分新老用户,\n // 不能用 sessionList.length:后端可能为新用户预建空会话。\n // 新用户(无记录)落在智能体选择页(欢迎页),引导猜你想问/停止按钮;\n // 老用户(有记录)落在列表页,仅引导进入会话与输入框\n var isNewUser = !this.sessionList.some(function (s) {\n return s.lastContent;\n });\n this.guideSteps = (0, _guideMobileConfig.generateMobileGuideSteps)({\n isNewUser: isNewUser\n });\n\n if ((0, _guideHelper.shouldShowMobileGuide)() && this.guideSteps.length > 0) {\n this.isMobileGuide = true;\n this.showAgentSelect = isNewUser;\n this.showH5List = !isNewUser;\n this.$nextTick(function () {\n setTimeout(function () {\n _this10.showGuide = true;\n }, 800);\n });\n }\n } else {\n // PC 端:同样以有无聊天记录区分新老用户。\n // 会话由引导中\"选择 Agent\"步骤点击穿透创建,此处不提前创建\n var _isNewUser2 = !this.sessionList.some(function (s) {\n return s.lastContent;\n });\n\n this.guideSteps = (0, _guideConfig.generateGuideSteps)({\n isMobile: false,\n isNewUser: _isNewUser2\n });\n\n if ((0, _guideHelper.shouldShowGuide)() && this.guideSteps.length > 0) {\n this.isMobileGuide = false;\n setTimeout(function () {\n _this10.showGuide = true;\n }, 600);\n }\n }\n },\n // ── 移动端跨页面导航 ──\n navigateToGuidePage: function navigateToGuidePage(page) {\n var _this11 = this;\n\n return new Promise(function (resolve) {\n switch (page) {\n case 'list':\n // 跳转到列表页\n _this11.showAgentSelect = false;\n _this11.showH5List = true;\n _this11.activeSessionId = '';\n break;\n\n case 'agentSelect':\n // 跳转到智能体选择页\n _this11.showAgentSelect = true;\n _this11.showH5List = false;\n break;\n\n case 'detail':\n // 跳转到详情页:老用户选中第一个会话;\n // 新用户选中第一个 Agent 发起真实会话,\n // 详情页才能加载出欢迎语与\"猜你想问\"等数据供后续步骤高亮\n _this11.showH5List = false;\n _this11.showAgentSelect = false;\n\n if (!_this11.activeSessionId) {\n if (_this11.sessionList.length > 0) {\n _this11.switchSession(_this11.sessionList[0]);\n } else if (_this11.agentList.length > 0) {\n _this11.startChatWithAgent(_this11.agentList[0]);\n }\n }\n\n break;\n\n default:\n break;\n } // 等待 DOM 更新\n\n\n _this11.$nextTick(function () {\n // 额外等待渲染完成\n setTimeout(resolve, 400);\n });\n });\n },\n // ── 引导步骤变化(包含跨页面切换) ──\n onGuideStepChange: function onGuideStepChange(index) {\n var _this12 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() {\n var step, currentPage, isNewUser;\n return regeneratorRuntime.wrap(function _callee2$(_context2) {\n while (1) {\n switch (_context2.prev = _context2.next) {\n case 0:\n step = _this12.guideSteps[index];\n\n if (step) {\n _context2.next = 3;\n break;\n }\n\n return _context2.abrupt(\"return\");\n\n case 3:\n if (!(_this12.isMobileGuide && step.page)) {\n _context2.next = 10;\n break;\n }\n\n currentPage = _this12.getCurrentPage();\n\n if (!(currentPage !== step.page)) {\n _context2.next = 10;\n break;\n }\n\n _this12.guidePageTransitioning = true;\n _context2.next = 9;\n return _this12.navigateToGuidePage(step.page);\n\n case 9:\n _this12.guidePageTransitioning = false;\n\n case 10:\n // PC 端:新用户引导时,欢迎步骤 → 选 Agent 步骤需要显示 AgentSelector\n if (!_this12.isMobileGuide && !_this12.isMobile) {\n isNewUser = !_this12.sessionList.some(function (s) {\n return s.lastContent;\n });\n\n if (isNewUser) {\n if (step.targetSelector && step.targetSelector.includes('agent-item')) {\n // 进入\"选择 Agent\"步骤,确保 AgentSelector 显示\n if (!_this12.showAgentSelect) {\n _this12.showAgentSelect = true;\n _this12.showH5List = false;\n }\n } else if (step.targetSelector && step.targetSelector.includes('guess-box')) {\n // 进入\"猜你想问\"步骤,隐藏 AgentSelector,显示 ChatDetail\n if (_this12.showAgentSelect) {\n _this12.showAgentSelect = false;\n }\n }\n }\n }\n\n case 11:\n case \"end\":\n return _context2.stop();\n }\n }\n }, _callee2);\n }))();\n },\n getCurrentPage: function getCurrentPage() {\n if (this.showH5List) return 'list';\n if (this.showAgentSelect) return 'agentSelect';\n return 'detail';\n },\n // ── 引导跳过 ──\n onGuideSkip: function onGuideSkip() {\n if (this.isMobileGuide) {\n (0, _guideHelper.markMobileGuideCompleted)();\n } else {\n (0, _guideHelper.markGuideCompleted)();\n }\n\n this.landAfterMobileGuide();\n },\n // ── 引导完成 ──\n onGuideComplete: function onGuideComplete() {\n if (this.isMobileGuide) {\n (0, _guideHelper.markMobileGuideCompleted)();\n } else {\n (0, _guideHelper.markGuideCompleted)();\n }\n\n this.showGuide = false;\n this.landAfterMobileGuide();\n },\n // ── 引导结束后的落地页:新用户(无聊天记录)停留在无会话的聊天页时\n // 没有可聊对象,带回智能体选择页,让其可以直接挑选 Agent 发起会话 ──\n landAfterMobileGuide: function landAfterMobileGuide() {\n if (!this.isMobileGuide) return;\n var isNewUser = !this.sessionList.some(function (s) {\n return s.lastContent;\n });\n\n if (isNewUser && !this.activeSessionId) {\n this.showAgentSelect = true;\n this.showH5List = false;\n }\n },\n // ── 引导关闭(动画结束后) ──\n handleGuideClosed: function handleGuideClosed() {\n this.showGuide = false;\n this.guidePageTransitioning = false; // 如果用户在引导期间被带到了详情页,但引导已完成,不需要额外操作\n }\n },\n beforeDestroy: function beforeDestroy() {\n window.removeEventListener('resize', this.updateViewportState);\n document.removeEventListener('touchmove', this._onSwipeMove);\n document.removeEventListener('touchend', this._onSwipeEnd);\n window.removeEventListener('popstate', this._onPopState);\n }\n};\nexports.default = _default;\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/components/chatMain/index.vue?./node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
944
944
|
|
|
945
945
|
/***/ }),
|
|
946
946
|
|
|
@@ -964,7 +964,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n
|
|
|
964
964
|
/***/ (function(module, exports, __webpack_require__) {
|
|
965
965
|
|
|
966
966
|
"use strict";
|
|
967
|
-
eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _knowledgeBase = __webpack_require__(/*! ./api/knowledgeBase */ \"./src/lib/knowledgeBase/api/knowledgeBase.js\");\n\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\
|
|
967
|
+
eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _knowledgeBase = __webpack_require__(/*! ./api/knowledgeBase */ \"./src/lib/knowledgeBase/api/knowledgeBase.js\");\n\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\nvar _default = {\n name: 'CategoryManager',\n props: {\n companyId: {\n type: String,\n default: ''\n }\n },\n data: function data() {\n return {\n list: [],\n flatList: [],\n treeData: [],\n treeProps: {\n children: 'children',\n label: 'name'\n },\n loading: false,\n dialogVisible: false,\n dialogType: 'add',\n editingId: null,\n submitting: false,\n formData: {\n name: '',\n parentId: 0,\n sortOrder: 0\n },\n formRules: {\n name: [{\n required: true,\n message: '请输入文件夹名称',\n trigger: 'blur'\n }, {\n max: 50,\n message: '长度不超过50个字符',\n trigger: 'blur'\n }]\n }\n };\n },\n computed: {\n /**\r\n * 可选作上级的文件夹列表(过滤掉已达最大层级的,避免无效选择)\r\n */\n availableParents: function availableParents() {\n var _this = this;\n\n return this.list.filter(function (f) {\n return _this.getFolderDepth(f) < 5;\n });\n }\n },\n watch: {\n companyId: function companyId() {\n this.loadList();\n }\n },\n created: function created() {\n if (this.companyId) {\n this.loadList();\n }\n },\n methods: {\n /**\r\n * 加载文件夹列表(平铺 → 组装树)\r\n */\n loadList: function loadList() {\n var _this2 = this;\n\n if (!this.companyId) {\n this.list = [];\n this.flatList = [];\n this.treeData = [];\n this.$emit('list', []);\n return;\n }\n\n this.loading = true;\n (0, _knowledgeBase.getFolderList)(this.companyId).then(function (list) {\n _this2.list = list || [];\n _this2.flatList = _this2.list;\n _this2.treeData = _this2.buildTree(_this2.list);\n\n _this2.$emit('list', _this2.list); // 默认展示全部文件\n\n\n _this2.handleSelectAll();\n }).catch(function () {}).finally(function () {\n _this2.loading = false;\n });\n },\n\n /**\r\n * 平铺列表组装为树结构\r\n */\n buildTree: function buildTree(list) {\n var map = {};\n list.forEach(function (f) {\n f.children = [];\n map[f.id] = f;\n });\n var roots = [];\n list.forEach(function (f) {\n var pid = f.parentId || 0;\n\n if (map[pid]) {\n map[pid].children.push(f);\n } else {\n roots.push(f);\n }\n });\n return roots;\n },\n handleSelect: function handleSelect(data) {\n this.$emit('select', data);\n },\n\n /**\r\n * 显示全部文件:不带 folderId 查询\r\n */\n handleSelectAll: function handleSelectAll() {\n this.$refs.treeRef && this.$refs.treeRef.setCurrentKey(null);\n this.$emit('select', {\n id: '',\n name: '全部文件'\n });\n },\n\n /**\r\n * 计算文件夹深度(第1层=根目录)\r\n */\n getFolderDepth: function getFolderDepth(item) {\n var depth = 1;\n var current = item;\n var visited = new Set();\n\n while (current.parentId && current.parentId !== 0) {\n if (visited.has(current.id)) break; // 防循环引用\n\n visited.add(current.id);\n var parent = this.list.find(function (f) {\n return f.id === current.parentId;\n });\n if (!parent) break;\n depth++;\n current = parent;\n }\n\n return depth;\n },\n handleAdd: function handleAdd(parent) {\n var _this3 = this;\n\n // 检查文件夹深度限制(最多5层)\n if (parent) {\n var depth = this.getFolderDepth(parent);\n\n if (depth >= 5) {\n this.$message.warning('文件夹层级已达到最大限制(5层),无法在此文件夹下继续新增子文件夹');\n return;\n }\n }\n\n this.dialogType = 'add';\n this.editingId = null;\n var defaultParentId = parent ? parent.id : 0;\n this.formData = {\n name: '',\n parentId: defaultParentId,\n sortOrder: 0\n };\n this.dialogVisible = true;\n this.$nextTick(function () {\n if (_this3.$refs.folderForm) _this3.$refs.folderForm.clearValidate();\n });\n },\n handleEdit: function handleEdit(item) {\n var _this4 = this;\n\n this.dialogType = 'edit';\n this.editingId = item.id;\n this.formData = {\n name: item.name,\n parentId: item.parentId || 0,\n sortOrder: item.sortOrder || 0\n };\n this.dialogVisible = true;\n this.$nextTick(function () {\n if (_this4.$refs.folderForm) _this4.$refs.folderForm.clearValidate();\n });\n },\n handleDelete: function handleDelete(item) {\n var _this5 = this;\n\n this.$confirm(\"\\u786E\\u8BA4\\u5220\\u9664\\u6587\\u4EF6\\u5939\\u300C\".concat(item.name, \"\\u300D\\uFF1F\\u5220\\u9664\\u540E\\u65E0\\u6CD5\\u6062\\u590D\\u3002\"), '提示', {\n type: 'warning',\n confirmButtonText: '确定删除',\n cancelButtonText: '取消'\n }).then(function () {\n (0, _knowledgeBase.deleteFolder)(item.id).then(function () {\n _this5.$message.success('删除成功');\n\n _this5.loadList();\n }).catch(function () {});\n }).catch(function () {});\n },\n\n /**\r\n * 提交新增/重命名文件夹\r\n */\n handleSubmit: function handleSubmit() {\n var _this6 = this;\n\n this.$refs.folderForm.validate(function (valid) {\n if (!valid) return; // 新增时检查所选上级文件夹深度(最多5层)\n\n if (_this6.dialogType === 'add') {\n var parentId = _this6.formData.parentId || 0;\n\n if (parentId !== 0) {\n var parent = _this6.list.find(function (f) {\n return f.id === parentId;\n });\n\n if (parent) {\n var depth = _this6.getFolderDepth(parent);\n\n if (depth >= 5) {\n _this6.$message.warning('所选上级文件夹已到达最大层级限制(5层),无法继续新增子文件夹');\n\n return;\n }\n }\n }\n }\n\n _this6.submitting = true;\n\n if (_this6.dialogType === 'add') {\n (0, _knowledgeBase.addFolder)({\n companyId: _this6.companyId,\n parentId: _this6.formData.parentId || 0,\n name: _this6.formData.name,\n sortOrder: _this6.formData.sortOrder,\n status: 1\n }).then(function () {\n _this6.$message.success('新增成功');\n\n _this6.dialogVisible = false;\n\n _this6.loadList();\n }).catch(function () {}).finally(function () {\n _this6.submitting = false;\n });\n } else {\n (0, _knowledgeBase.updateFolder)(_this6.editingId, {\n name: _this6.formData.name,\n sortOrder: _this6.formData.sortOrder\n }).then(function () {\n _this6.$message.success('修改成功');\n\n _this6.dialogVisible = false;\n\n _this6.loadList();\n }).catch(function () {}).finally(function () {\n _this6.submitting = false;\n });\n }\n });\n }\n }\n};\nexports.default = _default;\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/CategoryManager.vue?./node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
968
968
|
|
|
969
969
|
/***/ }),
|
|
970
970
|
|
|
@@ -976,7 +976,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n
|
|
|
976
976
|
/***/ (function(module, exports, __webpack_require__) {
|
|
977
977
|
|
|
978
978
|
"use strict";
|
|
979
|
-
eval("\n\nvar _interopRequireWildcard = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/interopRequireWildcard */ \"./node_modules/@babel/runtime/helpers/interopRequireWildcard.js\").default;\n\nvar _interopRequireDefault = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/interopRequireDefault */ \"./node_modules/@babel/runtime/helpers/interopRequireDefault.js\").default;\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _createForOfIteratorHelper2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/createForOfIteratorHelper */ \"./node_modules/@babel/runtime/helpers/createForOfIteratorHelper.js\"));\n\nvar _asyncToGenerator2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/asyncToGenerator */ \"./node_modules/@babel/runtime/helpers/asyncToGenerator.js\"));\n\nvar _marked = __webpack_require__(/*! marked */ \"./node_modules/marked/lib/marked.esm.js\");\n\nvar XLSX = _interopRequireWildcard(__webpack_require__(/*! xlsx */ \"./node_modules/xlsx/xlsx.mjs\"));\n\nvar _mammoth = _interopRequireDefault(__webpack_require__(/*! mammoth */ \"./node_modules/mammoth/lib/index.js\"));\n\nvar _docx = __webpack_require__(/*! docx */ \"./node_modules/docx/build/index.mjs\");\n\nvar _knowledgeBase = __webpack_require__(/*! ./api/knowledgeBase */ \"./src/lib/knowledgeBase/api/knowledgeBase.js\");\n\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n// 单文件大小上限:50MB\nvar MAX_SIZE = 50 * 1024 * 1024;\nvar ALLOWED_TYPES = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'pdf', 'txt', 'md', 'markdown', 'jpg', 'jpeg', 'png', 'gif', 'webp']; // 支持在线编辑的文本文件类型\n\nvar TEXT_EDITABLE_TYPES = ['txt', 'md', 'markdown', 'json', 'xml', 'yaml', 'yml', 'ini', 'cfg', 'env', 'js', 'ts', 'jsx', 'tsx', 'css', 'scss', 'less', 'html', 'htm', 'vue', 'py', 'java', 'rb', 'go', 'rs', 'sh', 'bat', 'ps1', 'sql', 'php', 'c', 'cpp', 'h', 'hpp', 'swift', 'kt', 'groovy'];\nvar _default2 = {\n name: 'DocumentForm',\n props: {\n companyId: {\n type: String,\n default: ''\n },\n folderList: {\n type: Array,\n default: function _default() {\n return [];\n }\n }\n },\n data: function data() {\n return {\n visible: false,\n isEdit: false,\n editingId: null,\n submitting: false,\n fileList: [],\n formData: {\n title: '',\n folderId: '',\n fileName: ''\n },\n resolvePromise: null,\n editingFileType: '',\n mdTab: 'edit',\n xlsxData: {\n sheetNames: [],\n currentSheet: '',\n rows: [],\n cols: [],\n workbook: null,\n loading: false\n },\n wordHtml: '',\n wordLoading: false\n };\n },\n watch: {\n /**\r\n * wordLoading 变为 false 时,编辑器 DOM 已渲染,将 wordHtml 写入编辑器\r\n */\n wordLoading: function wordLoading(val) {\n var _this = this;\n\n if (!val && this.wordHtml) {\n this.$nextTick(function () {\n var editor = _this.$refs && _this.$refs.wordEditor;\n\n if (editor) {\n editor.innerHTML = _this.wordHtml;\n }\n });\n }\n }\n },\n computed: {\n dialogTitle: function dialogTitle() {\n if (this.isXlsxType) return '编辑表格数据';\n if (this.isWordType) return '编辑 Word 文档';\n if (this.isTextEditable) return '编辑文件内容';\n return '编辑文档';\n },\n dialogWidth: function dialogWidth() {\n if (this.isXlsxType) return '960px';\n if (this.isWordType) return '900px';\n if (this.isTextEditable) return '860px';\n return '560px';\n },\n\n /**\r\n * 当前编辑的文件是否为文本可编辑类型\r\n */\n isTextEditable: function isTextEditable() {\n var t = (this.editingFileType || '').toLowerCase();\n return TEXT_EDITABLE_TYPES.includes(t);\n },\n\n /**\r\n * 当前编辑的文件是否为 Excel 类型\r\n */\n isXlsxType: function isXlsxType() {\n var t = (this.editingFileType || '').toLowerCase();\n return t === 'xlsx' || t === 'xls';\n },\n\n /**\r\n * 当前编辑的文件是否为 Markdown 类型(支持预览)\r\n */\n isMarkdownType: function isMarkdownType() {\n var t = (this.editingFileType || '').toLowerCase();\n return t === 'md' || t === 'markdown';\n },\n\n /**\r\n * 当前编辑的文件是否为 Word 类型\r\n */\n isWordType: function isWordType() {\n var t = (this.editingFileType || '').toLowerCase();\n return t === 'docx' || t === 'doc';\n },\n\n /**\r\n * Word 编辑器字符数\r\n */\n wordCharCount: function wordCharCount() {\n var editor = this.$refs && this.$refs.wordEditor;\n\n if (editor) {\n return (editor.innerText || '').replace(/\\s/g, '').length;\n }\n\n return (this.wordHtml || '').replace(/<[^>]+>/g, '').length;\n },\n\n /**\r\n * Markdown 预览 HTML\r\n */\n previewHtml: function previewHtml() {\n if (!this.formData.content) return '';\n return _marked.marked.parse(this.formData.content, {\n breaks: true,\n gfm: true\n });\n }\n },\n methods: {\n /**\r\n * 打开上传对话框(新增)\r\n * @param {string} folderId - 预选文件夹ID\r\n * @returns {Promise}\r\n */\n openAdd: function openAdd(folderId) {\n var _this2 = this;\n\n this.isEdit = false;\n this.editingId = null;\n this.fileList = [];\n this.formData = {\n title: '',\n folderId: folderId || '',\n fileName: ''\n };\n this.visible = true;\n return new Promise(function (resolve) {\n _this2.resolvePromise = resolve;\n });\n },\n\n /**\r\n * 打开编辑对话框\r\n * @param {object} file - 文件对象\r\n * @returns {Promise}\r\n */\n openEdit: function openEdit(file) {\n var _this3 = this;\n\n this.isEdit = true;\n this.editingId = file.id;\n this.editingFileType = file.fileType || '';\n this.fileList = [];\n this.mdTab = 'edit';\n this.resetXlsxData();\n this.formData = {\n title: file.title || '',\n folderId: file.folderId || '',\n fileName: file.fileName || '',\n content: ''\n };\n var t = (file.fileType || '').toLowerCase(); // 如果是文本可编辑类型,异步加载文件内容\n\n if (TEXT_EDITABLE_TYPES.includes(t)) {\n (0, _knowledgeBase.getFileDetail)(file.id).then(function (d) {\n _this3.formData.content = d.content || '';\n }).catch(function () {\n _this3.formData.content = '(内容加载失败)';\n });\n } // 如果是 Excel 类型,下载原文件并解析\n\n\n if (t === 'xlsx' || t === 'xls') {\n this.loadXlsxFile(file);\n }\n\n this.visible = true; // 如果是 Word 类型,下载原文件并解析(visible 先置为 true 确保 DOM 已渲染)\n\n if (t === 'docx' || t === 'doc') {\n this.loadWordFile(file);\n }\n\n return new Promise(function (resolve) {\n _this3.resolvePromise = resolve;\n });\n },\n // ==================== Excel 编辑 ====================\n resetXlsxData: function resetXlsxData() {\n this.xlsxData = {\n sheetNames: [],\n currentSheet: '',\n rows: [],\n cols: [],\n workbook: null,\n loading: false\n };\n },\n loadXlsxFile: function loadXlsxFile(file) {\n var _this4 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {\n var detail, fileUrl, response, ab, workbook;\n return regeneratorRuntime.wrap(function _callee$(_context) {\n while (1) {\n switch (_context.prev = _context.next) {\n case 0:\n _this4.xlsxData.loading = true;\n _context.prev = 1;\n _context.next = 4;\n return (0, _knowledgeBase.getFileDetail)(file.id);\n\n case 4:\n detail = _context.sent;\n fileUrl = detail.fileUrl || file.fileUrl;\n\n if (fileUrl) {\n _context.next = 9;\n break;\n }\n\n _this4.$message.warning('无法获取文件下载地址');\n\n return _context.abrupt(\"return\");\n\n case 9:\n _context.next = 11;\n return fetch(fileUrl);\n\n case 11:\n response = _context.sent;\n\n if (response.ok) {\n _context.next = 14;\n break;\n }\n\n throw new Error('下载文件失败');\n\n case 14:\n _context.next = 16;\n return response.arrayBuffer();\n\n case 16:\n ab = _context.sent;\n workbook = XLSX.read(ab, {\n type: 'array'\n });\n _this4.xlsxData.workbook = workbook;\n _this4.xlsxData.sheetNames = workbook.SheetNames;\n\n if (workbook.SheetNames.length > 0) {\n _this4.xlsxData.currentSheet = workbook.SheetNames[0];\n\n _this4.buildXlsxSheetData(workbook.SheetNames[0]);\n }\n\n _context.next = 27;\n break;\n\n case 23:\n _context.prev = 23;\n _context.t0 = _context[\"catch\"](1);\n console.error('加载 Excel 文件失败', _context.t0);\n\n _this4.$message.error('加载 Excel 文件失败');\n\n case 27:\n _context.prev = 27;\n _this4.xlsxData.loading = false;\n return _context.finish(27);\n\n case 30:\n case \"end\":\n return _context.stop();\n }\n }\n }, _callee, null, [[1, 23, 27, 30]]);\n }))();\n },\n buildXlsxSheetData: function buildXlsxSheetData(sheetName) {\n var _this5 = this;\n\n var workbook = this.xlsxData.workbook;\n if (!workbook) return;\n var sheet = workbook.Sheets[sheetName];\n\n if (!sheet) {\n this.xlsxData.rows = [];\n this.xlsxData.cols = [];\n return;\n } // 转为二维数组(第一行作表头)\n\n\n var data = XLSX.utils.sheet_to_json(sheet, {\n header: 1\n });\n\n if (data.length === 0) {\n this.xlsxData.rows = [];\n this.xlsxData.cols = [];\n return;\n } // 第一行作为列名\n\n\n this.xlsxData.cols = data[0].map(function (v, i) {\n return v != null ? String(v) : \"\\u5217\".concat(i + 1);\n }); // 剩余行作为数据\n\n this.xlsxData.rows = data.slice(1).map(function (row) {\n var obj = {};\n\n _this5.xlsxData.cols.forEach(function (col, ci) {\n obj[ci] = row[ci] != null ? String(row[ci]) : '';\n });\n\n return obj;\n });\n },\n switchXlsxSheet: function switchXlsxSheet(name) {\n // 先保存当前 sheet 的编辑数据到 workbook\n this.saveCurrentSheetData();\n this.xlsxData.currentSheet = name;\n this.buildXlsxSheetData(name);\n },\n addXlsxRow: function addXlsxRow() {\n var row = {};\n this.xlsxData.cols.forEach(function (col, ci) {\n row[ci] = '';\n });\n this.xlsxData.rows.push(row);\n },\n addXlsxCol: function addXlsxCol() {\n var _this6 = this;\n\n var newColName = \"\\u5217\".concat(this.xlsxData.cols.length + 1);\n this.xlsxData.cols.push(newColName);\n this.xlsxData.rows.forEach(function (row) {\n row[_this6.xlsxData.cols.length - 1] = '';\n });\n },\n deleteXlsxRow: function deleteXlsxRow() {\n if (this.xlsxData.rows.length === 0) return;\n this.xlsxData.rows.pop();\n },\n deleteXlsxCol: function deleteXlsxCol() {\n if (this.xlsxData.cols.length === 0) return;\n this.xlsxData.cols.pop();\n var ci = this.xlsxData.cols.length;\n this.xlsxData.rows.forEach(function (row) {\n delete row[ci];\n });\n },\n removeXlsxRow: function removeXlsxRow(index) {\n this.xlsxData.rows.splice(index, 1);\n },\n onXlsxCellChange: function onXlsxCellChange() {// 数据变更标记(将来可用于 dirty 判断)\n },\n\n /**\r\n * 将当前编辑的 rows/cols 写回 workbook.Sheets[currentSheet]\r\n */\n saveCurrentSheetData: function saveCurrentSheetData() {\n var _this7 = this;\n\n var workbook = this.xlsxData.workbook;\n var sheetName = this.xlsxData.currentSheet;\n if (!workbook || !sheetName || !this.xlsxData.cols.length) return;\n var aoa = [this.xlsxData.cols];\n this.xlsxData.rows.forEach(function (row) {\n var rowData = _this7.xlsxData.cols.map(function (col) {\n return row[_this7.xlsxData.cols.indexOf(col)] || '';\n });\n\n aoa.push(rowData);\n });\n workbook.Sheets[sheetName] = XLSX.utils.aoa_to_sheet(aoa);\n },\n\n /**\r\n * 生成新的 xlsx 文件并上传替换\r\n */\n replaceXlsxFile: function replaceXlsxFile() {\n var _this8 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() {\n var workbook, wbout, blob, originalName, file, formData, newFile;\n return regeneratorRuntime.wrap(function _callee2$(_context2) {\n while (1) {\n switch (_context2.prev = _context2.next) {\n case 0:\n // 先保存当前 sheet 的编辑数据\n _this8.saveCurrentSheetData();\n\n workbook = _this8.xlsxData.workbook; // 写出所有 sheet 到新的 xlsx 文件\n\n wbout = XLSX.write(workbook, {\n bookType: 'xlsx',\n type: 'array'\n });\n blob = new Blob([wbout], {\n type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'\n });\n originalName = _this8.formData.fileName || 'export.xlsx';\n file = new File([blob], originalName, {\n type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'\n }); // 上传新文件\n\n formData = new FormData();\n formData.append('file', file);\n formData.append('companyId', _this8.companyId);\n\n if (_this8.formData.folderId) {\n formData.append('folderId', _this8.formData.folderId);\n }\n\n _context2.next = 12;\n return (0, _knowledgeBase.uploadFile)(formData);\n\n case 12:\n newFile = _context2.sent;\n _context2.next = 15;\n return (0, _knowledgeBase.deleteFile)(_this8.editingId);\n\n case 15:\n return _context2.abrupt(\"return\", newFile);\n\n case 16:\n case \"end\":\n return _context2.stop();\n }\n }\n }, _callee2);\n }))();\n },\n // ==================== Word 编辑 ====================\n loadWordFile: function loadWordFile(file) {\n var _this9 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee3() {\n var detail, fileUrl, response, ab, result;\n return regeneratorRuntime.wrap(function _callee3$(_context3) {\n while (1) {\n switch (_context3.prev = _context3.next) {\n case 0:\n _this9.wordLoading = true;\n _this9.wordHtml = '';\n _context3.prev = 2;\n _context3.next = 5;\n return (0, _knowledgeBase.getFileDetail)(file.id);\n\n case 5:\n detail = _context3.sent;\n fileUrl = detail.fileUrl || file.fileUrl;\n\n if (fileUrl) {\n _context3.next = 10;\n break;\n }\n\n _this9.$message.warning('无法获取文件下载地址');\n\n return _context3.abrupt(\"return\");\n\n case 10:\n _context3.next = 12;\n return fetch(fileUrl);\n\n case 12:\n response = _context3.sent;\n\n if (response.ok) {\n _context3.next = 15;\n break;\n }\n\n throw new Error('下载文件失败');\n\n case 15:\n _context3.next = 17;\n return response.arrayBuffer();\n\n case 17:\n ab = _context3.sent;\n _context3.next = 20;\n return _mammoth.default.convertToHtml({\n arrayBuffer: ab\n });\n\n case 20:\n result = _context3.sent;\n _this9.wordHtml = result.value || '<p>(空文档)</p>';\n _context3.next = 28;\n break;\n\n case 24:\n _context3.prev = 24;\n _context3.t0 = _context3[\"catch\"](2);\n console.error('加载 Word 文件失败', _context3.t0);\n\n _this9.$message.error('加载 Word 文件失败: ' + (_context3.t0.message || ''));\n\n case 28:\n _context3.prev = 28;\n _this9.wordLoading = false;\n return _context3.finish(28);\n\n case 31:\n case \"end\":\n return _context3.stop();\n }\n }\n }, _callee3, null, [[2, 24, 28, 31]]);\n }))();\n },\n execFormat: function execFormat(command, value) {\n document.execCommand(command, false, value || null);\n this.$refs.wordEditor && this.$refs.wordEditor.focus();\n },\n onWordInput: function onWordInput() {\n // 触发 wordCharCount 重新计算\n this.$forceUpdate();\n },\n\n /**\r\n * 将编辑后的 HTML 转为 .docx 文件并上传替换\r\n */\n replaceWordFile: function replaceWordFile() {\n var _this10 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee4() {\n var editor, html, paragraphs, doc, blob, originalName, file, formData, newFile;\n return regeneratorRuntime.wrap(function _callee4$(_context4) {\n while (1) {\n switch (_context4.prev = _context4.next) {\n case 0:\n editor = _this10.$refs.wordEditor;\n html = editor ? editor.innerHTML : _this10.wordHtml; // 用 docx 包生成新文档\n\n paragraphs = _this10.htmlToDocxParagraphs(html);\n doc = new _docx.Document({\n sections: [{\n children: paragraphs\n }]\n });\n _context4.next = 6;\n return _docx.Packer.toBlob(doc);\n\n case 6:\n blob = _context4.sent;\n originalName = _this10.formData.fileName || 'document.docx';\n file = new File([blob], originalName, {\n type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'\n }); // 上传新文件\n\n formData = new FormData();\n formData.append('file', file);\n formData.append('companyId', _this10.companyId);\n\n if (_this10.formData.folderId) {\n formData.append('folderId', _this10.formData.folderId);\n }\n\n _context4.next = 15;\n return (0, _knowledgeBase.uploadFile)(formData);\n\n case 15:\n newFile = _context4.sent;\n _context4.next = 18;\n return (0, _knowledgeBase.deleteFile)(_this10.editingId);\n\n case 18:\n return _context4.abrupt(\"return\", newFile);\n\n case 19:\n case \"end\":\n return _context4.stop();\n }\n }\n }, _callee4);\n }))();\n },\n\n /**\r\n * 将 HTML 内容解析为 docx Paragraph 数组\r\n */\n htmlToDocxParagraphs: function htmlToDocxParagraphs(html) {\n var _this11 = this;\n\n var div = document.createElement('div');\n div.innerHTML = html;\n var children = div.childNodes;\n var paragraphs = [];\n\n var _iterator = (0, _createForOfIteratorHelper2.default)(children),\n _step;\n\n try {\n for (_iterator.s(); !(_step = _iterator.n()).done;) {\n var node = _step.value;\n\n if (node.nodeType === 3) {\n // 纯文本节点\n var text = (node.textContent || '').trim();\n\n if (text) {\n paragraphs.push(new _docx.Paragraph({\n children: [new _docx.TextRun(text)]\n }));\n }\n\n continue;\n }\n\n if (node.nodeType !== 1) continue;\n var tag = node.tagName.toLowerCase(); // 标题 h1-h6\n\n if (/^h[1-6]$/.test(tag)) {\n var level = parseInt(tag[1]);\n var headingMap = {\n 1: _docx.HeadingLevel.HEADING_1,\n 2: _docx.HeadingLevel.HEADING_2,\n 3: _docx.HeadingLevel.HEADING_3,\n 4: _docx.HeadingLevel.HEADING_4,\n 5: _docx.HeadingLevel.HEADING_5,\n 6: _docx.HeadingLevel.HEADING_6\n };\n\n var _text = node.textContent || '';\n\n paragraphs.push(new _docx.Paragraph({\n heading: headingMap[level] || _docx.HeadingLevel.HEADING_1,\n spacing: {\n before: 200,\n after: 100\n },\n children: [new _docx.TextRun({\n text: _text,\n bold: level <= 3,\n size: level <= 1 ? 32 : level <= 2 ? 28 : 24\n })]\n }));\n continue;\n } // 段落、div、引用\n\n\n if (tag === 'p' || tag === 'div' || tag === 'blockquote') {\n var _runs = this.buildTextRuns(node);\n\n var opts = {\n children: _runs,\n spacing: {\n after: 80\n }\n };\n\n if (tag === 'blockquote') {\n opts.indent = {\n left: 400\n };\n }\n\n paragraphs.push(new _docx.Paragraph(opts));\n continue;\n } // 无序列表\n\n\n if (tag === 'ul') {\n var items = node.querySelectorAll('li');\n items.forEach(function (li) {\n var runs = _this11.buildTextRuns(li);\n\n paragraphs.push(new _docx.Paragraph({\n children: runs,\n bullet: {\n level: 0\n },\n spacing: {\n after: 40\n }\n }));\n });\n continue;\n } // 有序列表\n\n\n if (tag === 'ol') {\n var _ret = function () {\n var items = node.querySelectorAll('li');\n var num = 1;\n items.forEach(function (li) {\n var runs = _this11.buildTextRuns(li);\n\n paragraphs.push(new _docx.Paragraph({\n children: runs,\n numbering: {\n reference: 1,\n level: 0\n },\n spacing: {\n after: 40\n }\n }));\n num++;\n });\n return \"continue\";\n }();\n\n if (_ret === \"continue\") continue;\n } // 其他标签(pre, hr 等)当作段落处理\n\n\n var runs = this.buildTextRuns(node);\n\n if (runs.length > 0) {\n paragraphs.push(new _docx.Paragraph({\n children: runs\n }));\n }\n }\n } catch (err) {\n _iterator.e(err);\n } finally {\n _iterator.f();\n }\n\n return paragraphs;\n },\n\n /**\r\n * 从 DOM 节点构建 TextRun 数组\r\n */\n buildTextRuns: function buildTextRuns(node) {\n var runs = [];\n\n var walk = function walk(el) {\n el.childNodes.forEach(function (child) {\n if (child.nodeType === 3) {\n var text = (child.textContent || '').trim();\n\n if (text) {\n runs.push(new _docx.TextRun({\n text: text\n }));\n }\n } else if (child.nodeType === 1) {\n var t = child.tagName.toLowerCase();\n\n var _text2 = child.textContent || '';\n\n if (t === 'br') {// 换行不处理\n } else if (t === 'strong' || t === 'b') {\n runs.push(new _docx.TextRun({\n text: _text2,\n bold: true\n }));\n } else if (t === 'em' || t === 'i') {\n runs.push(new _docx.TextRun({\n text: _text2,\n italics: true\n }));\n } else if (t === 'u') {\n runs.push(new _docx.TextRun({\n text: _text2,\n underline: {\n type: 'single'\n }\n }));\n } else if (t === 'a') {\n runs.push(new _docx.TextRun({\n text: _text2\n }));\n } else {\n walk(child);\n }\n }\n });\n };\n\n walk(node);\n return runs;\n },\n // ==================== 表单提交 ====================\n handleFileChange: function handleFileChange(file, fileList) {\n this.fileList = fileList;\n },\n handleFileRemove: function handleFileRemove(file, fileList) {\n this.fileList = fileList;\n },\n handleFileExceed: function handleFileExceed() {\n this.$message.warning('最多只能上传 5 个文件');\n },\n\n /**\r\n * 上传前校验:大小 50MB、类型白名单\r\n */\n beforeUpload: function beforeUpload(file) {\n if (file.size > MAX_SIZE) {\n this.$message.error(\"\\u6587\\u4EF6\\u300C\".concat(file.name, \"\\u300D\\u5927\\u5C0F\\u4E0D\\u80FD\\u8D85\\u8FC7 50MB\"));\n return false;\n }\n\n var ext = file.name.substring(file.name.lastIndexOf('.') + 1).toLowerCase();\n\n if (!ALLOWED_TYPES.includes(ext)) {\n this.$message.error(\"\\u6587\\u4EF6\\u300C\".concat(file.name, \"\\u300D\\u7C7B\\u578B\\u4E0D\\u652F\\u6301: .\").concat(ext));\n return false;\n }\n\n return true;\n },\n handleSubmit: function handleSubmit() {\n if (this.isEdit) {\n this.handleSubmitEdit();\n } else {\n this.handleSubmitUpload();\n }\n },\n\n /**\r\n * 上传模式提交:逐个文件构造 FormData 调用上传接口\r\n */\n handleSubmitUpload: function handleSubmitUpload() {\n var _this12 = this;\n\n if (!this.companyId) {\n this.$message.warning('公司ID不能为空');\n return;\n }\n\n if (!this.formData.folderId) {\n this.$message.warning('请选择所属文件夹');\n return;\n }\n\n var files = this.fileList.filter(function (f) {\n return f.status !== 'success';\n });\n\n if (files.length === 0) {\n this.$message.warning('请先选择文件');\n return;\n }\n\n this.submitting = true; // 逐个上传(后端单文件接口)\n\n var tasks = files.map(function (file) {\n var formData = new FormData();\n formData.append('file', file.raw);\n formData.append('companyId', _this12.companyId);\n console.log('[DocumentForm] 上传文件:', file.name, ', companyId:', _this12.companyId);\n\n if (_this12.formData.folderId) {\n formData.append('folderId', _this12.formData.folderId);\n }\n\n return (0, _knowledgeBase.uploadFile)(formData);\n });\n Promise.all(tasks).then(function () {\n _this12.$message.success(\"\\u6210\\u529F\\u4E0A\\u4F20 \".concat(files.length, \" \\u4E2A\\u6587\\u4EF6\"));\n\n _this12.visible = false;\n\n if (_this12.resolvePromise) {\n _this12.resolvePromise(true);\n\n _this12.resolvePromise = null;\n }\n }).catch(function () {}).finally(function () {\n _this12.submitting = false;\n });\n },\n\n /**\r\n * 编辑模式提交\r\n */\n handleSubmitEdit: function handleSubmitEdit() {\n var _this13 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee5() {\n var data;\n return regeneratorRuntime.wrap(function _callee5$(_context5) {\n while (1) {\n switch (_context5.prev = _context5.next) {\n case 0:\n _this13.submitting = true;\n _context5.prev = 1;\n\n if (!_this13.isXlsxType) {\n _context5.next = 7;\n break;\n }\n\n _context5.next = 5;\n return _this13.replaceXlsxFile();\n\n case 5:\n _context5.next = 17;\n break;\n\n case 7:\n if (!_this13.isWordType) {\n _context5.next = 12;\n break;\n }\n\n _context5.next = 10;\n return _this13.replaceWordFile();\n\n case 10:\n _context5.next = 17;\n break;\n\n case 12:\n // 普通文件:更新元数据 + 文本内容\n data = {\n title: _this13.formData.title\n };\n\n if (_this13.formData.folderId) {\n data.folderId = _this13.formData.folderId;\n }\n\n if (_this13.isTextEditable && _this13.formData.content) {\n data.content = _this13.formData.content;\n }\n\n _context5.next = 17;\n return (0, _knowledgeBase.updateFile)(_this13.editingId, data);\n\n case 17:\n _this13.$message.success('修改成功');\n\n _this13.visible = false;\n\n if (_this13.resolvePromise) {\n _this13.resolvePromise(true);\n\n _this13.resolvePromise = null;\n }\n\n _context5.next = 26;\n break;\n\n case 22:\n _context5.prev = 22;\n _context5.t0 = _context5[\"catch\"](1);\n console.error('保存失败', _context5.t0);\n\n _this13.$message.error('保存失败');\n\n case 26:\n _context5.prev = 26;\n _this13.submitting = false;\n return _context5.finish(26);\n\n case 29:\n case \"end\":\n return _context5.stop();\n }\n }\n }, _callee5, null, [[1, 22, 26, 29]]);\n }))();\n },\n onDialogOpened: function onDialogOpened() {\n // dialog 完全打开后,尝试将 wordHtml 写入编辑器\n if (this.isWordType && this.wordHtml && this.$refs && this.$refs.wordEditor) {\n this.$refs.wordEditor.innerHTML = this.wordHtml;\n }\n },\n handleClosed: function handleClosed() {\n // 关闭后清空上传列表,避免下次打开残留\n if (this.$refs.uploadRef) {\n this.$refs.uploadRef.clearFiles();\n }\n\n this.fileList = [];\n this.resetXlsxData();\n this.wordHtml = '';\n\n if (this.resolvePromise) {\n this.resolvePromise(null);\n this.resolvePromise = null;\n }\n }\n }\n};\nexports.default = _default2;\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/DocumentForm.vue?./node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
979
|
+
eval("\n\nvar _interopRequireWildcard = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/interopRequireWildcard */ \"./node_modules/@babel/runtime/helpers/interopRequireWildcard.js\").default;\n\nvar _interopRequireDefault = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/interopRequireDefault */ \"./node_modules/@babel/runtime/helpers/interopRequireDefault.js\").default;\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _createForOfIteratorHelper2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/createForOfIteratorHelper */ \"./node_modules/@babel/runtime/helpers/createForOfIteratorHelper.js\"));\n\nvar _asyncToGenerator2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/asyncToGenerator */ \"./node_modules/@babel/runtime/helpers/asyncToGenerator.js\"));\n\nvar _objectSpread2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/objectSpread2 */ \"./node_modules/@babel/runtime/helpers/objectSpread2.js\"));\n\nvar _marked = __webpack_require__(/*! marked */ \"./node_modules/marked/lib/marked.esm.js\");\n\nvar XLSX = _interopRequireWildcard(__webpack_require__(/*! xlsx */ \"./node_modules/xlsx/xlsx.mjs\"));\n\nvar _mammoth = _interopRequireDefault(__webpack_require__(/*! mammoth */ \"./node_modules/mammoth/lib/index.js\"));\n\nvar _docx = __webpack_require__(/*! docx */ \"./node_modules/docx/build/index.mjs\");\n\nvar _knowledgeBase = __webpack_require__(/*! ./api/knowledgeBase */ \"./src/lib/knowledgeBase/api/knowledgeBase.js\");\n\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n// 单文件大小上限:50MB\nvar MAX_SIZE = 50 * 1024 * 1024;\nvar ALLOWED_TYPES = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'pdf', 'txt', 'md', 'markdown', 'jpg', 'jpeg', 'png', 'gif', 'webp']; // 支持在线编辑的文本文件类型\n\nvar TEXT_EDITABLE_TYPES = ['txt', 'md', 'markdown', 'json', 'xml', 'yaml', 'yml', 'ini', 'cfg', 'env', 'js', 'ts', 'jsx', 'tsx', 'css', 'scss', 'less', 'html', 'htm', 'vue', 'py', 'java', 'rb', 'go', 'rs', 'sh', 'bat', 'ps1', 'sql', 'php', 'c', 'cpp', 'h', 'hpp', 'swift', 'kt', 'groovy'];\nvar _default2 = {\n name: 'DocumentForm',\n props: {\n companyId: {\n type: String,\n default: ''\n },\n folderList: {\n type: Array,\n default: function _default() {\n return [];\n }\n }\n },\n data: function data() {\n return {\n visible: false,\n isEdit: false,\n editingId: null,\n submitting: false,\n fileList: [],\n formData: {\n title: '',\n folderId: '',\n fileName: ''\n },\n resolvePromise: null,\n editingFileType: '',\n originalIndexStatus: 0,\n mdTab: 'edit',\n xlsxData: {\n sheetNames: [],\n currentSheet: '',\n rows: [],\n cols: [],\n workbook: null,\n loading: false\n },\n wordHtml: '',\n wordLoading: false,\n folderTreeProps: {\n children: 'children',\n label: 'name'\n },\n folderPopoverVisible: false\n };\n },\n watch: {\n /**\r\n * wordLoading 变为 false 时,编辑器 DOM 已渲染,将 wordHtml 写入编辑器\r\n */\n wordLoading: function wordLoading(val) {\n var _this = this;\n\n if (!val && this.wordHtml) {\n this.$nextTick(function () {\n var editor = _this.$refs && _this.$refs.wordEditor;\n\n if (editor) {\n editor.innerHTML = _this.wordHtml;\n }\n });\n }\n }\n },\n computed: {\n dialogTitle: function dialogTitle() {\n if (this.isXlsxType) return '编辑表格数据';\n if (this.isWordType) return '编辑 Word 文档';\n if (this.isTextEditable) return '编辑文件内容';\n return '编辑文档';\n },\n dialogWidth: function dialogWidth() {\n if (this.isXlsxType) return '960px';\n if (this.isWordType) return '900px';\n if (this.isTextEditable) return '860px';\n return '560px';\n },\n\n /**\r\n * 当前编辑的文件是否为文本可编辑类型\r\n */\n isTextEditable: function isTextEditable() {\n var t = (this.editingFileType || '').toLowerCase();\n return TEXT_EDITABLE_TYPES.includes(t);\n },\n\n /**\r\n * 当前编辑的文件是否为 Excel 类型\r\n */\n isXlsxType: function isXlsxType() {\n var t = (this.editingFileType || '').toLowerCase();\n return t === 'xlsx' || t === 'xls';\n },\n\n /**\r\n * 当前编辑的文件是否为 Markdown 类型(支持预览)\r\n */\n isMarkdownType: function isMarkdownType() {\n var t = (this.editingFileType || '').toLowerCase();\n return t === 'md' || t === 'markdown';\n },\n\n /**\r\n * 当前编辑的文件是否为 Word 类型\r\n */\n isWordType: function isWordType() {\n var t = (this.editingFileType || '').toLowerCase();\n return t === 'docx' || t === 'doc';\n },\n\n /**\r\n * Word 编辑器字符数\r\n */\n wordCharCount: function wordCharCount() {\n var editor = this.$refs && this.$refs.wordEditor;\n\n if (editor) {\n return (editor.innerText || '').replace(/\\s/g, '').length;\n }\n\n return (this.wordHtml || '').replace(/<[^>]+>/g, '').length;\n },\n\n /**\r\n * Markdown 预览 HTML\r\n */\n previewHtml: function previewHtml() {\n if (!this.formData.content) return '';\n return _marked.marked.parse(this.formData.content, {\n breaks: true,\n gfm: true\n });\n },\n\n /**\r\n * 所属文件夹树(由平铺列表组装,不污染 prop)\r\n */\n folderTree: function folderTree() {\n var nodes = this.folderList.map(function (f) {\n return (0, _objectSpread2.default)((0, _objectSpread2.default)({}, f), {}, {\n children: []\n });\n });\n var map = {};\n nodes.forEach(function (n) {\n map[n.id] = n;\n });\n var roots = [];\n nodes.forEach(function (n) {\n var pid = n.parentId || 0;\n\n if (map[pid]) {\n map[pid].children.push(n);\n } else {\n roots.push(n);\n }\n });\n return roots;\n },\n\n /**\r\n * 当前所选文件夹的名称(用于下拉框显示)\r\n */\n selectedFolderName: function selectedFolderName() {\n var _this2 = this;\n\n if (!this.formData.folderId) return '';\n var f = this.folderList.find(function (x) {\n return x.id === _this2.formData.folderId;\n });\n return f ? f.name : '';\n }\n },\n methods: {\n handleFolderSelect: function handleFolderSelect(data) {\n this.formData.folderId = data.id;\n this.folderPopoverVisible = false;\n },\n handleFolderPopoverShow: function handleFolderPopoverShow() {\n var _this3 = this;\n\n this.$nextTick(function () {\n if (_this3.$refs.folderTreeRef) _this3.$refs.folderTreeRef.setCurrentKey(_this3.formData.folderId || null);\n });\n },\n clearFolderSelect: function clearFolderSelect() {\n this.formData.folderId = '';\n this.folderPopoverVisible = false;\n },\n\n /**\r\n * 打开上传对话框(新增)\r\n * @param {string} folderId - 预选文件夹ID\r\n * @returns {Promise}\r\n */\n openAdd: function openAdd(folderId) {\n var _this4 = this;\n\n this.isEdit = false;\n this.editingId = null;\n this.fileList = [];\n this.formData = {\n title: '',\n folderId: folderId || '',\n fileName: ''\n };\n this.visible = true;\n this.$nextTick(function () {\n if (_this4.$refs.folderTreeRef) _this4.$refs.folderTreeRef.setCurrentKey(_this4.formData.folderId || null);\n });\n return new Promise(function (resolve) {\n _this4.resolvePromise = resolve;\n });\n },\n\n /**\r\n * 打开编辑对话框\r\n * @param {object} file - 文件对象\r\n * @returns {Promise}\r\n */\n openEdit: function openEdit(file) {\n var _this5 = this;\n\n this.isEdit = true;\n this.editingId = file.id;\n this.editingFileType = file.fileType || '';\n this.originalIndexStatus = file.indexStatus || 0;\n this.fileList = [];\n this.mdTab = 'edit';\n this.resetXlsxData();\n this.formData = {\n title: file.title || '',\n folderId: file.folderId || '',\n fileName: file.fileName || '',\n content: ''\n };\n var t = (file.fileType || '').toLowerCase(); // 如果是文本可编辑类型,异步加载文件内容\n\n if (TEXT_EDITABLE_TYPES.includes(t)) {\n (0, _knowledgeBase.getFileDetail)(file.id).then(function (d) {\n _this5.formData.content = d.content || '';\n }).catch(function () {\n _this5.formData.content = '(内容加载失败)';\n });\n } // 如果是 Excel 类型,下载原文件并解析\n\n\n if (t === 'xlsx' || t === 'xls') {\n this.loadXlsxFile(file);\n }\n\n this.visible = true;\n this.$nextTick(function () {\n if (_this5.$refs.folderTreeRef) _this5.$refs.folderTreeRef.setCurrentKey(_this5.formData.folderId || null);\n }); // 如果是 Word 类型,下载原文件并解析(visible 先置为 true 确保 DOM 已渲染)\n\n if (t === 'docx' || t === 'doc') {\n this.loadWordFile(file);\n }\n\n return new Promise(function (resolve) {\n _this5.resolvePromise = resolve;\n });\n },\n // ==================== Excel 编辑 ====================\n resetXlsxData: function resetXlsxData() {\n this.xlsxData = {\n sheetNames: [],\n currentSheet: '',\n rows: [],\n cols: [],\n workbook: null,\n loading: false\n };\n },\n loadXlsxFile: function loadXlsxFile(file) {\n var _this6 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {\n var detail, fileUrl, response, ab, workbook;\n return regeneratorRuntime.wrap(function _callee$(_context) {\n while (1) {\n switch (_context.prev = _context.next) {\n case 0:\n _this6.xlsxData.loading = true;\n _context.prev = 1;\n _context.next = 4;\n return (0, _knowledgeBase.getFileDetail)(file.id);\n\n case 4:\n detail = _context.sent;\n fileUrl = detail.fileUrl || file.fileUrl;\n\n if (fileUrl) {\n _context.next = 9;\n break;\n }\n\n _this6.$message.warning('无法获取文件下载地址');\n\n return _context.abrupt(\"return\");\n\n case 9:\n _context.next = 11;\n return fetch(fileUrl);\n\n case 11:\n response = _context.sent;\n\n if (response.ok) {\n _context.next = 14;\n break;\n }\n\n throw new Error('下载文件失败');\n\n case 14:\n _context.next = 16;\n return response.arrayBuffer();\n\n case 16:\n ab = _context.sent;\n workbook = XLSX.read(ab, {\n type: 'array'\n });\n _this6.xlsxData.workbook = workbook;\n _this6.xlsxData.sheetNames = workbook.SheetNames;\n\n if (workbook.SheetNames.length > 0) {\n _this6.xlsxData.currentSheet = workbook.SheetNames[0];\n\n _this6.buildXlsxSheetData(workbook.SheetNames[0]);\n }\n\n _context.next = 27;\n break;\n\n case 23:\n _context.prev = 23;\n _context.t0 = _context[\"catch\"](1);\n console.error('加载 Excel 文件失败', _context.t0);\n\n _this6.$message.error('加载 Excel 文件失败');\n\n case 27:\n _context.prev = 27;\n _this6.xlsxData.loading = false;\n return _context.finish(27);\n\n case 30:\n case \"end\":\n return _context.stop();\n }\n }\n }, _callee, null, [[1, 23, 27, 30]]);\n }))();\n },\n buildXlsxSheetData: function buildXlsxSheetData(sheetName) {\n var workbook = this.xlsxData.workbook;\n if (!workbook) return;\n var sheet = workbook.Sheets[sheetName];\n\n if (!sheet) {\n this.xlsxData.rows = [];\n this.xlsxData.cols = [];\n return;\n } // 转为二维数组(defval 补齐空单元格,保证各列取值一致)\n\n\n var data = XLSX.utils.sheet_to_json(sheet, {\n header: 1,\n defval: ''\n });\n\n if (data.length === 0) {\n this.xlsxData.rows = [];\n this.xlsxData.cols = [];\n return;\n } // 列数以「所有行的最大宽度」为准:表头行可能只填了第一格,若直接按第一行长度定列会丢失后续列\n\n\n var maxCols = Math.max.apply(null, data.map(function (row) {\n return row && row.length || 0;\n })); // 第一行作为列名,未填表头的列用「列N」补齐\n\n this.xlsxData.cols = Array.from({\n length: maxCols\n }, function (_, i) {\n var v = data[0] && data[0][i];\n return v != null && String(v).trim() !== '' ? String(v) : \"\\u5217\".concat(i + 1);\n }); // 剩余行作为数据:过滤全空行,避免出现多余空白行\n\n this.xlsxData.rows = data.slice(1).filter(function (row) {\n return row && row.some(function (c) {\n return c != null && String(c).trim() !== '';\n });\n }).map(function (row) {\n var obj = {};\n\n for (var ci = 0; ci < maxCols; ci++) {\n obj[ci] = row[ci] != null ? String(row[ci]) : '';\n }\n\n return obj;\n });\n },\n switchXlsxSheet: function switchXlsxSheet(name) {\n // 先保存当前 sheet 的编辑数据到 workbook\n this.saveCurrentSheetData();\n this.xlsxData.currentSheet = name;\n this.buildXlsxSheetData(name);\n },\n addXlsxRow: function addXlsxRow() {\n var row = {};\n this.xlsxData.cols.forEach(function (col, ci) {\n row[ci] = '';\n });\n this.xlsxData.rows.push(row);\n },\n addXlsxCol: function addXlsxCol() {\n var _this7 = this;\n\n var newColName = \"\\u5217\".concat(this.xlsxData.cols.length + 1);\n this.xlsxData.cols.push(newColName);\n this.xlsxData.rows.forEach(function (row) {\n row[_this7.xlsxData.cols.length - 1] = '';\n });\n },\n deleteXlsxRow: function deleteXlsxRow() {\n if (this.xlsxData.rows.length === 0) return;\n this.xlsxData.rows.pop();\n },\n deleteXlsxCol: function deleteXlsxCol() {\n if (this.xlsxData.cols.length === 0) return;\n this.xlsxData.cols.pop();\n var ci = this.xlsxData.cols.length;\n this.xlsxData.rows.forEach(function (row) {\n delete row[ci];\n });\n },\n removeXlsxRow: function removeXlsxRow(index) {\n this.xlsxData.rows.splice(index, 1);\n },\n onXlsxCellChange: function onXlsxCellChange() {// 数据变更标记(将来可用于 dirty 判断)\n },\n\n /**\r\n * 将当前编辑的 rows/cols 写回 workbook.Sheets[currentSheet]\r\n */\n saveCurrentSheetData: function saveCurrentSheetData() {\n var _this8 = this;\n\n var workbook = this.xlsxData.workbook;\n var sheetName = this.xlsxData.currentSheet;\n if (!workbook || !sheetName || !this.xlsxData.cols.length) return;\n var aoa = [this.xlsxData.cols];\n this.xlsxData.rows.forEach(function (row) {\n // 用列索引取数,避免列名重复时 indexOf 定位错位\n var rowData = [];\n\n for (var ci = 0; ci < _this8.xlsxData.cols.length; ci++) {\n rowData.push(row[ci] != null ? row[ci] : '');\n }\n\n aoa.push(rowData);\n });\n workbook.Sheets[sheetName] = XLSX.utils.aoa_to_sheet(aoa);\n },\n\n /**\r\n * 生成新的 xlsx 文件并上传替换\r\n */\n replaceXlsxFile: function replaceXlsxFile() {\n var _this9 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() {\n var workbook, origExt, isXls, bookType, mime, wbout, blob, originalName, file, formData, newFile;\n return regeneratorRuntime.wrap(function _callee2$(_context2) {\n while (1) {\n switch (_context2.prev = _context2.next) {\n case 0:\n // 先保存当前 sheet 的编辑数据\n _this9.saveCurrentSheetData();\n\n workbook = _this9.xlsxData.workbook; // 写出所有 sheet 到新文件,保持与原文件扩展名一致(xls → biff8, xlsx → xlsx)\n\n origExt = ((_this9.formData.fileName || '').split('.').pop() || 'xlsx').toLowerCase();\n isXls = origExt === 'xls';\n bookType = isXls ? 'biff8' : 'xlsx';\n mime = isXls ? 'application/vnd.ms-excel' : 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';\n wbout = XLSX.write(workbook, {\n bookType: bookType,\n type: 'array'\n });\n blob = new Blob([wbout], {\n type: mime\n });\n originalName = (_this9.formData.fileName || 'export.xlsx').replace(/\\.(xls|xlsx)$/i, isXls ? '.xls' : '.xlsx');\n file = new File([blob], originalName, {\n type: mime\n }); // 上传新文件\n\n formData = new FormData();\n formData.append('file', file);\n formData.append('companyId', _this9.companyId);\n\n if (_this9.formData.folderId) {\n formData.append('folderId', _this9.formData.folderId);\n }\n\n _context2.next = 16;\n return (0, _knowledgeBase.uploadFile)(formData);\n\n case 16:\n newFile = _context2.sent;\n _context2.next = 19;\n return (0, _knowledgeBase.deleteFile)(_this9.editingId);\n\n case 19:\n return _context2.abrupt(\"return\", newFile);\n\n case 20:\n case \"end\":\n return _context2.stop();\n }\n }\n }, _callee2);\n }))();\n },\n // ==================== Word 编辑 ====================\n loadWordFile: function loadWordFile(file) {\n var _this10 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee3() {\n var detail, fileUrl, response, ab, result;\n return regeneratorRuntime.wrap(function _callee3$(_context3) {\n while (1) {\n switch (_context3.prev = _context3.next) {\n case 0:\n _this10.wordLoading = true;\n _this10.wordHtml = '';\n _context3.prev = 2;\n _context3.next = 5;\n return (0, _knowledgeBase.getFileDetail)(file.id);\n\n case 5:\n detail = _context3.sent;\n fileUrl = detail.fileUrl || file.fileUrl;\n\n if (fileUrl) {\n _context3.next = 10;\n break;\n }\n\n _this10.$message.warning('无法获取文件下载地址');\n\n return _context3.abrupt(\"return\");\n\n case 10:\n _context3.next = 12;\n return fetch(fileUrl);\n\n case 12:\n response = _context3.sent;\n\n if (response.ok) {\n _context3.next = 15;\n break;\n }\n\n throw new Error('下载文件失败');\n\n case 15:\n _context3.next = 17;\n return response.arrayBuffer();\n\n case 17:\n ab = _context3.sent;\n _context3.next = 20;\n return _mammoth.default.convertToHtml({\n arrayBuffer: ab\n });\n\n case 20:\n result = _context3.sent;\n _this10.wordHtml = result.value || '<p>(空文档)</p>';\n _context3.next = 28;\n break;\n\n case 24:\n _context3.prev = 24;\n _context3.t0 = _context3[\"catch\"](2);\n console.error('加载 Word 文件失败', _context3.t0);\n\n _this10.$message.error('加载 Word 文件失败: ' + (_context3.t0.message || ''));\n\n case 28:\n _context3.prev = 28;\n _this10.wordLoading = false;\n return _context3.finish(28);\n\n case 31:\n case \"end\":\n return _context3.stop();\n }\n }\n }, _callee3, null, [[2, 24, 28, 31]]);\n }))();\n },\n execFormat: function execFormat(command, value) {\n document.execCommand(command, false, value || null);\n this.$refs.wordEditor && this.$refs.wordEditor.focus();\n },\n onWordInput: function onWordInput() {\n // 触发 wordCharCount 重新计算\n this.$forceUpdate();\n },\n\n /**\r\n * 将编辑后的 HTML 转为 .docx 文件并上传替换\r\n */\n replaceWordFile: function replaceWordFile() {\n var _this11 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee4() {\n var editor, html, paragraphs, doc, blob, originalName, file, formData, newFile;\n return regeneratorRuntime.wrap(function _callee4$(_context4) {\n while (1) {\n switch (_context4.prev = _context4.next) {\n case 0:\n editor = _this11.$refs.wordEditor;\n html = editor ? editor.innerHTML : _this11.wordHtml; // 用 docx 包生成新文档\n\n paragraphs = _this11.htmlToDocxParagraphs(html);\n doc = new _docx.Document({\n sections: [{\n children: paragraphs\n }]\n });\n _context4.next = 6;\n return _docx.Packer.toBlob(doc);\n\n case 6:\n blob = _context4.sent;\n originalName = _this11.formData.fileName || 'document.docx';\n file = new File([blob], originalName, {\n type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'\n }); // 上传新文件\n\n formData = new FormData();\n formData.append('file', file);\n formData.append('companyId', _this11.companyId);\n\n if (_this11.formData.folderId) {\n formData.append('folderId', _this11.formData.folderId);\n }\n\n _context4.next = 15;\n return (0, _knowledgeBase.uploadFile)(formData);\n\n case 15:\n newFile = _context4.sent;\n _context4.next = 18;\n return (0, _knowledgeBase.deleteFile)(_this11.editingId);\n\n case 18:\n return _context4.abrupt(\"return\", newFile);\n\n case 19:\n case \"end\":\n return _context4.stop();\n }\n }\n }, _callee4);\n }))();\n },\n\n /**\r\n * 将 HTML 内容解析为 docx Paragraph 数组\r\n */\n htmlToDocxParagraphs: function htmlToDocxParagraphs(html) {\n var _this12 = this;\n\n var div = document.createElement('div');\n div.innerHTML = html;\n var children = div.childNodes;\n var paragraphs = [];\n\n var _iterator = (0, _createForOfIteratorHelper2.default)(children),\n _step;\n\n try {\n for (_iterator.s(); !(_step = _iterator.n()).done;) {\n var node = _step.value;\n\n if (node.nodeType === 3) {\n // 纯文本节点\n var text = (node.textContent || '').trim();\n\n if (text) {\n paragraphs.push(new _docx.Paragraph({\n children: [new _docx.TextRun(text)]\n }));\n }\n\n continue;\n }\n\n if (node.nodeType !== 1) continue;\n var tag = node.tagName.toLowerCase(); // 标题 h1-h6\n\n if (/^h[1-6]$/.test(tag)) {\n var level = parseInt(tag[1]);\n var headingMap = {\n 1: _docx.HeadingLevel.HEADING_1,\n 2: _docx.HeadingLevel.HEADING_2,\n 3: _docx.HeadingLevel.HEADING_3,\n 4: _docx.HeadingLevel.HEADING_4,\n 5: _docx.HeadingLevel.HEADING_5,\n 6: _docx.HeadingLevel.HEADING_6\n };\n\n var _text = node.textContent || '';\n\n paragraphs.push(new _docx.Paragraph({\n heading: headingMap[level] || _docx.HeadingLevel.HEADING_1,\n spacing: {\n before: 200,\n after: 100\n },\n children: [new _docx.TextRun({\n text: _text,\n bold: level <= 3,\n size: level <= 1 ? 32 : level <= 2 ? 28 : 24\n })]\n }));\n continue;\n } // 段落、div、引用\n\n\n if (tag === 'p' || tag === 'div' || tag === 'blockquote') {\n var _runs = this.buildTextRuns(node);\n\n var opts = {\n children: _runs,\n spacing: {\n after: 80\n }\n };\n\n if (tag === 'blockquote') {\n opts.indent = {\n left: 400\n };\n }\n\n paragraphs.push(new _docx.Paragraph(opts));\n continue;\n } // 无序列表\n\n\n if (tag === 'ul') {\n var items = node.querySelectorAll('li');\n items.forEach(function (li) {\n var runs = _this12.buildTextRuns(li);\n\n paragraphs.push(new _docx.Paragraph({\n children: runs,\n bullet: {\n level: 0\n },\n spacing: {\n after: 40\n }\n }));\n });\n continue;\n } // 有序列表\n\n\n if (tag === 'ol') {\n var _ret = function () {\n var items = node.querySelectorAll('li');\n var num = 1;\n items.forEach(function (li) {\n var runs = _this12.buildTextRuns(li);\n\n paragraphs.push(new _docx.Paragraph({\n children: runs,\n numbering: {\n reference: 1,\n level: 0\n },\n spacing: {\n after: 40\n }\n }));\n num++;\n });\n return \"continue\";\n }();\n\n if (_ret === \"continue\") continue;\n } // 其他标签(pre, hr 等)当作段落处理\n\n\n var runs = this.buildTextRuns(node);\n\n if (runs.length > 0) {\n paragraphs.push(new _docx.Paragraph({\n children: runs\n }));\n }\n }\n } catch (err) {\n _iterator.e(err);\n } finally {\n _iterator.f();\n }\n\n return paragraphs;\n },\n\n /**\r\n * 从 DOM 节点构建 TextRun 数组\r\n */\n buildTextRuns: function buildTextRuns(node) {\n var runs = [];\n\n var walk = function walk(el) {\n el.childNodes.forEach(function (child) {\n if (child.nodeType === 3) {\n var text = (child.textContent || '').trim();\n\n if (text) {\n runs.push(new _docx.TextRun({\n text: text\n }));\n }\n } else if (child.nodeType === 1) {\n var t = child.tagName.toLowerCase();\n\n var _text2 = child.textContent || '';\n\n if (t === 'br') {// 换行不处理\n } else if (t === 'strong' || t === 'b') {\n runs.push(new _docx.TextRun({\n text: _text2,\n bold: true\n }));\n } else if (t === 'em' || t === 'i') {\n runs.push(new _docx.TextRun({\n text: _text2,\n italics: true\n }));\n } else if (t === 'u') {\n runs.push(new _docx.TextRun({\n text: _text2,\n underline: {\n type: 'single'\n }\n }));\n } else if (t === 'a') {\n runs.push(new _docx.TextRun({\n text: _text2\n }));\n } else {\n walk(child);\n }\n }\n });\n };\n\n walk(node);\n return runs;\n },\n // ==================== 表单提交 ====================\n handleFileChange: function handleFileChange(file, fileList) {\n this.fileList = fileList;\n },\n handleFileRemove: function handleFileRemove(file, fileList) {\n this.fileList = fileList;\n },\n handleFileExceed: function handleFileExceed() {\n this.$message.warning('最多只能上传 5 个文件');\n },\n\n /**\r\n * 上传前校验:大小 50MB、类型白名单\r\n */\n beforeUpload: function beforeUpload(file) {\n if (file.size > MAX_SIZE) {\n this.$message.error(\"\\u6587\\u4EF6\\u300C\".concat(file.name, \"\\u300D\\u5927\\u5C0F\\u4E0D\\u80FD\\u8D85\\u8FC7 50MB\"));\n return false;\n }\n\n var ext = file.name.substring(file.name.lastIndexOf('.') + 1).toLowerCase();\n\n if (!ALLOWED_TYPES.includes(ext)) {\n this.$message.error(\"\\u6587\\u4EF6\\u300C\".concat(file.name, \"\\u300D\\u7C7B\\u578B\\u4E0D\\u652F\\u6301: .\").concat(ext));\n return false;\n }\n\n return true;\n },\n handleSubmit: function handleSubmit() {\n if (this.isEdit) {\n this.handleSubmitEdit();\n } else {\n this.handleSubmitUpload();\n }\n },\n\n /**\r\n * 上传模式提交:逐个文件构造 FormData 调用上传接口\r\n */\n handleSubmitUpload: function handleSubmitUpload() {\n var _this13 = this;\n\n if (!this.companyId) {\n this.$message.warning('公司ID不能为空');\n return;\n }\n\n if (!this.formData.folderId) {\n this.$message.warning('请选择所属文件夹');\n return;\n }\n\n var files = this.fileList.filter(function (f) {\n return f.status !== 'success';\n });\n\n if (files.length === 0) {\n this.$message.warning('请先选择文件');\n return;\n }\n\n this.submitting = true; // 逐个上传(后端单文件接口)\n\n var tasks = files.map(function (file) {\n var formData = new FormData();\n formData.append('file', file.raw);\n formData.append('companyId', _this13.companyId);\n console.log('[DocumentForm] 上传文件:', file.name, ', companyId:', _this13.companyId);\n\n if (_this13.formData.folderId) {\n formData.append('folderId', _this13.formData.folderId);\n }\n\n return (0, _knowledgeBase.uploadFile)(formData);\n });\n Promise.all(tasks).then(function () {\n _this13.$message.success(\"\\u6210\\u529F\\u4E0A\\u4F20 \".concat(files.length, \" \\u4E2A\\u6587\\u4EF6\"));\n\n _this13.visible = false;\n\n if (_this13.resolvePromise) {\n _this13.resolvePromise(true);\n\n _this13.resolvePromise = null;\n }\n }).catch(function () {}).finally(function () {\n _this13.submitting = false;\n });\n },\n\n /**\r\n * 编辑模式提交\r\n */\n handleSubmitEdit: function handleSubmitEdit() {\n var _this14 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee5() {\n var data;\n return regeneratorRuntime.wrap(function _callee5$(_context5) {\n while (1) {\n switch (_context5.prev = _context5.next) {\n case 0:\n _this14.submitting = true;\n _context5.prev = 1;\n\n if (!_this14.isXlsxType) {\n _context5.next = 7;\n break;\n }\n\n _context5.next = 5;\n return _this14.replaceXlsxFile();\n\n case 5:\n _context5.next = 17;\n break;\n\n case 7:\n if (!_this14.isWordType) {\n _context5.next = 12;\n break;\n }\n\n _context5.next = 10;\n return _this14.replaceWordFile();\n\n case 10:\n _context5.next = 17;\n break;\n\n case 12:\n // 普通文件:更新元数据 + 文本内容\n data = {\n title: _this14.formData.title\n };\n\n if (_this14.formData.folderId) {\n data.folderId = _this14.formData.folderId;\n }\n\n if (_this14.isTextEditable && _this14.formData.content) {\n data.content = _this14.formData.content;\n }\n\n _context5.next = 17;\n return (0, _knowledgeBase.updateFile)(_this14.editingId, data);\n\n case 17:\n if (!(_this14.originalIndexStatus === 1)) {\n _context5.next = 27;\n break;\n }\n\n _context5.prev = 18;\n _context5.next = 21;\n return (0, _knowledgeBase.deleteFileIndex)(_this14.editingId);\n\n case 21:\n _this14.$message.info('文件已修改,原索引已删除,请重新执行索引');\n\n _context5.next = 27;\n break;\n\n case 24:\n _context5.prev = 24;\n _context5.t0 = _context5[\"catch\"](18);\n console.error('删除旧索引失败', _context5.t0);\n\n case 27:\n _this14.$message.success('修改成功');\n\n _this14.visible = false;\n\n if (_this14.resolvePromise) {\n _this14.resolvePromise(true);\n\n _this14.resolvePromise = null;\n }\n\n _context5.next = 36;\n break;\n\n case 32:\n _context5.prev = 32;\n _context5.t1 = _context5[\"catch\"](1);\n console.error('保存失败', _context5.t1);\n\n _this14.$message.error('保存失败');\n\n case 36:\n _context5.prev = 36;\n _this14.submitting = false;\n return _context5.finish(36);\n\n case 39:\n case \"end\":\n return _context5.stop();\n }\n }\n }, _callee5, null, [[1, 32, 36, 39], [18, 24]]);\n }))();\n },\n onDialogOpened: function onDialogOpened() {\n // dialog 完全打开后,尝试将 wordHtml 写入编辑器\n if (this.isWordType && this.wordHtml && this.$refs && this.$refs.wordEditor) {\n this.$refs.wordEditor.innerHTML = this.wordHtml;\n }\n },\n handleClosed: function handleClosed() {\n // 关闭后清空上传列表,避免下次打开残留\n if (this.$refs.uploadRef) {\n this.$refs.uploadRef.clearFiles();\n }\n\n this.fileList = [];\n this.resetXlsxData();\n this.wordHtml = '';\n\n if (this.resolvePromise) {\n this.resolvePromise(null);\n this.resolvePromise = null;\n }\n }\n }\n};\nexports.default = _default2;\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/DocumentForm.vue?./node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
980
980
|
|
|
981
981
|
/***/ }),
|
|
982
982
|
|
|
@@ -988,7 +988,7 @@ eval("\n\nvar _interopRequireWildcard = __webpack_require__(/*! ./node_modules/@
|
|
|
988
988
|
/***/ (function(module, exports, __webpack_require__) {
|
|
989
989
|
|
|
990
990
|
"use strict";
|
|
991
|
-
eval("\n\nvar _interopRequireWildcard = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/interopRequireWildcard */ \"./node_modules/@babel/runtime/helpers/interopRequireWildcard.js\").default;\n\nvar _interopRequireDefault = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/interopRequireDefault */ \"./node_modules/@babel/runtime/helpers/interopRequireDefault.js\").default;\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _asyncToGenerator2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/asyncToGenerator */ \"./node_modules/@babel/runtime/helpers/asyncToGenerator.js\"));\n\nvar _marked = __webpack_require__(/*! marked */ \"./node_modules/marked/lib/marked.esm.js\");\n\nvar XLSX = _interopRequireWildcard(__webpack_require__(/*! xlsx */ \"./node_modules/xlsx/xlsx.mjs\"));\n\nvar _mammoth = _interopRequireDefault(__webpack_require__(/*! mammoth */ \"./node_modules/mammoth/lib/index.js\"));\n\nvar _knowledgeBase = __webpack_require__(/*! ./api/knowledgeBase */ \"./src/lib/knowledgeBase/api/knowledgeBase.js\");\n\nvar _DocumentForm = _interopRequireDefault(__webpack_require__(/*! ./DocumentForm */ \"./src/lib/knowledgeBase/DocumentForm.vue\"));\n\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n// 预览最多显示的行数(防止大文件卡死)\nvar MAX_PREVIEW_ROWS = 500;\nvar _default2 = {\n name: 'DocumentList',\n components: {\n DocumentForm: _DocumentForm.default\n },\n props: {\n selectedCategory: {\n type: Object,\n default: null\n },\n categoryList: {\n type: Array,\n default: function _default() {\n return [];\n }\n },\n companyId: {\n type: String,\n default: ''\n }\n },\n data: function data() {\n return {\n list: [],\n loading: false,\n total: 0,\n page: 1,\n pageSize: 10,\n searchKeyword: '',\n // 在线预览\n previewVisible: false,\n previewFile: null,\n previewType: 'none',\n previewText: '',\n // PDF 预览(Blob URL 避免下载)\n pdfBlobUrl: '',\n // Excel 预览\n xlsxLoading: false,\n xlsxWorkbook: null,\n xlsxSheetNames: [],\n xlsxCurrentSheet: '',\n xlsxCols: [],\n xlsxTableData: [],\n hasMoreRows: false,\n // Word 预览\n docxLoading: false,\n docxHtml: ''\n };\n },\n computed: {\n /**\r\n * Markdown 渲染后的 HTML(使用 marked)\r\n */\n previewHtml: function previewHtml() {\n if (!this.previewText) return '';\n return _marked.marked.parse(this.previewText, {\n breaks: true,\n gfm: true\n });\n },\n maxPreviewRows: function maxPreviewRows() {\n return MAX_PREVIEW_ROWS;\n }\n },\n watch: {\n selectedCategory: function selectedCategory() {\n this.page = 1;\n this.loadList();\n },\n companyId: function companyId() {\n this.page = 1;\n this.searchKeyword = '';\n this.list = [];\n this.total = 0;\n this.loadList();\n },\n\n /**\r\n * 预览弹窗关闭时清理 PDF Blob URL\r\n */\n previewVisible: function previewVisible(val) {\n if (!val && this.pdfBlobUrl) {\n URL.revokeObjectURL(this.pdfBlobUrl);\n this.pdfBlobUrl = '';\n }\n }\n },\n created: function created() {\n this.loadList();\n },\n methods: {\n loadList: function loadList() {\n var _this = this;\n\n if (!this.companyId) return;\n this.loading = true;\n var params = {\n companyId: this.companyId,\n pageNum: this.page,\n pageSize: this.pageSize\n }; // 选中具体文件夹时按文件夹过滤(\"显示全部文件\"时 id 为空串,不传)\n\n if (this.selectedCategory && this.selectedCategory.id) {\n params.folderId = this.selectedCategory.id;\n }\n\n if (this.searchKeyword) {\n params.keyword = this.searchKeyword;\n }\n\n (0, _knowledgeBase.getFileList)(params).then(function (data) {\n _this.list = data.records || [];\n _this.total = data.total || 0;\n }).catch(function () {}).finally(function () {\n _this.loading = false;\n });\n },\n handleSearch: function handleSearch() {\n this.page = 1;\n this.loadList();\n },\n handleSizeChange: function handleSizeChange(size) {\n this.pageSize = size;\n this.page = 1;\n this.loadList();\n },\n handleCurrentChange: function handleCurrentChange(page) {\n this.page = page;\n this.loadList();\n },\n handleAdd: function handleAdd() {\n var _this2 = this;\n\n if (!this.companyId) {\n this.$message.warning('公司数据未加载,请稍后刷新');\n return;\n }\n\n if (!this.categoryList || this.categoryList.length === 0) {\n this.$message.warning('暂无根目录,请先创建文件夹再新增文档');\n return;\n }\n\n var folderId = this.selectedCategory && this.selectedCategory.id ? this.selectedCategory.id : '';\n this.$refs.docFormRef.openAdd(folderId).then(function (res) {\n if (res) _this2.loadList();\n });\n },\n handleEdit: function handleEdit(row) {\n var _this3 = this;\n\n this.$refs.docFormRef.openEdit(row).then(function (res) {\n if (res) _this3.loadList();\n });\n },\n\n /**\r\n * 在线预览:图片 / PDF / Excel / Word / 文本\r\n */\n handlePreview: function handlePreview(row) {\n var _this4 = this;\n\n this.previewFile = row;\n this.previewText = '';\n this.docxHtml = '';\n this.xlsxWorkbook = null;\n this.xlsxSheetNames = [];\n this.xlsxCurrentSheet = '';\n this.xlsxCols = [];\n this.xlsxTableData = []; // 清理上一次的 PDF Blob URL\n\n if (this.pdfBlobUrl) {\n URL.revokeObjectURL(this.pdfBlobUrl);\n this.pdfBlobUrl = '';\n }\n\n var t = (row.fileType || '').toLowerCase();\n\n if (['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp'].includes(t)) {\n this.previewType = 'image';\n } else if (t === 'pdf') {\n this.previewType = 'pdf';\n this.loadPdfPreview(row);\n } else if (t === 'xlsx' || t === 'xls') {\n this.previewType = 'xlsx';\n this.loadXlsxPreview(row);\n } else if (t === 'docx' || t === 'doc') {\n this.previewType = 'docx';\n this.loadDocxPreview(row);\n } else if (['txt', 'md', 'markdown', 'html', 'htm'].includes(t)) {\n this.previewType = 'text';\n (0, _knowledgeBase.getFileDetail)(row.id).then(function (d) {\n _this4.previewText = d.content || '(该文件暂无可预览的文本内容)';\n }).catch(function () {\n _this4.previewText = '(文本内容加载失败)';\n });\n } else {\n this.previewType = 'none';\n }\n\n this.previewVisible = true;\n },\n // ==================== Excel 预览 ====================\n loadXlsxPreview: function loadXlsxPreview(file) {\n var _this5 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {\n var detail, fileUrl, response, ab, workbook;\n return regeneratorRuntime.wrap(function _callee$(_context) {\n while (1) {\n switch (_context.prev = _context.next) {\n case 0:\n _this5.xlsxLoading = true;\n _context.prev = 1;\n _context.next = 4;\n return (0, _knowledgeBase.getFileDetail)(file.id);\n\n case 4:\n detail = _context.sent;\n fileUrl = detail.fileUrl || file.fileUrl;\n\n if (fileUrl) {\n _context.next = 9;\n break;\n }\n\n _this5.$message.warning('无法获取文件下载地址');\n\n return _context.abrupt(\"return\");\n\n case 9:\n _context.next = 11;\n return fetch(fileUrl);\n\n case 11:\n response = _context.sent;\n\n if (response.ok) {\n _context.next = 14;\n break;\n }\n\n throw new Error('下载文件失败');\n\n case 14:\n _context.next = 16;\n return response.arrayBuffer();\n\n case 16:\n ab = _context.sent;\n _context.next = 19;\n return new Promise(function (r) {\n return setTimeout(r, 0);\n });\n\n case 19:\n workbook = XLSX.read(ab, {\n type: 'array'\n });\n _this5.xlsxWorkbook = workbook;\n _this5.xlsxSheetNames = workbook.SheetNames;\n\n if (workbook.SheetNames.length > 0) {\n _this5.xlsxCurrentSheet = workbook.SheetNames[0];\n\n _this5.renderXlsxSheet();\n }\n\n _context.next = 29;\n break;\n\n case 25:\n _context.prev = 25;\n _context.t0 = _context[\"catch\"](1);\n console.error('加载 Excel 文件失败', _context.t0);\n\n _this5.$message.error('Excel 文件加载失败');\n\n case 29:\n _context.prev = 29;\n _this5.xlsxLoading = false;\n return _context.finish(29);\n\n case 32:\n case \"end\":\n return _context.stop();\n }\n }\n }, _callee, null, [[1, 25, 29, 32]]);\n }))();\n },\n renderXlsxSheet: function renderXlsxSheet() {\n var _this6 = this;\n\n var workbook = this.xlsxWorkbook;\n var sheetName = this.xlsxCurrentSheet;\n if (!workbook || !sheetName) return;\n var sheet = workbook.Sheets[sheetName];\n\n if (!sheet) {\n this.xlsxCols = [];\n this.xlsxTableData = [];\n return;\n } // 让出主线程,防止 sheet_to_json 阻塞 UI\n\n\n var data = XLSX.utils.sheet_to_json(sheet, {\n header: 1\n });\n\n if (data.length === 0) {\n this.xlsxCols = [];\n this.xlsxTableData = [];\n return;\n }\n\n this.xlsxCols = data[0].map(function (v, i) {\n return v != null ? String(v) : \"\\u5217\".concat(i + 1);\n }); // 限制预览行数,防止渲染过多 DOM 卡死\n\n var rows = data.slice(1, MAX_PREVIEW_ROWS + 1);\n this.hasMoreRows = data.length - 1 > MAX_PREVIEW_ROWS;\n this.xlsxTableData = rows.map(function (row) {\n var obj = {};\n\n _this6.xlsxCols.forEach(function (col, ci) {\n obj[ci] = row[ci] != null ? String(row[ci]) : '';\n });\n\n return obj;\n });\n },\n // ==================== Word 预览 ====================\n loadDocxPreview: function loadDocxPreview(file) {\n var _this7 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() {\n var detail, fileUrl, response, ab, result;\n return regeneratorRuntime.wrap(function _callee2$(_context2) {\n while (1) {\n switch (_context2.prev = _context2.next) {\n case 0:\n _this7.docxLoading = true;\n _context2.prev = 1;\n _context2.next = 4;\n return (0, _knowledgeBase.getFileDetail)(file.id);\n\n case 4:\n detail = _context2.sent;\n fileUrl = detail.fileUrl || file.fileUrl;\n\n if (fileUrl) {\n _context2.next = 9;\n break;\n }\n\n _this7.$message.warning('无法获取文件下载地址');\n\n return _context2.abrupt(\"return\");\n\n case 9:\n _context2.next = 11;\n return fetch(fileUrl);\n\n case 11:\n response = _context2.sent;\n\n if (response.ok) {\n _context2.next = 14;\n break;\n }\n\n throw new Error('下载文件失败');\n\n case 14:\n _context2.next = 16;\n return response.arrayBuffer();\n\n case 16:\n ab = _context2.sent;\n _context2.next = 19;\n return _mammoth.default.convertToHtml({\n arrayBuffer: ab\n });\n\n case 19:\n result = _context2.sent;\n _this7.docxHtml = result.value || '<p>(空文档)</p>';\n _context2.next = 27;\n break;\n\n case 23:\n _context2.prev = 23;\n _context2.t0 = _context2[\"catch\"](1);\n console.error('加载 Word 文件失败', _context2.t0);\n\n _this7.$message.error('Word 文件加载失败');\n\n case 27:\n _context2.prev = 27;\n _this7.docxLoading = false;\n return _context2.finish(27);\n\n case 30:\n case \"end\":\n return _context2.stop();\n }\n }\n }, _callee2, null, [[1, 23, 27, 30]]);\n }))();\n },\n // ==================== PDF 预览 ====================\n loadPdfPreview: function loadPdfPreview(file) {\n var _this8 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee3() {\n var detail, fileUrl, response, blob;\n return regeneratorRuntime.wrap(function _callee3$(_context3) {\n while (1) {\n switch (_context3.prev = _context3.next) {\n case 0:\n _context3.prev = 0;\n _context3.next = 3;\n return (0, _knowledgeBase.getFileDetail)(file.id);\n\n case 3:\n detail = _context3.sent;\n fileUrl = detail.fileUrl || file.fileUrl;\n\n if (fileUrl) {\n _context3.next = 8;\n break;\n }\n\n _this8.$message.warning('无法获取文件下载地址');\n\n return _context3.abrupt(\"return\");\n\n case 8:\n _context3.next = 10;\n return fetch(fileUrl);\n\n case 10:\n response = _context3.sent;\n\n if (response.ok) {\n _context3.next = 13;\n break;\n }\n\n throw new Error('下载文件失败');\n\n case 13:\n _context3.next = 15;\n return response.blob();\n\n case 15:\n blob = _context3.sent;\n _this8.pdfBlobUrl = URL.createObjectURL(blob);\n _context3.next = 23;\n break;\n\n case 19:\n _context3.prev = 19;\n _context3.t0 = _context3[\"catch\"](0);\n console.error('加载 PDF 文件失败', _context3.t0);\n\n _this8.$message.error('PDF 文件加载失败');\n\n case 23:\n case \"end\":\n return _context3.stop();\n }\n }\n }, _callee3, null, [[0, 19]]);\n }))();\n },\n\n /**\r\n * 索引 / 重建索引(二次确认)\r\n */\n handleIndex: function handleIndex(row) {\n var _this9 = this;\n\n var isRebuild = row.indexStatus === 1;\n console.log('[DocumentList] 索引文件:', row.id, row.title, 'companyId:', this.companyId);\n this.$confirm(\"\\u786E\\u8BA4\\u5BF9\\u6587\\u4EF6\\u300C\".concat(row.title || row.fileName, \"\\u300D\").concat(isRebuild ? '重建' : '创建', \"\\u7D22\\u5F15\\uFF1F\"), '提示', {\n type: 'warning',\n confirmButtonText: '确定',\n cancelButtonText: '取消'\n }).then(function () {\n var request = isRebuild ? (0, _knowledgeBase.reindexFile)(row.id) : (0, _knowledgeBase.indexFile)(row.id);\n request.then(function () {\n _this9.$message.success(isRebuild ? '已开始重建索引' : '已开始索引');\n\n _this9.loadList();\n }).catch(function () {});\n }).catch(function () {});\n },\n\n /**\r\n * 删除索引(二次确认)\r\n */\n handleDeleteIndex: function handleDeleteIndex(row) {\n var _this10 = this;\n\n this.$confirm(\"\\u786E\\u8BA4\\u5220\\u9664\\u6587\\u4EF6\\u300C\".concat(row.title || row.fileName, \"\\u300D\\u7684\\u7D22\\u5F15\\uFF1F\\u5220\\u9664\\u540E\\u65E0\\u6CD5\\u6062\\u590D\\u3002\"), '提示', {\n type: 'warning',\n confirmButtonText: '确定删除',\n cancelButtonText: '取消'\n }).then(function () {\n (0, _knowledgeBase.deleteFileIndex)(row.id).then(function () {\n _this10.$message.success('索引已删除');\n\n _this10.loadList();\n }).catch(function () {});\n }).catch(function () {});\n },\n handleDelete: function handleDelete(row) {\n var _this11 = this;\n\n // 索引中的文件不允许删除\n if (row.indexStatus === 2) {\n this.$message.warning('该文件正在索引中,无法删除');\n return;\n }\n\n var hasIndex = row.indexStatus === 1;\n this.$confirm(\"\\u786E\\u8BA4\\u5220\\u9664\\u6587\\u4EF6\\u300C\".concat(row.title || row.fileName, \"\\u300D\\uFF1F\").concat(hasIndex ? '删除文件前会先删除该文件的索引。' : '', \"\\u5220\\u9664\\u540E\\u65E0\\u6CD5\\u6062\\u590D\\u3002\"), '提示', {\n type: 'warning',\n confirmButtonText: '确定删除',\n cancelButtonText: '取消'\n }).then( /*#__PURE__*/(0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee4() {\n return regeneratorRuntime.wrap(function _callee4$(_context4) {\n while (1) {\n switch (_context4.prev = _context4.next) {\n case 0:\n _context4.prev = 0;\n\n if (!hasIndex) {\n _context4.next = 4;\n break;\n }\n\n _context4.next = 4;\n return (0, _knowledgeBase.deleteFileIndex)(row.id);\n\n case 4:\n _context4.next = 6;\n return (0, _knowledgeBase.deleteFile)(row.id);\n\n case 6:\n _this11.$message.success('文件已删除');\n\n _this11.loadList();\n\n _context4.next = 12;\n break;\n\n case 10:\n _context4.prev = 10;\n _context4.t0 = _context4[\"catch\"](0);\n\n case 12:\n case \"end\":\n return _context4.stop();\n }\n }\n }, _callee4, null, [[0, 10]]);\n }))).catch(function () {});\n },\n\n /**\r\n * 通过 folderId 映射文件夹名称\r\n */\n folderName: function folderName(folderId) {\n if (!folderId) return '根目录';\n var found = this.categoryList.find(function (f) {\n return String(f.id) === String(folderId);\n });\n return found ? found.name : '-';\n },\n\n /**\r\n * 根据文件类型返回对应图标\r\n */\n getFileIcon: function getFileIcon(fileType) {\n var type = (fileType || '').toLowerCase();\n var iconMap = {\n pdf: 'el-icon-document',\n doc: 'el-icon-document',\n docx: 'el-icon-document',\n xls: 'el-icon-tickets',\n xlsx: 'el-icon-tickets',\n ppt: 'el-icon-data-line',\n pptx: 'el-icon-data-line',\n txt: 'el-icon-document',\n md: 'el-icon-edit-outline',\n png: 'el-icon-picture',\n jpg: 'el-icon-picture',\n jpeg: 'el-icon-picture',\n gif: 'el-icon-picture',\n zip: 'el-icon-folder-opened',\n rar: 'el-icon-folder-opened'\n };\n return iconMap[type] || 'el-icon-document';\n },\n\n /**\r\n * 文件大小格式化(字节 → B/KB/MB/GB)\r\n */\n formatSize: function formatSize(bytes) {\n if (!bytes && bytes !== 0) return '-';\n if (bytes === 0) return '0 B';\n var units = ['B', 'KB', 'MB', 'GB', 'TB'];\n var size = bytes;\n var idx = 0;\n\n while (size >= 1024 && idx < units.length - 1) {\n size /= 1024;\n idx++;\n }\n\n return \"\".concat(idx === 0 ? size : size.toFixed(2), \" \").concat(units[idx]);\n },\n\n /**\r\n * 时间展示:ISO 本地时间去掉 T 分隔符\r\n */\n formatTime: function formatTime(time) {\n if (!time) return '-';\n return String(time).replace('T', ' ').replace(/\\+08:00$/, '').replace(/\\.\\d{3}/, '');\n },\n\n /**\r\n * 索引状态标签:0 未索引 / 1 已索引 / 2 索引中 / 3 失败\r\n */\n indexTagType: function indexTagType(status) {\n var map = {\n 0: 'info',\n 1: 'success',\n 2: 'warning',\n 3: 'danger'\n };\n return map[status] || 'info';\n },\n indexTagText: function indexTagText(status) {\n var map = {\n 0: '未索引',\n 1: '已索引',\n 2: '索引中',\n 3: '索引失败'\n };\n return map[status] || '未知';\n }\n }\n};\nexports.default = _default2;\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/DocumentList.vue?./node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
991
|
+
eval("\n\nvar _interopRequireWildcard = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/interopRequireWildcard */ \"./node_modules/@babel/runtime/helpers/interopRequireWildcard.js\").default;\n\nvar _interopRequireDefault = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/interopRequireDefault */ \"./node_modules/@babel/runtime/helpers/interopRequireDefault.js\").default;\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _asyncToGenerator2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/asyncToGenerator */ \"./node_modules/@babel/runtime/helpers/asyncToGenerator.js\"));\n\nvar _objectSpread2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/objectSpread2 */ \"./node_modules/@babel/runtime/helpers/objectSpread2.js\"));\n\nvar _marked = __webpack_require__(/*! marked */ \"./node_modules/marked/lib/marked.esm.js\");\n\nvar XLSX = _interopRequireWildcard(__webpack_require__(/*! xlsx */ \"./node_modules/xlsx/xlsx.mjs\"));\n\nvar _mammoth = _interopRequireDefault(__webpack_require__(/*! mammoth */ \"./node_modules/mammoth/lib/index.js\"));\n\nvar _knowledgeBase = __webpack_require__(/*! ./api/knowledgeBase */ \"./src/lib/knowledgeBase/api/knowledgeBase.js\");\n\nvar _DocumentForm = _interopRequireDefault(__webpack_require__(/*! ./DocumentForm */ \"./src/lib/knowledgeBase/DocumentForm.vue\"));\n\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n// 预览最多显示的行数(防止大文件卡死)\nvar MAX_PREVIEW_ROWS = 500;\nvar _default2 = {\n name: 'DocumentList',\n components: {\n DocumentForm: _DocumentForm.default\n },\n props: {\n selectedCategory: {\n type: Object,\n default: null\n },\n categoryList: {\n type: Array,\n default: function _default() {\n return [];\n }\n },\n companyId: {\n type: String,\n default: ''\n }\n },\n data: function data() {\n return {\n list: [],\n loading: false,\n total: 0,\n page: 1,\n pageSize: 10,\n searchKeyword: '',\n // 批量删除选中的文件\n selectedRows: [],\n // 批量移动\n moveVisible: false,\n moveFolderId: null,\n moving: false,\n folderList: [],\n folderTreeProps: {\n children: 'children',\n label: 'name'\n },\n // 在线预览\n previewVisible: false,\n previewFile: null,\n previewType: 'none',\n previewText: '',\n // PDF 预览(Blob URL 避免下载)\n pdfBlobUrl: '',\n // Excel 预览\n xlsxLoading: false,\n xlsxWorkbook: null,\n xlsxSheetNames: [],\n xlsxCurrentSheet: '',\n xlsxCols: [],\n xlsxTableData: [],\n hasMoreRows: false,\n // Word 预览\n docxLoading: false,\n docxHtml: ''\n };\n },\n computed: {\n /**\r\n * Markdown 渲染后的 HTML(使用 marked)\r\n */\n previewHtml: function previewHtml() {\n if (!this.previewText) return '';\n return _marked.marked.parse(this.previewText, {\n breaks: true,\n gfm: true\n });\n },\n maxPreviewRows: function maxPreviewRows() {\n return MAX_PREVIEW_ROWS;\n },\n\n /**\r\n * 批量移动的文件夹树(由平铺列表组装)\r\n */\n moveFolderTree: function moveFolderTree() {\n var nodes = this.folderList.map(function (f) {\n return (0, _objectSpread2.default)((0, _objectSpread2.default)({}, f), {}, {\n children: []\n });\n });\n var map = {};\n nodes.forEach(function (n) {\n map[n.id] = n;\n });\n var roots = [];\n nodes.forEach(function (n) {\n var pid = n.parentId || 0;\n if (map[pid]) map[pid].children.push(n);else roots.push(n);\n });\n return roots;\n },\n\n /**\r\n * 目标文件夹名称\r\n */\n moveTargetName: function moveTargetName() {\n var _this = this;\n\n if (!this.moveFolderId) return '';\n var f = this.folderList.find(function (x) {\n return x.id === _this.moveFolderId;\n });\n return f ? f.name : '';\n }\n },\n watch: {\n selectedCategory: function selectedCategory() {\n this.page = 1;\n this.loadList();\n },\n companyId: function companyId() {\n this.page = 1;\n this.searchKeyword = '';\n this.list = [];\n this.total = 0;\n this.loadList();\n },\n\n /**\r\n * 预览弹窗关闭时清理 PDF Blob URL\r\n */\n previewVisible: function previewVisible(val) {\n if (!val && this.pdfBlobUrl) {\n URL.revokeObjectURL(this.pdfBlobUrl);\n this.pdfBlobUrl = '';\n }\n }\n },\n created: function created() {\n this.loadList();\n },\n methods: {\n loadList: function loadList() {\n var _this2 = this;\n\n if (!this.companyId) return;\n this.loading = true;\n var params = {\n companyId: this.companyId,\n pageNum: this.page,\n pageSize: this.pageSize\n }; // 选中具体文件夹时按文件夹过滤(\"显示全部文件\"时 id 为空串,不传)\n\n if (this.selectedCategory && this.selectedCategory.id) {\n params.folderId = this.selectedCategory.id;\n }\n\n if (this.searchKeyword) {\n params.keyword = this.searchKeyword;\n }\n\n (0, _knowledgeBase.getFileList)(params).then(function (data) {\n _this2.list = data.records || [];\n _this2.total = data.total || 0; // 列表刷新后清空勾选\n\n _this2.selectedRows = [];\n\n _this2.$nextTick(function () {\n if (_this2.$refs.tableRef) _this2.$refs.tableRef.clearSelection();\n });\n }).catch(function () {}).finally(function () {\n _this2.loading = false;\n });\n },\n handleSearch: function handleSearch() {\n this.page = 1;\n this.loadList();\n },\n handleSizeChange: function handleSizeChange(size) {\n this.pageSize = size;\n this.page = 1;\n this.loadList();\n },\n handleCurrentChange: function handleCurrentChange(page) {\n this.page = page;\n this.loadList();\n },\n handleAdd: function handleAdd() {\n var _this3 = this;\n\n if (!this.companyId) {\n this.$message.warning('公司数据未加载,请稍后刷新');\n return;\n }\n\n if (!this.categoryList || this.categoryList.length === 0) {\n this.$message.warning('暂无根目录,请先创建文件夹再新增文档');\n return;\n }\n\n var folderId = this.selectedCategory && this.selectedCategory.id ? this.selectedCategory.id : '';\n this.$refs.docFormRef.openAdd(folderId).then(function (res) {\n if (res) _this3.loadList();\n });\n },\n handleEdit: function handleEdit(row) {\n var _this4 = this;\n\n this.$refs.docFormRef.openEdit(row).then(function (res) {\n if (res) _this4.loadList();\n });\n },\n\n /**\r\n * 在线预览:图片 / PDF / Excel / Word / 文本\r\n */\n handlePreview: function handlePreview(row) {\n var _this5 = this;\n\n this.previewFile = row;\n this.previewText = '';\n this.docxHtml = '';\n this.xlsxWorkbook = null;\n this.xlsxSheetNames = [];\n this.xlsxCurrentSheet = '';\n this.xlsxCols = [];\n this.xlsxTableData = []; // 清理上一次的 PDF Blob URL\n\n if (this.pdfBlobUrl) {\n URL.revokeObjectURL(this.pdfBlobUrl);\n this.pdfBlobUrl = '';\n }\n\n var t = (row.fileType || '').toLowerCase();\n\n if (['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp'].includes(t)) {\n this.previewType = 'image';\n } else if (t === 'pdf') {\n this.previewType = 'pdf';\n this.loadPdfPreview(row);\n } else if (t === 'xlsx' || t === 'xls') {\n this.previewType = 'xlsx';\n this.loadXlsxPreview(row);\n } else if (t === 'docx' || t === 'doc') {\n this.previewType = 'docx';\n this.loadDocxPreview(row);\n } else if (['txt', 'md', 'markdown', 'html', 'htm'].includes(t)) {\n this.previewType = 'text';\n (0, _knowledgeBase.getFileDetail)(row.id).then(function (d) {\n _this5.previewText = d.content || '(该文件暂无可预览的文本内容)';\n }).catch(function () {\n _this5.previewText = '(文本内容加载失败)';\n });\n } else {\n this.previewType = 'none';\n }\n\n this.previewVisible = true;\n },\n // ==================== Excel 预览 ====================\n loadXlsxPreview: function loadXlsxPreview(file) {\n var _this6 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {\n var detail, fileUrl, response, ab, workbook;\n return regeneratorRuntime.wrap(function _callee$(_context) {\n while (1) {\n switch (_context.prev = _context.next) {\n case 0:\n _this6.xlsxLoading = true;\n _context.prev = 1;\n _context.next = 4;\n return (0, _knowledgeBase.getFileDetail)(file.id);\n\n case 4:\n detail = _context.sent;\n fileUrl = detail.fileUrl || file.fileUrl;\n\n if (fileUrl) {\n _context.next = 9;\n break;\n }\n\n _this6.$message.warning('无法获取文件下载地址');\n\n return _context.abrupt(\"return\");\n\n case 9:\n _context.next = 11;\n return fetch(fileUrl);\n\n case 11:\n response = _context.sent;\n\n if (response.ok) {\n _context.next = 14;\n break;\n }\n\n throw new Error('下载文件失败');\n\n case 14:\n _context.next = 16;\n return response.arrayBuffer();\n\n case 16:\n ab = _context.sent;\n _context.next = 19;\n return new Promise(function (r) {\n return setTimeout(r, 0);\n });\n\n case 19:\n workbook = XLSX.read(ab, {\n type: 'array'\n });\n _this6.xlsxWorkbook = workbook;\n _this6.xlsxSheetNames = workbook.SheetNames;\n\n if (workbook.SheetNames.length > 0) {\n _this6.xlsxCurrentSheet = workbook.SheetNames[0];\n\n _this6.renderXlsxSheet();\n }\n\n _context.next = 29;\n break;\n\n case 25:\n _context.prev = 25;\n _context.t0 = _context[\"catch\"](1);\n console.error('加载 Excel 文件失败', _context.t0);\n\n _this6.$message.error('Excel 文件加载失败');\n\n case 29:\n _context.prev = 29;\n _this6.xlsxLoading = false;\n return _context.finish(29);\n\n case 32:\n case \"end\":\n return _context.stop();\n }\n }\n }, _callee, null, [[1, 25, 29, 32]]);\n }))();\n },\n renderXlsxSheet: function renderXlsxSheet() {\n var workbook = this.xlsxWorkbook;\n var sheetName = this.xlsxCurrentSheet;\n if (!workbook || !sheetName) return;\n var sheet = workbook.Sheets[sheetName];\n\n if (!sheet) {\n this.xlsxCols = [];\n this.xlsxTableData = [];\n return;\n } // 让出主线程,防止 sheet_to_json 阻塞 UI\n\n\n var data = XLSX.utils.sheet_to_json(sheet, {\n header: 1,\n defval: ''\n });\n\n if (data.length === 0) {\n this.xlsxCols = [];\n this.xlsxTableData = [];\n return;\n } // 列数以「所有行的最大宽度」为准:表头行可能只填了第一格,若直接按第一行长度定列会丢失后续列\n\n\n var maxCols = Math.max.apply(null, data.map(function (row) {\n return row && row.length || 0;\n }));\n this.xlsxCols = Array.from({\n length: maxCols\n }, function (_, i) {\n var v = data[0] && data[0][i];\n return v != null && String(v).trim() !== '' ? String(v) : \"\\u5217\".concat(i + 1);\n }); // 限制预览行数,防止渲染过多 DOM 卡死;同时过滤全空行\n\n var allRows = data.slice(1).filter(function (row) {\n return row && row.some(function (c) {\n return c != null && String(c).trim() !== '';\n });\n });\n this.hasMoreRows = allRows.length > MAX_PREVIEW_ROWS;\n this.xlsxTableData = allRows.slice(0, MAX_PREVIEW_ROWS).map(function (row) {\n var obj = {};\n\n for (var ci = 0; ci < maxCols; ci++) {\n obj[ci] = row[ci] != null ? String(row[ci]) : '';\n }\n\n return obj;\n });\n },\n // ==================== Word 预览 ====================\n loadDocxPreview: function loadDocxPreview(file) {\n var _this7 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() {\n var detail, fileUrl, response, ab, result;\n return regeneratorRuntime.wrap(function _callee2$(_context2) {\n while (1) {\n switch (_context2.prev = _context2.next) {\n case 0:\n _this7.docxLoading = true;\n _context2.prev = 1;\n _context2.next = 4;\n return (0, _knowledgeBase.getFileDetail)(file.id);\n\n case 4:\n detail = _context2.sent;\n fileUrl = detail.fileUrl || file.fileUrl;\n\n if (fileUrl) {\n _context2.next = 9;\n break;\n }\n\n _this7.$message.warning('无法获取文件下载地址');\n\n return _context2.abrupt(\"return\");\n\n case 9:\n _context2.next = 11;\n return fetch(fileUrl);\n\n case 11:\n response = _context2.sent;\n\n if (response.ok) {\n _context2.next = 14;\n break;\n }\n\n throw new Error('下载文件失败');\n\n case 14:\n _context2.next = 16;\n return response.arrayBuffer();\n\n case 16:\n ab = _context2.sent;\n _context2.next = 19;\n return _mammoth.default.convertToHtml({\n arrayBuffer: ab\n });\n\n case 19:\n result = _context2.sent;\n _this7.docxHtml = result.value || '<p>(空文档)</p>';\n _context2.next = 27;\n break;\n\n case 23:\n _context2.prev = 23;\n _context2.t0 = _context2[\"catch\"](1);\n console.error('加载 Word 文件失败', _context2.t0);\n\n _this7.$message.error('Word 文件加载失败');\n\n case 27:\n _context2.prev = 27;\n _this7.docxLoading = false;\n return _context2.finish(27);\n\n case 30:\n case \"end\":\n return _context2.stop();\n }\n }\n }, _callee2, null, [[1, 23, 27, 30]]);\n }))();\n },\n // ==================== PDF 预览 ====================\n loadPdfPreview: function loadPdfPreview(file) {\n var _this8 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee3() {\n var detail, fileUrl, response, blob;\n return regeneratorRuntime.wrap(function _callee3$(_context3) {\n while (1) {\n switch (_context3.prev = _context3.next) {\n case 0:\n _context3.prev = 0;\n _context3.next = 3;\n return (0, _knowledgeBase.getFileDetail)(file.id);\n\n case 3:\n detail = _context3.sent;\n fileUrl = detail.fileUrl || file.fileUrl;\n\n if (fileUrl) {\n _context3.next = 8;\n break;\n }\n\n _this8.$message.warning('无法获取文件下载地址');\n\n return _context3.abrupt(\"return\");\n\n case 8:\n _context3.next = 10;\n return fetch(fileUrl);\n\n case 10:\n response = _context3.sent;\n\n if (response.ok) {\n _context3.next = 13;\n break;\n }\n\n throw new Error('下载文件失败');\n\n case 13:\n _context3.next = 15;\n return response.blob();\n\n case 15:\n blob = _context3.sent;\n _this8.pdfBlobUrl = URL.createObjectURL(blob);\n _context3.next = 23;\n break;\n\n case 19:\n _context3.prev = 19;\n _context3.t0 = _context3[\"catch\"](0);\n console.error('加载 PDF 文件失败', _context3.t0);\n\n _this8.$message.error('PDF 文件加载失败');\n\n case 23:\n case \"end\":\n return _context3.stop();\n }\n }\n }, _callee3, null, [[0, 19]]);\n }))();\n },\n\n /**\r\n * 索引 / 重建索引(二次确认)\r\n */\n handleIndex: function handleIndex(row) {\n var _this9 = this;\n\n var isRebuild = row.indexStatus === 1;\n console.log('[DocumentList] 索引文件:', row.id, row.title, 'companyId:', this.companyId);\n this.$confirm(\"\\u786E\\u8BA4\\u5BF9\\u6587\\u4EF6\\u300C\".concat(row.title || row.fileName, \"\\u300D\").concat(isRebuild ? '重建' : '创建', \"\\u7D22\\u5F15\\uFF1F\"), '提示', {\n type: 'warning',\n confirmButtonText: '确定',\n cancelButtonText: '取消'\n }).then(function () {\n var request = isRebuild ? (0, _knowledgeBase.reindexFile)(row.id) : (0, _knowledgeBase.indexFile)(row.id);\n request.then(function () {\n _this9.$message.success(isRebuild ? '已开始重建索引' : '已开始索引');\n\n _this9.loadList();\n }).catch(function () {});\n }).catch(function () {});\n },\n\n /**\r\n * 删除索引(二次确认)\r\n */\n handleDeleteIndex: function handleDeleteIndex(row) {\n var _this10 = this;\n\n this.$confirm(\"\\u786E\\u8BA4\\u5220\\u9664\\u6587\\u4EF6\\u300C\".concat(row.title || row.fileName, \"\\u300D\\u7684\\u7D22\\u5F15\\uFF1F\\u5220\\u9664\\u540E\\u65E0\\u6CD5\\u6062\\u590D\\u3002\"), '提示', {\n type: 'warning',\n confirmButtonText: '确定删除',\n cancelButtonText: '取消'\n }).then(function () {\n (0, _knowledgeBase.deleteFileIndex)(row.id).then(function () {\n _this10.$message.success('索引已删除');\n\n _this10.loadList();\n }).catch(function () {});\n }).catch(function () {});\n },\n handleDelete: function handleDelete(row) {\n var _this11 = this;\n\n // 索引中的文件不允许删除\n if (row.indexStatus === 2) {\n this.$message.warning('该文件正在索引中,无法删除');\n return;\n }\n\n var hasIndex = row.indexStatus === 1;\n this.$confirm(\"\\u786E\\u8BA4\\u5220\\u9664\\u6587\\u4EF6\\u300C\".concat(row.title || row.fileName, \"\\u300D\\uFF1F\").concat(hasIndex ? '删除文件前会先删除该文件的索引。' : '', \"\\u5220\\u9664\\u540E\\u65E0\\u6CD5\\u6062\\u590D\\u3002\"), '提示', {\n type: 'warning',\n confirmButtonText: '确定删除',\n cancelButtonText: '取消'\n }).then( /*#__PURE__*/(0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee4() {\n return regeneratorRuntime.wrap(function _callee4$(_context4) {\n while (1) {\n switch (_context4.prev = _context4.next) {\n case 0:\n _context4.prev = 0;\n\n if (!hasIndex) {\n _context4.next = 4;\n break;\n }\n\n _context4.next = 4;\n return (0, _knowledgeBase.deleteFileIndex)(row.id);\n\n case 4:\n _context4.next = 6;\n return (0, _knowledgeBase.deleteFile)(row.id);\n\n case 6:\n _this11.$message.success('文件已删除');\n\n _this11.loadList();\n\n _context4.next = 12;\n break;\n\n case 10:\n _context4.prev = 10;\n _context4.t0 = _context4[\"catch\"](0);\n\n case 12:\n case \"end\":\n return _context4.stop();\n }\n }\n }, _callee4, null, [[0, 10]]);\n }))).catch(function () {});\n },\n\n /**\r\n * 表格勾选变化\r\n */\n handleSelectionChange: function handleSelectionChange(rows) {\n this.selectedRows = rows || [];\n },\n\n /**\r\n * 索引中的文件不可勾选删除\r\n */\n rowSelectable: function rowSelectable(row) {\n return row.indexStatus !== 2;\n },\n\n /**\r\n * 批量删除勾选的文件\r\n */\n handleBatchDelete: function handleBatchDelete() {\n var _this12 = this;\n\n var selected = this.selectedRows || [];\n\n if (!selected.length) {\n this.$message.warning('请先勾选要删除的文件');\n return;\n } // 索引中的文件不允许删除\n\n\n var indexingFiles = selected.filter(function (f) {\n return f.indexStatus === 2;\n });\n var deletable = selected.filter(function (f) {\n return f.indexStatus !== 2;\n });\n\n if (!deletable.length) {\n this.$message.warning('所选文件均正在索引中,无法删除');\n return;\n }\n\n if (indexingFiles.length) {\n this.$message.warning(\"\\u6709 \".concat(indexingFiles.length, \" \\u4E2A\\u6587\\u4EF6\\u6B63\\u5728\\u7D22\\u5F15\\u4E2D\\uFF0C\\u672C\\u6B21\\u5C06\\u8DF3\\u8FC7\"));\n } // 已索引的文件需先删除索引,再删除文件\n\n\n var indexed = deletable.filter(function (f) {\n return f.indexStatus === 1;\n });\n var ids = deletable.map(function (f) {\n return f.id;\n });\n this.$confirm(\"\\u786E\\u8BA4\\u5220\\u9664\\u9009\\u4E2D\\u7684 \".concat(deletable.length, \" \\u4E2A\\u6587\\u4EF6\\uFF1F\").concat(indexed.length ? '删除前会先删除这些文件的索引。' : '', \"\\u5220\\u9664\\u540E\\u65E0\\u6CD5\\u6062\\u590D\\u3002\"), '提示', {\n type: 'warning',\n confirmButtonText: '确定删除',\n cancelButtonText: '取消'\n }).then( /*#__PURE__*/(0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee5() {\n return regeneratorRuntime.wrap(function _callee5$(_context5) {\n while (1) {\n switch (_context5.prev = _context5.next) {\n case 0:\n _context5.prev = 0;\n\n if (!indexed.length) {\n _context5.next = 4;\n break;\n }\n\n _context5.next = 4;\n return Promise.all(indexed.map(function (f) {\n return (0, _knowledgeBase.deleteFileIndex)(f.id);\n }));\n\n case 4:\n _context5.next = 6;\n return (0, _knowledgeBase.deleteFiles)(ids);\n\n case 6:\n _this12.$message.success('批量删除成功');\n\n _this12.loadList();\n\n _context5.next = 12;\n break;\n\n case 10:\n _context5.prev = 10;\n _context5.t0 = _context5[\"catch\"](0);\n\n case 12:\n case \"end\":\n return _context5.stop();\n }\n }\n }, _callee5, null, [[0, 10]]);\n }))).catch(function () {});\n },\n\n /**\r\n * 批量移动:打开目标文件夹选择弹窗\r\n */\n handleBatchMove: function handleBatchMove() {\n var _this13 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee6() {\n var selected, folders;\n return regeneratorRuntime.wrap(function _callee6$(_context6) {\n while (1) {\n switch (_context6.prev = _context6.next) {\n case 0:\n selected = _this13.selectedRows || [];\n\n if (selected.length) {\n _context6.next = 4;\n break;\n }\n\n _this13.$message.warning('请先勾选要移动的文件');\n\n return _context6.abrupt(\"return\");\n\n case 4:\n if (_this13.companyId) {\n _context6.next = 7;\n break;\n }\n\n _this13.$message.warning('公司数据未加载');\n\n return _context6.abrupt(\"return\");\n\n case 7:\n _this13.moveFolderId = null;\n _this13.moveVisible = true; // 首次打开时加载文件夹列表\n\n if (_this13.folderList.length) {\n _context6.next = 19;\n break;\n }\n\n _context6.prev = 10;\n _context6.next = 13;\n return (0, _knowledgeBase.getFolderList)(_this13.companyId);\n\n case 13:\n folders = _context6.sent;\n _this13.folderList = folders || [];\n _context6.next = 19;\n break;\n\n case 17:\n _context6.prev = 17;\n _context6.t0 = _context6[\"catch\"](10);\n\n case 19:\n _this13.$nextTick(function () {\n if (_this13.$refs.moveTreeRef) _this13.$refs.moveTreeRef.setCurrentKey(null);\n });\n\n case 20:\n case \"end\":\n return _context6.stop();\n }\n }\n }, _callee6, null, [[10, 17]]);\n }))();\n },\n\n /**\r\n * 选择目标文件夹\r\n */\n handleMoveFolderSelect: function handleMoveFolderSelect(data) {\n this.moveFolderId = data.id;\n },\n\n /**\r\n * 确认批量移动\r\n */\n handleMoveConfirm: function handleMoveConfirm() {\n var _this14 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee7() {\n var ids;\n return regeneratorRuntime.wrap(function _callee7$(_context7) {\n while (1) {\n switch (_context7.prev = _context7.next) {\n case 0:\n if (_this14.moveFolderId) {\n _context7.next = 3;\n break;\n }\n\n _this14.$message.warning('请选择目标文件夹');\n\n return _context7.abrupt(\"return\");\n\n case 3:\n ids = _this14.selectedRows.map(function (f) {\n return f.id;\n });\n _this14.moving = true;\n _context7.prev = 5;\n _context7.next = 8;\n return (0, _knowledgeBase.moveFiles)(ids, _this14.moveFolderId);\n\n case 8:\n _this14.$message.success('批量移动成功');\n\n _this14.moveVisible = false;\n\n _this14.loadList();\n\n _context7.next = 15;\n break;\n\n case 13:\n _context7.prev = 13;\n _context7.t0 = _context7[\"catch\"](5);\n\n case 15:\n _context7.prev = 15;\n _this14.moving = false;\n return _context7.finish(15);\n\n case 18:\n case \"end\":\n return _context7.stop();\n }\n }\n }, _callee7, null, [[5, 13, 15, 18]]);\n }))();\n },\n\n /**\r\n * 通过 folderId 映射文件夹名称\r\n */\n folderName: function folderName(folderId) {\n if (!folderId) return '根目录';\n var found = this.categoryList.find(function (f) {\n return String(f.id) === String(folderId);\n });\n return found ? found.name : '-';\n },\n\n /**\r\n * 根据文件类型返回对应图标\r\n */\n getFileIcon: function getFileIcon(fileType) {\n var type = (fileType || '').toLowerCase();\n var iconMap = {\n pdf: 'el-icon-document',\n doc: 'el-icon-document',\n docx: 'el-icon-document',\n xls: 'el-icon-tickets',\n xlsx: 'el-icon-tickets',\n ppt: 'el-icon-data-line',\n pptx: 'el-icon-data-line',\n txt: 'el-icon-document',\n md: 'el-icon-edit-outline',\n png: 'el-icon-picture',\n jpg: 'el-icon-picture',\n jpeg: 'el-icon-picture',\n gif: 'el-icon-picture',\n zip: 'el-icon-folder-opened',\n rar: 'el-icon-folder-opened'\n };\n return iconMap[type] || 'el-icon-document';\n },\n\n /**\r\n * 根据文件类型返回对应的图标 URL\r\n */\n getFileIconUrl: function getFileIconUrl(fileType) {\n var type = (fileType || '').toLowerCase();\n var base = 'https://qiguanbang-client.obs.cn-east-3.myhuaweicloud.com/fileicom';\n var urlMap = {\n txt: \"\".concat(base, \"/txt-icon.png\"),\n md: \"\".concat(base, \"/md-icon.png\"),\n markdown: \"\".concat(base, \"/markdown-icon.png\"),\n html: \"\".concat(base, \"/html-icon.png\"),\n htm: \"\".concat(base, \"/htm-icon.png\"),\n pdf: \"\".concat(base, \"/pdf-icon.png\"),\n docx: \"\".concat(base, \"/docx-icon.png\"),\n doc: \"\".concat(base, \"/doc-icon.png\"),\n pptx: \"\".concat(base, \"/pptx-icon.png\"),\n ppt: \"\".concat(base, \"/ppt-icon.png\"),\n xlsx: \"\".concat(base, \"/xlsx-icon.png\"),\n xls: \"\".concat(base, \"/xls-icon.png\")\n };\n return urlMap[type] || \"\".concat(base, \"/txt-icon.png\");\n },\n\n /**\r\n * 文件大小格式化(字节 → B/KB/MB/GB)\r\n */\n formatSize: function formatSize(bytes) {\n if (!bytes && bytes !== 0) return '-';\n if (bytes === 0) return '0 B';\n var units = ['B', 'KB', 'MB', 'GB', 'TB'];\n var size = bytes;\n var idx = 0;\n\n while (size >= 1024 && idx < units.length - 1) {\n size /= 1024;\n idx++;\n }\n\n return \"\".concat(idx === 0 ? size : size.toFixed(2), \" \").concat(units[idx]);\n },\n\n /**\r\n * 时间展示:ISO 本地时间去掉 T 分隔符\r\n */\n formatTime: function formatTime(time) {\n if (!time) return '-';\n return String(time).replace('T', ' ').replace(/\\+08:00$/, '').replace(/\\.\\d{3}/, '');\n },\n\n /**\r\n * 索引状态标签:0 未索引 / 1 已索引 / 2 索引中 / 3 失败\r\n */\n indexTagType: function indexTagType(status) {\n var map = {\n 0: 'info',\n 1: 'success',\n 2: 'warning',\n 3: 'danger'\n };\n return map[status] || 'info';\n },\n indexTagText: function indexTagText(status) {\n var map = {\n 0: '未索引',\n 1: '已索引',\n 2: '索引中',\n 3: '索引失败'\n };\n return map[status] || '未知';\n }\n }\n};\nexports.default = _default2;\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/DocumentList.vue?./node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
992
992
|
|
|
993
993
|
/***/ }),
|
|
994
994
|
|
|
@@ -1000,7 +1000,7 @@ eval("\n\nvar _interopRequireWildcard = __webpack_require__(/*! ./node_modules/@
|
|
|
1000
1000
|
/***/ (function(module, exports, __webpack_require__) {
|
|
1001
1001
|
|
|
1002
1002
|
"use strict";
|
|
1003
|
-
eval("\n\nvar _interopRequireDefault = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/interopRequireDefault */ \"./node_modules/@babel/runtime/helpers/interopRequireDefault.js\").default;\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _toConsumableArray2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/toConsumableArray */ \"./node_modules/@babel/runtime/helpers/toConsumableArray.js\"));\n\nvar _asyncToGenerator2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/asyncToGenerator */ \"./node_modules/@babel/runtime/helpers/asyncToGenerator.js\"));\n\nvar _typeof2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/typeof */ \"./node_modules/@babel/runtime/helpers/typeof.js\"));\n\nvar _logo = _interopRequireDefault(__webpack_require__(/*! @/assets/logo.png */ \"./src/assets/logo.png\"));\n\nvar _kbChat = __webpack_require__(/*! ./api/kbChat */ \"./src/lib/knowledgeBase/api/kbChat.js\");\n\nvar _request = __webpack_require__(/*! @lib/utils/request.js */ \"./src/lib/utils/request.js\");\n\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\nvar AI_AVATAR_URL = 'https://qiguanbang.obs.cn-east-3.myhuaweicloud.com/production/image/AlAgent/knowledge_assistant.png';\nvar _default = {\n name: 'KbChatMain',\n props: {\n companyId: {\n type: String,\n default: ''\n },\n userno: {\n type: String,\n default: ''\n },\n imAccount: {\n type: [String, Object],\n default: ''\n }\n },\n computed: {\n suggestedQuestions: function suggestedQuestions() {\n return ['企业规章制度有哪些?', '公司福利政策是什么?'];\n },\n aiAvatar: function aiAvatar() {\n return AI_AVATAR_URL;\n },\n userAvatar: function userAvatar() {\n var account = this.imAccount;\n console.log('KbChatMain - imAccount 原始值:', account); // 尝试解析字符串为对象\n\n if (typeof account === 'string') {\n try {\n account = JSON.parse(account);\n console.log('KbChatMain - imAccount 解析后:', account);\n } catch (e) {\n // 如果解析失败,检查是否是纯 URL 字符串\n if (account.trim() && (account.includes('http://') || account.includes('https://'))) {\n console.log('KbChatMain - imAccount 是 URL:', account.trim());\n return account.trim();\n }\n }\n } // 如果是对象,尝试多个可能的头像字段(优先使用 headImg)\n\n\n if (account && (0, _typeof2.default)(account) === 'object') {\n var avatarFields = ['headImg', 'headPortrait', 'avatar', 'avatarUrl', 'headImage', 'photoUrl', 'portrait', 'headUrl', 'userHeadUrl', 'userAvatar'];\n\n for (var _i = 0, _avatarFields = avatarFields; _i < _avatarFields.length; _i++) {\n var field = _avatarFields[_i];\n\n if (account[field] && String(account[field]).trim()) {\n console.log(\"KbChatMain - \\u627E\\u5230\\u5934\\u50CF\\u5B57\\u6BB5 \".concat(field, \":\"), account[field]);\n return String(account[field]).trim();\n }\n }\n }\n\n console.log('KbChatMain - 使用默认头像');\n return _logo.default;\n }\n },\n data: function data() {\n return {\n initDone: false,\n activeSessionId: '',\n messages: [],\n retrievalSources: [],\n inputText: '',\n streaming: false,\n streamingAnswer: '',\n streamingThinking: '',\n eventSource: null,\n streamRequestId: 0\n };\n },\n watch: {\n companyId: function companyId() {\n this.initDone = false;\n this.initSession();\n },\n userno: function userno() {\n this.initDone = false;\n this.closeEventSource();\n this.activeSessionId = '';\n this.messages = [];\n this.retrievalSources = [];\n this.initSession();\n }\n },\n mounted: function mounted() {\n this.initSession();\n },\n beforeDestroy: function beforeDestroy() {\n this.closeEventSource();\n },\n methods: {\n escapeHtml: function escapeHtml(text) {\n if (!text) return '';\n var map = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": '''\n };\n return String(text).replace(/[&<>\"']/g, function (c) {\n return map[c];\n });\n },\n autoResizeTextarea: function autoResizeTextarea() {\n var ta = this.$refs.taRef;\n if (!ta) return;\n var baseH = 100;\n var maxH = 180;\n ta.style.height = \"\".concat(baseH, \"px\");\n var realH = ta.scrollHeight;\n\n if (realH > baseH) {\n ta.style.height = \"\".concat(Math.min(realH, maxH), \"px\");\n }\n },\n newLine: function newLine(e) {\n var _this = this;\n\n e.preventDefault();\n var ta = this.$refs.taRef;\n var start = ta.selectionStart;\n var end = ta.selectionEnd;\n this.inputText = this.inputText.slice(0, start) + '\\n' + this.inputText.slice(end);\n this.$nextTick(function () {\n ta.selectionStart = ta.selectionEnd = start + 1;\n\n _this.autoResizeTextarea();\n });\n },\n initSession: function initSession() {\n var _this2 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {\n var list;\n return regeneratorRuntime.wrap(function _callee$(_context) {\n while (1) {\n switch (_context.prev = _context.next) {\n case 0:\n if (_this2.companyId) {\n _context.next = 2;\n break;\n }\n\n return _context.abrupt(\"return\");\n\n case 2:\n if (!(_this2.initDone && (_this2.messages.length || _this2.streaming || _this2.inputText))) {\n _context.next = 4;\n break;\n }\n\n return _context.abrupt(\"return\");\n\n case 4:\n _this2.initDone = false;\n _context.prev = 5;\n _context.next = 8;\n return (0, _kbChat.getKbSessionList)(_this2.companyId);\n\n case 8:\n _context.t0 = _context.sent;\n\n if (_context.t0) {\n _context.next = 11;\n break;\n }\n\n _context.t0 = [];\n\n case 11:\n list = _context.t0;\n\n if (!(list.length > 0)) {\n _context.next = 17;\n break;\n }\n\n _context.next = 15;\n return _this2.selectSession(list[0].sessionId);\n\n case 15:\n _context.next = 19;\n break;\n\n case 17:\n _context.next = 19;\n return _this2.autoCreateSession();\n\n case 19:\n _context.next = 26;\n break;\n\n case 21:\n _context.prev = 21;\n _context.t1 = _context[\"catch\"](5);\n console.error('获取会话列表失败,尝试自动创建', _context.t1);\n _context.next = 26;\n return _this2.autoCreateSession();\n\n case 26:\n _context.prev = 26;\n _this2.initDone = true;\n return _context.finish(26);\n\n case 29:\n case \"end\":\n return _context.stop();\n }\n }\n }, _callee, null, [[5, 21, 26, 29]]);\n }))();\n },\n autoCreateSession: function autoCreateSession() {\n var _this3 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() {\n var data;\n return regeneratorRuntime.wrap(function _callee2$(_context2) {\n while (1) {\n switch (_context2.prev = _context2.next) {\n case 0:\n if (_this3.companyId) {\n _context2.next = 3;\n break;\n }\n\n console.warn('[KbChat] 无法创建会话:companyId 为空');\n return _context2.abrupt(\"return\");\n\n case 3:\n console.log('[KbChat] 创建会话,companyId:', _this3.companyId);\n _context2.prev = 4;\n _context2.next = 7;\n return (0, _kbChat.createKbSession)(_this3.companyId);\n\n case 7:\n data = _context2.sent;\n console.log('[KbChat] 会话创建成功,sessionId:', data.sessionId);\n _this3.activeSessionId = data.sessionId;\n _this3.streamRequestId++;\n _this3.messages = [];\n _this3.retrievalSources = [];\n _this3.streamingAnswer = '';\n _this3.streamingThinking = '';\n\n _this3.$nextTick(function () {\n return _this3.scrollToBottom();\n });\n\n _context2.next = 21;\n break;\n\n case 18:\n _context2.prev = 18;\n _context2.t0 = _context2[\"catch\"](4);\n console.error('自动创建会话失败', _context2.t0);\n\n case 21:\n case \"end\":\n return _context2.stop();\n }\n }\n }, _callee2, null, [[4, 18]]);\n }))();\n },\n selectSession: function selectSession(sessionId) {\n var _this4 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee3() {\n var raw, msgs;\n return regeneratorRuntime.wrap(function _callee3$(_context3) {\n while (1) {\n switch (_context3.prev = _context3.next) {\n case 0:\n if (_this4.streaming) _this4.handleStop();\n _this4.activeSessionId = sessionId;\n _this4.messages = [];\n _this4.retrievalSources = [];\n _this4.streamingAnswer = '';\n _this4.streamingThinking = '';\n _context3.prev = 6;\n _context3.next = 9;\n return (0, _kbChat.getKbSessionMessages)(sessionId);\n\n case 9:\n raw = _context3.sent;\n msgs = Array.isArray(raw) ? raw : raw.messages || raw.records || raw.list || [];\n _this4.messages = msgs.map(function (m) {\n return {\n role: m.role,\n content: m.content || '',\n thinking: m.thinking || m.reasoning || m.reasoning_result || m.reasoningContent || m.reasoning_content || m.thinkingContent || m.thinking_content || '',\n retrievalSources: _this4.normalizeSources(m.retrievalSources || m.sources || m.references || m.source)\n };\n });\n\n _this4.$nextTick(function () {\n return _this4.scrollToBottom();\n });\n\n _context3.next = 18;\n break;\n\n case 15:\n _context3.prev = 15;\n _context3.t0 = _context3[\"catch\"](6);\n console.error('加载消息失败', _context3.t0);\n\n case 18:\n case \"end\":\n return _context3.stop();\n }\n }\n }, _callee3, null, [[6, 15]]);\n }))();\n },\n handleResetSession: function handleResetSession() {\n var _this5 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee4() {\n return regeneratorRuntime.wrap(function _callee4$(_context4) {\n while (1) {\n switch (_context4.prev = _context4.next) {\n case 0:\n if (!_this5.activeSessionId) {\n _context4.next = 8;\n break;\n }\n\n _context4.prev = 1;\n _context4.next = 4;\n return (0, _kbChat.closeKbSession)(_this5.activeSessionId);\n\n case 4:\n _context4.next = 8;\n break;\n\n case 6:\n _context4.prev = 6;\n _context4.t0 = _context4[\"catch\"](1);\n\n case 8:\n _context4.next = 10;\n return _this5.autoCreateSession();\n\n case 10:\n case \"end\":\n return _context4.stop();\n }\n }\n }, _callee4, null, [[1, 6]]);\n }))();\n },\n handleSend: function handleSend(quickText) {\n var _this6 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee5() {\n var text, requestSessionId, requestId, url;\n return regeneratorRuntime.wrap(function _callee5$(_context5) {\n while (1) {\n switch (_context5.prev = _context5.next) {\n case 0:\n text = typeof quickText === 'string' ? quickText.trim() : _this6.inputText.trim();\n\n if (!(!text || !_this6.activeSessionId || _this6.streaming)) {\n _context5.next = 3;\n break;\n }\n\n return _context5.abrupt(\"return\");\n\n case 3:\n _this6.inputText = '';\n requestSessionId = _this6.activeSessionId;\n requestId = ++_this6.streamRequestId;\n\n _this6.messages.push({\n role: 'user',\n content: text\n });\n\n _this6.retrievalSources = [];\n\n _this6.$nextTick(function () {\n return _this6.scrollToBottom();\n });\n\n _this6.streaming = true;\n _this6.streamingAnswer = '';\n _this6.streamingThinking = '';\n url = (0, _kbChat.createKbStreamUrl)(_this6.activeSessionId, text);\n _this6.eventSource = new EventSource(url);\n\n _this6.eventSource.addEventListener('retrieval', function (e) {\n if (requestId !== _this6.streamRequestId || requestSessionId !== _this6.activeSessionId) return;\n\n try {\n var _this6$retrievalSourc;\n\n var sources = _this6.normalizeSources(JSON.parse(e.data));\n\n (_this6$retrievalSourc = _this6.retrievalSources).push.apply(_this6$retrievalSourc, (0, _toConsumableArray2.default)(sources));\n\n _this6.$nextTick(function () {\n return _this6.scrollToBottom();\n });\n } catch (err) {\n console.error('retrieval parse error', err);\n }\n });\n\n _this6.eventSource.addEventListener('answer', function (e) {\n if (requestId !== _this6.streamRequestId || requestSessionId !== _this6.activeSessionId) return;\n _this6.streamingAnswer += e.data;\n\n _this6.$nextTick(function () {\n return _this6.scrollToBottom();\n });\n });\n\n _this6.eventSource.addEventListener('reasoning', function (e) {\n if (requestId !== _this6.streamRequestId || requestSessionId !== _this6.activeSessionId) return;\n _this6.streamingThinking += e.data;\n\n _this6.$nextTick(function () {\n return _this6.scrollToBottom();\n });\n });\n\n _this6.eventSource.addEventListener('reasoning_result', function (e) {\n if (requestId !== _this6.streamRequestId || requestSessionId !== _this6.activeSessionId) return;\n _this6.streamingThinking = e.data;\n\n _this6.$nextTick(function () {\n return _this6.scrollToBottom();\n });\n });\n\n _this6.eventSource.addEventListener('done', function () {\n if (_this6.streamingAnswer || _this6.streamingThinking || _this6.retrievalSources.length) {\n _this6.messages.push({\n role: 'assistant',\n content: _this6.streamingAnswer,\n thinking: _this6.streamingThinking,\n retrievalSources: _this6.retrievalSources.slice()\n });\n }\n\n _this6.finishStream();\n });\n\n _this6.eventSource.addEventListener('error', function () {\n if (requestId !== _this6.streamRequestId || requestSessionId !== _this6.activeSessionId) return;\n\n if (_this6.eventSource && _this6.eventSource.readyState === EventSource.CLOSED) {\n if (_this6.streamingAnswer || _this6.streamingThinking || _this6.retrievalSources.length) {\n _this6.messages.push({\n role: 'assistant',\n content: _this6.streamingAnswer,\n thinking: _this6.streamingThinking,\n retrievalSources: _this6.retrievalSources.slice()\n });\n }\n\n _this6.finishStream();\n }\n });\n\n case 20:\n case \"end\":\n return _context5.stop();\n }\n }\n }, _callee5);\n }))();\n },\n handleStop: function handleStop() {\n this.streamRequestId++;\n\n if (this.activeSessionId) {\n (0, _kbChat.stopKbStream)(this.activeSessionId).catch(function () {});\n }\n\n this.appendStreamingAssistant();\n this.finishStream();\n },\n appendStreamingAssistant: function appendStreamingAssistant() {\n if (this.streamingAnswer || this.streamingThinking || this.retrievalSources.length) {\n this.messages.push({\n role: 'assistant',\n content: this.streamingAnswer || '',\n thinking: this.streamingThinking || '',\n retrievalSources: this.retrievalSources.slice()\n });\n }\n },\n finishStream: function finishStream() {\n var _this7 = this;\n\n this.closeEventSource();\n this.streaming = false;\n this.streamingAnswer = '';\n this.streamingThinking = '';\n this.$nextTick(function () {\n return _this7.scrollToBottom();\n });\n },\n closeEventSource: function closeEventSource() {\n if (this.eventSource) {\n this.eventSource.close();\n this.eventSource = null;\n }\n },\n sourceItems: function sourceItems(msg) {\n var _this8 = this;\n\n var seen = new Set();\n return this.normalizeSources(msg && msg.retrievalSources).filter(function (source) {\n var fileName = _this8.sourceFileName(source);\n\n if (!fileName || seen.has(fileName)) return false;\n seen.add(fileName);\n return true;\n });\n },\n sourceText: function sourceText(msg) {\n var _this9 = this;\n\n return this.sourceItems(msg).map(function (source) {\n return _this9.sourceFileName(source);\n }).join('、');\n },\n normalizeSources: function normalizeSources(raw) {\n var _this10 = this;\n\n if (!raw) return [];\n var value = raw;\n\n if (typeof value === 'string') {\n try {\n value = JSON.parse(value);\n } catch (e) {\n return [];\n }\n }\n\n if (!Array.isArray(value)) value = value.sources || value.references || value.source || [value];\n if (!Array.isArray(value)) value = [value];\n return value.reduce(function (result, item) {\n if (!item) return result;\n if (Array.isArray(item)) return result.concat(_this10.normalizeSources(item));\n\n if (item.sources || item.references || item.source) {\n return result.concat(_this10.normalizeSources(item.sources || item.references || item.source));\n }\n\n var source = item.docTitle || item.fileName || item.title || item.documentName || item.name || item.contextPath ? item : item.document || item.metadata;\n if (source && (source.docTitle || source.fileName || source.title || source.documentName || source.name || source.contextPath)) result.push(source);\n return result;\n }, []);\n },\n sourceFileName: function sourceFileName(source) {\n if (!source) return '';\n var value = source.docTitle || source.fileName || source.title || source.documentName || source.name || source.contextPath || '';\n return String(value).split(/[\\\\/]/).pop();\n },\n copyText: function copyText(text) {\n if (navigator.clipboard) {\n navigator.clipboard.writeText(text);\n }\n },\n scrollToBottom: function scrollToBottom() {\n var el = this.$refs.messagesRef;\n\n if (el) {\n el.scrollTop = el.scrollHeight;\n }\n }\n }\n};\nexports.default = _default;\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/KbChatMain.vue?./node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
1003
|
+
eval("\n\nvar _interopRequireDefault = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/interopRequireDefault */ \"./node_modules/@babel/runtime/helpers/interopRequireDefault.js\").default;\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _toConsumableArray2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/toConsumableArray */ \"./node_modules/@babel/runtime/helpers/toConsumableArray.js\"));\n\nvar _asyncToGenerator2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/asyncToGenerator */ \"./node_modules/@babel/runtime/helpers/asyncToGenerator.js\"));\n\nvar _typeof2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/typeof */ \"./node_modules/@babel/runtime/helpers/typeof.js\"));\n\nvar _logo = _interopRequireDefault(__webpack_require__(/*! @/assets/logo.png */ \"./src/assets/logo.png\"));\n\nvar _markdownIt = _interopRequireDefault(__webpack_require__(/*! markdown-it */ \"./node_modules/markdown-it/index.js\"));\n\nvar _kbChat = __webpack_require__(/*! ./api/kbChat */ \"./src/lib/knowledgeBase/api/kbChat.js\");\n\nvar _request = __webpack_require__(/*! @lib/utils/request.js */ \"./src/lib/utils/request.js\");\n\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\nvar AI_AVATAR_URL = 'https://qiguanbang.obs.cn-east-3.myhuaweicloud.com/production/image/AlAgent/doc_query.png'; // 与 ChatDetail 使用相同的 Markdown 配置\n\nvar md = new _markdownIt.default({\n breaks: true,\n html: false,\n linkify: true,\n typographer: true\n});\nmd.enable('table');\nvar _default = {\n name: 'KbChatMain',\n props: {\n companyId: {\n type: String,\n default: ''\n },\n userno: {\n type: String,\n default: ''\n },\n imAccount: {\n type: [String, Object],\n default: ''\n }\n },\n computed: {\n suggestedQuestions: function suggestedQuestions() {\n return ['企业规章制度有哪些?', '公司福利政策是什么?'];\n },\n aiAvatar: function aiAvatar() {\n return AI_AVATAR_URL;\n },\n userAvatar: function userAvatar() {\n var account = this.imAccount;\n console.log('KbChatMain - imAccount 原始值:', account); // 尝试解析字符串为对象\n\n if (typeof account === 'string') {\n try {\n account = JSON.parse(account);\n console.log('KbChatMain - imAccount 解析后:', account);\n } catch (e) {\n // 如果解析失败,检查是否是纯 URL 字符串\n if (account.trim() && (account.includes('http://') || account.includes('https://'))) {\n console.log('KbChatMain - imAccount 是 URL:', account.trim());\n return account.trim();\n }\n }\n } // 如果是对象,尝试多个可能的头像字段(优先使用 headImg)\n\n\n if (account && (0, _typeof2.default)(account) === 'object') {\n var avatarFields = ['headImg', 'headPortrait', 'avatar', 'avatarUrl', 'headImage', 'photoUrl', 'portrait', 'headUrl', 'userHeadUrl', 'userAvatar'];\n\n for (var _i = 0, _avatarFields = avatarFields; _i < _avatarFields.length; _i++) {\n var field = _avatarFields[_i];\n\n if (account[field] && String(account[field]).trim()) {\n console.log(\"KbChatMain - \\u627E\\u5230\\u5934\\u50CF\\u5B57\\u6BB5 \".concat(field, \":\"), account[field]);\n return String(account[field]).trim();\n }\n }\n }\n\n console.log('KbChatMain - 使用默认头像');\n return _logo.default;\n }\n },\n data: function data() {\n return {\n initDone: false,\n activeSessionId: '',\n messages: [],\n retrievalSources: [],\n inputText: '',\n streaming: false,\n streamMsg: null,\n showToolInfo: false,\n contentCache: {},\n eventSource: null,\n streamRequestId: 0\n };\n },\n watch: {\n companyId: function companyId() {\n this.initDone = false;\n this.finishStream();\n this.initSession();\n },\n userno: function userno() {\n this.initDone = false;\n this.finishStream();\n this.activeSessionId = '';\n this.messages = [];\n this.retrievalSources = [];\n this.initSession();\n }\n },\n mounted: function mounted() {\n this.initSession();\n },\n beforeDestroy: function beforeDestroy() {\n this.streamRequestId++;\n this.closeEventSource();\n this.clearTypeTimer();\n this.contentCache = {};\n },\n methods: {\n renderMarkdown: function renderMarkdown(text) {\n if (!text) return '';\n\n try {\n return md.render(text);\n } catch (err) {\n return this.escapeHtml(text);\n }\n },\n // 与 ChatDetail 一致的增量渲染:完整行走 markdown、未完成尾行转义拼接,\n // 借助 markdown-it breaks:true 实现逐行浮现效果。\n renderStreaming: function renderStreaming(text, msgKey) {\n if (!text) return '';\n var cache = this.contentCache[msgKey];\n var lines = text.split('\\n');\n var lastLineIdx = lines.length - 1;\n\n if (!cache) {\n try {\n var html = md.render(text);\n this.$set(this.contentCache, msgKey, {\n renderedLines: lastLineIdx,\n html: html,\n fullText: text\n });\n return html;\n } catch (_unused) {\n return this.escapeHtml(text);\n }\n }\n\n if (text === cache.fullText) return cache.html;\n cache.fullText = text; // 行数没变,仅末尾追加字符,直接拼接尾行\n\n if (lastLineIdx === cache.renderedLines) {\n return cache.html + this.escapeHtml(lines[lastLineIdx]);\n } // 行数变少/文本回退截断,全量重渲染\n\n\n if (lastLineIdx < cache.renderedLines) {\n try {\n var fullLines = lines.slice(0, lastLineIdx);\n var newBaseHtml = '';\n\n if (fullLines.length > 0) {\n newBaseHtml = md.render(fullLines.join('\\n'));\n }\n\n cache.html = newBaseHtml;\n cache.renderedLines = lastLineIdx;\n\n var _lastLine = this.escapeHtml(lines[lastLineIdx]);\n\n return cache.html + (_lastLine || '');\n } catch (_unused2) {\n return this.escapeHtml(text);\n }\n } // 新增完整行\n\n\n if (cache.renderedLines < lastLineIdx) {\n var allFullLines = lines.slice(0, lastLineIdx);\n var _newBaseHtml = '';\n\n if (allFullLines.length > 0) {\n _newBaseHtml = md.render(allFullLines.join('\\n'));\n }\n\n cache.html = _newBaseHtml;\n cache.renderedLines = lastLineIdx;\n }\n\n var lastLine = this.escapeHtml(lines[lastLineIdx]);\n cache.renderedLines = lastLineIdx;\n return cache.html + (lastLine || '');\n },\n escapeHtml: function escapeHtml(text) {\n if (!text) return '';\n var map = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": '''\n };\n return String(text).replace(/[&<>\"']/g, function (c) {\n return map[c];\n });\n },\n autoResizeTextarea: function autoResizeTextarea() {\n var ta = this.$refs.taRef;\n if (!ta) return;\n var baseH = 100;\n var maxH = 180;\n ta.style.height = \"\".concat(baseH, \"px\");\n var realH = ta.scrollHeight;\n\n if (realH > baseH) {\n ta.style.height = \"\".concat(Math.min(realH, maxH), \"px\");\n }\n },\n newLine: function newLine(e) {\n var _this = this;\n\n e.preventDefault();\n var ta = this.$refs.taRef;\n var start = ta.selectionStart;\n var end = ta.selectionEnd;\n this.inputText = this.inputText.slice(0, start) + '\\n' + this.inputText.slice(end);\n this.$nextTick(function () {\n ta.selectionStart = ta.selectionEnd = start + 1;\n\n _this.autoResizeTextarea();\n });\n },\n initSession: function initSession() {\n var _this2 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {\n var list;\n return regeneratorRuntime.wrap(function _callee$(_context) {\n while (1) {\n switch (_context.prev = _context.next) {\n case 0:\n if (_this2.companyId) {\n _context.next = 2;\n break;\n }\n\n return _context.abrupt(\"return\");\n\n case 2:\n if (!(_this2.initDone && (_this2.messages.length || _this2.streaming || _this2.inputText))) {\n _context.next = 4;\n break;\n }\n\n return _context.abrupt(\"return\");\n\n case 4:\n _this2.initDone = false;\n _context.prev = 5;\n _context.next = 8;\n return (0, _kbChat.getKbSessionList)(_this2.companyId);\n\n case 8:\n _context.t0 = _context.sent;\n\n if (_context.t0) {\n _context.next = 11;\n break;\n }\n\n _context.t0 = [];\n\n case 11:\n list = _context.t0;\n\n if (!(list.length > 0)) {\n _context.next = 17;\n break;\n }\n\n _context.next = 15;\n return _this2.selectSession(list[0].sessionId);\n\n case 15:\n _context.next = 19;\n break;\n\n case 17:\n _context.next = 19;\n return _this2.autoCreateSession();\n\n case 19:\n _context.next = 26;\n break;\n\n case 21:\n _context.prev = 21;\n _context.t1 = _context[\"catch\"](5);\n console.error('获取会话列表失败,尝试自动创建', _context.t1);\n _context.next = 26;\n return _this2.autoCreateSession();\n\n case 26:\n _context.prev = 26;\n _this2.initDone = true;\n return _context.finish(26);\n\n case 29:\n case \"end\":\n return _context.stop();\n }\n }\n }, _callee, null, [[5, 21, 26, 29]]);\n }))();\n },\n autoCreateSession: function autoCreateSession() {\n var _this3 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() {\n var data;\n return regeneratorRuntime.wrap(function _callee2$(_context2) {\n while (1) {\n switch (_context2.prev = _context2.next) {\n case 0:\n if (_this3.companyId) {\n _context2.next = 3;\n break;\n }\n\n console.warn('[KbChat] 无法创建会话:companyId 为空');\n return _context2.abrupt(\"return\");\n\n case 3:\n console.log('[KbChat] 创建会话,companyId:', _this3.companyId);\n _context2.prev = 4;\n _context2.next = 7;\n return (0, _kbChat.createKbSession)(_this3.companyId);\n\n case 7:\n data = _context2.sent;\n console.log('[KbChat] 会话创建成功,sessionId:', data.sessionId);\n\n _this3.finishStream();\n\n _this3.activeSessionId = data.sessionId;\n _this3.streamRequestId++;\n _this3.messages = [];\n _this3.retrievalSources = [];\n\n _this3.$nextTick(function () {\n return _this3.scrollToBottom();\n });\n\n _context2.next = 20;\n break;\n\n case 17:\n _context2.prev = 17;\n _context2.t0 = _context2[\"catch\"](4);\n console.error('自动创建会话失败', _context2.t0);\n\n case 20:\n case \"end\":\n return _context2.stop();\n }\n }\n }, _callee2, null, [[4, 17]]);\n }))();\n },\n selectSession: function selectSession(sessionId) {\n var _this4 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee3() {\n var raw, msgs;\n return regeneratorRuntime.wrap(function _callee3$(_context3) {\n while (1) {\n switch (_context3.prev = _context3.next) {\n case 0:\n if (_this4.streaming) _this4.handleStop();\n _this4.activeSessionId = sessionId;\n _this4.messages = [];\n _this4.retrievalSources = [];\n _this4.streamMsg = null;\n _context3.prev = 5;\n _context3.next = 8;\n return (0, _kbChat.getKbSessionMessages)(sessionId);\n\n case 8:\n raw = _context3.sent;\n msgs = Array.isArray(raw) ? raw : raw.messages || raw.records || raw.list || [];\n _this4.messages = msgs.map(function (m, i) {\n return {\n role: m.role,\n content: m.content || '',\n thinking: m.thinking || m.reasoning || m.reasoning_result || m.reasoningContent || m.reasoning_content || m.thinkingContent || m.thinking_content || '',\n retrievalSources: _this4.normalizeSources(m.retrievalSources || m.sources || m.references || m.source),\n _key: m.createTime ? 'msg_' + m.createTime + '_' + i : 'msg_' + Date.now() + '_' + Math.random()\n };\n });\n\n _this4.$nextTick(function () {\n return _this4.scrollToBottom();\n });\n\n _context3.next = 17;\n break;\n\n case 14:\n _context3.prev = 14;\n _context3.t0 = _context3[\"catch\"](5);\n console.error('加载消息失败', _context3.t0);\n\n case 17:\n case \"end\":\n return _context3.stop();\n }\n }\n }, _callee3, null, [[5, 14]]);\n }))();\n },\n handleResetSession: function handleResetSession() {\n var _this5 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee4() {\n return regeneratorRuntime.wrap(function _callee4$(_context4) {\n while (1) {\n switch (_context4.prev = _context4.next) {\n case 0:\n if (!_this5.activeSessionId) {\n _context4.next = 8;\n break;\n }\n\n _context4.prev = 1;\n _context4.next = 4;\n return (0, _kbChat.closeKbSession)(_this5.activeSessionId);\n\n case 4:\n _context4.next = 8;\n break;\n\n case 6:\n _context4.prev = 6;\n _context4.t0 = _context4[\"catch\"](1);\n\n case 8:\n _context4.next = 10;\n return _this5.autoCreateSession();\n\n case 10:\n case \"end\":\n return _context4.stop();\n }\n }\n }, _callee4, null, [[1, 6]]);\n }))();\n },\n handleSend: function handleSend(quickText) {\n var _this6 = this;\n\n return (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee5() {\n var text, requestSessionId, requestId, msgKey, msgIdx, streamMsg, isCurrentStream, answerBuffer, finalContent, streamDone, url;\n return regeneratorRuntime.wrap(function _callee5$(_context5) {\n while (1) {\n switch (_context5.prev = _context5.next) {\n case 0:\n text = typeof quickText === 'string' ? quickText.trim() : _this6.inputText.trim();\n\n if (!(!text || !_this6.activeSessionId || _this6.streaming)) {\n _context5.next = 3;\n break;\n }\n\n return _context5.abrupt(\"return\");\n\n case 3:\n _this6.inputText = '';\n requestSessionId = _this6.activeSessionId;\n requestId = ++_this6.streamRequestId;\n\n _this6.clearTypeTimer();\n\n _this6.messages.push({\n role: 'user',\n content: text\n });\n\n _this6.retrievalSources = [];\n\n _this6.$nextTick(function () {\n return _this6.scrollToBottom();\n }); // 流式消息:作为 messages 中的对象(带 _key/loading),与 ChatDetail 一致\n\n\n msgKey = 'stream_' + requestId;\n msgIdx = _this6.messages.push({\n role: 'assistant',\n content: '',\n thinking: '',\n toolCalls: [],\n retrievalSources: [],\n loading: true,\n _key: msgKey\n }) - 1;\n streamMsg = _this6.messages[msgIdx];\n _this6.streamMsg = streamMsg;\n _this6.streaming = true;\n\n isCurrentStream = function isCurrentStream() {\n return requestId === _this6.streamRequestId && requestSessionId === _this6.activeSessionId && _this6.streamMsg === streamMsg;\n }; // SSE 只把数据累积到 answerBuffer。typeTimer(30ms) 节流按「完整换行」逐行把内容\n // 推进到 msg.content:每个 tick 最多只推进「到下一个 \\n 为止」的一行,未完成的尾行\n // 保持不动;配合 renderStreaming(breaks:true) 实现「一行显示一行」。\n // 不是打字机模式(不逐字)、也不是一次性整段渲染(不会一出就是全文)。\n\n\n answerBuffer = '';\n finalContent = '';\n streamDone = false;\n _this6._typeTimer = setInterval(function () {\n if (!isCurrentStream()) return;\n var msg = _this6.messages[msgIdx];\n var source = finalContent || answerBuffer;\n var curLen = msg._prevReveal || 0;\n var nextLen = curLen; // 找到「当前已揭示进度之后」的第一条完整换行,仅推进这一行\n\n var nl = source.indexOf('\\n', curLen);\n\n if (nl !== -1) {\n nextLen = nl + 1;\n } else if (streamDone) {\n // 流结束且没有更多换行:最后一段未以换行结尾的内容一次性露出(此时即完整一行)\n nextLen = source.length;\n }\n\n if (nextLen > curLen) {\n msg._prevReveal = nextLen;\n msg.content = source.substring(0, nextLen);\n\n _this6.$nextTick(function () {\n return _this6.scrollToBottom();\n });\n } // 流结束且所有内容都已逐行露出 → 收尾\n\n\n if (streamDone && nextLen >= source.length) {\n _this6.finishStream();\n }\n }, 30);\n url = (0, _kbChat.createKbStreamUrl)(_this6.activeSessionId, text);\n _this6.eventSource = new EventSource(url);\n\n _this6.eventSource.addEventListener('retrieval', function (e) {\n if (!isCurrentStream() || streamDone) return;\n\n try {\n var _streamMsg$retrievalS;\n\n var sources = _this6.normalizeSources(JSON.parse(e.data));\n\n (_streamMsg$retrievalS = streamMsg.retrievalSources).push.apply(_streamMsg$retrievalS, (0, _toConsumableArray2.default)(sources));\n\n _this6.$nextTick(function () {\n return _this6.scrollToBottom();\n });\n } catch (err) {\n console.error('retrieval parse error', err);\n }\n });\n\n _this6.eventSource.addEventListener('tool_result', function (e) {\n if (!isCurrentStream() || streamDone) return;\n\n if (e.data && _this6.messages[msgIdx]) {\n if (!_this6.messages[msgIdx].toolCalls) _this6.messages[msgIdx].toolCalls = [];\n\n _this6.messages[msgIdx].toolCalls.push(e.data);\n\n _this6.$nextTick(function () {\n return _this6.scrollToBottom();\n });\n }\n });\n\n _this6.eventSource.addEventListener('answer', function (e) {\n if (!isCurrentStream() || streamDone) return;\n answerBuffer += e.data;\n });\n\n _this6.eventSource.addEventListener('answer_result', function (e) {\n if (!isCurrentStream() || streamDone) return; // 兼容保留:若后端未来补发 answer_result,则以它为最终真相源。\n // 只记录 finalContent 并结束流,不打断已在逐行推进的 content,交给 typeTimer 以 target 推进/校正。\n\n if (e.data) finalContent = e.data;\n streamDone = true;\n\n _this6.closeEventSource();\n });\n\n _this6.eventSource.addEventListener('reasoning', function (e) {\n if (!isCurrentStream() || streamDone) return;\n streamMsg.thinking += e.data;\n\n _this6.$nextTick(function () {\n return _this6.scrollToBottom();\n });\n });\n\n _this6.eventSource.addEventListener('reasoning_result', function (e) {\n if (!isCurrentStream() || streamDone) return;\n streamMsg.thinking = e.data;\n\n _this6.$nextTick(function () {\n return _this6.scrollToBottom();\n });\n });\n\n _this6.eventSource.addEventListener('done', function () {\n if (!isCurrentStream() || streamDone) return; // 后端不在 AGENT_RESULT 时补发 answer_result,这里用「answer 累积的完整文本」\n // 作为最终真相源,交由 typeTimer 逐行推进到全文并收尾。\n\n if (!finalContent) finalContent = answerBuffer;\n streamDone = true;\n\n _this6.closeEventSource();\n });\n\n _this6.eventSource.addEventListener('error', function () {\n if (!isCurrentStream() || streamDone) return;\n if (!answerBuffer) answerBuffer = '❌ 连接中断';\n streamDone = true;\n\n _this6.closeEventSource();\n });\n\n case 30:\n case \"end\":\n return _context5.stop();\n }\n }\n }, _callee5);\n }))();\n },\n handleStop: function handleStop() {\n this.streamRequestId++;\n\n if (this.activeSessionId) {\n (0, _kbChat.stopKbStream)(this.activeSessionId).catch(function () {});\n }\n\n this.finishStream();\n },\n finishStream: function finishStream() {\n var _this7 = this;\n\n this.clearTypeTimer();\n\n if (this.streamMsg) {\n this.$delete(this.contentCache, this.streamMsg._key);\n this.streamMsg.loading = false;\n this.streamMsg = null;\n }\n\n this.closeEventSource();\n this.streaming = false;\n this.$nextTick(function () {\n return _this7.scrollToBottom();\n });\n },\n clearTypeTimer: function clearTypeTimer() {\n if (this._typeTimer) {\n clearInterval(this._typeTimer);\n this._typeTimer = null;\n }\n },\n closeEventSource: function closeEventSource() {\n if (this.eventSource) {\n this.eventSource.close();\n this.eventSource = null;\n }\n },\n normalizeSources: function normalizeSources(raw) {\n var _this8 = this;\n\n if (!raw) return [];\n var value = raw;\n\n if (typeof value === 'string') {\n try {\n value = JSON.parse(value);\n } catch (e) {\n return [];\n }\n }\n\n if (!Array.isArray(value)) value = value.sources || value.references || value.source || [value];\n if (!Array.isArray(value)) value = [value];\n return value.reduce(function (result, item) {\n if (!item) return result;\n if (Array.isArray(item)) return result.concat(_this8.normalizeSources(item));\n\n if (item.sources || item.references || item.source) {\n return result.concat(_this8.normalizeSources(item.sources || item.references || item.source));\n }\n\n var source = item.docTitle || item.fileName || item.title || item.documentName || item.name || item.contextPath ? item : item.document || item.metadata;\n if (source && (source.docTitle || source.fileName || source.title || source.documentName || source.name || source.contextPath)) result.push(source);\n return result;\n }, []);\n },\n copyText: function copyText(text) {\n if (navigator.clipboard) {\n navigator.clipboard.writeText(text);\n }\n },\n scrollToBottom: function scrollToBottom() {\n var el = this.$refs.messagesRef;\n\n if (el) {\n el.scrollTop = el.scrollHeight;\n }\n }\n }\n};\nexports.default = _default;\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/KbChatMain.vue?./node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
1004
1004
|
|
|
1005
1005
|
/***/ }),
|
|
1006
1006
|
|
|
@@ -1012,7 +1012,7 @@ eval("\n\nvar _interopRequireDefault = __webpack_require__(/*! ./node_modules/@b
|
|
|
1012
1012
|
/***/ (function(module, exports, __webpack_require__) {
|
|
1013
1013
|
|
|
1014
1014
|
"use strict";
|
|
1015
|
-
eval("\n\nvar _interopRequireDefault = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/interopRequireDefault */ \"./node_modules/@babel/runtime/helpers/interopRequireDefault.js\").default;\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _CategoryManager = _interopRequireDefault(__webpack_require__(/*! ./CategoryManager */ \"./src/lib/knowledgeBase/CategoryManager.vue\"));\n\nvar _DocumentList = _interopRequireDefault(__webpack_require__(/*! ./DocumentList */ \"./src/lib/knowledgeBase/DocumentList.vue\"));\n\nvar _KbChatMain = _interopRequireDefault(__webpack_require__(/*! ./KbChatMain */ \"./src/lib/knowledgeBase/KbChatMain.vue\"));\n\nvar _knowledgeBase = __webpack_require__(/*! ./api/knowledgeBase */ \"./src/lib/knowledgeBase/api/knowledgeBase.js\");\n\nvar _request = __webpack_require__(/*! @lib/utils/request.js */ \"./src/lib/utils/request.js\");\n\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\nvar _default = {\n name: 'ZjKnowledgeBase',\n components: {\n CategoryManager: _CategoryManager.default,\n DocumentList: _DocumentList.default,\n KbChatMain: _KbChatMain.default\n },\n props: {\n title: {\n type: String,\n default: '知识库'\n },\n Ak: {\n type: String,\n default: ''\n },\n Sk: {\n type: String,\n default: ''\n },\n imAccount: {\n type: [String, Object],\n default: ''\n },\n companyNo: {\n type: String,\n default: ''\n },\n chatImToken: {\n type: String,\n default: ''\n },\n userno: {\n type: String,\n default: ''\n },\n // 公司知识库固定公司ID(公司编号)。不传时自动使用第一家公司的ID\n companyId: {\n type: String,\n default: ''\n },\n\n /**\r\n * 是否有知识库管理权限\r\n * true → 显示知识库管理界面(文件夹树 + 文件列表 + 增删改查)\r\n * false → 显示知识库 AI 聊天界面(仅对话查询,不可管理文档)\r\n */\n hasPermission: {\n type: Boolean,\n default: true\n }\n },\n data: function data() {\n return {\n viewMode: 'manage',\n // 'manage' | 'chat'\n currentCategory: null,\n categoryList: [],\n currentCompanyId: this.companyId || this.companyNo || ''\n };\n },\n watch: {\n companyId: function companyId(val) {\n if (val) {\n this.currentCompanyId = val;\n this.refreshAuth();\n }\n },\n companyNo: function companyNo(val) {\n if (val) {\n this.currentCompanyId = val;\n }\n\n this.refreshAuth();\n },\n userno: function userno() {\n this.refreshAuth();\n }\n },\n created: function created() {\n this.refreshAuth();\n\n if (!this.currentCompanyId) {\n this.loadCompanies();\n }\n },\n methods: {\n refreshAuth: function refreshAuth() {\n if (this.Ak || this.Sk) {\n (0, _request.
|
|
1015
|
+
eval("\n\nvar _interopRequireDefault = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/interopRequireDefault */ \"./node_modules/@babel/runtime/helpers/interopRequireDefault.js\").default;\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _CategoryManager = _interopRequireDefault(__webpack_require__(/*! ./CategoryManager */ \"./src/lib/knowledgeBase/CategoryManager.vue\"));\n\nvar _DocumentList = _interopRequireDefault(__webpack_require__(/*! ./DocumentList */ \"./src/lib/knowledgeBase/DocumentList.vue\"));\n\nvar _KbChatMain = _interopRequireDefault(__webpack_require__(/*! ./KbChatMain */ \"./src/lib/knowledgeBase/KbChatMain.vue\"));\n\nvar _knowledgeBase = __webpack_require__(/*! ./api/knowledgeBase */ \"./src/lib/knowledgeBase/api/knowledgeBase.js\");\n\nvar _request = __webpack_require__(/*! @lib/utils/request.js */ \"./src/lib/utils/request.js\");\n\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\nvar _default = {\n name: 'ZjKnowledgeBase',\n components: {\n CategoryManager: _CategoryManager.default,\n DocumentList: _DocumentList.default,\n KbChatMain: _KbChatMain.default\n },\n props: {\n title: {\n type: String,\n default: '知识库'\n },\n Ak: {\n type: String,\n default: ''\n },\n Sk: {\n type: String,\n default: ''\n },\n imAccount: {\n type: [String, Object],\n default: ''\n },\n companyNo: {\n type: String,\n default: ''\n },\n chatImToken: {\n type: String,\n default: ''\n },\n userno: {\n type: String,\n default: ''\n },\n // 公司知识库固定公司ID(公司编号)。不传时自动使用第一家公司的ID\n companyId: {\n type: String,\n default: ''\n },\n\n /**\r\n * 是否有知识库管理权限\r\n * true → 显示知识库管理界面(文件夹树 + 文件列表 + 增删改查)\r\n * false → 显示知识库 AI 聊天界面(仅对话查询,不可管理文档)\r\n */\n hasPermission: {\n type: Boolean,\n default: true\n }\n },\n data: function data() {\n return {\n viewMode: 'manage',\n // 'manage' | 'chat'\n currentCategory: null,\n categoryList: [],\n currentCompanyId: this.companyId || this.companyNo || ''\n };\n },\n watch: {\n companyId: function companyId(val) {\n if (val) {\n this.currentCompanyId = val;\n this.refreshAuth();\n }\n },\n companyNo: function companyNo(val) {\n if (val) {\n this.currentCompanyId = val;\n }\n\n this.refreshAuth();\n },\n userno: function userno() {\n this.refreshAuth();\n }\n },\n created: function created() {\n this.refreshAuth();\n\n if (!this.currentCompanyId) {\n this.loadCompanies();\n }\n },\n methods: {\n refreshAuth: function refreshAuth() {\n if (this.Ak || this.Sk) {\n (0, _request.setKbAuth)(this.Ak, this.Sk, this.userno, this.currentCompanyId || this.companyNo, this.chatImToken, this.userno);\n }\n },\n loadCompanies: function loadCompanies() {\n var _this = this;\n\n (0, _knowledgeBase.getCompanyList)().then(function (list) {\n var companies = list || [];\n\n if (companies.length > 0 && !_this.currentCompanyId) {\n _this.currentCompanyId = companies[0];\n }\n }).catch(function () {});\n },\n handleCategorySelect: function handleCategorySelect(category) {\n this.currentCategory = category;\n },\n setCategoryList: function setCategoryList(list) {\n this.categoryList = list;\n },\n handleRefresh: function handleRefresh() {\n if (!this.currentCompanyId) {\n this.loadCompanies();\n return;\n }\n\n if (this.$refs.categoryRef) {\n this.$refs.categoryRef.loadList();\n }\n\n if (this.$refs.docListRef) {\n this.$refs.docListRef.loadList();\n }\n }\n }\n};\nexports.default = _default;\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/KnowledgeBaseMain.vue?./node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
1016
1016
|
|
|
1017
1017
|
/***/ }),
|
|
1018
1018
|
|
|
@@ -1120,7 +1120,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) *
|
|
|
1120
1120
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
1121
1121
|
|
|
1122
1122
|
"use strict";
|
|
1123
|
-
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return staticRenderFns; });\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\n \"div\",\n { staticClass: \"kb-category-manager\" },\n [\n _c(\"div\", { staticClass: \"category-header\" }, [\n _c(\"span\", [_vm._v(\"知识库\")]),\n _c(\n \"div\",\n [\n _c(\"el-button\", {\n attrs: {\n size: \"mini\",\n type: \"text\",\n icon: \"el-icon-plus\",\n disabled: !_vm.companyId,\n title: \"新建根文件夹\"\n },\n on: {\n click: function($event) {\n return _vm.handleAdd(null)\n }\n }\n }),\n _c(\"el-button\", {\n attrs: {\n size: \"mini\",\n type: \"text\",\n icon: \"el-icon-setting\",\n title: \"管理文件夹\"\n },\n on: { click: _vm.showManageDialog }\n })\n ],\n 1\n )\n ]),\n _c(\n \"div\",\n {\n directives: [\n {\n name: \"loading\",\n rawName: \"v-loading\",\n value: _vm.loading,\n expression: \"loading\"\n }\n ],\n staticClass: \"category-list\"\n },\n [\n _c(\"el-tree\", {\n ref: \"treeRef\",\n attrs: {\n data: _vm.treeData,\n props: _vm.treeProps,\n \"node-key\": \"id\",\n \"highlight-current\": \"\",\n \"expand-on-click-node\": false,\n \"default-expand-all\": \"\",\n indent: 12\n },\n on: { \"node-click\": _vm.handleSelect },\n scopedSlots: _vm._u([\n {\n key: \"default\",\n fn: function(ref) {\n var data = ref.data\n return _c(\"span\", { staticClass: \"tree-node\" }, [\n _c(\"span\", { staticClass: \"tree-label\" }, [\n _c(\"i\", {\n staticClass: \"tree-icon\",\n class:\n data.parentId === 0\n ? \"el-icon-folder-opened\"\n : \"el-icon-folder\"\n }),\n _vm._v(\" \" + _vm._s(data.name) + \" \")\n ]),\n _c(\n \"span\",\n {\n staticClass: \"tree-actions\",\n on: {\n click: function($event) {\n $event.stopPropagation()\n }\n }\n },\n [\n _c(\"el-button\", {\n attrs: {\n size: \"mini\",\n type: \"text\",\n icon: \"el-icon-plus\",\n title: \"新增子文件夹\"\n },\n on: {\n click: function($event) {\n return _vm.handleAdd(data)\n }\n }\n }),\n _c(\"el-button\", {\n attrs: {\n size: \"mini\",\n type: \"text\",\n icon: \"el-icon-edit\",\n title: \"重命名\"\n },\n on: {\n click: function($event) {\n return _vm.handleEdit(data)\n }\n }\n }),\n _c(\"el-button\", {\n staticStyle: { color: \"#f56c6c\" },\n attrs: {\n size: \"mini\",\n type: \"text\",\n icon: \"el-icon-delete\",\n title: \"删除\"\n },\n on: {\n click: function($event) {\n return _vm.handleDelete(data)\n }\n }\n })\n ],\n 1\n )\n ])\n }\n }\n ])\n }),\n !_vm.loading && !_vm.companyId\n ? _c(\"div\", { staticClass: \"category-empty\" }, [\n _vm._v(\"暂无公司数据\")\n ])\n : !_vm.loading && _vm.treeData.length === 0\n ? _c(\"div\", { staticClass: \"category-empty\" }, [\n _vm._v(\"暂无文件夹\")\n ])\n : _vm._e()\n ],\n 1\n ),\n _c(\n \"el-button\",\n {\n staticClass: \"show-all-btn\",\n attrs: { type: \"text\", size: \"small\", disabled: !_vm.companyId },\n on: { click: _vm.handleSelectAll }\n },\n [_vm._v(\" 显示全部文件 \")]\n ),\n _c(\n \"el-dialog\",\n {\n attrs: {\n title: _vm.dialogType === \"add\" ? \"新增文件夹\" : \"重命名文件夹\",\n visible: _vm.dialogVisible,\n width: \"420px\",\n \"close-on-click-modal\": false\n },\n on: {\n \"update:visible\": function($event) {\n _vm.dialogVisible = $event\n }\n }\n },\n [\n _c(\n \"el-form\",\n {\n ref: \"folderForm\",\n attrs: {\n model: _vm.formData,\n rules: _vm.formRules,\n \"label-width\": \"80px\"\n }\n },\n [\n _vm.dialogType === \"add\"\n ? _c(\n \"el-form-item\",\n { attrs: { label: \"上级目录\" } },\n [\n _c(\n \"el-select\",\n {\n staticStyle: { width: \"100%\" },\n attrs: {\n placeholder: \"选择上级目录(默认根目录)\",\n clearable: \"\"\n },\n model: {\n value: _vm.formData.parentId,\n callback: function($$v) {\n _vm.$set(_vm.formData, \"parentId\", $$v)\n },\n expression: \"formData.parentId\"\n }\n },\n [\n _c(\"el-option\", {\n attrs: { label: \"根目录\", value: 0 }\n }),\n _vm._l(_vm.availableParents, function(f) {\n return _c(\"el-option\", {\n key: f.id,\n attrs: {\n label:\n f.name +\n \"(第\" +\n _vm.getFolderDepth(f) +\n \"层)\",\n value: f.id\n }\n })\n })\n ],\n 2\n )\n ],\n 1\n )\n : _vm._e(),\n _c(\n \"el-form-item\",\n { attrs: { label: \"名称\", prop: \"name\" } },\n [\n _c(\"el-input\", {\n attrs: { placeholder: \"请输入文件夹名称\", maxlength: \"50\" },\n model: {\n value: _vm.formData.name,\n callback: function($$v) {\n _vm.$set(_vm.formData, \"name\", $$v)\n },\n expression: \"formData.name\"\n }\n })\n ],\n 1\n ),\n _c(\n \"el-form-item\",\n { attrs: { label: \"排序\", prop: \"sortOrder\" } },\n [\n _c(\"el-input-number\", {\n attrs: { min: 0, max: 9999 },\n model: {\n value: _vm.formData.sortOrder,\n callback: function($$v) {\n _vm.$set(_vm.formData, \"sortOrder\", $$v)\n },\n expression: \"formData.sortOrder\"\n }\n })\n ],\n 1\n )\n ],\n 1\n ),\n _c(\n \"span\",\n { attrs: { slot: \"footer\" }, slot: \"footer\" },\n [\n _c(\n \"el-button\",\n {\n on: {\n click: function($event) {\n _vm.dialogVisible = false\n }\n }\n },\n [_vm._v(\"取消\")]\n ),\n _c(\n \"el-button\",\n {\n attrs: { type: \"primary\", loading: _vm.submitting },\n on: { click: _vm.handleSubmit }\n },\n [_vm._v(\"确定\")]\n )\n ],\n 1\n )\n ],\n 1\n )\n ],\n 1\n )\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/CategoryManager.vue?./node_modules/cache-loader/dist/cjs.js?%7B%22cacheDirectory%22:%22node_modules/.cache/vue-loader%22,%22cacheIdentifier%22:%2268609640-vue-loader-template%22%7D!./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
1123
|
+
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return staticRenderFns; });\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\n \"div\",\n { staticClass: \"kb-category-manager\" },\n [\n _c(\"div\", { staticClass: \"category-header\" }, [\n _c(\"span\", { staticClass: \"category-title\" }, [_vm._v(\"知识库\")]),\n _c(\n \"div\",\n [\n _c(\"el-button\", {\n attrs: {\n size: \"mini\",\n type: \"text\",\n icon: \"el-icon-plus\",\n disabled: !_vm.companyId,\n title: \"新建根文件夹\"\n },\n on: {\n click: function($event) {\n return _vm.handleAdd(null)\n }\n }\n })\n ],\n 1\n )\n ]),\n _c(\n \"div\",\n {\n directives: [\n {\n name: \"loading\",\n rawName: \"v-loading\",\n value: _vm.loading,\n expression: \"loading\"\n }\n ],\n staticClass: \"category-list\"\n },\n [\n _c(\"el-tree\", {\n ref: \"treeRef\",\n attrs: {\n data: _vm.treeData,\n props: _vm.treeProps,\n \"node-key\": \"id\",\n \"highlight-current\": \"\",\n \"expand-on-click-node\": false,\n \"default-expand-all\": \"\",\n indent: 12\n },\n on: { \"node-click\": _vm.handleSelect },\n scopedSlots: _vm._u([\n {\n key: \"default\",\n fn: function(ref) {\n var data = ref.data\n return _c(\"span\", { staticClass: \"tree-node\" }, [\n _c(\"span\", { staticClass: \"tree-label\" }, [\n _c(\"i\", {\n staticClass: \"tree-icon\",\n class:\n data.parentId === 0\n ? \"el-icon-folder-opened\"\n : \"el-icon-folder\"\n }),\n _vm._v(\" \" + _vm._s(data.name) + \" \")\n ]),\n _c(\n \"span\",\n {\n staticClass: \"tree-actions\",\n on: {\n click: function($event) {\n $event.stopPropagation()\n }\n }\n },\n [\n _c(\"el-button\", {\n attrs: {\n size: \"mini\",\n type: \"text\",\n icon: \"el-icon-plus\",\n title: \"新增子文件夹\"\n },\n on: {\n click: function($event) {\n return _vm.handleAdd(data)\n }\n }\n }),\n _c(\"el-button\", {\n attrs: {\n size: \"mini\",\n type: \"text\",\n icon: \"el-icon-edit\",\n title: \"重命名\"\n },\n on: {\n click: function($event) {\n return _vm.handleEdit(data)\n }\n }\n }),\n _c(\"el-button\", {\n staticStyle: { color: \"#f56c6c\" },\n attrs: {\n size: \"mini\",\n type: \"text\",\n icon: \"el-icon-delete\",\n title: \"删除\"\n },\n on: {\n click: function($event) {\n return _vm.handleDelete(data)\n }\n }\n })\n ],\n 1\n )\n ])\n }\n }\n ])\n }),\n !_vm.loading && !_vm.companyId\n ? _c(\"div\", { staticClass: \"category-empty\" }, [\n _vm._v(\"暂无公司数据\")\n ])\n : !_vm.loading && _vm.treeData.length === 0\n ? _c(\"div\", { staticClass: \"category-empty\" }, [\n _vm._v(\"暂无文件夹\")\n ])\n : _vm._e()\n ],\n 1\n ),\n _c(\n \"el-button\",\n {\n staticClass: \"show-all-btn\",\n attrs: { type: \"text\", size: \"small\", disabled: !_vm.companyId },\n on: { click: _vm.handleSelectAll }\n },\n [_vm._v(\" 显示全部文件 \")]\n ),\n _c(\n \"el-dialog\",\n {\n attrs: {\n title: _vm.dialogType === \"add\" ? \"新增文件夹\" : \"重命名文件夹\",\n visible: _vm.dialogVisible,\n width: \"420px\",\n \"close-on-click-modal\": false\n },\n on: {\n \"update:visible\": function($event) {\n _vm.dialogVisible = $event\n }\n }\n },\n [\n _c(\n \"el-form\",\n {\n ref: \"folderForm\",\n attrs: {\n model: _vm.formData,\n rules: _vm.formRules,\n \"label-width\": \"80px\"\n }\n },\n [\n _vm.dialogType === \"add\"\n ? _c(\n \"el-form-item\",\n { attrs: { label: \"上级目录\" } },\n [\n _c(\n \"el-select\",\n {\n staticStyle: { width: \"100%\" },\n attrs: {\n placeholder: \"选择上级目录(默认根目录)\",\n clearable: \"\"\n },\n model: {\n value: _vm.formData.parentId,\n callback: function($$v) {\n _vm.$set(_vm.formData, \"parentId\", $$v)\n },\n expression: \"formData.parentId\"\n }\n },\n [\n _c(\"el-option\", {\n attrs: { label: \"根目录\", value: 0 }\n }),\n _vm._l(_vm.availableParents, function(f) {\n return _c(\"el-option\", {\n key: f.id,\n attrs: { label: f.name, value: f.id }\n })\n })\n ],\n 2\n )\n ],\n 1\n )\n : _vm._e(),\n _c(\n \"el-form-item\",\n { attrs: { label: \"名称\", prop: \"name\" } },\n [\n _c(\"el-input\", {\n attrs: { placeholder: \"请输入文件夹名称\", maxlength: \"50\" },\n model: {\n value: _vm.formData.name,\n callback: function($$v) {\n _vm.$set(_vm.formData, \"name\", $$v)\n },\n expression: \"formData.name\"\n }\n })\n ],\n 1\n ),\n _c(\n \"el-form-item\",\n { attrs: { label: \"排序\", prop: \"sortOrder\" } },\n [\n _c(\"el-input-number\", {\n attrs: { min: 0, max: 9999 },\n model: {\n value: _vm.formData.sortOrder,\n callback: function($$v) {\n _vm.$set(_vm.formData, \"sortOrder\", $$v)\n },\n expression: \"formData.sortOrder\"\n }\n })\n ],\n 1\n )\n ],\n 1\n ),\n _c(\n \"span\",\n { attrs: { slot: \"footer\" }, slot: \"footer\" },\n [\n _c(\n \"el-button\",\n {\n on: {\n click: function($event) {\n _vm.dialogVisible = false\n }\n }\n },\n [_vm._v(\"取消\")]\n ),\n _c(\n \"el-button\",\n {\n attrs: { type: \"primary\", loading: _vm.submitting },\n on: { click: _vm.handleSubmit }\n },\n [_vm._v(\"确定\")]\n )\n ],\n 1\n )\n ],\n 1\n )\n ],\n 1\n )\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/CategoryManager.vue?./node_modules/cache-loader/dist/cjs.js?%7B%22cacheDirectory%22:%22node_modules/.cache/vue-loader%22,%22cacheIdentifier%22:%2268609640-vue-loader-template%22%7D!./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
1124
1124
|
|
|
1125
1125
|
/***/ }),
|
|
1126
1126
|
|
|
@@ -1132,7 +1132,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) *
|
|
|
1132
1132
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
1133
1133
|
|
|
1134
1134
|
"use strict";
|
|
1135
|
-
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return staticRenderFns; });\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\n \"el-dialog\",\n {\n attrs: {\n title: _vm.isEdit ? _vm.dialogTitle : \"新增文档\",\n visible: _vm.visible,\n width: _vm.dialogWidth,\n \"close-on-click-modal\": false\n },\n on: {\n \"update:visible\": function($event) {\n _vm.visible = $event\n },\n opened: _vm.onDialogOpened,\n closed: _vm.handleClosed\n }\n },\n [\n _c(\n \"el-form\",\n {\n ref: \"docForm\",\n attrs: { model: _vm.formData, \"label-width\": \"100px\" }\n },\n [\n !_vm.isEdit\n ? _c(\n \"el-form-item\",\n { attrs: { label: \"选择文件\" } },\n [\n _c(\n \"el-upload\",\n {\n ref: \"uploadRef\",\n attrs: {\n drag: \"\",\n multiple: \"\",\n \"auto-upload\": false,\n limit: 5,\n \"file-list\": _vm.fileList,\n \"on-change\": _vm.handleFileChange,\n \"on-remove\": _vm.handleFileRemove,\n \"on-exceed\": _vm.handleFileExceed,\n \"before-upload\": _vm.beforeUpload,\n accept:\n \".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.md,.jpg,.jpeg,.png,.gif,.webp\"\n }\n },\n [\n _c(\"i\", { staticClass: \"el-icon-upload\" }),\n _c(\"div\", { staticClass: \"el-upload__text\" }, [\n _vm._v(\"将文件拖到此处,或\"),\n _c(\"em\", [_vm._v(\"点击上传\")])\n ]),\n _c(\n \"div\",\n {\n staticClass: \"el-upload__tip\",\n attrs: { slot: \"tip\" },\n slot: \"tip\"\n },\n [\n _vm._v(\n \" 支持 Word / Excel / PPT / PDF / TXT / Markdown / 图片,单文件不超过 50MB,最多 5 个文件 \"\n )\n ]\n )\n ]\n )\n ],\n 1\n )\n : _vm._e(),\n _vm.isEdit\n ? _c(\n \"el-form-item\",\n { attrs: { label: \"文件标题\", prop: \"title\" } },\n [\n _c(\"el-input\", {\n attrs: { placeholder: \"请输入文件标题\", maxlength: \"100\" },\n model: {\n value: _vm.formData.title,\n callback: function($$v) {\n _vm.$set(_vm.formData, \"title\", $$v)\n },\n expression: \"formData.title\"\n }\n })\n ],\n 1\n )\n : _vm._e(),\n _c(\n \"el-form-item\",\n { attrs: { label: \"所属文件夹\", required: !_vm.isEdit } },\n [\n _c(\n \"el-select\",\n {\n staticStyle: { width: \"100%\" },\n attrs: { placeholder: \"请选择所属文件夹\", clearable: \"\" },\n model: {\n value: _vm.formData.folderId,\n callback: function($$v) {\n _vm.$set(_vm.formData, \"folderId\", $$v)\n },\n expression: \"formData.folderId\"\n }\n },\n _vm._l(_vm.folderList, function(f) {\n return _c(\"el-option\", {\n key: f.id,\n attrs: { label: f.name, value: f.id }\n })\n }),\n 1\n )\n ],\n 1\n ),\n _vm.isEdit && _vm.isTextEditable && !_vm.isXlsxType\n ? _c(\"el-form-item\", { attrs: { label: \"文件内容\" } }, [\n _c(\"div\", { staticClass: \"md-editor-wrap\" }, [\n _c(\"div\", { staticClass: \"md-editor-toolbar\" }, [\n _c(\n \"span\",\n {\n staticClass: \"md-editor-tab\",\n class: { active: _vm.mdTab === \"edit\" },\n on: {\n click: function($event) {\n _vm.mdTab = \"edit\"\n }\n }\n },\n [_vm._v(\"编辑\")]\n ),\n _vm.isMarkdownType\n ? _c(\n \"span\",\n {\n staticClass: \"md-editor-tab\",\n class: { active: _vm.mdTab === \"preview\" },\n on: {\n click: function($event) {\n _vm.mdTab = \"preview\"\n }\n }\n },\n [_vm._v(\"预览\")]\n )\n : _vm._e(),\n _c(\"span\", { staticClass: \"md-editor-status\" }, [\n _vm._v(\n _vm._s(\n _vm.formData.content\n ? _vm.formData.content.length + \" 字符\"\n : \"空内容\"\n )\n )\n ])\n ]),\n _vm.mdTab === \"edit\"\n ? _c(\"textarea\", {\n directives: [\n {\n name: \"model\",\n rawName: \"v-model\",\n value: _vm.formData.content,\n expression: \"formData.content\"\n }\n ],\n staticClass: \"md-textarea\",\n attrs: {\n placeholder:\n \"在此输入 \" +\n (_vm.editingFileType || \"\") +\n \" 内容...\"\n },\n domProps: { value: _vm.formData.content },\n on: {\n input: function($event) {\n if ($event.target.composing) {\n return\n }\n _vm.$set(\n _vm.formData,\n \"content\",\n $event.target.value\n )\n }\n }\n })\n : _c(\"div\", {\n staticClass: \"md-preview\",\n attrs: { contenteditable: \"false\" },\n domProps: { innerHTML: _vm._s(_vm.previewHtml) }\n })\n ])\n ])\n : _vm._e(),\n _vm.isEdit && _vm.isXlsxType\n ? _c(\"el-form-item\", { attrs: { label: \"表格编辑\" } }, [\n _c(\n \"div\",\n { staticClass: \"xlsx-editor-wrap\" },\n [\n _vm.xlsxData.loading\n ? _c(\"div\", { staticClass: \"xlsx-loading\" }, [\n _c(\"i\", { staticClass: \"el-icon-loading\" }),\n _vm._v(\" 正在解析 Excel 文件... \")\n ])\n : [\n _c(\"div\", { staticClass: \"xlsx-toolbar\" }, [\n _c(\n \"div\",\n { staticClass: \"xlsx-sheet-tabs\" },\n _vm._l(_vm.xlsxData.sheetNames, function(name) {\n return _c(\n \"span\",\n {\n key: name,\n staticClass: \"xlsx-sheet-tab\",\n class: {\n active: _vm.xlsxData.currentSheet === name\n },\n on: {\n click: function($event) {\n return _vm.switchXlsxSheet(name)\n }\n }\n },\n [_vm._v(_vm._s(name))]\n )\n }),\n 0\n ),\n _c(\n \"div\",\n { staticClass: \"xlsx-actions\" },\n [\n _c(\n \"el-button\",\n {\n attrs: { size: \"mini\" },\n on: { click: _vm.addXlsxRow }\n },\n [_vm._v(\"添加行\")]\n ),\n _c(\n \"el-button\",\n {\n attrs: { size: \"mini\" },\n on: { click: _vm.addXlsxCol }\n },\n [_vm._v(\"添加列\")]\n ),\n _c(\n \"el-button\",\n {\n attrs: {\n size: \"mini\",\n type: \"danger\",\n plain: \"\"\n },\n on: { click: _vm.deleteXlsxRow }\n },\n [_vm._v(\"删除最后行\")]\n ),\n _c(\n \"el-button\",\n {\n attrs: {\n size: \"mini\",\n type: \"warning\",\n plain: \"\"\n },\n on: { click: _vm.deleteXlsxCol }\n },\n [_vm._v(\"删除最后列\")]\n ),\n _c(\"span\", { staticClass: \"xlsx-info\" }, [\n _vm._v(\n _vm._s(_vm.xlsxData.rows.length) +\n \" 行 x \" +\n _vm._s(_vm.xlsxData.cols.length) +\n \" 列\"\n )\n ])\n ],\n 1\n )\n ]),\n _vm.xlsxData.rows.length > 0\n ? _c(\n \"div\",\n { staticClass: \"xlsx-table-wrap\" },\n [\n _c(\n \"el-table\",\n {\n staticStyle: { width: \"100%\" },\n attrs: {\n data: _vm.xlsxData.rows,\n border: \"\",\n size: \"mini\",\n \"max-height\": 420\n }\n },\n [\n _vm._l(_vm.xlsxData.cols, function(\n col,\n ci\n ) {\n return _c(\"el-table-column\", {\n key: ci,\n attrs: {\n label: col,\n width: 120,\n \"min-width\": 90\n },\n scopedSlots: _vm._u(\n [\n {\n key: \"default\",\n fn: function(ref) {\n var row = ref.row\n return [\n _c(\"el-input\", {\n attrs: {\n size: \"mini\",\n placeholder: col\n },\n on: {\n change:\n _vm.onXlsxCellChange\n },\n model: {\n value: row[ci],\n callback: function(\n $$v\n ) {\n _vm.$set(row, ci, $$v)\n },\n expression: \"row[ci]\"\n }\n })\n ]\n }\n }\n ],\n null,\n true\n )\n })\n }),\n _c(\"el-table-column\", {\n attrs: {\n label: \"操作\",\n width: \"60\",\n fixed: \"right\"\n },\n scopedSlots: _vm._u(\n [\n {\n key: \"default\",\n fn: function(ref) {\n var $index = ref.$index\n return [\n _c(\"el-button\", {\n attrs: {\n size: \"mini\",\n type: \"danger\",\n icon: \"el-icon-delete\",\n circle: \"\"\n },\n on: {\n click: function($event) {\n return _vm.removeXlsxRow(\n $index\n )\n }\n }\n })\n ]\n }\n }\n ],\n null,\n false,\n 438211574\n )\n })\n ],\n 2\n )\n ],\n 1\n )\n : _c(\n \"div\",\n { staticClass: \"xlsx-empty\" },\n [\n _c(\"el-empty\", {\n attrs: { description: \"当前 sheet 无数据\" }\n })\n ],\n 1\n )\n ]\n ],\n 2\n )\n ])\n : _vm._e(),\n _vm.isEdit && _vm.isWordType\n ? _c(\"el-form-item\", { attrs: { label: \"文档内容\" } }, [\n _c(\n \"div\",\n { staticClass: \"word-editor-wrap\" },\n [\n _vm.wordLoading\n ? _c(\"div\", { staticClass: \"word-loading\" }, [\n _c(\"i\", { staticClass: \"el-icon-loading\" }),\n _vm._v(\" 正在解析 Word 文件... \")\n ])\n : [\n _c(\"div\", { staticClass: \"word-header\" }, [\n _c(\"span\", { staticClass: \"word-filename\" }, [\n _c(\"i\", { staticClass: \"el-icon-document\" }),\n _vm._v(\" \" + _vm._s(_vm.formData.fileName) + \" \")\n ]),\n _c(\"span\", { staticClass: \"word-stats\" }, [\n _vm._v(_vm._s(_vm.wordCharCount) + \" 字符\")\n ])\n ]),\n _c(\"div\", { staticClass: \"word-toolbar\" }, [\n _c(\n \"button\",\n {\n staticClass: \"word-tb-btn\",\n attrs: { type: \"button\", title: \"加粗\" },\n on: {\n click: function($event) {\n return _vm.execFormat(\"bold\")\n }\n }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"16\",\n height: \"16\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M15.6 10.79c.97-.67 1.65-1.77 1.65-2.79 0-2.26-1.75-4-4-4H7v14h7.04c2.09 0 3.71-1.7 3.71-3.79 0-1.52-.86-2.82-2.15-3.42zM10 6.5h3c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5h-3v-3zm3.5 9H10v-3h3.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5z\",\n fill: \"currentColor\"\n }\n })\n ]\n )\n ]\n ),\n _c(\n \"button\",\n {\n staticClass: \"word-tb-btn\",\n attrs: { type: \"button\", title: \"斜体\" },\n on: {\n click: function($event) {\n return _vm.execFormat(\"italic\")\n }\n }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"16\",\n height: \"16\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M10 4v3h2.21l-3.42 8H6v3h8v-3h-2.21l3.42-8H18V4z\",\n fill: \"currentColor\"\n }\n })\n ]\n )\n ]\n ),\n _c(\n \"button\",\n {\n staticClass: \"word-tb-btn\",\n attrs: { type: \"button\", title: \"下划线\" },\n on: {\n click: function($event) {\n return _vm.execFormat(\"underline\")\n }\n }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"16\",\n height: \"16\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M12 17c3.31 0 6-2.69 6-6V3h-2.5v8c0 1.93-1.57 3.5-3.5 3.5S8.5 12.93 8.5 11V3H6v8c0 3.31 2.69 6 6 6zm-7 2v2h14v-2H5z\",\n fill: \"currentColor\"\n }\n })\n ]\n )\n ]\n ),\n _c(\"span\", { staticClass: \"word-tb-sep\" }),\n _c(\n \"button\",\n {\n staticClass: \"word-tb-btn word-tb-btn-heading\",\n attrs: { type: \"button\", title: \"标题1\" },\n on: {\n click: function($event) {\n return _vm.execFormat(\"formatBlock\", \"h1\")\n }\n }\n },\n [_vm._v(\"H1\")]\n ),\n _c(\n \"button\",\n {\n staticClass: \"word-tb-btn word-tb-btn-heading\",\n attrs: { type: \"button\", title: \"标题2\" },\n on: {\n click: function($event) {\n return _vm.execFormat(\"formatBlock\", \"h2\")\n }\n }\n },\n [_vm._v(\"H2\")]\n ),\n _c(\n \"button\",\n {\n staticClass: \"word-tb-btn word-tb-btn-heading\",\n attrs: { type: \"button\", title: \"标题3\" },\n on: {\n click: function($event) {\n return _vm.execFormat(\"formatBlock\", \"h3\")\n }\n }\n },\n [_vm._v(\"H3\")]\n ),\n _c(\n \"button\",\n {\n staticClass: \"word-tb-btn word-tb-btn-heading\",\n attrs: { type: \"button\", title: \"正文\" },\n on: {\n click: function($event) {\n return _vm.execFormat(\"formatBlock\", \"p\")\n }\n }\n },\n [_vm._v(\"P\")]\n ),\n _c(\"span\", { staticClass: \"word-tb-sep\" }),\n _c(\n \"button\",\n {\n staticClass: \"word-tb-btn\",\n attrs: { type: \"button\", title: \"无序列表\" },\n on: {\n click: function($event) {\n return _vm.execFormat(\"insertUnorderedList\")\n }\n }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"16\",\n height: \"16\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5 5.5 6.83 5.5 6 4.83 4.5 4 4.5zm0 12c-.83 0-1.5.68-1.5 1.5s.68 1.5 1.5 1.5 1.5-.68 1.5-1.5-.67-1.5-1.5-1.5zM7 19h14v-2H7v2zm0-6h14v-2H7v2zm0-8v2h14V5H7z\",\n fill: \"currentColor\"\n }\n })\n ]\n )\n ]\n ),\n _c(\n \"button\",\n {\n staticClass: \"word-tb-btn\",\n attrs: { type: \"button\", title: \"有序列表\" },\n on: {\n click: function($event) {\n return _vm.execFormat(\"insertOrderedList\")\n }\n }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"16\",\n height: \"16\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M2 17h2v.5H3v1h1v.5H2v1h3v-4H2v1zm1-9h1V4H2v1h1v3zm-1 3h1.8L2 13.1v.9h3v-1H3.2L5 10.9V10H2v1zm5-6v2h14V5H7zm0 14h14v-2H7v2zm0-6h14v-2H7v2z\",\n fill: \"currentColor\"\n }\n })\n ]\n )\n ]\n ),\n _c(\"span\", { staticClass: \"word-tb-sep\" }),\n _c(\n \"button\",\n {\n staticClass: \"word-tb-btn\",\n attrs: { type: \"button\", title: \"引用\" },\n on: {\n click: function($event) {\n return _vm.execFormat(\n \"formatBlock\",\n \"blockquote\"\n )\n }\n }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"16\",\n height: \"16\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M6 17h3l2-4V7H5v6h3zm8 0h3l2-4V7h-6v6h3z\",\n fill: \"currentColor\"\n }\n })\n ]\n )\n ]\n ),\n _c(\n \"button\",\n {\n staticClass: \"word-tb-btn\",\n attrs: { type: \"button\", title: \"清除格式\" },\n on: {\n click: function($event) {\n return _vm.execFormat(\"removeFormat\")\n }\n }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"16\",\n height: \"16\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z\",\n fill: \"currentColor\"\n }\n })\n ]\n )\n ]\n )\n ]),\n _c(\"div\", {\n ref: \"wordEditor\",\n staticClass: \"word-editor-content\",\n attrs: { contenteditable: \"true\" },\n on: { input: _vm.onWordInput }\n })\n ]\n ],\n 2\n )\n ])\n : _vm._e()\n ],\n 1\n ),\n _c(\n \"span\",\n { attrs: { slot: \"footer\" }, slot: \"footer\" },\n [\n _c(\n \"el-button\",\n {\n on: {\n click: function($event) {\n _vm.visible = false\n }\n }\n },\n [_vm._v(\"取消\")]\n ),\n _c(\n \"el-button\",\n {\n attrs: { type: \"primary\", loading: _vm.submitting },\n on: { click: _vm.handleSubmit }\n },\n [_vm._v(\"确定\")]\n )\n ],\n 1\n )\n ],\n 1\n )\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/DocumentForm.vue?./node_modules/cache-loader/dist/cjs.js?%7B%22cacheDirectory%22:%22node_modules/.cache/vue-loader%22,%22cacheIdentifier%22:%2268609640-vue-loader-template%22%7D!./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
1135
|
+
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return staticRenderFns; });\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\n \"el-dialog\",\n {\n attrs: {\n \"custom-class\": \"document-form-dialog\",\n title: _vm.isEdit ? _vm.dialogTitle : \"新增文档\",\n visible: _vm.visible,\n width: _vm.dialogWidth,\n \"close-on-click-modal\": false\n },\n on: {\n \"update:visible\": function($event) {\n _vm.visible = $event\n },\n opened: _vm.onDialogOpened,\n closed: _vm.handleClosed\n }\n },\n [\n _c(\n \"el-form\",\n {\n ref: \"docForm\",\n attrs: { model: _vm.formData, \"label-width\": \"100px\" }\n },\n [\n !_vm.isEdit\n ? _c(\n \"el-form-item\",\n { attrs: { label: \"选择文件\" } },\n [\n _c(\n \"el-upload\",\n {\n ref: \"uploadRef\",\n attrs: {\n drag: \"\",\n multiple: \"\",\n \"auto-upload\": false,\n limit: 5,\n \"file-list\": _vm.fileList,\n \"on-change\": _vm.handleFileChange,\n \"on-remove\": _vm.handleFileRemove,\n \"on-exceed\": _vm.handleFileExceed,\n \"before-upload\": _vm.beforeUpload,\n accept:\n \".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.md,.jpg,.jpeg,.png,.gif,.webp\"\n }\n },\n [\n _c(\"i\", { staticClass: \"el-icon-upload\" }),\n _c(\"div\", { staticClass: \"el-upload__text\" }, [\n _vm._v(\"将文件拖到此处,或\"),\n _c(\"em\", [_vm._v(\"点击上传\")])\n ]),\n _c(\n \"div\",\n {\n staticClass: \"el-upload__tip\",\n attrs: { slot: \"tip\" },\n slot: \"tip\"\n },\n [\n _vm._v(\n \" 支持 Word / Excel / PPT / PDF / TXT / Markdown / 图片,单文件不超过 50MB,最多 5 个文件 \"\n )\n ]\n )\n ]\n )\n ],\n 1\n )\n : _vm._e(),\n _vm.isEdit\n ? _c(\n \"el-form-item\",\n { attrs: { label: \"文件标题\", prop: \"title\" } },\n [\n _c(\"el-input\", {\n staticClass: \"doc-title-input\",\n attrs: { placeholder: \"请输入文件标题\", maxlength: \"100\" },\n model: {\n value: _vm.formData.title,\n callback: function($$v) {\n _vm.$set(_vm.formData, \"title\", $$v)\n },\n expression: \"formData.title\"\n }\n })\n ],\n 1\n )\n : _vm._e(),\n _c(\n \"el-form-item\",\n { attrs: { label: \"所属文件夹\", required: !_vm.isEdit } },\n [\n _c(\n \"el-popover\",\n {\n ref: \"folderPopover\",\n attrs: {\n trigger: \"click\",\n placement: \"bottom-start\",\n width: 360\n },\n on: { show: _vm.handleFolderPopoverShow },\n model: {\n value: _vm.folderPopoverVisible,\n callback: function($$v) {\n _vm.folderPopoverVisible = $$v\n },\n expression: \"folderPopoverVisible\"\n }\n },\n [\n _c(\n \"div\",\n { staticClass: \"folder-tree-wrap\" },\n [\n _c(\"el-tree\", {\n ref: \"folderTreeRef\",\n attrs: {\n data: _vm.folderTree,\n props: _vm.folderTreeProps,\n \"node-key\": \"id\",\n \"highlight-current\": \"\",\n \"expand-on-click-node\": false,\n \"default-expand-all\": \"\"\n },\n on: { \"node-click\": _vm.handleFolderSelect }\n })\n ],\n 1\n ),\n _c(\n \"div\",\n {\n staticClass: \"folder-picker-input\",\n attrs: { slot: \"reference\" },\n slot: \"reference\"\n },\n [\n _c(\n \"el-input\",\n {\n staticClass: \"folder-picker-input-el\",\n attrs: {\n readonly: \"\",\n placeholder: \"请选择所属文件夹\",\n value: _vm.selectedFolderName,\n clearable: \"\"\n },\n on: { clear: _vm.clearFolderSelect }\n },\n [\n _c(\"i\", {\n staticClass: \"el-input__icon el-icon-arrow-down\",\n attrs: { slot: \"suffix\" },\n slot: \"suffix\"\n })\n ]\n )\n ],\n 1\n )\n ]\n )\n ],\n 1\n ),\n _vm.isEdit && _vm.isTextEditable && !_vm.isXlsxType\n ? _c(\"el-form-item\", { attrs: { label: \"文件内容\" } }, [\n _c(\"div\", { staticClass: \"md-editor-wrap\" }, [\n _c(\"div\", { staticClass: \"md-editor-toolbar\" }, [\n _c(\n \"span\",\n {\n staticClass: \"md-editor-tab\",\n class: { active: _vm.mdTab === \"edit\" },\n on: {\n click: function($event) {\n _vm.mdTab = \"edit\"\n }\n }\n },\n [_vm._v(\"编辑\")]\n ),\n _vm.isMarkdownType\n ? _c(\n \"span\",\n {\n staticClass: \"md-editor-tab\",\n class: { active: _vm.mdTab === \"preview\" },\n on: {\n click: function($event) {\n _vm.mdTab = \"preview\"\n }\n }\n },\n [_vm._v(\"预览\")]\n )\n : _vm._e(),\n _c(\"span\", { staticClass: \"md-editor-status\" }, [\n _vm._v(\n _vm._s(\n _vm.formData.content\n ? _vm.formData.content.length + \" 字符\"\n : \"空内容\"\n )\n )\n ])\n ]),\n _vm.mdTab === \"edit\"\n ? _c(\"textarea\", {\n directives: [\n {\n name: \"model\",\n rawName: \"v-model\",\n value: _vm.formData.content,\n expression: \"formData.content\"\n }\n ],\n staticClass: \"md-textarea\",\n attrs: {\n placeholder:\n \"在此输入 \" +\n (_vm.editingFileType || \"\") +\n \" 内容...\"\n },\n domProps: { value: _vm.formData.content },\n on: {\n input: function($event) {\n if ($event.target.composing) {\n return\n }\n _vm.$set(\n _vm.formData,\n \"content\",\n $event.target.value\n )\n }\n }\n })\n : _c(\"div\", {\n staticClass: \"md-preview\",\n attrs: { contenteditable: \"false\" },\n domProps: { innerHTML: _vm._s(_vm.previewHtml) }\n })\n ])\n ])\n : _vm._e(),\n _vm.isEdit && _vm.isXlsxType\n ? _c(\"el-form-item\", { attrs: { label: \"表格编辑\" } }, [\n _c(\n \"div\",\n { staticClass: \"xlsx-editor-wrap\" },\n [\n _vm.xlsxData.loading\n ? _c(\"div\", { staticClass: \"xlsx-loading\" }, [\n _c(\"i\", { staticClass: \"el-icon-loading\" }),\n _vm._v(\" 正在解析 Excel 文件... \")\n ])\n : [\n _c(\"div\", { staticClass: \"xlsx-toolbar\" }, [\n _c(\n \"div\",\n { staticClass: \"xlsx-sheet-tabs\" },\n _vm._l(_vm.xlsxData.sheetNames, function(name) {\n return _c(\n \"span\",\n {\n key: name,\n staticClass: \"xlsx-sheet-tab\",\n class: {\n active: _vm.xlsxData.currentSheet === name\n },\n on: {\n click: function($event) {\n return _vm.switchXlsxSheet(name)\n }\n }\n },\n [_vm._v(_vm._s(name))]\n )\n }),\n 0\n ),\n _c(\n \"div\",\n { staticClass: \"xlsx-actions\" },\n [\n _c(\n \"el-button\",\n {\n attrs: { size: \"mini\" },\n on: { click: _vm.addXlsxRow }\n },\n [_vm._v(\"添加行\")]\n ),\n _c(\n \"el-button\",\n {\n attrs: { size: \"mini\" },\n on: { click: _vm.addXlsxCol }\n },\n [_vm._v(\"添加列\")]\n ),\n _c(\n \"el-button\",\n {\n attrs: {\n size: \"mini\",\n type: \"danger\",\n plain: \"\"\n },\n on: { click: _vm.deleteXlsxRow }\n },\n [_vm._v(\"删除最后行\")]\n ),\n _c(\n \"el-button\",\n {\n attrs: {\n size: \"mini\",\n type: \"warning\",\n plain: \"\"\n },\n on: { click: _vm.deleteXlsxCol }\n },\n [_vm._v(\"删除最后列\")]\n ),\n _c(\"span\", { staticClass: \"xlsx-info\" }, [\n _vm._v(\n _vm._s(_vm.xlsxData.rows.length) +\n \" 行 x \" +\n _vm._s(_vm.xlsxData.cols.length) +\n \" 列\"\n )\n ])\n ],\n 1\n )\n ]),\n _vm.xlsxData.rows.length > 0\n ? _c(\n \"div\",\n { staticClass: \"xlsx-table-wrap\" },\n [\n _c(\n \"el-table\",\n {\n staticStyle: { width: \"100%\" },\n attrs: {\n data: _vm.xlsxData.rows,\n border: \"\",\n size: \"mini\",\n \"max-height\": 420\n }\n },\n [\n _vm._l(_vm.xlsxData.cols, function(\n col,\n ci\n ) {\n return _c(\"el-table-column\", {\n key: ci,\n attrs: {\n label: col,\n width: 120,\n \"min-width\": 90\n },\n scopedSlots: _vm._u(\n [\n {\n key: \"default\",\n fn: function(ref) {\n var row = ref.row\n return [\n _c(\"el-input\", {\n attrs: {\n size: \"mini\",\n placeholder: col\n },\n on: {\n change:\n _vm.onXlsxCellChange\n },\n model: {\n value: row[ci],\n callback: function(\n $$v\n ) {\n _vm.$set(row, ci, $$v)\n },\n expression: \"row[ci]\"\n }\n })\n ]\n }\n }\n ],\n null,\n true\n )\n })\n }),\n _c(\"el-table-column\", {\n attrs: {\n label: \"操作\",\n width: \"60\",\n fixed: \"right\"\n },\n scopedSlots: _vm._u(\n [\n {\n key: \"default\",\n fn: function(ref) {\n var $index = ref.$index\n return [\n _c(\"el-button\", {\n attrs: {\n size: \"mini\",\n type: \"danger\",\n icon: \"el-icon-delete\",\n circle: \"\"\n },\n on: {\n click: function($event) {\n return _vm.removeXlsxRow(\n $index\n )\n }\n }\n })\n ]\n }\n }\n ],\n null,\n false,\n 438211574\n )\n })\n ],\n 2\n )\n ],\n 1\n )\n : _c(\n \"div\",\n { staticClass: \"xlsx-empty\" },\n [\n _c(\"el-empty\", {\n attrs: { description: \"当前 sheet 无数据\" }\n })\n ],\n 1\n )\n ]\n ],\n 2\n )\n ])\n : _vm._e(),\n _vm.isEdit && _vm.isWordType\n ? _c(\"el-form-item\", { attrs: { label: \"文档内容\" } }, [\n _c(\n \"div\",\n { staticClass: \"word-editor-wrap\" },\n [\n _vm.wordLoading\n ? _c(\"div\", { staticClass: \"word-loading\" }, [\n _c(\"i\", { staticClass: \"el-icon-loading\" }),\n _vm._v(\" 正在解析 Word 文件... \")\n ])\n : [\n _c(\"div\", { staticClass: \"word-header\" }, [\n _c(\"span\", { staticClass: \"word-filename\" }, [\n _c(\"i\", { staticClass: \"el-icon-document\" }),\n _vm._v(\" \" + _vm._s(_vm.formData.fileName) + \" \")\n ]),\n _c(\"span\", { staticClass: \"word-stats\" }, [\n _vm._v(_vm._s(_vm.wordCharCount) + \" 字符\")\n ])\n ]),\n _c(\"div\", { staticClass: \"word-toolbar\" }, [\n _c(\n \"button\",\n {\n staticClass: \"word-tb-btn\",\n attrs: { type: \"button\", title: \"加粗\" },\n on: {\n click: function($event) {\n return _vm.execFormat(\"bold\")\n }\n }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"16\",\n height: \"16\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M15.6 10.79c.97-.67 1.65-1.77 1.65-2.79 0-2.26-1.75-4-4-4H7v14h7.04c2.09 0 3.71-1.7 3.71-3.79 0-1.52-.86-2.82-2.15-3.42zM10 6.5h3c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5h-3v-3zm3.5 9H10v-3h3.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5z\",\n fill: \"currentColor\"\n }\n })\n ]\n )\n ]\n ),\n _c(\n \"button\",\n {\n staticClass: \"word-tb-btn\",\n attrs: { type: \"button\", title: \"斜体\" },\n on: {\n click: function($event) {\n return _vm.execFormat(\"italic\")\n }\n }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"16\",\n height: \"16\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M10 4v3h2.21l-3.42 8H6v3h8v-3h-2.21l3.42-8H18V4z\",\n fill: \"currentColor\"\n }\n })\n ]\n )\n ]\n ),\n _c(\n \"button\",\n {\n staticClass: \"word-tb-btn\",\n attrs: { type: \"button\", title: \"下划线\" },\n on: {\n click: function($event) {\n return _vm.execFormat(\"underline\")\n }\n }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"16\",\n height: \"16\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M12 17c3.31 0 6-2.69 6-6V3h-2.5v8c0 1.93-1.57 3.5-3.5 3.5S8.5 12.93 8.5 11V3H6v8c0 3.31 2.69 6 6 6zm-7 2v2h14v-2H5z\",\n fill: \"currentColor\"\n }\n })\n ]\n )\n ]\n ),\n _c(\"span\", { staticClass: \"word-tb-sep\" }),\n _c(\n \"button\",\n {\n staticClass: \"word-tb-btn word-tb-btn-heading\",\n attrs: { type: \"button\", title: \"标题1\" },\n on: {\n click: function($event) {\n return _vm.execFormat(\"formatBlock\", \"h1\")\n }\n }\n },\n [_vm._v(\"H1\")]\n ),\n _c(\n \"button\",\n {\n staticClass: \"word-tb-btn word-tb-btn-heading\",\n attrs: { type: \"button\", title: \"标题2\" },\n on: {\n click: function($event) {\n return _vm.execFormat(\"formatBlock\", \"h2\")\n }\n }\n },\n [_vm._v(\"H2\")]\n ),\n _c(\n \"button\",\n {\n staticClass: \"word-tb-btn word-tb-btn-heading\",\n attrs: { type: \"button\", title: \"标题3\" },\n on: {\n click: function($event) {\n return _vm.execFormat(\"formatBlock\", \"h3\")\n }\n }\n },\n [_vm._v(\"H3\")]\n ),\n _c(\n \"button\",\n {\n staticClass: \"word-tb-btn word-tb-btn-heading\",\n attrs: { type: \"button\", title: \"正文\" },\n on: {\n click: function($event) {\n return _vm.execFormat(\"formatBlock\", \"p\")\n }\n }\n },\n [_vm._v(\"P\")]\n ),\n _c(\"span\", { staticClass: \"word-tb-sep\" }),\n _c(\n \"button\",\n {\n staticClass: \"word-tb-btn\",\n attrs: { type: \"button\", title: \"无序列表\" },\n on: {\n click: function($event) {\n return _vm.execFormat(\"insertUnorderedList\")\n }\n }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"16\",\n height: \"16\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5 5.5 6.83 5.5 6 4.83 4.5 4 4.5zm0 12c-.83 0-1.5.68-1.5 1.5s.68 1.5 1.5 1.5 1.5-.68 1.5-1.5-.67-1.5-1.5-1.5zM7 19h14v-2H7v2zm0-6h14v-2H7v2zm0-8v2h14V5H7z\",\n fill: \"currentColor\"\n }\n })\n ]\n )\n ]\n ),\n _c(\n \"button\",\n {\n staticClass: \"word-tb-btn\",\n attrs: { type: \"button\", title: \"有序列表\" },\n on: {\n click: function($event) {\n return _vm.execFormat(\"insertOrderedList\")\n }\n }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"16\",\n height: \"16\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M2 17h2v.5H3v1h1v.5H2v1h3v-4H2v1zm1-9h1V4H2v1h1v3zm-1 3h1.8L2 13.1v.9h3v-1H3.2L5 10.9V10H2v1zm5-6v2h14V5H7zm0 14h14v-2H7v2zm0-6h14v-2H7v2z\",\n fill: \"currentColor\"\n }\n })\n ]\n )\n ]\n ),\n _c(\"span\", { staticClass: \"word-tb-sep\" }),\n _c(\n \"button\",\n {\n staticClass: \"word-tb-btn\",\n attrs: { type: \"button\", title: \"引用\" },\n on: {\n click: function($event) {\n return _vm.execFormat(\n \"formatBlock\",\n \"blockquote\"\n )\n }\n }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"16\",\n height: \"16\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M6 17h3l2-4V7H5v6h3zm8 0h3l2-4V7h-6v6h3z\",\n fill: \"currentColor\"\n }\n })\n ]\n )\n ]\n ),\n _c(\n \"button\",\n {\n staticClass: \"word-tb-btn\",\n attrs: { type: \"button\", title: \"清除格式\" },\n on: {\n click: function($event) {\n return _vm.execFormat(\"removeFormat\")\n }\n }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"16\",\n height: \"16\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z\",\n fill: \"currentColor\"\n }\n })\n ]\n )\n ]\n )\n ]),\n _c(\"div\", {\n ref: \"wordEditor\",\n staticClass: \"word-editor-content\",\n attrs: { contenteditable: \"true\" },\n on: { input: _vm.onWordInput }\n })\n ]\n ],\n 2\n )\n ])\n : _vm._e()\n ],\n 1\n ),\n _c(\n \"span\",\n { attrs: { slot: \"footer\" }, slot: \"footer\" },\n [\n _c(\n \"el-button\",\n {\n on: {\n click: function($event) {\n _vm.visible = false\n }\n }\n },\n [_vm._v(\"取消\")]\n ),\n _c(\n \"el-button\",\n {\n attrs: { type: \"primary\", loading: _vm.submitting },\n on: { click: _vm.handleSubmit }\n },\n [_vm._v(\"确定\")]\n )\n ],\n 1\n )\n ],\n 1\n )\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/DocumentForm.vue?./node_modules/cache-loader/dist/cjs.js?%7B%22cacheDirectory%22:%22node_modules/.cache/vue-loader%22,%22cacheIdentifier%22:%2268609640-vue-loader-template%22%7D!./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
1136
1136
|
|
|
1137
1137
|
/***/ }),
|
|
1138
1138
|
|
|
@@ -1144,7 +1144,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) *
|
|
|
1144
1144
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
1145
1145
|
|
|
1146
1146
|
"use strict";
|
|
1147
|
-
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return staticRenderFns; });\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\n \"div\",\n { staticClass: \"kb-document-list\" },\n [\n _c(\"div\", { staticClass: \"doc-toolbar\" }, [\n _c(\n \"div\",\n { staticClass: \"doc-toolbar-left\" },\n [\n _c(\"el-input\", {\n staticClass: \"search-input\",\n attrs: {\n placeholder: \"搜索文件标题...\",\n \"prefix-icon\": \"el-icon-search\",\n size: \"small\",\n clearable: \"\"\n },\n on: { clear: _vm.handleSearch },\n nativeOn: {\n keyup: function($event) {\n if (\n !$event.type.indexOf(\"key\") &&\n _vm._k($event.keyCode, \"enter\", 13, $event.key, \"Enter\")\n ) {\n return null\n }\n return _vm.handleSearch.apply(null, arguments)\n }\n },\n model: {\n value: _vm.searchKeyword,\n callback: function($$v) {\n _vm.searchKeyword = $$v\n },\n expression: \"searchKeyword\"\n }\n }),\n _c(\n \"el-button\",\n {\n attrs: {\n type: \"primary\",\n size: \"small\",\n icon: \"el-icon-search\",\n disabled: !_vm.companyId\n },\n on: { click: _vm.handleSearch }\n },\n [_vm._v(\"搜索\")]\n ),\n _c(\n \"el-button\",\n {\n attrs: { type: \"success\", size: \"small\", icon: \"el-icon-plus\" },\n on: { click: _vm.handleAdd }\n },\n [_vm._v(\"新增文档\")]\n )\n ],\n 1\n ),\n _c(\n \"div\",\n { staticClass: \"doc-toolbar-right\" },\n [\n _c(\n \"el-button\",\n {\n attrs: { size: \"small\", icon: \"el-icon-refresh\" },\n on: { click: _vm.loadList }\n },\n [_vm._v(\"刷新\")]\n )\n ],\n 1\n )\n ]),\n _c(\n \"div\",\n {\n directives: [\n {\n name: \"loading\",\n rawName: \"v-loading\",\n value: _vm.loading,\n expression: \"loading\"\n }\n ],\n staticClass: \"doc-table-wrap\"\n },\n [\n _c(\n \"el-table\",\n {\n attrs: {\n data: _vm.list,\n stripe: \"\",\n height: \"100%\",\n \"header-cell-style\": {\n background: \"#f5f7fa\",\n color: \"#333\",\n fontWeight: \"600\"\n }\n }\n },\n [\n _c(\"el-table-column\", {\n attrs: { label: \"ID\", prop: \"id\", width: \"80\", align: \"center\" }\n }),\n _c(\"el-table-column\", {\n attrs: {\n label: \"文件标题\",\n prop: \"title\",\n \"min-width\": \"200\",\n \"show-overflow-tooltip\": \"\"\n },\n scopedSlots: _vm._u([\n {\n key: \"default\",\n fn: function(ref) {\n var row = ref.row\n return [\n _c(\"div\", { staticClass: \"file-title-cell\" }, [\n _c(\"i\", {\n staticClass: \"file-icon\",\n class: _vm.getFileIcon(row.fileType)\n }),\n _c(\"span\", [_vm._v(_vm._s(row.title))])\n ])\n ]\n }\n }\n ])\n }),\n _c(\"el-table-column\", {\n attrs: {\n label: \"原始文件名\",\n prop: \"fileName\",\n \"min-width\": \"160\",\n \"show-overflow-tooltip\": \"\"\n }\n }),\n _c(\"el-table-column\", {\n attrs: {\n label: \"类型\",\n prop: \"fileType\",\n width: \"80\",\n align: \"center\"\n },\n scopedSlots: _vm._u([\n {\n key: \"default\",\n fn: function(ref) {\n var row = ref.row\n return [\n _c(\n \"el-tag\",\n { attrs: { size: \"mini\", effect: \"plain\" } },\n [_vm._v(_vm._s(row.fileType))]\n )\n ]\n }\n }\n ])\n }),\n _c(\"el-table-column\", {\n attrs: { label: \"大小\", width: \"100\", align: \"center\" },\n scopedSlots: _vm._u([\n {\n key: \"default\",\n fn: function(ref) {\n var row = ref.row\n return [_vm._v(_vm._s(_vm.formatSize(row.fileSize)))]\n }\n }\n ])\n }),\n _c(\"el-table-column\", {\n attrs: { label: \"文件夹\", \"min-width\": \"120\" },\n scopedSlots: _vm._u([\n {\n key: \"default\",\n fn: function(ref) {\n var row = ref.row\n return [\n _c(\"span\", { staticClass: \"folder-name\" }, [\n _vm._v(_vm._s(_vm.folderName(row.folderId)))\n ])\n ]\n }\n }\n ])\n }),\n _c(\"el-table-column\", {\n attrs: { label: \"索引状态\", width: \"100\", align: \"center\" },\n scopedSlots: _vm._u([\n {\n key: \"default\",\n fn: function(ref) {\n var row = ref.row\n return [\n _c(\n \"el-tag\",\n {\n attrs: {\n type: _vm.indexTagType(row.indexStatus),\n size: \"small\",\n effect: \"plain\"\n }\n },\n [\n _vm._v(\n \" \" +\n _vm._s(_vm.indexTagText(row.indexStatus)) +\n \" \"\n )\n ]\n )\n ]\n }\n }\n ])\n }),\n _c(\"el-table-column\", {\n attrs: { label: \"创建时间\", width: \"165\", align: \"center\" },\n scopedSlots: _vm._u([\n {\n key: \"default\",\n fn: function(ref) {\n var row = ref.row\n return [_vm._v(_vm._s(_vm.formatTime(row.createTime)))]\n }\n }\n ])\n }),\n _c(\"el-table-column\", {\n attrs: {\n label: \"操作\",\n width: \"220\",\n fixed: \"right\",\n align: \"center\"\n },\n scopedSlots: _vm._u([\n {\n key: \"default\",\n fn: function(ref) {\n var row = ref.row\n return [\n _c(\n \"el-button\",\n {\n attrs: {\n size: \"mini\",\n type: \"text\",\n icon: \"el-icon-view\"\n },\n on: {\n click: function($event) {\n return _vm.handlePreview(row)\n }\n }\n },\n [_vm._v(\"预览\")]\n ),\n _c(\n \"el-button\",\n {\n attrs: {\n size: \"mini\",\n type: \"text\",\n icon: \"el-icon-edit\"\n },\n on: {\n click: function($event) {\n return _vm.handleEdit(row)\n }\n }\n },\n [_vm._v(\"编辑\")]\n ),\n row.indexStatus !== 2\n ? _c(\n \"el-button\",\n {\n attrs: {\n size: \"mini\",\n type: \"text\",\n icon: \"el-icon-coin\"\n },\n on: {\n click: function($event) {\n return _vm.handleIndex(row)\n }\n }\n },\n [\n _vm._v(\n _vm._s(\n row.indexStatus === 1 ? \"重建\" : \"索引\"\n )\n )\n ]\n )\n : _vm._e(),\n _c(\n \"el-button\",\n {\n staticStyle: { color: \"#f56c6c\" },\n attrs: {\n size: \"mini\",\n type: \"text\",\n icon: \"el-icon-delete\"\n },\n on: {\n click: function($event) {\n return _vm.handleDelete(row)\n }\n }\n },\n [_vm._v(\"删除\")]\n )\n ]\n }\n }\n ])\n })\n ],\n 1\n )\n ],\n 1\n ),\n _c(\n \"div\",\n { staticClass: \"doc-pagination\" },\n [\n _c(\"el-pagination\", {\n attrs: {\n \"current-page\": _vm.page,\n \"page-sizes\": [10, 20, 50, 100],\n \"page-size\": _vm.pageSize,\n layout: \"total, sizes, prev, pager, next, jumper\",\n total: _vm.total,\n background: \"\"\n },\n on: {\n \"size-change\": _vm.handleSizeChange,\n \"current-change\": _vm.handleCurrentChange\n }\n })\n ],\n 1\n ),\n _c(\"DocumentForm\", {\n ref: \"docFormRef\",\n attrs: { \"company-id\": _vm.companyId, \"folder-list\": _vm.categoryList }\n }),\n _c(\n \"el-dialog\",\n {\n attrs: {\n title: \"在线预览\",\n visible: _vm.previewVisible,\n width: \"960px\",\n top: \"5vh\",\n \"close-on-click-modal\": false\n },\n on: {\n \"update:visible\": function($event) {\n _vm.previewVisible = $event\n }\n }\n },\n [\n _c(\n \"div\",\n { staticClass: \"preview-body\" },\n [\n _vm.previewType === \"image\"\n ? _c(\"div\", { staticClass: \"preview-center\" }, [\n _c(\"img\", {\n staticStyle: {\n \"max-width\": \"100%\",\n \"max-height\": \"100%\"\n },\n attrs: { src: _vm.previewFile.fileUrl }\n })\n ])\n : _vm.previewType === \"pdf\"\n ? _c(\"iframe\", {\n staticClass: \"preview-frame\",\n attrs: { src: _vm.pdfBlobUrl }\n })\n : _vm.previewType === \"xlsx\"\n ? _c(\n \"div\",\n { staticClass: \"preview-xlsx-wrap\" },\n [\n _vm.xlsxLoading\n ? _c(\"div\", { staticClass: \"preview-loading\" }, [\n _c(\"i\", { staticClass: \"el-icon-loading\" }),\n _vm._v(\" 正在解析 Excel 文件...\")\n ])\n : [\n _c(\n \"div\",\n { staticClass: \"preview-xlsx-toolbar\" },\n _vm._l(_vm.xlsxSheetNames, function(name) {\n return _c(\n \"span\",\n {\n key: name,\n staticClass: \"preview-xlsx-tab\",\n class: {\n active: _vm.xlsxCurrentSheet === name\n },\n on: {\n click: function($event) {\n _vm.xlsxCurrentSheet = name\n _vm.renderXlsxSheet()\n }\n }\n },\n [_vm._v(_vm._s(name))]\n )\n }),\n 0\n ),\n _c(\n \"div\",\n { staticClass: \"preview-xlsx-table-wrap\" },\n [\n _vm.hasMoreRows\n ? _c(\n \"div\",\n { staticClass: \"preview-xlsx-notice\" },\n [\n _vm._v(\n \" 预览仅显示前 \" +\n _vm._s(_vm.maxPreviewRows) +\n \" 行数据(共更多行),完整数据请下载后查看 \"\n )\n ]\n )\n : _vm._e(),\n _vm.xlsxTableData.length\n ? _c(\n \"table\",\n {\n staticClass: \"preview-xlsx-table\",\n attrs: { contenteditable: \"false\" }\n },\n [\n _c(\"thead\", [\n _c(\n \"tr\",\n _vm._l(_vm.xlsxCols, function(\n col,\n ci\n ) {\n return _c(\"th\", { key: ci }, [\n _vm._v(_vm._s(col))\n ])\n }),\n 0\n )\n ]),\n _c(\n \"tbody\",\n _vm._l(_vm.xlsxTableData, function(\n row,\n ri\n ) {\n return _c(\n \"tr\",\n { key: ri },\n _vm._l(_vm.xlsxCols, function(\n col,\n ci\n ) {\n return _c(\"td\", { key: ci }, [\n _vm._v(_vm._s(row[ci]))\n ])\n }),\n 0\n )\n }),\n 0\n )\n ]\n )\n : _c(\"el-empty\", {\n attrs: {\n description: \"当前 Sheet 无数据\"\n }\n })\n ],\n 1\n )\n ]\n ],\n 2\n )\n : _vm.previewType === \"docx\"\n ? _c(\"div\", { staticClass: \"preview-docx-wrap\" }, [\n _vm.docxLoading\n ? _c(\"div\", { staticClass: \"preview-loading\" }, [\n _c(\"i\", { staticClass: \"el-icon-loading\" }),\n _vm._v(\" 正在解析 Word 文件...\")\n ])\n : _c(\"div\", {\n staticClass: \"preview-docx-content\",\n attrs: { contenteditable: \"false\" },\n domProps: { innerHTML: _vm._s(_vm.docxHtml) }\n })\n ])\n : _vm.previewType === \"text\"\n ? _c(\"div\", { staticClass: \"preview-text\" }, [\n _c(\"div\", {\n staticClass: \"preview-markdown\",\n attrs: { contenteditable: \"false\" },\n domProps: { innerHTML: _vm._s(_vm.previewHtml) }\n })\n ])\n : _c(\"el-empty\", {\n attrs: { description: \"该文件类型暂不支持在线预览\" }\n })\n ],\n 1\n )\n ]\n )\n ],\n 1\n )\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/DocumentList.vue?./node_modules/cache-loader/dist/cjs.js?%7B%22cacheDirectory%22:%22node_modules/.cache/vue-loader%22,%22cacheIdentifier%22:%2268609640-vue-loader-template%22%7D!./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
1147
|
+
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return staticRenderFns; });\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\n \"div\",\n { staticClass: \"kb-document-list\" },\n [\n _c(\"div\", { staticClass: \"doc-toolbar\" }, [\n _c(\n \"div\",\n { staticClass: \"doc-toolbar-left\" },\n [\n _c(\"el-input\", {\n staticClass: \"search-input\",\n attrs: {\n placeholder: \"搜索文件标题...\",\n \"prefix-icon\": \"el-icon-search\",\n size: \"small\",\n clearable: \"\"\n },\n on: { clear: _vm.handleSearch },\n nativeOn: {\n keyup: function($event) {\n if (\n !$event.type.indexOf(\"key\") &&\n _vm._k($event.keyCode, \"enter\", 13, $event.key, \"Enter\")\n ) {\n return null\n }\n return _vm.handleSearch.apply(null, arguments)\n }\n },\n model: {\n value: _vm.searchKeyword,\n callback: function($$v) {\n _vm.searchKeyword = $$v\n },\n expression: \"searchKeyword\"\n }\n }),\n _c(\n \"el-button\",\n {\n attrs: {\n type: \"primary\",\n size: \"small\",\n icon: \"el-icon-search\",\n disabled: !_vm.companyId\n },\n on: { click: _vm.handleSearch }\n },\n [_vm._v(\"搜索\")]\n ),\n _c(\n \"el-button\",\n {\n attrs: { type: \"success\", size: \"small\", icon: \"el-icon-plus\" },\n on: { click: _vm.handleAdd }\n },\n [_vm._v(\"新增文档\")]\n ),\n _c(\n \"el-button\",\n {\n attrs: {\n type: \"danger\",\n size: \"small\",\n icon: \"el-icon-delete\",\n disabled: !_vm.selectedRows.length\n },\n on: { click: _vm.handleBatchDelete }\n },\n [\n _vm._v(\n \" 批量删除\" +\n _vm._s(\n _vm.selectedRows.length\n ? \" (\" + _vm.selectedRows.length + \")\"\n : \"\"\n ) +\n \" \"\n )\n ]\n ),\n _c(\n \"el-button\",\n {\n attrs: {\n type: \"warning\",\n size: \"small\",\n icon: \"el-icon-folder-opened\",\n disabled: !_vm.selectedRows.length\n },\n on: { click: _vm.handleBatchMove }\n },\n [\n _vm._v(\n \" 批量移动\" +\n _vm._s(\n _vm.selectedRows.length\n ? \" (\" + _vm.selectedRows.length + \")\"\n : \"\"\n ) +\n \" \"\n )\n ]\n )\n ],\n 1\n )\n ]),\n _c(\n \"div\",\n {\n directives: [\n {\n name: \"loading\",\n rawName: \"v-loading\",\n value: _vm.loading,\n expression: \"loading\"\n }\n ],\n staticClass: \"doc-table-wrap\"\n },\n [\n _c(\n \"el-table\",\n {\n ref: \"tableRef\",\n attrs: {\n data: _vm.list,\n stripe: \"\",\n height: \"100%\",\n \"header-cell-style\": {\n background: \"#f5f7fa\",\n color: \"#333\",\n fontWeight: \"600\"\n }\n },\n on: { \"selection-change\": _vm.handleSelectionChange }\n },\n [\n _c(\"el-table-column\", {\n attrs: {\n type: \"selection\",\n width: \"48\",\n align: \"center\",\n selectable: _vm.rowSelectable\n }\n }),\n _c(\"el-table-column\", {\n attrs: { label: \"ID\", prop: \"id\", width: \"80\", align: \"center\" }\n }),\n _c(\"el-table-column\", {\n attrs: {\n label: \"文件标题\",\n prop: \"title\",\n \"min-width\": \"200\",\n \"show-overflow-tooltip\": \"\"\n },\n scopedSlots: _vm._u([\n {\n key: \"default\",\n fn: function(ref) {\n var row = ref.row\n return [\n _c(\"div\", { staticClass: \"file-title-cell\" }, [\n _c(\"img\", {\n staticClass: \"file-icon-img\",\n attrs: {\n src: _vm.getFileIconUrl(row.fileType),\n alt: \"\"\n }\n }),\n _c(\"span\", [_vm._v(_vm._s(row.title))])\n ])\n ]\n }\n }\n ])\n }),\n _c(\"el-table-column\", {\n attrs: {\n label: \"类型\",\n prop: \"fileType\",\n width: \"120\",\n align: \"center\"\n },\n scopedSlots: _vm._u([\n {\n key: \"default\",\n fn: function(ref) {\n var row = ref.row\n return [\n _c(\n \"el-tag\",\n { attrs: { size: \"mini\", effect: \"plain\" } },\n [_vm._v(_vm._s(row.fileType))]\n )\n ]\n }\n }\n ])\n }),\n _c(\"el-table-column\", {\n attrs: { label: \"大小\", width: \"130\", align: \"center\" },\n scopedSlots: _vm._u([\n {\n key: \"default\",\n fn: function(ref) {\n var row = ref.row\n return [_vm._v(_vm._s(_vm.formatSize(row.fileSize)))]\n }\n }\n ])\n }),\n _c(\"el-table-column\", {\n attrs: { label: \"索引状态\", width: \"130\", align: \"center\" },\n scopedSlots: _vm._u([\n {\n key: \"default\",\n fn: function(ref) {\n var row = ref.row\n return [\n _c(\n \"el-tag\",\n {\n attrs: {\n type: _vm.indexTagType(row.indexStatus),\n size: \"small\",\n effect: \"plain\"\n }\n },\n [\n _vm._v(\n \" \" +\n _vm._s(_vm.indexTagText(row.indexStatus)) +\n \" \"\n )\n ]\n )\n ]\n }\n }\n ])\n }),\n _c(\"el-table-column\", {\n attrs: { label: \"创建时间\", width: \"185\", align: \"center\" },\n scopedSlots: _vm._u([\n {\n key: \"default\",\n fn: function(ref) {\n var row = ref.row\n return [_vm._v(_vm._s(_vm.formatTime(row.createTime)))]\n }\n }\n ])\n }),\n _c(\"el-table-column\", {\n attrs: {\n label: \"操作\",\n width: \"400\",\n fixed: \"right\",\n align: \"center\"\n },\n scopedSlots: _vm._u([\n {\n key: \"default\",\n fn: function(ref) {\n var row = ref.row\n return [\n _c(\n \"el-button\",\n {\n attrs: {\n size: \"mini\",\n type: \"text\",\n icon: \"el-icon-view\"\n },\n on: {\n click: function($event) {\n return _vm.handlePreview(row)\n }\n }\n },\n [_vm._v(\"预览\")]\n ),\n _c(\n \"el-button\",\n {\n attrs: {\n size: \"mini\",\n type: \"text\",\n icon: \"el-icon-edit\"\n },\n on: {\n click: function($event) {\n return _vm.handleEdit(row)\n }\n }\n },\n [_vm._v(\"编辑\")]\n ),\n row.indexStatus !== 2\n ? _c(\n \"el-button\",\n {\n attrs: {\n size: \"mini\",\n type: \"text\",\n icon: \"el-icon-coin\"\n },\n on: {\n click: function($event) {\n return _vm.handleIndex(row)\n }\n }\n },\n [\n _vm._v(\n _vm._s(\n row.indexStatus === 1 ? \"重建\" : \"索引\"\n )\n )\n ]\n )\n : _vm._e(),\n _c(\n \"el-button\",\n {\n staticStyle: { color: \"#f56c6c\" },\n attrs: {\n size: \"mini\",\n type: \"text\",\n icon: \"el-icon-delete\"\n },\n on: {\n click: function($event) {\n return _vm.handleDelete(row)\n }\n }\n },\n [_vm._v(\"删除\")]\n )\n ]\n }\n }\n ])\n })\n ],\n 1\n )\n ],\n 1\n ),\n _c(\n \"div\",\n { staticClass: \"doc-pagination\" },\n [\n _c(\"el-pagination\", {\n attrs: {\n \"current-page\": _vm.page,\n \"page-sizes\": [10, 20, 50, 100],\n \"page-size\": _vm.pageSize,\n layout: \"total, sizes, prev, pager, next, jumper\",\n total: _vm.total,\n background: \"\"\n },\n on: {\n \"size-change\": _vm.handleSizeChange,\n \"current-change\": _vm.handleCurrentChange\n }\n })\n ],\n 1\n ),\n _c(\"DocumentForm\", {\n ref: \"docFormRef\",\n attrs: { \"company-id\": _vm.companyId, \"folder-list\": _vm.categoryList }\n }),\n _c(\n \"el-dialog\",\n {\n attrs: {\n visible: _vm.previewVisible,\n width: \"1080px\",\n top: \"4vh\",\n \"close-on-click-modal\": false,\n \"custom-class\": \"wps-preview-dialog\"\n },\n on: {\n \"update:visible\": function($event) {\n _vm.previewVisible = $event\n }\n }\n },\n [\n _c(\"template\", { slot: \"title\" }, [\n _c(\"div\", { staticClass: \"wps-dialog-header\" }, [\n _c(\"div\", { staticClass: \"wps-dialog-left\" }, [\n _c(\"img\", {\n staticClass: \"wps-dialog-icon-img\",\n attrs: {\n src: _vm.getFileIconUrl(\n _vm.previewFile && _vm.previewFile.fileType\n ),\n alt: \"\"\n }\n }),\n _c(\"span\", { staticClass: \"wps-dialog-name\" }, [\n _vm._v(\n _vm._s(\n _vm.previewFile\n ? _vm.previewFile.title ||\n _vm.previewFile.fileName ||\n \"在线预览\"\n : \"在线预览\"\n )\n )\n ]),\n _c(\"span\", { staticClass: \"wps-dialog-badge\" }, [\n _c(\"i\", { staticClass: \"el-icon-view\" }),\n _vm._v(\" 只读预览\")\n ])\n ])\n ])\n ]),\n _c(\n \"div\",\n { staticClass: \"preview-body\" },\n [\n _vm.previewType === \"image\"\n ? _c(\"div\", { staticClass: \"preview-center\" }, [\n _c(\"img\", {\n staticStyle: {\n \"max-width\": \"100%\",\n \"max-height\": \"100%\"\n },\n attrs: { src: _vm.previewFile.fileUrl }\n })\n ])\n : _vm.previewType === \"pdf\"\n ? _c(\"div\", { staticClass: \"preview-page-canvas\" }, [\n _c(\"div\", { staticClass: \"preview-page paper-pdf\" }, [\n _c(\"iframe\", {\n staticClass: \"preview-frame\",\n attrs: { src: _vm.pdfBlobUrl }\n })\n ])\n ])\n : _vm.previewType === \"xlsx\"\n ? _c(\"div\", { staticClass: \"preview-page-canvas\" }, [\n _c(\n \"div\",\n { staticClass: \"preview-page\" },\n [\n _vm.xlsxLoading\n ? _c(\"div\", { staticClass: \"preview-loading\" }, [\n _c(\"i\", { staticClass: \"el-icon-loading\" }),\n _vm._v(\" 正在解析 Excel 文件...\")\n ])\n : [\n _c(\n \"div\",\n { staticClass: \"preview-xlsx-toolbar\" },\n _vm._l(_vm.xlsxSheetNames, function(name) {\n return _c(\n \"span\",\n {\n key: name,\n staticClass: \"preview-xlsx-tab\",\n class: {\n active: _vm.xlsxCurrentSheet === name\n },\n on: {\n click: function($event) {\n _vm.xlsxCurrentSheet = name\n _vm.renderXlsxSheet()\n }\n }\n },\n [_vm._v(_vm._s(name))]\n )\n }),\n 0\n ),\n _c(\n \"div\",\n { staticClass: \"preview-xlsx-table-wrap\" },\n [\n _vm.hasMoreRows\n ? _c(\n \"div\",\n { staticClass: \"preview-xlsx-notice\" },\n [\n _vm._v(\n \" 预览仅显示前 \" +\n _vm._s(_vm.maxPreviewRows) +\n \" 行数据(共更多行),完整数据请下载后查看 \"\n )\n ]\n )\n : _vm._e(),\n _vm.xlsxTableData.length\n ? _c(\n \"table\",\n {\n staticClass: \"preview-xlsx-table\",\n attrs: { contenteditable: \"false\" }\n },\n [\n _c(\"thead\", [\n _c(\n \"tr\",\n _vm._l(_vm.xlsxCols, function(\n col,\n ci\n ) {\n return _c(\"th\", { key: ci }, [\n _vm._v(_vm._s(col))\n ])\n }),\n 0\n )\n ]),\n _c(\n \"tbody\",\n _vm._l(_vm.xlsxTableData, function(\n row,\n ri\n ) {\n return _c(\n \"tr\",\n { key: ri },\n _vm._l(_vm.xlsxCols, function(\n col,\n ci\n ) {\n return _c(\"td\", { key: ci }, [\n _vm._v(_vm._s(row[ci]))\n ])\n }),\n 0\n )\n }),\n 0\n )\n ]\n )\n : _c(\"el-empty\", {\n attrs: {\n description: \"当前 Sheet 无数据\"\n }\n })\n ],\n 1\n )\n ]\n ],\n 2\n )\n ])\n : _vm.previewType === \"docx\"\n ? _c(\"div\", { staticClass: \"preview-page-canvas\" }, [\n _c(\"div\", { staticClass: \"preview-page\" }, [\n _vm.docxLoading\n ? _c(\"div\", { staticClass: \"preview-loading\" }, [\n _c(\"i\", { staticClass: \"el-icon-loading\" }),\n _vm._v(\" 正在解析 Word 文件...\")\n ])\n : _c(\"div\", {\n staticClass: \"preview-docx-content\",\n attrs: { contenteditable: \"false\" },\n domProps: { innerHTML: _vm._s(_vm.docxHtml) }\n })\n ])\n ])\n : _vm.previewType === \"text\"\n ? _c(\"div\", { staticClass: \"preview-page-canvas\" }, [\n _c(\"div\", { staticClass: \"preview-page\" }, [\n _c(\"div\", {\n staticClass: \"preview-markdown\",\n attrs: { contenteditable: \"false\" },\n domProps: { innerHTML: _vm._s(_vm.previewHtml) }\n })\n ])\n ])\n : _c(\"el-empty\", {\n attrs: { description: \"该文件类型暂不支持在线预览\" }\n })\n ],\n 1\n )\n ],\n 2\n ),\n _c(\n \"el-dialog\",\n {\n attrs: {\n title: \"批量移动文件\",\n visible: _vm.moveVisible,\n width: \"420px\",\n \"close-on-click-modal\": false,\n \"append-to-body\": \"\"\n },\n on: {\n \"update:visible\": function($event) {\n _vm.moveVisible = $event\n }\n }\n },\n [\n _c(\"div\", { staticClass: \"move-tip\" }, [\n _vm._v(\" 已选 \"),\n _c(\"span\", { staticClass: \"move-count\" }, [\n _vm._v(_vm._s(_vm.selectedRows.length))\n ]),\n _vm._v(\" 个文件,移动到: \"),\n _c(\n \"span\",\n { class: _vm.moveFolderId ? \"move-target\" : \"move-target-empty\" },\n [_vm._v(_vm._s(_vm.moveTargetName || \"请选择文件夹\"))]\n )\n ]),\n _c(\n \"div\",\n { staticClass: \"move-folder-wrap\" },\n [\n _c(\"el-tree\", {\n ref: \"moveTreeRef\",\n attrs: {\n data: _vm.moveFolderTree,\n props: _vm.folderTreeProps,\n \"node-key\": \"id\",\n \"highlight-current\": \"\",\n \"expand-on-click-node\": false,\n \"default-expand-all\": \"\"\n },\n on: { \"node-click\": _vm.handleMoveFolderSelect }\n })\n ],\n 1\n ),\n _c(\n \"div\",\n { attrs: { slot: \"footer\" }, slot: \"footer\" },\n [\n _c(\n \"el-button\",\n {\n attrs: { size: \"small\" },\n on: {\n click: function($event) {\n _vm.moveVisible = false\n }\n }\n },\n [_vm._v(\"取消\")]\n ),\n _c(\n \"el-button\",\n {\n attrs: {\n size: \"small\",\n type: \"primary\",\n loading: _vm.moving,\n disabled: !_vm.moveFolderId\n },\n on: { click: _vm.handleMoveConfirm }\n },\n [_vm._v(\"确定移动\")]\n )\n ],\n 1\n )\n ]\n )\n ],\n 1\n )\n}\nvar staticRenderFns = []\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/DocumentList.vue?./node_modules/cache-loader/dist/cjs.js?%7B%22cacheDirectory%22:%22node_modules/.cache/vue-loader%22,%22cacheIdentifier%22:%2268609640-vue-loader-template%22%7D!./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
1148
1148
|
|
|
1149
1149
|
/***/ }),
|
|
1150
1150
|
|
|
@@ -1156,7 +1156,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) *
|
|
|
1156
1156
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
1157
1157
|
|
|
1158
1158
|
"use strict";
|
|
1159
|
-
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return staticRenderFns; });\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"div\", { staticClass: \"kb-chat-root\" }, [\n _c(\"div\", { staticClass: \"chat-header\" }, [\n _c(\"div\", { staticClass: \"header-left\" }, [\n _c(\"div\", { staticClass: \"agent-info\" }, [\n _c(\"div\", { staticClass: \"agent-avatar\" }, [\n _c(\"img\", {\n staticClass: \"avatar-image\",\n attrs: { src: _vm.aiAvatar, alt: \"AI\" }\n })\n ]),\n _vm._m(0)\n ])\n ])\n ]),\n _c(\n \"div\",\n { ref: \"messagesRef\", staticClass: \"chat-body\" },\n [\n !_vm.activeSessionId\n ? _c(\"div\", { staticClass: \"welcome welcome-loading\" }, [\n _c(\"div\", { staticClass: \"welcome-title\" }, [\n _vm._v(\"知识库 AI 助手\")\n ]),\n _c(\"div\", { staticClass: \"welcome-text\" }, [\n _vm._v(\"正在初始化会话...\")\n ])\n ])\n : _vm.messages.length === 0\n ? _c(\"div\", { staticClass: \"chat-empty-welcome\" }, [\n _c(\"div\", { staticClass: \"welcome-card\" }, [\n _c(\"div\", { staticClass: \"welcome-avatar-box\" }, [\n _c(\"div\", {\n staticClass: \"welcome-avatar has-chat-agent-img\",\n style: { backgroundImage: \"url(\" + _vm.aiAvatar + \")\" }\n })\n ]),\n _vm._m(1)\n ]),\n _c(\"div\", { staticClass: \"guess-box\" }, [\n _c(\"div\", { staticClass: \"guess-title\" }, [\n _c(\n \"svg\",\n {\n attrs: {\n width: \"16\",\n height: \"16\",\n viewBox: \"0 0 24 24\",\n fill: \"#1677ff\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z\"\n }\n })\n ]\n ),\n _c(\"span\", [_vm._v(\"猜您想问\")])\n ]),\n _c(\n \"div\",\n { staticClass: \"tag-wrap\" },\n _vm._l(_vm.suggestedQuestions, function(question) {\n return _c(\n \"div\",\n {\n key: question,\n staticClass: \"quick-tag\",\n on: {\n click: function($event) {\n return _vm.handleSend(question)\n }\n }\n },\n [_vm._v(\" \" + _vm._s(question) + \" \")]\n )\n }),\n 0\n )\n ])\n ])\n : [\n _vm._l(_vm.messages, function(msg, idx) {\n return _c(\n \"div\",\n { key: \"m-\" + idx, staticClass: \"msg-row\", class: msg.role },\n [\n _c(\"div\", { staticClass: \"msg-main\" }, [\n _c(\"img\", {\n staticClass: \"message-avatar\",\n attrs: {\n src:\n msg.role === \"assistant\"\n ? _vm.aiAvatar\n : _vm.userAvatar,\n alt: msg.role === \"assistant\" ? \"AI\" : \"用户\"\n }\n }),\n _c(\"div\", { staticClass: \"msg-content\" }, [\n _c(\"div\", { staticClass: \"bubble\", class: msg.role }, [\n msg.role === \"assistant\" && msg.thinking\n ? _c(\n \"details\",\n {\n staticClass: \"thinking-details\",\n attrs: { open: \"\" }\n },\n [\n _c(\n \"summary\",\n { staticClass: \"thinking-summary\" },\n [_vm._v(\"💭 思考过程\")]\n ),\n _c(\"div\", {\n staticClass: \"thinking-body\",\n domProps: {\n innerHTML: _vm._s(\n _vm.escapeHtml(msg.thinking)\n )\n }\n })\n ]\n )\n : _vm._e(),\n _c(\"div\", { staticClass: \"final-content\" }, [\n _vm._v(_vm._s(msg.content))\n ]),\n msg.role === \"assistant\" && _vm.sourceText(msg)\n ? _c(\"div\", { staticClass: \"source-line\" }, [\n _vm._v(\"来源:\" + _vm._s(_vm.sourceText(msg)))\n ])\n : _vm._e()\n ]),\n msg.role === \"assistant\" && msg.content\n ? _c(\"div\", { staticClass: \"msg-actions\" }, [\n _c(\n \"span\",\n {\n staticClass: \"action-btn\",\n on: {\n click: function($event) {\n return _vm.copyText(msg.content)\n }\n }\n },\n [_vm._v(\"复制\")]\n ),\n _c(\"span\", { staticClass: \"ai-badge\" }, [\n _vm._v(\"AI 生成(仅供参考)\")\n ])\n ])\n : _vm._e()\n ])\n ])\n ]\n )\n }),\n _vm.streaming\n ? _c(\"div\", { staticClass: \"msg-row assistant\" }, [\n _c(\"div\", { staticClass: \"msg-main\" }, [\n _c(\"img\", {\n staticClass: \"message-avatar\",\n attrs: { src: _vm.aiAvatar, alt: \"AI\" }\n }),\n _c(\"div\", { staticClass: \"msg-content\" }, [\n _c(\"div\", { staticClass: \"bubble assistant\" }, [\n _vm.streamingThinking\n ? _c(\n \"details\",\n {\n staticClass: \"thinking-details\",\n attrs: { open: \"\", \"aria-live\": \"polite\" }\n },\n [\n _c(\n \"summary\",\n { staticClass: \"thinking-summary\" },\n [_vm._v(\"💭 思考过程\")]\n ),\n _c(\"div\", {\n staticClass: \"thinking-body\",\n domProps: {\n innerHTML: _vm._s(\n _vm.escapeHtml(_vm.streamingThinking)\n )\n }\n })\n ]\n )\n : _vm._e(),\n _vm.streamingAnswer\n ? _c(\n \"div\",\n {\n staticClass: \"final-content\",\n attrs: { \"aria-live\": \"polite\" }\n },\n [\n _vm._v(_vm._s(_vm.streamingAnswer)),\n _c(\"span\", { staticClass: \"cursor\" }, [\n _vm._v(\"|\")\n ])\n ]\n )\n : _c(\"div\", { staticClass: \"typing\" }, [\n _c(\"span\", { staticClass: \"phrase\" }, [\n _vm._v(\"思考中\")\n ]),\n _vm._m(2)\n ]),\n _vm.streamingAnswer &&\n _vm.sourceText({\n retrievalSources: _vm.retrievalSources\n })\n ? _c(\"div\", { staticClass: \"source-line\" }, [\n _vm._v(\n \"来源:\" +\n _vm._s(\n _vm.sourceText({\n retrievalSources: _vm.retrievalSources\n })\n )\n )\n ])\n : _vm._e()\n ])\n ])\n ])\n ])\n : _vm._e()\n ]\n ],\n 2\n ),\n _c(\"div\", { staticClass: \"chat-footer\" }, [\n _c(\"div\", { staticClass: \"input-area\" }, [\n _c(\"textarea\", {\n directives: [\n {\n name: \"model\",\n rawName: \"v-model\",\n value: _vm.inputText,\n expression: \"inputText\"\n }\n ],\n ref: \"taRef\",\n staticClass: \"chat-textarea\",\n attrs: {\n placeholder: \"请输入消息\",\n maxlength: \"300\",\n disabled: !_vm.activeSessionId || _vm.streaming\n },\n domProps: { value: _vm.inputText },\n on: {\n input: [\n function($event) {\n if ($event.target.composing) {\n return\n }\n _vm.inputText = $event.target.value\n },\n _vm.autoResizeTextarea\n ],\n keydown: [\n function($event) {\n if (\n !$event.type.indexOf(\"key\") &&\n _vm._k($event.keyCode, \"enter\", 13, $event.key, \"Enter\")\n ) {\n return null\n }\n if (\n $event.ctrlKey ||\n $event.shiftKey ||\n $event.altKey ||\n $event.metaKey\n ) {\n return null\n }\n $event.preventDefault()\n return _vm.handleSend.apply(null, arguments)\n },\n function($event) {\n if (\n !$event.type.indexOf(\"key\") &&\n _vm._k($event.keyCode, \"enter\", 13, $event.key, \"Enter\")\n ) {\n return null\n }\n if (!$event.ctrlKey) {\n return null\n }\n return _vm.newLine.apply(null, arguments)\n }\n ]\n }\n }),\n !_vm.streaming\n ? _c(\n \"button\",\n {\n staticClass: \"send-btn\",\n class: { active: _vm.inputText.trim() },\n attrs: {\n disabled: !_vm.activeSessionId || !_vm.inputText.trim()\n },\n on: { click: _vm.handleSend }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"20\",\n height: \"20\",\n fill: \"currentColor\"\n }\n },\n [\n _c(\"path\", {\n attrs: { d: \"M2.01 21L23 12 2.01 3 2 10l15 2-15 2z\" }\n })\n ]\n )\n ]\n )\n : _vm._e(),\n _vm.streaming\n ? _c(\n \"button\",\n { staticClass: \"stop-btn\", on: { click: _vm.handleStop } },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"18\",\n height: \"18\",\n fill: \"currentColor\"\n }\n },\n [\n _c(\"rect\", {\n attrs: {\n x: \"6\",\n y: \"6\",\n width: \"12\",\n height: \"12\",\n rx: \"2\"\n }\n })\n ]\n )\n ]\n )\n : _vm._e()\n ])\n ])\n ])\n}\nvar staticRenderFns = [\n function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"div\", { staticClass: \"agent-text\" }, [\n _c(\"div\", { staticClass: \"agent-name\" }, [_vm._v(\"知识库 AI\")]),\n _c(\"div\", { staticClass: \"agent-desc\" }, [\n _vm._v(\"基于企业知识库智能问答\")\n ])\n ])\n },\n function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"div\", { staticClass: \"welcome-text-group\" }, [\n _c(\"div\", { staticClass: \"text-sm\" }, [_vm._v(\"您好,我是知识库 AI\")]),\n _c(\"div\", { staticClass: \"text-lg\" }, [\n _vm._v(\"我可以帮您快速查找和解读企业知识\")\n ])\n ])\n },\n function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"span\", { staticClass: \"dots\" }, [\n _c(\"span\"),\n _c(\"span\"),\n _c(\"span\")\n ])\n }\n]\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/KbChatMain.vue?./node_modules/cache-loader/dist/cjs.js?%7B%22cacheDirectory%22:%22node_modules/.cache/vue-loader%22,%22cacheIdentifier%22:%2268609640-vue-loader-template%22%7D!./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
1159
|
+
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"render\", function() { return render; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"staticRenderFns\", function() { return staticRenderFns; });\nvar render = function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"div\", { staticClass: \"kb-chat-root\" }, [\n _c(\"div\", { staticClass: \"chat-header\" }, [\n _c(\"div\", { staticClass: \"header-left\" }, [\n _c(\"div\", { staticClass: \"agent-info\" }, [\n _c(\"div\", { staticClass: \"agent-avatar\" }, [\n _c(\"img\", {\n staticClass: \"avatar-image\",\n attrs: { src: _vm.aiAvatar, alt: \"AI\" }\n })\n ]),\n _vm._m(0)\n ])\n ])\n ]),\n _c(\n \"div\",\n { ref: \"messagesRef\", staticClass: \"chat-body\" },\n [\n !_vm.activeSessionId\n ? _c(\"div\", { staticClass: \"welcome welcome-loading\" }, [\n _c(\"div\", { staticClass: \"welcome-title\" }, [\n _vm._v(\"知识库 AI 助手\")\n ]),\n _c(\"div\", { staticClass: \"welcome-text\" }, [\n _vm._v(\"正在初始化会话...\")\n ])\n ])\n : _vm.messages.length === 0\n ? _c(\"div\", { staticClass: \"chat-empty-welcome\" }, [\n _c(\"div\", { staticClass: \"welcome-card\" }, [\n _c(\"div\", { staticClass: \"welcome-avatar-box\" }, [\n _c(\"div\", {\n staticClass: \"welcome-avatar has-chat-agent-img\",\n style: { backgroundImage: \"url(\" + _vm.aiAvatar + \")\" }\n })\n ]),\n _vm._m(1)\n ]),\n _c(\"div\", { staticClass: \"guess-box\" }, [\n _c(\"div\", { staticClass: \"guess-title\" }, [\n _c(\n \"svg\",\n {\n attrs: {\n width: \"16\",\n height: \"16\",\n viewBox: \"0 0 24 24\",\n fill: \"#1677ff\"\n }\n },\n [\n _c(\"path\", {\n attrs: {\n d:\n \"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z\"\n }\n })\n ]\n ),\n _c(\"span\", [_vm._v(\"猜您想问\")])\n ]),\n _c(\n \"div\",\n { staticClass: \"tag-wrap\" },\n _vm._l(_vm.suggestedQuestions, function(question) {\n return _c(\n \"div\",\n {\n key: question,\n staticClass: \"quick-tag\",\n on: {\n click: function($event) {\n return _vm.handleSend(question)\n }\n }\n },\n [_vm._v(\" \" + _vm._s(question) + \" \")]\n )\n }),\n 0\n )\n ])\n ])\n : _vm._l(_vm.messages, function(msg, idx) {\n return _c(\n \"div\",\n {\n key: msg._key || \"m-\" + idx,\n staticClass: \"msg-row\",\n class: msg.role\n },\n [\n _c(\"div\", { staticClass: \"msg-main\" }, [\n _c(\"img\", {\n staticClass: \"message-avatar\",\n attrs: {\n src:\n msg.role === \"assistant\"\n ? _vm.aiAvatar\n : _vm.userAvatar,\n alt: msg.role === \"assistant\" ? \"AI\" : \"用户\"\n }\n }),\n _c(\"div\", { staticClass: \"msg-content\" }, [\n _c(\"div\", { staticClass: \"bubble\", class: msg.role }, [\n msg.role === \"assistant\" && msg.thinking\n ? _c(\n \"details\",\n {\n staticClass: \"thinking-details\",\n attrs: { open: msg.loading }\n },\n [\n _c(\n \"summary\",\n { staticClass: \"thinking-summary\" },\n [_vm._v(\"💭 思考过程\")]\n ),\n _c(\"div\", {\n staticClass: \"thinking-body\",\n domProps: {\n innerHTML: _vm._s(\n _vm.escapeHtml(msg.thinking)\n )\n }\n })\n ]\n )\n : _vm._e(),\n msg.loading && msg.content\n ? _c(\"div\", {\n staticClass: \"final-content\",\n attrs: { \"aria-live\": \"polite\" },\n domProps: {\n innerHTML: _vm._s(\n _vm.renderStreaming(msg.content, msg._key)\n )\n }\n })\n : msg.content\n ? _c(\"div\", {\n staticClass: \"final-content\",\n domProps: {\n innerHTML: _vm._s(\n _vm.renderMarkdown(msg.content)\n )\n }\n })\n : _vm._e(),\n _c(\n \"div\",\n {\n directives: [\n {\n name: \"show\",\n rawName: \"v-show\",\n value:\n _vm.showToolInfo &&\n msg.toolCalls &&\n msg.toolCalls.length,\n expression:\n \"showToolInfo && msg.toolCalls && msg.toolCalls.length\"\n }\n ],\n staticClass: \"tool-calls\"\n },\n _vm._l(msg.toolCalls, function(tc, ti) {\n return _c(\n \"div\",\n { key: ti, staticClass: \"tool-item\" },\n [\n _c(\"span\", { staticClass: \"tool-badge\" }, [\n _vm._v(\"工具返回\")\n ]),\n _c(\"pre\", [\n _vm._v(\n _vm._s(tc.substring(0, 300)) +\n _vm._s(tc.length > 300 ? \"...\" : \"\")\n )\n ])\n ]\n )\n }),\n 0\n ),\n msg.loading && !msg.content\n ? _c(\"div\", { staticClass: \"typing\" }, [\n _c(\"span\", { staticClass: \"phrase\" }, [\n _vm._v(\"思考中\")\n ]),\n _vm._m(2, true)\n ])\n : _vm._e()\n ]),\n msg.role === \"assistant\" && msg.content && !msg.loading\n ? _c(\"div\", { staticClass: \"msg-actions\" }, [\n _c(\n \"span\",\n {\n staticClass: \"action-btn\",\n on: {\n click: function($event) {\n return _vm.copyText(msg.content)\n }\n }\n },\n [_vm._v(\"复制\")]\n ),\n _c(\"span\", { staticClass: \"ai-badge\" }, [\n _vm._v(\"AI 生成(仅供参考)\")\n ])\n ])\n : _vm._e()\n ])\n ])\n ]\n )\n })\n ],\n 2\n ),\n _c(\"div\", { staticClass: \"chat-footer\" }, [\n _c(\"div\", { staticClass: \"input-area\" }, [\n _c(\"textarea\", {\n directives: [\n {\n name: \"model\",\n rawName: \"v-model\",\n value: _vm.inputText,\n expression: \"inputText\"\n }\n ],\n ref: \"taRef\",\n staticClass: \"chat-textarea\",\n attrs: {\n placeholder: \"请输入消息\",\n maxlength: \"300\",\n disabled: !_vm.activeSessionId || _vm.streaming\n },\n domProps: { value: _vm.inputText },\n on: {\n input: [\n function($event) {\n if ($event.target.composing) {\n return\n }\n _vm.inputText = $event.target.value\n },\n _vm.autoResizeTextarea\n ],\n keydown: [\n function($event) {\n if (\n !$event.type.indexOf(\"key\") &&\n _vm._k($event.keyCode, \"enter\", 13, $event.key, \"Enter\")\n ) {\n return null\n }\n if (\n $event.ctrlKey ||\n $event.shiftKey ||\n $event.altKey ||\n $event.metaKey\n ) {\n return null\n }\n $event.preventDefault()\n return _vm.handleSend.apply(null, arguments)\n },\n function($event) {\n if (\n !$event.type.indexOf(\"key\") &&\n _vm._k($event.keyCode, \"enter\", 13, $event.key, \"Enter\")\n ) {\n return null\n }\n if (!$event.ctrlKey) {\n return null\n }\n return _vm.newLine.apply(null, arguments)\n }\n ]\n }\n }),\n !_vm.streaming\n ? _c(\n \"button\",\n {\n staticClass: \"send-btn\",\n class: { active: _vm.inputText.trim() },\n attrs: {\n disabled: !_vm.activeSessionId || !_vm.inputText.trim()\n },\n on: { click: _vm.handleSend }\n },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"20\",\n height: \"20\",\n fill: \"currentColor\"\n }\n },\n [\n _c(\"path\", {\n attrs: { d: \"M2.01 21L23 12 2.01 3 2 10l15 2-15 2z\" }\n })\n ]\n )\n ]\n )\n : _vm._e(),\n _vm.streaming\n ? _c(\n \"button\",\n { staticClass: \"stop-btn\", on: { click: _vm.handleStop } },\n [\n _c(\n \"svg\",\n {\n attrs: {\n viewBox: \"0 0 24 24\",\n width: \"18\",\n height: \"18\",\n fill: \"currentColor\"\n }\n },\n [\n _c(\"rect\", {\n attrs: {\n x: \"6\",\n y: \"6\",\n width: \"12\",\n height: \"12\",\n rx: \"2\"\n }\n })\n ]\n )\n ]\n )\n : _vm._e()\n ])\n ])\n ])\n}\nvar staticRenderFns = [\n function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"div\", { staticClass: \"agent-text\" }, [\n _c(\"div\", { staticClass: \"agent-name\" }, [_vm._v(\"知识库 AI\")]),\n _c(\"div\", { staticClass: \"agent-desc\" }, [\n _vm._v(\"基于企业知识库智能问答\")\n ])\n ])\n },\n function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"div\", { staticClass: \"welcome-text-group\" }, [\n _c(\"div\", { staticClass: \"text-sm\" }, [_vm._v(\"您好,我是知识库 AI\")]),\n _c(\"div\", { staticClass: \"text-lg\" }, [\n _vm._v(\"我可以帮您快速查找和解读企业知识\")\n ])\n ])\n },\n function() {\n var _vm = this\n var _h = _vm.$createElement\n var _c = _vm._self._c || _h\n return _c(\"span\", { staticClass: \"dots\" }, [\n _c(\"span\"),\n _c(\"span\"),\n _c(\"span\")\n ])\n }\n]\nrender._withStripped = true\n\n\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/KbChatMain.vue?./node_modules/cache-loader/dist/cjs.js?%7B%22cacheDirectory%22:%22node_modules/.cache/vue-loader%22,%22cacheIdentifier%22:%2268609640-vue-loader-template%22%7D!./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
1160
1160
|
|
|
1161
1161
|
/***/ }),
|
|
1162
1162
|
|
|
@@ -1664,7 +1664,7 @@ eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../.
|
|
|
1664
1664
|
/*! no static exports found */
|
|
1665
1665
|
/***/ (function(module, exports, __webpack_require__) {
|
|
1666
1666
|
|
|
1667
|
-
eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../../../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\nexports = ___CSS_LOADER_API_IMPORT___(false);\n// Module\nexports.push([module.i, \"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\r\\n/* H5 列表模式:侧边栏全屏展示 */\\n.chat-layout.h5-list-mode .chat-sidebar {\\r\\n position: relative !important;\\r\\n width: 100% !important;\\r\\n max-width: 100% !important;\\r\\n min-width: 100% !important;\\r\\n height: 100%;\\r\\n transform: translateX(0) !important;\\r\\n box-shadow: none !important;\\r\\n border-right: none;\\n}\\n.chat-layout.h5-list-mode .chat-sidebar .sidebar-header {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n justify-content: center;\\r\\n padding: 14px 16px;\\r\\n position: relative;\\n}\\n.chat-layout.h5-list-mode .chat-sidebar .sidebar-header img {\\r\\n position: absolute;\\r\\n right: 16px;\\r\\n top: 50%;\\r\\n transform: translateY(-50%);\\r\\n width: 22px;\\r\\n height: 22px;\\r\\n -o-object-fit: contain;\\r\\n object-fit: contain;\\r\\n cursor: pointer;\\n}\\n@media screen and (max-width: 768px) {\\n.chat-sidebar {\\r\\n position: absolute;\\r\\n inset: 0 auto 0 0;\\r\\n width: min(82vw, 320px);\\r\\n min-width: auto;\\r\\n max-width: 320px;\\r\\n height: 100%;\\r\\n border-right: none;\\r\\n box-shadow: 8px 0 24px rgba(15, 23, 42, 0.12);\\r\\n transform: translateX(-100%);\\n}\\n.chat-sidebar.slide-mode {\\r\\n transition: transform 0.24s ease;\\n}\\n.chat-sidebar.open {\\r\\n transform: translateX(0);\\n}\\n}\\r\\n\", \"\"]);\n// Exports\nmodule.exports = exports;\n\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/components/chatMain/index.vue?./node_modules/css-loader/dist/cjs.js??ref--6-oneOf-1-1!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src??ref--6-oneOf-1-2!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
1667
|
+
eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../../../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\nexports = ___CSS_LOADER_API_IMPORT___(false);\n// Module\nexports.push([module.i, \"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\r\\n/* H5 列表模式:侧边栏全屏展示 */\\n.chat-layout.h5-list-mode .chat-sidebar {\\r\\n position: relative !important;\\r\\n width: 100% !important;\\r\\n max-width: 100% !important;\\r\\n min-width: 100% !important;\\r\\n height: 100%;\\r\\n transform: translateX(0) !important;\\r\\n box-shadow: none !important;\\r\\n border-right: none;\\n}\\n.chat-layout.h5-list-mode .chat-sidebar .sidebar-header {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n justify-content: center;\\r\\n padding: 14px 16px;\\r\\n position: relative;\\n}\\n.chat-layout.h5-list-mode .chat-sidebar .sidebar-header img {\\r\\n position: absolute;\\r\\n right: 16px;\\r\\n top: 50%;\\r\\n transform: translateY(-50%);\\r\\n width: 22px;\\r\\n height: 22px;\\r\\n -o-object-fit: contain;\\r\\n object-fit: contain;\\r\\n cursor: pointer;\\n}\\n@media screen and (max-width: 768px) {\\n.chat-sidebar {\\r\\n position: absolute;\\r\\n inset: 0 auto 0 0;\\r\\n width: min(82vw, 320px);\\r\\n min-width: auto;\\r\\n max-width: 320px;\\r\\n height: 100%;\\r\\n border-right: none;\\r\\n box-shadow: 8px 0 24px rgba(15, 23, 42, 0.12);\\r\\n transform: translateX(-100%);\\n}\\n.chat-sidebar.slide-mode {\\r\\n transition: transform 0.24s ease;\\n}\\n.chat-sidebar.open {\\r\\n transform: translateX(0);\\n}\\n}\\r\\n\", \"\"]);\n// Exports\nmodule.exports = exports;\n\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/components/chatMain/index.vue?./node_modules/css-loader/dist/cjs.js??ref--6-oneOf-1-1!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src??ref--6-oneOf-1-2!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
1668
1668
|
|
|
1669
1669
|
/***/ }),
|
|
1670
1670
|
|
|
@@ -1686,7 +1686,7 @@ eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../.
|
|
|
1686
1686
|
/*! no static exports found */
|
|
1687
1687
|
/***/ (function(module, exports, __webpack_require__) {
|
|
1688
1688
|
|
|
1689
|
-
eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\nexports = ___CSS_LOADER_API_IMPORT___(false);\n// Module\nexports.push([module.i, \"\\n.kb-category-manager[data-v-9baf7408] {\\r\\n height: 100%;\\r\\n display: flex;\\r\\n flex-direction: column;\\r\\n background: #fff;\\n}\\n.kb-category-manager[data-v-9baf7408],\\r\\n.kb-category-manager *[data-v-9baf7408],\\r\\n.kb-category-manager *[data-v-9baf7408]::before,\\r\\n.kb-category-manager *[data-v-9baf7408]::after {\\r\\n font-size: 14px !important;\\n}\\n.category-header[data-v-9baf7408] {\\r\\n display: flex;\\r\\n justify-content: space-between;\\r\\n align-items: center;\\r\\n padding: 16px;\\r\\n font-weight: 600;\\r\\n font-size: 15px;\\r\\n color: #303133;\\r\\n border-bottom: 1px solid #ebeef5;\\r\\n background: #fff;\\n}\\n.category-header > div[data-v-9baf7408] {\\r\\n display: flex;\\r\\n gap:
|
|
1689
|
+
eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\nexports = ___CSS_LOADER_API_IMPORT___(false);\n// Module\nexports.push([module.i, \"\\n.kb-category-manager[data-v-9baf7408] {\\r\\n height: 100%;\\r\\n display: flex;\\r\\n flex-direction: column;\\r\\n background: #fff;\\n}\\n.kb-category-manager[data-v-9baf7408],\\r\\n.kb-category-manager *[data-v-9baf7408],\\r\\n.kb-category-manager *[data-v-9baf7408]::before,\\r\\n.kb-category-manager *[data-v-9baf7408]::after {\\r\\n font-size: 14px !important;\\n}\\n.category-header[data-v-9baf7408] {\\r\\n display: flex;\\r\\n justify-content: space-between;\\r\\n align-items: center;\\r\\n padding: 14px 16px;\\r\\n font-weight: 600;\\r\\n font-size: 15px;\\r\\n color: #303133;\\r\\n border-bottom: 1px solid #ebeef5;\\r\\n background: #fff;\\n}\\n.category-header .category-title[data-v-9baf7408] {\\r\\n display: inline-flex;\\r\\n align-items: center;\\r\\n gap: 8px;\\n}\\n.category-header .category-title[data-v-9baf7408]::before {\\r\\n content: '';\\r\\n width: 4px;\\r\\n height: 16px;\\r\\n border-radius: 2px;\\r\\n background: linear-gradient(180deg, #409eff, #79bbff);\\n}\\n.category-header > div[data-v-9baf7408] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n gap: 2px;\\n}\\n.category-list[data-v-9baf7408] {\\r\\n flex: 1;\\r\\n overflow-y: auto;\\r\\n padding: 10px 8px;\\n}\\r\\n\\r\\n/* 滚动条 */\\n.category-list[data-v-9baf7408]::-webkit-scrollbar {\\r\\n width: 6px;\\n}\\n.category-list[data-v-9baf7408]::-webkit-scrollbar-track {\\r\\n background: transparent;\\n}\\n.category-list[data-v-9baf7408]::-webkit-scrollbar-thumb {\\r\\n background: #dcdfe6;\\r\\n border-radius: 3px;\\n}\\n.category-list[data-v-9baf7408]::-webkit-scrollbar-thumb:hover {\\r\\n background: #c0c4cc;\\n}\\n.tree-node[data-v-9baf7408] {\\r\\n flex: 1;\\r\\n display: flex;\\r\\n align-items: center;\\r\\n font-size: 14px;\\r\\n padding-right: 8px;\\r\\n overflow: hidden;\\r\\n color: #606266;\\n}\\n.tree-icon[data-v-9baf7408] {\\r\\n margin-right: 8px;\\r\\n color: #909399;\\r\\n flex-shrink: 0;\\r\\n font-size: 16px;\\r\\n transition: color 0.2s;\\n}\\n.tree-node:hover .tree-icon[data-v-9baf7408] {\\r\\n color: #409eff;\\n}\\n.tree-label[data-v-9baf7408] {\\r\\n flex: 1;\\r\\n min-width: 0;\\r\\n overflow: hidden;\\r\\n text-overflow: ellipsis;\\r\\n white-space: nowrap;\\r\\n font-weight: 500;\\r\\n transition: color 0.2s;\\n}\\n.tree-actions[data-v-9baf7408] {\\r\\n flex-shrink: 0;\\r\\n opacity: 0;\\r\\n transition: opacity 0.2s;\\r\\n margin-left: 8px;\\r\\n white-space: nowrap;\\r\\n display: flex;\\r\\n align-items: center;\\r\\n gap: 2px;\\n}\\r\\n\\r\\n/* 操作按钮 */\\n.tree-actions[data-v-9baf7408] .el-button--mini,\\r\\n.category-header[data-v-9baf7408] .el-button--mini {\\r\\n padding: 6px;\\r\\n font-size: 14px;\\r\\n border-radius: 6px;\\r\\n transition: all 0.2s;\\r\\n border: none;\\r\\n background: transparent;\\r\\n margin-left: 0;\\n}\\n.tree-actions[data-v-9baf7408] .el-button--mini + .el-button--mini {\\r\\n margin-left: 2px;\\n}\\n.tree-actions[data-v-9baf7408] .el-button--mini:hover {\\r\\n background: #ecf5ff;\\r\\n color: #409eff;\\n}\\n.tree-actions[data-v-9baf7408] .el-button--mini[style*=\\\"color: #f56c6c\\\"]:hover {\\r\\n background: #fef0f0;\\r\\n color: #f56c6c;\\n}\\n.category-header[data-v-9baf7408] .el-button--mini:hover {\\r\\n background: #ecf5ff;\\r\\n color: #409eff;\\n}\\n.tree-node:hover .tree-actions[data-v-9baf7408] {\\r\\n opacity: 1;\\n}\\n.show-all-btn[data-v-9baf7408] {\\r\\n width: 100%;\\r\\n margin: 0;\\r\\n padding: 12px;\\r\\n border-top: 1px solid #ebeef5;\\r\\n border-radius: 0;\\r\\n color: #409eff;\\r\\n background: #fff;\\r\\n font-weight: 500;\\r\\n font-size: 14px;\\r\\n transition: all 0.2s;\\n}\\n.show-all-btn[data-v-9baf7408]:hover {\\r\\n background: #ecf5ff;\\n}\\n.show-all-btn[data-v-9baf7408]:disabled {\\r\\n color: #c0c4cc;\\r\\n background: #fff;\\r\\n opacity: 0.5;\\n}\\n.category-empty[data-v-9baf7408] {\\r\\n text-align: center;\\r\\n padding: 40px 16px;\\r\\n color: #909399;\\r\\n font-size: 14px;\\n}\\r\\n\\r\\n/* Element UI 树组件 */\\n.kb-category-manager[data-v-9baf7408] .el-tree {\\r\\n background: transparent;\\r\\n color: #606266;\\n}\\n.kb-category-manager[data-v-9baf7408] .el-tree-node__content {\\r\\n background: transparent;\\r\\n border-radius: 8px;\\r\\n transition: background 0.2s;\\r\\n padding: 0 4px 0 2px;\\r\\n height: auto;\\r\\n min-height: 34px;\\r\\n margin-bottom: 2px;\\r\\n position: relative;\\n}\\n.kb-category-manager[data-v-9baf7408] .el-tree-node__content:hover {\\r\\n background: #f5f7fa;\\n}\\n.kb-category-manager[data-v-9baf7408] .el-tree-node.is-current > .el-tree-node__content {\\r\\n background: #ecf5ff;\\r\\n color: #409eff;\\n}\\n.kb-category-manager[data-v-9baf7408] .el-tree-node.is-current > .el-tree-node__content::before {\\r\\n content: '';\\r\\n position: absolute;\\r\\n left: 0;\\r\\n top: 50%;\\r\\n transform: translateY(-50%);\\r\\n width: 3px;\\r\\n height: 16px;\\r\\n border-radius: 2px;\\r\\n background: #409eff;\\n}\\n.kb-category-manager[data-v-9baf7408] .el-tree-node.is-current > .el-tree-node__content .tree-label,\\r\\n.kb-category-manager[data-v-9baf7408] .el-tree-node.is-current > .el-tree-node__content .tree-icon {\\r\\n color: #409eff;\\n}\\n.kb-category-manager[data-v-9baf7408] .el-tree-node__expand-icon {\\r\\n color: #c0c4cc;\\r\\n font-size: 14px;\\r\\n transition: color 0.2s;\\n}\\n.kb-category-manager[data-v-9baf7408] .el-tree-node__expand-icon:hover {\\r\\n color: #409eff;\\n}\\r\\n\\r\\n/* 无子级文件夹:隐藏展开图标 */\\n.kb-category-manager[data-v-9baf7408] .el-tree-node__expand-icon.is-leaf {\\r\\n color: transparent;\\r\\n cursor: default;\\n}\\n.kb-category-manager[data-v-9baf7408] .el-tree-node__expand-icon.expanded {\\r\\n color: #409eff;\\n}\\r\\n\", \"\"]);\n// Exports\nmodule.exports = exports;\n\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/CategoryManager.vue?./node_modules/css-loader/dist/cjs.js??ref--6-oneOf-1-1!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src??ref--6-oneOf-1-2!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
1690
1690
|
|
|
1691
1691
|
/***/ }),
|
|
1692
1692
|
|
|
@@ -1697,7 +1697,7 @@ eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../.
|
|
|
1697
1697
|
/*! no static exports found */
|
|
1698
1698
|
/***/ (function(module, exports, __webpack_require__) {
|
|
1699
1699
|
|
|
1700
|
-
eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\nexports = ___CSS_LOADER_API_IMPORT___(false);\n// Module\nexports.push([module.i, \"\\n.el-upload__tip[data-v-4b2b74bc] {\\r\\n line-height: 1.5;\\r\\n color: #909399;\\r\\n font-size: 12px;\\n}\\n.md-editor-wrap[data-v-4b2b74bc] {\\r\\n border: 1px solid #dcdfe6;\\r\\n border-radius: 6px;\\r\\n overflow: hidden;\\r\\n background: #fff;\\n}\\n.md-editor-toolbar[data-v-4b2b74bc] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n border-bottom: 1px solid #e8eaed;\\r\\n background: #f8f9fb;\\n}\\n.md-editor-tab[data-v-4b2b74bc] {\\r\\n padding: 10px 20px;\\r\\n font-size: 13px;\\r\\n cursor: pointer;\\r\\n border-bottom: 2px solid transparent;\\r\\n transition: all 0.2s;\\r\\n -webkit-user-select: none;\\r\\n -moz-user-select: none;\\r\\n -ms-user-select: none;\\r\\n user-select: none;\\r\\n color: #606266;\\n}\\n.md-editor-tab[data-v-4b2b74bc]:hover {\\r\\n color: #409eff;\\n}\\n.md-editor-tab.active[data-v-4b2b74bc] {\\r\\n color: #409eff;\\r\\n border-bottom-color: #409eff;\\r\\n background: #fff;\\n}\\n.md-editor-status[data-v-4b2b74bc] {\\r\\n margin-left: auto;\\r\\n padding-right: 16px;\\r\\n font-size: 12px;\\r\\n color: #909399;\\n}\\n.md-textarea[data-v-4b2b74bc] {\\r\\n width: 100%;\\r\\n height: 450px;\\r\\n border: 0;\\r\\n outline: none;\\r\\n resize: vertical;\\r\\n padding: 16px;\\r\\n font-size: 14px;\\r\\n line-height: 1.7;\\r\\n font-family: 'SF Mono', 'Monaco', 'Menlo', 'Consolas', 'Courier New', monospace;\\r\\n box-sizing: border-box;\\r\\n color: #1f2937;\\r\\n background: #fafbfc;\\n}\\n.md-textarea[data-v-4b2b74bc]:focus {\\r\\n background: #fff;\\n}\\n.md-preview[data-v-4b2b74bc] {\\r\\n height: 450px;\\r\\n overflow: auto;\\r\\n padding: 16px 20px;\\r\\n font-size: 14px;\\r\\n line-height: 1.8;\\r\\n color: #333;\\r\\n background: #fff;\\r\\n box-sizing: border-box;\\n}\\r\\n/* 滚动条 */\\n.md-preview[data-v-4b2b74bc]::-webkit-scrollbar,\\r\\n.md-textarea[data-v-4b2b74bc]::-webkit-scrollbar {\\r\\n width: 6px;\\r\\n height: 6px;\\n}\\n.md-preview[data-v-4b2b74bc]::-webkit-scrollbar-thumb,\\r\\n.md-textarea[data-v-4b2b74bc]::-webkit-scrollbar-thumb {\\r\\n background: #d0d5dd;\\r\\n border-radius: 3px;\\n}\\n.md-preview[data-v-4b2b74bc]::-webkit-scrollbar-thumb:hover,\\r\\n.md-textarea[data-v-4b2b74bc]::-webkit-scrollbar-thumb:hover {\\r\\n background: #b0b5bd;\\n}\\r\\n/* 标题 */\\n.md-preview h1[data-v-4b2b74bc] {\\r\\n font-size: 24px;\\r\\n margin: 20px 0 10px;\\r\\n border-bottom: 2px solid #e8eaed;\\r\\n padding-bottom: 8px;\\r\\n color: #1a1a2e;\\n}\\n.md-preview h2[data-v-4b2b74bc] {\\r\\n font-size: 20px;\\r\\n margin: 18px 0 8px;\\r\\n padding-bottom: 4px;\\r\\n border-bottom: 1px solid #f0f0f0;\\r\\n color: #1a1a2e;\\n}\\n.md-preview h3[data-v-4b2b74bc] {\\r\\n font-size: 17px;\\r\\n margin: 14px 0 6px;\\r\\n color: #2c3e50;\\n}\\n.md-preview h4[data-v-4b2b74bc] {\\r\\n font-size: 15px;\\r\\n margin: 12px 0 4px;\\r\\n color: #34495e;\\n}\\n.md-preview p[data-v-4b2b74bc] {\\r\\n margin: 10px 0;\\n}\\n.md-preview ul[data-v-4b2b74bc], .md-preview ol[data-v-4b2b74bc] {\\r\\n padding-left: 26px;\\r\\n margin: 10px 0;\\n}\\n.md-preview li[data-v-4b2b74bc] {\\r\\n margin: 4px 0;\\n}\\n.md-preview li > ul[data-v-4b2b74bc], .md-preview li > ol[data-v-4b2b74bc] {\\r\\n margin: 2px 0;\\n}\\r\\n/* 行内代码 */\\n.md-preview code[data-v-4b2b74bc]:not(pre code) {\\r\\n background: #f0f2f5;\\r\\n padding: 2px 8px;\\r\\n border-radius: 4px;\\r\\n font-size: 13px;\\r\\n color: #c7254e;\\r\\n border: 1px solid #e8eaed;\\n}\\r\\n/* 代码块 */\\n.md-preview pre[data-v-4b2b74bc] {\\r\\n background: #1e1e2e;\\r\\n color: #cdd6f4;\\r\\n padding: 18px 20px;\\r\\n border-radius: 8px;\\r\\n overflow-x: auto;\\r\\n font-size: 13px;\\r\\n line-height: 1.65;\\r\\n margin: 14px 0;\\r\\n border: 0;\\n}\\n.md-preview pre code[data-v-4b2b74bc] {\\r\\n background: none;\\r\\n padding: 0;\\r\\n color: inherit;\\r\\n font-size: inherit;\\r\\n border: 0;\\n}\\r\\n/* 引用 */\\n.md-preview blockquote[data-v-4b2b74bc] {\\r\\n border-left: 4px solid #409eff;\\r\\n padding: 10px 18px;\\r\\n margin: 14px 0;\\r\\n background: #f0f7ff;\\r\\n color: #555;\\r\\n border-radius: 0 4px 4px 0;\\n}\\n.md-preview blockquote p[data-v-4b2b74bc] {\\r\\n margin: 6px 0;\\n}\\r\\n/* 表格 */\\n.md-preview table[data-v-4b2b74bc] {\\r\\n border-collapse: collapse;\\r\\n width: 100%;\\r\\n margin: 14px 0;\\r\\n font-size: 13px;\\n}\\n.md-preview th[data-v-4b2b74bc], .md-preview td[data-v-4b2b74bc] {\\r\\n border: 1px solid #e0e2e6;\\r\\n padding: 10px 14px;\\r\\n text-align: left;\\n}\\n.md-preview th[data-v-4b2b74bc] {\\r\\n background: #f5f7fa;\\r\\n font-weight: 600;\\r\\n color: #1f2937;\\n}\\n.md-preview tr[data-v-4b2b74bc]:nth-child(even) {\\r\\n background: #fafbfc;\\n}\\n.md-preview tr[data-v-4b2b74bc]:hover {\\r\\n background: #f0f7ff;\\n}\\r\\n/* 图片 */\\n.md-preview img[data-v-4b2b74bc] {\\r\\n max-width: 100%;\\r\\n border-radius: 6px;\\r\\n margin: 8px 0;\\r\\n box-shadow: 0 1px 4px rgba(0,0,0,0.1);\\n}\\r\\n/* 链接 */\\n.md-preview a[data-v-4b2b74bc] {\\r\\n color: #409eff;\\r\\n text-decoration: none;\\n}\\n.md-preview a[data-v-4b2b74bc]:hover {\\r\\n text-decoration: underline;\\r\\n color: #2a7de1;\\n}\\r\\n/* 分割线 */\\n.md-preview hr[data-v-4b2b74bc] {\\r\\n border: none;\\r\\n border-top: 1px solid #e8eaed;\\r\\n margin: 20px 0;\\n}\\r\\n/* 任务列表 */\\n.md-preview input[type=\\\"checkbox\\\"][data-v-4b2b74bc] {\\r\\n margin-right: 6px;\\n}\\r\\n\\r\\n/* ==================== Excel 编辑器样式 ==================== */\\n.xlsx-editor-wrap[data-v-4b2b74bc] {\\r\\n border: 1px solid #dcdfe6;\\r\\n border-radius: 6px;\\r\\n overflow: hidden;\\n}\\n.xlsx-toolbar[data-v-4b2b74bc] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n flex-wrap: wrap;\\r\\n gap: 8px;\\r\\n padding: 8px 12px;\\r\\n background: #f8f9fb;\\r\\n border-bottom: 1px solid #e8eaed;\\n}\\n.xlsx-sheet-tabs[data-v-4b2b74bc] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n gap: 2px;\\n}\\n.xlsx-sheet-tab[data-v-4b2b74bc] {\\r\\n padding: 4px 12px;\\r\\n font-size: 12px;\\r\\n cursor: pointer;\\r\\n border: 1px solid transparent;\\r\\n border-radius: 3px 3px 0 0;\\r\\n -webkit-user-select: none;\\r\\n -moz-user-select: none;\\r\\n -ms-user-select: none;\\r\\n user-select: none;\\r\\n color: #606266;\\r\\n background: #f0f2f5;\\n}\\n.xlsx-sheet-tab[data-v-4b2b74bc]:hover {\\r\\n color: #409eff;\\n}\\n.xlsx-sheet-tab.active[data-v-4b2b74bc] {\\r\\n color: #409eff;\\r\\n background: #fff;\\r\\n border-color: #dcdfe6;\\r\\n border-bottom-color: #fff;\\n}\\n.xlsx-actions[data-v-4b2b74bc] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n gap: 6px;\\r\\n margin-left: auto;\\r\\n flex-wrap: wrap;\\n}\\n.xlsx-info[data-v-4b2b74bc] {\\r\\n font-size: 12px;\\r\\n color: #909399;\\r\\n margin-left: 4px;\\r\\n white-space: nowrap;\\n}\\n.xlsx-table-wrap[data-v-4b2b74bc] {\\r\\n overflow-x: auto;\\r\\n padding: 4px;\\n}\\n.xlsx-table-wrap .el-table[data-v-4b2b74bc] {\\r\\n font-size: 12px;\\n}\\n.xlsx-table-wrap .el-input__inner[data-v-4b2b74bc] {\\r\\n border: 1px solid transparent;\\r\\n background: transparent;\\r\\n padding: 0 4px;\\r\\n height: 28px;\\r\\n line-height: 28px;\\r\\n border-radius: 2px;\\n}\\n.xlsx-table-wrap .el-input__inner[data-v-4b2b74bc]:hover {\\r\\n border-color: #c0c4cc;\\n}\\n.xlsx-table-wrap .el-input__inner[data-v-4b2b74bc]:focus {\\r\\n border-color: #409eff;\\r\\n background: #fff;\\n}\\n.xlsx-empty[data-v-4b2b74bc] {\\r\\n padding: 40px 0;\\n}\\n.xlsx-loading[data-v-4b2b74bc] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n justify-content: center;\\r\\n padding: 60px 0;\\r\\n color: #909399;\\r\\n font-size: 14px;\\r\\n gap: 8px;\\n}\\r\\n\\r\\n/* ==================== Word 编辑器样式 ==================== */\\n.word-editor-wrap[data-v-4b2b74bc] {\\r\\n border: 1px solid #dcdfe6;\\r\\n border-radius: 8px;\\r\\n overflow: hidden;\\r\\n background: #fff;\\n}\\n.word-loading[data-v-4b2b74bc] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n justify-content: center;\\r\\n padding: 60px 0;\\r\\n color: #909399;\\r\\n font-size: 14px;\\r\\n gap: 8px;\\n}\\n.word-header[data-v-4b2b74bc] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n justify-content: space-between;\\r\\n padding: 8px 14px;\\r\\n background: #f7f8fa;\\r\\n border-bottom: 1px solid #e8eaed;\\r\\n font-size: 13px;\\n}\\n.word-filename[data-v-4b2b74bc] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n gap: 6px;\\r\\n color: #303133;\\r\\n font-weight: 500;\\r\\n max-width: 60%;\\r\\n overflow: hidden;\\r\\n text-overflow: ellipsis;\\r\\n white-space: nowrap;\\n}\\n.word-filename .el-icon-document[data-v-4b2b74bc] {\\r\\n color: #409eff;\\r\\n font-size: 15px;\\n}\\n.word-stats[data-v-4b2b74bc] {\\r\\n color: #909399;\\r\\n font-size: 12px;\\r\\n flex-shrink: 0;\\n}\\n.word-toolbar[data-v-4b2b74bc] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n flex-wrap: wrap;\\r\\n gap: 1px;\\r\\n padding: 5px 10px;\\r\\n background: #fafbfc;\\r\\n border-bottom: 1px solid #e8eaed;\\n}\\n.word-tb-btn[data-v-4b2b74bc] {\\r\\n display: inline-flex;\\r\\n align-items: center;\\r\\n justify-content: center;\\r\\n width: 30px;\\r\\n height: 28px;\\r\\n border: 1px solid transparent;\\r\\n background: transparent;\\r\\n border-radius: 4px;\\r\\n cursor: pointer;\\r\\n color: #5f6368;\\r\\n outline: none;\\r\\n transition: all 0.15s;\\n}\\n.word-tb-btn[data-v-4b2b74bc]:hover {\\r\\n background: #e8eaed;\\r\\n color: #202124;\\n}\\n.word-tb-btn[data-v-4b2b74bc]:active {\\r\\n background: #d2d5d9;\\n}\\n.word-tb-btn-heading[data-v-4b2b74bc] {\\r\\n font-size: 11px;\\r\\n font-weight: 600;\\r\\n font-family: 'Segoe UI', Arial, sans-serif;\\r\\n width: 32px;\\n}\\n.word-tb-sep[data-v-4b2b74bc] {\\r\\n display: inline-block;\\r\\n width: 1px;\\r\\n height: 18px;\\r\\n background: #dcdfe6;\\r\\n margin: 0 5px;\\r\\n flex-shrink: 0;\\n}\\n.word-editor-content[data-v-4b2b74bc] {\\r\\n min-height: 380px;\\r\\n max-height: 500px;\\r\\n overflow-y: auto;\\r\\n padding: 18px 22px;\\r\\n font-size: 14px;\\r\\n line-height: 1.8;\\r\\n color: #333;\\r\\n outline: none;\\r\\n -moz-user-select: text;\\r\\n -ms-user-select: text;\\r\\n user-select: text;\\r\\n -webkit-user-select: text;\\n}\\n.word-editor-content[data-v-4b2b74bc]:focus {\\r\\n background: #fff;\\n}\\n.word-editor-content h1[data-v-4b2b74bc] {\\r\\n font-size: 22px;\\r\\n font-weight: 700;\\r\\n margin: 20px 0 10px;\\r\\n padding-bottom: 6px;\\r\\n border-bottom: 2px solid #e8eaed;\\r\\n color: #1a1a2e;\\n}\\n.word-editor-content h2[data-v-4b2b74bc] {\\r\\n font-size: 18px;\\r\\n font-weight: 600;\\r\\n margin: 16px 0 8px;\\r\\n padding-bottom: 4px;\\r\\n border-bottom: 1px solid #e8eaed;\\r\\n color: #1a1a2e;\\n}\\n.word-editor-content h3[data-v-4b2b74bc] {\\r\\n font-size: 16px;\\r\\n font-weight: 600;\\r\\n margin: 14px 0 6px;\\r\\n color: #2c3e50;\\n}\\n.word-editor-content p[data-v-4b2b74bc] {\\r\\n margin: 8px 0;\\n}\\n.word-editor-content ul[data-v-4b2b74bc], .word-editor-content ol[data-v-4b2b74bc] {\\r\\n padding-left: 24px;\\r\\n margin: 8px 0;\\n}\\n.word-editor-content li[data-v-4b2b74bc] {\\r\\n margin: 3px 0;\\n}\\n.word-editor-content blockquote[data-v-4b2b74bc] {\\r\\n border-left: 3px solid #409eff;\\r\\n padding: 6px 14px;\\r\\n margin: 10px 0;\\r\\n background: #f0f7ff;\\r\\n color: #555;\\r\\n border-radius: 0 4px 4px 0;\\n}\\n.word-editor-content pre[data-v-4b2b74bc] {\\r\\n background: #f5f7fa;\\r\\n padding: 12px 16px;\\r\\n border-radius: 6px;\\r\\n overflow-x: auto;\\r\\n font-size: 13px;\\r\\n border: 1px solid #e8eaed;\\r\\n margin: 10px 0;\\r\\n font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;\\n}\\n.word-editor-content a[data-v-4b2b74bc] {\\r\\n color: #409eff;\\r\\n text-decoration: underline;\\n}\\n.word-editor-content img[data-v-4b2b74bc] {\\r\\n max-width: 100%;\\r\\n border-radius: 4px;\\r\\n margin: 6px 0;\\n}\\n.word-editor-content[data-v-4b2b74bc] ::-moz-selection {\\r\\n background: #c7dbff;\\n}\\n.word-editor-content[data-v-4b2b74bc] ::selection {\\r\\n background: #c7dbff;\\n}\\r\\n\", \"\"]);\n// Exports\nmodule.exports = exports;\n\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/DocumentForm.vue?./node_modules/css-loader/dist/cjs.js??ref--6-oneOf-1-1!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src??ref--6-oneOf-1-2!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
1700
|
+
eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\nexports = ___CSS_LOADER_API_IMPORT___(false);\n// Module\nexports.push([module.i, \"\\n.el-upload__tip[data-v-4b2b74bc] {\\r\\n line-height: 1.5;\\r\\n color: #909399;\\r\\n font-size: 12px;\\n}\\r\\n/* 所属文件夹下拉树选择 */\\n.folder-picker-input[data-v-4b2b74bc] {\\r\\n width: 100%;\\n}\\n.folder-picker-input-el[data-v-4b2b74bc] {\\r\\n width: 100%;\\n}\\n.folder-tree-wrap[data-v-4b2b74bc] {\\r\\n max-height: 280px;\\r\\n overflow-y: auto;\\r\\n padding: 4px;\\n}\\n.folder-tree-wrap[data-v-4b2b74bc] .el-tree {\\r\\n background: transparent;\\r\\n color: #606266;\\n}\\n.folder-tree-wrap[data-v-4b2b74bc] .el-tree-node__content {\\r\\n height: 30px;\\r\\n border-radius: 4px;\\n}\\n.folder-tree-wrap[data-v-4b2b74bc] .el-tree-node__content:hover {\\r\\n background: #f5f7fa;\\n}\\n.folder-tree-wrap[data-v-4b2b74bc] .el-tree-node.is-current > .el-tree-node__content {\\r\\n background: #ecf5ff;\\r\\n color: #409eff;\\n}\\n.md-editor-wrap[data-v-4b2b74bc] {\\r\\n border: 1px solid #e4e7ed;\\r\\n border-radius: 8px;\\r\\n overflow: hidden;\\r\\n background: #fff;\\r\\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);\\n}\\n.md-editor-toolbar[data-v-4b2b74bc] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n border-bottom: 1px solid #e4e7ed;\\r\\n background: #fafbfc;\\n}\\n.md-editor-tab[data-v-4b2b74bc] {\\r\\n padding: 10px 20px;\\r\\n font-size: 13px;\\r\\n cursor: pointer;\\r\\n border-bottom: 2px solid transparent;\\r\\n transition: all 0.2s;\\r\\n -webkit-user-select: none;\\r\\n -moz-user-select: none;\\r\\n -ms-user-select: none;\\r\\n user-select: none;\\r\\n color: #606266;\\n}\\n.md-editor-tab[data-v-4b2b74bc]:hover {\\r\\n color: #409eff;\\n}\\n.md-editor-tab.active[data-v-4b2b74bc] {\\r\\n color: #409eff;\\r\\n border-bottom-color: #409eff;\\r\\n background: #fff;\\n}\\n.md-editor-status[data-v-4b2b74bc] {\\r\\n margin-left: auto;\\r\\n padding-right: 16px;\\r\\n font-size: 12px;\\r\\n color: #909399;\\n}\\n.md-textarea[data-v-4b2b74bc] {\\r\\n width: 100%;\\r\\n height: 450px;\\r\\n border: 0;\\r\\n outline: none;\\r\\n resize: vertical;\\r\\n padding: 16px;\\r\\n font-size: 14px;\\r\\n line-height: 1.7;\\r\\n font-family: 'SF Mono', 'Monaco', 'Menlo', 'Consolas', 'Courier New', monospace;\\r\\n box-sizing: border-box;\\r\\n color: #1f2937;\\r\\n background: #fafbfc;\\n}\\n.md-textarea[data-v-4b2b74bc]:focus {\\r\\n background: #fff;\\n}\\n.md-preview[data-v-4b2b74bc] {\\r\\n height: 450px;\\r\\n overflow: auto;\\r\\n padding: 16px 20px;\\r\\n font-size: 14px;\\r\\n line-height: 1.8;\\r\\n color: #333;\\r\\n background: #fff;\\r\\n box-sizing: border-box;\\n}\\r\\n/* 滚动条 */\\n.md-preview[data-v-4b2b74bc]::-webkit-scrollbar,\\r\\n.md-textarea[data-v-4b2b74bc]::-webkit-scrollbar {\\r\\n width: 6px;\\r\\n height: 6px;\\n}\\n.md-preview[data-v-4b2b74bc]::-webkit-scrollbar-thumb,\\r\\n.md-textarea[data-v-4b2b74bc]::-webkit-scrollbar-thumb {\\r\\n background: #d0d5dd;\\r\\n border-radius: 3px;\\n}\\n.md-preview[data-v-4b2b74bc]::-webkit-scrollbar-thumb:hover,\\r\\n.md-textarea[data-v-4b2b74bc]::-webkit-scrollbar-thumb:hover {\\r\\n background: #b0b5bd;\\n}\\r\\n/* 标题 */\\n.md-preview h1[data-v-4b2b74bc] {\\r\\n font-size: 24px;\\r\\n margin: 20px 0 10px;\\r\\n border-bottom: 2px solid #e8eaed;\\r\\n padding-bottom: 8px;\\r\\n color: #1a1a2e;\\n}\\n.md-preview h2[data-v-4b2b74bc] {\\r\\n font-size: 20px;\\r\\n margin: 18px 0 8px;\\r\\n padding-bottom: 4px;\\r\\n border-bottom: 1px solid #f0f0f0;\\r\\n color: #1a1a2e;\\n}\\n.md-preview h3[data-v-4b2b74bc] {\\r\\n font-size: 17px;\\r\\n margin: 14px 0 6px;\\r\\n color: #2c3e50;\\n}\\n.md-preview h4[data-v-4b2b74bc] {\\r\\n font-size: 15px;\\r\\n margin: 12px 0 4px;\\r\\n color: #34495e;\\n}\\n.md-preview p[data-v-4b2b74bc] {\\r\\n margin: 10px 0;\\n}\\n.md-preview ul[data-v-4b2b74bc], .md-preview ol[data-v-4b2b74bc] {\\r\\n padding-left: 26px;\\r\\n margin: 10px 0;\\n}\\n.md-preview li[data-v-4b2b74bc] {\\r\\n margin: 4px 0;\\n}\\n.md-preview li > ul[data-v-4b2b74bc], .md-preview li > ol[data-v-4b2b74bc] {\\r\\n margin: 2px 0;\\n}\\r\\n/* 行内代码 */\\n.md-preview code[data-v-4b2b74bc]:not(pre code) {\\r\\n background: #f0f2f5;\\r\\n padding: 2px 8px;\\r\\n border-radius: 4px;\\r\\n font-size: 13px;\\r\\n color: #c7254e;\\r\\n border: 1px solid #e8eaed;\\n}\\r\\n/* 代码块 */\\n.md-preview pre[data-v-4b2b74bc] {\\r\\n background: #1e1e2e;\\r\\n color: #cdd6f4;\\r\\n padding: 18px 20px;\\r\\n border-radius: 8px;\\r\\n overflow-x: auto;\\r\\n font-size: 13px;\\r\\n line-height: 1.65;\\r\\n margin: 14px 0;\\r\\n border: 0;\\n}\\n.md-preview pre code[data-v-4b2b74bc] {\\r\\n background: none;\\r\\n padding: 0;\\r\\n color: inherit;\\r\\n font-size: inherit;\\r\\n border: 0;\\n}\\r\\n/* 引用 */\\n.md-preview blockquote[data-v-4b2b74bc] {\\r\\n border-left: 4px solid #409eff;\\r\\n padding: 10px 18px;\\r\\n margin: 14px 0;\\r\\n background: #f0f7ff;\\r\\n color: #555;\\r\\n border-radius: 0 4px 4px 0;\\n}\\n.md-preview blockquote p[data-v-4b2b74bc] {\\r\\n margin: 6px 0;\\n}\\r\\n/* 表格 */\\n.md-preview table[data-v-4b2b74bc] {\\r\\n border-collapse: collapse;\\r\\n width: 100%;\\r\\n margin: 14px 0;\\r\\n font-size: 13px;\\n}\\n.md-preview th[data-v-4b2b74bc], .md-preview td[data-v-4b2b74bc] {\\r\\n border: 1px solid #e0e2e6;\\r\\n padding: 10px 14px;\\r\\n text-align: left;\\n}\\n.md-preview th[data-v-4b2b74bc] {\\r\\n background: #f5f7fa;\\r\\n font-weight: 600;\\r\\n color: #1f2937;\\n}\\n.md-preview tr[data-v-4b2b74bc]:nth-child(even) {\\r\\n background: #fafbfc;\\n}\\n.md-preview tr[data-v-4b2b74bc]:hover {\\r\\n background: #f0f7ff;\\n}\\r\\n/* 图片 */\\n.md-preview img[data-v-4b2b74bc] {\\r\\n max-width: 100%;\\r\\n border-radius: 6px;\\r\\n margin: 8px 0;\\r\\n box-shadow: 0 1px 4px rgba(0,0,0,0.1);\\n}\\r\\n/* 链接 */\\n.md-preview a[data-v-4b2b74bc] {\\r\\n color: #409eff;\\r\\n text-decoration: none;\\n}\\n.md-preview a[data-v-4b2b74bc]:hover {\\r\\n text-decoration: underline;\\r\\n color: #2a7de1;\\n}\\r\\n/* 分割线 */\\n.md-preview hr[data-v-4b2b74bc] {\\r\\n border: none;\\r\\n border-top: 1px solid #e8eaed;\\r\\n margin: 20px 0;\\n}\\r\\n/* 任务列表 */\\n.md-preview input[type=\\\"checkbox\\\"][data-v-4b2b74bc] {\\r\\n margin-right: 6px;\\n}\\r\\n\\r\\n/* ==================== Excel 编辑器样式 ==================== */\\n.xlsx-editor-wrap[data-v-4b2b74bc] {\\r\\n border: 1px solid #e4e7ed;\\r\\n border-radius: 8px;\\r\\n overflow: hidden;\\r\\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);\\n}\\n.xlsx-toolbar[data-v-4b2b74bc] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n flex-wrap: wrap;\\r\\n gap: 8px;\\r\\n padding: 8px 12px;\\r\\n background: #fafbfc;\\r\\n border-bottom: 1px solid #e4e7ed;\\n}\\n.xlsx-sheet-tabs[data-v-4b2b74bc] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n gap: 2px;\\n}\\n.xlsx-sheet-tab[data-v-4b2b74bc] {\\r\\n padding: 4px 12px;\\r\\n font-size: 12px;\\r\\n cursor: pointer;\\r\\n border: 1px solid transparent;\\r\\n border-radius: 3px 3px 0 0;\\r\\n -webkit-user-select: none;\\r\\n -moz-user-select: none;\\r\\n -ms-user-select: none;\\r\\n user-select: none;\\r\\n color: #606266;\\r\\n background: #f0f2f5;\\n}\\n.xlsx-sheet-tab[data-v-4b2b74bc]:hover {\\r\\n color: #409eff;\\n}\\n.xlsx-sheet-tab.active[data-v-4b2b74bc] {\\r\\n color: #409eff;\\r\\n background: #fff;\\r\\n border-color: #dcdfe6;\\r\\n border-bottom-color: #fff;\\n}\\n.xlsx-actions[data-v-4b2b74bc] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n gap: 6px;\\r\\n margin-left: auto;\\r\\n flex-wrap: wrap;\\n}\\n.xlsx-info[data-v-4b2b74bc] {\\r\\n font-size: 12px;\\r\\n color: #909399;\\r\\n margin-left: 4px;\\r\\n white-space: nowrap;\\n}\\n.xlsx-table-wrap[data-v-4b2b74bc] {\\r\\n overflow-x: auto;\\r\\n padding: 4px;\\n}\\n.xlsx-table-wrap .el-table[data-v-4b2b74bc] {\\r\\n font-size: 12px;\\n}\\n.xlsx-table-wrap .el-input__inner[data-v-4b2b74bc] {\\r\\n border: 1px solid transparent;\\r\\n background: transparent;\\r\\n padding: 0 4px;\\r\\n height: 28px;\\r\\n line-height: 28px;\\r\\n border-radius: 2px;\\n}\\n.xlsx-table-wrap .el-input__inner[data-v-4b2b74bc]:hover {\\r\\n border-color: #c0c4cc;\\n}\\n.xlsx-table-wrap .el-input__inner[data-v-4b2b74bc]:focus {\\r\\n border-color: #409eff;\\r\\n background: #fff;\\n}\\n.xlsx-empty[data-v-4b2b74bc] {\\r\\n padding: 40px 0;\\n}\\n.xlsx-loading[data-v-4b2b74bc] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n justify-content: center;\\r\\n padding: 60px 0;\\r\\n color: #909399;\\r\\n font-size: 14px;\\r\\n gap: 8px;\\n}\\r\\n\\r\\n/* ==================== Word 编辑器样式 ==================== */\\n.word-editor-wrap[data-v-4b2b74bc] {\\r\\n border: 1px solid #e4e7ed;\\r\\n border-radius: 8px;\\r\\n overflow: hidden;\\r\\n background: #fff;\\r\\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);\\n}\\n.word-loading[data-v-4b2b74bc] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n justify-content: center;\\r\\n padding: 60px 0;\\r\\n color: #909399;\\r\\n font-size: 14px;\\r\\n gap: 8px;\\n}\\n.word-header[data-v-4b2b74bc] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n justify-content: space-between;\\r\\n padding: 8px 14px;\\r\\n background: #fafbfc;\\r\\n border-bottom: 1px solid #e4e7ed;\\r\\n font-size: 13px;\\n}\\n.word-filename[data-v-4b2b74bc] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n gap: 6px;\\r\\n color: #303133;\\r\\n font-weight: 500;\\r\\n max-width: 60%;\\r\\n overflow: hidden;\\r\\n text-overflow: ellipsis;\\r\\n white-space: nowrap;\\n}\\n.word-filename .el-icon-document[data-v-4b2b74bc] {\\r\\n color: #409eff;\\r\\n font-size: 15px;\\n}\\n.word-stats[data-v-4b2b74bc] {\\r\\n color: #909399;\\r\\n font-size: 12px;\\r\\n flex-shrink: 0;\\n}\\n.word-toolbar[data-v-4b2b74bc] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n flex-wrap: wrap;\\r\\n gap: 1px;\\r\\n padding: 5px 10px;\\r\\n background: #fafbfc;\\r\\n border-bottom: 1px solid #e8eaed;\\n}\\n.word-tb-btn[data-v-4b2b74bc] {\\r\\n display: inline-flex;\\r\\n align-items: center;\\r\\n justify-content: center;\\r\\n width: 30px;\\r\\n height: 28px;\\r\\n border: 1px solid transparent;\\r\\n background: transparent;\\r\\n border-radius: 4px;\\r\\n cursor: pointer;\\r\\n color: #5f6368;\\r\\n outline: none;\\r\\n transition: all 0.15s;\\n}\\n.word-tb-btn[data-v-4b2b74bc]:hover {\\r\\n background: #e8eaed;\\r\\n color: #202124;\\n}\\n.word-tb-btn[data-v-4b2b74bc]:active {\\r\\n background: #d2d5d9;\\n}\\n.word-tb-btn-heading[data-v-4b2b74bc] {\\r\\n font-size: 11px;\\r\\n font-weight: 600;\\r\\n font-family: 'Segoe UI', Arial, sans-serif;\\r\\n width: 32px;\\n}\\n.word-tb-sep[data-v-4b2b74bc] {\\r\\n display: inline-block;\\r\\n width: 1px;\\r\\n height: 18px;\\r\\n background: #dcdfe6;\\r\\n margin: 0 5px;\\r\\n flex-shrink: 0;\\n}\\n.word-editor-content[data-v-4b2b74bc] {\\r\\n min-height: 380px;\\r\\n max-height: 500px;\\r\\n overflow-y: auto;\\r\\n padding: 18px 22px;\\r\\n font-size: 14px;\\r\\n line-height: 1.8;\\r\\n color: #333;\\r\\n outline: none;\\r\\n -moz-user-select: text;\\r\\n -ms-user-select: text;\\r\\n user-select: text;\\r\\n -webkit-user-select: text;\\n}\\n.word-editor-content[data-v-4b2b74bc]:focus {\\r\\n background: #fff;\\n}\\n.word-editor-content h1[data-v-4b2b74bc] {\\r\\n font-size: 22px;\\r\\n font-weight: 700;\\r\\n margin: 20px 0 10px;\\r\\n padding-bottom: 6px;\\r\\n border-bottom: 2px solid #e8eaed;\\r\\n color: #1a1a2e;\\n}\\n.word-editor-content h2[data-v-4b2b74bc] {\\r\\n font-size: 18px;\\r\\n font-weight: 600;\\r\\n margin: 16px 0 8px;\\r\\n padding-bottom: 4px;\\r\\n border-bottom: 1px solid #e8eaed;\\r\\n color: #1a1a2e;\\n}\\n.word-editor-content h3[data-v-4b2b74bc] {\\r\\n font-size: 16px;\\r\\n font-weight: 600;\\r\\n margin: 14px 0 6px;\\r\\n color: #2c3e50;\\n}\\n.word-editor-content p[data-v-4b2b74bc] {\\r\\n margin: 8px 0;\\n}\\n.word-editor-content ul[data-v-4b2b74bc], .word-editor-content ol[data-v-4b2b74bc] {\\r\\n padding-left: 24px;\\r\\n margin: 8px 0;\\n}\\n.word-editor-content li[data-v-4b2b74bc] {\\r\\n margin: 3px 0;\\n}\\n.word-editor-content blockquote[data-v-4b2b74bc] {\\r\\n border-left: 3px solid #409eff;\\r\\n padding: 6px 14px;\\r\\n margin: 10px 0;\\r\\n background: #f0f7ff;\\r\\n color: #555;\\r\\n border-radius: 0 4px 4px 0;\\n}\\n.word-editor-content pre[data-v-4b2b74bc] {\\r\\n background: #f5f7fa;\\r\\n padding: 12px 16px;\\r\\n border-radius: 6px;\\r\\n overflow-x: auto;\\r\\n font-size: 13px;\\r\\n border: 1px solid #e8eaed;\\r\\n margin: 10px 0;\\r\\n font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;\\n}\\n.word-editor-content a[data-v-4b2b74bc] {\\r\\n color: #409eff;\\r\\n text-decoration: underline;\\n}\\n.word-editor-content img[data-v-4b2b74bc] {\\r\\n max-width: 100%;\\r\\n border-radius: 4px;\\r\\n margin: 6px 0;\\n}\\n.word-editor-content[data-v-4b2b74bc] ::-moz-selection {\\r\\n background: #c7dbff;\\n}\\n.word-editor-content[data-v-4b2b74bc] ::selection {\\r\\n background: #c7dbff;\\n}\\r\\n\\r\\n/* ==================== 弹窗整体美化 ==================== */\\n.document-form-dialog[data-v-4b2b74bc] {\\r\\n border-radius: 12px;\\r\\n box-shadow: 0 12px 32px rgba(0, 0, 0, 0.12);\\r\\n overflow: hidden;\\n}\\n.document-form-dialog[data-v-4b2b74bc] .el-dialog__header {\\r\\n padding: 16px 24px;\\r\\n border-bottom: 1px solid #ebeef5;\\r\\n position: relative;\\n}\\n.document-form-dialog[data-v-4b2b74bc] .el-dialog__title {\\r\\n display: inline-flex;\\r\\n align-items: center;\\r\\n gap: 8px;\\r\\n font-size: 16px;\\r\\n font-weight: 600;\\r\\n color: #303133;\\n}\\n.document-form-dialog[data-v-4b2b74bc] .el-dialog__title::before {\\r\\n content: '';\\r\\n width: 4px;\\r\\n height: 16px;\\r\\n border-radius: 2px;\\r\\n background: linear-gradient(180deg, #409eff, #79bbff);\\n}\\n.document-form-dialog[data-v-4b2b74bc] .el-dialog__headerbtn {\\r\\n top: 16px;\\r\\n right: 20px;\\n}\\n.document-form-dialog[data-v-4b2b74bc] .el-dialog__headerbtn .el-dialog__close {\\r\\n color: #909399;\\r\\n font-size: 18px;\\r\\n transition: color 0.2s;\\n}\\n.document-form-dialog[data-v-4b2b74bc] .el-dialog__headerbtn .el-dialog__close:hover {\\r\\n color: #303133;\\n}\\n.document-form-dialog[data-v-4b2b74bc] .el-dialog__body {\\r\\n padding: 20px 24px;\\r\\n max-height: 70vh;\\r\\n overflow-y: auto;\\n}\\n.document-form-dialog[data-v-4b2b74bc] .el-dialog__footer {\\r\\n padding: 12px 24px;\\r\\n border-top: 1px solid #ebeef5;\\r\\n background: #fafbfc;\\n}\\r\\n\\r\\n/* 滚动条 */\\n.document-form-dialog[data-v-4b2b74bc] .el-dialog__body::-webkit-scrollbar {\\r\\n width: 6px;\\n}\\n.document-form-dialog[data-v-4b2b74bc] .el-dialog__body::-webkit-scrollbar-thumb {\\r\\n background: #dcdfe6;\\r\\n border-radius: 3px;\\n}\\n.document-form-dialog[data-v-4b2b74bc] .el-dialog__body::-webkit-scrollbar-thumb:hover {\\r\\n background: #c0c4cc;\\n}\\r\\n\\r\\n/* 表单 */\\n.document-form-dialog[data-v-4b2b74bc] .el-form-item__label {\\r\\n color: #606266;\\r\\n font-weight: 500;\\n}\\r\\n\\r\\n/* 上传区 */\\n.document-form-dialog[data-v-4b2b74bc] .el-upload-dragger {\\r\\n border: 1px dashed #c0c4cc;\\r\\n border-radius: 10px;\\r\\n background: #fafbfc;\\r\\n transition: all 0.2s;\\n}\\n.document-form-dialog[data-v-4b2b74bc] .el-upload-dragger:hover {\\r\\n border-color: #409eff;\\r\\n background: #f0f7ff;\\n}\\n.document-form-dialog[data-v-4b2b74bc] .el-upload-dragger .el-icon-upload {\\r\\n color: #409eff;\\r\\n font-size: 56px;\\r\\n margin-bottom: 8px;\\n}\\r\\n\\r\\n/* 标题输入 */\\n.document-form-dialog[data-v-4b2b74bc] .el-input.doc-title-input .el-input__inner {\\r\\n border-radius: 6px;\\r\\n transition: all 0.2s;\\n}\\n.document-form-dialog[data-v-4b2b74bc] .el-input.doc-title-input .el-input__inner:hover {\\r\\n border-color: #c0c4cc;\\n}\\n.document-form-dialog[data-v-4b2b74bc] .el-input.doc-title-input .el-input__inner:focus {\\r\\n border-color: #409eff;\\n}\\r\\n\\r\\n/* 所属文件夹选择 */\\n.document-form-dialog[data-v-4b2b74bc] .el-input.folder-picker-input-el .el-input__inner {\\r\\n border-radius: 6px;\\r\\n background: #fafbfc;\\r\\n cursor: pointer;\\r\\n color: #606266;\\r\\n transition: all 0.2s;\\n}\\n.document-form-dialog[data-v-4b2b74bc] .el-input.folder-picker-input-el .el-input__inner:hover {\\r\\n border-color: #c0c4cc;\\r\\n background: #fff;\\n}\\n.document-form-dialog[data-v-4b2b74bc] .el-input.folder-picker-input-el .el-input__inner:focus {\\r\\n border-color: #409eff;\\r\\n background: #fff;\\n}\\r\\n\\r\\n/* 上传区(新增文档) */\\n.document-form-dialog[data-v-4b2b74bc] .el-upload-dragger {\\r\\n border: 1px dashed #c0c4cc;\\r\\n border-radius: 10px;\\r\\n background: #fafbfc;\\r\\n padding: 28px 16px;\\r\\n transition: all 0.2s;\\n}\\n.document-form-dialog[data-v-4b2b74bc] .el-upload-dragger:hover {\\r\\n border-color: #409eff;\\r\\n background: #f5faff;\\n}\\n.document-form-dialog[data-v-4b2b74bc] .el-upload-dragger .el-icon-upload {\\r\\n color: #409eff;\\r\\n font-size: 60px;\\r\\n margin-bottom: 10px;\\r\\n transition: transform 0.2s;\\n}\\n.document-form-dialog[data-v-4b2b74bc] .el-upload-dragger:hover .el-icon-upload {\\r\\n transform: translateY(-2px);\\n}\\n.document-form-dialog[data-v-4b2b74bc] .el-upload-dragger .el-upload__text {\\r\\n font-size: 14px;\\r\\n color: #606266;\\r\\n margin-bottom: 6px;\\n}\\n.document-form-dialog[data-v-4b2b74bc] .el-upload-dragger .el-upload__text em {\\r\\n font-style: normal;\\r\\n color: #409eff;\\r\\n font-weight: 600;\\r\\n padding: 0 8px;\\r\\n height: 26px;\\r\\n line-height: 26px;\\r\\n border: 1px solid #b3d8ff;\\r\\n border-radius: 13px;\\r\\n display: inline-block;\\r\\n vertical-align: middle;\\r\\n background: #ecf5ff;\\r\\n transition: all 0.2s;\\n}\\n.document-form-dialog[data-v-4b2b74bc] .el-upload-dragger:hover .el-upload__text em {\\r\\n background: #409eff;\\r\\n color: #fff;\\r\\n border-color: #409eff;\\n}\\n.document-form-dialog[data-v-4b2b74bc] .el-upload-dragger .el-upload__tip {\\r\\n margin-top: 12px;\\r\\n font-size: 12px;\\r\\n color: #909399;\\n}\\r\\n\\r\\n/* 上传文件列表内简化看板 */\\n.document-form-dialog[data-v-4b2b74bc] .el-upload-list {\\r\\n margin-top: 12px;\\n}\\n.document-form-dialog[data-v-4b2b74bc] .el-upload-list__item {\\r\\n border-radius: 6px;\\r\\n transition: background 0.2s;\\n}\\n.document-form-dialog[data-v-4b2b74bc] .el-upload-list__item:hover {\\r\\n background: #f5f7fa;\\n}\\n.document-form-dialog[data-v-4b2b74bc] .el-upload-list__item-name {\\r\\n color: #303133;\\n}\\r\\n\", \"\"]);\n// Exports\nmodule.exports = exports;\n\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/DocumentForm.vue?./node_modules/css-loader/dist/cjs.js??ref--6-oneOf-1-1!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src??ref--6-oneOf-1-2!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
1701
1701
|
|
|
1702
1702
|
/***/ }),
|
|
1703
1703
|
|
|
@@ -1708,7 +1708,18 @@ eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../.
|
|
|
1708
1708
|
/*! no static exports found */
|
|
1709
1709
|
/***/ (function(module, exports, __webpack_require__) {
|
|
1710
1710
|
|
|
1711
|
-
eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\nexports = ___CSS_LOADER_API_IMPORT___(false);\n// Module\nexports.push([module.i, \"\\n.kb-document-list[data-v-0e636c7c] {\\r\\n height: 100%;\\r\\n display: flex;\\r\\n flex-direction: column;\\r\\n padding: 24px;\\r\\n box-sizing: border-box;\\r\\n background: #fafbfc;\\n}\\n.kb-document-list[data-v-0e636c7c],\\r\\n.kb-document-list *[data-v-0e636c7c],\\r\\n.kb-document-list *[data-v-0e636c7c]::before,\\r\\n.kb-document-list *[data-v-0e636c7c]::after {\\r\\n font-size: 14px !important;\\n}\\r\\n\\r\\n/* ===== 工具栏 ===== */\\n.doc-toolbar[data-v-0e636c7c] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n justify-content: space-between;\\r\\n margin-bottom: 20px;\\r\\n flex-shrink: 0;\\n}\\n.doc-toolbar-left[data-v-0e636c7c] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n gap: 12px;\\n}\\n.doc-toolbar-right[data-v-0e636c7c] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n gap: 12px;\\n}\\r\\n\\r\\n/* 搜索输入框 */\\n.doc-toolbar[data-v-0e636c7c] .search-input {\\r\\n width: 280px;\\n}\\n.doc-toolbar[data-v-0e636c7c] .search-input .el-input__inner {\\r\\n background: #fff;\\r\\n border: 1px solid #dcdfe6;\\r\\n border-radius: 4px;\\r\\n transition: all 0.2s;\\n}\\n.doc-toolbar[data-v-0e636c7c] .search-input .el-input__inner:focus {\\r\\n border-color: #409eff;\\r\\n box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1);\\n}\\r\\n\\r\\n/* 按钮 */\\n.doc-toolbar[data-v-0e636c7c] .el-button--small {\\r\\n padding: 9px 16px;\\r\\n border-radius: 4px;\\r\\n font-weight: 500;\\r\\n transition: all 0.2s;\\n}\\n.doc-toolbar[data-v-0e636c7c] .el-button--primary {\\r\\n background: #409eff;\\r\\n border-color: #409eff;\\n}\\n.doc-toolbar[data-v-0e636c7c] .el-button--primary:hover {\\r\\n background: #66b1ff;\\r\\n border-color: #66b1ff;\\n}\\n.doc-toolbar[data-v-0e636c7c] .el-button--success {\\r\\n background: #67c23a;\\r\\n border-color: #67c23a;\\n}\\n.doc-toolbar[data-v-0e636c7c] .el-button--success:hover {\\r\\n background: #85ce61;\\r\\n border-color: #85ce61;\\n}\\n.doc-toolbar[data-v-0e636c7c] .el-button--default {\\r\\n background: #fff;\\r\\n border-color: #dcdfe6;\\r\\n color: #606266;\\n}\\n.doc-toolbar[data-v-0e636c7c] .el-button--default:hover {\\r\\n color: #409eff;\\r\\n border-color: #c6e2ff;\\r\\n background: #ecf5ff;\\n}\\r\\n\\r\\n/* ===== 表格容器 ===== */\\n.doc-table-wrap[data-v-0e636c7c] {\\r\\n flex: 1;\\r\\n overflow: hidden;\\r\\n min-height: 0;\\r\\n background: #fff;\\r\\n border-radius: 4px;\\r\\n border: 1px solid #ebeef5;\\n}\\r\\n\\r\\n/* 表格 */\\n.doc-table-wrap[data-v-0e636c7c] .el-table {\\r\\n font-size: 14px;\\n}\\n.doc-table-wrap[data-v-0e636c7c] .el-table th.el-table__cell {\\r\\n background: #f5f7fa !important;\\r\\n color: #303133 !important;\\r\\n font-weight: 600 !important;\\r\\n border-bottom: 1px solid #ebeef5;\\n}\\n.doc-table-wrap[data-v-0e636c7c] .el-table__row:hover {\\r\\n background: #f5f7fa !important;\\n}\\n.doc-table-wrap[data-v-0e636c7c] .el-table td.el-table__cell {\\r\\n border-bottom: 1px solid #ebeef5;\\r\\n color: #606266;\\n}\\n.doc-table-wrap[data-v-0e636c7c] .el-table--striped .el-table__body tr.el-table__row--striped td {\\r\\n background: #fafafa;\\n}\\r\\n\\r\\n/* 文件标题单元格 */\\n.file-title-cell[data-v-0e636c7c] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n gap: 8px;\\n}\\n.file-icon[data-v-0e636c7c] {\\r\\n flex-shrink: 0;\\r\\n font-size: 18px;\\r\\n color: #909399;\\r\\n transition: color 0.2s;\\n}\\n.doc-table-wrap[data-v-0e636c7c] .el-table__row:hover .file-icon {\\r\\n color: #409eff;\\n}\\r\\n\\r\\n/* 文件夹名称 */\\n.folder-name[data-v-0e636c7c] {\\r\\n color: #909399;\\r\\n font-size: 13px;\\n}\\r\\n\\r\\n/* Tag */\\n.doc-table-wrap[data-v-0e636c7c] .el-tag {\\r\\n border-radius: 4px;\\r\\n font-weight: 500;\\n}\\r\\n\\r\\n/* 操作按钮 */\\n.doc-table-wrap[data-v-0e636c7c] .el-button--mini.el-button--text {\\r\\n padding: 6px 10px;\\r\\n font-weight: 500;\\n}\\n.doc-table-wrap[data-v-0e636c7c] .el-button--mini.el-button--text:hover {\\r\\n background: #ecf5ff;\\r\\n color: #409eff;\\n}\\n.doc-table-wrap[data-v-0e636c7c] .el-button--mini.el-button--text[style*=\\\"color: #f56c6c\\\"]:hover {\\r\\n background: #fef0f0;\\r\\n color: #f56c6c;\\n}\\r\\n\\r\\n/* ===== 分页 ===== */\\n.doc-pagination[data-v-0e636c7c] {\\r\\n display: flex;\\r\\n justify-content: flex-end;\\r\\n padding-top: 16px;\\r\\n flex-shrink: 0;\\n}\\n.doc-pagination[data-v-0e636c7c] .el-pagination.is-background .el-pager li {\\r\\n background: #f4f4f5;\\r\\n border-radius: 4px;\\r\\n margin: 0 3px;\\n}\\n.doc-pagination[data-v-0e636c7c] .el-pagination.is-background .el-pager li:hover {\\r\\n color: #409eff;\\n}\\n.doc-pagination[data-v-0e636c7c] .el-pagination.is-background .el-pager li.active {\\r\\n background: #409eff;\\r\\n color: #fff;\\n}\\n.preview-body[data-v-0e636c7c] {\\r\\n height: calc(90vh - 120px);\\n}\\n.preview-frame[data-v-0e636c7c] {\\r\\n width: 100%;\\r\\n height: 100%;\\r\\n border: 0;\\r\\n border-radius: 4px;\\r\\n background: #fff;\\n}\\n.preview-center[data-v-0e636c7c] {\\r\\n height: 100%;\\r\\n overflow: auto;\\r\\n text-align: center;\\r\\n background: #f5f7fa;\\r\\n border-radius: 4px;\\r\\n padding: 10px;\\n}\\n.preview-text[data-v-0e636c7c] {\\r\\n height: 100%;\\r\\n overflow: auto;\\r\\n background: #f5f7fa;\\r\\n border-radius: 4px;\\r\\n padding: 16px;\\n}\\n.preview-text pre[data-v-0e636c7c] {\\r\\n white-space: pre-wrap;\\r\\n word-break: break-all;\\r\\n font-size: 13px;\\r\\n line-height: 1.8;\\r\\n margin: 0;\\n}\\n.preview-markdown[data-v-0e636c7c] {\\r\\n font-size: 14px;\\r\\n line-height: 1.8;\\r\\n color: #333;\\n}\\n.preview-markdown h1[data-v-0e636c7c] { font-size: 22px; margin: 16px 0 8px; border-bottom: 1px solid #eee; padding-bottom: 6px;\\n}\\n.preview-markdown h2[data-v-0e636c7c] { font-size: 19px; margin: 14px 0 6px;\\n}\\n.preview-markdown h3[data-v-0e636c7c] { font-size: 16px; margin: 12px 0 4px;\\n}\\n.preview-markdown p[data-v-0e636c7c] { margin: 8px 0;\\n}\\n.preview-markdown ul[data-v-0e636c7c], .preview-markdown ol[data-v-0e636c7c] { padding-left: 24px; margin: 8px 0;\\n}\\n.preview-markdown code[data-v-0e636c7c] {\\r\\n background: #f0f0f0;\\r\\n padding: 2px 6px;\\r\\n border-radius: 3px;\\r\\n font-size: 13px;\\r\\n color: #d63384;\\n}\\n.preview-markdown pre[data-v-0e636c7c] {\\r\\n background: #282c34;\\r\\n color: #abb2bf;\\r\\n padding: 16px;\\r\\n border-radius: 6px;\\r\\n overflow-x: auto;\\r\\n font-size: 13px;\\r\\n line-height: 1.6;\\r\\n margin: 12px 0;\\n}\\n.preview-markdown pre code[data-v-0e636c7c] {\\r\\n background: none;\\r\\n padding: 0;\\r\\n color: inherit;\\r\\n font-size: inherit;\\n}\\n.preview-markdown blockquote[data-v-0e636c7c] {\\r\\n border-left: 4px solid #409eff;\\r\\n padding: 8px 16px;\\r\\n margin: 12px 0;\\r\\n background: #f5f7fa;\\r\\n color: #666;\\n}\\n.preview-markdown table[data-v-0e636c7c] {\\r\\n border-collapse: collapse;\\r\\n width: 100%;\\r\\n margin: 12px 0;\\n}\\n.preview-markdown th[data-v-0e636c7c], .preview-markdown td[data-v-0e636c7c] {\\r\\n border: 1px solid #ddd;\\r\\n padding: 8px 12px;\\r\\n text-align: left;\\n}\\n.preview-markdown th[data-v-0e636c7c] {\\r\\n background: #f5f7fa;\\r\\n font-weight: bold;\\n}\\n.preview-markdown img[data-v-0e636c7c] {\\r\\n max-width: 100%;\\r\\n border-radius: 4px;\\n}\\n.preview-markdown a[data-v-0e636c7c] {\\r\\n color: #409eff;\\n}\\r\\n\\r\\n/* ==================== Excel 预览样式 ==================== */\\n.preview-xlsx-wrap[data-v-0e636c7c] {\\r\\n height: 100%;\\r\\n display: flex;\\r\\n flex-direction: column;\\r\\n background: #fff;\\r\\n border-radius: 4px;\\r\\n border: 1px solid #e8eaed;\\n}\\n.preview-loading[data-v-0e636c7c] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n justify-content: center;\\r\\n padding: 60px 0;\\r\\n color: #909399;\\r\\n font-size: 14px;\\r\\n gap: 8px;\\n}\\n.preview-xlsx-toolbar[data-v-0e636c7c] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n gap: 2px;\\r\\n padding: 8px 12px;\\r\\n background: #f8f9fb;\\r\\n border-bottom: 1px solid #e8eaed;\\r\\n flex-shrink: 0;\\n}\\n.preview-xlsx-tab[data-v-0e636c7c] {\\r\\n padding: 4px 14px;\\r\\n font-size: 12px;\\r\\n cursor: pointer;\\r\\n border: 1px solid transparent;\\r\\n border-radius: 3px 3px 0 0;\\r\\n -webkit-user-select: none;\\r\\n -moz-user-select: none;\\r\\n -ms-user-select: none;\\r\\n user-select: none;\\r\\n color: #606266;\\r\\n background: #f0f2f5;\\n}\\n.preview-xlsx-tab[data-v-0e636c7c]:hover {\\r\\n color: #409eff;\\n}\\n.preview-xlsx-tab.active[data-v-0e636c7c] {\\r\\n color: #409eff;\\r\\n background: #fff;\\r\\n border-color: #dcdfe6;\\r\\n border-bottom-color: #fff;\\n}\\n.preview-xlsx-table-wrap[data-v-0e636c7c] {\\r\\n flex: 1;\\r\\n overflow: auto;\\r\\n padding: 8px;\\n}\\n.preview-xlsx-notice[data-v-0e636c7c] {\\r\\n padding: 6px 12px;\\r\\n margin-bottom: 8px;\\r\\n font-size: 12px;\\r\\n color: #e6a23c;\\r\\n background: #fdf6ec;\\r\\n border: 1px solid #faecd8;\\r\\n border-radius: 4px;\\n}\\n.preview-xlsx-table[data-v-0e636c7c] {\\r\\n border-collapse: collapse;\\r\\n width: 100%;\\r\\n font-size: 13px;\\n}\\n.preview-xlsx-table th[data-v-0e636c7c],\\r\\n.preview-xlsx-table td[data-v-0e636c7c] {\\r\\n border: 1px solid #dcdfe6;\\r\\n padding: 6px 10px;\\r\\n text-align: left;\\r\\n white-space: nowrap;\\r\\n min-width: 80px;\\n}\\n.preview-xlsx-table th[data-v-0e636c7c] {\\r\\n background: #f5f7fa;\\r\\n font-weight: 600;\\r\\n color: #1f2937;\\r\\n position: sticky;\\r\\n top: 0;\\n}\\n.preview-xlsx-table tr[data-v-0e636c7c]:nth-child(even) {\\r\\n background: #fafbfc;\\n}\\n.preview-xlsx-table tr[data-v-0e636c7c]:hover {\\r\\n background: #f0f7ff;\\n}\\r\\n\\r\\n/* ==================== Word 预览样式 ==================== */\\n.preview-docx-wrap[data-v-0e636c7c] {\\r\\n height: 100%;\\r\\n overflow: auto;\\r\\n background: #fff;\\r\\n border-radius: 4px;\\r\\n border: 1px solid #e8eaed;\\n}\\n.preview-docx-content[data-v-0e636c7c] {\\r\\n padding: 24px 32px;\\r\\n font-size: 14px;\\r\\n line-height: 1.8;\\r\\n color: #333;\\r\\n min-height: 300px;\\r\\n -webkit-user-select: text;\\r\\n -moz-user-select: text;\\r\\n -ms-user-select: text;\\r\\n user-select: text;\\n}\\n.preview-docx-content h1[data-v-0e636c7c] {\\r\\n font-size: 22px;\\r\\n font-weight: 700;\\r\\n margin: 20px 0 10px;\\r\\n padding-bottom: 6px;\\r\\n border-bottom: 2px solid #e8eaed;\\r\\n color: #1a1a2e;\\n}\\n.preview-docx-content h2[data-v-0e636c7c] {\\r\\n font-size: 18px;\\r\\n font-weight: 600;\\r\\n margin: 16px 0 8px;\\r\\n padding-bottom: 4px;\\r\\n border-bottom: 1px solid #e8eaed;\\r\\n color: #1a1a2e;\\n}\\n.preview-docx-content h3[data-v-0e636c7c] {\\r\\n font-size: 16px;\\r\\n font-weight: 600;\\r\\n margin: 14px 0 6px;\\r\\n color: #2c3e50;\\n}\\n.preview-docx-content p[data-v-0e636c7c] {\\r\\n margin: 8px 0;\\n}\\n.preview-docx-content ul[data-v-0e636c7c],\\r\\n.preview-docx-content ol[data-v-0e636c7c] {\\r\\n padding-left: 24px;\\r\\n margin: 8px 0;\\n}\\n.preview-docx-content li[data-v-0e636c7c] {\\r\\n margin: 3px 0;\\n}\\n.preview-docx-content blockquote[data-v-0e636c7c] {\\r\\n border-left: 3px solid #409eff;\\r\\n padding: 6px 14px;\\r\\n margin: 10px 0;\\r\\n background: #f0f7ff;\\r\\n color: #555;\\r\\n border-radius: 0 4px 4px 0;\\n}\\n.preview-docx-content pre[data-v-0e636c7c] {\\r\\n background: #f5f7fa;\\r\\n padding: 12px 16px;\\r\\n border-radius: 6px;\\r\\n overflow-x: auto;\\r\\n font-size: 13px;\\r\\n border: 1px solid #e8eaed;\\r\\n margin: 10px 0;\\n}\\n.preview-docx-content a[data-v-0e636c7c] {\\r\\n color: #409eff;\\r\\n text-decoration: underline;\\n}\\n.preview-docx-content img[data-v-0e636c7c] {\\r\\n max-width: 100%;\\r\\n border-radius: 4px;\\r\\n margin: 6px 0;\\n}\\n.preview-docx-content table[data-v-0e636c7c] {\\r\\n border-collapse: collapse;\\r\\n width: 100%;\\r\\n margin: 12px 0;\\r\\n font-size: 13px;\\n}\\n.preview-docx-content th[data-v-0e636c7c],\\r\\n.preview-docx-content td[data-v-0e636c7c] {\\r\\n border: 1px solid #dcdfe6;\\r\\n padding: 8px 12px;\\r\\n text-align: left;\\n}\\n.preview-docx-content th[data-v-0e636c7c] {\\r\\n background: #f5f7fa;\\r\\n font-weight: 600;\\n}\\r\\n\", \"\"]);\n// Exports\nmodule.exports = exports;\n\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/DocumentList.vue?./node_modules/css-loader/dist/cjs.js??ref--6-oneOf-1-1!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src??ref--6-oneOf-1-2!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
1711
|
+
eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\nexports = ___CSS_LOADER_API_IMPORT___(false);\n// Module\nexports.push([module.i, \"\\n.kb-document-list[data-v-0e636c7c] {\\r\\n height: 100%;\\r\\n display: flex;\\r\\n flex-direction: column;\\r\\n padding: 24px;\\r\\n box-sizing: border-box;\\r\\n background: #fafbfc;\\n}\\n.kb-document-list[data-v-0e636c7c],\\r\\n.kb-document-list *[data-v-0e636c7c],\\r\\n.kb-document-list *[data-v-0e636c7c]::before,\\r\\n.kb-document-list *[data-v-0e636c7c]::after {\\r\\n font-size: 14px !important;\\n}\\r\\n\\r\\n/* ===== 工具栏 ===== */\\n.doc-toolbar[data-v-0e636c7c] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n justify-content: space-between;\\r\\n margin-bottom: 20px;\\r\\n flex-shrink: 0;\\n}\\n.doc-toolbar-left[data-v-0e636c7c] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n gap: 12px;\\n}\\n.doc-toolbar-right[data-v-0e636c7c] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n gap: 12px;\\n}\\r\\n\\r\\n/* 搜索输入框 */\\n.doc-toolbar[data-v-0e636c7c] .search-input {\\r\\n width: 280px;\\n}\\n.doc-toolbar[data-v-0e636c7c] .search-input .el-input__inner {\\r\\n background: #fff;\\r\\n border: 1px solid #dcdfe6;\\r\\n border-radius: 4px;\\r\\n transition: all 0.2s;\\n}\\n.doc-toolbar[data-v-0e636c7c] .search-input .el-input__inner:focus {\\r\\n border-color: #409eff;\\r\\n box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1);\\n}\\r\\n\\r\\n/* 按钮 */\\n.doc-toolbar[data-v-0e636c7c] .el-button--small {\\r\\n padding: 9px 16px;\\r\\n border-radius: 4px;\\r\\n font-weight: 500;\\r\\n transition: all 0.2s;\\n}\\n.doc-toolbar[data-v-0e636c7c] .el-button--primary {\\r\\n background: #409eff;\\r\\n border-color: #409eff;\\n}\\n.doc-toolbar[data-v-0e636c7c] .el-button--primary:hover {\\r\\n background: #66b1ff;\\r\\n border-color: #66b1ff;\\n}\\n.doc-toolbar[data-v-0e636c7c] .el-button--success {\\r\\n background: #67c23a;\\r\\n border-color: #67c23a;\\n}\\n.doc-toolbar[data-v-0e636c7c] .el-button--success:hover {\\r\\n background: #85ce61;\\r\\n border-color: #85ce61;\\n}\\n.doc-toolbar[data-v-0e636c7c] .el-button--default {\\r\\n background: #fff;\\r\\n border-color: #dcdfe6;\\r\\n color: #606266;\\n}\\n.doc-toolbar[data-v-0e636c7c] .el-button--default:hover {\\r\\n color: #409eff;\\r\\n border-color: #c6e2ff;\\r\\n background: #ecf5ff;\\n}\\r\\n\\r\\n/* ===== 表格容器 ===== */\\n.doc-table-wrap[data-v-0e636c7c] {\\r\\n flex: 1;\\r\\n overflow: hidden;\\r\\n min-height: 0;\\r\\n background: #fff;\\r\\n border-radius: 4px;\\r\\n border: 1px solid #ebeef5;\\n}\\r\\n\\r\\n/* 表格 */\\n.doc-table-wrap[data-v-0e636c7c] .el-table {\\r\\n font-size: 14px;\\n}\\n.doc-table-wrap[data-v-0e636c7c] .el-table th.el-table__cell {\\r\\n background: #f5f7fa !important;\\r\\n color: #303133 !important;\\r\\n font-weight: 600 !important;\\r\\n border-bottom: 1px solid #ebeef5;\\n}\\n.doc-table-wrap[data-v-0e636c7c] .el-table__row:hover {\\r\\n background: #f5f7fa !important;\\n}\\n.doc-table-wrap[data-v-0e636c7c] .el-table td.el-table__cell {\\r\\n border-bottom: 1px solid #ebeef5;\\r\\n color: #606266;\\n}\\n.doc-table-wrap[data-v-0e636c7c] .el-table--striped .el-table__body tr.el-table__row--striped td {\\r\\n background: #fafafa;\\n}\\r\\n\\r\\n/* 文件标题单元格 */\\n.file-title-cell[data-v-0e636c7c] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n gap: 8px;\\n}\\n.file-icon-img[data-v-0e636c7c] {\\r\\n flex-shrink: 0;\\r\\n width: 20px;\\r\\n height: 20px;\\r\\n -o-object-fit: contain;\\r\\n object-fit: contain;\\r\\n vertical-align: middle;\\n}\\n.doc-table-wrap[data-v-0e636c7c] .el-table__row:hover .file-icon-img {\\r\\n transform: scale(1.08);\\n}\\r\\n\\r\\n/* 文件夹名称 */\\n.folder-name[data-v-0e636c7c] {\\r\\n color: #909399;\\r\\n font-size: 13px;\\n}\\r\\n\\r\\n/* Tag */\\n.doc-table-wrap[data-v-0e636c7c] .el-tag {\\r\\n border-radius: 4px;\\r\\n font-weight: 500;\\n}\\r\\n\\r\\n/* 操作按钮 */\\n.doc-table-wrap[data-v-0e636c7c] .el-button--mini.el-button--text {\\r\\n padding: 6px 10px;\\r\\n font-weight: 500;\\n}\\n.doc-table-wrap[data-v-0e636c7c] .el-button--mini.el-button--text:hover {\\r\\n background: #ecf5ff;\\r\\n color: #409eff;\\n}\\n.doc-table-wrap[data-v-0e636c7c] .el-button--mini.el-button--text[style*=\\\"color: #f56c6c\\\"]:hover {\\r\\n background: #fef0f0;\\r\\n color: #f56c6c;\\n}\\r\\n\\r\\n/* ===== 分页 ===== */\\n.doc-pagination[data-v-0e636c7c] {\\r\\n display: flex;\\r\\n justify-content: flex-end;\\r\\n padding-top: 16px;\\r\\n flex-shrink: 0;\\n}\\n.doc-pagination[data-v-0e636c7c] .el-pagination.is-background .el-pager li {\\r\\n background: #f4f4f5;\\r\\n border-radius: 4px;\\r\\n margin: 0 3px;\\n}\\n.doc-pagination[data-v-0e636c7c] .el-pagination.is-background .el-pager li:hover {\\r\\n color: #409eff;\\n}\\n.doc-pagination[data-v-0e636c7c] .el-pagination.is-background .el-pager li.active {\\r\\n background: #409eff;\\r\\n color: #fff;\\n}\\r\\n/* ==================== 预览弹框头部(WPS 风格) ==================== */\\n.wps-dialog-header[data-v-0e636c7c] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n justify-content: space-between;\\r\\n height: 52px;\\r\\n padding: 0 54px 0 20px;\\r\\n background: #f5f7fa;\\r\\n box-sizing: border-box;\\n}\\n.wps-dialog-left[data-v-0e636c7c] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n gap: 8px;\\r\\n min-width: 0;\\n}\\n.wps-dialog-icon-img[data-v-0e636c7c] {\\r\\n width: 22px;\\r\\n height: 22px;\\r\\n -o-object-fit: contain;\\r\\n object-fit: contain;\\r\\n flex-shrink: 0;\\n}\\n.wps-dialog-name[data-v-0e636c7c] {\\r\\n font-size: 15px;\\r\\n font-weight: 600;\\r\\n color: #1f2329;\\r\\n white-space: nowrap;\\r\\n overflow: hidden;\\r\\n text-overflow: ellipsis;\\r\\n max-width: 520px;\\n}\\n.wps-dialog-badge[data-v-0e636c7c] {\\r\\n display: inline-flex;\\r\\n align-items: center;\\r\\n gap: 4px;\\r\\n margin-left: 8px;\\r\\n padding: 2px 10px;\\r\\n font-size: 12px;\\r\\n color: #909399;\\r\\n background: #fff;\\r\\n border: 1px solid #dcdfe6;\\r\\n border-radius: 12px;\\r\\n flex-shrink: 0;\\n}\\n.preview-body[data-v-0e636c7c] {\\r\\n height: calc(90vh - 120px);\\r\\n display: flex;\\r\\n flex-direction: column;\\r\\n background: #fff;\\n}\\r\\n/* ==================== WPS 白色纸张页面观感 ==================== */\\n.preview-page-canvas[data-v-0e636c7c] {\\r\\n flex: 1;\\r\\n overflow: auto;\\r\\n background: #fafbfc; /* 页面背景色 */\\r\\n padding: 32px 24px;\\r\\n margin-bottom: 10px; /* 外边框底部留 10px 边界 */\\r\\n display: flex;\\r\\n justify-content: center;\\r\\n align-items: flex-start;\\n}\\r\\n/* 白色纸张页面 */\\n.preview-page[data-v-0e636c7c] {\\r\\n width: 100%;\\r\\n max-width: 820px; /* 约 A4 宽度 */\\r\\n min-height: 640px;\\r\\n box-sizing: border-box;\\r\\n background: #fff;\\r\\n border-radius: 2px;\\r\\n box-shadow: 0 3px 12px rgba(0, 0, 0, 0.10), 0 1px 2px rgba(0, 0, 0, 0.06);\\r\\n padding: 40px 56px;\\r\\n position: relative;\\n}\\n.preview-page.paper-pdf[data-v-0e636c7c] {\\r\\n height: 100%;\\r\\n min-height: 0;\\r\\n padding: 0;\\r\\n overflow: hidden;\\n}\\n.preview-frame[data-v-0e636c7c] {\\r\\n width: 100%;\\r\\n height: 100%;\\r\\n border: 0;\\r\\n background: #fff;\\n}\\n.preview-center[data-v-0e636c7c] {\\r\\n height: 100%;\\r\\n overflow: auto;\\r\\n text-align: center;\\r\\n background: #e8eaed;\\r\\n border-radius: 4px;\\r\\n padding: 16px;\\n}\\n.preview-center img[data-v-0e636c7c] {\\r\\n box-shadow: 0 3px 12px rgba(0, 0, 0, 0.10), 0 1px 2px rgba(0, 0, 0, 0.06);\\r\\n border-radius: 2px;\\n}\\n.preview-text[data-v-0e636c7c] {\\r\\n height: 100%;\\r\\n overflow: auto;\\r\\n background: #f5f7fa;\\r\\n border-radius: 4px;\\r\\n padding: 16px;\\n}\\n.preview-text pre[data-v-0e636c7c] {\\r\\n white-space: pre-wrap;\\r\\n word-break: break-all;\\r\\n font-size: 13px;\\r\\n line-height: 1.8;\\r\\n margin: 0;\\n}\\n.preview-markdown[data-v-0e636c7c] {\\r\\n font-size: 14px;\\r\\n line-height: 1.8;\\r\\n color: #333;\\n}\\n.preview-markdown h1[data-v-0e636c7c] { font-size: 22px; margin: 16px 0 8px; border-bottom: 1px solid #eee; padding-bottom: 6px;\\n}\\n.preview-markdown h2[data-v-0e636c7c] { font-size: 19px; margin: 14px 0 6px;\\n}\\n.preview-markdown h3[data-v-0e636c7c] { font-size: 16px; margin: 12px 0 4px;\\n}\\n.preview-markdown p[data-v-0e636c7c] { margin: 8px 0;\\n}\\n.preview-markdown ul[data-v-0e636c7c], .preview-markdown ol[data-v-0e636c7c] { padding-left: 24px; margin: 8px 0;\\n}\\n.preview-markdown code[data-v-0e636c7c] {\\r\\n background: #f0f0f0;\\r\\n padding: 2px 6px;\\r\\n border-radius: 3px;\\r\\n font-size: 13px;\\r\\n color: #d63384;\\n}\\n.preview-markdown pre[data-v-0e636c7c] {\\r\\n background: #282c34;\\r\\n color: #abb2bf;\\r\\n padding: 16px;\\r\\n border-radius: 6px;\\r\\n overflow-x: auto;\\r\\n font-size: 13px;\\r\\n line-height: 1.6;\\r\\n margin: 12px 0;\\n}\\n.preview-markdown pre code[data-v-0e636c7c] {\\r\\n background: none;\\r\\n padding: 0;\\r\\n color: inherit;\\r\\n font-size: inherit;\\n}\\n.preview-markdown blockquote[data-v-0e636c7c] {\\r\\n border-left: 4px solid #409eff;\\r\\n padding: 8px 16px;\\r\\n margin: 12px 0;\\r\\n background: #f5f7fa;\\r\\n color: #666;\\n}\\n.preview-markdown table[data-v-0e636c7c] {\\r\\n border-collapse: collapse;\\r\\n width: 100%;\\r\\n margin: 12px 0;\\n}\\n.preview-markdown th[data-v-0e636c7c], .preview-markdown td[data-v-0e636c7c] {\\r\\n border: 1px solid #ddd;\\r\\n padding: 8px 12px;\\r\\n text-align: left;\\n}\\n.preview-markdown th[data-v-0e636c7c] {\\r\\n background: #f5f7fa;\\r\\n font-weight: bold;\\n}\\n.preview-markdown img[data-v-0e636c7c] {\\r\\n max-width: 100%;\\r\\n border-radius: 4px;\\n}\\n.preview-markdown a[data-v-0e636c7c] {\\r\\n color: #409eff;\\n}\\r\\n\\r\\n/* ==================== Excel 预览样式 ==================== */\\n.preview-xlsx-wrap[data-v-0e636c7c] {\\r\\n height: 100%;\\r\\n display: flex;\\r\\n flex-direction: column;\\r\\n background: #fff;\\r\\n border-radius: 4px;\\r\\n border: 1px solid #e8eaed;\\n}\\n.preview-loading[data-v-0e636c7c] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n justify-content: center;\\r\\n padding: 60px 0;\\r\\n color: #909399;\\r\\n font-size: 14px;\\r\\n gap: 8px;\\n}\\n.preview-xlsx-toolbar[data-v-0e636c7c] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n gap: 2px;\\r\\n padding: 8px 12px;\\r\\n background: #f8f9fb;\\r\\n border-bottom: 1px solid #e8eaed;\\r\\n flex-shrink: 0;\\n}\\n.preview-xlsx-tab[data-v-0e636c7c] {\\r\\n padding: 4px 14px;\\r\\n font-size: 12px;\\r\\n cursor: pointer;\\r\\n border: 1px solid transparent;\\r\\n border-radius: 3px 3px 0 0;\\r\\n -webkit-user-select: none;\\r\\n -moz-user-select: none;\\r\\n -ms-user-select: none;\\r\\n user-select: none;\\r\\n color: #606266;\\r\\n background: #f0f2f5;\\n}\\n.preview-xlsx-tab[data-v-0e636c7c]:hover {\\r\\n color: #409eff;\\n}\\n.preview-xlsx-tab.active[data-v-0e636c7c] {\\r\\n color: #409eff;\\r\\n background: #fff;\\r\\n border-color: #dcdfe6;\\r\\n border-bottom-color: #fff;\\n}\\n.preview-xlsx-table-wrap[data-v-0e636c7c] {\\r\\n max-height: 680px;\\r\\n overflow: auto;\\r\\n padding: 8px;\\r\\n border: 1px solid #e8eaed;\\r\\n border-radius: 4px;\\n}\\n.preview-xlsx-notice[data-v-0e636c7c] {\\r\\n padding: 6px 12px;\\r\\n margin-bottom: 8px;\\r\\n font-size: 12px;\\r\\n color: #e6a23c;\\r\\n background: #fdf6ec;\\r\\n border: 1px solid #faecd8;\\r\\n border-radius: 4px;\\n}\\n.preview-xlsx-table[data-v-0e636c7c] {\\r\\n border-collapse: collapse;\\r\\n width: 100%;\\r\\n font-size: 13px;\\n}\\n.preview-xlsx-table th[data-v-0e636c7c],\\r\\n.preview-xlsx-table td[data-v-0e636c7c] {\\r\\n border: 1px solid #dcdfe6;\\r\\n padding: 6px 10px;\\r\\n text-align: left;\\r\\n white-space: nowrap;\\r\\n min-width: 80px;\\n}\\n.preview-xlsx-table th[data-v-0e636c7c] {\\r\\n background: #f5f7fa;\\r\\n font-weight: 600;\\r\\n color: #1f2937;\\r\\n position: sticky;\\r\\n top: 0;\\n}\\n.preview-xlsx-table tr[data-v-0e636c7c]:nth-child(even) {\\r\\n background: #fafbfc;\\n}\\n.preview-xlsx-table tr[data-v-0e636c7c]:hover {\\r\\n background: #f0f7ff;\\n}\\r\\n\\r\\n/* ==================== Word 预览样式 ==================== */\\n.preview-docx-wrap[data-v-0e636c7c] {\\r\\n height: 100%;\\r\\n overflow: auto;\\r\\n background: #fff;\\r\\n border-radius: 4px;\\r\\n border: 1px solid #e8eaed;\\n}\\n.preview-docx-content[data-v-0e636c7c] {\\r\\n padding: 0;\\r\\n font-size: 14px;\\r\\n line-height: 1.8;\\r\\n color: #333;\\r\\n min-height: 0;\\r\\n -webkit-user-select: text;\\r\\n -moz-user-select: text;\\r\\n -ms-user-select: text;\\r\\n user-select: text;\\n}\\n.preview-docx-content h1[data-v-0e636c7c] {\\r\\n font-size: 22px;\\r\\n font-weight: 700;\\r\\n margin: 20px 0 10px;\\r\\n padding-bottom: 6px;\\r\\n border-bottom: 2px solid #e8eaed;\\r\\n color: #1a1a2e;\\n}\\n.preview-docx-content h2[data-v-0e636c7c] {\\r\\n font-size: 18px;\\r\\n font-weight: 600;\\r\\n margin: 16px 0 8px;\\r\\n padding-bottom: 4px;\\r\\n border-bottom: 1px solid #e8eaed;\\r\\n color: #1a1a2e;\\n}\\n.preview-docx-content h3[data-v-0e636c7c] {\\r\\n font-size: 16px;\\r\\n font-weight: 600;\\r\\n margin: 14px 0 6px;\\r\\n color: #2c3e50;\\n}\\n.preview-docx-content p[data-v-0e636c7c] {\\r\\n margin: 8px 0;\\n}\\n.preview-docx-content ul[data-v-0e636c7c],\\r\\n.preview-docx-content ol[data-v-0e636c7c] {\\r\\n padding-left: 24px;\\r\\n margin: 8px 0;\\n}\\n.preview-docx-content li[data-v-0e636c7c] {\\r\\n margin: 3px 0;\\n}\\n.preview-docx-content blockquote[data-v-0e636c7c] {\\r\\n border-left: 3px solid #409eff;\\r\\n padding: 6px 14px;\\r\\n margin: 10px 0;\\r\\n background: #f0f7ff;\\r\\n color: #555;\\r\\n border-radius: 0 4px 4px 0;\\n}\\n.preview-docx-content pre[data-v-0e636c7c] {\\r\\n background: #f5f7fa;\\r\\n padding: 12px 16px;\\r\\n border-radius: 6px;\\r\\n overflow-x: auto;\\r\\n font-size: 13px;\\r\\n border: 1px solid #e8eaed;\\r\\n margin: 10px 0;\\n}\\n.preview-docx-content a[data-v-0e636c7c] {\\r\\n color: #409eff;\\r\\n text-decoration: underline;\\n}\\n.preview-docx-content img[data-v-0e636c7c] {\\r\\n max-width: 100%;\\r\\n border-radius: 4px;\\r\\n margin: 6px 0;\\n}\\n.preview-docx-content table[data-v-0e636c7c] {\\r\\n border-collapse: collapse;\\r\\n width: 100%;\\r\\n margin: 12px 0;\\r\\n font-size: 13px;\\n}\\n.preview-docx-content th[data-v-0e636c7c],\\r\\n.preview-docx-content td[data-v-0e636c7c] {\\r\\n border: 1px solid #dcdfe6;\\r\\n padding: 8px 12px;\\r\\n text-align: left;\\n}\\n.preview-docx-content th[data-v-0e636c7c] {\\r\\n background: #f5f7fa;\\r\\n font-weight: 600;\\n}\\r\\n\\r\\n/* ==================== 批量移动弹窗 ==================== */\\n.move-tip[data-v-0e636c7c] {\\r\\n margin-bottom: 12px;\\r\\n padding: 10px 12px;\\r\\n background: #f5f7fa;\\r\\n border-radius: 4px;\\r\\n font-size: 13px;\\r\\n color: #606266;\\n}\\n.move-count[data-v-0e636c7c] {\\r\\n color: #409eff;\\r\\n font-weight: 600;\\n}\\n.move-target[data-v-0e636c7c] {\\r\\n color: #67c23a;\\r\\n font-weight: 600;\\n}\\n.move-target-empty[data-v-0e636c7c] {\\r\\n color: #e6a23c;\\n}\\n.move-folder-wrap[data-v-0e636c7c] {\\r\\n max-height: 420px;\\r\\n overflow: auto;\\r\\n border: 1px solid #e8eaed;\\r\\n border-radius: 4px;\\r\\n padding: 8px;\\n}\\n.move-folder-wrap[data-v-0e636c7c] .el-tree-node__content {\\r\\n height: 32px;\\n}\\r\\n\", \"\"]);\n// Exports\nmodule.exports = exports;\n\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/DocumentList.vue?./node_modules/css-loader/dist/cjs.js??ref--6-oneOf-1-1!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src??ref--6-oneOf-1-2!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
1712
|
+
|
|
1713
|
+
/***/ }),
|
|
1714
|
+
|
|
1715
|
+
/***/ "./node_modules/css-loader/dist/cjs.js?!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src/index.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/lib/knowledgeBase/DocumentList.vue?vue&type=style&index=1&lang=css&":
|
|
1716
|
+
/*!**********************************************************************************************************************************************************************************************************************************************************************************************************************************************************!*\
|
|
1717
|
+
!*** ./node_modules/css-loader/dist/cjs.js??ref--6-oneOf-1-1!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src??ref--6-oneOf-1-2!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options!./src/lib/knowledgeBase/DocumentList.vue?vue&type=style&index=1&lang=css& ***!
|
|
1718
|
+
\**********************************************************************************************************************************************************************************************************************************************************************************************************************************************************/
|
|
1719
|
+
/*! no static exports found */
|
|
1720
|
+
/***/ (function(module, exports, __webpack_require__) {
|
|
1721
|
+
|
|
1722
|
+
eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\nexports = ___CSS_LOADER_API_IMPORT___(false);\n// Module\nexports.push([module.i, \"\\n.wps-preview-dialog.el-dialog {\\r\\n border-radius: 10px;\\r\\n overflow: hidden;\\r\\n box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18), 0 3px 8px rgba(0, 0, 0, 0.10);\\r\\n background: #fff;\\n}\\n.wps-preview-dialog .el-dialog__header {\\r\\n padding: 0;\\r\\n margin: 0;\\r\\n border-bottom: 1px solid #e5e6eb;\\r\\n background: #f5f7fa;\\n}\\n.wps-preview-dialog .el-dialog__title {\\r\\n display: none;\\n}\\n.wps-preview-dialog .el-dialog__headerbtn {\\r\\n top: 14px;\\r\\n right: 16px;\\n}\\n.wps-preview-dialog .el-dialog__body {\\r\\n padding: 0;\\n}\\n.wps-preview-dialog .el-dialog__headerbtn .el-dialog__close {\\r\\n color: #909399;\\r\\n font-size: 18px;\\n}\\n.wps-preview-dialog .el-dialog__headerbtn:hover .el-dialog__close {\\r\\n color: #409eff;\\n}\\r\\n\", \"\"]);\n// Exports\nmodule.exports = exports;\n\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/DocumentList.vue?./node_modules/css-loader/dist/cjs.js??ref--6-oneOf-1-1!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src??ref--6-oneOf-1-2!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
1712
1723
|
|
|
1713
1724
|
/***/ }),
|
|
1714
1725
|
|
|
@@ -1719,7 +1730,7 @@ eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../.
|
|
|
1719
1730
|
/*! no static exports found */
|
|
1720
1731
|
/***/ (function(module, exports, __webpack_require__) {
|
|
1721
1732
|
|
|
1722
|
-
eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\nexports = ___CSS_LOADER_API_IMPORT___(false);\n// Module\nexports.push([module.i, \"\\n.kb-chat-root[data-v-1ce90f0b] {\\r\\n flex: 1;\\r\\n display: flex;\\r\\n flex-direction: column;\\r\\n min-width: 0;\\r\\n min-height: 0;\\n}\\r\\n\\r\\n/* 完全隔离主应用 lib-flexible 的影响 - 强制所有子元素使用 px 单位 */\\n.kb-chat-root[data-v-1ce90f0b],\\r\\n.kb-chat-root *[data-v-1ce90f0b],\\r\\n.kb-chat-root *[data-v-1ce90f0b]::before,\\r\\n.kb-chat-root *[data-v-1ce90f0b]::after {\\r\\n font-size: 14px !important;\\n}\\r\\n\\r\\n/* ===== 聊天头部 ===== */\\n.chat-header[data-v-1ce90f0b] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n justify-content: space-between;\\r\\n padding: 10px 20px;\\r\\n background: #fff;\\r\\n border-bottom: 1px solid #e8e8e8;\\r\\n flex-shrink: 0;\\r\\n z-index: 10;\\n}\\n.header-left[data-v-1ce90f0b] { display: flex; align-items: center; gap: 10px; min-width: 0; flex: 1;\\n}\\n.agent-info[data-v-1ce90f0b] { display: flex; align-items: center; gap: 10px;\\n}\\n.agent-avatar[data-v-1ce90f0b] { width: 36px; height: 36px; border-radius: 50%; overflow: hidden; display: flex; align-items: center; justify-content: center; flex-shrink: 0; background: #fff;\\n}\\n.avatar-image[data-v-1ce90f0b] { width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover; display: block;\\n}\\n.agent-text[data-v-1ce90f0b] { min-width: 0;\\n}\\n.agent-name[data-v-1ce90f0b] { font-size: 15px; font-weight: 600; color: #333; line-height: 1.3;\\n}\\n.agent-desc[data-v-1ce90f0b] { font-size: 12px; color: #999; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: min(300px, 45vw);\\n}\\r\\n\\r\\n/* ===== 消息列表 ===== */\\n.chat-body[data-v-1ce90f0b] {\\r\\n flex: 1;\\r\\n overflow-y: auto;\\r\\n padding: 16px clamp(12px, 3vw, 40px);\\r\\n scroll-behavior: auto;\\r\\n position: relative;\\r\\n -webkit-overflow-scrolling: touch;\\r\\n background: url(https://qiguanbang.obs.cn-east-3.myhuaweicloud.com/production/image/AlAgent/agentbackground.png) 100% no-repeat;\\r\\n background-size: cover;\\r\\n background-position: center;\\r\\n min-height: 0;\\n}\\r\\n\\r\\n/* 欢迎页 */\\n.welcome[data-v-1ce90f0b] { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: #bbb; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; margin: 0 -20px;\\n}\\n.welcome-icon[data-v-1ce90f0b] { margin-bottom: 12px; opacity: .7;\\n}\\n.welcome-title[data-v-1ce90f0b] { font-size: 20px; font-weight: 600; color: #333; margin-bottom: 8px;\\n}\\n.welcome-text[data-v-1ce90f0b] { font-size: 15px; color: #bbb;\\n}\\n@-webkit-keyframes pulse-data-v-1ce90f0b {\\n0%, 100% { opacity: 1;\\n}\\n50% { opacity: 0.4;\\n}\\n}\\n@keyframes pulse-data-v-1ce90f0b {\\n0%, 100% { opacity: 1;\\n}\\n50% { opacity: 0.4;\\n}\\n}\\r\\n\\r\\n/* 空消息欢迎卡片 */\\n.chat-empty-welcome[data-v-1ce90f0b] {\\r\\n width: 100%;\\r\\n padding: 40px 20px;\\r\\n box-sizing: border-box;\\r\\n display: flex;\\r\\n flex-direction: column;\\r\\n gap: 0;\\r\\n max-width: 600px;\\r\\n margin: 80px auto;\\n}\\n.welcome-card[data-v-1ce90f0b] {\\r\\n position: relative;\\r\\n background: linear-gradient(296deg, #D6EDFF 20%, #E8EBFF 80%);\\r\\n border-radius: 16px 16px 0 0;\\r\\n padding: 24px 24px 24px 150px;\\r\\n overflow: visible;\\n}\\n.welcome-avatar-box[data-v-1ce90f0b] {\\r\\n position: absolute;\\r\\n left: 15px;\\r\\n bottom: 0;\\r\\n width: 110px;\\r\\n height: 137px;\\n}\\n.welcome-avatar[data-v-1ce90f0b] {\\r\\n width: 100%;\\r\\n height: 100%;\\r\\n border-radius: 12px;\\r\\n background-size: cover;\\r\\n background-position: center;\\r\\n background-repeat: no-repeat;\\n}\\n.welcome-text-group[data-v-1ce90f0b] { display: flex; flex-direction: column; gap: 6px;\\n}\\n.welcome-text-group .text-sm[data-v-1ce90f0b] { font-weight: 400; color: #666; font-size: 12px; text-align: left;\\n}\\n.welcome-text-group .text-lg[data-v-1ce90f0b] { color: #000; font-size: 16px; font-weight: 500;\\n}\\r\\n\\r\\n/* 猜您想问 */\\n.guess-box[data-v-1ce90f0b] {\\r\\n border-top: 1px solid #fff;\\r\\n padding: 20px;\\r\\n background: linear-gradient(180deg, #b8dcff 0%, #dff0ff 50%, #fff 100%);\\r\\n border-radius: 0 0 16px 16px;\\n}\\n.guess-title[data-v-1ce90f0b] { display: flex; align-items: center; gap: 6px; margin-bottom: 16px; color: #222; font-size: 16px; font-weight: 500;\\n}\\n.tag-wrap[data-v-1ce90f0b] { display: flex; flex-wrap: wrap; gap: 10px;\\n}\\n.quick-tag[data-v-1ce90f0b] { padding: 8px 14px; background: #f2faff; border: 1px solid #e0eaff; border-radius: 8px; color: #333; cursor: pointer; font-size: 14px; white-space: nowrap; transition: all .2s;\\n}\\n.quick-tag[data-v-1ce90f0b]:hover { background: #f0f7ff; border-color: #1677ff;\\n}\\r\\n\\r\\n/* ── Thinking ── */\\n.thinking-details[data-v-1ce90f0b] { margin-bottom: 10px; border: 1px solid #e8e8e8; border-radius: 8px; overflow: hidden; background: #fafafa;\\n}\\n.thinking-summary[data-v-1ce90f0b] { font-size: 12px; color: #888; cursor: pointer; padding: 6px 10px; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;\\n}\\n.thinking-summary[data-v-1ce90f0b]:hover { color: #1677ff;\\n}\\n.thinking-body[data-v-1ce90f0b] { font-size: 14px; color: #666; padding: 8px 12px; line-height: 1.6; border-top: 1px solid #f0f0f0; text-align: left; white-space: pre-wrap; word-break: break-word; overflow-wrap: break-word; overflow-wrap: anywhere;\\n}\\n.final-content[data-v-1ce90f0b] { word-break: break-word; overflow-wrap: break-word; overflow-wrap: anywhere;\\n}\\r\\n\\r\\n/* 消息行 */\\n.msg-row[data-v-1ce90f0b] { width: 100%; display: flex; justify-content: center; padding: 20px 16px; box-sizing: border-box; align-items: center;\\n}\\n.msg-main[data-v-1ce90f0b] { display: flex; align-items: flex-start; gap: 10px; width: min(100%, 860px) !important; max-width: 860px;\\n}\\n.msg-row.user .msg-main[data-v-1ce90f0b] { flex-direction: row-reverse;\\n}\\n.msg-row.assistant .msg-main[data-v-1ce90f0b] { flex-direction: row;\\n}\\r\\n\\r\\n/* 头像 */\\n.message-avatar[data-v-1ce90f0b] { width: 36px; height: 36px; border-radius: 50%; flex-shrink: 0; -o-object-fit: cover; object-fit: cover;\\n}\\r\\n\\r\\n/* 消息内容区域 */\\n.msg-content[data-v-1ce90f0b] { width: 100% !important; max-width: 100%; box-sizing: border-box; overflow: hidden; display: flex; flex-direction: column;\\n}\\n.msg-row.user .msg-content[data-v-1ce90f0b] { align-items: flex-end;\\n}\\n.msg-row.assistant .msg-content[data-v-1ce90f0b] { align-items: flex-start;\\n}\\r\\n\\r\\n/* 气泡 */\\n.bubble[data-v-1ce90f0b] { max-width: 100%; min-width: 30px; width: -webkit-fit-content; width: -moz-fit-content; width: fit-content; padding: 8px 14px; border-radius: 12px; line-height: 1.4; font-size: 15px; word-break: break-word; white-space: normal; text-align: left; overflow-wrap: anywhere;\\n}\\n.bubble.assistant[data-v-1ce90f0b] { background: #fff; color: #333; border-bottom-left-radius: 4px; box-shadow: 0 1px 4px rgba(0,0,0,.06); width: 100%; box-sizing: border-box;\\n}\\n.bubble.user[data-v-1ce90f0b] { background: #1677ff; color: #fff; border-bottom-right-radius: 4px; box-shadow: 0 2px 8px rgba(22,119,255,.15); float: none; margin-left: auto; max-width: min(100%, 520px);\\n}\\r\\n\\r\\n/* 来源行 */\\n.source-line[data-v-1ce90f0b] { margin-top: 10px; padding-top: 8px; border-top: 1px solid #f0f0f0; color: #999; font-size: 12px;\\n}\\r\\n\\r\\n/* 消息操作区 */\\n.msg-actions[data-v-1ce90f0b] { display: flex; gap: 4px; margin-top: 4px; padding-left: 4px; align-items: center; justify-content: space-between;\\n}\\n.action-btn[data-v-1ce90f0b] { font-size: 12px; color: #999; cursor: pointer; padding: 2px 6px 2px 0; border-radius: 4px; transition: all .2s; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;\\n}\\n.action-btn[data-v-1ce90f0b]:hover { color: #1677ff; background: #f0f5ff;\\n}\\n.ai-badge[data-v-1ce90f0b] { font-size: 11px; color: #999; padding: 1px 6px; border-radius: 3px; white-space: nowrap; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; margin-left: auto;\\n}\\r\\n\\r\\n/* 流式打字动画 */\\n.typing[data-v-1ce90f0b] { display: flex; align-items: center; gap: 6px; padding: 4px 0;\\n}\\n.phrase[data-v-1ce90f0b] { font-size: 14px; color: #999;\\n}\\n.dots[data-v-1ce90f0b] { display: flex; align-items: center; gap: 3px;\\n}\\n.dots span[data-v-1ce90f0b] { width: 6px; height: 6px; border-radius: 50%; background: #bbb; display: inline-block; -webkit-animation: dot-bounce-data-v-1ce90f0b 1.4s infinite ease-in-out both; animation: dot-bounce-data-v-1ce90f0b 1.4s infinite ease-in-out both;\\n}\\n.dots span[data-v-1ce90f0b]:nth-child(1) { -webkit-animation-delay: -0.32s; animation-delay: -0.32s;\\n}\\n.dots span[data-v-1ce90f0b]:nth-child(2) { -webkit-animation-delay: -0.16s; animation-delay: -0.16s;\\n}\\n@-webkit-keyframes dot-bounce-data-v-1ce90f0b {\\n0%, 80%, 100% { transform: scale(0);\\n}\\n40% { transform: scale(1);\\n}\\n}\\n@keyframes dot-bounce-data-v-1ce90f0b {\\n0%, 80%, 100% { transform: scale(0);\\n}\\n40% { transform: scale(1);\\n}\\n}\\r\\n\\r\\n/* 光标动画 */\\n.cursor[data-v-1ce90f0b] { display: inline-block; -webkit-animation: blink-data-v-1ce90f0b 1s step-end infinite; animation: blink-data-v-1ce90f0b 1s step-end infinite; color: #1677ff; font-weight: 700;\\n}\\n@-webkit-keyframes blink-data-v-1ce90f0b {\\n50% { opacity: 0;\\n}\\n}\\n@keyframes blink-data-v-1ce90f0b {\\n50% { opacity: 0;\\n}\\n}\\r\\n\\r\\n/* ===== 输入区域 ===== */\\n.chat-footer[data-v-1ce90f0b] { flex-shrink: 0; border-top: none; padding: 30px 0; display: flex; align-items: center; justify-content: center; margin: 0 auto; width: 100%;\\n}\\n.input-area[data-v-1ce90f0b] { position: relative; display: flex; align-items: flex-end; gap: 6px; background: #fff; border-radius: 24px; margin: 0 24px; padding: 18px 20px; border: 1px solid #b3d8ff; transition: border-color .2s; flex-wrap: wrap; width: min(100%, 1000px) !important; box-sizing: border-box;\\n}\\n.input-area[data-v-1ce90f0b]:focus-within { border-color: #1677ff; background: #fff; box-shadow: none;\\n}\\n.input-area[data-v-1ce90f0b]::after { content: \\\"Enter 发送,Ctrl+Enter 换行\\\"; position: absolute; right: 100px; bottom: 28px; font-size: 14px; color: #8c8c8c; pointer-events: none;\\n}\\n.chat-textarea[data-v-1ce90f0b] { flex: 1 1 320px; min-width: 0; border: none; outline: none; background: transparent; resize: none; font-size: 16px; line-height: 24px; min-height: 38px; height: 100px; max-height: 180px; overflow-y: auto; padding: 0; box-sizing: border-box; font-family: inherit; color: #333;\\n}\\n.chat-textarea[data-v-1ce90f0b]::-moz-placeholder { color: #999; font-size: 16px;\\n}\\n.chat-textarea[data-v-1ce90f0b]:-ms-input-placeholder { color: #999; font-size: 16px;\\n}\\n.chat-textarea[data-v-1ce90f0b]::placeholder { color: #999; font-size: 16px;\\n}\\n.chat-textarea[data-v-1ce90f0b]:disabled { cursor: not-allowed; opacity: .5;\\n}\\n.send-btn[data-v-1ce90f0b] { width: auto; height: 40px; padding: 0 21px; border-radius: 6px; border: none; display: flex; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0; transition: all .2s; background: #1677ff; color: #fff; font-size: 14px;\\n}\\n.send-btn svg[data-v-1ce90f0b] { display: none;\\n}\\n.send-btn[data-v-1ce90f0b]::after { content: \\\"发送\\\";\\n}\\n.send-btn.active[data-v-1ce90f0b] { background: #1677ff;\\n}\\n.send-btn.active[data-v-1ce90f0b]:hover { background: #4096ff;\\n}\\n.send-btn[data-v-1ce90f0b]:disabled { background: #88bfff; cursor: not-allowed;\\n}\\n.stop-btn[data-v-1ce90f0b] { width: 35px; height: 35px; border-radius: 50%; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; background: #ff4d4f; color: #fff; flex-shrink: 0; transition: all .2s;\\n}\\n.stop-btn[data-v-1ce90f0b]:hover { background: #ff7875;\\n}\\r\\n\", \"\"]);\n// Exports\nmodule.exports = exports;\n\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/KbChatMain.vue?./node_modules/css-loader/dist/cjs.js??ref--6-oneOf-1-1!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src??ref--6-oneOf-1-2!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
1733
|
+
eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\nexports = ___CSS_LOADER_API_IMPORT___(false);\n// Module\nexports.push([module.i, \"\\n.kb-chat-root[data-v-1ce90f0b] {\\r\\n flex: 1;\\r\\n display: flex;\\r\\n flex-direction: column;\\r\\n min-width: 0;\\r\\n min-height: 0;\\n}\\r\\n\\r\\n/* 完全隔离主应用 lib-flexible 的影响 - 强制所有子元素使用 px 单位 */\\n.kb-chat-root[data-v-1ce90f0b],\\r\\n.kb-chat-root *[data-v-1ce90f0b],\\r\\n.kb-chat-root *[data-v-1ce90f0b]::before,\\r\\n.kb-chat-root *[data-v-1ce90f0b]::after {\\r\\n font-size: 14px !important;\\n}\\r\\n\\r\\n/* ===== 聊天头部 ===== */\\n.chat-header[data-v-1ce90f0b] {\\r\\n display: flex;\\r\\n align-items: center;\\r\\n justify-content: space-between;\\r\\n padding: 10px 20px;\\r\\n background: #fff;\\r\\n border-bottom: 1px solid #e8e8e8;\\r\\n flex-shrink: 0;\\r\\n z-index: 10;\\n}\\n.header-left[data-v-1ce90f0b] { display: flex; align-items: center; gap: 10px; min-width: 0; flex: 1;\\n}\\n.agent-info[data-v-1ce90f0b] { display: flex; align-items: center; gap: 10px;\\n}\\n.agent-avatar[data-v-1ce90f0b] { width: 36px; height: 36px; border-radius: 50%; overflow: hidden; display: flex; align-items: center; justify-content: center; flex-shrink: 0; background: #fff;\\n}\\n.avatar-image[data-v-1ce90f0b] { width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover; display: block;\\n}\\n.agent-text[data-v-1ce90f0b] { min-width: 0;\\n}\\n.agent-name[data-v-1ce90f0b] { font-size: 15px; font-weight: 600; color: #333; line-height: 1.3;\\n}\\n.agent-desc[data-v-1ce90f0b] { font-size: 12px; color: #999; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: min(300px, 45vw);\\n}\\r\\n\\r\\n/* ===== 消息列表 ===== */\\n.chat-body[data-v-1ce90f0b] {\\r\\n flex: 1;\\r\\n overflow-y: auto;\\r\\n padding: 16px clamp(12px, 3vw, 40px);\\r\\n scroll-behavior: auto;\\r\\n position: relative;\\r\\n -webkit-overflow-scrolling: touch;\\r\\n background: url(https://qiguanbang.obs.cn-east-3.myhuaweicloud.com/production/image/AlAgent/agentbackground.png) 100% no-repeat;\\r\\n background-size: cover;\\r\\n background-position: center;\\r\\n min-height: 0;\\n}\\r\\n\\r\\n/* 欢迎页 */\\n.welcome[data-v-1ce90f0b] { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: #bbb; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; margin: 0 -20px;\\n}\\n.welcome-icon[data-v-1ce90f0b] { margin-bottom: 12px; opacity: .7;\\n}\\n.welcome-title[data-v-1ce90f0b] { font-size: 20px; font-weight: 600; color: #333; margin-bottom: 8px;\\n}\\n.welcome-text[data-v-1ce90f0b] { font-size: 15px; color: #bbb;\\n}\\n@-webkit-keyframes pulse-data-v-1ce90f0b {\\n0%, 100% { opacity: 1;\\n}\\n50% { opacity: 0.4;\\n}\\n}\\n@keyframes pulse-data-v-1ce90f0b {\\n0%, 100% { opacity: 1;\\n}\\n50% { opacity: 0.4;\\n}\\n}\\r\\n\\r\\n/* 空消息欢迎卡片 */\\n.chat-empty-welcome[data-v-1ce90f0b] {\\r\\n width: 100%;\\r\\n padding: 40px 20px;\\r\\n box-sizing: border-box;\\r\\n display: flex;\\r\\n flex-direction: column;\\r\\n gap: 0;\\r\\n max-width: 600px;\\r\\n margin: 80px auto;\\n}\\n.welcome-card[data-v-1ce90f0b] {\\r\\n position: relative;\\r\\n background: linear-gradient(296deg, #D6EDFF 20%, #E8EBFF 80%);\\r\\n border-radius: 16px 16px 0 0;\\r\\n padding: 24px 24px 24px 150px;\\r\\n overflow: visible;\\n}\\n.welcome-avatar-box[data-v-1ce90f0b] {\\r\\n position: absolute;\\r\\n left: 15px;\\r\\n bottom: 0;\\r\\n width: 110px;\\r\\n height: 137px;\\n}\\n.welcome-avatar[data-v-1ce90f0b] {\\r\\n width: 100%;\\r\\n height: 100%;\\r\\n border-radius: 12px;\\r\\n background-size: cover;\\r\\n background-position: center;\\r\\n background-repeat: no-repeat;\\n}\\n.welcome-text-group[data-v-1ce90f0b] { display: flex; flex-direction: column; gap: 6px;\\n}\\n.welcome-text-group .text-sm[data-v-1ce90f0b] { font-weight: 400; color: #666; font-size: 12px; text-align: left;\\n}\\n.welcome-text-group .text-lg[data-v-1ce90f0b] { color: #000; font-size: 16px; font-weight: 500;\\n}\\r\\n\\r\\n/* 猜您想问 */\\n.guess-box[data-v-1ce90f0b] {\\r\\n border-top: 1px solid #fff;\\r\\n padding: 20px;\\r\\n background: linear-gradient(180deg, #b8dcff 0%, #dff0ff 50%, #fff 100%);\\r\\n border-radius: 0 0 16px 16px;\\n}\\n.guess-title[data-v-1ce90f0b] { display: flex; align-items: center; gap: 6px; margin-bottom: 16px; color: #222; font-size: 16px; font-weight: 500;\\n}\\n.tag-wrap[data-v-1ce90f0b] { display: flex; flex-wrap: wrap; gap: 10px;\\n}\\n.quick-tag[data-v-1ce90f0b] { padding: 8px 14px; background: #f2faff; border: 1px solid #e0eaff; border-radius: 8px; color: #333; cursor: pointer; font-size: 14px; white-space: nowrap; transition: all .2s;\\n}\\n.quick-tag[data-v-1ce90f0b]:hover { background: #f0f7ff; border-color: #1677ff;\\n}\\r\\n\\r\\n/* ── Thinking ── */\\n.thinking-details[data-v-1ce90f0b] { margin-bottom: 10px; border: 1px solid #e8e8e8; border-radius: 8px; overflow: hidden; background: #fafafa;\\n}\\n.thinking-summary[data-v-1ce90f0b] { font-size: 12px; color: #888; cursor: pointer; padding: 6px 10px; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;\\n}\\n.thinking-summary[data-v-1ce90f0b]:hover { color: #1677ff;\\n}\\n.thinking-body[data-v-1ce90f0b] { font-size: 14px; color: #999; padding: 8px 12px; line-height: 1.6; border-top: 1px solid #f0f0f0; text-align: left; white-space: pre-wrap; word-break: break-word; overflow-wrap: break-word; overflow-wrap: anywhere;\\n}\\n.final-content[data-v-1ce90f0b] { word-break: break-word; overflow-wrap: break-word; overflow-wrap: anywhere; white-space: normal; font-size: 16px; line-height: 1.7;\\n}\\r\\n/* markdown 内容子元素样式 */\\n.final-content[data-v-1ce90f0b] h1, .final-content[data-v-1ce90f0b] h2, .final-content[data-v-1ce90f0b] h3, .final-content[data-v-1ce90f0b] h4, .final-content[data-v-1ce90f0b] h5, .final-content[data-v-1ce90f0b] h6 { margin: 12px 0 6px; font-weight: 600; line-height: 1.5;\\n}\\n.final-content[data-v-1ce90f0b] h1 { font-size: 24px;\\n}\\n.final-content[data-v-1ce90f0b] h2 { font-size: 20px;\\n}\\n.final-content[data-v-1ce90f0b] h3 { font-size: 18px;\\n}\\n.final-content[data-v-1ce90f0b] h4, .final-content[data-v-1ce90f0b] h5, .final-content[data-v-1ce90f0b] h6 { font-size: 16px;\\n}\\n.final-content[data-v-1ce90f0b] strong { font-weight: 700;\\n}\\n.final-content[data-v-1ce90f0b] em { font-style: italic;\\n}\\n.final-content[data-v-1ce90f0b] blockquote { margin: 8px 0; padding: 4px 12px; border-left: 3px solid #b3d8ff; color: #666; background: #f6f8fa;\\n}\\n.final-content[data-v-1ce90f0b] a { color: #1677ff;\\n}\\n.final-content[data-v-1ce90f0b] p { margin: 6px 0;\\n}\\n.final-content[data-v-1ce90f0b] ul, .final-content[data-v-1ce90f0b] ol { padding-left: 20px; margin: 4px 0;\\n}\\n.final-content[data-v-1ce90f0b] li { margin: 2px 0;\\n}\\n.final-content[data-v-1ce90f0b] table { border-collapse: collapse; width: 100%; margin: 6px 0; font-size: 14px;\\n}\\n.final-content[data-v-1ce90f0b] th, .final-content[data-v-1ce90f0b] td { border: 1px solid #e8e8e8; padding: 5px 8px; text-align: left;\\n}\\n.final-content[data-v-1ce90f0b] th { background: #fafafa; font-weight: 600;\\n}\\n.final-content[data-v-1ce90f0b] code { padding: 1px 5px; border-radius: 3px; font-size: 14px;\\n}\\n.final-content[data-v-1ce90f0b] pre { background: #f6f8fa; color: #333; padding: 12px; border-radius: 6px; overflow-x: auto; font-size: 13px; border: 1px solid #e8e8e8; margin: 8px 0; line-height: 1.5; white-space: pre;\\n}\\n.final-content[data-v-1ce90f0b] pre code { padding: 0; font-size: inherit;\\n}\\r\\n\\r\\n/* 消息行 */\\n.msg-row[data-v-1ce90f0b] { width: 100%; display: flex; justify-content: center; padding: 20px 16px; box-sizing: border-box; align-items: center;\\n}\\n.msg-main[data-v-1ce90f0b] { display: flex; align-items: flex-start; gap: 10px; width: min(100%, 860px) !important; max-width: 860px;\\n}\\n.msg-row.user .msg-main[data-v-1ce90f0b] { flex-direction: row-reverse;\\n}\\n.msg-row.assistant .msg-main[data-v-1ce90f0b] { flex-direction: row;\\n}\\r\\n\\r\\n/* 头像 */\\n.message-avatar[data-v-1ce90f0b] { width: 36px; height: 36px; border-radius: 50%; flex-shrink: 0; -o-object-fit: cover; object-fit: cover;\\n}\\r\\n\\r\\n/* 消息内容区域 */\\n.msg-content[data-v-1ce90f0b] { width: 100% !important; max-width: 100%; box-sizing: border-box; overflow: hidden; display: flex; flex-direction: column;\\n}\\n.msg-row.user .msg-content[data-v-1ce90f0b] { align-items: flex-end;\\n}\\n.msg-row.assistant .msg-content[data-v-1ce90f0b] { align-items: flex-start;\\n}\\r\\n\\r\\n/* 气泡 */\\n.bubble[data-v-1ce90f0b] { max-width: 100%; min-width: 30px; width: -webkit-fit-content; width: -moz-fit-content; width: fit-content; padding: 8px 14px; border-radius: 12px; line-height: 1.4; font-size: 15px; word-break: break-word; white-space: normal; text-align: left; overflow-wrap: anywhere;\\n}\\n.bubble.assistant[data-v-1ce90f0b] { background: #fff; color: #333; border-bottom-left-radius: 4px; box-shadow: 0 1px 4px rgba(0,0,0,.06); width: 100%; box-sizing: border-box;\\n}\\n.bubble.user[data-v-1ce90f0b] { background: #1677ff; color: #fff; border-bottom-right-radius: 4px; box-shadow: 0 2px 8px rgba(22,119,255,.15); float: none; margin-left: auto; max-width: min(100%, 520px);\\n}\\r\\n\\r\\n/* ── Tool Calls(与 AI 助理 ChatDetail 完全一致)── */\\n.tool-calls[data-v-1ce90f0b] { margin-top: 8px;\\n}\\n.tool-item[data-v-1ce90f0b] { background: #f5f5f5; border-radius: 6px; padding: 6px 10px; margin-bottom: 4px; font-size: 12px;\\n}\\n.tool-badge[data-v-1ce90f0b] { display: inline-block; margin-bottom: 2px; font-size: 11px; color: #fa8c16; font-weight: 500;\\n}\\n.tool-item pre[data-v-1ce90f0b] { margin: 0; white-space: pre-wrap; word-break: break-all; font-size: 12px; color: #666; max-height: 120px; overflow-y: auto;\\n}\\r\\n\\r\\n/* 消息操作区 */\\n.msg-actions[data-v-1ce90f0b] { display: flex; gap: 4px; margin-top: 4px; padding-left: 4px; align-items: center; justify-content: space-between;\\n}\\n.action-btn[data-v-1ce90f0b] { font-size: 12px; color: #999; cursor: pointer; padding: 2px 6px 2px 0; border-radius: 4px; transition: all .2s; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;\\n}\\n.action-btn[data-v-1ce90f0b]:hover { color: #1677ff; background: #f0f5ff;\\n}\\n.ai-badge[data-v-1ce90f0b] { font-size: 11px; color: #999; padding: 1px 6px; border-radius: 3px; white-space: nowrap; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; margin-left: auto;\\n}\\r\\n\\r\\n/* 流式打字动画 */\\n.typing[data-v-1ce90f0b] { display: flex; align-items: center; gap: 6px; padding: 4px 0;\\n}\\n.phrase[data-v-1ce90f0b] { font-size: 14px; color: #999;\\n}\\n.dots[data-v-1ce90f0b] { display: flex; align-items: center; gap: 3px;\\n}\\n.dots span[data-v-1ce90f0b] { width: 6px; height: 6px; border-radius: 50%; background: #bbb; display: inline-block; -webkit-animation: dot-bounce-data-v-1ce90f0b 1.4s infinite ease-in-out both; animation: dot-bounce-data-v-1ce90f0b 1.4s infinite ease-in-out both;\\n}\\n.dots span[data-v-1ce90f0b]:nth-child(1) { -webkit-animation-delay: -0.32s; animation-delay: -0.32s;\\n}\\n.dots span[data-v-1ce90f0b]:nth-child(2) { -webkit-animation-delay: -0.16s; animation-delay: -0.16s;\\n}\\n@-webkit-keyframes dot-bounce-data-v-1ce90f0b {\\n0%, 80%, 100% { transform: scale(0);\\n}\\n40% { transform: scale(1);\\n}\\n}\\n@keyframes dot-bounce-data-v-1ce90f0b {\\n0%, 80%, 100% { transform: scale(0);\\n}\\n40% { transform: scale(1);\\n}\\n}\\r\\n\\r\\n/* ===== 输入区域 ===== */\\n.chat-footer[data-v-1ce90f0b] { flex-shrink: 0; border-top: none; padding: 30px 0; display: flex; align-items: center; justify-content: center; margin: 0 auto; width: 100%;\\n}\\n.input-area[data-v-1ce90f0b] { position: relative; display: flex; align-items: flex-end; gap: 6px; background: #fff; border-radius: 24px; margin: 0 24px; padding: 18px 20px; border: 1px solid #b3d8ff; transition: border-color .2s; flex-wrap: wrap; width: min(100%, 1000px) !important; box-sizing: border-box;\\n}\\n.input-area[data-v-1ce90f0b]:focus-within { border-color: #1677ff; background: #fff; box-shadow: none;\\n}\\n.input-area[data-v-1ce90f0b]::after { content: \\\"Enter 发送,Ctrl+Enter 换行\\\"; position: absolute; right: 100px; bottom: 28px; font-size: 14px; color: #8c8c8c; pointer-events: none;\\n}\\n.chat-textarea[data-v-1ce90f0b] { flex: 1 1 320px; min-width: 0; border: none; outline: none; background: transparent; resize: none; font-size: 16px; line-height: 24px; min-height: 38px; height: 100px; max-height: 180px; overflow-y: auto; padding: 0; box-sizing: border-box; font-family: inherit; color: #333;\\n}\\n.chat-textarea[data-v-1ce90f0b]::-moz-placeholder { color: #999; font-size: 16px;\\n}\\n.chat-textarea[data-v-1ce90f0b]:-ms-input-placeholder { color: #999; font-size: 16px;\\n}\\n.chat-textarea[data-v-1ce90f0b]::placeholder { color: #999; font-size: 16px;\\n}\\n.chat-textarea[data-v-1ce90f0b]:disabled { cursor: not-allowed; opacity: .5;\\n}\\n.send-btn[data-v-1ce90f0b] { width: auto; height: 40px; padding: 0 21px; border-radius: 6px; border: none; display: flex; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0; transition: all .2s; background: #1677ff; color: #fff; font-size: 14px;\\n}\\n.send-btn svg[data-v-1ce90f0b] { display: none;\\n}\\n.send-btn[data-v-1ce90f0b]::after { content: \\\"发送\\\";\\n}\\n.send-btn.active[data-v-1ce90f0b] { background: #1677ff;\\n}\\n.send-btn.active[data-v-1ce90f0b]:hover { background: #4096ff;\\n}\\n.send-btn[data-v-1ce90f0b]:disabled { background: #88bfff; cursor: not-allowed;\\n}\\n.stop-btn[data-v-1ce90f0b] { width: 35px; height: 35px; border-radius: 50%; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; background: #ff4d4f; color: #fff; flex-shrink: 0; transition: all .2s;\\n}\\n.stop-btn[data-v-1ce90f0b]:hover { background: #ff7875;\\n}\\r\\n\", \"\"]);\n// Exports\nmodule.exports = exports;\n\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/KbChatMain.vue?./node_modules/css-loader/dist/cjs.js??ref--6-oneOf-1-1!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src??ref--6-oneOf-1-2!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
1723
1734
|
|
|
1724
1735
|
/***/ }),
|
|
1725
1736
|
|
|
@@ -14161,6 +14172,17 @@ eval("// style-loader: Adds some css to the DOM by adding a <style> tag\n\n// lo
|
|
|
14161
14172
|
|
|
14162
14173
|
/***/ }),
|
|
14163
14174
|
|
|
14175
|
+
/***/ "./node_modules/vue-style-loader/index.js?!./node_modules/css-loader/dist/cjs.js?!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src/index.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/lib/knowledgeBase/DocumentList.vue?vue&type=style&index=1&lang=css&":
|
|
14176
|
+
/*!************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************!*\
|
|
14177
|
+
!*** ./node_modules/vue-style-loader??ref--6-oneOf-1-0!./node_modules/css-loader/dist/cjs.js??ref--6-oneOf-1-1!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src??ref--6-oneOf-1-2!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options!./src/lib/knowledgeBase/DocumentList.vue?vue&type=style&index=1&lang=css& ***!
|
|
14178
|
+
\************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************/
|
|
14179
|
+
/*! no static exports found */
|
|
14180
|
+
/***/ (function(module, exports, __webpack_require__) {
|
|
14181
|
+
|
|
14182
|
+
eval("// style-loader: Adds some css to the DOM by adding a <style> tag\n\n// load the styles\nvar content = __webpack_require__(/*! !../../../node_modules/css-loader/dist/cjs.js??ref--6-oneOf-1-1!../../../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../../../node_modules/postcss-loader/src??ref--6-oneOf-1-2!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader/lib??vue-loader-options!./DocumentList.vue?vue&type=style&index=1&lang=css& */ \"./node_modules/css-loader/dist/cjs.js?!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src/index.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/lib/knowledgeBase/DocumentList.vue?vue&type=style&index=1&lang=css&\");\nif(content.__esModule) content = content.default;\nif(typeof content === 'string') content = [[module.i, content, '']];\nif(content.locals) module.exports = content.locals;\n// add the styles to the DOM\nvar add = __webpack_require__(/*! ../../../node_modules/vue-style-loader/lib/addStylesClient.js */ \"./node_modules/vue-style-loader/lib/addStylesClient.js\").default\nvar update = add(\"fb653140\", content, false, {\"sourceMap\":false,\"shadowMode\":false});\n// Hot Module Replacement\nif(false) {}\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/DocumentList.vue?./node_modules/vue-style-loader??ref--6-oneOf-1-0!./node_modules/css-loader/dist/cjs.js??ref--6-oneOf-1-1!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src??ref--6-oneOf-1-2!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options");
|
|
14183
|
+
|
|
14184
|
+
/***/ }),
|
|
14185
|
+
|
|
14164
14186
|
/***/ "./node_modules/vue-style-loader/index.js?!./node_modules/css-loader/dist/cjs.js?!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src/index.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/lib/knowledgeBase/KbChatMain.vue?vue&type=style&index=0&id=1ce90f0b&scoped=true&lang=css&":
|
|
14165
14187
|
/*!**********************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************!*\
|
|
14166
14188
|
!*** ./node_modules/vue-style-loader??ref--6-oneOf-1-0!./node_modules/css-loader/dist/cjs.js??ref--6-oneOf-1-1!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src??ref--6-oneOf-1-2!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options!./src/lib/knowledgeBase/KbChatMain.vue?vue&type=style&index=0&id=1ce90f0b&scoped=true&lang=css& ***!
|
|
@@ -14568,7 +14590,7 @@ eval("// Generated by CoffeeScript 1.12.7\n(function() {\n var XMLDocument, XML
|
|
|
14568
14590
|
/*! exports provided: name, version, main, private, scripts, dependencies, devDependencies, browserslist, directories, keywords, license, files, default */
|
|
14569
14591
|
/***/ (function(module) {
|
|
14570
14592
|
|
|
14571
|
-
eval("module.exports = JSON.parse(\"{\\\"name\\\":\\\"zj-plugin-intelligent\\\",\\\"version\\\":\\\"1.2.12-beta.
|
|
14593
|
+
eval("module.exports = JSON.parse(\"{\\\"name\\\":\\\"zj-plugin-intelligent\\\",\\\"version\\\":\\\"1.2.12-beta.7\\\",\\\"main\\\":\\\"lib/ZjPluginIntelligent.umd.min.js\\\",\\\"private\\\":false,\\\"scripts\\\":{\\\"serve\\\":\\\"set NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve\\\",\\\"build\\\":\\\"set NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build\\\",\\\"lib\\\":\\\"node scripts/build-lib.js\\\",\\\"lib:test\\\":\\\"node scripts/build-lib.js test\\\"},\\\"dependencies\\\":{\\\"@jboltai/tokui\\\":\\\"^0.1.6\\\",\\\"crypto-js\\\":\\\"^4.2.0\\\",\\\"docx\\\":\\\"^8.5.0\\\",\\\"html2canvas\\\":\\\"^1.4.1\\\",\\\"mammoth\\\":\\\"^1.6.0\\\",\\\"markdown-it\\\":\\\"^13.0.2\\\",\\\"marked\\\":\\\"^4.3.0\\\",\\\"xlsx\\\":\\\"^0.18.5\\\"},\\\"devDependencies\\\":{\\\"@babel/preset-env\\\":\\\"^7.14.9\\\",\\\"@vue/cli-plugin-babel\\\":\\\"~4.5.0\\\",\\\"@vue/cli-plugin-eslint\\\":\\\"~4.5.0\\\",\\\"@vue/cli-plugin-vuex\\\":\\\"~4.5.0\\\",\\\"@vue/cli-service\\\":\\\"~4.5.0\\\",\\\"axios\\\":\\\"^0.27.2\\\",\\\"babel-eslint\\\":\\\"^10.1.0\\\",\\\"babel-plugin-component\\\":\\\"^1.1.1\\\",\\\"compression-webpack-plugin\\\":\\\"^6.0.3\\\",\\\"core-js\\\":\\\"^3.6.5\\\",\\\"element-ui\\\":\\\"^2.15.4\\\",\\\"eslint\\\":\\\"^6.8.0\\\",\\\"eslint-plugin-vue\\\":\\\"^7.13.0\\\",\\\"js-cookie\\\":\\\"^2.2.1\\\",\\\"obs-upload\\\":\\\"^1.0.4\\\",\\\"pako\\\":\\\"^2.1.0\\\",\\\"reconnecting-websocket\\\":\\\"^4.4.0\\\",\\\"sass\\\":\\\"^1.71.0\\\",\\\"sass-loader\\\":\\\"^8.0.2\\\",\\\"snowflake-id\\\":\\\"^1.1.0\\\",\\\"vue\\\":\\\"^2.6.11\\\",\\\"vue-template-compiler\\\":\\\"^2.6.11\\\",\\\"vuex\\\":\\\"^3.4.0\\\",\\\"webpack-bundle-analyzer\\\":\\\"^4.4.2\\\"},\\\"browserslist\\\":[\\\"> 1%\\\",\\\"last 2 versions\\\",\\\"not dead\\\"],\\\"directories\\\":{\\\"lib\\\":\\\"lib\\\"},\\\"keywords\\\":[],\\\"license\\\":\\\"ISC\\\",\\\"files\\\":[\\\"lib\\\"]}\");\n\n//# sourceURL=webpack://ZjPluginIntelligent/./package.json?");
|
|
14572
14594
|
|
|
14573
14595
|
/***/ }),
|
|
14574
14596
|
|
|
@@ -15280,7 +15302,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _nod
|
|
|
15280
15302
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
15281
15303
|
|
|
15282
15304
|
"use strict";
|
|
15283
|
-
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _DocumentList_vue_vue_type_template_id_0e636c7c_scoped_true___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./DocumentList.vue?vue&type=template&id=0e636c7c&scoped=true& */ \"./src/lib/knowledgeBase/DocumentList.vue?vue&type=template&id=0e636c7c&scoped=true&\");\n/* harmony import */ var _DocumentList_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./DocumentList.vue?vue&type=script&lang=js& */ \"./src/lib/knowledgeBase/DocumentList.vue?vue&type=script&lang=js&\");\n/* harmony reexport (unknown) */ for(var __WEBPACK_IMPORT_KEY__ in _DocumentList_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__) if([\"default\"].indexOf(__WEBPACK_IMPORT_KEY__) < 0) (function(key) { __webpack_require__.d(__webpack_exports__, key, function() { return _DocumentList_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__[key]; }) }(__WEBPACK_IMPORT_KEY__));\n/* harmony import */ var _DocumentList_vue_vue_type_style_index_0_id_0e636c7c_scoped_true_lang_css___WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./DocumentList.vue?vue&type=style&index=0&id=0e636c7c&scoped=true&lang=css& */ \"./src/lib/knowledgeBase/DocumentList.vue?vue&type=style&index=0&id=0e636c7c&scoped=true&lang=css&\");\n/* harmony import */ var
|
|
15305
|
+
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _DocumentList_vue_vue_type_template_id_0e636c7c_scoped_true___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./DocumentList.vue?vue&type=template&id=0e636c7c&scoped=true& */ \"./src/lib/knowledgeBase/DocumentList.vue?vue&type=template&id=0e636c7c&scoped=true&\");\n/* harmony import */ var _DocumentList_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./DocumentList.vue?vue&type=script&lang=js& */ \"./src/lib/knowledgeBase/DocumentList.vue?vue&type=script&lang=js&\");\n/* harmony reexport (unknown) */ for(var __WEBPACK_IMPORT_KEY__ in _DocumentList_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__) if([\"default\"].indexOf(__WEBPACK_IMPORT_KEY__) < 0) (function(key) { __webpack_require__.d(__webpack_exports__, key, function() { return _DocumentList_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__[key]; }) }(__WEBPACK_IMPORT_KEY__));\n/* harmony import */ var _DocumentList_vue_vue_type_style_index_0_id_0e636c7c_scoped_true_lang_css___WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./DocumentList.vue?vue&type=style&index=0&id=0e636c7c&scoped=true&lang=css& */ \"./src/lib/knowledgeBase/DocumentList.vue?vue&type=style&index=0&id=0e636c7c&scoped=true&lang=css&\");\n/* harmony import */ var _DocumentList_vue_vue_type_style_index_1_lang_css___WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./DocumentList.vue?vue&type=style&index=1&lang=css& */ \"./src/lib/knowledgeBase/DocumentList.vue?vue&type=style&index=1&lang=css&\");\n/* harmony import */ var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */ \"./node_modules/vue-loader/lib/runtime/componentNormalizer.js\");\n\n\n\n\n\n\n\n/* normalize component */\n\nvar component = Object(_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_4__[\"default\"])(\n _DocumentList_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n _DocumentList_vue_vue_type_template_id_0e636c7c_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"render\"],\n _DocumentList_vue_vue_type_template_id_0e636c7c_scoped_true___WEBPACK_IMPORTED_MODULE_0__[\"staticRenderFns\"],\n false,\n null,\n \"0e636c7c\",\n null\n \n)\n\n/* hot reload */\nif (false) { var api; }\ncomponent.options.__file = \"src/lib/knowledgeBase/DocumentList.vue\"\n/* harmony default export */ __webpack_exports__[\"default\"] = (component.exports);\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/DocumentList.vue?");
|
|
15284
15306
|
|
|
15285
15307
|
/***/ }),
|
|
15286
15308
|
|
|
@@ -15308,6 +15330,18 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _nod
|
|
|
15308
15330
|
|
|
15309
15331
|
/***/ }),
|
|
15310
15332
|
|
|
15333
|
+
/***/ "./src/lib/knowledgeBase/DocumentList.vue?vue&type=style&index=1&lang=css&":
|
|
15334
|
+
/*!*********************************************************************************!*\
|
|
15335
|
+
!*** ./src/lib/knowledgeBase/DocumentList.vue?vue&type=style&index=1&lang=css& ***!
|
|
15336
|
+
\*********************************************************************************/
|
|
15337
|
+
/*! no static exports found */
|
|
15338
|
+
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
15339
|
+
|
|
15340
|
+
"use strict";
|
|
15341
|
+
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _node_modules_vue_style_loader_index_js_ref_6_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_6_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_6_oneOf_1_2_node_modules_cache_loader_dist_cjs_js_ref_0_0_node_modules_vue_loader_lib_index_js_vue_loader_options_DocumentList_vue_vue_type_style_index_1_lang_css___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../node_modules/vue-style-loader??ref--6-oneOf-1-0!../../../node_modules/css-loader/dist/cjs.js??ref--6-oneOf-1-1!../../../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../../../node_modules/postcss-loader/src??ref--6-oneOf-1-2!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader/lib??vue-loader-options!./DocumentList.vue?vue&type=style&index=1&lang=css& */ \"./node_modules/vue-style-loader/index.js?!./node_modules/css-loader/dist/cjs.js?!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/postcss-loader/src/index.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/lib/knowledgeBase/DocumentList.vue?vue&type=style&index=1&lang=css&\");\n/* harmony import */ var _node_modules_vue_style_loader_index_js_ref_6_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_6_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_6_oneOf_1_2_node_modules_cache_loader_dist_cjs_js_ref_0_0_node_modules_vue_loader_lib_index_js_vue_loader_options_DocumentList_vue_vue_type_style_index_1_lang_css___WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_vue_style_loader_index_js_ref_6_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_6_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_6_oneOf_1_2_node_modules_cache_loader_dist_cjs_js_ref_0_0_node_modules_vue_loader_lib_index_js_vue_loader_options_DocumentList_vue_vue_type_style_index_1_lang_css___WEBPACK_IMPORTED_MODULE_0__);\n/* harmony reexport (unknown) */ for(var __WEBPACK_IMPORT_KEY__ in _node_modules_vue_style_loader_index_js_ref_6_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_6_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_6_oneOf_1_2_node_modules_cache_loader_dist_cjs_js_ref_0_0_node_modules_vue_loader_lib_index_js_vue_loader_options_DocumentList_vue_vue_type_style_index_1_lang_css___WEBPACK_IMPORTED_MODULE_0__) if([\"default\"].indexOf(__WEBPACK_IMPORT_KEY__) < 0) (function(key) { __webpack_require__.d(__webpack_exports__, key, function() { return _node_modules_vue_style_loader_index_js_ref_6_oneOf_1_0_node_modules_css_loader_dist_cjs_js_ref_6_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_6_oneOf_1_2_node_modules_cache_loader_dist_cjs_js_ref_0_0_node_modules_vue_loader_lib_index_js_vue_loader_options_DocumentList_vue_vue_type_style_index_1_lang_css___WEBPACK_IMPORTED_MODULE_0__[key]; }) }(__WEBPACK_IMPORT_KEY__));\n\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/DocumentList.vue?");
|
|
15342
|
+
|
|
15343
|
+
/***/ }),
|
|
15344
|
+
|
|
15311
15345
|
/***/ "./src/lib/knowledgeBase/DocumentList.vue?vue&type=template&id=0e636c7c&scoped=true&":
|
|
15312
15346
|
/*!*******************************************************************************************!*\
|
|
15313
15347
|
!*** ./src/lib/knowledgeBase/DocumentList.vue?vue&type=template&id=0e636c7c&scoped=true& ***!
|
|
@@ -15436,7 +15470,7 @@ eval("\n\nvar _interopRequireWildcard = __webpack_require__(/*! ./node_modules/@
|
|
|
15436
15470
|
/***/ (function(module, exports, __webpack_require__) {
|
|
15437
15471
|
|
|
15438
15472
|
"use strict";
|
|
15439
|
-
eval("\n\nvar _interopRequireDefault = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/interopRequireDefault */ \"./node_modules/@babel/runtime/helpers/interopRequireDefault.js\").default;\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.getCompanyList = getCompanyList;\nexports.getFolderList = getFolderList;\nexports.addFolder = addFolder;\nexports.updateFolder = updateFolder;\nexports.deleteFolder = deleteFolder;\nexports.getFileList = getFileList;\nexports.getFileDetail = getFileDetail;\nexports.updateFile = updateFile;\nexports.deleteFile = deleteFile;\nexports.uploadFile = uploadFile;\nexports.indexFile = indexFile;\nexports.reindexFile = reindexFile;\nexports.deleteFileIndex = deleteFileIndex;\n\nvar _request = _interopRequireDefault(__webpack_require__(/*! @lib/utils/request */ \"./src/lib/utils/request.js\"));\n\n/*\r\n * @Desc: 公司知识库 API(对接 /api/v1/admin/company-kb)\r\n * @Date: 2026-08-18\r\n */\n// ==================== 公司 ====================\n\n/**\r\n * 获取公司列表(从文件夹/文件表去重提取)\r\n */\nfunction getCompanyList() {\n return (0, _request.default)({\n url: '/api/v1/admin/company-kb/companies',\n method: 'get'\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n} // ==================== 文件夹 ====================\n\n/**\r\n * 获取文件夹列表(平铺结构,带 parentId,前端组装树)\r\n * @param {string} companyId - 公司ID(必填)\r\n */\n\n\nfunction getFolderList(companyId) {\n return (0, _request.default)({\n url: '/api/v1/admin/company-kb/folders',\n method: 'get',\n params: {\n companyId: companyId\n }\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n/**\r\n * 新增文件夹\r\n * @param {object} data - { companyId, parentId, name, sortOrder, status }\r\n */\n\n\nfunction addFolder(data) {\n return (0, _request.default)({\n url: '/api/v1/admin/company-kb/folders',\n method: 'post',\n data: data\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n/**\r\n * 更新文件夹\r\n * @param {number} id\r\n * @param {object} data - { name, sortOrder }\r\n */\n\n\nfunction updateFolder(id, data) {\n return (0, _request.default)({\n url: \"/api/v1/admin/company-kb/folders/\".concat(id),\n method: 'put',\n data: data\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n/**\r\n * 删除文件夹(后端会校验子文件夹/文件,不允许时返回错误)\r\n * @param {number} id\r\n */\n\n\nfunction deleteFolder(id) {\n return (0, _request.default)({\n url: \"/api/v1/admin/company-kb/folders/\".concat(id),\n method: 'delete'\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n} // ==================== 文件 ====================\n\n/**\r\n * 文件列表(分页)\r\n * @param {object} params - { companyId(必填), folderId, keyword, pageNum, pageSize }\r\n */\n\n\nfunction getFileList(params) {\n return (0, _request.default)({\n url: '/api/v1/admin/company-kb/files',\n method: 'get',\n params: params\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n/**\r\n * 文件详情\r\n * @param {number} id\r\n */\n\n\nfunction getFileDetail(id) {\n return (0, _request.default)({\n url: \"/api/v1/admin/company-kb/files/\".concat(id),\n method: 'get'\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n/**\r\n * 更新文件信息(标题、所属文件夹等)\r\n * @param {number} id\r\n * @param {object} data - { title, folderId }\r\n */\n\n\nfunction updateFile(id, data) {\n return (0, _request.default)({\n url: \"/api/v1/admin/company-kb/files/\".concat(id),\n method: 'put',\n data: data\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n/**\r\n * 删除文件\r\n * @param {number} id\r\n */\n\n\nfunction deleteFile(id) {\n return (0, _request.default)({\n url: \"/api/v1/admin/company-kb/files/\".concat(id),\n method: 'delete'\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n/**\r\n * 上传文件(multipart:file + folderId + companyId)\r\n * @param {FormData} formData - 需 append:file、folderId、companyId\r\n */\n\n\nfunction uploadFile(formData) {\n return (0, _request.default)({\n url: '/api/v1/admin/company-kb/files/upload',\n method: 'post',\n data: formData,\n timeout: 60000 // 上传大文件放宽超时\n\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n} // ==================== 索引操作 ====================\n\n/**\r\n * 索引文件\r\n * @param {number} id\r\n */\n\n\nfunction indexFile(id) {\n return (0, _request.default)({\n url: \"/api/v1/admin/company-kb/files/\".concat(id, \"/index\"),\n method: 'post'\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n/**\r\n * 重建索引\r\n * @param {number} id\r\n */\n\n\nfunction reindexFile(id) {\n return (0, _request.default)({\n url: \"/api/v1/admin/company-kb/files/\".concat(id, \"/reindex\"),\n method: 'post'\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n/**\r\n * 删除索引\r\n * @param {number} id\r\n */\n\n\nfunction deleteFileIndex(id) {\n return (0, _request.default)({\n url: \"/api/v1/admin/company-kb/files/\".concat(id, \"/index\"),\n method: 'delete'\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/api/knowledgeBase.js?");
|
|
15473
|
+
eval("\n\nvar _interopRequireDefault = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/interopRequireDefault */ \"./node_modules/@babel/runtime/helpers/interopRequireDefault.js\").default;\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.getCompanyList = getCompanyList;\nexports.getFolderList = getFolderList;\nexports.addFolder = addFolder;\nexports.updateFolder = updateFolder;\nexports.deleteFolder = deleteFolder;\nexports.getFileList = getFileList;\nexports.getFileDetail = getFileDetail;\nexports.updateFile = updateFile;\nexports.deleteFile = deleteFile;\nexports.deleteFiles = deleteFiles;\nexports.moveFiles = moveFiles;\nexports.uploadFile = uploadFile;\nexports.indexFile = indexFile;\nexports.reindexFile = reindexFile;\nexports.deleteFileIndex = deleteFileIndex;\n\nvar _request = _interopRequireDefault(__webpack_require__(/*! @lib/utils/request */ \"./src/lib/utils/request.js\"));\n\n/*\r\n * @Desc: 公司知识库 API(对接 /api/v1/admin/company-kb)\r\n * @Date: 2026-08-18\r\n */\n// ==================== 公司 ====================\n\n/**\r\n * 获取公司列表(从文件夹/文件表去重提取)\r\n */\nfunction getCompanyList() {\n return (0, _request.default)({\n url: '/api/v1/admin/company-kb/companies',\n method: 'get'\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n} // ==================== 文件夹 ====================\n\n/**\r\n * 获取文件夹列表(平铺结构,带 parentId,前端组装树)\r\n * @param {string} companyId - 公司ID(必填)\r\n */\n\n\nfunction getFolderList(companyId) {\n return (0, _request.default)({\n url: '/api/v1/admin/company-kb/folders',\n method: 'get',\n params: {\n companyId: companyId\n }\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n/**\r\n * 新增文件夹\r\n * @param {object} data - { companyId, parentId, name, sortOrder, status }\r\n */\n\n\nfunction addFolder(data) {\n return (0, _request.default)({\n url: '/api/v1/admin/company-kb/folders',\n method: 'post',\n data: data\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n/**\r\n * 更新文件夹\r\n * @param {number} id\r\n * @param {object} data - { name, sortOrder }\r\n */\n\n\nfunction updateFolder(id, data) {\n return (0, _request.default)({\n url: \"/api/v1/admin/company-kb/folders/\".concat(id),\n method: 'put',\n data: data\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n/**\r\n * 删除文件夹(后端会校验子文件夹/文件,不允许时返回错误)\r\n * @param {number} id\r\n */\n\n\nfunction deleteFolder(id) {\n return (0, _request.default)({\n url: \"/api/v1/admin/company-kb/folders/\".concat(id),\n method: 'delete'\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n} // ==================== 文件 ====================\n\n/**\r\n * 文件列表(分页)\r\n * @param {object} params - { companyId(必填), folderId, keyword, pageNum, pageSize }\r\n */\n\n\nfunction getFileList(params) {\n return (0, _request.default)({\n url: '/api/v1/admin/company-kb/files',\n method: 'get',\n params: params\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n/**\r\n * 文件详情\r\n * @param {number} id\r\n */\n\n\nfunction getFileDetail(id) {\n return (0, _request.default)({\n url: \"/api/v1/admin/company-kb/files/\".concat(id),\n method: 'get'\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n/**\r\n * 更新文件信息(标题、所属文件夹等)\r\n * @param {number} id\r\n * @param {object} data - { title, folderId }\r\n */\n\n\nfunction updateFile(id, data) {\n return (0, _request.default)({\n url: \"/api/v1/admin/company-kb/files/\".concat(id),\n method: 'put',\n data: data\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n/**\r\n * 删除文件\r\n * @param {number} id\r\n */\n\n\nfunction deleteFile(id) {\n return (0, _request.default)({\n url: \"/api/v1/admin/company-kb/files/\".concat(id),\n method: 'delete'\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n/**\r\n * 批量删除文件\r\n * @param {number[]} ids\r\n */\n\n\nfunction deleteFiles(ids) {\n return (0, _request.default)({\n url: '/api/v1/admin/company-kb/files',\n method: 'delete',\n data: ids\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n/**\r\n * 批量移动文件到指定文件夹\r\n * @param {number[]} ids\r\n * @param {number} folderId\r\n */\n\n\nfunction moveFiles(ids, folderId) {\n return (0, _request.default)({\n url: '/api/v1/admin/company-kb/files/batch-move',\n method: 'put',\n data: {\n ids: ids,\n folderId: folderId\n }\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n/**\r\n * 上传文件(multipart:file + folderId + companyId)\r\n * @param {FormData} formData - 需 append:file、folderId、companyId\r\n */\n\n\nfunction uploadFile(formData) {\n return (0, _request.default)({\n url: '/api/v1/admin/company-kb/files/upload',\n method: 'post',\n data: formData,\n timeout: 60000 // 上传大文件放宽超时\n\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n} // ==================== 索引操作 ====================\n\n/**\r\n * 索引文件\r\n * @param {number} id\r\n */\n\n\nfunction indexFile(id) {\n return (0, _request.default)({\n url: \"/api/v1/admin/company-kb/files/\".concat(id, \"/index\"),\n method: 'post'\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n/**\r\n * 重建索引\r\n * @param {number} id\r\n */\n\n\nfunction reindexFile(id) {\n return (0, _request.default)({\n url: \"/api/v1/admin/company-kb/files/\".concat(id, \"/reindex\"),\n method: 'post'\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n/**\r\n * 删除索引\r\n * @param {number} id\r\n */\n\n\nfunction deleteFileIndex(id) {\n return (0, _request.default)({\n url: \"/api/v1/admin/company-kb/files/\".concat(id, \"/index\"),\n method: 'delete'\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/knowledgeBase/api/knowledgeBase.js?");
|
|
15440
15474
|
|
|
15441
15475
|
/***/ }),
|
|
15442
15476
|
|
|
@@ -15759,7 +15793,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n
|
|
|
15759
15793
|
/***/ (function(module, exports, __webpack_require__) {
|
|
15760
15794
|
|
|
15761
15795
|
"use strict";
|
|
15762
|
-
eval("\n\nvar _interopRequireWildcard = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/interopRequireWildcard */ \"./node_modules/@babel/runtime/helpers/interopRequireWildcard.js\").default;\n\nvar _interopRequireDefault = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/interopRequireDefault */ \"./node_modules/@babel/runtime/helpers/interopRequireDefault.js\").default;\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.setupValidator = setupValidator;\n\nvar _typeof2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/typeof */ \"./node_modules/@babel/runtime/helpers/typeof.js\"));\n\nvar _asyncToGenerator2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/asyncToGenerator */ \"./node_modules/@babel/runtime/helpers/asyncToGenerator.js\"));\n\nvar _message = __webpack_require__(/*! @lib/utils/message.js */ \"./src/lib/utils/message.js\");\n\nvar _request = _interopRequireWildcard(__webpack_require__(/*! @lib/utils/request.js */ \"./src/lib/utils/request.js\"));\n\n/**\r\n * TokUI Validator Bridge — 把 Validator 引擎绑定到 TokUI 事件系统\r\n *\r\n * 使用方式:\r\n * import V from './validators';\r\n * import { setupValidator } from './tokui-validator-bridge';\r\n * setupValidator(tokuiInstance, V, {\r\n * parseResult: (json) => json.valid ? '' : json.message,\r\n * buildOptions: (json) => json.data.map(i => ({ v: i.code, tx: i.name }))\r\n * });\r\n *\r\n * DSL 侧:\r\n * [input id:email l:邮箱 data-rule:\"required|email|remote:/api/validate/check-email\" data-trigger:input,blur]\r\n * [select id:city l:城市 data-load:\"/api/cities\"]\r\n * [select id:district l:区县 data-load:\"/api/districts?city=${city}\"]\r\n * [btn tx:提交 sub:formSubmit]\r\n *\r\n * 数据加载:DSL 中声明 data-load 属性即可,无需 JS 配置。\r\n * 校验配置:parseResult 控制远程校验的响应解析(可选,默认 json.valid)。\r\n * 加载映射:buildOptions 控制 JSON→options 格式映射(可选,默认从 data.code/name 或 data.id/label)。\r\n *\r\n * 本项目适配说明(zj-plugin-intelligent):\r\n * - 消息提示使用项目内置 showMessage(@lib/utils/message.js),不依赖 element-ui\r\n * - 所有网络请求统一走 @lib/utils/request.js,复用其 AK/SK 签名、token 注入与 code 校验\r\n */\n// 统一走 request.js(AK/SK 签名、token 注入、code 校验),不使用 http/index.js\n\n/**\r\n * 格式化时间 → YYYY-MM-dd HH:mm 字符串\r\n * 对齐参照项目 getTime 工具函数行为(workTimeApplyAdd.vue submitForm 中使用)\r\n * 输入:Date 对象、时间戳(数字/字符串)、Date 字符串\r\n */\nfunction formatToDateTimeStr(input) {\n var date;\n\n if (input instanceof Date) {\n date = input;\n } else if (typeof input === 'number' || typeof input === 'string' && /^\\d+$/.test(input.trim())) {\n date = new Date(Number(input));\n } else {\n date = new Date(input);\n }\n\n if (isNaN(date.getTime())) return String(input);\n var Y = date.getFullYear() + '-';\n var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-';\n var D = (date.getDate() < 10 ? '0' + date.getDate() : date.getDate()) + ' ';\n var h = date.getHours() < 10 ? '0' + date.getHours() + ':' : date.getHours() + ':';\n var m = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes();\n return Y + M + D + h + m;\n}\n\nfunction resolveForm(el) {\n // TokUI datepicker 调用 clk 处理器时只传 data 对象,element 为 undefined\n // 此时回退到 document 级别查找表单(单表单场景兜底,多表单场景下 element 始终存在)\n if (!el) return document.querySelector('[data-form-id]') || document.querySelector('form');\n var form = el.closest('[data-form-id]') || el.closest('form');\n if (form) return form; // ★ 多表单隔离:优先在元素所属的 TokUI 容器(.tokui-stream)内查找表单,\n // 避免 document 级查询返回第一个表单导致跨消息串扰\n\n var root = el.closest('.tokui-stream');\n\n if (root) {\n var scoped = root.querySelector('[data-form-id]') || root.querySelector('form');\n if (scoped) return scoped;\n }\n\n var formId = el.getAttribute('form') || el.getAttribute('data-tokui-form');\n\n if (formId) {\n // 多表单隔离:优先在元素所属 .tokui-stream 内查找,避免跨消息串扰\n var scopeRoot = el.closest('.tokui-stream');\n var byId = null;\n\n if (scopeRoot) {\n var allWithId = scopeRoot.querySelectorAll('[id]');\n\n for (var fi = 0; fi < allWithId.length; fi++) {\n if (allWithId[fi].id === formId) {\n byId = allWithId[fi];\n break;\n }\n }\n } else {\n byId = document.getElementById(formId);\n }\n\n if (byId) return byId;\n } // 回退:按按钮的 form 属性引用查找\n\n\n var formAttr = el.getAttribute && el.getAttribute('form');\n\n if (formAttr) {\n var scopeRoot2 = el.closest('.tokui-stream');\n var ref = null;\n\n if (scopeRoot2) {\n var allWithId2 = scopeRoot2.querySelectorAll('[id]');\n\n for (var fj = 0; fj < allWithId2.length; fj++) {\n if (allWithId2[fj].id === formAttr) {\n ref = allWithId2[fj];\n break;\n }\n }\n } else {\n ref = document.getElementById(formAttr);\n }\n\n if (ref) return ref;\n }\n\n return null;\n}\n\nfunction collectFormData(el) {\n var form = resolveForm(el);\n if (!form) return {};\n var data = {}; // 优先从 form 子树收集,再从更广范围补充(TokUI 可能将部分字段渲染在 form 外部)\n\n var scopes = [form]; // 向上找 TokUI 容器作为补充搜索范围(多表单隔离:不 fallback 到 document)\n\n var parentScope = form.closest('.tokui-stream') || form.closest('[data-tokui-tag]') || form;\n\n if (parentScope !== form) {\n scopes.push(parentScope);\n }\n\n scopes.forEach(function (scope) {\n scope.querySelectorAll('[id], [name]').forEach(function (f) {\n var key = f.id || f.name;\n if (!key) return; // 跳过被条件显示隐藏的元素(如 data-visible-when 隐藏的 datepicker/datetimepicker)\n\n if (f.closest('.tokui-conditional-hidden')) return; // 跳过非表单元素(如 div/span 上的 id)除非它们有 value 属性\n\n if (f.tagName !== 'INPUT' && f.tagName !== 'SELECT' && f.tagName !== 'TEXTAREA') {\n // tokui-field 包装层等非表单元素:尝试找内部表单控件\n var inner = f.querySelector && f.querySelector('input,select,textarea');\n if (!inner) return;\n f = inner;\n if (!f.id && !f.name) return;\n }\n\n var k = f.id || f.name;\n var newVal;\n\n if (f.type === 'checkbox' || f.type === 'radio') {\n if (f.checked) newVal = f.value;else return;\n } else if (f.tagName === 'SELECT' && f.multiple) {\n newVal = Array.from(f.selectedOptions).map(function (o) {\n return o.value;\n });\n } else {\n newVal = f.value;\n } // 防止同名元素空值覆盖已收集的非空值(如 readonly datepicker input 的值被另一个空 input 覆盖)\n\n\n if (data[k] !== undefined && data[k] !== '' && (newVal === '' || newVal === undefined)) return;\n data[k] = newVal;\n\n if (k === 'entryTime') {}\n });\n });\n return data;\n} // ============ 防抖 ============\n\n\nvar debounceTimers = {};\n\nfunction debounce(key, fn, ms) {\n if (debounceTimers[key]) clearTimeout(debounceTimers[key]);\n debounceTimers[key] = setTimeout(function () {\n delete debounceTimers[key];\n fn();\n }, ms);\n}\n\nfunction cancelDebounce(key) {\n if (debounceTimers[key]) {\n clearTimeout(debounceTimers[key]);\n delete debounceTimers[key];\n }\n}\n\nvar DEBOUNCED_EVENTS = ['input', 'keyup'];\nvar TRIGGER_EVENTS = ['blur', 'focusout', 'input', 'change', 'keyup']; // ★ 从元素向上查找所属 TokUI 表单作用域(.tokui-stream / data-tokui-scope)。\n// 仅做 DOM 级向上查找,绝不回退到任何闭包捕获的 container,避免多表单间串扰。\n\nfunction findNearestTokuiScope(el) {\n if (el && el.closest) {\n return el.closest('[data-tokui-scope]') || el.closest('.tokui-stream') || null;\n }\n\n return null;\n}\n\nfunction setupValidator(tokuiInstance, V) {\n var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};\n var _options$container = options.container,\n container = _options$container === void 0 ? document : _options$container,\n _options$debounceMs = options.debounceMs,\n debounceMs = _options$debounceMs === void 0 ? 400 : _options$debounceMs,\n onFormSuccess = options.onFormSuccess,\n _options$scopeKey = options.scopeKey,\n scopeKey = _options$scopeKey === void 0 ? '' : _options$scopeKey; // 成功回调挂在容器 DOM 上:多表单会话中各消息容器各自持有,handler 按元素归属向上查找\n\n if (container && container !== document) {\n container._onFormSuccess = onFormSuccess; // 用 msgKey 标识当前表单容器,作为 DOM 查询的唯一作用域,实现多表单隔离\n\n if (scopeKey) container.setAttribute('data-tokui-scope', scopeKey);\n } // 作用域解析:优先从元素向上查找所属表单容器,回退到本实例的 container。\n // 多表单场景下所有跨表单的 document 级查询都必须通过该函数收敛到当前消息容器。\n\n\n function resolveScope(el) {\n if (el && el.closest) {\n var scoped = el.closest('[data-tokui-scope]') || el.closest('.tokui-stream');\n if (scoped) return scoped;\n }\n\n return container && container !== document ? container : document;\n } // 在指定作用域内按 id 精确查找元素(避免 document.getElementById 跨表单串扰)\n\n\n function queryById(id, scope) {\n var root = scope || (container && container.querySelectorAll ? container : document);\n if (!root || !root.querySelectorAll || !id) return null;\n var all = root.querySelectorAll('[id]');\n\n for (var i = 0; i < all.length; i++) {\n if (all[i].id === id) return all[i];\n }\n\n return null;\n } // ============ clk handler 本地注册表 ============\n // 拦截 window.TokUI.registerHandler,将处理器函数存入本地 _clkHandlers,\n // 供 change 事件委托直接调用(绕过 TokUI 的 click-only 限制)\n\n\n var _clkHandlers = window.TokUI._clkHandlers = window.TokUI._clkHandlers || {};\n\n if (!window.TokUI.__registerHandlerPatched) {\n window.TokUI.__registerHandlerPatched = true;\n var _origRegisterHandler = window.TokUI.registerHandler;\n\n window.TokUI.registerHandler = function (name, fn) {\n _clkHandlers[name] = fn;\n return _origRegisterHandler.call(window.TokUI, name, fn);\n };\n } // 安全反馈:挂到指定宿主元素(通常为当前表单),保证多表单会话中反馈不串消息\n\n\n function safeFeed(dsl, hostEl) {\n try {\n // ★ 找到合适的容器:优先 btngroup 所在的容器,确保 callout 在表单最底部\n var host = null;\n\n if (hostEl) {\n // 找 btngroup 容器(可能不在 hostEl 内部),作用域限定在当前表单容器,避免跨消息串扰\n var scope = resolveScope(hostEl);\n var submitBtn = scope && scope.querySelector ? scope.querySelector('[data-tokui-sub=\"formSubmit\"]') : null;\n\n if (submitBtn) {\n // 找 hostEl 和 submitBtn 的共同父级(LCA),callout 放在共同父级的末尾\n var p = hostEl.parentElement;\n\n while (p && p !== document.body) {\n if (p.contains(submitBtn)) break;\n p = p.parentElement;\n }\n\n host = p || hostEl;\n } else {\n host = hostEl;\n }\n }\n\n if (!host) {\n host = container && container !== document ? container : null;\n }\n\n if (!host) return;\n var Ctor = tokuiInstance && tokuiInstance.constructor;\n if (!Ctor) return; // ★ 清除旧的 feedback,每次重新创建确保位置正确\n\n var oldFb = host.querySelector('.tokui-validator-feedback');\n if (oldFb) oldFb.remove();\n var fb = document.createElement('div');\n fb.className = 'tokui-validator-feedback';\n host.appendChild(fb);\n new Ctor({\n container: fb\n }).render(dsl);\n } catch (e) {\n console.warn('[Validator] feedback render error:', e);\n }\n } // 从元素向上查找宿主容器注册的表单成功回调\n\n\n function findFormSuccessCb(el) {\n for (var n = el; n; n = n.parentElement) {\n if (n._onFormSuccess) return n._onFormSuccess;\n }\n\n return null;\n } // ============ 数据请求缓存(data-load 用) ============\n\n\n var CACHE_TTL = options.cacheTtl !== undefined ? options.cacheTtl : 300000;\n\n var _dataCache = new Map();\n\n function getCached(url) {\n var entry = _dataCache.get(url);\n\n if (!entry) return null;\n\n if (CACHE_TTL && Date.now() - entry.ts > CACHE_TTL) {\n _dataCache.delete(url);\n\n return null;\n }\n\n return entry.data;\n }\n\n function setCached(url, data) {\n _dataCache.set(url, {\n data: data,\n ts: Date.now()\n });\n }\n\n var clearCache = function clearCache(url) {\n if (url !== undefined) _dataCache.delete(url);else _dataCache.clear();\n }; // =================================================\n // ============ 注册 [repeat] 组件 ============\n // 渲染为不可见 <script>,原始内容存在 textContent 中\n // 桥接层读取 textContent 作为模板,替换 $field$ 后 feed 给 TokUI\n\n\n if (tokuiInstance && tokuiInstance.renderer) {\n tokuiInstance.renderer.register('repeat', function (node) {\n var el = document.createElement('script');\n el.type = 'text/tokui-template';\n el.style.display = 'none';\n if (node.content) el.textContent = node.content;\n el._slot = el;\n el._tokuiType = 'repeat';\n return el;\n }); // ============ 注册 [imageupload] 组件 ============\n // 补卡申请需要的图片上传组件\n\n tokuiInstance.renderer.register('imageupload', function (node) {\n var wrapper = document.createElement('div');\n wrapper.className = 'tokui-field tokui-imageupload';\n wrapper.id = node.id || ''; // 标签\n\n if (node.l) {\n var label = document.createElement('label');\n label.className = 'tokui-field__label';\n label.textContent = node.l;\n wrapper.appendChild(label);\n } // 上传区域\n\n\n var uploadArea = document.createElement('div');\n uploadArea.className = 'tokui-imageupload-area'; // 文件input(隐藏)\n\n var fileInput = document.createElement('input');\n fileInput.type = 'file';\n fileInput.id = node.id || '';\n fileInput.name = node.n || node.id || '';\n fileInput.accept = node['data-accept'] || 'image/*';\n fileInput.multiple = true;\n fileInput.style.display = 'none'; // 预览容器\n\n var previewContainer = document.createElement('div');\n previewContainer.className = 'tokui-imageupload-preview'; // 添加按钮\n\n var addBtn = document.createElement('div');\n addBtn.className = 'tokui-imageupload-add';\n addBtn.innerHTML = '<span class=\"tokui-imageupload-icon\">+</span><span class=\"tokui-imageupload-text\">上传图片</span>';\n addBtn.addEventListener('click', function () {\n fileInput.click();\n }); // 提示文字\n\n var tip = document.createElement('div');\n tip.className = 'tokui-imageupload-tip';\n tip.textContent = node['data-tip'] || '支持 jpg、png、gif 格式,单张不超过3MB'; // 存储已上传的图片URL列表\n\n var uploadedUrls = [];\n var maxCount = parseInt(node['data-max-count']) || 5;\n var maxSize = parseInt(node['data-max-size']) || 3; // MB\n // 文件选择处理\n\n fileInput.addEventListener('change', function (e) {\n var files = Array.from(e.target.files);\n\n if (uploadedUrls.length + files.length > maxCount) {\n (0, _message.showMessage)('最多只能上传' + maxCount + '张图片', 'warning');\n return;\n }\n\n files.forEach(function (file) {\n var validTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/jpg', 'image/bmp'];\n\n if (!validTypes.includes(file.type)) {\n (0, _message.showMessage)('图片格式不支持', 'error');\n return;\n }\n\n if (file.size / 1024 / 1024 > maxSize) {\n (0, _message.showMessage)('图片大小不能超过' + maxSize + 'MB', 'error');\n return;\n }\n\n var reader = new FileReader();\n\n reader.onload = function (event) {\n addPreviewItem(event.target.result, file);\n };\n\n reader.readAsDataURL(file);\n });\n fileInput.value = '';\n });\n\n function addPreviewItem(dataUrl, file) {\n var item = document.createElement('div');\n item.className = 'tokui-imageupload-item';\n var img = document.createElement('img');\n img.src = dataUrl;\n img.className = 'tokui-imageupload-img';\n var removeBtn = document.createElement('span');\n removeBtn.className = 'tokui-imageupload-remove';\n removeBtn.innerHTML = '×';\n removeBtn.addEventListener('click', function () {\n var index = uploadedUrls.indexOf(dataUrl);\n if (index > -1) uploadedUrls.splice(index, 1);\n item.remove();\n updateAddBtnVisibility();\n updateHiddenInput();\n });\n var loading = document.createElement('div');\n loading.className = 'tokui-imageupload-loading';\n loading.textContent = '上传中...';\n item.appendChild(img);\n item.appendChild(removeBtn);\n item.appendChild(loading);\n previewContainer.insertBefore(item, addBtn);\n uploadToServer(file, item, dataUrl);\n }\n\n function uploadToServer(file, itemEl, dataUrl) {\n var formData = new FormData();\n formData.append('file', file);\n var uploadUrl = node['data-upload-url'] || '/api/file/upload';\n fetch(uploadUrl, {\n method: 'POST',\n body: formData,\n headers: {\n 'Authorization': 'Bearer ' + ((0, _request.getAuth)().token || '')\n }\n }).then(function (response) {\n return response.json();\n }).then(function (json) {\n var loading = itemEl.querySelector('.tokui-imageupload-loading');\n if (loading) loading.remove();\n\n if (json.code === 200) {\n var _json$data, _json$data2;\n\n var url = ((_json$data = json.data) === null || _json$data === void 0 ? void 0 : _json$data.obsUrl) || ((_json$data2 = json.data) === null || _json$data2 === void 0 ? void 0 : _json$data2.url) || json.data;\n uploadedUrls.push(url);\n itemEl.setAttribute('data-url', url);\n updateHiddenInput();\n updateAddBtnVisibility();\n } else {\n (0, _message.showMessage)('上传失败:' + (json.msg || json.message), 'error');\n itemEl.remove();\n }\n }).catch(function (err) {\n var loading = itemEl.querySelector('.tokui-imageupload-loading');\n if (loading) loading.remove();\n (0, _message.showMessage)('上传失败:' + err.message, 'error');\n itemEl.remove();\n });\n }\n\n function updateHiddenInput() {\n var hiddenInput = wrapper.querySelector('input[type=\"hidden\"]');\n\n if (!hiddenInput) {\n hiddenInput = document.createElement('input');\n hiddenInput.type = 'hidden';\n hiddenInput.name = node.n || node.id || '';\n hiddenInput.id = (node.id || '') + '_urls';\n wrapper.appendChild(hiddenInput);\n }\n\n hiddenInput.value = JSON.stringify(uploadedUrls);\n }\n\n function updateAddBtnVisibility() {\n if (uploadedUrls.length >= maxCount) {\n addBtn.style.display = 'none';\n } else {\n addBtn.style.display = 'flex';\n }\n }\n\n if (node.v) {\n try {\n var initUrls = typeof node.v === 'string' ? JSON.parse(node.v) : node.v;\n\n if (Array.isArray(initUrls)) {\n initUrls.forEach(function (url) {\n var item = document.createElement('div');\n item.className = 'tokui-imageupload-item';\n item.setAttribute('data-url', url);\n var img = document.createElement('img');\n img.src = url;\n img.className = 'tokui-imageupload-img';\n var removeBtn = document.createElement('span');\n removeBtn.className = 'tokui-imageupload-remove';\n removeBtn.innerHTML = '×';\n removeBtn.addEventListener('click', function () {\n var index = uploadedUrls.indexOf(url);\n if (index > -1) uploadedUrls.splice(index, 1);\n item.remove();\n updateAddBtnVisibility();\n updateHiddenInput();\n });\n item.appendChild(img);\n item.appendChild(removeBtn);\n previewContainer.insertBefore(item, addBtn);\n uploadedUrls.push(url);\n });\n updateAddBtnVisibility();\n updateHiddenInput();\n }\n } catch (e) {}\n }\n\n previewContainer.appendChild(addBtn);\n uploadArea.appendChild(fileInput);\n uploadArea.appendChild(previewContainer);\n wrapper.appendChild(uploadArea);\n wrapper.appendChild(tip);\n return wrapper;\n });\n } // ============ 补卡申请:日历加载与时间联动 ============\n\n\n function setupPatchCardCalendar(formElement) {\n // ★ 表单隔离:传入的可能不是 form,先向上找到最近的 form/[data-form-id] 容器\n var formScope = formElement && (formElement.tagName === 'FORM' || formElement.hasAttribute('data-form-id')) ? formElement : formElement ? formElement.closest('form') || formElement.closest('[data-form-id]') || formElement : document; // 通过 data-id 查找补卡字段(TokUI 渲染后元素是 data-id 而非 id),\n // 优先在 formScope 内找,未找到则向上级父元素递归查找\n\n function findField(dataId, scope, maxDepth) {\n if (!scope || !scope.querySelectorAll) return null;\n maxDepth = maxDepth || 5;\n var el = scope.querySelector('[data-id=\"' + dataId + '\"]');\n if (el) return el; // 向上递归查找\n\n var parent = scope.parentElement;\n var depth = 0;\n\n while (parent && parent !== document && parent !== document.body && depth < maxDepth) {\n el = parent.querySelector('[data-id=\"' + dataId + '\"]');\n if (el) return el;\n parent = parent.parentElement;\n depth++;\n }\n\n return null;\n }\n\n var patchDateEl = findField('patchDate', formScope);\n var patchTimeEl = findField('patchTime', formScope);\n if (!patchDateEl || !patchTimeEl) return; // data-id 通常挂在 TokUI 字段包装层,联动时必须操作内部真实控件\n\n var patchDateInput = patchDateEl.matches && patchDateEl.matches('input') ? patchDateEl : patchDateEl.querySelector('.tokui-datepicker-input, input:not([type=\"hidden\"])');\n var patchTimeSelect = patchTimeEl.matches && patchTimeEl.matches('select') ? patchTimeEl : patchTimeEl.querySelector('select');\n if (!patchDateInput || !patchTimeSelect) return; // ★ 防止重复初始化\n\n if (patchDateEl._patchCalendarInited) return;\n patchDateEl._patchCalendarInited = true;\n var calendarList = [];\n var patchRuleTypes = [];\n var patchRuleDays = 0;\n var calendarLoaded = false;\n var pendingPatchTimeValue = patchTimeSelect.getAttribute('data-pending-patch-time') || patchTimeSelect.value || patchTimeSelect.getAttribute('data-default-value') || '';\n var initialPatchDate = patchDateInput.value || getPatchDate(pendingPatchTimeValue);\n\n if (!patchDateInput.value && initialPatchDate) {\n syncPatchDateValue(initialPatchDate);\n } // 加载当月和上月考勤日历数据(供 updateTimeOptions 联动使用,\n // 日期禁用由 datepicker 的 data-load-calendar / data-disabled-date-from 自行处理)\n\n\n function loadPatchCalendar() {\n return _loadPatchCalendar.apply(this, arguments);\n } // 按 TokUI datepicker 的真实日期节点标记可补卡日期\n\n\n function _loadPatchCalendar() {\n _loadPatchCalendar = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {\n var _options$user, _options$user2, _options$user3, _options$user4, companyNoEl, departmentNoEl, formCompanyNo, formDepartmentNo, auth, userNo, companyNo, calendarUrl, now, year, month, dateStr, res, lastMonth, lastYear, lastDateStr, res2, lastCalendar, latestPendingValue, pendingDate, pendingYearMonth, hasData, res3, extraCalendar;\n\n return regeneratorRuntime.wrap(function _callee$(_context) {\n while (1) {\n switch (_context.prev = _context.next) {\n case 0:\n console.log('[loadPatchCalendar] 开始加载补卡日历');\n _context.prev = 1;\n // ★ 优先从表单字段读取当前选中的公司和部门(支持切换后重新加载)\n companyNoEl = findField('companyNo', formScope);\n departmentNoEl = findField('departmentNo', formScope);\n formCompanyNo = companyNoEl ? companyNoEl.value || '' : '';\n formDepartmentNo = departmentNoEl ? departmentNoEl.value || '' : ''; // 回退到 options.user 或全局 getAuth()\n\n auth = (0, _request.getAuth)();\n userNo = ((_options$user = options.user) === null || _options$user === void 0 ? void 0 : _options$user.no) || ((_options$user2 = options.user) === null || _options$user2 === void 0 ? void 0 : _options$user2.id) || auth.userno || auth.user;\n companyNo = formCompanyNo || ((_options$user3 = options.user) === null || _options$user3 === void 0 ? void 0 : _options$user3.cno) || ((_options$user4 = options.user) === null || _options$user4 === void 0 ? void 0 : _options$user4.comNo) || auth.companyNo;\n console.log('[loadPatchCalendar] 用户信息 - userNo:', userNo, 'companyNo:', companyNo, '(来源: 表单字段=' + formCompanyNo + ')');\n\n if (!(!userNo || !companyNo)) {\n _context.next = 13;\n break;\n }\n\n console.warn('补卡日历:用户信息不完整');\n return _context.abrupt(\"return\");\n\n case 13:\n // 优先从 patchDate 的 data-load-calendar 属性取日历 API 地址,回退到 options.api\n calendarUrl = patchDateEl.getAttribute('data-load-calendar') || options.api && options.api.patchQueryMonthClockStatus || '/api/patchQueryMonthClockStatus';\n console.log('[loadPatchCalendar] API 地址:', calendarUrl);\n now = new Date();\n year = now.getFullYear();\n month = now.getMonth() + 1;\n dateStr = year + '-' + (month < 10 ? '0' + month : month);\n console.log('[loadPatchCalendar] 加载当月:', dateStr);\n _context.next = 22;\n return _request.default.post(calendarUrl, {\n clientType: 'web',\n userNo: userNo,\n companyNo: companyNo,\n dateStr: dateStr,\n inType: '1'\n });\n\n case 22:\n res = _context.sent;\n console.log('[loadPatchCalendar] 当月响应:', res);\n\n if (!(res.code === 200 && res.data)) {\n _context.next = 62;\n break;\n }\n\n patchRuleDays = res.data.patchRuleDays || 0;\n patchRuleTypes = res.data.patchRuleTypes || [];\n console.log('[loadPatchCalendar] patchRuleDays:', patchRuleDays);\n console.log('[loadPatchCalendar] patchRuleTypes:', patchRuleTypes);\n console.log('[loadPatchCalendar] 原始 calendarList:', res.data.calendarList);\n calendarList = (res.data.calendarList || []).filter(function (v) {\n return v.status !== 'normal' && v.status !== '';\n });\n console.log('[loadPatchCalendar] 过滤后 calendarList:', calendarList); // 加载上月数据\n\n lastMonth = month === 1 ? 12 : month - 1;\n lastYear = month === 1 ? year - 1 : year;\n lastDateStr = lastYear + '-' + (lastMonth < 10 ? '0' + lastMonth : lastMonth);\n console.log('[loadPatchCalendar] 加载上月:', lastDateStr);\n _context.next = 38;\n return _request.default.post(calendarUrl, {\n clientType: 'web',\n userNo: userNo,\n companyNo: companyNo,\n dateStr: lastDateStr,\n inType: '1'\n });\n\n case 38:\n res2 = _context.sent;\n console.log('[loadPatchCalendar] 上月响应:', res2);\n\n if (res2.code === 200 && res2.data) {\n lastCalendar = (res2.data.calendarList || []).filter(function (v) {\n return v.status !== 'normal' && v.status !== '';\n });\n console.log('[loadPatchCalendar] 上月过滤后 calendarList:', lastCalendar);\n calendarList = calendarList.concat(lastCalendar);\n\n if (res2.data.patchRuleTypes) {\n patchRuleTypes = res2.data.patchRuleTypes;\n }\n } // 如果有待反显的补卡日期,且不在已加载的月份中,额外加载该月份\n\n\n latestPendingValue = patchTimeSelect.getAttribute('data-pending-patch-time') || pendingPatchTimeValue;\n pendingDate = getPatchDate(patchDateInput.value || latestPendingValue);\n console.log('[loadPatchCalendar] 待反显日期:', pendingDate);\n\n if (!pendingDate) {\n _context.next = 55;\n break;\n }\n\n pendingYearMonth = pendingDate.substring(0, 7); // YYYY-MM\n\n hasData = calendarList.some(function (item) {\n return item.day.startsWith(pendingYearMonth);\n });\n console.log('[loadPatchCalendar] 待反显月份:', pendingYearMonth, '已有数据:', hasData);\n\n if (hasData) {\n _context.next = 55;\n break;\n }\n\n console.log('[补卡日历] 补卡日期所属月份未加载,额外加载:', pendingYearMonth);\n _context.next = 52;\n return _request.default.post(calendarUrl, {\n clientType: 'web',\n userNo: userNo,\n companyNo: companyNo,\n dateStr: pendingYearMonth,\n inType: '1'\n });\n\n case 52:\n res3 = _context.sent;\n console.log('[loadPatchCalendar] 额外月份响应:', res3);\n\n if (res3.code === 200 && res3.data) {\n extraCalendar = (res3.data.calendarList || []).filter(function (v) {\n return v.status !== 'normal' && v.status !== '';\n });\n console.log('[loadPatchCalendar] 额外月份过滤后 calendarList:', extraCalendar);\n calendarList = calendarList.concat(extraCalendar);\n }\n\n case 55:\n console.log('[loadPatchCalendar] 最终合并后 calendarList 总数:', calendarList.length);\n console.log('[loadPatchCalendar] 最终 calendarList:', calendarList);\n calendarLoaded = true;\n setupDatePickerRestrictions(); // 使用最新的 data-pending-patch-time 或初始值\n\n updateTimeOptions(patchDateInput.value || getPatchDate(latestPendingValue), latestPendingValue);\n _context.next = 63;\n break;\n\n case 62:\n console.warn('补卡日历加载失败:', res.msg);\n\n case 63:\n _context.next = 68;\n break;\n\n case 65:\n _context.prev = 65;\n _context.t0 = _context[\"catch\"](1);\n console.warn('补卡日历加载异常:', _context.t0.message, _context.t0);\n\n case 68:\n case \"end\":\n return _context.stop();\n }\n }\n }, _callee, null, [[1, 65]]);\n }));\n return _loadPatchCalendar.apply(this, arguments);\n }\n\n function setupDatePickerRestrictions() {\n var validDates = calendarList.filter(function (item) {\n return patchRuleTypes.includes(item.status);\n }).map(function (item) {\n return item.day;\n });\n var root = patchDateEl.closest('.tokui-datepicker') || patchDateEl;\n var observer = new MutationObserver(function () {\n observer.disconnect();\n var dropdown = root.querySelector('.tokui-datepicker-dropdown');\n\n if (dropdown && dropdown.style.display !== 'none') {\n var title = dropdown.querySelector('.tokui-datepicker-title');\n var match = title && title.textContent.match(/(\\d{4}).*?(\\d{1,2})/);\n\n if (match) {\n var prefix = match[1] + '-' + String(parseInt(match[2])).padStart(2, '0') + '-';\n dropdown.querySelectorAll('.tokui-datepicker-day[data-day]').forEach(function (dayEl) {\n var date = prefix + String(parseInt(dayEl.getAttribute('data-day'))).padStart(2, '0');\n var available = validDates.includes(date);\n dayEl.classList.toggle('patch-date-available', available);\n dayEl.classList.toggle('patch-date-disabled', !available);\n dayEl.style.color = available ? '#f56c6c' : '#c0c4cc';\n dayEl.style.pointerEvents = available ? '' : 'none';\n });\n }\n }\n\n observer.observe(root, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['style', 'class']\n });\n });\n observer.observe(root, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['style', 'class']\n });\n patchDateEl.addEventListener('click', function () {\n setTimeout(function () {\n observer.takeRecords();\n var dropdown = root.querySelector('.tokui-datepicker-dropdown');\n\n if (dropdown && dropdown.style.display !== 'none') {\n observer.disconnect();\n observer.observe(root, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['style', 'class']\n });\n }\n }, 0);\n });\n patchDateEl._patchDateObserver = observer;\n } // TokUI datepicker 选择日期时只更新可见 input/隐藏 input,不派发 change,\n // 因此同时监听 click,并延迟读取组件写入后的日期值。\n\n\n patchDateEl.addEventListener('click', function (event) {\n console.log('[patchDateEl click] 事件触发:', event.target);\n var day = event.target && event.target.closest ? event.target.closest('.tokui-datepicker-day:not(.tokui-datepicker-day--other)') : null;\n console.log('[patchDateEl click] 找到的 day 元素:', day);\n if (!day) return;\n setTimeout(function () {\n console.log('[patchDateEl click] 延迟后读取 patchDateInput.value:', patchDateInput.value);\n updateTimeOptions(patchDateInput.value, '');\n }, 50);\n });\n patchDateInput.addEventListener('change', function () {\n console.log('[patchDateInput change] 事件触发, value:', this.value);\n updateTimeOptions(this.value, '');\n }); // ★ 增强监听:监听 patchDateEl 包装器的所有 input 事件(覆盖手动输入场景)\n\n patchDateEl.addEventListener('input', function (event) {\n if (event.target === patchDateInput) {\n console.log('[patchDateInput input] 事件触发, value:', event.target.value); // 使用防抖避免频繁触发\n\n if (patchDateEl._inputDebounceTimer) clearTimeout(patchDateEl._inputDebounceTimer);\n patchDateEl._inputDebounceTimer = setTimeout(function () {\n updateTimeOptions(patchDateInput.value, '');\n }, 300);\n }\n }); // ★ 监听隐藏 input 的值变化(TokUI datepicker 会同步更新隐藏 input)\n\n var wrapper = patchDateInput.closest('.tokui-datepicker');\n\n if (wrapper) {\n var hiddenInput = wrapper.querySelector('input[type=\"hidden\"]');\n\n if (hiddenInput) {\n var observer = new MutationObserver(function (mutations) {\n mutations.forEach(function (mutation) {\n if (mutation.type === 'attributes' && mutation.attributeName === 'value') {\n var newValue = hiddenInput.value || hiddenInput.getAttribute('value');\n console.log('[patchDate hidden input] 值变化:', newValue);\n\n if (newValue && newValue !== patchDateInput.value) {\n patchDateInput.value = newValue;\n } // 延迟触发,确保 patchDateInput.value 已同步\n\n\n setTimeout(function () {\n updateTimeOptions(patchDateInput.value, '');\n }, 50);\n }\n });\n });\n observer.observe(hiddenInput, {\n attributes: true,\n attributeFilter: ['value']\n });\n patchDateEl._hiddenInputObserver = observer;\n }\n } // 日期选择后生成补卡时间选项;反显时优先恢复已有值,新增时默认第一项\n\n\n function updateTimeOptions(selectedDate, reflectedValue) {\n console.log('[updateTimeOptions] 开始执行 - selectedDate:', selectedDate, 'reflectedValue:', reflectedValue);\n\n if (!patchTimeSelect) {\n console.warn('[updateTimeOptions] patchTimeSelect 不存在');\n return;\n }\n\n var previousValue = reflectedValue || patchTimeSelect.value || '';\n var normalizedDate = getPatchDate(selectedDate);\n console.log('[updateTimeOptions] normalizedDate:', normalizedDate, 'previousValue:', previousValue);\n patchTimeSelect.innerHTML = '';\n\n if (!normalizedDate) {\n console.log('[updateTimeOptions] 日期为空,显示\"请先选择补卡日期\"');\n appendTimeOption('', '请先选择补卡日期');\n patchTimeSelect.disabled = true;\n return;\n }\n\n var dateStr = normalizedDate;\n var timeOptions = [];\n var expired = false;\n console.log('[updateTimeOptions] calendarList 总数:', calendarList.length);\n console.log('[updateTimeOptions] patchRuleTypes:', patchRuleTypes);\n console.log('[updateTimeOptions] 目标日期:', dateStr);\n calendarList.forEach(function (item) {\n console.log('[updateTimeOptions] 遍历 calendarList 项:', {\n day: item.day,\n status: item.status,\n patchTime: item.patchTime,\n patchRuleFlag: item.patchRuleFlag\n });\n\n if (item.day !== dateStr) {\n console.log('[updateTimeOptions] 日期不匹配,跳过');\n return;\n }\n\n if (!patchRuleTypes.includes(item.status)) {\n console.log('[updateTimeOptions] 状态不在允许范围,跳过. status:', item.status);\n return;\n }\n\n if (Number(item.patchRuleFlag) === 0) {\n console.log('[updateTimeOptions] patchRuleFlag=0,已过期');\n expired = true;\n return;\n }\n\n if (item.patchTime) {\n var value = normalizePatchTime(item.patchTime);\n console.log('[updateTimeOptions] 找到可用时间:', value);\n\n if (!timeOptions.some(function (opt) {\n return opt.value === value;\n })) {\n timeOptions.push({\n value: value,\n label: value.slice(-5)\n });\n }\n } else {\n console.warn('[updateTimeOptions] 该项没有 patchTime 字段');\n }\n });\n console.log('[updateTimeOptions] 生成的 timeOptions:', timeOptions);\n console.log('[updateTimeOptions] expired:', expired);\n\n if (expired) {\n console.log('[updateTimeOptions] 显示\"超过可补卡时间范围\"');\n appendTimeOption('', '超过可补卡时间范围');\n patchTimeSelect.disabled = true;\n return;\n } // 如果有反显值,但 timeOptions 为空(数据不一致或跨月反显),尝试手动添加该值\n\n\n var normalizedPrevious = normalizePatchTime(previousValue);\n console.log('[updateTimeOptions] normalizedPrevious:', normalizedPrevious);\n\n if (normalizedPrevious && timeOptions.length === 0) {\n var previousDate = normalizedPrevious.substring(0, 10);\n console.log('[updateTimeOptions] previousDate:', previousDate, 'dateStr:', dateStr);\n\n if (previousDate === dateStr) {\n console.log('[补卡反显] calendarList 中无可用时间,但存在反显值,手动添加:', normalizedPrevious);\n timeOptions.push({\n value: normalizedPrevious,\n label: normalizedPrevious.slice(-5)\n });\n }\n }\n\n if (!timeOptions.length) {\n console.warn('[updateTimeOptions] 最终 timeOptions 为空,显示\"该日期无可补卡时间\"');\n appendTimeOption('', '该日期无可补卡时间');\n patchTimeSelect.disabled = true; // ★ 清空待反显的值,因为该日期没有可补卡时间\n\n if (previousValue) {\n console.warn('[updateTimeOptions] AI 返回的默认值无效(该日期无可补卡时间),已清空');\n patchTimeSelect.removeAttribute('data-pending-patch-time');\n }\n\n return;\n }\n\n console.log('[updateTimeOptions] 成功生成时间选项,启用下拉框');\n patchTimeSelect.disabled = false;\n appendTimeOption('', '请选择时间');\n timeOptions.forEach(function (opt) {\n appendTimeOption(opt.value, opt.label);\n }); // ★ 智能选择逻辑:优先精确匹配,失败则模糊匹配时间段,最后智能默认选择\n\n var reflectedOption = timeOptions.find(function (opt) {\n return opt.value === normalizedPrevious;\n }); // 如果精确匹配失败,尝试时间段模糊匹配\n\n if (normalizedPrevious && !reflectedOption) {\n console.log('[updateTimeOptions] 精确匹配失败,尝试时间段模糊匹配');\n console.log('[updateTimeOptions] AI 期望值:', normalizedPrevious); // 从 AI 返回的时间中提取小时数\n\n var aiTimeMatch = normalizedPrevious.match(/(\\d{2}):(\\d{2})/);\n\n if (aiTimeMatch) {\n var aiHour = parseInt(aiTimeMatch[1]);\n console.log('[updateTimeOptions] AI 期望小时:', aiHour); // 根据小时数判断时间段(早上:6-11, 下午/晚上:12-23 统一为下班卡)\n\n var aiPeriod = aiHour >= 6 && aiHour < 12 ? 'morning' : 'offwork';\n console.log('[updateTimeOptions] AI 时间段:', aiPeriod, aiPeriod === 'morning' ? '(上班卡)' : '(下班卡)'); // 在可用时间中查找同时间段的选项\n\n reflectedOption = timeOptions.find(function (opt) {\n var optTimeMatch = opt.value.match(/(\\d{2}):(\\d{2})/);\n\n if (optTimeMatch) {\n var optHour = parseInt(optTimeMatch[1]);\n var optPeriod = optHour >= 6 && optHour < 12 ? 'morning' : 'offwork';\n return optPeriod === aiPeriod;\n }\n\n return false;\n });\n\n if (reflectedOption) {\n console.log('[updateTimeOptions] ✅ 时间段模糊匹配成功!');\n console.log('[updateTimeOptions] AI 期望: ' + normalizedPrevious + ' (' + (aiPeriod === 'morning' ? '上班卡' : '下班卡') + ')');\n console.log('[updateTimeOptions] 实际选中: ' + reflectedOption.value);\n }\n }\n } // 如果还没有选中值(AI 没返回或匹配失败),使用智能默认选择\n\n\n if (!reflectedOption) {\n console.log('[updateTimeOptions] 未找到匹配项,使用智能默认选择'); // ★ 智能默认:优先选择下班卡(12点之后的时间)\n\n var offworkOption = timeOptions.find(function (opt) {\n var optTimeMatch = opt.value.match(/(\\d{2}):(\\d{2})/);\n\n if (optTimeMatch) {\n var optHour = parseInt(optTimeMatch[1]);\n return optHour >= 12; // 12点及之后的视为下班卡\n }\n\n return false;\n });\n\n if (offworkOption) {\n reflectedOption = offworkOption;\n console.log('[updateTimeOptions] ✅ 智能默认选择:下班卡', offworkOption.value);\n } else {\n // 如果没有下班卡,选择第一个(上班卡)\n reflectedOption = timeOptions[0];\n console.log('[updateTimeOptions] ⚠️ 没有下班卡,选择上班卡', timeOptions[0].value);\n }\n }\n\n if (normalizedPrevious && reflectedOption && reflectedOption.value !== normalizedPrevious) {\n console.log('[updateTimeOptions] AI 期望值未精确匹配,但已通过智能匹配选中');\n console.log('[updateTimeOptions] AI 期望:', normalizedPrevious);\n console.log('[updateTimeOptions] 实际选中:', reflectedOption.value);\n }\n\n patchTimeSelect.value = reflectedOption.value;\n console.log('[updateTimeOptions] 最终选中值:', patchTimeSelect.value);\n console.log('[updateTimeOptions] 选择方式:', !previousValue ? '智能默认(无AI默认值)' : reflectedOption.value === normalizedPrevious ? '精确匹配' : '智能匹配(时间段或默认规则)');\n patchTimeSelect.setAttribute('data-default-value', patchTimeSelect.value); // ★ 清空待反显标记,避免后续重复使用\n\n if (patchTimeSelect.hasAttribute('data-pending-patch-time')) {\n patchTimeSelect.removeAttribute('data-pending-patch-time');\n console.log('[updateTimeOptions] 已清空 data-pending-patch-time 标记');\n }\n\n patchTimeSelect.dispatchEvent(new Event('change', {\n bubbles: true\n }));\n }\n\n function appendTimeOption(value, label) {\n var option = document.createElement('option');\n option.value = value;\n option.textContent = label;\n patchTimeSelect.appendChild(option);\n } // 编辑反显可能返回时间戳;统一转成接口选项使用的 YYYY-MM-DD HH:mm\n\n\n function normalizePatchTime(value) {\n console.log('[normalizePatchTime] 输入值:', value, '类型:', (0, _typeof2.default)(value));\n if (value === undefined || value === null || value === '') return '';\n var text = String(value).trim();\n console.log('[normalizePatchTime] 转字符串:', text);\n var match = text.match(/^(\\d{4}-\\d{2}-\\d{2})[ T](\\d{2}):(\\d{2})/);\n\n if (match) {\n var result = match[1] + ' ' + match[2] + ':' + match[3];\n console.log('[normalizePatchTime] 匹配到日期时间格式,返回:', result);\n return result;\n }\n\n if (!/^\\d{10,13}$/.test(text)) {\n console.log('[normalizePatchTime] 不是时间戳,直接返回:', text);\n return text;\n }\n\n var timestamp = Number(text);\n var date = new Date(text.length === 10 ? timestamp * 1000 : timestamp);\n\n if (isNaN(date.getTime())) {\n console.log('[normalizePatchTime] 时间戳无效,返回原值:', text);\n return text;\n }\n\n var formatted = formatDate(date) + ' ' + String(date.getHours()).padStart(2, '0') + ':' + String(date.getMinutes()).padStart(2, '0');\n console.log('[normalizePatchTime] 时间戳转换结果:', formatted);\n return formatted;\n }\n\n function getPatchDate(value) {\n console.log('[getPatchDate] 输入值:', value);\n var normalized = normalizePatchTime(value);\n var result = normalized ? normalized.slice(0, 10) : '';\n console.log('[getPatchDate] 返回日期:', result);\n return result;\n }\n\n function syncPatchDateValue(value) {\n if (!value) return;\n patchDateInput.value = value;\n var wrapper = patchDateInput.closest('.tokui-datepicker');\n var hiddenInput = wrapper && wrapper.querySelector('input[type=\"hidden\"]');\n if (hiddenInput) hiddenInput.value = value;\n }\n\n function formatDate(date) {\n if (!date) return '';\n var d = new Date(date);\n var year = d.getFullYear();\n var month = d.getMonth() + 1;\n var day = d.getDate();\n return year + '-' + (month < 10 ? '0' + month : month) + '-' + (day < 10 ? '0' + day : day);\n } // 提供给 applyFormData:编辑详情回填发生在日历加载之后时,重新生成选项并恢复补卡时间\n\n\n var updatePatchTimeOptions = function updatePatchTimeOptions(selectedDate, reflectedValue) {\n pendingPatchTimeValue = reflectedValue || '';\n patchTimeSelect.setAttribute('data-pending-patch-time', pendingPatchTimeValue);\n var patchDate = getPatchDate(selectedDate || pendingPatchTimeValue);\n if (patchDate && !patchDateInput.value) syncPatchDateValue(patchDate);\n\n if (calendarLoaded) {\n updateTimeOptions(patchDate, pendingPatchTimeValue);\n } else {\n // 日历未加载完成,等待加载完成后会自动使用 data-pending-patch-time 反显\n console.log('[补卡反显] 日历数据加载中,已记录待反显值:', pendingPatchTimeValue);\n }\n };\n\n patchTimeEl._updatePatchTimeOptions = updatePatchTimeOptions;\n patchTimeSelect._updatePatchTimeOptions = updatePatchTimeOptions; // ★ 监听公司和部门字段变化,切换后重新加载日历数据\n\n var companyNoEl = findField('companyNo', formScope);\n var departmentNoEl = findField('departmentNo', formScope);\n\n function handleCompanyOrDeptChange() {\n console.log('[补卡日历] 检测到公司/部门切换,重新加载日历数据'); // 清空已选日期和时间\n\n if (patchDateInput) patchDateInput.value = '';\n var wrapper = patchDateInput && patchDateInput.closest('.tokui-datepicker');\n var hiddenInput = wrapper && wrapper.querySelector('input[type=\"hidden\"]');\n if (hiddenInput) hiddenInput.value = '';\n patchTimeSelect.innerHTML = '';\n appendTimeOption('', '请先选择补卡日期');\n patchTimeSelect.disabled = true; // 重新加载日历\n\n calendarLoaded = false;\n loadPatchCalendar();\n }\n\n if (companyNoEl && !companyNoEl._patchCalendarChangeListenerBound) {\n companyNoEl._patchCalendarChangeListenerBound = true;\n companyNoEl.addEventListener('change', handleCompanyOrDeptChange);\n }\n\n if (departmentNoEl && !departmentNoEl._patchCalendarChangeListenerBound) {\n departmentNoEl._patchCalendarChangeListenerBound = true;\n departmentNoEl.addEventListener('change', handleCompanyOrDeptChange);\n } // 初始化加载日历数据\n\n\n loadPatchCalendar();\n } // ============ 注入通用后端校验处理器 ============\n\n\n var buildBody = options.buildBody || function (value, field, formData) {\n return {\n value: value,\n field: field,\n formData: formData\n };\n };\n\n var parseResult = options.parseResult || function (json) {\n return json.valid !== false ? '' : json.message || '校验失败';\n };\n\n V._onRemote = /*#__PURE__*/function () {\n var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee2(url, value, field, formData) {\n var _options$remoteTimeou, json;\n\n return regeneratorRuntime.wrap(function _callee2$(_context2) {\n while (1) {\n switch (_context2.prev = _context2.next) {\n case 0:\n _context2.prev = 0;\n _context2.next = 3;\n return _request.default.post(url, buildBody(value, field, formData), {\n timeout: (_options$remoteTimeou = options.remoteTimeout) !== null && _options$remoteTimeou !== void 0 ? _options$remoteTimeou : 8000\n });\n\n case 3:\n json = _context2.sent;\n return _context2.abrupt(\"return\", parseResult(json));\n\n case 7:\n _context2.prev = 7;\n _context2.t0 = _context2[\"catch\"](0);\n return _context2.abrupt(\"return\", '网络请求失败,请重试');\n\n case 10:\n case \"end\":\n return _context2.stop();\n }\n }\n }, _callee2, null, [[0, 7]]);\n }));\n\n return function (_x, _x2, _x3, _x4) {\n return _ref.apply(this, arguments);\n };\n }(); // ============ 执行单字段校验 ============\n // 定位字段元素:优先按 id,查不到时按 name 在指定作用域(默认本消息容器)内查找\n // (DSL 只写 n:name 不写 id: 时,TokUI 不会给控件生成 id)\n\n\n function findFieldEl(key, scope) {\n if (!key) return null;\n var root = scope || (container && container.querySelectorAll ? container : document); // ★ 优先在当前作用域内按 id 精确查找,再按 name 查找,\n // 避免 document.getElementById 在多表单同 id 时串扰到其他消息\n\n if (root && root.querySelectorAll) {\n var byIdInScope = queryById(key, root);\n if (byIdInScope) return byIdInScope;\n var byName = root.querySelector('[name=\"' + key + '\"]');\n if (byName) return byName;\n } // 兜底(仅当 root 为 document 即单表单场景时才做全局查找,多表单 scoped 容器内查不到直接返回 null)\n\n\n return root === document ? document.getElementById(key) : null;\n } // 读取字段值:radio/checkbox 组按共享 name 聚合勾选值\n // (radio 单个 input 的 value 是选项值而非选中状态,直接读会导致 required 误判通过)\n\n\n function readFieldValue(el, inner) {\n var t = (inner.type || '').toLowerCase();\n if (t !== 'radio' && t !== 'checkbox') return inner.value;\n var name = inner.name || inner.id || '';\n if (!name) return inner.checked ? inner.value : '';\n var scope = el && el.closest && el.closest('form') || (container && container.querySelector ? container : document);\n var checked = [];\n Array.prototype.forEach.call(scope.querySelectorAll('input[name=\"' + name + '\"]'), function (n) {\n if (n.checked) checked.push(n.value);\n });\n return t === 'radio' ? checked.length ? checked[0] : '' : checked;\n } // 与 Validator._setFieldState 行为一致,但直接操作元素(用于无 id 场景)\n\n\n function setFieldStateLocal(el, isError, msg) {\n var wrapper = el.closest('.tokui-field') || el.parentNode;\n if (!wrapper) return;\n wrapper.classList.remove('tokui-field--error', 'tokui-field--success');\n var oldHint = wrapper.querySelector('.tokui-field__hint');\n if (oldHint) oldHint.remove();\n\n if (isError) {\n wrapper.classList.add('tokui-field--error');\n\n if (msg) {\n var hint = document.createElement('div');\n hint.className = 'tokui-field__hint tokui-field__hint--error';\n hint.textContent = msg;\n wrapper.appendChild(hint);\n }\n } else {\n wrapper.classList.add('tokui-field--success');\n }\n } // 报错展示:统一使用 setFieldStateLocal 直接操作元素,避免 Validator._setFieldState\n // 内部的 document.getElementById 在多表单同 id 场景下串扰到其他消息的错误提示\n\n\n function showError(key, msg, scope) {\n var el = findFieldEl(key, scope);\n if (!el) return;\n setFieldStateLocal(el, true, msg);\n }\n\n function clearError(key, scope) {\n var el = findFieldEl(key, scope);\n if (!el) return;\n setFieldStateLocal(el, false, '');\n }\n\n function runFieldValidation(_x5) {\n return _runFieldValidation.apply(this, arguments);\n } // ============ 注册 clk 驱动的显式校验 ============\n // 注意:registerHandler 是全局单例,每条消息渲染都会调用 setupValidator。\n // 全局 handler 只注册一次,内部全部按被点元素归属路由(回调/反馈/遮罩),避免旧表单的\n // 操作错挂到最新渲染的消息上。\n\n\n function _runFieldValidation() {\n _runFieldValidation = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee21(el) {\n var ruleStr, inner, targetKey, formData, msg, scope;\n return regeneratorRuntime.wrap(function _callee21$(_context21) {\n while (1) {\n switch (_context21.prev = _context21.next) {\n case 0:\n ruleStr = el.getAttribute('data-rule');\n\n if (ruleStr) {\n _context21.next = 3;\n break;\n }\n\n return _context21.abrupt(\"return\");\n\n case 3:\n inner = el.matches && el.matches('input,select,textarea') ? el : el.querySelector('input,select,textarea') || el;\n targetKey = inner.id || inner.name || el.id || '';\n\n if (targetKey) {\n _context21.next = 7;\n break;\n }\n\n return _context21.abrupt(\"return\");\n\n case 7:\n formData = collectFormData(inner);\n _context21.next = 10;\n return V.validateField(readFieldValue(el, inner), ruleStr, formData, targetKey);\n\n case 10:\n msg = _context21.sent;\n scope = resolveForm(el);\n\n if (msg) {\n showError(targetKey, msg, scope);\n } else {\n clearError(targetKey, scope);\n }\n\n case 13:\n case \"end\":\n return _context21.stop();\n }\n }\n }, _callee21);\n }));\n return _runFieldValidation.apply(this, arguments);\n }\n\n if (!window.TokUI.__validatorHandlersBound) {\n window.TokUI.__validatorHandlersBound = true; // ============ 建立 _eventBus 桥接 ============\n // TokUI 的 datetimepicker/datepicker/timepicker 等组件在确认按钮中通过\n // window.TokUI._eventBus.getHandler(n.clk)(...)\n // 调用 clk 处理器。但 TokUI 源码中 _eventBus 从未被赋值,导致 handler 静默失败。\n // 这里用拦截式代理:registerHandler 被调用时自动同步到 _eventBus.getHandler() 可用。\n\n (function setupEventBus() {\n if (window.TokUI._eventBus) return; // 已由外部设置,不覆盖\n\n var handlerMap = {};\n var orig = window.TokUI.registerHandler;\n\n window.TokUI.registerHandler = function (name, fn) {\n handlerMap[name] = fn;\n return orig.call(this, name, fn);\n };\n\n window.TokUI._eventBus = {\n getHandler: function getHandler(name) {\n return handlerMap[name] || null;\n }\n };\n })();\n\n window.TokUI.registerHandler('doValidate', function (data, event, element) {\n runFieldValidation(element);\n }); // ============ 注册表单重置 ============\n // 支持 [btn tx:重置 sub:formReset] 或 [btn tx:重置 clk:doReset]\n // (核心原生 [btn ... reset] 属性由 patch.js 包装的 _doReset 处理)\n\n function resetFormState(form, element) {\n // ★ 收集要清空的范围:form 本身 + TokUI 流式容器(不扩展到 document,避免跨消息串扰)\n var scopes = [form]; // 向上找 TokUI 容器\n\n var stream = form.closest('.tokui-stream') || form.closest('[data-tokui-tag=\"card\"]');\n if (stream) scopes.push(stream);\n var cleared = 0,\n skipped = 0; // TokUI 自定义组件的 CSS 类名(这些组件的可见 input 带有 readonly,但应该被清空)\n\n var tokuiComponentSelector = '.tokui-datepicker,.tokui-datetimepicker,.tokui-timepicker,.tokui-cascader,.tokui-picker';\n scopes.forEach(function (scope) {\n scope.querySelectorAll('input,select,textarea').forEach(function (el) {\n // ★ 跳过禁用字段;跳过只读字段——但 TokUI 组件内的 readonly input(如 datepicker)需要清空\n var inTokuiComponent = el.closest(tokuiComponentSelector);\n\n if (el.disabled || el.readOnly && !inTokuiComponent) {\n skipped++;\n return;\n }\n\n var t = (el.type || '').toLowerCase();\n\n if (t === 'checkbox' || t === 'radio') {\n el.checked = false;\n } else if (el.tagName === 'SELECT') {\n el.selectedIndex = 0;\n el.dispatchEvent(new Event('change', {\n bubbles: true\n }));\n } else {\n el.value = '';\n }\n\n cleared++;\n }); // ★ 清除 TokUI 自定义组件的选中状态样式\n\n scope.querySelectorAll(tokuiComponentSelector).forEach(function (comp) {\n // picker: 移除选项选中样式和标签\n comp.querySelectorAll('.tokui-picker-option--selected').forEach(function (opt) {\n opt.classList.remove('tokui-picker-option--selected');\n });\n comp.querySelectorAll('.tokui-picker-tag').forEach(function (tag) {\n tag.remove();\n }); // cascader: 移除选中样式\n\n comp.querySelectorAll('.tokui-cascader-menu-item--selected').forEach(function (opt) {\n opt.classList.remove('tokui-cascader-menu-item--selected');\n });\n });\n }); // 清除校验状态(所有 scope)\n\n scopes.forEach(function (scope) {\n scope.querySelectorAll('.tokui-field--error, .tokui-field--success').forEach(function (w) {\n w.classList.remove('tokui-field--error', 'tokui-field--success');\n });\n scope.querySelectorAll('.tokui-field__hint').forEach(function (h) {\n h.remove();\n });\n }); // 清除反馈信息\n\n if (form._tvFeedback) form._tvFeedback.innerHTML = ''; // 清除关联的 hidden 辅助字段(限定在当前表单容器内,避免跨消息串扰)\n\n var resetScope = stream || form;\n var auxFields = ['_holidayType', '_leaveType', '_balanceMode', '_durationDay', 'duration'];\n auxFields.forEach(function (id) {\n var el2 = queryById(id, resetScope);\n if (el2 && el2.type === 'hidden') el2.value = '';\n }); // 刷新条件显示(限定在当前表单作用域,避免跨消息串扰)\n\n try {\n updateConditionalVisibility(resolveScope(form));\n } catch (e2) {} // 移除遮罩(如果已提交后被重置)\n\n\n var lca = form.parentElement;\n\n while (lca && lca !== document.body) {\n var overlay = lca.querySelector('.tokui-form-overlay');\n if (overlay) overlay.remove();\n lca._tvOverlayLocked = false;\n lca = lca.parentElement;\n }\n }\n\n window.TokUI.registerHandler('formReset', function (data, event, element) {\n var f = resolveForm(element);\n resetFormState(f, element);\n });\n window.TokUI.registerHandler('doReset', function (data, event, element) {\n var f = resolveForm(element);\n resetFormState(f, element);\n }); // ============ 注册提交校验 ============\n\n function notifyFormSuccess(form, data, resultText) {\n var cb = findFormSuccessCb(form);\n if (typeof cb !== 'function') return;\n\n try {\n cb({\n formId: form ? form.id : null,\n formData: data,\n resultText: resultText,\n success: true\n });\n } catch (e) {\n console.warn('[Validator] onFormSuccess callback error:', e);\n }\n } // 提交成功后锁定表单:遮罩覆盖整个表单区域(含表单+审批流程+按钮组),禁止一切操作\n\n\n function lockFormWithOverlay(form) {\n try {\n // ★ 打印祖先链辅助调试\n var chain = [];\n var walker = form;\n\n while (walker && walker !== document.body) {\n chain.push(walker.tagName + (walker.id ? '#' + walker.id : '') + (walker.className ? '.' + walker.className.split(' ').slice(0, 3).join('.') : ''));\n walker = walker.parentElement;\n } // ★ 新策略:找到 form + btngroup + 审批流程 的 LCA(最小共同祖先)\n // 收集关键节点(作用域限定在当前表单容器,避免跨消息串扰)\n\n\n var keyNodes = [form];\n var overlayScope = resolveScope(form);\n var submitBtn = overlayScope && overlayScope.querySelector ? overlayScope.querySelector('[data-tokui-sub=\"formSubmit\"]') : null;\n if (submitBtn) keyNodes.push(submitBtn);\n var approvalFlow = form && form.parentElement ? form.parentElement.querySelector('.tv-approval-flow') : null;\n\n if (!approvalFlow && submitBtn) {\n // 审批流程可能不在 form 附近,尝试从 btngroup 附近找\n var btnParent = submitBtn.parentElement;\n\n while (btnParent && btnParent !== document.body) {\n approvalFlow = btnParent.querySelector('.tv-approval-flow');\n if (approvalFlow) break;\n btnParent = btnParent.parentElement;\n }\n }\n\n if (approvalFlow) keyNodes.push(approvalFlow);\n\n if (form) {\n var durationEl2 = queryById('duration', overlayScope);\n if (durationEl2 && durationEl2 !== form) keyNodes.push(durationEl2);\n var balanceEl2 = queryById('staffBalance', overlayScope);\n if (balanceEl2) keyNodes.push(balanceEl2);\n } // 对每个关键节点,收集其所有祖先(从自身到 body)\n\n\n var ancestorSets = keyNodes.map(function (node) {\n var ancestors = [];\n var n = node;\n\n while (n && n !== document.body) {\n ancestors.push(n);\n n = n.parentElement;\n }\n\n return ancestors;\n }); // 找 LCA:从 form 祖先往外走,找到第一个包含所有关键节点的\n\n var target = null;\n\n if (ancestorSets.length > 0) {\n var formAncestors = ancestorSets[0];\n\n for (var i = 0; i < formAncestors.length; i++) {\n var candidate = formAncestors[i];\n var containsAll = true;\n\n for (var j = 1; j < ancestorSets.length; j++) {\n if (ancestorSets[j].indexOf(candidate) === -1) {\n containsAll = false;\n break;\n }\n }\n\n if (containsAll) {\n target = candidate;\n break; // 找到最内层共同祖先\n }\n }\n } // 回退\n\n\n if (!target) {\n target = form && form.closest('.tokui-stream') || form && form.closest('[data-tokui-tag=\"card\"]') || (overlayScope && overlayScope !== document ? overlayScope : null) || form && form.parentElement || form;\n }\n\n if (!target || target._tvOverlayLocked || target === document.body) {\n return;\n }\n\n target._tvOverlayLocked = true; // ★ 确保 target 为 position: relative,使 absolute 子元素相对于它定位\n\n target.style.position = 'relative'; // 确保 target 有尺寸信息\n\n if (target.offsetHeight === 0) {\n target.style.minHeight = '200px';\n }\n\n var overlay = document.createElement('div');\n overlay.className = 'tokui-form-overlay';\n overlay.innerHTML = '<span class=\"tokui-form-overlay__text\"><svg t=\"1724150561000\" class=\"icon\" viewBox=\"0 0 1024 1024\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" p-id=\"2769\" width=\"16\" height=\"16\"><path d=\"M512 128c-106.037333 0-192 85.962667-192 192v128h-64a64 64 0 0 0-64 64v320a64 64 0 0 0 64 64h512a64 64 0 0 0 64-64v-320a64 64 0 0 0-64-64h-64V320c0-106.037333-85.962667-192-192-192z m-128 192a128 128 0 0 1 256 0v128H384V320z m-64 192h384v320H320V512z\" fill=\"currentColor\"></path></svg> 已提交</span>';\n target.appendChild(overlay); // 禁用容器内所有表单控件和按钮\n\n target.querySelectorAll('input,select,textarea,button').forEach(function (el) {\n el.disabled = true;\n });\n } catch (e) {\n console.warn('[Validator] overlay failed:', e.message);\n }\n } // 暴露 lockFormWithOverlay 到 bridge,供历史消息恢复遮罩复用(与提交后效果一致)。\n // 注意:此函数在 __validatorHandlersBound 守卫块内声明(块级作用域),\n // 只能在首次 setupValidator 时挂载,后续实例复用同一闭包(resolveScope 动态定位,作用域正确)。\n\n\n window.__tokuiValidatorBridge = window.__tokuiValidatorBridge || {};\n window.__tokuiValidatorBridge.lockFormWithOverlay = lockFormWithOverlay;\n window.TokUI.registerHandler('formSubmit', /*#__PURE__*/function () {\n var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee4(formData, event, element) {\n var _options$user6, _options$user7, _options$user8, _options$user9, _options$user10, _options$user11, _options$user12, _options$user13, _options$user14, _options$user15;\n\n var form, allFields, hasError, resolveField, tasks, submitUrl, allData, submitScope, auth, formType, typeAttrEl, meetingIdEl, meetingIdAttr, meetingTypeAttr, meetingTagEl, _durEl, balanceModeEl, balanceMode, _balEl, _balDom, rawBalance, leaveBalance, duration, typeEl_1, holidayName, holidayTypeEl, minLeaveUnit, durationDayEl, durationDay, stHalf, etHalf, halfLabels, stHalfEl, etHalfEl, stVal, etVal, sd, ed, stMs, stRaw, stDate, etMs, etRaw, etDate, applierName, companySel, departSel, tokuiParams, companyNoEl, departmentNoEl, _options$user5, tokuiParams_1, meetingToMs, meetingTokui, mUserNo, mHostName, mImAccount, mUserImg, mMeetingNo, mNoticeTime, mNoticeType, mMembers, mNewNotice, stStr, etStr, cfgDeptField, deptSelEl, outDeptVal, durationNum, _otListEl, approvalResult, method, resultJson, params, resultText;\n\n return regeneratorRuntime.wrap(function _callee4$(_context4) {\n while (1) {\n switch (_context4.prev = _context4.next) {\n case 0:\n resolveField = function _resolveField(el) {\n if (el.matches && el.matches('input,select,textarea')) return el;\n var inner = el.querySelector('input,select,textarea');\n return inner || el;\n };\n\n form = resolveForm(element);\n\n if (form) {\n _context4.next = 4;\n break;\n }\n\n return _context4.abrupt(\"return\");\n\n case 4:\n if (!form._tvSubmitting) {\n _context4.next = 8;\n break;\n }\n\n console.warn('[Validator] formSubmit: 正在提交中,跳过');\n (0, _message.showMessage)('正在提交中,请稍候...', 'warning');\n return _context4.abrupt(\"return\");\n\n case 8:\n form._tvSubmitting = true;\n allFields = form ? form.querySelectorAll('[data-rule]') : [];\n hasError = false;\n tasks = [];\n allFields.forEach(function (f) {\n tasks.push((0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee3() {\n var ruleStr, inner, targetKey, fd, fieldVal, msg;\n return regeneratorRuntime.wrap(function _callee3$(_context3) {\n while (1) {\n switch (_context3.prev = _context3.next) {\n case 0:\n ruleStr = f.getAttribute('data-rule');\n\n if (ruleStr) {\n _context3.next = 3;\n break;\n }\n\n return _context3.abrupt(\"return\");\n\n case 3:\n inner = resolveField(f);\n targetKey = inner.id || inner.name || f.id || '';\n\n if (targetKey) {\n _context3.next = 7;\n break;\n }\n\n return _context3.abrupt(\"return\");\n\n case 7:\n fd = collectFormData(f);\n fieldVal = readFieldValue(f, inner);\n _context3.next = 11;\n return V.validateField(fieldVal, ruleStr, fd, targetKey);\n\n case 11:\n msg = _context3.sent;\n\n if (msg) {\n showError(targetKey, msg, form);\n hasError = true;\n } else {\n clearError(targetKey, form);\n }\n\n case 13:\n case \"end\":\n return _context3.stop();\n }\n }\n }, _callee3);\n }))());\n });\n _context4.next = 15;\n return Promise.all(tasks);\n\n case 15:\n if (!hasError) {\n _context4.next = 19;\n break;\n }\n\n form._tvSubmitting = false;\n safeFeed('[callout t:error tt:校验未通过 tx:请修正红色标记的字段]', form);\n return _context4.abrupt(\"return\");\n\n case 19:\n // 校验通过 → 提交\n submitUrl = form ? form.getAttribute('data-url') : null;\n\n if (submitUrl) {\n _context4.next = 26;\n break;\n }\n\n // 无后端地址(纯聊天表单):校验通过即触发成功回调,由上层完成已提交标记和摘要回传\n form._tvSubmitting = false;\n safeFeed('[callout t:success tt:校验通过 tx:全部字段通过校验]', form);\n notifyFormSuccess(form, collectFormData(element), '校验通过');\n lockFormWithOverlay(form);\n return _context4.abrupt(\"return\");\n\n case 26:\n allData = collectFormData(element);\n submitScope = resolveScope(form) || form;\n auth = (0, _request.getAuth)(); // ============ 表单类型分发:不同表单走不同的校验/数据组装逻辑 ============\n // 通过 data-approval-type 区分表单类型(leave=请假, patch=补卡, 其他=无特殊处理)\n\n formType = (form.getAttribute('data-approval-type') || '').trim(); // 如果 data-approval-type 没有直接挂在 form 上,尝试在 form 内查找\n\n if (!formType) {\n typeAttrEl = form.querySelector('[data-approval-type]');\n if (typeAttrEl) formType = (typeAttrEl.getAttribute('data-approval-type') || '').trim();\n } // 非审批型表单(预约会议等直接提交、无审批流)无 data-approval-type,\n // 按 data-form-id(bookingForm)或 data-form-type(meeting)识别为预约会议。\n // ① form 自身属性 ② form 内元素属性(同 data-approval-type 的查找方式)\n\n\n if (!formType) {\n meetingIdEl = null;\n meetingIdAttr = (form.getAttribute('data-form-id') || '').trim();\n meetingTypeAttr = (form.getAttribute('data-form-type') || '').trim();\n\n if (!meetingIdAttr && !meetingTypeAttr) {\n meetingTagEl = form.querySelector('[data-form-id], [data-form-type]');\n\n if (meetingTagEl) {\n meetingIdAttr = (meetingTagEl.getAttribute('data-form-id') || '').trim();\n meetingTypeAttr = (meetingTagEl.getAttribute('data-form-type') || '').trim();\n }\n }\n\n if (meetingIdAttr === 'bookingForm' || meetingTypeAttr === 'meeting') {\n formType = 'meeting';\n } else if (!formType && form.querySelector('#meetingNO,#meetingNo,[id=\"meetingNO\"],[id=\"meetingNo\"],[name=\"meetingNO\"],[name=\"meetingNo\"]')) {\n // ② 兜底:表单含会议号字段(meetingNO/meetingNo)即视为预约会议\n formType = 'meeting';\n }\n }\n\n _context4.t0 = formType;\n _context4.next = _context4.t0 === 'leave' ? 35 : _context4.t0 === 'patch' ? 105 : _context4.t0 === 'meeting' ? 128 : _context4.t0 === 'out' ? 148 : _context4.t0 === 'overtime' ? 171 : _context4.t0 === 'overTimeApply' ? 171 : 209;\n break;\n\n case 35:\n // ============ 请假专用:余额校验 + 数据组装 ============\n // 调试:打印 duration 字段的实际状态\n _durEl = queryById('duration', submitScope);\n balanceModeEl = queryById('_balanceMode', submitScope);\n balanceMode = balanceModeEl ? parseInt(balanceModeEl.value, 10) : -1; // 余额数值读取:优先直接读 staffBalance 元素(可能是 readonly input 或展示型文本),\n // 其次取 formData(对齐源项目 leaveApply.vue 用 staffBalance 校验,而非不存在的 leaveBalance)。\n\n _balEl = queryById('staffBalance', submitScope);\n _balDom = '';\n\n if (_balEl) {\n _balDom = _balEl.tagName === 'INPUT' || _balEl.tagName === 'TEXTAREA' || _balEl.tagName === 'SELECT' ? _balEl.value || '' : (_balEl.textContent || '').trim();\n }\n\n rawBalance = _balDom !== '' && _balDom !== undefined ? _balDom : allData.staffBalance !== undefined && allData.staffBalance !== '' ? allData.staffBalance : allData.leaveBalance || '0';\n leaveBalance = parseFloat(rawBalance);\n duration = parseFloat(allData.duration || '0'); // balanceMode === 2:该假期对当前用户不适用\n\n if (!(balanceMode === 2)) {\n _context4.next = 49;\n break;\n }\n\n form._tvSubmitting = false;\n (0, _message.showMessage)('该假期类型对您不适用', 'warning');\n safeFeed('[callout t:warning tt:提交失败 tx:该假期类型对您不适用,请选择其他假期类型]', form);\n return _context4.abrupt(\"return\");\n\n case 49:\n if (!(balanceMode === 0 && duration > leaveBalance)) {\n _context4.next = 54;\n break;\n }\n\n form._tvSubmitting = false;\n (0, _message.showMessage)('假期余额不足,当前余额:' + leaveBalance + ',请假时长:' + duration, 'warning');\n safeFeed('[callout t:error tt:余额不足 tx:\"假期余额不足,当前余额 ' + leaveBalance + ',请假时长 ' + duration + '\"]', form);\n return _context4.abrupt(\"return\");\n\n case 54:\n if (!(!duration || isNaN(duration))) {\n _context4.next = 59;\n break;\n }\n\n form._tvSubmitting = false;\n (0, _message.showMessage)('请假时长不能为空,请先选择起止时间', 'warning');\n safeFeed('[callout t:warning tt:提交失败 tx:请假时长不能为空,请先选择起止时间]', form);\n return _context4.abrupt(\"return\");\n\n case 59:\n // 组装请假提交数据(对齐后端 tbManagementApplyLeave 表结构)\n // 获取选中假期类型的名称\n typeEl_1 = queryById('type', submitScope);\n holidayName = '';\n\n if (typeEl_1 && typeEl_1.selectedIndex >= 0 && typeEl_1.options[typeEl_1.selectedIndex]) {\n holidayName = typeEl_1.options[typeEl_1.selectedIndex].textContent || typeEl_1.options[typeEl_1.selectedIndex].innerText || '';\n }\n\n holidayTypeEl = queryById('_holidayType', submitScope);\n minLeaveUnit = holidayTypeEl && holidayTypeEl.value ? parseInt(holidayTypeEl.value, 10) : 0;\n durationDayEl = queryById('_durationDay', submitScope);\n durationDay = durationDayEl ? parseFloat(durationDayEl.value) : 0; // 半天模式:组装 startTimeStr / endTimeStr\n\n stHalf = 1, etHalf = 1;\n\n if (minLeaveUnit === 1) {\n halfLabels = {\n 1: '上午',\n 2: '下午'\n };\n stHalfEl = queryById('startTimeHalf', submitScope);\n etHalfEl = queryById('endTimeHalf', submitScope);\n stHalf = stHalfEl ? parseInt(stHalfEl.value, 10) : 1;\n etHalf = etHalfEl ? parseInt(etHalfEl.value, 10) : 1;\n stVal = allData.startTime || '';\n etVal = allData.endTime || '';\n\n if (stVal) {\n try {\n sd = new Date(stVal);\n allData.startTimeStr = sd.getFullYear() + '-' + String(sd.getMonth() + 1).padStart(2, '0') + '-' + String(sd.getDate()).padStart(2, '0') + ' ' + (halfLabels[stHalf] || '上午');\n } catch (e) {}\n }\n\n if (etVal) {\n try {\n ed = new Date(etVal);\n allData.endTimeStr = ed.getFullYear() + '-' + String(ed.getMonth() + 1).padStart(2, '0') + '-' + String(ed.getDate()).padStart(2, '0') + ' ' + (halfLabels[etHalf] || '下午');\n } catch (e) {}\n }\n } // 日期转时间戳(对齐 _calcLeaveDurationImpl:补全本地时间 + 上/下午调整)\n // ★ 纯日期 \"YYYY-MM-DD\" 需补全为本地时间 \"YYYY-MM-DDT00:00:00\",否则 new Date() 按 UTC 解析导致时区偏差\n // ★ 半天模式需按上/下午调整时间,与 startTimeStr/endTimeStr 保持一致\n\n\n if (allData.startTime) {\n try {\n if (typeof allData.startTime === 'number') {\n stMs = allData.startTime;\n } else {\n stRaw = String(allData.startTime);\n if (stRaw.length === 10) stRaw += 'T00:00:00';else stRaw = stRaw.replace(' ', 'T');\n stMs = new Date(stRaw).getTime();\n }\n\n stDate = new Date(stMs);\n\n if (minLeaveUnit === 1) {\n stDate.setHours(stHalf === 2 ? 12 : 0, 0, 0, 0);\n }\n\n allData.startTime = stDate.getTime();\n } catch (e) {}\n }\n\n if (allData.endTime) {\n try {\n if (typeof allData.endTime === 'number') {\n etMs = allData.endTime;\n } else {\n etRaw = String(allData.endTime);\n if (etRaw.length === 10) etRaw += 'T00:00:00';else etRaw = etRaw.replace(' ', 'T');\n etMs = new Date(etRaw).getTime();\n }\n\n etDate = new Date(etMs);\n\n if (minLeaveUnit === 1) {\n if (etHalf === 1) {\n etDate.setHours(12, 0, 0, 0);\n } else {\n etDate.setHours(23, 59, 59, 0);\n }\n } else if (minLeaveUnit === 2) {\n etDate.setHours(23, 59, 59, 999);\n }\n\n allData.endTime = etDate.getTime();\n } catch (e) {}\n }\n\n if (allData.entryTime) {\n try {\n allData.entryTime = new Date(allData.entryTime).getTime();\n } catch (e) {}\n } // 补充服务端需要的字段(对齐源项目 leaveApply.vue submitForm 的参数)\n\n\n allData.companyId = auth.companyNo || '';\n allData.userId = auth.userno || auth.user || '';\n allData.holidayNo = allData.type || '';\n allData.holidayName = holidayName;\n allData.minLeaveUnit = minLeaveUnit;\n allData.durationDay = durationDay; // ★ duration 转数字类型(表单收集为字符串 \"1.5\",后端需要数字 1.5)\n\n if (allData.duration !== undefined && allData.duration !== '') {\n allData.duration = parseFloat(allData.duration);\n }\n\n allData.saveFlag = 1; // 提交标记\n\n allData.createCompany = allData.companyNo || '';\n allData.departmentNo = allData.departNo || '1'; // 源项目: departNo == \"\" ? \"1\" : departNo\n // 源项目补充的空字段\n\n allData.isDelete = '';\n allData.processId = '';\n allData.status = '';\n allData.no = ''; // images:源项目提交 pictures 数组,详情页 JSON.parse(images) 要求合法 JSON\n // 如果为空则提交 \"[]\"(JSON 字符串),避免详情页 JSON.parse 报错\n\n if (!allData.images || allData.images === '' || allData.images === '[]') {\n allData.images = [];\n } // 源项目计算的附加字段\n\n\n applierName = allData.applierName || '';\n allData.businessName = applierName ? applierName + '的请假申请' : ''; // typeStr:假期类型名称\n\n allData.typeStr = holidayName; // companyName / departName:从 select 文本获取\n\n companySel = queryById('companyNo', submitScope);\n\n if (companySel && companySel.selectedIndex >= 0 && companySel.options[companySel.selectedIndex]) {\n allData.companyName = companySel.options[companySel.selectedIndex].textContent || '';\n }\n\n departSel = queryById('departNo', submitScope);\n\n if (departSel && departSel.selectedIndex >= 0 && departSel.options[departSel.selectedIndex]) {\n allData.departName = departSel.options[departSel.selectedIndex].textContent || '';\n } // operator:操作人(源项目: this.$store.state.user.realName || accountName)\n // 优先用 applierName(即 staffName),其次 tokuiParams 中的 realName/accountName\n\n\n tokuiParams = typeof window !== 'undefined' && window.__tokuiParams || {};\n allData.operator = allData.applierName || tokuiParams.loginRealName || tokuiParams.loginStaffName || tokuiParams.loginAccountName || auth.user || ''; // 清理隐藏辅助字段(不提交到后端)\n\n delete allData._holidayType;\n delete allData._leaveType;\n delete allData._balanceMode;\n delete allData._durationDay;\n delete allData.startTimeHalf;\n delete allData.endTimeHalf; // 源项目不提交以下字段(仅用于前端显示/校验)\n\n delete allData.leaveBalance;\n delete allData.staffBalance;\n delete allData.staffBalanceMode;\n return _context4.abrupt(\"break\", 210);\n\n case 105:\n // ============ 补卡申请专用:提交数据组装 ============\n console.log('[补卡提交] 检测到补卡表单,开始组装提交数据'); // 补卡时间处理:patchTime 选择框的值格式为 \"YYYY-MM-DD HH:mm\"\n // 需要转换为时间戳(毫秒)\n\n if (allData.patchTime) {\n try {\n allData.patchTime = new Date(allData.patchTime).getTime();\n } catch (e) {\n console.error('[补卡提交] 补卡时间转换失败:', e);\n }\n } // 图片数据处理:images 字段应该是数组,如果为空则传空数组\n\n\n if (!allData.images || allData.images === '' || allData.images === '[]') {\n allData.images = [];\n }\n\n if (typeof allData.images === 'string' && allData.images) {\n try {\n allData.images = JSON.parse(allData.images);\n } catch (e) {\n allData.images = [];\n }\n } // 补充公司、部门名称\n\n\n companyNoEl = queryById('companyNo', submitScope);\n\n if (companyNoEl && companyNoEl.selectedIndex >= 0 && companyNoEl.options[companyNoEl.selectedIndex]) {\n allData.companyName = companyNoEl.options[companyNoEl.selectedIndex].textContent || '';\n }\n\n departmentNoEl = queryById('departmentNo', submitScope);\n\n if (departmentNoEl && departmentNoEl.selectedIndex >= 0 && departmentNoEl.options[departmentNoEl.selectedIndex]) {\n allData.departName = departmentNoEl.options[departmentNoEl.selectedIndex].textContent || '';\n } // 申请人姓名\n\n\n if (!allData.applierName) {\n tokuiParams_1 = typeof window !== 'undefined' && window.__tokuiParams || {};\n allData.applierName = ((_options$user5 = options.user) === null || _options$user5 === void 0 ? void 0 : _options$user5.staffName) || tokuiParams_1.loginStaffName || auth.staffName || '';\n } // 补充服务端需要的字段\n\n\n allData.companyId = ((_options$user6 = options.user) === null || _options$user6 === void 0 ? void 0 : _options$user6.cno) || ((_options$user7 = options.user) === null || _options$user7 === void 0 ? void 0 : _options$user7.comNo) || auth.companyNo || '';\n allData.userNo = ((_options$user8 = options.user) === null || _options$user8 === void 0 ? void 0 : _options$user8.no) || ((_options$user9 = options.user) === null || _options$user9 === void 0 ? void 0 : _options$user9.id) || auth.userno || auth.user || '';\n allData.userId = allData.userNo;\n allData.empNo = '';\n allData.userName = '';\n allData.businessName = '';\n allData.isDelete = '';\n allData.processId = '';\n allData.status = '';\n allData.no = ''; // saveFlag:1=提交,0=暂存\n\n if (allData.saveFlag === undefined || allData.saveFlag === '') {\n allData.saveFlag = 1;\n }\n\n allData.createCompany = allData.companyNo || '';\n console.log('[补卡提交] 组装完成的提交数据:', allData);\n return _context4.abrupt(\"break\", 210);\n\n case 128:\n // ============ 预约会议专用:提交数据组装(对齐 sgb-im meeting/submitV1) ============\n // 表单字段:name(会议主题) / meetingNO(会议号) / startTime / endTime\n // 其余字段(hostNo/hostName/hostImAccount/members 等)从登录态补齐,可被表单隐藏字段覆盖\n console.log('[会议提交] 检测到预约会议表单,开始组装 submitV1 参数'); // 时间 → 时间戳(由 \"YYYY-MM-DD HH:mm\" 转为毫秒;已是数字则原样保留)\n\n meetingToMs = function meetingToMs(v) {\n if (v === undefined || v === null || v === '') return v;\n if (typeof v === 'number') return v;\n\n try {\n return new Date(String(v).replace(' ', 'T')).getTime();\n } catch (e) {\n return v;\n }\n };\n\n if (allData.startTime !== undefined) allData.startTime = meetingToMs(allData.startTime);\n if (allData.endTime !== undefined) allData.endTime = meetingToMs(allData.endTime); // 登录态用户信息(与请假/补卡同源约定,缺省可被表单字段覆盖)\n\n meetingTokui = typeof window !== 'undefined' && window.__tokuiParams || {};\n mUserNo = allData.userNo || allData.hostNo || ((_options$user10 = options.user) === null || _options$user10 === void 0 ? void 0 : _options$user10.no) || ((_options$user11 = options.user) === null || _options$user11 === void 0 ? void 0 : _options$user11.id) || auth.userno || auth.user || '';\n mHostName = allData.hostName || ((_options$user12 = options.user) === null || _options$user12 === void 0 ? void 0 : _options$user12.staffName) || meetingTokui.loginStaffName || meetingTokui.loginRealName || auth.staffName || '';\n mImAccount = allData.hostImAccount || ((_options$user13 = options.user) === null || _options$user13 === void 0 ? void 0 : _options$user13.account) || meetingTokui.loginAccountName || auth.user || '';\n mUserImg = allData.userImg || ((_options$user14 = options.user) === null || _options$user14 === void 0 ? void 0 : _options$user14.userUrl) || ((_options$user15 = options.user) === null || _options$user15 === void 0 ? void 0 : _options$user15.img) || ''; // 会议号:表单字段为 meetingNO,兼容 meetingNo\n\n mMeetingNo = allData.meetingNO !== undefined ? allData.meetingNO : allData.meetingNo; // 通知规则:默认提前 15 分钟提醒一次(FIFTEEN_MINUTE / APPLICATION)\n\n mNoticeTime = allData.noticeTime !== undefined ? allData.noticeTime : ['FIFTEEN_MINUTE'];\n\n if (typeof mNoticeTime === 'string') {\n try {\n mNoticeTime = JSON.parse(mNoticeTime);\n } catch (e) {\n mNoticeTime = allData.noticeTime;\n }\n }\n\n mNoticeType = allData.noticeType || 'APPLICATION'; // 参会人:默认当前用户为主持人(host),可被表单 members 覆盖\n\n mMembers = allData.members !== undefined ? allData.members : [{\n imAccount: mImAccount,\n name: mHostName,\n userImg: mUserImg,\n userNo: mUserNo,\n status: 'ACCEPT',\n host: 'YES',\n isSelect: true\n }];\n\n if (typeof mMembers === 'string') {\n try {\n mMembers = JSON.parse(mMembers);\n } catch (e) {}\n } // 扩展字段(如 newNoticeTime)需在重建 allData 前捕获\n\n\n mNewNotice = allData.newNoticeTime;\n\n if (typeof mNewNotice === 'string') {\n try {\n mNewNotice = JSON.parse(mNewNotice);\n } catch (e) {\n mNewNotice = allData.newNoticeTime;\n }\n } // 组装 submitV1 提交体(替换原表单字段集合)\n\n\n allData = {\n hostNo: allData.hostNo || mUserNo,\n topic: allData.topic || allData.name || '',\n matter: allData.matter || '',\n startTime: allData.startTime,\n endTime: allData.endTime,\n meetingNo: mMeetingNo || '',\n meetingUnit: allData.meetingUnit || '',\n meetingNoType: allData.meetingNoType || 'RANDOM',\n hostName: mHostName,\n hostImAccount: mImAccount,\n openWaitRoom: allData.openWaitRoom === undefined ? false : !!allData.openWaitRoom,\n sendInvite: allData.sendInvite === undefined ? true : !!allData.sendInvite,\n noticeTime: mNoticeTime,\n noticeType: mNoticeType,\n members: mMembers || []\n }; // 保留表单扩展字段(如 newNoticeTime)\n\n if (mNewNotice !== undefined) allData.newNoticeTime = mNewNotice;\n console.log('[会议提交] 组装完成的 submitV1 参数:', allData);\n\n case 148:\n // ============ 外出申请专用:提交数据组装 ============\n // 参照源项目 goOutApplyAdd.vue submitForm 方法的字段结构\n console.log('[外出提交] 检测到外出表单,开始组装提交数据'); // 起止时间转为时间戳(毫秒)— 对齐 kqgl: new Date(startTime).getTime()\n\n if (allData.startTime) {\n try {\n if (typeof allData.startTime === 'number') {// 已是时间戳无需转换\n } else {\n stStr = String(allData.startTime);\n if (stStr.length === 10) stStr += 'T00:00:00';else stStr = stStr.replace(' ', 'T');\n allData.startTime = new Date(stStr).getTime();\n }\n } catch (e) {\n console.error('[外出提交] startTime 转换失败:', e);\n }\n }\n\n if (allData.endTime) {\n try {\n if (typeof allData.endTime === 'number') {// 已是时间戳无需转换\n } else {\n etStr = String(allData.endTime);\n if (etStr.length === 10) etStr += 'T00:00:00';else etStr = etStr.replace(' ', 'T');\n allData.endTime = new Date(etStr).getTime();\n }\n } catch (e) {\n console.error('[外出提交] endTime 转换失败:', e);\n }\n } // 图片处理:images 字段应为数组 — 对齐 kqgl: this.form.images = this.pictures\n\n\n if (!allData.images || allData.images === '' || allData.images === '[]') {\n allData.images = [];\n }\n\n if (typeof allData.images === 'string' && allData.images) {\n try {\n allData.images = JSON.parse(allData.images);\n } catch (e) {\n allData.images = [];\n }\n } // 补充服务端需要的字段(对齐 kqgl goOutApplyAdd.vue submitForm)\n\n\n tokuiParams = typeof window !== 'undefined' && window.__tokuiParams || {};\n allData.createUserNo = tokuiParams.loginUserNo || auth.userno || auth.user || '';\n allData.createCompanyNo = tokuiParams.loginCompanyNo || auth.companyNo || ''; // ★ 外出申请部门字段:\n // 部门 select 实际 id 可由 DSL 通过 data-apply-dept-field / data-dept-field 显式配置,\n // 未配置时按 departmentNo → applyDeptId 候选自动识别;\n // 有部门时同时提交 applyDeptId 与 departmentNo,无部门时仅 departmentNo 兜底 '1' 且不带 applyDeptId\n\n cfgDeptField = (form.getAttribute('data-apply-dept-field') || form.getAttribute('data-dept-field') || '').trim();\n deptSelEl = (cfgDeptField ? queryById(cfgDeptField, submitScope) : null) || queryById('departmentNo', submitScope) || queryById('applyDeptId', submitScope);\n outDeptVal = '';\n\n if (deptSelEl) {\n if (deptSelEl.tagName === 'SELECT' && deptSelEl.selectedIndex >= 0 && deptSelEl.options[deptSelEl.selectedIndex]) {\n outDeptVal = deptSelEl.options[deptSelEl.selectedIndex].getAttribute('value') || deptSelEl.options[deptSelEl.selectedIndex].value || '';\n } else {\n outDeptVal = deptSelEl.value || deptSelEl.getAttribute('value') || '';\n }\n }\n\n if (outDeptVal) {\n // 有部门:applyDeptId 与 departmentNo 均带部门值提交\n allData.applyDeptId = outDeptVal;\n allData.departmentNo = outDeptVal;\n } else {\n // 无部门:不带 applyDeptId,departmentNo 兜底 '1'\n delete allData.applyDeptId;\n allData.departmentNo = allData.departmentNo || '1';\n } // companyNo(select 的 value)只在 DSL 内部用,kqgl 提交的是 companyId,不提交 companyNo\n // ★ 先保存 companyId 再删除 companyNo,避免 companyNo 丢失后退化为 auth.companyNo\n\n\n allData.companyId = allData.companyNo || auth.companyNo || '';\n delete allData.companyNo;\n allData.userNo = tokuiParams.loginUserNo || auth.userno || auth.user || ''; // saveFlag:1=提交,0=暂存 — 对齐 kqgl: submitForm('form', saveFlag)\n\n if (allData.saveFlag === undefined || allData.saveFlag === '') {\n allData.saveFlag = 1;\n } // 编辑模式保留 id,新增模式设为 null — 对齐 kqgl: id = $route.query.id || null\n\n\n if (!allData.id) {\n allData.id = null;\n } // ★ 补充 kqgl 必传字段\n // duration/burningTime:对齐 kqgl 将 duration 作为外出时长提交\n\n\n if (allData.duration !== undefined && allData.duration !== '') {\n durationNum = parseFloat(allData.duration);\n allData.duration = durationNum;\n allData.burningTime = durationNum; // 对齐 kqgl: burningTime 与 duration 相同\n } // reasons:对齐 kqgl 使用 reasons 字段名(当前 DSL 可能用 reason)\n\n\n if (allData.reason && !allData.reasons) {\n allData.reasons = allData.reason;\n }\n\n delete allData.reason;\n console.log('[外出提交] 组装完成的提交数据:', allData);\n return _context4.abrupt(\"break\", 210);\n\n case 171:\n // ============ 加班申请专用:提交数据组装 ============\n // 参照源项目 workTimeApplyAdd.vue submitForm 方法的字段结构(v1/overTimeApply/save)\n console.log('[加班提交] 检测到加班表单,开始组装提交数据'); // 时长 timeLength:从 duration 字段取值,转为数字\n\n if (allData.duration !== undefined && allData.duration !== '') {\n allData.timeLength = parseFloat(allData.duration);\n } else {\n allData.timeLength = 0;\n }\n\n delete allData.duration; // 起止时间转为 YYYY-MM-dd HH:mm 格式字符串(对齐 getTime 工具函数行为)\n\n if (allData.startTime) {\n try {\n allData.startTime = formatToDateTimeStr(allData.startTime);\n } catch (e) {\n console.error('[加班提交] startTime 转换失败:', e);\n }\n }\n\n if (allData.endTime) {\n try {\n allData.endTime = formatToDateTimeStr(allData.endTime);\n } catch (e) {\n console.error('[加班提交] endTime 转换失败:', e);\n }\n } // 时长不能为零或空\n\n\n if (!(!allData.timeLength || allData.timeLength <= 0)) {\n _context4.next = 181;\n break;\n }\n\n form._tvSubmitting = false;\n (0, _message.showMessage)('加班时长不能为空,请先选择起止时间', 'warning');\n safeFeed('[callout t:warning tt:提交失败 tx:加班时长不能为空,请先选择起止时间]', form);\n return _context4.abrupt(\"return\");\n\n case 181:\n // 图片处理 → 参照项目用 file 字段提交图片数组\n if (!allData.images || allData.images === '' || allData.images === '[]') {\n allData.images = [];\n }\n\n if (typeof allData.images === 'string' && allData.images) {\n try {\n allData.images = JSON.parse(allData.images);\n } catch (e) {\n allData.images = [];\n }\n }\n\n allData.file = allData.images; // 参照项目用 file 字段名\n\n delete allData.images; // 跨日加班明细:从 overtimeList 隐藏字段读取,转为对象数组\n\n _otListEl = queryById('overtimeList', submitScope);\n\n if (_otListEl && _otListEl.value) {\n try {\n allData.overTimeList = typeof _otListEl.value === 'string' ? JSON.parse(_otListEl.value) : _otListEl.value;\n } catch (e) {\n console.warn('[加班提交] overTimeList 解析失败:', e);\n allData.overTimeList = [];\n }\n } else {\n // 没有跨天明细时,兜底提交空数组\n allData.overTimeList = [];\n } // type 字段:1=单天加班,2=跨天加班(对齐参照项目 type 字段)\n\n\n allData.type = allData.overTimeList && allData.overTimeList.length > 0 ? 2 : 1; // 公司、部门编号与名称\n // 参照项目:companyNo(编号)、companyName(文本)、deptNo(编号)、deptName(文本)\n\n companyNoEl = queryById('companyNo', submitScope);\n\n if (companyNoEl) {\n if (companyNoEl.selectedIndex >= 0 && companyNoEl.options[companyNoEl.selectedIndex]) {\n allData.companyName = companyNoEl.options[companyNoEl.selectedIndex].textContent || '';\n }\n\n allData.companyNo = companyNoEl.value || allData.companyNo || '';\n }\n\n departmentNoEl = queryById('departmentNo', submitScope) || queryById('deptNo', submitScope);\n\n if (departmentNoEl) {\n if (departmentNoEl.selectedIndex >= 0 && departmentNoEl.options[departmentNoEl.selectedIndex]) {\n allData.deptName = departmentNoEl.options[departmentNoEl.selectedIndex].textContent || '';\n }\n\n allData.deptNo = departmentNoEl.value || allData.deptNo || '';\n allData.departmentNo = allData.deptNo || '1';\n } // 申请人信息(参照项目:applyUserName、applyUserNo、createCompanyNo)\n\n\n tokuiParams = typeof window !== 'undefined' && window.__tokuiParams || {};\n allData.createCompanyNo = tokuiParams.loginCompanyNo || auth.companyNo || '';\n allData.applyUserName = tokuiParams.loginStaffName || tokuiParams.loginRealName || auth.user || '';\n allData.applyUserNo = tokuiParams.loginUserno || tokuiParams.loginUserno || auth.userno || auth.user || '';\n allData.operator = allData.applyUserName;\n allData.businessName = allData.applyUserName ? allData.applyUserName + '的加班申请' : ''; // 审批集成字段(参照项目会通过审批流程生成 uniqueId)\n\n allData.userNo = auth.userno || auth.user || '';\n allData.userId = allData.userNo;\n allData.createCompany = allData.companyNo || '';\n allData.isDelete = '';\n allData.processId = '';\n allData.status = '';\n allData.no = ''; // 清理辅助字段(防止误提交)\n\n delete allData._durationDay; // saveFlag:1=提交,0=暂存\n\n if (allData.saveFlag === undefined || allData.saveFlag === '') {\n allData.saveFlag = 1;\n }\n\n console.log('[加班提交] 组装完成的提交数据:', allData);\n return _context4.abrupt(\"break\", 210);\n\n case 209:\n return _context4.abrupt(\"break\", 210);\n\n case 210:\n _context4.next = 212;\n return runApprovalFlow(form, allData);\n\n case 212:\n approvalResult = _context4.sent;\n\n if (!(approvalResult === false)) {\n _context4.next = 216;\n break;\n }\n\n form._tvSubmitting = false;\n return _context4.abrupt(\"return\");\n\n case 216:\n if (approvalResult && approvalResult.uniqueId) {\n allData.uniqueId = approvalResult.uniqueId;\n }\n\n safeFeed('[callout t:info tt:提交中 tx:正在提交数据...]', form);\n _context4.prev = 218;\n method = (form.getAttribute('data-method') || 'POST').toUpperCase();\n resultJson = null;\n\n if (!(method === 'GET')) {\n _context4.next = 229;\n break;\n }\n\n params = new URLSearchParams(allData).toString();\n if (params) submitUrl += (submitUrl.includes('?') ? '&' : '?') + params;\n _context4.next = 226;\n return _request.default.get(submitUrl);\n\n case 226:\n resultJson = _context4.sent;\n _context4.next = 238;\n break;\n\n case 229:\n if (!(method === 'DELETE')) {\n _context4.next = 235;\n break;\n }\n\n _context4.next = 232;\n return _request.default.delete(submitUrl, {\n data: allData\n });\n\n case 232:\n resultJson = _context4.sent;\n _context4.next = 238;\n break;\n\n case 235:\n _context4.next = 237;\n return _request.default.post(submitUrl, allData);\n\n case 237:\n resultJson = _context4.sent;\n\n case 238:\n resultText = resultJson ? resultJson.message || resultJson.msg || '提交成功' : '提交成功';\n (0, _message.showMessage)(resultText || '提交成功', 'success');\n safeFeed('[callout t:success tt:提交成功 tx:\"' + resultText.replace(/\"/g, '') + '\"]', form); // 回调通知上层:表单提交成功后的业务钩子\n\n notifyFormSuccess(form, allData, resultText); // 遮罩锁定整个 TokUI 容器,防止二次提交\n\n lockFormWithOverlay(form);\n _context4.next = 248;\n break;\n\n case 245:\n _context4.prev = 245;\n _context4.t1 = _context4[\"catch\"](218);\n // 错误提示已由 http 拦截器统一处理,这里仅做表单内反馈\n safeFeed('[callout t:error tt:提交失败 tx:\"' + (_context4.t1.message || '').replace(/\"/g, '') + '\"]', form);\n\n case 248:\n _context4.prev = 248;\n form._tvSubmitting = false;\n return _context4.finish(248);\n\n case 251:\n case \"end\":\n return _context4.stop();\n }\n }\n }, _callee4, null, [[218, 245, 248, 251]]);\n }));\n\n return function (_x6, _x7, _x8) {\n return _ref2.apply(this, arguments);\n };\n }()); // ============ 注册请假时长计算 ============\n // 用于请假表单:选择开始/结束时间或半天选择器后,自动调用服务端计算时长\n // DSL: [datepicker ... clk:calcLeaveDuration] 或 [select ... clk:calcLeaveDuration]\n // 参数映射声明在 duration 字段上,和 data-load-body-dep-* 同一设计:\n // data-calc-duration-url → 计算时长 API 地址\n // data-calc-duration-body-dep-{param} → 参数值来源:可以是字段ID 或 \"login*\" 键\n // 示例:\n // data-calc-duration-body-dep-comNo:\"loginCompanyNo\"\n // data-calc-duration-body-dep-holidayNo:\"type\"\n // data-calc-duration-body-dep-startTime:\"startTime\"\n // data-calc-duration-body-dep-endTime:\"endTime\"\n // calcLeaveDuration 防抖:避免 datepicker 交互过程中频繁触发 API\n // ★ 多表单隔离:防抖计时器与并发锁挂在各自表单容器(scope)上,\n // 否则两个表单共用同一个全局 handler 时,A 表单的防抖会被 B 表单清掉/锁住。\n // ★ 兼容 change+input 双事件:输入事件去重窗口 800ms 内只执行一次 API。\n\n window.TokUI.registerHandler('calcLeaveDuration', /*#__PURE__*/function () {\n var _ref4 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee6(data, event, element) {\n var scope, now;\n return regeneratorRuntime.wrap(function _callee6$(_context6) {\n while (1) {\n switch (_context6.prev = _context6.next) {\n case 0:\n // ★ 多表单隔离:作用域只允许从当前元素(或事件目标)向上查找,\n // 绝不回退到本 handler 首次注册时闭包捕获的 container(首个表单)。\n // 否则 datepicker 确认按钮只传 data 不传 element 时会串扰到第一个表单。\n scope = findNearestTokuiScope(element);\n if (!scope && event && event.target) scope = findNearestTokuiScope(event.target);\n\n if (scope) {\n _context6.next = 4;\n break;\n }\n\n return _context6.abrupt(\"return\");\n\n case 4:\n // ★ 输入去重:change+input 双事件可能在 1s 内触发多次,\n // 用 scope._calcDurationDedup 记录最近一次执行时间戳,\n // 800ms 内不再重复执行(防抖仅延迟但不清掉时仍会触发)。\n now = Date.now();\n\n if (!(scope._calcDurationDedup && now - scope._calcDurationDedup < 800)) {\n _context6.next = 7;\n break;\n }\n\n return _context6.abrupt(\"return\");\n\n case 7:\n // 防抖:延迟 400ms 执行,期间如有新触发则取消前一次(仅在当前表单作用域内)\n if (scope._calcDurationTimer) clearTimeout(scope._calcDurationTimer);\n return _context6.abrupt(\"return\", new Promise(function (resolve) {\n scope._calcDurationTimer = setTimeout( /*#__PURE__*/(0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee5() {\n return regeneratorRuntime.wrap(function _callee5$(_context5) {\n while (1) {\n switch (_context5.prev = _context5.next) {\n case 0:\n scope._calcDurationTimer = null;\n\n if (!scope._calcDurationLock) {\n _context5.next = 4;\n break;\n }\n\n resolve();\n return _context5.abrupt(\"return\");\n\n case 4:\n scope._calcDurationLock = true;\n _context5.prev = 5;\n scope._calcDurationDedup = Date.now();\n _context5.next = 9;\n return _calcLeaveDurationImpl(element, scope);\n\n case 9:\n _context5.prev = 9;\n scope._calcDurationLock = false;\n return _context5.finish(9);\n\n case 12:\n resolve();\n\n case 13:\n case \"end\":\n return _context5.stop();\n }\n }\n }, _callee5, null, [[5,, 9, 12]]);\n })), 400);\n }));\n\n case 9:\n case \"end\":\n return _context6.stop();\n }\n }\n }, _callee6);\n }));\n\n return function (_x9, _x10, _x11) {\n return _ref4.apply(this, arguments);\n };\n }());\n\n function _calcLeaveDurationImpl(_x12, _x13) {\n return _calcLeaveDurationImpl2.apply(this, arguments);\n } // ============ 注册加班时长计算 ============\n // 用于加班表单:选择开始/结束时间后,自动调用服务端计算时长\n // DSL: [datetimepicker ... clk:calcOvertimeDuration] 或 [datetimepicker ... clk:calcOvertimeDuration]\n // 参数映射声明在 duration 字段上,和请假 data-calc-duration-body-dep-* 同一设计:\n // data-calc-duration-body-dep-startTime:\"startTime\"\n // data-calc-duration-body-dep-endTime:\"endTime\"\n // 实现逻辑(对齐源项目 workTimeApplyAdd.vue endChange 方法):\n // 1. 同一天(开始日期 === 结束日期)→ 单段加班,调用 calculateDuration API\n // 2. 跨天 → 多段加班,需要按天拆分并计算每天时长(简化为只计算总时长)\n // 3. 回填 duration 字段\n // ★ 兼容 change+input 双事件:800ms 去重窗口\n\n\n function _calcLeaveDurationImpl2() {\n _calcLeaveDurationImpl2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee12(element, explicitScope) {\n var calcScope, durationEl, durationField, bodyObj, bodyDeps, hasParams, i, attr, suffix, depParam, paramKey, depElId, htEl, typeSel, opt, minUnit, startTimeStr, endTimeStr, startTimeMs, endTimeMs, endEl, holidayType, stHalfEl, etHalfEl, stHalf, etHalf, startDate, endDate1, endDate, diffHours, endEl2, calcUrl, json, hasMinLeaveUnit, unitText, fieldWrapper, appendEl, children, ci, span, ddEl, form;\n return regeneratorRuntime.wrap(function _callee12$(_context12) {\n while (1) {\n switch (_context12.prev = _context12.next) {\n case 0:\n // ★ 多表单隔离:优先使用调用方传入的作用域,否则从 element 向上查找\n calcScope = explicitScope || findNearestTokuiScope(element);\n\n if (calcScope) {\n _context12.next = 3;\n break;\n }\n\n return _context12.abrupt(\"return\");\n\n case 3:\n durationEl = queryById('duration', calcScope);\n\n if (durationEl) {\n _context12.next = 6;\n break;\n }\n\n return _context12.abrupt(\"return\");\n\n case 6:\n // 从 duration 字段的 .tokui-field 包装层读配置(patch.js 将 data-* 放在包装层上)\n durationField = durationEl.closest('[data-calc-duration-url]');\n\n if (durationField) {\n _context12.next = 9;\n break;\n }\n\n return _context12.abrupt(\"return\");\n\n case 9:\n // 读取请求体参数映射(data-calc-duration-body-dep-{param})\n bodyObj = {};\n bodyDeps = {};\n hasParams = false;\n\n if (!durationField.attributes) {\n _context12.next = 31;\n break;\n }\n\n i = 0;\n\n case 14:\n if (!(i < durationField.attributes.length)) {\n _context12.next = 31;\n break;\n }\n\n attr = durationField.attributes[i];\n\n if (!(!attr.name || attr.name.indexOf('data-calc-duration-body-') !== 0)) {\n _context12.next = 18;\n break;\n }\n\n return _context12.abrupt(\"continue\", 28);\n\n case 18:\n suffix = attr.name.substring('data-calc-duration-body-'.length);\n\n if (suffix) {\n _context12.next = 21;\n break;\n }\n\n return _context12.abrupt(\"continue\", 28);\n\n case 21:\n if (!(suffix.indexOf('dep-') === 0)) {\n _context12.next = 25;\n break;\n }\n\n depParam = normalizeBodyKey(suffix.substring('dep-'.length));\n\n if (depParam) {\n bodyDeps[depParam] = attr.value.trim();\n hasParams = true;\n }\n\n return _context12.abrupt(\"continue\", 28);\n\n case 25:\n // data-calc-duration-body-{param} → 静态值\n paramKey = normalizeBodyKey(suffix);\n bodyObj[paramKey] = attr.value;\n hasParams = true;\n\n case 28:\n i++;\n _context12.next = 14;\n break;\n\n case 31:\n if (hasParams) {\n _context12.next = 34;\n break;\n }\n\n console.warn('[Validator] calcLeaveDuration: duration 字段上未配置 data-calc-duration-body-dep-* 参数映射,跳过计算');\n return _context12.abrupt(\"return\");\n\n case 34:\n _context12.t0 = regeneratorRuntime.keys(bodyDeps);\n\n case 35:\n if ((_context12.t1 = _context12.t0()).done) {\n _context12.next = 43;\n break;\n }\n\n depParam = _context12.t1.value;\n\n if (bodyDeps.hasOwnProperty(depParam)) {\n _context12.next = 39;\n break;\n }\n\n return _context12.abrupt(\"continue\", 35);\n\n case 39:\n depElId = bodyDeps[depParam];\n bodyObj[depParam] = _readFieldValueById(depElId, calcScope);\n _context12.next = 35;\n break;\n\n case 43:\n // ★ 兜底:确保 minLeaveUnit 始终存在(源项目始终发送 minLeaveUnit: this.holidayType)\n // 如果 DSL 未配置 data-calc-duration-body-dep-minLeaveUnit,从 _holidayType 隐藏字段读取\n // 注意:外出表单 DSL 没有 minLeaveUnit 依赖,不添加此字段\n if (bodyDeps.minLeaveUnit !== undefined) {\n if (bodyObj.minLeaveUnit === undefined || bodyObj.minLeaveUnit === '') {\n htEl = queryById('_holidayType', calcScope);\n bodyObj.minLeaveUnit = htEl ? htEl.value : ''; // ★ 增强兜底:如果 _holidayType 仍未取到,尝试从 type 下拉框选中项读取 data-min-leave-unit\n\n if (bodyObj.minLeaveUnit === '' || bodyObj.minLeaveUnit === undefined) {\n typeSel = queryById('type', calcScope);\n\n if (typeSel && typeSel.tagName === 'SELECT' && typeSel.selectedIndex >= 0) {\n opt = typeSel.options[typeSel.selectedIndex];\n minUnit = opt.getAttribute('data-min-leave-unit');\n\n if (minUnit !== null && minUnit !== undefined && String(minUnit) !== '') {\n bodyObj.minLeaveUnit = String(minUnit);\n }\n }\n }\n\n if (bodyObj.minLeaveUnit === '' || bodyObj.minLeaveUnit === undefined) {\n bodyObj.minLeaveUnit = '0';\n }\n }\n } // 校验:起止时间必须都有值\n\n\n if (!(!bodyObj.startTime || !bodyObj.endTime)) {\n _context12.next = 47;\n break;\n }\n\n if (durationEl) durationEl.value = '';\n return _context12.abrupt(\"return\");\n\n case 47:\n // 转换日期 \"YYYY-MM-DD\" 或 \"YYYY-MM-DD HH:mm\" → 毫秒时间戳\n // ★ 纯日期 \"YYYY-MM-DD\" 需要补全为本地时间 \"YYYY-MM-DDT00:00:00\",否则 new Date() 按 UTC 解析导致时区偏差\n startTimeStr = String(bodyObj.startTime);\n endTimeStr = String(bodyObj.endTime);\n if (startTimeStr.length === 10) startTimeStr += 'T00:00:00';else startTimeStr = startTimeStr.replace(' ', 'T');\n if (endTimeStr.length === 10) endTimeStr += 'T00:00:00';else endTimeStr = endTimeStr.replace(' ', 'T');\n startTimeMs = new Date(startTimeStr).getTime();\n endTimeMs = new Date(endTimeStr).getTime();\n\n if (!(isNaN(startTimeMs) || isNaN(endTimeMs))) {\n _context12.next = 55;\n break;\n }\n\n return _context12.abrupt(\"return\");\n\n case 55:\n if (!(endTimeMs < startTimeMs)) {\n _context12.next = 60;\n break;\n }\n\n (0, _message.showMessage)('结束时间不能早于开始时间', 'warning');\n endEl = queryById(bodyDeps.endTime, calcScope);\n if (endEl) endEl.value = '';\n return _context12.abrupt(\"return\");\n\n case 60:\n bodyObj.startTime = startTimeMs;\n bodyObj.endTime = endTimeMs; // 读取 minLeaveUnit(0=按小时, 1=按半天, 2=按天)\n // 外出表单无 minLeaveUnit 依赖,holidayType 为 NaN,不进入任何分支\n\n holidayType = parseInt(bodyObj.minLeaveUnit, 10) || 0; // ★ 半天模式:根据上/下午调整时间(对齐源项目 getDuration)\n // 上午 → 00:00:00,下午 → 23:59:59\n\n if (holidayType === 1) {\n stHalfEl = queryById('startTimeHalf', calcScope);\n etHalfEl = queryById('endTimeHalf', calcScope); // ★ 年假上/下午选择变化后需重新计算时长:这两个控件通常不带 clk:calcLeaveDuration,\n // 导致用户选完起止日期再改上/下午时接口不被触发、时长不反显。这里补绑定一次。\n\n if (stHalfEl && !stHalfEl._tvCalcHalfBound) {\n stHalfEl._tvCalcHalfBound = true;\n stHalfEl.addEventListener('change', function () {\n var h = _clkHandlers['calcLeaveDuration'];\n if (typeof h === 'function') h({}, new Event('change'), stHalfEl);\n });\n }\n\n if (etHalfEl && !etHalfEl._tvCalcHalfBound) {\n etHalfEl._tvCalcHalfBound = true;\n etHalfEl.addEventListener('change', function () {\n var h = _clkHandlers['calcLeaveDuration'];\n if (typeof h === 'function') h({}, new Event('change'), etHalfEl);\n });\n }\n\n stHalf = stHalfEl ? parseInt(stHalfEl.value, 10) : 1;\n etHalf = etHalfEl ? parseInt(etHalfEl.value, 10) : 1; // ★ 半天模式时间规则:\n // 开始时间:上午→00:00:00,下午→12:00:00\n // 结束时间:上午→12:00:00,下午→23:59:59\n\n startDate = new Date(startTimeMs);\n\n if (stHalf === 1) {\n startDate.setHours(0, 0, 0, 0);\n } else {\n startDate.setHours(12, 0, 0, 0);\n }\n\n bodyObj.startTime = startDate.getTime();\n endDate1 = new Date(endTimeMs);\n\n if (etHalf === 1) {\n endDate1.setHours(12, 0, 0, 0);\n } else {\n endDate1.setHours(23, 59, 59, 0);\n }\n\n bodyObj.endTime = endDate1.getTime();\n } // 按天模式:结束时间统一设为 23:59:59\n\n\n if (holidayType === 2) {\n endDate = new Date(endTimeMs);\n endDate.setHours(23, 59, 59, 999);\n bodyObj.endTime = endDate.getTime();\n } // 按小时模式:最少1小时校验\n\n\n if (!(holidayType === 0)) {\n _context12.next = 72;\n break;\n }\n\n diffHours = (bodyObj.endTime - bodyObj.startTime) / (1000 * 60 * 60);\n\n if (!(diffHours < 1)) {\n _context12.next = 72;\n break;\n }\n\n (0, _message.showMessage)('请假时长不得小于一小时', 'warning');\n endEl2 = queryById(bodyDeps.endTime, calcScope);\n if (endEl2) endEl2.value = '';\n return _context12.abrupt(\"return\");\n\n case 72:\n // ★ 仅在 DSL 声明了 startTimeStr/endTimeStr 依赖时才构造(请假表单需要,外出不需要)\n // 源项目: startTimeStr = `${dayjs(startTime).format(\"YYYY-MM-DD\")} ${startTimeHalf == 1 ? \"上午\" : \"下午\"}`\n if (bodyDeps.startTimeStr !== undefined) {\n bodyObj.startTimeStr = buildHalfDayTimeStr(bodyObj.startTime, 'startTimeHalf', calcScope);\n }\n\n if (bodyDeps.endTimeStr !== undefined) {\n bodyObj.endTimeStr = buildHalfDayTimeStr(bodyObj.endTime, 'endTimeHalf', calcScope);\n } // 调用服务端时长计算 API\n\n\n calcUrl = cleanUrl(durationField.getAttribute('data-calc-duration-url') || '');\n\n if (calcUrl) {\n _context12.next = 78;\n break;\n }\n\n console.warn('[Validator] calcLeaveDuration: duration 字段上未配置 data-calc-duration-url');\n return _context12.abrupt(\"return\");\n\n case 78:\n _context12.prev = 78;\n _context12.next = 81;\n return _request.default.post(calcUrl, bodyObj);\n\n case 81:\n json = _context12.sent;\n\n if (!(json.code === 200 && json.data)) {\n _context12.next = 117;\n break;\n }\n\n // ★ 外出表单(无 minLeaveUnit):显示 leaveTime(小时),值用 duration(天/数值)\n // 请假表单(有 minLeaveUnit):显示 duration,单位由 minLeaveUnit 决定\n hasMinLeaveUnit = bodyDeps.minLeaveUnit !== undefined;\n\n if (hasMinLeaveUnit) {\n if (durationEl) durationEl.value = String(json.data.duration || '');\n } else {\n // 外出表单:使用 leaveTime 作为显示值(如 3 小时),duration 作为数值(如 0.42 天)\n if (durationEl) durationEl.value = String(json.data.leaveTime || json.data.duration || '');\n }\n\n unitText = hasMinLeaveUnit ? getLeaveUnitText(bodyObj.minLeaveUnit) : '小时';\n fieldWrapper = durationEl.closest('.tokui-field') || durationEl.parentElement;\n\n if (!fieldWrapper) {\n _context12.next = 100;\n break;\n }\n\n appendEl = fieldWrapper.querySelector('.tokui-input-group__append, .tokui-append, .el-input-group__append');\n\n if (appendEl) {\n _context12.next = 99;\n break;\n }\n\n children = fieldWrapper.querySelectorAll('span, div');\n ci = children.length - 1;\n\n case 92:\n if (!(ci >= 0)) {\n _context12.next = 99;\n break;\n }\n\n if (!(children[ci].textContent && children[ci].textContent.trim() && !children[ci].querySelector('input'))) {\n _context12.next = 96;\n break;\n }\n\n appendEl = children[ci];\n return _context12.abrupt(\"break\", 99);\n\n case 96:\n ci--;\n _context12.next = 92;\n break;\n\n case 99:\n if (appendEl) {\n appendEl.textContent = unitText;\n } else {\n span = document.createElement('span');\n span.className = 'tokui-duration-unit';\n span.textContent = unitText;\n span.style.cssText = 'margin-left:6px;color:#909399;font-size:14px;';\n fieldWrapper.appendChild(span);\n }\n\n case 100:\n syncLeaveUnitSuffix('staffBalance', unitText, calcScope); // 存储 durationDay 用于审批流程变量\n\n ddEl = queryById('_durationDay', calcScope); // ★ 多表单隔离:表单必须限定在当前 calcScope 内查找。\n // datepicker 确认按钮可能只传 data 不传 element,此时 resolveForm(undefined)\n // 会退化为 document 级查询并返回文档第一个表单,导致 _durationDay 与审批流串扰到别的表单。\n\n form = calcScope.querySelector('[data-form-id=\"leaveForm\"]') || queryById('leaveForm', calcScope) || calcScope.querySelector('[data-form-id]') || calcScope.querySelector('form');\n\n if (!form && element) {\n form = resolveForm(element); // resolveForm 可能返回 document 级或 body 弹层的表单,必须校验其确实落在当前表单容器内\n\n if (form && !calcScope.contains(form)) form = null;\n }\n\n if (!form && ddEl && ddEl.form) form = ddEl.form;\n\n if (ddEl) {\n _context12.next = 113;\n break;\n }\n\n if (form) {\n _context12.next = 108;\n break;\n }\n\n return _context12.abrupt(\"return\");\n\n case 108:\n ddEl = document.createElement('input');\n ddEl.type = 'hidden';\n ddEl.id = '_durationDay';\n ddEl.name = '_durationDay';\n form.appendChild(ddEl);\n\n case 113:\n ddEl.value = String(json.data.durationDay || ''); // ★ 时长计算完毕后,刷新审批流程(durationDay 变量已就绪)\n\n if (form) {\n try {\n refreshApprovalFlow(form);\n } catch (e2) {}\n }\n\n _context12.next = 118;\n break;\n\n case 117:\n if (holidayType !== 1) {\n (0, _message.showMessage)(json.msg || json.message || '计算时长失败', 'warning');\n }\n\n case 118:\n _context12.next = 123;\n break;\n\n case 120:\n _context12.prev = 120;\n _context12.t2 = _context12[\"catch\"](78);\n console.warn('[Validator] calcLeaveDuration API error:', _context12.t2.message);\n\n case 123:\n case \"end\":\n return _context12.stop();\n }\n }\n }, _callee12, null, [[78, 120]]);\n }));\n return _calcLeaveDurationImpl2.apply(this, arguments);\n }\n\n window.TokUI.registerHandler('calcOvertimeDuration', /*#__PURE__*/function () {\n var _ref6 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee8(data, event, element) {\n var scope, now;\n return regeneratorRuntime.wrap(function _callee8$(_context8) {\n while (1) {\n switch (_context8.prev = _context8.next) {\n case 0:\n scope = findNearestTokuiScope(element);\n if (!scope && event && event.target) scope = findNearestTokuiScope(event.target);\n\n if (scope) {\n _context8.next = 4;\n break;\n }\n\n return _context8.abrupt(\"return\");\n\n case 4:\n // ★ 输入去重:change+input 双事件 800ms 内不再重复执行\n now = Date.now();\n\n if (!(scope._calcOvertimeDedup && now - scope._calcOvertimeDedup < 800)) {\n _context8.next = 7;\n break;\n }\n\n return _context8.abrupt(\"return\");\n\n case 7:\n // 防抖:延迟 400ms\n if (scope._calcOvertimeTimer) clearTimeout(scope._calcOvertimeTimer);\n return _context8.abrupt(\"return\", new Promise(function (resolve) {\n scope._calcOvertimeTimer = setTimeout( /*#__PURE__*/(0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee7() {\n return regeneratorRuntime.wrap(function _callee7$(_context7) {\n while (1) {\n switch (_context7.prev = _context7.next) {\n case 0:\n scope._calcOvertimeTimer = null;\n\n if (!scope._calcOvertimeLock) {\n _context7.next = 4;\n break;\n }\n\n resolve();\n return _context7.abrupt(\"return\");\n\n case 4:\n scope._calcOvertimeLock = true;\n _context7.prev = 5;\n scope._calcOvertimeDedup = Date.now();\n _context7.next = 9;\n return _calcOvertimeDurationImpl(element, scope);\n\n case 9:\n _context7.prev = 9;\n scope._calcOvertimeLock = false;\n return _context7.finish(9);\n\n case 12:\n resolve();\n\n case 13:\n case \"end\":\n return _context7.stop();\n }\n }\n }, _callee7, null, [[5,, 9, 12]]);\n })), 400);\n }));\n\n case 9:\n case \"end\":\n return _context8.stop();\n }\n }\n }, _callee8);\n }));\n\n return function (_x14, _x15, _x16) {\n return _ref6.apply(this, arguments);\n };\n }());\n\n function _calcOvertimeDurationImpl(_x17, _x18) {\n return _calcOvertimeDurationImpl2.apply(this, arguments);\n } // ============ 注册外出时长计算 ============\n // 用于外出表单:选择开始/结束时间后,自动调用服务端计算时长\n // DSL: [datetimepicker ... clk:calcGoOutDuration]\n // 参数映射声明在 duration 字段上,与 calcLeaveDuration 同一设计:\n // data-calc-duration-body-dep-startTime:\"startTime\"\n // data-calc-duration-body-dep-endTime:\"endTime\"\n // 实现逻辑:\n // 1. 从 DSL 配置读取所有参数(完全配置驱动)\n // 2. 调用服务端 API 计算时长\n // 3. 回填 duration 字段\n\n\n function _calcOvertimeDurationImpl2() {\n _calcOvertimeDurationImpl2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee13(element, explicitScope) {\n var calcScope, durationEl, durationField, bodyObj, bodyDeps, hasParams, i, attr, suffix, depParam, paramKey, depElId, startTimeStr, endTimeStr, startTimeMs, endTimeMs, endEl, startDate, endDate, isSameDay, calcUrl, json, leaveTime, fieldWrapper, appendEl, children, ci, span, overtimeList, overtimeListEl, form;\n return regeneratorRuntime.wrap(function _callee13$(_context13) {\n while (1) {\n switch (_context13.prev = _context13.next) {\n case 0:\n console.log('[Validator] _calcOvertimeDurationImpl 被调用', element ? element.tagName || element.id || '' : '无element');\n calcScope = explicitScope || findNearestTokuiScope(element);\n\n if (calcScope) {\n _context13.next = 5;\n break;\n }\n\n console.warn('[Validator] calcOvertimeDuration: 未找到 scope');\n return _context13.abrupt(\"return\");\n\n case 5:\n durationEl = queryById('duration', calcScope);\n\n if (durationEl) {\n _context13.next = 9;\n break;\n }\n\n console.warn('[Validator] calcOvertimeDuration: 未找到 duration 字段');\n return _context13.abrupt(\"return\");\n\n case 9:\n // 从 duration 字段的 .tokui-field 包装层读配置\n durationField = durationEl.closest('[data-calc-duration-url]');\n\n if (durationField) {\n _context13.next = 13;\n break;\n }\n\n console.warn('[Validator] calcOvertimeDuration: duration 字段父级未找到 data-calc-duration-url');\n return _context13.abrupt(\"return\");\n\n case 13:\n // 读取请求体参数映射(data-calc-duration-body-dep-{param})\n bodyObj = {};\n bodyDeps = {};\n hasParams = false;\n\n if (!durationField.attributes) {\n _context13.next = 35;\n break;\n }\n\n i = 0;\n\n case 18:\n if (!(i < durationField.attributes.length)) {\n _context13.next = 35;\n break;\n }\n\n attr = durationField.attributes[i];\n\n if (!(!attr.name || attr.name.indexOf('data-calc-duration-body-') !== 0)) {\n _context13.next = 22;\n break;\n }\n\n return _context13.abrupt(\"continue\", 32);\n\n case 22:\n suffix = attr.name.substring('data-calc-duration-body-'.length);\n\n if (suffix) {\n _context13.next = 25;\n break;\n }\n\n return _context13.abrupt(\"continue\", 32);\n\n case 25:\n if (!(suffix.indexOf('dep-') === 0)) {\n _context13.next = 29;\n break;\n }\n\n depParam = normalizeBodyKey(suffix.substring('dep-'.length));\n\n if (depParam) {\n bodyDeps[depParam] = attr.value.trim();\n hasParams = true;\n }\n\n return _context13.abrupt(\"continue\", 32);\n\n case 29:\n paramKey = normalizeBodyKey(suffix);\n bodyObj[paramKey] = attr.value;\n hasParams = true;\n\n case 32:\n i++;\n _context13.next = 18;\n break;\n\n case 35:\n if (hasParams) {\n _context13.next = 38;\n break;\n }\n\n console.warn('[Validator] calcOvertimeDuration: duration 字段上未配置 data-calc-duration-body-dep-* 参数映射,跳过计算');\n return _context13.abrupt(\"return\");\n\n case 38:\n _context13.t0 = regeneratorRuntime.keys(bodyDeps);\n\n case 39:\n if ((_context13.t1 = _context13.t0()).done) {\n _context13.next = 47;\n break;\n }\n\n depParam = _context13.t1.value;\n\n if (bodyDeps.hasOwnProperty(depParam)) {\n _context13.next = 43;\n break;\n }\n\n return _context13.abrupt(\"continue\", 39);\n\n case 43:\n depElId = bodyDeps[depParam];\n bodyObj[depParam] = _readFieldValueById(depElId, calcScope);\n _context13.next = 39;\n break;\n\n case 47:\n if (!(!bodyObj.startTime || !bodyObj.endTime)) {\n _context13.next = 51;\n break;\n }\n\n console.log('[Validator] calcOvertimeDuration: 起止时间不完整', bodyObj.startTime, bodyObj.endTime);\n if (durationEl) durationEl.value = '';\n return _context13.abrupt(\"return\");\n\n case 51:\n // 转换日期 → 毫秒时间戳\n console.log('[Validator] calcOvertimeDuration: bodyObj=', JSON.stringify(bodyObj), 'bodyDeps=', JSON.stringify(bodyDeps));\n startTimeStr = String(bodyObj.startTime);\n endTimeStr = String(bodyObj.endTime);\n if (startTimeStr.length === 10) startTimeStr += 'T00:00:00';else startTimeStr = startTimeStr.replace(' ', 'T');\n if (endTimeStr.length === 10) endTimeStr += 'T00:00:00';else endTimeStr = endTimeStr.replace(' ', 'T');\n startTimeMs = new Date(startTimeStr).getTime();\n endTimeMs = new Date(endTimeStr).getTime();\n\n if (!(isNaN(startTimeMs) || isNaN(endTimeMs))) {\n _context13.next = 60;\n break;\n }\n\n return _context13.abrupt(\"return\");\n\n case 60:\n if (!(endTimeMs < startTimeMs)) {\n _context13.next = 65;\n break;\n }\n\n (0, _message.showMessage)('结束时间不能早于开始时间', 'warning');\n endEl = queryById(bodyDeps.endTime, calcScope);\n if (endEl) endEl.value = '';\n return _context13.abrupt(\"return\");\n\n case 65:\n bodyObj.startTime = startTimeMs;\n bodyObj.endTime = endTimeMs; // 判断是否同一天\n\n startDate = new Date(startTimeMs);\n endDate = new Date(endTimeMs);\n isSameDay = startDate.getFullYear() === endDate.getFullYear() && startDate.getMonth() === endDate.getMonth() && startDate.getDate() === endDate.getDate();\n calcUrl = cleanUrl(durationField.getAttribute('data-calc-duration-url') || '');\n\n if (calcUrl) {\n _context13.next = 74;\n break;\n }\n\n console.warn('[Validator] calcOvertimeDuration: duration 字段上未配置 data-calc-duration-url');\n return _context13.abrupt(\"return\");\n\n case 74:\n console.log('[Validator] calcOvertimeDuration: 开始计算, URL=', calcUrl, 'startTimeMs=', startTimeMs, 'endTimeMs=', endTimeMs, 'isSameDay=', isSameDay);\n _context13.prev = 75;\n // 同天:调用计算时长 API\n console.log('[Validator] calcOvertimeDuration: 同天 API 请求', calcUrl, JSON.stringify(bodyObj));\n _context13.next = 79;\n return _request.default.post(calcUrl, bodyObj);\n\n case 79:\n json = _context13.sent;\n console.log('[Validator] calcOvertimeDuration: 同天 API 响应', json.code, JSON.stringify(json.data));\n\n if (!(json.code === 200 && json.data)) {\n _context13.next = 101;\n break;\n }\n\n leaveTime = json.data.leaveTime || json.data.duration || '0';\n if (durationEl) durationEl.value = String(leaveTime); // 更新单位后缀\n\n fieldWrapper = durationEl.closest('.tokui-field') || durationEl.parentElement;\n\n if (!fieldWrapper) {\n _context13.next = 98;\n break;\n }\n\n appendEl = fieldWrapper.querySelector('.tokui-input-group__append, .tokui-append, .el-input-group__append');\n\n if (appendEl) {\n _context13.next = 97;\n break;\n }\n\n children = fieldWrapper.querySelectorAll('span, div');\n ci = children.length - 1;\n\n case 90:\n if (!(ci >= 0)) {\n _context13.next = 97;\n break;\n }\n\n if (!(children[ci].textContent && children[ci].textContent.trim() && !children[ci].querySelector('input'))) {\n _context13.next = 94;\n break;\n }\n\n appendEl = children[ci];\n return _context13.abrupt(\"break\", 97);\n\n case 94:\n ci--;\n _context13.next = 90;\n break;\n\n case 97:\n if (appendEl) {\n appendEl.textContent = '小时';\n } else {\n span = document.createElement('span');\n span.className = 'tokui-duration-unit';\n span.textContent = '小时';\n span.style.cssText = 'margin-left:6px;color:#909399;font-size:14px;';\n fieldWrapper.appendChild(span);\n }\n\n case 98:\n // 跨天时,将 overtimeList 明细存储到隐藏字段供提交使用\n if (!isSameDay) {\n overtimeList = json.data.dateList || json.data.overTimeList || [];\n\n if (overtimeList.length > 0) {\n overtimeListEl = queryById('overtimeList', calcScope);\n\n if (overtimeListEl) {\n overtimeListEl.value = JSON.stringify(overtimeList);\n }\n }\n }\n\n _context13.next = 102;\n break;\n\n case 101:\n (0, _message.showMessage)(json.msg || json.message || '计算时长失败', 'warning');\n\n case 102:\n _context13.next = 107;\n break;\n\n case 104:\n _context13.prev = 104;\n _context13.t2 = _context13[\"catch\"](75);\n console.warn('[Validator] calcOvertimeDuration API error:', _context13.t2.message);\n\n case 107:\n // 时长计算后刷新审批\n form = calcScope.querySelector('[data-form-id=\"overtimeForm\"]') || queryById('overtimeForm', calcScope) || calcScope.querySelector('[data-form-id]') || calcScope.querySelector('form');\n\n if (form) {\n try {\n refreshApprovalFlow(form);\n } catch (e2) {}\n }\n\n case 109:\n case \"end\":\n return _context13.stop();\n }\n }\n }, _callee13, null, [[75, 104]]);\n }));\n return _calcOvertimeDurationImpl2.apply(this, arguments);\n }\n\n window.TokUI.registerHandler('calcGoOutDuration', /*#__PURE__*/function () {\n var _ref8 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee10(data, event, element) {\n var scope;\n return regeneratorRuntime.wrap(function _callee10$(_context10) {\n while (1) {\n switch (_context10.prev = _context10.next) {\n case 0:\n scope = findNearestTokuiScope(element);\n if (!scope && event && event.target) scope = findNearestTokuiScope(event.target);\n\n if (scope) {\n _context10.next = 4;\n break;\n }\n\n return _context10.abrupt(\"return\");\n\n case 4:\n // 防抖:延迟 400ms\n if (scope._calcGoOutTimer) clearTimeout(scope._calcGoOutTimer);\n return _context10.abrupt(\"return\", new Promise(function (resolve) {\n scope._calcGoOutTimer = setTimeout( /*#__PURE__*/(0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee9() {\n return regeneratorRuntime.wrap(function _callee9$(_context9) {\n while (1) {\n switch (_context9.prev = _context9.next) {\n case 0:\n scope._calcGoOutTimer = null;\n\n if (!scope._calcGoOutLock) {\n _context9.next = 4;\n break;\n }\n\n resolve();\n return _context9.abrupt(\"return\");\n\n case 4:\n scope._calcGoOutLock = true;\n _context9.prev = 5;\n _context9.next = 8;\n return _calcGoOutDurationImpl(element, scope);\n\n case 8:\n _context9.prev = 8;\n scope._calcGoOutLock = false;\n return _context9.finish(8);\n\n case 11:\n resolve();\n\n case 12:\n case \"end\":\n return _context9.stop();\n }\n }\n }, _callee9, null, [[5,, 8, 11]]);\n })), 400);\n }));\n\n case 6:\n case \"end\":\n return _context10.stop();\n }\n }\n }, _callee10);\n }));\n\n return function (_x19, _x20, _x21) {\n return _ref8.apply(this, arguments);\n };\n }());\n\n function _calcGoOutDurationImpl(_x22, _x23) {\n return _calcGoOutDurationImpl2.apply(this, arguments);\n }\n /**\r\n * 从表单字段读取值,兼容 select / input / hidden\r\n * - \"login*\" 前缀值从 window.__tokuiParams 读取\r\n * - 否则在当前作用域内按 id 精确查找(多表单隔离)\r\n */\n\n\n function _calcGoOutDurationImpl2() {\n _calcGoOutDurationImpl2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee14(element, explicitScope) {\n var calcScope, durationEl, durationField, bodyObj, bodyDeps, hasParams, i, attr, suffix, depParam, paramKey, depElId, startTimeStr, endTimeStr, startTimeMs, endTimeMs, endEl, calcUrl, json, goOutTime, fieldWrapper, appendEl, children, ci, span, form;\n return regeneratorRuntime.wrap(function _callee14$(_context14) {\n while (1) {\n switch (_context14.prev = _context14.next) {\n case 0:\n console.log('[Validator] _calcGoOutDurationImpl 被调用', element ? element.tagName || element.id || '' : '无element');\n calcScope = explicitScope || findNearestTokuiScope(element);\n\n if (calcScope) {\n _context14.next = 5;\n break;\n }\n\n console.warn('[Validator] calcGoOutDuration: 未找到 scope');\n return _context14.abrupt(\"return\");\n\n case 5:\n durationEl = queryById('duration', calcScope);\n\n if (durationEl) {\n _context14.next = 9;\n break;\n }\n\n console.warn('[Validator] calcGoOutDuration: 未找到 duration 字段');\n return _context14.abrupt(\"return\");\n\n case 9:\n // 从 duration 字段的 .tokui-field 包装层读配置\n durationField = durationEl.closest('[data-calc-duration-url]');\n\n if (durationField) {\n _context14.next = 13;\n break;\n }\n\n console.warn('[Validator] calcGoOutDuration: duration 字段父级未找到 data-calc-duration-url');\n return _context14.abrupt(\"return\");\n\n case 13:\n // 读取请求体参数映射(data-calc-duration-body-dep-{param})\n bodyObj = {};\n bodyDeps = {};\n hasParams = false;\n\n if (!durationField.attributes) {\n _context14.next = 35;\n break;\n }\n\n i = 0;\n\n case 18:\n if (!(i < durationField.attributes.length)) {\n _context14.next = 35;\n break;\n }\n\n attr = durationField.attributes[i];\n\n if (!(!attr.name || attr.name.indexOf('data-calc-duration-body-') !== 0)) {\n _context14.next = 22;\n break;\n }\n\n return _context14.abrupt(\"continue\", 32);\n\n case 22:\n suffix = attr.name.substring('data-calc-duration-body-'.length);\n\n if (suffix) {\n _context14.next = 25;\n break;\n }\n\n return _context14.abrupt(\"continue\", 32);\n\n case 25:\n if (!(suffix.indexOf('dep-') === 0)) {\n _context14.next = 29;\n break;\n }\n\n depParam = normalizeBodyKey(suffix.substring('dep-'.length));\n\n if (depParam) {\n bodyDeps[depParam] = attr.value.trim();\n hasParams = true;\n }\n\n return _context14.abrupt(\"continue\", 32);\n\n case 29:\n paramKey = normalizeBodyKey(suffix);\n bodyObj[paramKey] = attr.value;\n hasParams = true;\n\n case 32:\n i++;\n _context14.next = 18;\n break;\n\n case 35:\n if (hasParams) {\n _context14.next = 38;\n break;\n }\n\n console.warn('[Validator] calcGoOutDuration: duration 字段上未配置 data-calc-duration-body-dep-* 参数映射,跳过计算');\n return _context14.abrupt(\"return\");\n\n case 38:\n _context14.t0 = regeneratorRuntime.keys(bodyDeps);\n\n case 39:\n if ((_context14.t1 = _context14.t0()).done) {\n _context14.next = 47;\n break;\n }\n\n depParam = _context14.t1.value;\n\n if (bodyDeps.hasOwnProperty(depParam)) {\n _context14.next = 43;\n break;\n }\n\n return _context14.abrupt(\"continue\", 39);\n\n case 43:\n depElId = bodyDeps[depParam];\n bodyObj[depParam] = _readFieldValueById(depElId, calcScope);\n _context14.next = 39;\n break;\n\n case 47:\n if (!(!bodyObj.startTime || !bodyObj.endTime)) {\n _context14.next = 51;\n break;\n }\n\n console.log('[Validator] calcGoOutDuration: 起止时间不完整', bodyObj.startTime, bodyObj.endTime);\n if (durationEl) durationEl.value = '';\n return _context14.abrupt(\"return\");\n\n case 51:\n // 转换日期 → 毫秒时间戳\n startTimeStr = String(bodyObj.startTime);\n endTimeStr = String(bodyObj.endTime);\n if (startTimeStr.length === 10) startTimeStr += 'T00:00:00';else startTimeStr = startTimeStr.replace(' ', 'T');\n if (endTimeStr.length === 10) endTimeStr += 'T00:00:00';else endTimeStr = endTimeStr.replace(' ', 'T');\n startTimeMs = new Date(startTimeStr).getTime();\n endTimeMs = new Date(endTimeStr).getTime();\n\n if (!(isNaN(startTimeMs) || isNaN(endTimeMs))) {\n _context14.next = 59;\n break;\n }\n\n return _context14.abrupt(\"return\");\n\n case 59:\n if (!(endTimeMs < startTimeMs)) {\n _context14.next = 64;\n break;\n }\n\n (0, _message.showMessage)('结束时间不能早于开始时间', 'warning');\n endEl = queryById(bodyDeps.endTime, calcScope);\n if (endEl) endEl.value = '';\n return _context14.abrupt(\"return\");\n\n case 64:\n bodyObj.startTime = startTimeMs;\n bodyObj.endTime = endTimeMs;\n calcUrl = cleanUrl(durationField.getAttribute('data-calc-duration-url') || '');\n\n if (calcUrl) {\n _context14.next = 70;\n break;\n }\n\n console.warn('[Validator] calcGoOutDuration: duration 字段上未配置 data-calc-duration-url');\n return _context14.abrupt(\"return\");\n\n case 70:\n console.log('[Validator] calcGoOutDuration: 开始计算, URL=', calcUrl, JSON.stringify(bodyObj));\n _context14.prev = 71;\n _context14.next = 74;\n return _request.default.post(calcUrl, bodyObj);\n\n case 74:\n json = _context14.sent;\n console.log('[Validator] calcGoOutDuration: API 响应', json.code, JSON.stringify(json.data));\n\n if (!(json.code === 200 && json.data)) {\n _context14.next = 95;\n break;\n }\n\n goOutTime = json.data.leaveTime || json.data.duration || '0';\n if (durationEl) durationEl.value = String(goOutTime); // 更新单位后缀\n\n fieldWrapper = durationEl.closest('.tokui-field') || durationEl.parentElement;\n\n if (!fieldWrapper) {\n _context14.next = 93;\n break;\n }\n\n appendEl = fieldWrapper.querySelector('.tokui-input-group__append, .tokui-append, .el-input-group__append');\n\n if (appendEl) {\n _context14.next = 92;\n break;\n }\n\n children = fieldWrapper.querySelectorAll('span, div');\n ci = children.length - 1;\n\n case 85:\n if (!(ci >= 0)) {\n _context14.next = 92;\n break;\n }\n\n if (!(children[ci].textContent && children[ci].textContent.trim() && !children[ci].querySelector('input'))) {\n _context14.next = 89;\n break;\n }\n\n appendEl = children[ci];\n return _context14.abrupt(\"break\", 92);\n\n case 89:\n ci--;\n _context14.next = 85;\n break;\n\n case 92:\n if (appendEl) {\n appendEl.textContent = '小时';\n } else {\n span = document.createElement('span');\n span.className = 'tokui-duration-unit';\n span.textContent = '小时';\n span.style.cssText = 'margin-left:6px;color:#909399;font-size:14px;';\n fieldWrapper.appendChild(span);\n }\n\n case 93:\n _context14.next = 96;\n break;\n\n case 95:\n (0, _message.showMessage)(json.msg || json.message || '计算时长失败', 'warning');\n\n case 96:\n _context14.next = 101;\n break;\n\n case 98:\n _context14.prev = 98;\n _context14.t2 = _context14[\"catch\"](71);\n console.warn('[Validator] calcGoOutDuration API error:', _context14.t2.message);\n\n case 101:\n // 时长计算后刷新审批\n form = calcScope.querySelector('[data-form-id=\"goOutForm\"]') || queryById('goOutForm', calcScope) || calcScope.querySelector('[data-form-id]') || calcScope.querySelector('form');\n\n if (form) {\n try {\n refreshApprovalFlow(form);\n } catch (e2) {}\n }\n\n case 103:\n case \"end\":\n return _context14.stop();\n }\n }\n }, _callee14, null, [[71, 98]]);\n }));\n return _calcGoOutDurationImpl2.apply(this, arguments);\n }\n\n function _readFieldValueById(elId, scope) {\n if (!elId) return ''; // 确保 elId 是字符串\n\n if (typeof elId !== 'string') elId = String(elId); // 登录参数\n\n if (elId.indexOf('login') === 0) {\n return window.__tokuiParams && window.__tokuiParams[elId] !== undefined ? String(window.__tokuiParams[elId]) : '';\n }\n\n var root = scope && scope.querySelectorAll ? scope : container && container.querySelectorAll ? container : document; // 表单字段:优先取未被条件隐藏的元素(DSL 中可能有多组同 id 元素,如 datetimepicker + datepicker)\n\n var el = queryById(elId, root);\n\n if (el && el.closest('.tokui-conditional-hidden')) {\n // 第一个匹配被隐藏,查找同 id 的其他可见元素\n var allSameId = root.querySelectorAll('[id=\"' + elId + '\"]');\n\n for (var si = 0; si < allSameId.length; si++) {\n if (!allSameId[si].closest('.tokui-conditional-hidden')) {\n el = allSameId[si];\n break;\n }\n }\n }\n\n if (!el) return '';\n\n if (el.tagName === 'SELECT') {\n if (el.selectedIndex >= 0 && el.options[el.selectedIndex]) {\n return el.options[el.selectedIndex].getAttribute('value') || el.options[el.selectedIndex].value || '';\n }\n\n return '';\n }\n\n return el.value || '';\n }\n /**\r\n * 半天模式:拼接 \"YYYY-MM-DD 上午/下午\" 字符串\r\n */\n\n\n function buildHalfDayTimeStr(timestampMs, halfElId, scope) {\n var d = new Date(timestampMs);\n var y = d.getFullYear();\n var m = String(d.getMonth() + 1).padStart(2, '0');\n var day = String(d.getDate()).padStart(2, '0');\n\n var halfVal = _readFieldValueById(halfElId, scope);\n\n var halfLabel = {\n 1: '上午',\n 2: '下午'\n }[halfVal] || '上午';\n return y + '-' + m + '-' + day + ' ' + halfLabel;\n } // ============ 注册暂存处理 ============\n // DSL: [btn tx:暂存 clk:saveDraft]\n\n\n window.TokUI.registerHandler('saveDraft', /*#__PURE__*/function () {\n var _ref10 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee11(data, event, element) {\n var form, allData, submitUrl;\n return regeneratorRuntime.wrap(function _callee11$(_context11) {\n while (1) {\n switch (_context11.prev = _context11.next) {\n case 0:\n form = resolveForm(element);\n\n if (form) {\n _context11.next = 3;\n break;\n }\n\n return _context11.abrupt(\"return\");\n\n case 3:\n if (!form._tvSubmitting) {\n _context11.next = 6;\n break;\n }\n\n (0, _message.showMessage)('正在处理中,请稍候...', 'warning');\n return _context11.abrupt(\"return\");\n\n case 6:\n form._tvSubmitting = true;\n allData = collectFormData(element); // 清理隐藏辅助字段\n\n delete allData._holidayType;\n delete allData._leaveType;\n delete allData._balanceMode;\n delete allData._durationDay; // 暂存标记\n\n allData.saveFlag = 0;\n submitUrl = form.getAttribute('data-url');\n\n if (submitUrl) {\n _context11.next = 18;\n break;\n }\n\n form._tvSubmitting = false;\n (0, _message.showMessage)('暂存失败:未配置提交地址', 'error');\n return _context11.abrupt(\"return\");\n\n case 18:\n safeFeed('[callout t:info tt:暂存中 tx:正在保存草稿...]', form);\n _context11.prev = 19;\n _context11.next = 22;\n return _request.default.post(submitUrl, allData);\n\n case 22:\n (0, _message.showMessage)('草稿已保存', 'success');\n safeFeed('[callout t:success tt:保存成功 tx:\"草稿已保存\"]', form);\n _context11.next = 28;\n break;\n\n case 26:\n _context11.prev = 26;\n _context11.t0 = _context11[\"catch\"](19);\n\n case 28:\n _context11.prev = 28;\n form._tvSubmitting = false;\n return _context11.finish(28);\n\n case 31:\n case \"end\":\n return _context11.stop();\n }\n }\n }, _callee11, null, [[19, 26, 28, 31]]);\n }));\n\n return function (_x24, _x25, _x26) {\n return _ref10.apply(this, arguments);\n };\n }());\n } // end window.TokUI.__validatorHandlersBound 全局守卫\n // ============ DOM 事件委托校验(data-trigger) ============\n\n\n function handleTriggerEvent(e) {\n var el = e.target.hasAttribute('data-trigger') ? e.target : e.target.closest('[data-trigger]');\n if (!el) return;\n var triggers = (el.getAttribute('data-trigger') || '').split(',').map(function (t) {\n return t.trim();\n });\n if (!triggers.includes(e.type)) return;\n var key = el.id || el.name || String(Math.random());\n\n if (DEBOUNCED_EVENTS.includes(e.type)) {\n cancelDebounce(key);\n debounce(key, function () {\n return runFieldValidation(el);\n }, debounceMs);\n } else {\n cancelDebounce(key);\n runFieldValidation(el);\n }\n }\n\n TRIGGER_EVENTS.forEach(function (evt) {\n container.addEventListener(evt, handleTriggerEvent, true);\n }); // ============ change 事件委托:触发 clk:* 处理器 ============\n // datepicker/select 等组件选择值后触发 change 事件,但 TokUI 的 clk:\n // 只在 click 时触发,导致 calcLeaveDuration 等处理器不执行。\n // 这里在容器上代理 change 事件,查找元素及其祖先上的 clk: 属性并调用对应 handler。\n\n function handleClkOnChange(e) {\n // 从事件目标向上查找带 data-tokui-clk 属性的元素\n // (patch.js 将 DSL 的 clk: 透传为 DOM 上的 data-tokui-clk)\n var target = e.target;\n var matched = false;\n\n while (target && target !== container) {\n if (target.hasAttribute && target.getAttribute) {\n var clkVal = target.getAttribute('data-tokui-clk');\n\n if (clkVal) {\n clkVal.split(',').forEach(function (name) {\n name = name.trim();\n if (!name) return;\n var handler = _clkHandlers[name];\n\n if (typeof handler === 'function') {\n try {\n handler({}, e, target);\n } catch (ex) {\n console.warn('[Validator] clk handler error:', name, ex);\n }\n }\n });\n matched = true;\n break;\n } // 也检查父级 .tokui-field 上的 data-tokui-clk\n\n\n if (target.closest) {\n var field = target.closest('.tokui-field');\n\n if (field && field !== target) {\n var fieldClk = field.getAttribute('data-tokui-clk');\n\n if (fieldClk) {\n fieldClk.split(',').forEach(function (name) {\n name = name.trim();\n if (!name) return;\n var handler = _clkHandlers[name];\n\n if (typeof handler === 'function') {\n try {\n handler({}, e, field);\n } catch (ex) {\n console.warn('[Validator] clk handler error:', name, ex);\n }\n }\n });\n matched = true;\n break;\n }\n }\n }\n }\n\n target = target.parentElement;\n } // ★ fallback:未匹配 clk,但 change 源在 data-calc-duration-url 表单内 → 自动触发时长计算\n // 解决 TokUI datetimepicker 内部 change 事件不冒泡到 container 的问题\n\n\n if (!matched && e.target) {\n var calcField = e.target.closest('[data-calc-duration-url]');\n\n if (!calcField) {\n // 尝试从 .tokui-field 或 form 内查找\n var calcForm = e.target.closest('[data-form-id]') || e.target.closest('form') || e.target.closest('.tokui-stream');\n\n if (calcForm) {\n calcField = calcForm.querySelector('[data-calc-duration-url]');\n }\n }\n\n if (calcField) {\n var calcUrl = calcField.getAttribute('data-calc-duration-url');\n\n if (calcUrl) {\n // ★ fallback 只调用 calcLeaveDuration:data-calc-duration-body-dep-* 参数映射\n // 是为请假/外出设计的,加班表单有独立的 clk:calcOvertimeDuration 直接匹配。\n // 同时 calcOvertimeDuration 内部校验逻辑不同,传错参数会报警。\n var leaveHandler = _clkHandlers['calcLeaveDuration'];\n\n if (typeof leaveHandler === 'function') {\n try {\n leaveHandler({}, e, calcField);\n } catch (ex) {\n console.warn('[Validator] calcLeaveDuration fallback error:', ex);\n }\n }\n }\n }\n }\n } // 在容器上监听 change 事件(捕获阶段,确保优先处理)\n\n\n container.addEventListener('change', handleClkOnChange, true); // ★ 补充 input 事件监听:TokUI datetimepicker 值变化可能只触发 input 不触发 change\n\n container.addEventListener('input', handleClkOnChange, true); // ============ click 事件委托:处理 sub:formSubmit / sub:formReset / clk:* ============\n // TokUI 内部的按钮点击机制可能未正确调用 registerHandler 注册的 handler,\n // 这里在容器上代理 click 事件,确保 sub: 和 clk: 属性的按钮能正确触发。\n\n container.addEventListener('click', function (e) {\n var target = e.target; // 向上查找 button 元素或带 data-tokui-sub / data-tokui-clk 的元素\n\n while (target && target !== container) {\n if (target.hasAttribute && target.getAttribute) {\n // ★★ 处理 TokUI 原生 reset 按钮([btn tx:重置 reset] → <button type=\"reset\">)\n // 这种按钮没有 data-tokui-sub/clk 属性,需单独处理\n if (target.tagName === 'BUTTON' && target.getAttribute('type') === 'reset') {\n var resetHandler = _clkHandlers['formReset'] || _clkHandlers['doReset'];\n\n if (typeof resetHandler === 'function') {\n e.preventDefault();\n e.stopPropagation();\n\n try {\n resetHandler({}, e, target);\n } catch (ex) {\n console.warn('[Validator] reset handler error:', ex);\n }\n\n return;\n }\n } // 检查 sub: 属性(TokUI 可能透传为 data-tokui-sub 或 sub)\n\n\n var subVal = target.getAttribute('data-tokui-sub') || target.getAttribute('sub') || '';\n\n if (subVal) {\n var subHandler = _clkHandlers[subVal];\n\n if (typeof subHandler === 'function') {\n e.preventDefault();\n e.stopPropagation();\n\n try {\n subHandler({}, e, target);\n } catch (ex) {\n console.warn('[Validator] sub handler error:', subVal, ex);\n }\n\n return;\n }\n } // 检查 clk: 属性(通过 data-tokui-clk 透传)\n\n\n var clkVal = target.getAttribute('data-tokui-clk') || '';\n\n if (clkVal) {\n clkVal.split(',').forEach(function (name) {\n name = name.trim();\n if (!name) return;\n var handler = _clkHandlers[name];\n\n if (typeof handler === 'function') {\n try {\n handler({}, e, target);\n } catch (ex) {\n console.warn('[Validator] clk handler error:', name, ex);\n }\n }\n }); // 不 return,让 TokUI 原有处理也执行\n }\n }\n\n target = target.parentElement;\n }\n }, true); // ============ 数据预加载(data-load / data-load-list) ============\n //\n // 两种模式,共用 $字段名$ 语法:\n //\n // ★ 详情模式(data-load) — 取单条对象,替换 DOM 中的 $字段名$\n // [card id:detail data-load:\"/api/user/1\"]\n // [h3 \"$title$\"]\n // [p \"姓名: $name$\"]\n // [p \"邮箱: $email$\"]\n // [img s:\"$avatar$\"]\n // [/card]\n //\n // ★ 列表模式(data-load-list)— 取数组,按 [repeat] 模板重复渲染\n // [div id:cardList data-load-list:\"/api/users\"]\n // [repeat]\n // [card tt:\"$name$\" tx:\"$dept$\"]\n // [p \"$intro$\"]\n // [btn tx:\"编辑\" clk:\"edit-$id$\"]\n // [/card]\n // [/repeat]\n // [/div]\n //\n // ★ Select 兼容 — 列表模式特例,直接填充 <option>\n // [select id:city data-load:\"/api/cities\"]\n // [repeat][opt v:\"$code$\" tx:\"$name$\"][/repeat]\n // [/select]\n //\n // 注意:[repeat] 通过 patchTokUIParser() 注册为原始内容容器,不修改核心源码。\n // =========================================================\n // ---------- JSON 解析工具 ----------\n\n function resolveList(json) {\n var raw = json.data !== undefined ? json.data : json;\n if (Array.isArray(raw)) return raw;\n\n if (raw && (0, _typeof2.default)(raw) === 'object') {\n for (var _i = 0, _Object$keys = Object.keys(raw); _i < _Object$keys.length; _i++) {\n var key = _Object$keys[_i];\n if (Array.isArray(raw[key])) return raw[key];\n }\n }\n\n return [];\n }\n\n function resolveObject(json) {\n var raw = json.data !== undefined ? json.data : json;\n return raw && (0, _typeof2.default)(raw) === 'object' && !Array.isArray(raw) ? raw : raw;\n }\n\n var mapOptions = options.buildOptions || function (json) {\n var list = resolveList(json);\n return list.map(function (item) {\n // v 字段:优先 v,然后尝试 code/id/value/no/holidayNo 等常见字段\n var vKeys = ['v', 'code', 'id', 'value', 'no', 'No', 'NO', 'holidayNo', 'holidayId', 'userNo', 'companyNo', 'departNo', 'departmentNo'];\n var txKeys = ['tx', 'name', 'label', 'text', 'holidayName', 'companyName', 'departName', 'departmentName'];\n var v = '';\n var tx = '';\n\n for (var _i2 = 0, _vKeys = vKeys; _i2 < _vKeys.length; _i2++) {\n var k = _vKeys[_i2];\n\n if (item[k] !== undefined && item[k] !== null && item[k] !== '') {\n v = item[k];\n break;\n }\n }\n\n for (var _i3 = 0, _txKeys = txKeys; _i3 < _txKeys.length; _i3++) {\n var _k = _txKeys[_i3];\n\n if (item[_k] !== undefined && item[_k] !== null && item[_k] !== '') {\n tx = item[_k];\n break;\n }\n } // 如果仍然没有匹配到,从所有字段中取第一个非空值\n\n\n if (!v) {\n for (var _k2 in item) {\n if (item[_k2] !== undefined && item[_k2] !== null && item[_k2] !== '') {\n v = item[_k2];\n break;\n }\n }\n }\n\n if (!tx) {\n for (var _k3 in item) {\n if (item[_k3] !== undefined && item[_k3] !== null && item[_k3] !== '' && item[_k3] !== v) {\n tx = item[_k3];\n break;\n }\n }\n }\n\n return {\n v: v !== undefined && v !== null ? String(v) : '',\n tx: tx !== undefined && tx !== null ? String(tx) : ''\n };\n });\n };\n /**\r\n * 清除 TokUI DSL 解析后残留在 URL 值上的反引号与空格\r\n * DSL 中 data-load:\" `https://...` \" → 解析后 DOM 属性值为 `https://...`\r\n * 需要去掉首尾反引号才是有效 URL\r\n */\n\n\n function cleanUrl(raw) {\n return String(raw || '').trim().replace(/^`/, '').replace(/`$/, '');\n }\n\n function resolveUrl(tmpl, scope) {\n tmpl = cleanUrl(tmpl);\n var root = scope && scope.querySelectorAll ? scope : container && container.querySelectorAll ? container : document;\n return tmpl.replace(/\\$\\{(\\w+)\\}/g, function (_, id) {\n // 优先查 DOM 元素(前端级联引用,如 ${companyNo}),限定在当前表单容器避免跨消息串扰\n var el = queryById(id, root);\n\n if (el) {\n // <select>:始终取选中 option 的 value 属性,避免浏览器返回 innerText 的差异\n if (el.tagName === 'SELECT' && el.selectedIndex >= 0 && el.options[el.selectedIndex]) {\n return el.options[el.selectedIndex].getAttribute('value') || el.options[el.selectedIndex].value || '';\n }\n\n return el.value || '';\n } // 其次直接从 getAuth() 取值(loginUserNo / loginCompanyNo),始终读取最新值\n\n\n if (id === 'loginUserNo' || id === 'loginCompanyNo') {\n var auth = (0, _request.getAuth)();\n if (id === 'loginUserNo') return auth.userno || auth.user || '';\n if (id === 'loginCompanyNo') return auth.companyNo || '';\n } // 再查全局参数映射兜底\n\n\n if (window.__tokuiParams && window.__tokuiParams[id] !== undefined) {\n return String(window.__tokuiParams[id]);\n } // 最后查前端环境变量(src/lib/env/index.js 自动按域名匹配)\n\n\n try {\n var env = window.__tokuiEnv || null;\n\n if (!env) {\n // 动态引入避免循环依赖\n var envModule = window.__TOKUI_ENV_CACHE;\n\n if (!envModule) {\n // 通过 require 或全局挂载获取,这里用 script 引入方式:env 已挂载到 window\n env = window.__tokuiEnv;\n } else {\n env = envModule;\n }\n }\n\n if (env && env[id] !== undefined) {\n return String(env[id]);\n }\n } catch (e) {// ignore\n }\n\n return '';\n });\n } // ============ 审批流程集成(Approval Flow) ============\n // 当表单配置 data-approval-type 时,采用三阶段架构:\n // 1. initApprovalFlow → processIsOpen(获取审批状态和变量列表)\n // 2. refreshApprovalFlow → processPreview(字段变更时重新预览审批节点)+ 可视化渲染\n // 3. saveApprovalFlowState → processSave(提交时保存审批配置,获取 uniqueId)\n //\n // DSL 配置示例(form 标签上):\n // data-approval-type:\"leave\"\n // data-approval-prefix:\"https://api.qgbtech.cn/sgb-meta-design\"\n // data-approval-variable-map:\"type:type,departNo:departmentNo,durationDay:durationDay\"\n // 审批 handler 全局只注册一次,但 setupValidator 可能为多个消息容器重复调用。\n // 状态必须跨 setupValidator 闭包共享,否则界面选择与全局提交 handler 会读取不同 WeakMap。\n\n\n var _approvalStates = window.__tokuiApprovalStates = window.__tokuiApprovalStates || new WeakMap();\n\n function getApprovalState(form) {\n if (!_approvalStates.has(form)) {\n var approvalType = (form.getAttribute('data-approval-type') || '').trim();\n var approvalPrefix = cleanUrl(form.getAttribute('data-approval-prefix') || ''); // 解析 variable-map\n\n var variableMap = {};\n var variableMapStr = (form.getAttribute('data-approval-variable-map') || '').trim();\n\n if (variableMapStr) {\n variableMapStr.split(',').forEach(function (s) {\n var parts = s.trim().split(':');\n if (parts.length >= 2) variableMap[parts[0].trim()] = parts[1].trim();\n });\n }\n\n _approvalStates.set(form, {\n _form: form,\n approvalType: approvalType,\n approvalPrefix: approvalPrefix,\n variableMap: variableMap,\n approvaStatus: -1,\n variableList: [],\n processIdFromOpen: '',\n processId: '',\n nextNode: '',\n conditionMap: {},\n processList: [],\n // 用户手工选择的审批人独立于 processPreview,避免异步刷新覆盖选择结果\n selectedApprovers: {},\n container: null,\n inited: false\n });\n }\n\n return _approvalStates.get(form);\n }\n\n function getApproverStateKey(node, nodeIndex) {\n // processPreview 可能为同一节点返回不同 nodeId/id,使用稳定的节点序号和名称匹配\n return String(nodeIndex) + ':' + (node && (node.nodeName || node.nodeId || node.id || ''));\n }\n\n function rememberSelectedApprovers(state, node, nodeIndex) {\n if (!state || !node) return;\n var approvers = (node.approvers || []).map(function (approver) {\n return Object.assign({}, approver);\n });\n state.selectedApprovers[getApproverStateKey(node, nodeIndex)] = approvers;\n }\n\n function restoreSelectedApprovers(state, processList) {\n if (!state || !processList) return;\n processList.forEach(function (node, nodeIndex) {\n var key = getApproverStateKey(node, nodeIndex);\n\n if (Object.prototype.hasOwnProperty.call(state.selectedApprovers, key)) {\n node.approvers = state.selectedApprovers[key].map(function (approver) {\n return Object.assign({}, approver);\n });\n }\n });\n }\n\n function syncSelectedApproversToProcessList(state) {\n if (!state || !state.processList) return;\n restoreSelectedApprovers(state, state.processList);\n }\n\n function buildProcessSaveList(state) {\n if (!state || !state.processList) return [];\n syncSelectedApproversToProcessList(state);\n return state.processList.map(function (node) {\n var saveNode = Object.assign({}, node);\n\n if (Array.isArray(node.approvers)) {\n saveNode.approvers = node.approvers.map(function (approver) {\n return Object.assign({}, approver);\n });\n }\n\n return saveNode;\n });\n } // 从表单构建 typeList(用于 processPreview)\n\n\n function buildTypeListFromForm(form, state) {\n var auth = (0, _request.getAuth)();\n var companyNo = auth.companyNo || '';\n var userno = auth.userno || auth.user || '';\n var allData = collectFormData(form);\n var typeScope = resolveScope(form);\n var typeList = {}; // 按 variableMap 映射表单字段 → 审批变量\n\n Object.keys(state.variableMap).forEach(function (formFieldId) {\n var approvalVarName = state.variableMap[formFieldId];\n var el = queryById(formFieldId, typeScope); // 回退:durationDay 可能是隐藏字段 _durationDay\n\n if (!el && (formFieldId === 'durationDay' || formFieldId === '_durationDay')) {\n el = queryById('_durationDay', typeScope) || queryById('durationDay', typeScope);\n } // ★ 部门字段:各表单 DSL 的部门 select id 可能是 departNo / departmentNo / applyDeptId 之一,\n // 需跨候选 id 查找,避免外出表单(applyDeptId)取不到部门值\n\n\n if (!el && (formFieldId === 'departNo' || formFieldId === '_departNo' || formFieldId === 'departmentNo' || formFieldId === '_departmentNo')) {\n el = queryById('applyDeptId', typeScope) || queryById('_applyDeptId', typeScope) || queryById('departmentNo', typeScope) || queryById('_departmentNo', typeScope) || queryById('departNo', typeScope) || queryById('_departNo', typeScope);\n }\n\n var val = '';\n\n if (el) {\n if (el.tagName === 'SELECT' && el.selectedIndex >= 0 && el.options[el.selectedIndex]) {\n val = el.options[el.selectedIndex].getAttribute('value') || el.options[el.selectedIndex].value || '';\n } else {\n val = el.value || '';\n }\n }\n\n if (!val && allData[formFieldId] !== undefined) val = String(allData[formFieldId]); // 回退:也尝试 _ 前缀版本的 hidden 字段\n\n if (!val && formFieldId.indexOf('_') !== 0) {\n var hiddenVal = allData['_' + formFieldId];\n if (hiddenVal !== undefined) val = String(hiddenVal);\n }\n\n typeList[approvalVarName] = val;\n }); // 确保必有字段\n\n if (!typeList.proposer) typeList.proposer = userno;\n if (!typeList.companyId) typeList.companyId = companyNo;\n if (!typeList.userId) typeList.userId = userno; // ★ 对齐源项目 leaveApply.vue / goOutApplyAdd.vue 的取值:\n // departmentNo = 部门值(departNo / departmentNo / applyDeptId),为空时回退 companyNo(申请单位)\n\n if (!typeList.departmentNo) {\n typeList.departmentNo = allData.applyDeptId || allData.departNo || allData.departmentNo || allData.companyNo || '';\n }\n\n return typeList;\n } // ============ 审批字段变更监听辅助 ============\n // TokUI 的 datetimepicker/datepicker/picker 等组件在确认时触发 change 事件,\n // 但事件目标往往是内部只读 input(无 id),真正的字段 id 在其兄弟隐藏 input 上。\n // 因此不能只依赖 e.target.id,需在事件目标祖先与所属组件容器内定位相关字段。\n\n\n function getApprovalRelevantIds(state) {\n var ids = ['type', 'departNo', 'departmentNo', 'startTime', 'endTime', 'startTimeHalf', 'endTimeHalf', 'duration', 'durationDay', '_durationDay'];\n Object.keys(state.variableMap).forEach(function (f) {\n if (ids.indexOf(f) < 0) ids.push(f);\n if (ids.indexOf('_' + f) < 0) ids.push('_' + f);\n });\n return ids;\n }\n\n function findRelevantFieldId(target, state) {\n if (!target) return '';\n var scope = resolveScope(target);\n var ids = getApprovalRelevantIds(state); // 1) 从事件目标逐级向上找匹配的 id/name\n\n var node = target;\n\n while (node && node !== scope && node !== document) {\n if (node.id && ids.indexOf(node.id) >= 0) return node.id;\n if (node.name && ids.indexOf(node.name) >= 0) return node.name;\n node = node.parentElement;\n } // 2) 在事件目标所属组件容器内查找相关字段(覆盖 datepicker 内部只读 input 无 id 的情况)\n\n\n var comp = target.closest ? target.closest('.tokui-datepicker,.tokui-datetimepicker,.tokui-timepicker,.tokui-picker,.tokui-select,.tokui-field') : null;\n\n if (comp) {\n for (var i = 0; i < ids.length; i++) {\n if (comp.querySelector('#' + ids[i]) || comp.querySelector('[name=\"' + ids[i] + '\"]')) return ids[i];\n }\n }\n\n return '';\n } // ============ 表单就绪检测(审批流程前置门槛) ============\n // SSE 流式渲染是增量构建 DOM 的,[form data-approval-type] 先于 btngroup、\n // 时间选择器、下拉选项等出现。若在 DOM 半成品上初始化审批流程,会:\n // 1) processPreview 读不到完整 typeList,甚至 collectFormData 报错;\n // 2) renderApprovalFlow 找不到 btngroup,把审批节点插入错误位置。\n // 因此审批初始化/预览必须以「表单完全渲染」为前提。\n\n\n function isFormReady(form) {\n var scope = resolveScope(form) || form;\n if (!scope || !scope.getAttribute) return false; // 显式标记优先:ChatDetail endStream + reconcile 后调用 markFormReady 设置\n\n if (scope.getAttribute('data-tokui-ready') === '1') return true; // 仅在无 scope 标记(非 ChatDetail 直接调用 setupValidator)时才启用启发式回退,\n // 避免 SSE 流式渲染期间按钮刚出现就被误判为「已就绪」而提前初始化审批。\n\n if (!scope.getAttribute('data-tokui-scope')) {\n return !!scope.querySelector('[data-tokui-sub=\"formSubmit\"], button');\n }\n\n return false;\n } // ChatDetail._reconcileAfterStream 在 endStream 且所有对齐工作完成后调用,\n // 标记表单就绪,并据此初始化 / 刷新审批流程。\n\n\n function markFormReady(scope) {\n if (!scope || !scope.setAttribute) return;\n scope.setAttribute('data-tokui-ready', '1');\n\n try {\n var forms = scope.querySelectorAll('[data-approval-type]');\n forms.forEach(function (el) {\n var form = el.closest('form') || el.closest('[data-form-id]') || el;\n if (!form) return; // 停止可能存在的就绪轮询,避免与本次初始化重复\n\n form._approvalReadyWaiter = false;\n var st = getApprovalState(form);\n\n if (!st.inited) {\n initApprovalFlow(form);\n } else {\n // 已初始化:字段回填完成后再次刷新审批预览\n refreshApprovalFlow(form);\n }\n });\n } catch (e) {\n console.warn('[Approval] markFormReady error:', e.message);\n }\n\n bindPickerSafeArea();\n } // ============ Picker 弹层安全区守卫(H5):跟随字段,安全距离不足才回弹 ============\n // tokui 的日期/时间选择弹层为 position:fixed,由 tokui 按触发字段原生定位(跟随)。\n // 但当字段贴近屏幕底部/右侧时,弹层会超出安全区被遮挡。此处不改变默认跟随定位,\n // 只在弹层实际超出 底部/右侧安全区 时才用 JS 回弹贴合安全区,其余情况维持跟随。\n\n\n function getSafeInset(name, def) {\n try {\n var v = getComputedStyle(document.documentElement).getPropertyValue(name);\n var n = parseFloat(v);\n return isNaN(n) ? def : n;\n } catch (e) {\n return def;\n }\n }\n\n function clampPickerDropdown() {\n var sel = '.tokui-datepicker-dropdown,.tokui-timepicker-dropdown,.tokui-datetimepicker-dropdown';\n var nodes = document.querySelectorAll(sel);\n if (!nodes.length) return; // ★ 弹层打开期间隐藏聊天界面的\"向下箭头\"滚动按钮(.scroll-bottom-wrap / .scroll-btns):\n // 它在 .chat-body(overflow-y:auto) 内是 absolute/sticky+z-index:99,\n // 与弹层同处 .chat-body 堆叠上下文,在 iOS 上会叠在弹层\"确定\"按钮上方遮挡点击。\n // 弹层关闭后(display:none)恢复显示。\n\n var anyVisible = false;\n\n for (var _v = 0; _v < nodes.length; _v++) {\n if (nodes[_v].style.display !== 'none' && nodes[_v].offsetHeight > 0) {\n anyVisible = true;\n break;\n }\n }\n\n var scrollBtns = document.querySelectorAll('.scroll-bottom-wrap,.scroll-btns');\n\n for (var _s = 0; _s < scrollBtns.length; _s++) {\n scrollBtns[_s].style.display = anyVisible ? 'none' : '';\n }\n\n var vw = window.innerWidth || document.documentElement.clientWidth; // 用布局视口高度(clientHeight)而非 innerHeight:position:fixed 弹层是贴合\n // clientHeight 对齐渲染的。iOS 上 innerHeight 会把工具栏/底栏也算进去导致比\n // 实际可视区高,据此判定会漏掉\"底部已超界\",弹层仍被遮挡;安卓两者通常相等。\n\n var vh = document.documentElement.clientHeight || window.innerHeight;\n var safeB = getSafeInset('safe-area-inset-bottom', 12);\n var safeR = getSafeInset('safe-area-inset-right', 4);\n var safeT = getSafeInset('safe-area-inset-top', 0);\n var maxBottom = vh - safeB - 6;\n var minTop = safeT + 6;\n var maxRight = vw - safeR - 6; // ★ 底部聊天输入框/发送框(.chat-footer)占据屏底:弹层\"确定\"按钮若只贴安全区,\n // 仍会与该输入框重叠。需要把弹层底边收敛到聊天底栏的上沿之上。\n\n var vBarTop = vh;\n var vBars = document.querySelectorAll('.chat-footer');\n\n for (var vb = 0; vb < vBars.length; vb++) {\n var vbRect = vBars[vb].getBoundingClientRect(); // 仅在底栏当前处于可视区(bottom 在屏内)时参与计算,否则退化为全屏判定\n\n if (vbRect.top >= 0 && vbRect.bottom > 0 && vbRect.top < vBarTop) {\n vBarTop = vbRect.top;\n }\n }\n\n if (vBarTop <= minTop) vBarTop = vh;\n var bottomLimit = Math.min(vh - safeB - 6, vBarTop - 6); // ★ 顶部聊天标题栏(.chat-header)占据屏顶:弹层向上展开时顶部会被它遮住。\n // 需要把弹层顶边收敛到聊天顶栏的下沿之下。\n\n var vHeaderBottom = 0;\n var vHeaders = document.querySelectorAll('.chat-header');\n\n for (var vhIdx = 0; vhIdx < vHeaders.length; vhIdx++) {\n var vhRect = vHeaders[vhIdx].getBoundingClientRect();\n\n if (vhRect.top >= 0 && vhRect.bottom > 0 && vhRect.bottom > vHeaderBottom) {\n vHeaderBottom = vhRect.bottom;\n }\n }\n\n var topLimit = Math.max(minTop, vHeaderBottom + 6);\n var availH = bottomLimit - topLimit; // 上下栏之间的可用高度\n\n for (var i = 0; i < nodes.length; i++) {\n var el = nodes[i];\n if (!el.style || el.style.display === 'none' || el.offsetHeight === 0) continue; // ★ iOS 稳健兜底:显式固定弹层并抬到最上层(弹层底部不被底部输入框/发送框 DOM 遮盖)\n\n el.style.position = 'fixed';\n el.style.zIndex = '99999';\n var h = el.offsetHeight;\n var r = el.getBoundingClientRect();\n var overBottom = r.bottom > bottomLimit;\n var overTop = r.top < topLimit;\n var overRight = r.right > maxRight;\n\n if (overBottom || overTop) {\n // 弹层高度超过上下栏之间的可用空间 → 强制贴顶栏下沿并内部滚动\n if (h >= availH) {\n el.style.top = topLimit + 'px';\n el.style.bottom = 'auto';\n el.style.maxHeight = availH + 'px';\n el.style.overflowY = 'auto';\n continue;\n } // 场景1:字段下方放不下,但上方有足够空间 → 改为向上展开(弹层底部贴近字段上沿)\n\n\n var upwardY = r.top - h - 6;\n\n if (upwardY >= topLimit && !overTop && upwardY < r.top) {\n el.style.top = upwardY + 'px';\n el.style.bottom = 'auto';\n el.style.maxHeight = '';\n } else {\n // 场景2:上下空间都不足 → 贴到底栏(或安全区)之上,保证\"确定\"按钮完全在屏内、\n // 且不被底部输入框/发送框遮挡\n el.style.top = 'auto';\n el.style.bottom = vh - bottomLimit + 'px'; // 若高度仍超可视区:回顶栏下沿对齐并允许弹层内部滚动\n\n var r2 = el.getBoundingClientRect();\n\n if (r2.top < topLimit) {\n el.style.bottom = 'auto';\n el.style.top = topLimit + 'px';\n el.style.maxHeight = availH + 'px';\n el.style.overflowY = 'auto';\n }\n }\n } // 超出右侧安全边界 → 贴右侧安全区内\n\n\n if (overRight) {\n el.style.left = 'auto';\n el.style.right = Math.max(6, safeR + 6) + 'px';\n r = el.getBoundingClientRect();\n if (r.right > maxRight) el.style.transform = 'none';\n }\n }\n }\n\n function bindPickerSafeArea() {\n if (window._tokuiPickerSafeBound) return;\n window._tokuiPickerSafeBound = true;\n if (typeof MutationObserver === 'undefined') return;\n var clamp = clampPickerDropdown;\n var _clampT = null; // 弹层打开带 0.3s 淡入动画,首帧 offsetHeight 可能为 0 被跳过,\n // 故在多个时间点各夹一次,确保以\"最终布局尺寸\"判定超界并最终落位在屏内\n\n function schedule() {\n clamp();\n clearTimeout(_clampT);\n _clampT = setTimeout(function () {\n clamp();\n setTimeout(clamp, 220); // 0.3s 淡入结束后再夹一次,收敛最终位置\n }, 120);\n }\n\n var obs = new MutationObserver(schedule);\n\n try {\n obs.observe(document.body, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['style', 'class']\n });\n } catch (e) {\n /* 观察失败则退化为仅监听滚动/缩放 */\n }\n\n window.addEventListener('resize', schedule);\n window.addEventListener('orientationchange', schedule);\n window.addEventListener('scroll', schedule, true);\n } // ============ 阶段 1:initApprovalFlow — 表单加载时初始化 ============\n\n\n function initApprovalFlow(_x27) {\n return _initApprovalFlow.apply(this, arguments);\n } // ============ 阶段 2:refreshApprovalFlow — 字段变更时重新预览 ============\n\n\n function _initApprovalFlow() {\n _initApprovalFlow = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee22(form) {\n var state, _readyTries, auth, companyNo, processIsOpenUrl, openJson, openData, _approvalDebounceTimer, scheduleRefresh, _deptFieldIds, _deptScope, _dfi, _dfEl, _approvalScope, _approvalObs, mapKeys;\n\n return regeneratorRuntime.wrap(function _callee22$(_context22) {\n while (1) {\n switch (_context22.prev = _context22.next) {\n case 0:\n state = getApprovalState(form);\n\n if (!state.inited) {\n _context22.next = 3;\n break;\n }\n\n return _context22.abrupt(\"return\");\n\n case 3:\n if (state.approvalType) {\n _context22.next = 5;\n break;\n }\n\n return _context22.abrupt(\"return\");\n\n case 5:\n if (isFormReady(form)) {\n _context22.next = 8;\n break;\n }\n\n if (!form._approvalReadyWaiter) {\n form._approvalReadyWaiter = true;\n _readyTries = 0;\n\n (function waitReady() {\n if (!form._approvalReadyWaiter) return; // 已被 markFormReady 接管\n\n if (isFormReady(form)) {\n form._approvalReadyWaiter = false;\n initApprovalFlow(form);\n return;\n }\n\n if (++_readyTries > 100) {\n // 约 10s 兜底\n form._approvalReadyWaiter = false;\n console.warn('[Approval] 表单长时间未就绪,跳过审批初始化:', form.id || form.getAttribute('data-form-id'));\n return;\n }\n\n setTimeout(waitReady, 100);\n })();\n }\n\n return _context22.abrupt(\"return\");\n\n case 8:\n state.inited = true;\n auth = (0, _request.getAuth)();\n companyNo = auth.companyNo || '';\n _context22.prev = 11;\n processIsOpenUrl = state.approvalPrefix + '/v1/pc/process/processIsOpen';\n _context22.next = 15;\n return _request.default.post(processIsOpenUrl, {\n companyNo: companyNo,\n type: state.approvalType\n });\n\n case 15:\n openJson = _context22.sent;\n openData = openJson.data || {};\n state.approvaStatus = openData.status;\n state.variableList = openData.variableList || [];\n state.processIdFromOpen = openData.id || ''; // 审批未开启,跳过\n\n if (!(state.approvaStatus !== 0)) {\n _context22.next = 22;\n break;\n }\n\n return _context22.abrupt(\"return\");\n\n case 22:\n if (!(state.variableList.length === 0)) {\n _context22.next = 25;\n break;\n }\n\n _context22.next = 25;\n return refreshApprovalFlow(form);\n\n case 25:\n if (form._approvalChangeBound) {\n _context22.next = 44;\n break;\n }\n\n form._approvalChangeBound = true;\n _approvalDebounceTimer = null;\n\n scheduleRefresh = function scheduleRefresh() {\n state._retryCount = 0; // 用户已变更关键字段,重置自动重试计数,保证后续变更可持续刷新\n // 审批变量变化后,旧的手工选人只属于上一版流程,不能覆盖新 preview 返回的审批人\n\n state.selectedApprovers = {};\n clearTimeout(_approvalDebounceTimer);\n _approvalDebounceTimer = setTimeout(function () {\n refreshApprovalFlow(form);\n }, 150);\n }; // change:select / datepicker 等组件选择后触发\n\n\n form.addEventListener('change', function (e) {\n var fid = findRelevantFieldId(e.target, state);\n if (!fid) return;\n scheduleRefresh();\n }); // input:文本输入即时变更\n\n form.addEventListener('input', function (e) {\n var fid = findRelevantFieldId(e.target, state);\n if (!fid) return;\n scheduleRefresh();\n }); // ★ 关联\"申请部门/申请单位\"与审批流程:切换部门后重新调用审批预览接口。\n // 通用 change/input 委托依赖 findRelevantFieldId 从事件目标匹配到相关字段 id,\n // H5 移动端部门/单位可能是自定义控件、事件目标拿不到 departNo 等 id 而漏触发。\n // 这里直接对部门/单位相关字段绑定 change/input,一旦变化立即 scheduleRefresh\n // (scheduleRefresh 内部已做 150ms 防抖,多次触发不会重复调用,安全无副作用)。\n\n _deptFieldIds = ['departNo', '_departNo', 'departmentNo', '_departmentNo', 'department', 'dept', 'deptName', 'applyDept', 'companyNo', '_companyNo'];\n _deptScope = resolveScope(form);\n _dfi = 0;\n\n case 34:\n if (!(_dfi < _deptFieldIds.length)) {\n _context22.next = 43;\n break;\n }\n\n _dfEl = (_deptScope && _deptScope.querySelector ? _deptScope.querySelector('#' + _deptFieldIds[_dfi]) : null) || (_deptScope && _deptScope.querySelector ? _deptScope.querySelector('[name=\"' + _deptFieldIds[_dfi] + '\"]') : null);\n\n if (_dfEl) {\n _context22.next = 38;\n break;\n }\n\n return _context22.abrupt(\"continue\", 40);\n\n case 38:\n _dfEl.addEventListener('change', scheduleRefresh);\n\n _dfEl.addEventListener('input', scheduleRefresh);\n\n case 40:\n _dfi++;\n _context22.next = 34;\n break;\n\n case 43:\n // MutationObserver:捕获 hidden input 等不触发 change 的 value 属性变更\n try {\n _approvalScope = resolveScope(form);\n _approvalObs = new MutationObserver(function (mutations) {\n for (var mi = 0; mi < mutations.length; mi++) {\n var m = mutations[mi];\n\n if (m.type === 'attributes' && m.attributeName === 'value' && m.target && m.target.id) {\n if (getApprovalRelevantIds(state).indexOf(m.target.id) >= 0) {\n scheduleRefresh();\n break;\n }\n }\n }\n });\n\n _approvalObs.observe(_approvalScope, {\n subtree: true,\n attributes: true,\n attributeFilter: ['value']\n });\n\n form._approvalObserver = _approvalObs;\n } catch (e2) {\n console.warn('[Approval] MutationObserver 监听失败:', e2.message);\n }\n\n case 44:\n // ★ 关键修复:data-default-label 的初始 change 事件早于监听器注册,\n // 需要立即检查字段是否已有值,如有则直接触发 preview\n mapKeys = Object.keys(state.variableMap);\n refreshApprovalFlow(form);\n _context22.next = 52;\n break;\n\n case 48:\n _context22.prev = 48;\n _context22.t0 = _context22[\"catch\"](11);\n console.warn('[Approval] init error:', _context22.t0.message);\n safeFeed('[callout t:error tt:审批异常 tx:\"' + _context22.t0.message + '\"]', form);\n\n case 52:\n case \"end\":\n return _context22.stop();\n }\n }\n }, _callee22, null, [[11, 48]]);\n }));\n return _initApprovalFlow.apply(this, arguments);\n }\n\n function refreshApprovalFlow(_x28) {\n return _refreshApprovalFlow.apply(this, arguments);\n } // 清除审批可视化渲染\n\n\n function _refreshApprovalFlow() {\n _refreshApprovalFlow = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee23(form) {\n var state, typeList, typeScope, missingList, complete, fixedKeys, requiredApprovalVars, deptAlreadyMissing, auth, companyNo, previewUrl, previewBody, previewJson, previewData, feedbackHost, oldFeedbacks;\n return regeneratorRuntime.wrap(function _callee23$(_context23) {\n while (1) {\n switch (_context23.prev = _context23.next) {\n case 0:\n state = getApprovalState(form);\n\n if (!state._stopped) {\n _context23.next = 3;\n break;\n }\n\n return _context23.abrupt(\"return\");\n\n case 3:\n if (!(state.approvaStatus !== 0)) {\n _context23.next = 5;\n break;\n }\n\n return _context23.abrupt(\"return\");\n\n case 5:\n if (!state._rendering) {\n _context23.next = 8;\n break;\n }\n\n state._refreshPending = true;\n return _context23.abrupt(\"return\");\n\n case 8:\n state._rendering = true;\n _context23.prev = 9;\n // 检查 typeList 是否完整 — 以 variableMap 为准\n // variableMap: formFieldId → typeListKey,typeList 的 key 就是 typeListKey\n typeList = buildTypeListFromForm(form, state); // ★ 当前表单作用域:后续缺失字段查询必须限定在此作用域内,避免跨表单串扰\n\n typeScope = resolveScope(form);\n missingList = [];\n complete = true; // 固定必填但由 buildTypeListFromForm 自动补齐的字段(无需等待用户填写)\n\n fixedKeys = ['proposer', 'companyId', 'userId']; // ★ 审批变量必填项:departmentNo 与 durationDay 都必须有值才允许调用 processPreview\n\n requiredApprovalVars = ['departmentNo', 'durationDay'];\n Object.keys(state.variableMap).forEach(function (formFieldId) {\n var typeListKey = state.variableMap[formFieldId];\n if (fixedKeys.indexOf(typeListKey) >= 0) return;\n\n if (!typeList[typeListKey] && typeList[typeListKey] !== 0) {\n var el = queryById(formFieldId, typeScope) || queryById('_' + formFieldId, typeScope);\n var isRequired = requiredApprovalVars.indexOf(typeListKey) >= 0; // ★ 必填字段(departmentNo/durationDay)即使 hidden/不存在也视为缺失,\n // 等待用户选择部门 / calcLeaveDuration 计算时长后由 change 事件再次触发刷新\n\n if (!isRequired && (!el || el.type === 'hidden')) {\n return;\n }\n\n missingList.push({\n varName: typeListKey,\n fieldId: formFieldId,\n hasEl: !!el,\n elTag: el ? el.tagName : '',\n elValue: el ? el.value || '' : ''\n });\n complete = false;\n }\n }); // ★ departmentNo 若未通过 variableMap 映射(仅由 buildTypeListFromForm 兜底填充),单独校验\n\n if (requiredApprovalVars.indexOf('departmentNo') >= 0 && !typeList.departmentNo) {\n deptAlreadyMissing = missingList.some(function (m) {\n return m.varName === 'departmentNo';\n });\n\n if (!deptAlreadyMissing) {\n missingList.push({\n varName: 'departmentNo',\n fieldId: 'departNo',\n hasEl: false,\n elTag: '',\n elValue: ''\n });\n complete = false;\n }\n }\n\n if (complete) {\n _context23.next = 23;\n break;\n }\n\n // 审批变量尚未全部回填,先隐藏审批模块(避免展示不完整节点)。\n clearApprovalRender(form, state); // 不在此处向用户报错;等待 change 事件 / markFormReady / calcLeaveDuration 在\n // 字段回填后再次触发 refreshApprovalFlow。仅保留静默兜底重试,覆盖无事件回填的偶发场景。\n\n state._retryCount = (state._retryCount || 0) + 1;\n\n if (state._retryCount <= 10 && !state._retryScheduled) {\n state._retryScheduled = true;\n state._retryTimer = setTimeout(function () {\n state._retryScheduled = false;\n state._retryTimer = null;\n if (state._stopped) return;\n refreshApprovalFlow(form);\n }, 1500);\n }\n\n return _context23.abrupt(\"return\");\n\n case 23:\n // ★ 字段齐全,重置重试计数(成功调用后计数归零,避免影响后续刷新)\n state._retryCount = 0;\n auth = (0, _request.getAuth)();\n companyNo = auth.companyNo || '';\n previewUrl = state.approvalPrefix + '/v1/pc/process/processPreview';\n previewBody = {\n companyNo: companyNo,\n type: state.approvalType,\n id: state.processIdFromOpen,\n typeList: typeList\n };\n _context23.next = 30;\n return _request.default.post(previewUrl, previewBody);\n\n case 30:\n previewJson = _context23.sent;\n previewData = previewJson.data || {};\n state.processId = previewData.processId || '';\n state.nextNode = previewData.nextNode || '';\n state.conditionMap = previewData.conditionMap || {};\n state.processList = previewData.processList || []; // processPreview 可能在用户选人后异步返回,优先恢复本地已确认的审批人\n\n restoreSelectedApprovers(state, state.processList); // 清除旧的报错/警告 callout(如之前 typeList 不完整时的等待提示)\n\n feedbackHost = form.closest('[data-form-id]') || form.parentElement || form;\n oldFeedbacks = feedbackHost.querySelectorAll('.tokui-validator-feedback');\n oldFeedbacks.forEach(function (el) {\n el.remove();\n });\n renderApprovalFlow(form, state);\n _context23.next = 46;\n break;\n\n case 43:\n _context23.prev = 43;\n _context23.t0 = _context23[\"catch\"](9);\n console.warn('[Approval] refresh error:', _context23.t0.message);\n\n case 46:\n _context23.prev = 46;\n state._rendering = false; // 字段在 processPreview 请求期间发生变化时,补做一次最新预览,避免界面停留在旧节点\n\n if (state._refreshPending && !state._stopped) {\n state._refreshPending = false;\n setTimeout(function () {\n refreshApprovalFlow(form);\n }, 0);\n }\n\n return _context23.finish(46);\n\n case 50:\n case \"end\":\n return _context23.stop();\n }\n }\n }, _callee23, null, [[9, 43, 46, 50]]);\n }));\n return _refreshApprovalFlow.apply(this, arguments);\n }\n\n function clearApprovalRender(form, state) {\n // 清除追踪的 container\n if (state.container && state.container.parentNode) {\n state.container.parentNode.removeChild(state.container);\n state.container = null;\n } // 清除可能残留的孤立审批流程容器(防止并发渲染导致重复)\n // 从更广范围搜索:审批流程可能被插入到 form 外部的 wrapper 中\n\n\n var formScope = form.closest('[data-form-id]') || form.parentElement || form; // 向上再找一层,确保覆盖 wrapper 所在的容器\n\n var searchScope = formScope.parentElement || formScope;\n var orphans = searchScope.querySelectorAll('.tv-approval-flow');\n orphans.forEach(function (el) {\n if (el.parentNode) el.parentNode.removeChild(el);\n }); // 同时清除可能残留的空 wrapper\n\n var emptyWrappers = searchScope.querySelectorAll('.tv-approval-wrapper:empty');\n emptyWrappers.forEach(function (el) {\n el.remove();\n });\n } // 停止单个表单的审批流程(暂停 SSE / 放弃生成时调用):\n // 清除待执行的重试定时器、就绪轮询,并移除已渲染的审批节点,避免继续输出刷新日志。\n\n\n function stopApprovalFlow(form) {\n if (!form) return;\n var state = getApprovalState(form);\n state._stopped = true;\n state._retryCount = 0;\n state._retryScheduled = false;\n\n if (state._retryTimer) {\n clearTimeout(state._retryTimer);\n state._retryTimer = null;\n } // 停止 initApprovalFlow 的就绪轮询\n\n\n if (form._approvalReadyWaiter) {\n form._approvalReadyWaiter = false;\n }\n\n clearApprovalRender(form, state);\n } // 停止 scope 内所有审批表单的审批流程\n\n\n function stopApprovalFlows(scope) {\n var root = scope && scope.querySelectorAll ? scope : document;\n var forms = root.querySelectorAll('[data-approval-type]');\n forms.forEach(function (el) {\n var form = el.closest('form') || el.closest('[data-form-id]') || el;\n if (form) stopApprovalFlow(form);\n });\n } // ============ 阶段 3:saveApprovalFlowState — 提交时保存审批 ============\n\n\n function saveApprovalFlowState(_x29) {\n return _saveApprovalFlowState.apply(this, arguments);\n } // ============ 审批流程可视化渲染 ============\n\n\n function _saveApprovalFlowState() {\n _saveApprovalFlowState = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee24(form) {\n var state, processSaveList, saveBody, saveJson, saveData;\n return regeneratorRuntime.wrap(function _callee24$(_context24) {\n while (1) {\n switch (_context24.prev = _context24.next) {\n case 0:\n state = getApprovalState(form);\n\n if (!(!state.approvalType || state.approvaStatus !== 0)) {\n _context24.next = 3;\n break;\n }\n\n return _context24.abrupt(\"return\", null);\n\n case 3:\n _context24.prev = 3;\n // 提交前再次同步,并复制节点,避免预览重绘或弹窗操作改写请求中的引用\n processSaveList = buildProcessSaveList(state);\n saveBody = {\n nextNode: state.nextNode,\n conditionMap: state.conditionMap,\n processId: state.processId,\n processList: processSaveList\n };\n _context24.next = 8;\n return _request.default.post(state.approvalPrefix + '/v1/pc/process/processSave', saveBody);\n\n case 8:\n saveJson = _context24.sent;\n saveData = saveJson.data || {};\n return _context24.abrupt(\"return\", {\n uniqueId: saveData.uniqueId || ''\n });\n\n case 13:\n _context24.prev = 13;\n _context24.t0 = _context24[\"catch\"](3);\n console.warn('[Approval] save error:', _context24.t0.message);\n (0, _message.showMessage)('审批流程异常:' + _context24.t0.message, 'error');\n return _context24.abrupt(\"return\", false);\n\n case 18:\n case \"end\":\n return _context24.stop();\n }\n }\n }, _callee24, null, [[3, 13]]);\n }));\n return _saveApprovalFlowState.apply(this, arguments);\n }\n\n function renderApprovalFlow(form, state) {\n clearApprovalRender(form, state);\n if (!state.processList || state.processList.length === 0) return; // 创建容器(初始隐藏,等定位完成后再显示)\n\n var container = document.createElement('div');\n container.className = 'tv-approval-flow tv-approval-flow--hidden';\n container.setAttribute('data-tv-approval', '1'); // 标题\n\n var title = document.createElement('div');\n title.className = 'tv-approval-title';\n title.innerHTML = '<span class=\"tv-approval-title-bar\"></span>审批流程';\n container.appendChild(title); // 渲染节点\n\n var processList = state.processList;\n processList.forEach(function (node, index) {\n // 跳过头尾标记节点\n if (node.nodeType === 'START' || node.nodeType === 'TEMP') return;\n var isEnd = node.nodeType === 'END';\n var isLast = index === processList.length - 1 && isEnd; // 结束节点:显示简单的结束标记\n\n if (isEnd) {\n var endNode = document.createElement('div');\n endNode.className = 'tv-approval-node tv-approval-node--end';\n endNode.innerHTML = '<div class=\"tv-approval-node-dot end-dot\"></div><div class=\"tv-approval-node-name\">流程结束</div>';\n container.appendChild(endNode);\n return;\n }\n\n var nodeEl = document.createElement('div');\n nodeEl.className = 'tv-approval-node ' + (node.nodeType === 'CC_EVENT' ? 'tv-approval-node--cc' : ''); // 左侧图标\n\n var iconArea = document.createElement('div');\n iconArea.className = 'tv-approval-node-icon';\n\n if (node.nodeType === 'CC_EVENT') {\n iconArea.innerHTML = '<svg viewBox=\"0 0 44 44\" width=\"44\" height=\"44\"><rect width=\"44\" height=\"44\" rx=\"10\" fill=\"#909399\"/><text x=\"22\" y=\"28\" text-anchor=\"middle\" fill=\"#fff\" font-size=\"20\">📋</text></svg>';\n } else {\n iconArea.innerHTML = '<svg viewBox=\"0 0 44 44\" width=\"44\" height=\"44\"><rect width=\"44\" height=\"44\" rx=\"10\" fill=\"#0286df\"/><text x=\"22\" y=\"28\" text-anchor=\"middle\" fill=\"#fff\" font-size=\"20\">👤</text></svg>';\n } // 右侧内容\n\n\n var body = document.createElement('div');\n body.className = 'tv-approval-node-body'; // 节点名\n\n var nodeName = document.createElement('div');\n nodeName.className = 'tv-approval-node-name';\n nodeName.textContent = node.nodeName || '';\n body.appendChild(nodeName); // 审批方式提示\n\n if (node.nodeType === 'USER_EVENT') {\n var typeHint = document.createElement('div');\n typeHint.className = 'tv-approval-node-type';\n\n if (node.approvers && node.approvers.length > 0) {\n if (node.approverType === '3') typeHint.textContent = node.approvers.length + '人依次审批';else if (node.approverType === '1') typeHint.textContent = node.approvers.length + '人会签';else if (node.approverType === '2') typeHint.textContent = node.approvers.length + '人或签';\n }\n\n if (node.directorType === '2' || node.directorType === '4') {\n typeHint.textContent = node.noDirectorContent || '系统自动指定';\n }\n\n body.appendChild(typeHint); // 审批人列表\n\n var approverList = document.createElement('div');\n approverList.className = 'tv-approval-approvers';\n\n if (node.approvers && node.approvers.length > 0) {\n node.approvers.forEach(function (approver, ai) {\n var tag = createApproverTag(approver, state, index, ai, node);\n approverList.appendChild(tag);\n });\n } else if (node.directorType === '2' || node.directorType === '4') {\n var autoTag = document.createElement('span');\n autoTag.className = 'tv-approver-tag tv-approver-tag--auto';\n autoTag.textContent = node.noDirectorContent || '自动';\n approverList.appendChild(autoTag);\n } else {\n // 检查是否有可选审批人列表(selectRange==='1' 走组织架构树,selectRange!=='1' 走 optionalUserList)\n var isOrgMode = node.selectRange === '1';\n var hasOptionalList = node.optionalUserList && node.optionalUserList.length > 0;\n var emptyTag = document.createElement('span');\n emptyTag.className = 'tv-approver-tag tv-approver-tag--empty';\n\n if (isOrgMode || hasOptionalList) {\n emptyTag.textContent = '待选择审批人';\n emptyTag.title = '点击选择审批人';\n emptyTag.style.cursor = 'pointer'; // 点击打开对应弹窗:组织架构模式用 showOrgSelector,列表模式用 showApproverSelector\n\n emptyTag.addEventListener('click', function () {\n if (isOrgMode) {\n showOrgSelector(state, index, node);\n } else {\n showApproverSelector(state, index, node);\n }\n });\n } else {\n emptyTag.textContent = '无可用审批人';\n emptyTag.title = '后端未返回可选审批人列表(optionalUserList 为空),请检查审批流程配置';\n emptyTag.style.color = '#C0C4CC';\n }\n\n approverList.appendChild(emptyTag);\n } // \"+ 选择审批人\" 按钮(对应 ApprovalCustomization.vue 的 handlexinzeng)\n // 参考项目中仅 directorType == '2' 不显示选择按钮\n // directorType == '1'(直接主管)在没有审批人时仍然允许选择\n\n\n var isDirectorAuto = node.directorType === '2';\n var isOrgSelect = node.selectRange === '1'; // 组织架构选人模式\n\n var canSelect = false;\n\n if (isOrgSelect) {\n // 组织架构模式:不需要 optionalUserList,直接从组织树选择\n canSelect = node.directorType === '3' && node.selectType !== '1' || !node.approvers || node.approvers.length === 0;\n } else if (!isDirectorAuto && node.optionalUserList && node.optionalUserList.length > 0) {\n // 列表模式:需要 optionalUserList\n canSelect = node.selectType === '2' || node.selectType === '1' && (!node.approvers || node.approvers.length === 0);\n }\n\n if (canSelect) {\n var addBtn = document.createElement('span');\n addBtn.className = 'tv-approver-add';\n addBtn.textContent = '+ 选择';\n addBtn.title = '选择审批人';\n addBtn.addEventListener('click', function (e) {\n e.stopPropagation();\n\n if (isOrgSelect) {\n showOrgSelector(state, index, node);\n } else {\n showApproverSelector(state, index, node);\n }\n });\n approverList.appendChild(addBtn);\n }\n\n body.appendChild(approverList);\n } // 抄送节点\n\n\n if (node.nodeType === 'CC_EVENT') {\n var ccHint = document.createElement('div');\n ccHint.className = 'tv-approval-node-type';\n ccHint.textContent = '抄送' + (node.approvers ? node.approvers.length : 0) + '人';\n body.appendChild(ccHint);\n\n if (node.approvers && node.approvers.length > 0) {\n var ccList = document.createElement('div');\n ccList.className = 'tv-approval-approvers';\n node.approvers.forEach(function (approver, ai) {\n var tag = createApproverTag(approver, state, index, ai, node);\n ccList.appendChild(tag);\n });\n body.appendChild(ccList);\n } // 抄送人选择(ccSelfSelectFlag == '1')\n\n\n if (node.ccSelfSelectFlag === '1') {\n var addCc = document.createElement('span');\n addCc.className = 'tv-approver-add';\n addCc.textContent = '+ 选择抄送人';\n addCc.addEventListener('click', function () {\n // 使用组织架构选择器(showOrgSelector 不依赖 optionalUserList,\n // 后端未返回可选列表时也能正常弹出组织树,避免点击无响应)\n showOrgSelector(state, index, node);\n });\n body.appendChild(addCc);\n }\n }\n\n nodeEl.appendChild(iconArea);\n nodeEl.appendChild(body);\n container.appendChild(nodeEl); // 连接线(非最后一个节点)\n\n if (!isLast && index < processList.length - 1) {\n var nextNode = processList[index + 1];\n\n if (nextNode && nextNode.nodeType !== 'END') {\n var line = document.createElement('div');\n line.className = 'tv-approval-line';\n container.appendChild(line);\n } else if (nextNode && nextNode.nodeType === 'END') {\n var endLine = document.createElement('div');\n endLine.className = 'tv-approval-line tv-approval-line--end';\n container.appendChild(endLine);\n }\n }\n }); // 插入到提交按钮前面\n // 策略:找到 btngroup,用一个包裹层同时容纳审批流程和按钮组,内部顺序固定不受 CSS 影响\n\n function findBtngroup() {\n var btnScope = resolveScope(form);\n var allBtns = btnScope.querySelectorAll('button');\n\n for (var i = 0; i < allBtns.length; i++) {\n var txt = (allBtns[i].textContent || '').trim();\n\n if (txt === '提交' || txt === '暂存' || txt === '重置') {\n var parent = allBtns[i].parentElement; // 确认父级包含多个操作按钮\n\n if (parent && parent.querySelectorAll('button').length >= 2) return parent;\n return parent;\n }\n } // 回退:找 tokui 按钮属性\n\n\n var btn = btnScope.querySelector('[data-tokui-tag=\"btn\"], [class*=\"tokui-btn\"]');\n if (btn && btn.tagName !== 'BUTTON') btn = btn.querySelector('button') || btn;\n return btn ? btn.parentElement : null;\n }\n\n function insertBeforeBtngroup() {\n var btngroup = findBtngroup();\n if (!btngroup) return false; // 如果 btngroup 已在 wrapper 中,复用现有 wrapper\n\n var existingWrapper = btngroup.parentNode;\n\n if (existingWrapper && existingWrapper.classList.contains('tv-approval-wrapper')) {\n // 检查 wrapper 中是否已有审批流程\n var existingFlow = existingWrapper.querySelector('.tv-approval-flow');\n\n if (existingFlow && existingFlow !== container) {\n existingFlow.remove();\n } // 确保审批流程在 btngroup 前面\n\n\n if (container.parentNode !== existingWrapper) {\n existingWrapper.insertBefore(container, btngroup);\n } else if (btngroup.previousElementSibling !== container) {\n existingWrapper.insertBefore(container, btngroup);\n }\n\n container.classList.remove('tv-approval-flow--hidden');\n return true;\n } // 清除可能残留的旧 wrapper(避免嵌套)\n\n\n var formScope = form.closest('[data-form-id]') || form.parentElement || form;\n var searchScope = formScope.parentElement || formScope;\n var oldWrappers = searchScope.querySelectorAll('.tv-approval-wrapper');\n oldWrappers.forEach(function (w) {\n if (w === existingWrapper) return; // 把 wrapper 内的 btngroup 放回原位\n\n var wp = w.parentNode;\n\n if (wp) {\n while (w.firstChild) {\n wp.insertBefore(w.firstChild, w);\n }\n\n w.remove();\n }\n }); // 创建新包裹层,把审批流程和按钮组合并,确保审批流程在按钮组上方\n\n var wrapper = document.createElement('div');\n wrapper.className = 'tv-approval-wrapper';\n btngroup.parentNode.insertBefore(wrapper, btngroup);\n wrapper.appendChild(container);\n wrapper.appendChild(btngroup); // 定位完成,移除隐藏类\n\n container.classList.remove('tv-approval-flow--hidden');\n return true;\n }\n\n try {\n if (!insertBeforeBtngroup()) {\n (form.querySelector('.tokui-form-content') || form).appendChild(container);\n [500, 1500].forEach(function (d) {\n setTimeout(function () {\n if (container.parentNode) insertBeforeBtngroup();\n }, d);\n });\n }\n } catch (insertErr) {\n console.warn('[Approval] render insert error:', insertErr.message);\n\n try {\n (form.querySelector('.tokui-form-content') || form).appendChild(container);\n } catch (e2) {}\n }\n\n state.container = container;\n } // 创建审批人标签 DOM\n\n\n function createApproverTag(approver, state, nodeIndex, approverIndex, node) {\n var tag = document.createElement('span');\n tag.className = 'tv-approver-tag';\n var name = approver.userName || approver.name || ''; // 有头像 → 圆形头像 <img>;无头像(icon 为空/破图) → 直接显示姓名末尾 2 字占位,不包圆形底色\n\n var fallbackText = function fallbackText() {\n return name && name.length >= 2 ? name.substring(name.length - 2) : name || '?';\n };\n\n if (approver.icon) {\n var avatar = document.createElement('span');\n avatar.className = 'tv-approver-avatar';\n avatar.title = name;\n var img = document.createElement('img');\n img.className = 'tv-approver-avatar-img';\n img.src = approver.icon;\n img.alt = '';\n avatar.appendChild(img);\n\n img.onerror = function () {\n avatar.remove();\n tag.textContent = fallbackText();\n tag.title = name;\n };\n\n tag.appendChild(avatar);\n } else {\n tag.textContent = fallbackText();\n tag.title = name;\n } // 删除按钮(非固定审批人)\n\n\n var canDelete = node.directorType !== '1' && node.directorType !== '2' && node.directorType !== '4';\n\n if (node.nodeType === 'CC_EVENT') {\n canDelete = node.ccSelfSelectFlag === '1' && approver.settingType === '1';\n }\n\n if (canDelete) {\n var delBtn = document.createElement('span');\n delBtn.className = 'tv-approver-remove';\n delBtn.textContent = '×';\n delBtn.title = '删除';\n delBtn.addEventListener('click', function (e) {\n e.stopPropagation();\n node.approvers.splice(approverIndex, 1);\n rememberSelectedApprovers(state, node, nodeIndex);\n if (node.optionalUserList) node.optionalUserList.push(approver);\n renderApprovalFlow(state._form, state);\n });\n tag.appendChild(delBtn);\n }\n\n return tag;\n } // ============ 组织架构选人弹窗(selectRange === '1' 时使用) ============\n // 对应源项目 nodeForm.vue,从组织架构树中选择审批人\n\n\n function showOrgSelector(state, nodeIndex, node) {\n // 移除已存在的选择器\n var oldBackdrop = document.querySelector('.tv-approver-backdrop');\n if (oldBackdrop) oldBackdrop.remove();\n var isSingle = node.selectType === '1'; // 单选\n\n var isCC = node.nodeType === 'CC_EVENT';\n var dialogTitle = isCC ? '请选择抄送人' : '请选择审批人';\n var selectedList = (node.approvers || []).slice(); // 已选列表\n // 企业 API 前缀:优先从 env.VUE_APP_EMTERPRISE 获取,兼容旧 approvalPrefix 推导\n\n var enterpriseBase = state.approvalPrefix.replace(/\\/sgb-meta-design$/, \"/enterprise-managerment\");\n if (!enterpriseBase) enterpriseBase = state.approvalPrefix.replace(/\\/[^/]+$/, \"/enterprise-managerment\");\n var auth = (0, _request.getAuth)();\n var userNo = auth.userno || auth.user || '';\n var companyNo = auth.companyNo || ''; // ── backdrop ──\n\n var backdrop = document.createElement('div');\n backdrop.className = 'tv-approver-backdrop';\n backdrop.addEventListener('click', function () {\n backdrop.remove();\n }); // ── dialog ──\n\n var dialog = document.createElement('div');\n dialog.className = 'tv-org-dialog';\n dialog.addEventListener('click', function (e) {\n e.stopPropagation();\n }); // header\n\n var header = document.createElement('div');\n header.className = 'tv-approver-dialog-header';\n var titleEl = document.createElement('span');\n titleEl.className = 'tv-approver-dialog-title';\n titleEl.textContent = dialogTitle;\n var closeBtn = document.createElement('span');\n closeBtn.className = 'tv-approver-dialog-close';\n closeBtn.textContent = '×';\n closeBtn.addEventListener('click', function () {\n backdrop.remove();\n });\n header.appendChild(titleEl);\n header.appendChild(closeBtn);\n dialog.appendChild(header); // body: left (tree) + right (selected)\n\n var body = document.createElement('div');\n body.className = 'tv-org-dialog-body'; // ── 左侧树 ──\n\n var leftPanel = document.createElement('div');\n leftPanel.className = 'tv-org-left';\n var searchInput = document.createElement('input');\n searchInput.type = 'text';\n searchInput.className = 'tv-org-search';\n searchInput.placeholder = '输入姓名搜索';\n leftPanel.appendChild(searchInput);\n var treeContainer = document.createElement('div');\n treeContainer.className = 'tv-org-tree';\n treeContainer.innerHTML = '<div class=\"tv-org-loading\">加载组织架构中...</div>';\n leftPanel.appendChild(treeContainer);\n body.appendChild(leftPanel); // ── 右侧已选 ──\n\n var rightPanel = document.createElement('div');\n rightPanel.className = 'tv-org-right';\n var rightTitle = document.createElement('div');\n rightTitle.className = 'block-title';\n rightTitle.textContent = '已选:';\n rightPanel.appendChild(rightTitle);\n var rightList = document.createElement('ul');\n rightList.className = 'tv-org-selected-list';\n rightPanel.appendChild(rightList);\n body.appendChild(rightPanel);\n dialog.appendChild(body); // footer\n\n var footer = document.createElement('div');\n footer.className = 'tv-approver-dialog-footer';\n var cancelBtn = document.createElement('button');\n cancelBtn.className = 'tv-approver-dialog-cancel';\n cancelBtn.textContent = '取消';\n cancelBtn.addEventListener('click', function () {\n backdrop.remove();\n });\n footer.appendChild(cancelBtn);\n var confirmBtn = document.createElement('button');\n confirmBtn.className = 'tv-approver-dialog-confirm';\n confirmBtn.textContent = '确定';\n confirmBtn.addEventListener('click', function () {\n // 更新 node.approvers\n node.approvers = selectedList.map(function (item) {\n return {\n companyId: item.comNo || item.companyId || '',\n userId: item.memberNo || item.userId || '',\n userName: item.name || item.userName || '',\n memberNo: item.memberNo || item.userId || '',\n icon: item.icon || '',\n settingType: item.comNo ? '1' : '2'\n };\n });\n rememberSelectedApprovers(state, node, nodeIndex);\n var form = state._form;\n if (form) renderApprovalFlow(form, state);\n backdrop.remove();\n });\n footer.appendChild(confirmBtn);\n dialog.appendChild(footer);\n backdrop.appendChild(dialog);\n document.body.appendChild(backdrop); // ========== 渲染右侧已选列表 ==========\n\n function renderSelected() {\n rightList.innerHTML = '';\n selectedList.forEach(function (item, idx) {\n var li = document.createElement('li');\n li.className = 'tv-org-selected-item';\n var nameEl = document.createElement('span');\n nameEl.textContent = item.name || item.userName || '';\n li.appendChild(nameEl);\n var delBtn = document.createElement('span');\n delBtn.className = 'tv-org-selected-del';\n delBtn.textContent = '×';\n delBtn.addEventListener('click', function () {\n selectedList.splice(idx, 1);\n renderSelected();\n renderTree(); // 刷新左侧勾选状态\n });\n li.appendChild(delBtn);\n rightList.appendChild(li);\n });\n } // ========== 渲染左侧树 ==========\n\n\n var loadedData = null; // 根节点数据\n\n var deptChildrenMap = {}; // 已加载的子部门数据 { deptId: { orgInfoList: [...], contacts: [...] } }\n\n var expandedDepts = {}; // 已展开的部门\n\n var loadingDepts = {}; // 正在加载中的部门\n\n function renderTree() {\n if (!loadedData) {\n treeContainer.innerHTML = '<div class=\"tv-org-loading\">加载组织架构中...</div>';\n return;\n }\n\n var selectedIds = {};\n selectedList.forEach(function (s) {\n selectedIds[s.memberNo || s.userId] = true;\n });\n\n function renderNode(item, level) {\n var row = document.createElement('div');\n row.className = 'tv-org-node';\n row.style.paddingLeft = level * 20 + 8 + 'px';\n\n if (item.holidayType === 2 || item.leaf) {\n // 联系人(可勾选)\n var uid = item.memberNo || item.userId;\n var isChecked = selectedIds[uid];\n var cb = document.createElement('input');\n cb.type = isSingle ? 'radio' : 'checkbox';\n if (isSingle) cb.name = 'tv-org-radio';\n cb.checked = isChecked;\n cb.setAttribute('data-uid', uid);\n cb.className = 'tv-org-check';\n cb.addEventListener('change', function () {\n if (cb.checked) {\n if (isSingle) {\n selectedList = [item];\n treeContainer.querySelectorAll('input[type=\"radio\"]').forEach(function (r) {\n if (r !== cb) r.checked = false;\n });\n } else {\n selectedList.push(item);\n }\n } else {\n selectedList = selectedList.filter(function (s) {\n return (s.memberNo || s.userId) !== uid;\n });\n }\n\n renderSelected();\n });\n var label = document.createElement('span');\n label.className = 'tv-org-label';\n label.textContent = item.name || '';\n label.style.cursor = 'pointer';\n label.addEventListener('click', function () {\n cb.checked = !cb.checked;\n cb.dispatchEvent(new Event('change'));\n });\n row.appendChild(cb);\n row.appendChild(label);\n } else {\n // 部门(可展开,懒加载子节点)\n var deptId = item.id;\n var hasChildren = deptChildrenMap[deptId] !== undefined;\n var isExpanded = expandedDepts[deptId];\n var isLoading = loadingDepts[deptId];\n var arrow = document.createElement('span');\n arrow.className = 'tv-org-arrow ' + (isExpanded ? 'expanded' : '');\n arrow.textContent = isExpanded ? '▼' : '▶';\n var deptName = document.createElement('span');\n deptName.className = 'tv-org-label tv-org-label--dept';\n deptName.textContent = (isLoading ? '加载中... ' : '') + (item.name || '');\n row.appendChild(arrow);\n row.appendChild(deptName);\n row.style.cursor = 'pointer';\n row.addEventListener('click', function () {\n if (expandedDepts[deptId]) {\n // 收起\n delete expandedDepts[deptId];\n renderTree();\n } else {\n // 展开\n if (hasChildren) {\n // 已有数据,直接展开\n expandedDepts[deptId] = true;\n renderTree();\n } else if (!isLoading) {\n // 需要懒加载\n loadDeptChildren(deptId, item);\n }\n }\n });\n } // 统一追加行\n\n\n treeContainer.appendChild(row); // 渲染子节点(展开的部门)\n\n if (item.holidayType !== 2 && !item.leaf) {\n var _isExpanded = expandedDepts[item.id];\n\n if (_isExpanded) {\n var childData = deptChildrenMap[item.id];\n\n if (childData) {\n // 先渲染子部门,再渲染联系人\n (childData.orgInfoList || []).forEach(function (child) {\n renderNode(child, level + 1);\n });\n (childData.contacts || []).forEach(function (child) {\n child.leaf = true;\n child.holidayType = 2;\n renderNode(child, level + 1);\n });\n }\n }\n }\n }\n\n treeContainer.innerHTML = '';\n renderNode(loadedData, 0);\n } // ========== 懒加载子部门 ==========\n\n\n function loadDeptChildren(_x30, _x31) {\n return _loadDeptChildren.apply(this, arguments);\n } // ========== 加载组织架构(根节点) ==========\n\n\n function _loadDeptChildren() {\n _loadDeptChildren = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee15(deptId, deptItem) {\n var orgJson, orgData, contacts, orgInfoList;\n return regeneratorRuntime.wrap(function _callee15$(_context15) {\n while (1) {\n switch (_context15.prev = _context15.next) {\n case 0:\n if (!loadingDepts[deptId]) {\n _context15.next = 2;\n break;\n }\n\n return _context15.abrupt(\"return\");\n\n case 2:\n loadingDepts[deptId] = true;\n renderTree(); // 显示加载中状态\n\n _context15.prev = 4;\n _context15.next = 7;\n return _request.default.get(enterpriseBase + '/v1/organization/getOrgInfoV2?userNo=' + userNo + '&companyNo=' + companyNo + '&deptId=' + deptId);\n\n case 7:\n orgJson = _context15.sent;\n orgData = orgJson.data || {};\n contacts = orgData.contacts || [];\n orgInfoList = orgData.orgInfoList || []; // 标记叶子节点\n\n contacts.forEach(function (c) {\n c.leaf = true;\n c.holidayType = 2;\n }); // 标记子部门\n\n orgInfoList.forEach(function (d) {\n d.disabled = true;\n });\n deptChildrenMap[deptId] = {\n orgInfoList: orgInfoList,\n contacts: contacts\n };\n delete loadingDepts[deptId];\n expandedDepts[deptId] = true; // 展开已加载的部门\n\n renderTree();\n _context15.next = 24;\n break;\n\n case 19:\n _context15.prev = 19;\n _context15.t0 = _context15[\"catch\"](4);\n console.warn('[Validator] 加载子部门失败:', _context15.t0.message);\n delete loadingDepts[deptId];\n renderTree();\n\n case 24:\n case \"end\":\n return _context15.stop();\n }\n }\n }, _callee15, null, [[4, 19]]);\n }));\n return _loadDeptChildren.apply(this, arguments);\n }\n\n function loadOrgData() {\n return _loadOrgData.apply(this, arguments);\n } // ========== 搜索 ==========\n\n\n function _loadOrgData() {\n _loadOrgData = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee16() {\n var companyJson, companies, mainCompany, orgJson, orgData, contacts, orgInfoList;\n return regeneratorRuntime.wrap(function _callee16$(_context16) {\n while (1) {\n switch (_context16.prev = _context16.next) {\n case 0:\n _context16.prev = 0;\n _context16.next = 3;\n return _request.default.get(enterpriseBase + '/v1/organization/getCompanyInfo?userNo=' + userNo + '&companyNo=' + companyNo);\n\n case 3:\n companyJson = _context16.sent;\n companies = companyJson.data && Array.isArray(companyJson.data) ? companyJson.data : [];\n mainCompany = companies.filter(function (c) {\n return c.isMainCompany === 1 || c.isMainCompany === true;\n })[0] || companies[0];\n _context16.next = 8;\n return _request.default.get(enterpriseBase + '/v1/organization/getOrgInfoV2?userNo=' + userNo + '&companyNo=' + companyNo + '&deptId=1');\n\n case 8:\n orgJson = _context16.sent;\n orgData = orgJson.data || {};\n contacts = orgData.contacts || [];\n orgInfoList = orgData.orgInfoList || []; // 标记叶子节点\n\n contacts.forEach(function (c) {\n c.leaf = true;\n c.holidayType = 2;\n }); // 标记子部门\n\n orgInfoList.forEach(function (d) {\n d.disabled = true;\n }); // 根节点数据\n\n deptChildrenMap['1'] = {\n orgInfoList: orgInfoList,\n contacts: contacts\n };\n loadedData = {\n name: mainCompany.name || '组织架构',\n id: '1',\n disabled: true\n };\n expandedDepts['1'] = true; // 默认展开根节点\n\n treeContainer.innerHTML = '';\n renderTree();\n _context16.next = 25;\n break;\n\n case 21:\n _context16.prev = 21;\n _context16.t0 = _context16[\"catch\"](0);\n console.warn('[Validator] 加载组织架构失败:', _context16.t0.message);\n treeContainer.innerHTML = '<div class=\"tv-org-error\">加载组织架构失败: ' + _context16.t0.message + '</div>';\n\n case 25:\n case \"end\":\n return _context16.stop();\n }\n }\n }, _callee16, null, [[0, 21]]);\n }));\n return _loadOrgData.apply(this, arguments);\n }\n\n var searchTimer = null;\n searchInput.addEventListener('input', function () {\n clearTimeout(searchTimer);\n var keyword = searchInput.value.trim();\n\n if (keyword.length === 0) {\n renderTree();\n return;\n }\n\n searchTimer = setTimeout(function () {\n doSearch(keyword);\n }, 300);\n });\n\n function doSearch(_x32) {\n return _doSearch.apply(this, arguments);\n } // 初始化\n\n\n function _doSearch() {\n _doSearch = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee17(keyword) {\n var searchJson, results, selectedIds;\n return regeneratorRuntime.wrap(function _callee17$(_context17) {\n while (1) {\n switch (_context17.prev = _context17.next) {\n case 0:\n if (keyword) {\n _context17.next = 2;\n break;\n }\n\n return _context17.abrupt(\"return\");\n\n case 2:\n treeContainer.innerHTML = '<div class=\"tv-org-loading\">搜索中...</div>';\n _context17.prev = 3;\n _context17.next = 6;\n return _request.default.get(enterpriseBase + '/v2/api/contractBasicInfo/searchKeyWord?keyWord=' + encodeURIComponent(keyword) + '&comNo=' + companyNo);\n\n case 6:\n searchJson = _context17.sent;\n results = searchJson.data || [];\n selectedIds = {};\n selectedList.forEach(function (s) {\n selectedIds[s.memberNo || s.userId] = true;\n selectedIds[s.id] = true;\n });\n treeContainer.innerHTML = '';\n\n if (!(results.length === 0)) {\n _context17.next = 14;\n break;\n }\n\n treeContainer.innerHTML = '<div class=\"tv-org-empty\">无匹配结果</div>';\n return _context17.abrupt(\"return\");\n\n case 14:\n results.forEach(function (item) {\n var row = document.createElement('div');\n row.className = 'tv-org-node';\n row.style.paddingLeft = '8px';\n var uid = item.memberNo || item.userId;\n var isChecked = selectedIds[uid] || selectedIds[item.id];\n var cb = document.createElement('input');\n cb.type = isSingle ? 'radio' : 'checkbox';\n if (isSingle) cb.name = 'tv-org-radio';\n cb.checked = isChecked;\n cb.className = 'tv-org-check';\n cb.addEventListener('change', function () {\n if (cb.checked) {\n if (isSingle) {\n selectedList = [item];\n treeContainer.querySelectorAll('input[type=\"radio\"]').forEach(function (r) {\n if (r !== cb) r.checked = false;\n });\n } else {\n selectedList.push(item);\n }\n } else {\n selectedList = selectedList.filter(function (s) {\n return (s.memberNo || s.userId || s.id) !== uid;\n });\n }\n\n renderSelected();\n });\n var label = document.createElement('span');\n label.className = 'tv-org-label';\n label.textContent = item.name || '';\n label.style.cursor = 'pointer';\n label.addEventListener('click', function () {\n cb.checked = !cb.checked;\n cb.dispatchEvent(new Event('change'));\n });\n row.appendChild(cb);\n row.appendChild(label);\n treeContainer.appendChild(row);\n });\n _context17.next = 21;\n break;\n\n case 17:\n _context17.prev = 17;\n _context17.t0 = _context17[\"catch\"](3);\n console.warn('[Validator] 搜索失败:', _context17.t0.message);\n treeContainer.innerHTML = '<div class=\"tv-org-error\">搜索失败: ' + _context17.t0.message + '</div>';\n\n case 21:\n case \"end\":\n return _context17.stop();\n }\n }\n }, _callee17, null, [[3, 17]]);\n }));\n return _doSearch.apply(this, arguments);\n }\n\n renderSelected();\n loadOrgData();\n } // 审批人选择弹窗(「请选择审批人」dialog,对应 ApprovalCustomization.vue 的 el-dialog)\n\n\n function showApproverSelector(state, nodeIndex, node) {\n // 移除已存在的选择器\n var oldBackdrop = document.querySelector('.tv-approver-backdrop');\n if (oldBackdrop) oldBackdrop.remove();\n var isMulti = node.selectType === '2'; // 多选\n\n var isCC = node.nodeType === 'CC_EVENT';\n var dialogTitle = isCC ? '请选择抄送人' : '请选择审批人'; // ── backdrop ──\n\n var backdrop = document.createElement('div');\n backdrop.className = 'tv-approver-backdrop';\n backdrop.addEventListener('click', function () {\n backdrop.remove();\n }); // ── dialog ──\n\n var dialog = document.createElement('div');\n dialog.className = 'tv-approver-dialog';\n dialog.addEventListener('click', function (e) {\n e.stopPropagation();\n }); // header\n\n var header = document.createElement('div');\n header.className = 'tv-approver-dialog-header';\n var titleEl = document.createElement('span');\n titleEl.className = 'tv-approver-dialog-title';\n titleEl.textContent = dialogTitle;\n var closeBtn = document.createElement('span');\n closeBtn.className = 'tv-approver-dialog-close';\n closeBtn.textContent = '×';\n closeBtn.addEventListener('click', function () {\n backdrop.remove();\n });\n header.appendChild(titleEl);\n header.appendChild(closeBtn);\n dialog.appendChild(header); // body\n\n var body = document.createElement('div');\n body.className = 'tv-approver-dialog-body';\n var desc = document.createElement('p');\n desc.className = 'tv-approver-dialog-desc';\n desc.textContent = isMulti ? '请从指定以下成员中选择' : '请从指定以下成员中选择一人';\n body.appendChild(desc); // 过滤已选中的\n\n var selectedIds = {};\n\n if (node.approvers) {\n node.approvers.forEach(function (a) {\n selectedIds[a.userId || a.memberNo] = true;\n });\n }\n\n var listContainer = document.createElement('div');\n listContainer.className = 'tv-approver-dialog-list';\n\n if (node.optionalUserList && node.optionalUserList.length > 0) {\n var availableUsers = node.optionalUserList.filter(function (u) {\n return !selectedIds[u.userId || u.memberNo];\n });\n\n if (availableUsers.length === 0) {\n var emptyMsg = document.createElement('div');\n emptyMsg.className = 'tv-approver-dialog-empty';\n emptyMsg.textContent = '暂无可选成员';\n listContainer.appendChild(emptyMsg);\n } else {\n // 收集所有已选项用于批量确认\n var selectedCheckboxes = [];\n availableUsers.forEach(function (user) {\n var uid = user.userId || user.memberNo;\n var item = document.createElement('label');\n item.className = 'tv-approver-dialog-item';\n\n if (isMulti) {\n var cb = document.createElement('input');\n cb.type = 'checkbox';\n cb.value = uid;\n cb.setAttribute('data-uid', uid);\n item.appendChild(cb);\n selectedCheckboxes.push(cb);\n } else {\n var radio = document.createElement('input');\n radio.type = 'radio';\n radio.name = 'tv-approver-radio';\n radio.value = uid;\n radio.setAttribute('data-uid', uid);\n item.appendChild(radio);\n selectedCheckboxes.push(radio);\n }\n\n var label = document.createElement('span');\n label.textContent = user.userName || user.name || uid;\n item.appendChild(label); // 点击整行选中(仅多选时可用,筛选模式用 label 本身的 interaction)\n\n if (isMulti) {\n item.addEventListener('click', function (e) {\n if (e.target.tagName === 'INPUT') return;\n var cb = item.querySelector('input[type=\"checkbox\"]');\n if (cb) cb.checked = !cb.checked;\n });\n } else {\n // 单选模式:点击整行直接确认\n item.addEventListener('click', function () {\n confirmApproverSelection(state, nodeIndex, node, [user]);\n backdrop.remove();\n });\n }\n\n listContainer.appendChild(item);\n });\n }\n } else {\n var emptyMsg = document.createElement('div');\n emptyMsg.className = 'tv-approver-dialog-empty';\n emptyMsg.textContent = '暂无可选成员(后端未返回 optionalUserList,请检查审批流程配置或确认 API 调用是否成功)';\n console.warn('[Validator] showApproverSelector: optionalUserList 为空,请检查 processPreview 返回的 processList 节点数据');\n listContainer.appendChild(emptyMsg);\n }\n\n body.appendChild(listContainer);\n dialog.appendChild(body); // footer\n\n var footer = document.createElement('div');\n footer.className = 'tv-approver-dialog-footer';\n var cancelBtn = document.createElement('button');\n cancelBtn.className = 'tv-approver-dialog-cancel';\n cancelBtn.textContent = '取消';\n cancelBtn.addEventListener('click', function () {\n backdrop.remove();\n });\n footer.appendChild(cancelBtn);\n\n if (isMulti) {\n var confirmBtn = document.createElement('button');\n confirmBtn.className = 'tv-approver-dialog-confirm';\n confirmBtn.textContent = '确定';\n confirmBtn.addEventListener('click', function () {\n var selected = [];\n listContainer.querySelectorAll('input[type=\"checkbox\"]:checked').forEach(function (cb) {\n var uid = cb.getAttribute('data-uid');\n node.optionalUserList.forEach(function (u) {\n if ((u.userId || u.memberNo) === uid) selected.push(u);\n });\n });\n\n if (selected.length > 0) {\n confirmApproverSelection(state, nodeIndex, node, selected);\n }\n\n backdrop.remove();\n });\n footer.appendChild(confirmBtn);\n }\n\n dialog.appendChild(footer);\n backdrop.appendChild(dialog);\n document.body.appendChild(backdrop);\n }\n\n function confirmApproverSelection(state, nodeIndex, node, selectedUsers) {\n if (!node.approvers) node.approvers = [];\n selectedUsers.forEach(function (user) {\n // 从 optionalUserList 移除\n if (node.optionalUserList) {\n var idx = node.optionalUserList.indexOf(user);\n if (idx >= 0) node.optionalUserList.splice(idx, 1);\n } // 添加到 approvers\n\n\n var approverObj = {\n companyId: user.companyId || user.comNo || '',\n userId: user.userId || user.memberNo || '',\n userName: user.userName || user.name || '',\n icon: user.icon || '',\n memberNo: user.userId || user.memberNo || '',\n settingType: user.settingType || '2'\n };\n node.approvers.push(approverObj);\n });\n rememberSelectedApprovers(state, node, nodeIndex); // 重新渲染\n\n var form = state._form;\n if (form) renderApprovalFlow(form, state);\n } // ============ 提交时调用的审批入口(替代原 runApprovalFlow) ============\n\n\n function runApprovalFlow(_x33, _x34) {\n return _runApprovalFlow.apply(this, arguments);\n } // ---------- 还原 data-load-body-* key 的驼峰写法 ----------\n\n\n function _runApprovalFlow() {\n _runApprovalFlow = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee25(form, allData) {\n var state, _retry, _wait;\n\n return regeneratorRuntime.wrap(function _callee25$(_context25) {\n while (1) {\n switch (_context25.prev = _context25.next) {\n case 0:\n state = getApprovalState(form);\n\n if (state.approvalType) {\n _context25.next = 3;\n break;\n }\n\n return _context25.abrupt(\"return\", null);\n\n case 3:\n if (state.inited) {\n _context25.next = 6;\n break;\n }\n\n _context25.next = 6;\n return initApprovalFlow(form);\n\n case 6:\n if (!(state.approvaStatus !== 0)) {\n _context25.next = 8;\n break;\n }\n\n return _context25.abrupt(\"return\", {\n uniqueId: '',\n skipped: true\n });\n\n case 8:\n if (!(!state.processList || state.processList.length === 0)) {\n _context25.next = 11;\n break;\n }\n\n _context25.next = 11;\n return refreshApprovalFlow(form);\n\n case 11:\n if (!(!state.processList || state.processList.length === 0)) {\n _context25.next = 31;\n break;\n }\n\n _retry = 0;\n\n case 13:\n if (!(_retry < 3)) {\n _context25.next = 31;\n break;\n }\n\n // 等待 _rendering 锁释放\n _wait = 0;\n\n case 15:\n if (!(state._rendering && _wait < 2000)) {\n _context25.next = 21;\n break;\n }\n\n _context25.next = 18;\n return new Promise(function (r) {\n setTimeout(r, 100);\n });\n\n case 18:\n _wait += 100;\n _context25.next = 15;\n break;\n\n case 21:\n // 强制重置渲染锁并重试\n state._rendering = false;\n _context25.next = 24;\n return refreshApprovalFlow(form);\n\n case 24:\n if (!(state.processList && state.processList.length > 0)) {\n _context25.next = 26;\n break;\n }\n\n return _context25.abrupt(\"break\", 31);\n\n case 26:\n _context25.next = 28;\n return new Promise(function (r) {\n setTimeout(r, 500);\n });\n\n case 28:\n _retry++;\n _context25.next = 13;\n break;\n\n case 31:\n if (!(!state.processList || state.processList.length === 0)) {\n _context25.next = 35;\n break;\n }\n\n console.warn('[Approval] runApprovalFlow: processList 为空,中止提交');\n safeFeed('[callout t:error tt:审批流程未就绪 tx:\"审批流程数据未就绪,请检查请假类型和时长后重试\"]', form);\n return _context25.abrupt(\"return\", false);\n\n case 35:\n _context25.next = 37;\n return saveApprovalFlowState(form);\n\n case 37:\n return _context25.abrupt(\"return\", _context25.sent);\n\n case 38:\n case \"end\":\n return _context25.stop();\n }\n }\n }, _callee25);\n }));\n return _runApprovalFlow.apply(this, arguments);\n }\n\n function normalizeBodyKey(key) {\n // HTML 属性名不区分大小写,TokUI DSL 中 data-load-body-userNo 渲染为 data-load-body-userno\n // 常见 API 字段映射表\n var map = {\n userno: 'userNo',\n companyno: 'companyNo',\n comno: 'comNo',\n parentid: 'parentId',\n userid: 'userId',\n companyid: 'companyId',\n departmentid: 'departmentId',\n departmentno: 'departmentNo',\n holidayno: 'holidayNo',\n starttime: 'startTime',\n endtime: 'endTime',\n minleaveunit: 'minLeaveUnit',\n starttimestr: 'startTimeStr',\n endtimestr: 'endTimeStr'\n };\n return map[key.toLowerCase()] || key;\n } // ---------- data-load 请求构造(支持 POST:data-load-method / data-load-body-*) ----------\n //\n // 标准 POST 传参方式:用 data-load-body-{字段名} 声明每个 body 字段,\n // 避免 TokUI DSL 解析器遇到 JSON 内嵌引号时破坏属性值。\n //\n // 级联依赖(推荐):data-load-body-dep-{字段名}:\"{目标元素ID}\"\n // data-load-body-dep- 的值不会被 TokUI 篡改(不含 ${...}),\n // 运行时桥接层自动从目标 DOM 元素读取选中 option 的 value 属性。\n //\n // 示例 DSL:\n // data-load-method:POST\n // data-load-body-userNo:\"\" data-load-body-dep-userNo:\"loginUserNo\"\n // data-load-body-companyNo:\"\" data-load-body-dep-companyNo:\"loginCompanyNo\"\n // data-load-body-parentId:\"\" data-load-body-dep-parentId:\"companyNo\"\n //\n // 生成的请求体:{\"userNo\":\"202007...\",\"companyNo\":\"201912...\",\"parentId\":\"1\"}\n //\n\n\n function buildDataLoadRequest(el, rawUrl) {\n var reqScope = resolveScope(el); // 自动注入登录用户参数供 resolveUrl 使用(必须在 resolveUrl 之前)\n\n if (!window.__tokuiParams) {\n var auth = (0, _request.getAuth)();\n window.__tokuiParams = {};\n if (auth.userno) window.__tokuiParams.loginUserNo = auth.userno;else if (auth.user) window.__tokuiParams.loginUserNo = auth.user;\n if (auth.companyNo) window.__tokuiParams.loginCompanyNo = auth.companyNo;\n }\n\n var resolvedUrl = resolveUrl(rawUrl, reqScope); // TokUI 把 data-* 属性渲染到外层 .tokui-field 上,而不是内部 <select> 上。\n // 所以如果当前元素没有 data-load-method,向上找父级 .tokui-field。\n\n var target = el;\n\n if (target && !target.getAttribute('data-load-method') && target.parentElement && target.parentElement.getAttribute('data-load')) {\n target = target.parentElement;\n }\n\n var method = (target && target.getAttribute('data-load-method') || 'GET').toUpperCase();\n var opts = {\n method: method\n };\n\n if (method !== 'GET') {\n // 优先收集 data-load-body-* 前缀属性(从 target 上读)\n // 两种子类型:\n // data-load-body-{field} → 静态值 / 来自 resolveUrl 的 ${...}\n // data-load-body-dep-{field} → 级联依赖声明,值 = 目标元素ID,运行时从该元素读取选中值\n var bodyObj = {};\n var bodyDeps = {}; // { fieldName: depElementId }\n\n var hasBodyFields = false;\n\n if (target && target.attributes) {\n for (var i = 0; i < target.attributes.length; i++) {\n var attr = target.attributes[i];\n if (!attr.name || attr.name.indexOf('data-load-body-') !== 0) continue;\n var suffix = attr.name.substring('data-load-body-'.length);\n if (!suffix) continue; // data-load-body-dep-{field} → 级联依赖\n\n if (suffix.indexOf('dep-') === 0) {\n var depField = suffix.substring('dep-'.length);\n\n if (depField) {\n bodyDeps[normalizeBodyKey(depField)] = attr.value.trim();\n }\n\n continue;\n } // data-load-body-{field} → 普通 body 字段\n\n\n var key = normalizeBodyKey(suffix);\n bodyObj[key] = resolveUrl(attr.value, reqScope);\n hasBodyFields = true;\n }\n } // 用级联依赖覆盖 body 字段:从 DOM 读选中 option 的 value\n\n\n for (var depField in bodyDeps) {\n if (bodyDeps.hasOwnProperty(depField)) {\n var depElId = bodyDeps[depField];\n var depEl = queryById(depElId, reqScope);\n\n if (depEl && depEl.tagName === 'SELECT') {\n // 从 select 取选中项 value\n var depVal = '';\n\n if (depEl.selectedIndex >= 0 && depEl.options[depEl.selectedIndex]) {\n depVal = depEl.options[depEl.selectedIndex].getAttribute('value') || depEl.options[depEl.selectedIndex].value || '';\n }\n\n bodyObj[depField] = depVal;\n hasBodyFields = true;\n } else if (window.__tokuiParams && window.__tokuiParams[depElId] !== undefined) {\n // 从全局参数映射取(loginUserNo / loginCompanyNo 等)\n bodyObj[depField] = String(window.__tokuiParams[depElId]);\n hasBodyFields = true;\n }\n }\n }\n\n if (hasBodyFields) {\n // 如果任一 body 字段解析后为空(如级联依赖尚未加载完成),跳过此次请求\n var hasEmpty = false;\n\n for (var bk in bodyObj) {\n if (bodyObj.hasOwnProperty(bk) && bodyObj[bk] === '') {\n hasEmpty = true;\n break;\n }\n }\n\n if (hasEmpty) {\n return {\n url: resolvedUrl,\n opts: opts,\n cacheKey: cacheKey,\n skip: true\n };\n }\n\n opts.body = JSON.stringify(bodyObj);\n } else {\n // 降级:兼容旧的 data-load-body 完整 JSON 语法\n var fullBody = target ? target.getAttribute('data-load-body') : null;\n\n if (fullBody) {\n opts.body = resolveUrl(fullBody, reqScope);\n }\n }\n }\n\n var cacheKey = method + ' ' + resolvedUrl + (opts.body ? ' ' + opts.body : '');\n return {\n url: resolvedUrl,\n opts: opts,\n cacheKey: cacheKey\n };\n } // ---------- 用统一 http 封装执行 data-load 请求(复用 token/解密/错误拦截) ----------\n\n\n function httpDataLoad(_x35) {\n return _httpDataLoad.apply(this, arguments);\n } // ---------- 替换 $字段名$ ----------\n\n\n function _httpDataLoad() {\n _httpDataLoad = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee26(req) {\n var method, body;\n return regeneratorRuntime.wrap(function _callee26$(_context26) {\n while (1) {\n switch (_context26.prev = _context26.next) {\n case 0:\n method = req.opts && req.opts.method || 'GET';\n\n if (!(method === 'GET')) {\n _context26.next = 3;\n break;\n }\n\n return _context26.abrupt(\"return\", _request.default.get(req.url));\n\n case 3:\n body = null;\n\n if (req.opts && req.opts.body) {\n try {\n body = JSON.parse(req.opts.body);\n } catch (e) {\n body = req.opts.body;\n }\n }\n\n if (!(method === 'DELETE')) {\n _context26.next = 7;\n break;\n }\n\n return _context26.abrupt(\"return\", _request.default.delete(req.url, {\n data: body\n }));\n\n case 7:\n if (!(method === 'PUT')) {\n _context26.next = 9;\n break;\n }\n\n return _context26.abrupt(\"return\", _request.default.put(req.url, body));\n\n case 9:\n return _context26.abrupt(\"return\", _request.default.post(req.url, body));\n\n case 10:\n case \"end\":\n return _context26.stop();\n }\n }\n }, _callee26);\n }));\n return _httpDataLoad.apply(this, arguments);\n }\n\n function replaceFieldTokens(text, data) {\n return text.replace(/\\$(\\w+)\\$/g, function (_, key) {\n return data[key] !== undefined && data[key] !== null ? String(data[key]) : '$' + key + '$';\n });\n } // ---------- 详情模式:替换 DOM 文本节点 + 属性 ----------\n\n\n function applyDetailData(container, data) {\n // 替换文本节点\n var walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, null, false);\n var node;\n\n while (node = walker.nextNode()) {\n if (/\\$\\w+\\$/.test(node.textContent)) {\n node.textContent = replaceFieldTokens(node.textContent, data);\n }\n } // 替换 src / href / poster 等属性中的 $字段$\n\n\n container.querySelectorAll('[src]').forEach(function (el) {\n var v = el.getAttribute('src');\n if (v && /\\$\\w+\\$/.test(v)) el.setAttribute('src', replaceFieldTokens(v, data));\n });\n container.querySelectorAll('[href]').forEach(function (el) {\n var v = el.getAttribute('href');\n if (v && /\\$\\w+\\$/.test(v)) el.setAttribute('href', replaceFieldTokens(v, data));\n });\n container.querySelectorAll('[poster]').forEach(function (el) {\n var v = el.getAttribute('poster');\n if (v && /\\$\\w+\\$/.test(v)) el.setAttribute('poster', replaceFieldTokens(v, data));\n });\n } // ---------- 列表模式:读取 [repeat] 模板 → 生成 DSL → feed ----------\n\n\n function renderListFromTemplate(url, container) {\n if (!url) return; // 如果 container(select)本身没有 data-load-* 属性,切换为父 .tokui-field\n\n var ctn = container;\n\n if (ctn && !ctn.getAttribute('data-load') && ctn.parentElement && ctn.parentElement.getAttribute('data-load')) {\n ctn = ctn.parentElement;\n }\n\n var tplEl = container.querySelector('script[type=\"text/tokui-template\"]');\n if (!tplEl || !tplEl.textContent) return;\n var template = tplEl.textContent;\n if (!template) return;\n (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee18() {\n var req, cached, json, items;\n return regeneratorRuntime.wrap(function _callee18$(_context18) {\n while (1) {\n switch (_context18.prev = _context18.next) {\n case 0:\n _context18.prev = 0;\n req = buildDataLoadRequest(ctn, url);\n\n if (!req.skip) {\n _context18.next = 4;\n break;\n }\n\n return _context18.abrupt(\"return\");\n\n case 4:\n cached = getCached(req.cacheKey);\n\n if (!cached) {\n _context18.next = 8;\n break;\n }\n\n applyListTemplate(container, tplEl, template, cached);\n return _context18.abrupt(\"return\");\n\n case 8:\n _context18.next = 10;\n return httpDataLoad(req);\n\n case 10:\n json = _context18.sent;\n items = resolveList(json);\n setCached(req.cacheKey, items);\n applyListTemplate(container, tplEl, template, items);\n _context18.next = 19;\n break;\n\n case 16:\n _context18.prev = 16;\n _context18.t0 = _context18[\"catch\"](0);\n console.warn('data-load-list 失败:', container.id, _context18.t0.message);\n\n case 19:\n case \"end\":\n return _context18.stop();\n }\n }\n }, _callee18, null, [[0, 16]]);\n }))();\n }\n\n function applyListTemplate(container, tplEl, template, items) {\n if (!items || !items.length) {\n console.warn('applyListTemplate: items 为空, id=' + container.id + ', tpl=', template);\n return;\n } // 自动检测模板 $key$ → API 返回字段名映射\n\n\n var fieldMap = {};\n var phs = template.match(/\\$(\\w+)\\$/g);\n\n if (phs) {\n phs.forEach(function (ph) {\n var key = ph.slice(1, -1);\n\n if (items[0].hasOwnProperty(key)) {\n fieldMap[key] = key;\n return;\n } // 大写不敏感匹配\n\n\n var lk = key.toLowerCase();\n\n for (var ik in items[0]) {\n if (ik.toLowerCase() === lk) {\n fieldMap[key] = ik;\n return;\n }\n } // 后缀匹配:$no$ → holidayNo, $name$ → holidayName\n\n\n for (var ik in items[0]) {\n if (ik.toLowerCase().indexOf(lk) >= 0 && ik.toLowerCase().endsWith(lk)) {\n fieldMap[key] = ik;\n return;\n }\n } // 无匹配则记录,后续自动替换为空字符串\n\n\n fieldMap[key] = key;\n });\n } // 打印映射关系,便于排查字段名不一致的问题\n\n\n var dsl = '';\n items.forEach(function (item) {\n dsl += template.replace(/\\$(\\w+)\\$/g, function (_, key) {\n var mapped = fieldMap[key] || key;\n var raw = item[mapped]; // 为 undefined/null 统一替换为空字符串,避免模板里出现 \"undefined\" 文本\n\n if (raw === undefined || raw === null || raw === '') return '';\n return String(raw).replace(/\"/g, '\\\\\"');\n });\n });\n\n if (!dsl) {\n console.warn('applyListTemplate: 生成的 DSL 为空, items=', items, 'template=', template);\n return;\n } // 移除模板元素\n\n\n if (tplEl && tplEl.parentNode) tplEl.parentNode.removeChild(tplEl);\n\n if (container.tagName === 'SELECT') {\n selectListFirst(container, dsl, items);\n } else {\n generalListFirst(container, tplEl, dsl);\n }\n }\n\n function selectListFirst(container, dsl, items) {\n // select → 直接生成 <option>\n var optHtml = '';\n dsl.replace(/\\[opt[^\\]]*\\]/g, function (m) {\n var v = (m.match(/v:\"([^\"]*)\"/) || m.match(/v:(\\S+)/) || [])[1] || '';\n var tx = (m.match(/tx:\"([^\"]*)\"/) || m.match(/tx:(\\S+)/) || [])[1] || '';\n optHtml += '<option value=\"' + v.replace(/\"/g, '"') + '\">' + tx + '</option>\\n ';\n });\n if (optHtml) container.innerHTML = optHtml; // ★ 将 API 返回的 items 数据以 data-* 属性存储到对应的 option 上\n // 按 value 匹配 option 与 item(避免 placeholder 导致索引错位)\n\n if (items && container.options) {\n var vKeys = ['no', 'holidayNo', 'id', 'value', 'code', 'v'];\n items.forEach(function (item) {\n var itemVal = '';\n\n for (var vi = 0; vi < vKeys.length; vi++) {\n if (item[vKeys[vi]] !== undefined) {\n itemVal = String(item[vKeys[vi]]);\n break;\n }\n }\n\n var matched = false;\n\n for (var oi = 0; oi < container.options.length; oi++) {\n if (container.options[oi].value === itemVal) {\n matched = true;\n Object.keys(item).forEach(function (key) {\n var val = item[key];\n if (val === undefined || val === null || (0, _typeof2.default)(val) === 'object') return;\n var attrName = 'data-' + key.replace(/([A-Z])/g, '-$1').toLowerCase();\n container.options[oi].setAttribute(attrName, String(val));\n });\n break;\n }\n }\n });\n } // data-default-label:按文本匹配选中默认项\n\n\n selectDefaultByLabel(container); // 选中后始终触发 change(驱动级联和 detail-url),无论是否匹配到默认值\n\n if (container.options && container.options.length > 0) {\n container.dispatchEvent(new Event('change', {\n bubbles: true\n }));\n }\n } // 按 data-default-label 选中匹配项\n\n\n function selectDefaultByLabel(el) {\n // TokUI 把 data-* 属性渲染在外层 .tokui-field 上,不在 <select> 上,所以优先从父级读取\n var label = el.getAttribute('data-default-label');\n\n if (!label && el.parentElement) {\n label = el.parentElement.getAttribute('data-default-label');\n }\n\n if (!label || !el.options) return; // 如果 label 仍含 ${} 占位符(后端未替换),跳过\n\n if (label.indexOf('${') >= 0) {\n console.warn('selectDefaultByLabel: label 未被替换, label=' + label + ', el.id=' + el.id);\n return;\n }\n\n for (var i = 0; i < el.options.length; i++) {\n if ((el.options[i].textContent || el.options[i].innerText || '').trim() === label.trim()) {\n el.selectedIndex = i;\n return;\n }\n }\n\n console.warn('selectDefaultByLabel: 未找到匹配项, label=\"%s\", options=', label, Array.prototype.map.call(el.options, function (o) {\n return o.textContent;\n }));\n }\n\n function generalListFirst(container, repeatEl, dsl) {\n // 清空容器(只保留 repeat 模板),feed 新 DSL\n var parent = container.parentNode;\n var idx = -1;\n\n if (parent) {\n var children = parent.children;\n\n for (var i = 0; i < children.length; i++) {\n if (children[i] === container) {\n idx = i;\n break;\n }\n }\n }\n\n container.innerHTML = '';\n if (repeatEl) container.appendChild(repeatEl);\n safeFeed(dsl);\n } // ---------- select 级联 ----------\n // 将 API 返回的值转为适合目标元素展示的格式\n // - 数字时间戳(毫秒/秒)→ YYYY-MM-DD(用于 datepicker)\n // - datetimepicker(按小时模式)→ YYYY-MM-DD HH:mm\n // - 值中带明确时间(非 00:00:00)→ 保留该时间\n // - 值只有日期(yyyy-MM-dd / 零点时间戳)→ 补全默认时间(开始 00:00,结束 23:59)\n // - 其他值 → 原样转字符串\n // ★ startHalf / endHalf(时段 1=上午 2=下午):按小时模式反显时,仅在默认边界时间\n // (开始 00:00 / 结束 23:59)上按对应时段修正(开始用 startHalf,结束用 endHalf:\n // 上午→结束 12:00,下午→开始 12:00,全天→00:00-23:59),其余具体时间不做修改\n\n\n function formatDateValue(rawVal, tgtEl, fieldKey, startHalf, endHalf) {\n if (rawVal === undefined || rawVal === null) return ''; // 目标是否为 datetimepicker(按小时模式,需要 yyyy-MM-dd HH:mm)\n\n var isDatetime = !!(tgtEl && (tgtEl.closest && tgtEl.closest('.tokui-datetimepicker') || tgtEl.querySelector && tgtEl.querySelector('.tokui-datetimepicker'))); // 字符串值(后端可能返回 \"yyyy-MM-dd\" / \"yyyy-MM-dd HH:mm\" / \"yyyy-MM-dd HH:mm:ss\")\n\n if (typeof rawVal === 'string') {\n var s = String(rawVal).trim();\n if (!s) return ''; // 带时间的字符串 → 统一为 yyyy-MM-dd HH:mm(默认边界时间按对应时段修正)\n\n var dtMatch = s.match(/^(\\d{4}-\\d{2}-\\d{2})[ T](\\d{2}):(\\d{2})/);\n if (dtMatch) return dtMatch[1] + ' ' + applyPeriodToDefaultTime(dtMatch[2] + ':' + dtMatch[3], fieldKey, startHalf, endHalf); // 纯日期字符串:datetimepicker 补全默认时间,datepicker 保持日期\n\n var dateMatch = s.match(/^(\\d{4}-\\d{2}-\\d{2})$/);\n\n if (dateMatch) {\n return isDatetime ? dateMatch[1] + ' ' + getDefaultTimeForField(fieldKey, tgtEl, startHalf, endHalf) : dateMatch[1];\n } // 非日期字符串:尝试按数字时间戳处理\n\n\n var numFromStr = Number(s);\n if (isNaN(numFromStr)) return s;\n rawVal = numFromStr;\n } // 数字时间戳\n\n\n var numVal = Number(rawVal);\n if (isNaN(numVal) || typeof rawVal === 'boolean') return String(rawVal); // 0 表示无日期(如无入职日期),返回空\n\n if (numVal === 0) return ''; // 判断是否为合理的时间戳范围(2000-01-01 ~ 2100-01-01)\n // 秒级时间戳:946684800 ~ 4102444800(10位)\n // 毫秒级时间戳:946684800000 ~ 4102444800000(13位)\n\n var isTimestamp = numVal > 946684800 && numVal < 4102444800000;\n if (!isTimestamp) return String(rawVal); // 秒级转毫秒\n\n var ms = numVal < 10000000000 ? numVal * 1000 : numVal;\n var d = new Date(ms);\n if (isNaN(d.getTime())) return String(rawVal);\n var yyyy = d.getFullYear();\n var mm = String(d.getMonth() + 1).padStart(2, '0');\n var dd = String(d.getDate()).padStart(2, '0');\n\n if (isDatetime) {\n // 有明确时间(非零点)→ 保留;否则用默认时间补全\n var hasTime = !(d.getHours() === 0 && d.getMinutes() === 0 && d.getSeconds() === 0);\n var hh = String(d.getHours()).padStart(2, '0');\n var mi = String(d.getMinutes()).padStart(2, '0');\n var timePart = hasTime ? hh + ':' + mi : getDefaultTimeForField(fieldKey, tgtEl, startHalf, endHalf); // 默认边界时间(00:00 / 23:59)按对应时段修正,其余具体时间不动\n\n return yyyy + '-' + mm + '-' + dd + ' ' + applyPeriodToDefaultTime(timePart, fieldKey, startHalf, endHalf);\n }\n\n return yyyy + '-' + mm + '-' + dd;\n } // 按小时模式 datetimepicker 的默认补全时间:\n // 开始时间 → 00:00,结束时间 → 23:59(对应默认范围 \"0.00-23.59\")\n // ★ startHalf / endHalf(时段 1=上午 2=下午):默认边界时间按对应时段修正,\n // 开始时间按 startHalf、结束时间按 endHalf 判断:\n // 上午(1) → 开始 00:00、结束 12:00;下午(2) → 开始 12:00、结束 23:59;\n // 全天(开始时段 1 + 结束时段 2) → 00:00-23:59\n\n\n function getDefaultTimeForField(fieldKey, tgtEl, startHalf, endHalf) {\n var key = fieldKey;\n\n if (!key && tgtEl) {\n key = tgtEl.id || tgtEl.name || '';\n\n if (!key) {\n var wrap = tgtEl.closest && (tgtEl.closest('[id]') || tgtEl.closest('[name]'));\n if (wrap) key = wrap.id || wrap.name || '';\n }\n }\n\n var k = key ? String(key).toLowerCase() : '';\n var baseTime = k.indexOf('end') >= 0 ? '23:59' : '00:00';\n return applyPeriodToDefaultTime(baseTime, k, startHalf, endHalf);\n } // ★ 小时模式反显:仅当补全/返回的时间是默认边界(开始 00:00 / 结束 23:59)时,\n // 按对应时段调整(开始时间用 startHalf、结束时间用 endHalf,1=上午, 2=下午):\n // 上午 → 结束时间改为 12:00;下午 → 开始时间改为 12:00;\n // 其余具体时间(非 00:00 / 23:59)原样返回,不做修改\n\n\n function applyPeriodToDefaultTime(timePart, fieldKey, startHalf, endHalf) {\n if (!timePart) return timePart;\n var k = fieldKey ? String(fieldKey).toLowerCase() : ''; // 仅对请假起止时间字段(startTime / endTime)修正,避免误伤 entryTime 等其他日期字段\n\n if (k.indexOf('starttime') < 0 && k.indexOf('endtime') < 0) return timePart;\n var isEnd = k.indexOf('endtime') >= 0;\n var p = parseInt(isEnd ? endHalf : startHalf, 10);\n if (p !== 1 && p !== 2) return timePart;\n if (isEnd && timePart === '23:59' && p === 1) return '12:00';\n if (!isEnd && timePart === '00:00' && p === 2) return '12:00';\n return timePart;\n } // ★ 从作用域 DOM 读取指定时段控件(startTimeHalf / endTimeHalf,1=上午, 2=下午)。\n // 实时表单的时段控件由 DSL 渲染成 select/hidden,reconcile 后即可查到,\n // 用于 applyDefaultValues/fixDateTimeDefaults 修正小时模式的默认边界时间\n\n\n function resolveHalfPeriod(scope, halfFieldId) {\n var root = scope && scope.querySelectorAll ? scope : container && container.querySelectorAll ? container : document;\n var el = queryById(halfFieldId, root);\n if (!el) return '';\n\n if (el.tagName === 'SELECT') {\n if (el.selectedIndex >= 0 && el.options[el.selectedIndex]) {\n return el.options[el.selectedIndex].getAttribute('value') || el.options[el.selectedIndex].value || '';\n }\n\n return '';\n }\n\n return el.value || '';\n }\n\n function getLeaveUnitText(minLeaveUnit) {\n if (minLeaveUnit === undefined || minLeaveUnit === null || minLeaveUnit === '') {\n return null;\n }\n\n var unit = String(minLeaveUnit).trim();\n if (unit === '0' || unit === '小时') return '小时';\n return '天';\n }\n\n function syncLeaveUnitSuffix(fieldId, unitText, scope) {\n if (!fieldId || !unitText) return;\n var root = scope || container; // ★ 定位字段元素:优先按 id,其次按 name。\n // 假期余额在 DSL 中可能只存在 name/数据字段而无 id=\"staffBalance\",\n // 若仅按 id 查找会导致找不到元素而直接 return,单位永远不更新。\n\n var fieldEl = queryById(fieldId, root);\n\n if (!fieldEl && root && root.querySelector) {\n fieldEl = root.querySelector('[name=\"' + fieldId + '\"]') || root.querySelector('[id=\"' + fieldId + '\"]');\n }\n\n if (!fieldEl) {\n // ★ staffBalance 在 tokui 表单里可能只是纯数据字段,没有 id/name 对应的 DOM 元素,\n // 页面上可见的余额块由标签\"假期余额\"渲染。改按标签定位其容器,再替换容器内\"天/小时\"单位文本。\n if (fieldId === 'staffBalance' && root && root.querySelectorAll) {\n var labelText = '假期余额';\n var labelEls = root.querySelectorAll('*');\n\n for (var liA = 0; liA < labelEls.length; liA++) {\n var leA = labelEls[liA];\n if (leA.children && leA.children.length) continue; // 只匹配叶子标签\n\n if ((leA.textContent || '').replace(/[::]/g, '').trim() !== labelText) continue;\n var balWrap = leA.closest('.tokui-field') || leA.parentElement;\n if (!balWrap) continue; // 先找叶子文本正好是\"天\"/\"小时\"的单位\n\n var unitEls = balWrap.querySelectorAll('*');\n\n for (var uiA = 0; uiA < unitEls.length; uiA++) {\n var ueA = unitEls[uiA];\n if (ueA.children && ueA.children.length) continue;\n var utA = (ueA.textContent || '').trim();\n\n if (utA === '天' || utA === '小时') {\n ueA.textContent = unitText;\n return;\n }\n } // 再兼容\"200 天\"在同叶子的情况\n\n\n for (var ujA = 0; ujA < unitEls.length; ujA++) {\n var ueB = unitEls[ujA];\n if (ueB.children && ueB.children.length) continue;\n var tB = (ueB.textContent || '').trim();\n\n if (/[\\d.,]+\\s*(天|小时)$/.test(tB)) {\n ueB.textContent = tB.replace(/[\\s]*(天|小时)$/, ' ' + unitText);\n return;\n }\n }\n\n break;\n }\n }\n\n return;\n }\n\n var fieldWrapper = fieldEl.closest('.tokui-field') || fieldEl.parentElement;\n if (!fieldWrapper) return; // ★ 移除旧的自定义单位后缀,避免多次切换叠加多个单位\n\n var oldUnits = fieldWrapper.querySelectorAll('.tokui-duration-unit');\n\n for (var oi = 0; oi < oldUnits.length; oi++) {\n oldUnits[oi].remove();\n }\n\n var appendEl = fieldWrapper.querySelector('.tokui-input-group__append, .tokui-append, .el-input-group__append');\n\n if (!appendEl) {\n var children = fieldWrapper.querySelectorAll('span, div');\n\n for (var ci = children.length - 1; ci >= 0; ci--) {\n if (children[ci].textContent && children[ci].textContent.trim() && !children[ci].querySelector('input')) {\n appendEl = children[ci];\n break;\n }\n }\n }\n\n if (appendEl) {\n appendEl.textContent = unitText;\n return;\n } // ★ 纯文本型字段(如放假余额以 \"N 天\" 文本展示,无独立 append):保留数字,仅替换单位\n\n\n var isFormControl = fieldEl.tagName === 'INPUT' || fieldEl.tagName === 'SELECT' || fieldEl.tagName === 'TEXTAREA';\n\n if (!isFormControl) {\n var cur = (fieldEl.textContent || '').trim();\n var numMatch = cur.match(/^([\\d.,]+)\\s*(天|小时)?/);\n\n if (numMatch && numMatch[1]) {\n fieldEl.textContent = numMatch[1] + ' ' + unitText;\n return;\n }\n } // ★ 遍历字段容器:找一个叶子节点文本正好是\"天\"/\"小时\"的单位标签并覆盖\n // (余额为 input,但其单位的\"天\"由 tokui 用不定 class 的文本节点渲染,上面的选择器匹配不到,\n // 这里直接按文本内容命中,才能替换掉页面上真正显示的旧单位)\n\n\n var leafNodes = fieldWrapper.querySelectorAll('*');\n\n for (var li = 0; li < leafNodes.length; li++) {\n var leaf = leafNodes[li];\n if (leaf.children && leaf.children.length) continue; // 只处理叶子\n\n var lt = (leaf.textContent || '').trim();\n\n if (lt === '天' || lt === '小时') {\n leaf.textContent = unitText;\n return;\n }\n }\n\n if (!isFormControl && fieldEl.textContent && /^[\\d.,]+\\s*(天|小时)/.test(fieldEl.textContent.trim())) {\n var cur2 = fieldEl.textContent.trim();\n fieldEl.textContent = cur2.replace(/[\\s]*(天|小时)$/, ' ' + unitText);\n return;\n } // ★ 无现成后缀元素时兜底新增后缀 span,保证假期余额与时长使用同一单位文本\n\n\n appendEl = document.createElement('span');\n appendEl.className = 'tokui-duration-unit';\n appendEl.textContent = unitText;\n appendEl.style.cssText = 'margin-left:6px;color:#909399;font-size:14px;white-space:nowrap;';\n fieldWrapper.appendChild(appendEl);\n }\n\n function refreshHolidayUnitSuffixFromHiddenField(scope) {\n // ★ 余额单位与时长计算保持同源:优先取 duration 字段 data-calc-duration-body-dep-minLeaveUnit\n // 指向的字段值,其次 _holidayType(与 calcLeaveDuration 的 minLeaveUnit 兜底一致),\n // 避免切换请假类型后\"时长单位变了、假期余额单位没变\"。\n var unitText = getLeaveUnitText(resolveLeaveMinUnit(scope));\n if (!unitText) return null;\n syncLeaveUnitSuffix('duration', unitText, scope);\n syncLeaveUnitSuffix('staffBalance', unitText, scope);\n return unitText;\n } // 与 calcLeaveDuration 的 minLeaveUnit 解析保持一致,作为时长/余额单位的唯一来源\n\n\n function resolveLeaveMinUnit(scope) {\n var root = scope || container;\n var durationField = queryById('duration', root);\n\n if (durationField) {\n var depMinId = durationField.getAttribute('data-calc-duration-body-dep-minLeaveUnit');\n\n if (depMinId) {\n var v = readFieldValueById(depMinId, root);\n if (v !== undefined && v !== null && String(v) !== '') return String(v);\n }\n }\n\n var htEl = queryById('_holidayType', root);\n if (htEl && htEl.value !== undefined && htEl.value !== null && String(htEl.value) !== '') return String(htEl.value);\n return null;\n }\n\n function setupCascade(el) {\n // 如果 select 本身没有 data-load(在父 .tokui-field 上),切换为父元素读取属性\n var origin = el;\n\n if (el && !el.getAttribute('data-load') && el.parentElement && el.parentElement.getAttribute('data-load')) {\n el = el.parentElement;\n } // ★ 多表单隔离:解析当前元素所属的表单容器作用域,所有 DOM 查询都限定在此作用域内,\n // 避免 document.getElementById/querySelector 跨消息串扰\n\n\n var cascadeScope = resolveScope(origin || el); // 处理 data-load-detail-url:选择后获取详情(如假期余额)\n // data-load-detail-target 指定单个目标元素 ID\n // data-load-detail-field 指定用返回数据的哪个字段值\n // data-load-detail-map 支持多字段映射,逗号分隔:\"fromField1:toElementId1,fromField2:toElementId2\"\n\n var detailUrl = el.getAttribute('data-load-detail-url');\n var detailTarget = el.getAttribute('data-load-detail-target');\n var detailField = el.getAttribute('data-load-detail-field') || 'value';\n var detailMapRaw = el.getAttribute('data-load-detail-map');\n var detailMap = null;\n\n if (detailMapRaw) {\n // 尝试 JSON 解析;失败则按逗号分隔的 key:value 格式解析\n try {\n detailMap = JSON.parse(detailMapRaw);\n } catch (e) {\n detailMap = {};\n detailMapRaw.split(',').forEach(function (pair) {\n var colonIdx = pair.indexOf(':');\n\n if (colonIdx > 0) {\n var k = pair.substring(0, colonIdx).trim();\n var v = pair.substring(colonIdx + 1).trim();\n if (k && v) detailMap[k] = v;\n }\n });\n }\n }\n\n if (detailUrl && (detailTarget || detailMap)) {\n (function (sel, urlTpl, tgtId, field, dMap) {\n sel.addEventListener('change', function () {\n if (sel.selectedIndex < 0 || !sel.options[sel.selectedIndex]) return;\n var val = sel.options[sel.selectedIndex].getAttribute('value') || sel.options[sel.selectedIndex].value || '';\n if (!val) return;\n var finalUrl = urlTpl.replace('{value}', encodeURIComponent(val));\n\n _request.default.get(finalUrl).then(function (json) {\n if (!json) {\n console.warn('[CondVis] detail fetch 返回空, url=' + finalUrl);\n return;\n }\n\n var data = json.data !== undefined ? json.data : json;\n\n if (!data || (0, _typeof2.default)(data) !== 'object') {\n console.warn('[CondVis] detail data 不是对象:', data);\n return;\n } // 优先处理多字段映射 data-load-detail-map\n\n\n if (dMap) {\n Object.keys(dMap).forEach(function (srcKey) {\n var tgtElId = dMap[srcKey];\n if (!tgtElId) return;\n var tgtEl = queryById(tgtElId, cascadeScope);\n var srcVal = data[srcKey]; // ★ 修复:detail 返回空值时清空 _leaveType 旧值,\n // 避免切换非年假类型后\"入职时间\"(data-visible-when:_leaveType:年假)仍显示\n\n if (tgtElId === '_leaveType' && (srcVal === undefined || srcVal === null || srcVal === '')) {\n if (tgtEl) {\n tgtEl.value = '';\n\n try {\n tgtEl.dispatchEvent(new Event('change', {\n bubbles: true\n }));\n } catch (e2) {}\n }\n\n return;\n }\n\n if (srcVal === undefined || srcVal === null) return;\n\n if (tgtEl) {\n // 仅对日期选择器字段做时间戳→日期格式转换(如 entryTime)\n // .closest 只查祖先,若 id 在外层 .tokui-field 上则需查后代\n var isDateField = tgtEl.closest('.tokui-datepicker') || tgtEl.closest('.tokui-datetimepicker') || tgtEl.querySelector('.tokui-datepicker') || tgtEl.querySelector('.tokui-datetimepicker');\n var tagName = tgtEl.tagName;\n var isFormControl = tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA';\n\n if (isDateField) {\n var formattedVal = formatDateValue(srcVal, tgtEl, tgtElId); // 找到日期选择器内部真正的 input 元素,设置值并触发 change\n\n var dateInput = isFormControl ? tgtEl : tgtEl.querySelector('.tokui-datepicker input') || tgtEl.querySelector('.tokui-datetimepicker input') || tgtEl.querySelector('input');\n\n if (dateInput) {\n dateInput.value = formattedVal;\n dateInput.dispatchEvent(new Event('change', {\n bubbles: true\n }));\n dateInput.dispatchEvent(new Event('input', {\n bubbles: true\n }));\n } else {\n tgtEl.value = formattedVal;\n tgtEl.dispatchEvent(new Event('change', {\n bubbles: true\n }));\n }\n } else if (isFormControl) {\n tgtEl.value = String(srcVal);\n tgtEl.dispatchEvent(new Event('change', {\n bubbles: true\n }));\n } else {\n tgtEl.textContent = String(srcVal);\n }\n } else if (tgtElId.charAt(0) === '_') {\n // 隐藏辅助字段(如 _holidayType, _leaveType, _balanceMode):创建 hidden input\n var hiddenEl = queryById(tgtElId, cascadeScope);\n\n if (!hiddenEl) {\n hiddenEl = document.createElement('input');\n hiddenEl.type = 'hidden';\n hiddenEl.id = tgtElId;\n hiddenEl.name = tgtElId;\n var formEl = sel.closest('form') || sel.closest('[data-form-id]');\n if (formEl) formEl.appendChild(hiddenEl);else (cascadeScope && cascadeScope.appendChild ? cascadeScope : document.body).appendChild(hiddenEl);\n }\n\n hiddenEl.value = String(srcVal); // 触发 change 事件 + 手动调用条件更新(hidden input 不自动触发 change)\n\n try {\n hiddenEl.dispatchEvent(new Event('change', {\n bubbles: true\n }));\n } catch (e2) {}\n }\n }); // 多字段映射完成后触发条件显示更新\n\n updateConditionalVisibility(cascadeScope);\n\n if (dMap.minLeaveUnit || dMap._holidayType) {\n refreshHolidayUnitSuffixFromHiddenField(cascadeScope);\n var stEl = queryById('startTime', cascadeScope);\n var etEl = queryById('endTime', cascadeScope);\n var durEl = queryById('duration', cascadeScope); // ★ 只在原有值为空时清空,避免编辑模式下清除已回填的起止时间\n\n if (stEl && !stEl.value) stEl.value = '';\n if (etEl && !etEl.value) etEl.value = '';\n if (durEl) durEl.value = '';\n } // ★ 起止时间回填后,主动触发时长计算\n // (data-load-detail-map 的 change 事件委托可能因 DOM 结构未找到 clk: 处理器,\n // 此处作为兜底,确保反显后 duration 自动计算)\n\n\n var _stEl = queryById('startTime', cascadeScope);\n\n var _etEl = queryById('endTime', cascadeScope);\n\n if (_stEl && _etEl && _stEl.value && _etEl.value) {\n var clkHandler = _clkHandlers['calcLeaveDuration'];\n\n if (clkHandler) {\n setTimeout(function () {\n try {\n clkHandler({}, new Event('change'), _etEl);\n } catch (e2) {}\n }, 100);\n }\n }\n\n return; // dMap 模式完成,跳过单字段逻辑\n } // 单字段模式(data-load-detail-target)\n\n\n if (!tgtId) return;\n var tgt = queryById(tgtId, cascadeScope);\n if (!tgt) return; // 目标可能是 input/select/textarea,也可能是 span/div 等文本元素\n\n var tgName = tgt.tagName;\n var isFC = tgName === 'INPUT' || tgName === 'SELECT' || tgName === 'TEXTAREA';\n\n if (field !== 'value' && (0, _typeof2.default)(data) === 'object' && data !== null) {\n if (data[field] !== undefined) {\n if (isFC) {\n tgt.value = String(data[field]);\n } else {\n tgt.textContent = String(data[field]);\n }\n }\n\n return;\n } // 默认:取第一个数值字段\n\n\n var keys = Object.keys(data);\n\n for (var ki = 0; ki < keys.length; ki++) {\n if (typeof data[keys[ki]] === 'number' || typeof data[keys[ki]] === 'string' && !isNaN(Number(data[keys[ki]]))) {\n if (isFC) {\n tgt.value = String(data[keys[ki]]);\n } else {\n tgt.textContent = String(data[keys[ki]]);\n }\n\n break;\n }\n }\n }).catch(function (e) {\n console.warn('data-load-detail-url 失败:', e.message);\n });\n });\n })(origin, detailUrl, detailTarget, detailField, detailMap);\n } // ★ 从 option 的 data-* 属性填充隐藏辅助字段(如 _holidayType 的 minLeaveUnit)\n // 当 data-load 已返回完整数据时,无需依赖 data-load-detail-url 再次请求\n\n\n (function (sel) {\n sel.addEventListener('change', function () {\n if (!sel || !sel.options || sel.selectedIndex < 0 || !sel.options[sel.selectedIndex]) return; // 切换请假类型时,清除底部旧的报错/警告提示\n // safeFeed 可能将 feedback 挂到 form 和 submitBtn 的 LCA 上,需从更高层级搜索\n\n var _submitBtn = cascadeScope.querySelector('[data-tokui-sub=\"formSubmit\"]');\n\n var _searchRoot = sel.closest('[data-form-id]') || sel.closest('form') || sel.parentElement;\n\n if (_submitBtn) {\n var _p = sel.parentElement;\n\n while (_p && _p !== document.body) {\n if (_p.contains(_submitBtn)) break;\n _p = _p.parentElement;\n }\n\n if (_p) _searchRoot = _p;\n }\n\n if (_searchRoot) {\n var _oldFbs = _searchRoot.querySelectorAll('.tokui-validator-feedback');\n\n _oldFbs.forEach(function (el) {\n el.remove();\n });\n }\n\n var opt = sel.options[sel.selectedIndex]; // 已知的隐藏字段映射:data-属性名 → 目标元素 ID\n\n var hiddenFieldMap = {\n 'min-leave-unit': '_holidayType',\n 'leave-type': '_leaveType',\n 'balance-mode': '_balanceMode'\n }; // ★ 仅当本下拉是请假类型选择器(任一 option 带请假相关 data 属性)时才处理 _leaveType,\n // 避免误伤 dept/company 等其他级联下拉(它们的 option 无这些属性,会误清 _leaveType)\n\n var _isLeaveTypeSelect = false;\n\n for (var _loi = 0; _loi < sel.options.length; _loi++) {\n var _opt2 = sel.options[_loi];\n\n if (_opt2.getAttribute('data-leave-type') || _opt2.getAttribute('data-holiday-name') || _opt2.getAttribute('data-balance-mode')) {\n _isLeaveTypeSelect = true;\n break;\n }\n }\n\n Object.keys(hiddenFieldMap).forEach(function (dataKey) {\n var tgtId = hiddenFieldMap[dataKey];\n var attrVal = opt.getAttribute('data-' + dataKey);\n var isLeaveTypeField = tgtId === '_leaveType';\n if (isLeaveTypeField && !_isLeaveTypeSelect) return; // ★ 修复:切换请假类型时 _leaveType 旧值残留(如仍是\"年假\"),\n // 会导致非年假类型下\"入职时间\"(data-visible-when:_leaveType:年假)仍显示。\n // 新选中项缺 data-leave-type 时清空旧值,条件显示据此正确隐藏。\n\n if (isLeaveTypeField && (attrVal === null || attrVal === undefined || String(attrVal) === '')) {\n var _ltEl0 = queryById(tgtId, cascadeScope);\n\n if (_ltEl0) {\n _ltEl0.value = '';\n\n try {\n _ltEl0.dispatchEvent(new Event('change', {\n bubbles: true\n }));\n } catch (e2) {}\n }\n\n return;\n }\n\n if (attrVal === null || attrVal === undefined) return;\n var tgtEl = queryById(tgtId, cascadeScope);\n\n if (!tgtEl) {\n tgtEl = document.createElement('input');\n tgtEl.type = 'hidden';\n tgtEl.id = tgtId;\n tgtEl.name = tgtId;\n var formEl = sel.closest('form') || sel.closest('[data-form-id]');\n if (formEl) formEl.appendChild(tgtEl);else (cascadeScope && cascadeScope.appendChild ? cascadeScope : document.body).appendChild(tgtEl);\n }\n\n tgtEl.value = attrVal;\n\n try {\n tgtEl.dispatchEvent(new Event('change', {\n bubbles: true\n }));\n } catch (e2) {}\n }); // ★ 修复(安卓):_leaveType 的 change 监听由 MutationObserver/定时器异步注册,\n // 安卓上可能因监听未就绪或 `.value` 属性不触发 MutationObserver 而漏刷新,\n // 导致切到非年假时\"入职时间\"(data-visible-when:_leaveType:年假)仍显示。\n // 这里直接同步刷新一次条件显示,消除对事件监听时序的依赖。\n\n updateConditionalVisibility(cascadeScope); // 从 option data 属性读取 holidayName 并填充 _holidayName 隐藏字段\n\n var holidayName = opt.getAttribute('data-holiday-name');\n\n if (holidayName) {\n var hnEl = queryById('_holidayName', cascadeScope);\n\n if (!hnEl) {\n hnEl = document.createElement('input');\n hnEl.type = 'hidden';\n hnEl.id = '_holidayName';\n hnEl.name = '_holidayName';\n var fe = sel.closest('form') || sel.closest('[data-form-id]');\n if (fe) fe.appendChild(hnEl);else (cascadeScope && cascadeScope.appendChild ? cascadeScope : document.body).appendChild(hnEl);\n }\n\n hnEl.value = holidayName;\n } // 同步假期单位后缀显示\n\n\n refreshHolidayUnitSuffixFromHiddenField(cascadeScope);\n });\n })(origin); // 收集所有级联依赖的元素 ID\n\n\n var depIds = []; // 方式1(推荐):data-load-body-dep-{field} 属性,值 = 目标元素ID,TokUI 不会篡改\n\n if (el && el.attributes) {\n for (var i = 0; i < el.attributes.length; i++) {\n var attr = el.attributes[i];\n\n if (attr.name && attr.name.indexOf('data-load-body-dep-') === 0) {\n var id = attr.value.trim();\n if (id && depIds.indexOf(id) < 0) depIds.push(id);\n }\n }\n } // 方式2(降级):从 data-load URL、data-load-body、data-load-body-* 中扫描 ${元素ID}\n\n\n var fallbackAttrs = [el.getAttribute('data-load'), el.getAttribute('data-load-body')];\n\n if (el && el.attributes) {\n for (var i = 0; i < el.attributes.length; i++) {\n var a = el.attributes[i];\n\n if (a.name && a.name.indexOf('data-load-body-') === 0 && a.name.indexOf('data-load-body-dep-') !== 0) {\n fallbackAttrs.push(a.value);\n }\n }\n }\n\n fallbackAttrs.forEach(function (src) {\n if (!src) return;\n src.replace(/\\$\\{(\\w+)\\}/g, function (_, id) {\n if (depIds.indexOf(id) < 0) depIds.push(id);\n });\n }); // 注册 change 监听\n\n depIds.forEach(function (depId) {\n var depEl = queryById(depId, cascadeScope);\n if (depEl) depEl.addEventListener('change', function () {\n var r = origin.querySelector('script[type=\"text/tokui-template\"]');\n\n if (r && r.textContent) {\n renderListFromTemplate(el.getAttribute('data-load'), origin);\n } else {\n loadDataFromAttr(origin);\n }\n });\n });\n } // ============ 条件显示/隐藏 ============\n // 支持 data-visible-when:\"fieldId:value\" — 当指定字段值等于 value 时显示\n // 支持 data-visible-when-not:\"fieldId:value\" — 当指定字段值不等于 value 时显示\n // value 支持 | 分隔多值,如 \"_leaveType:年假|产假\" 表示匹配任一值即显示\n // 隐藏的元素添加 .tokui-conditional-hidden 类\n // 已监听的字段集合,避免重复绑定\n\n\n var _condWatchedFields = {};\n\n function updateConditionalVisibility(scope) {\n var root = scope && scope.querySelectorAll ? scope : container && container.querySelectorAll ? container : document;\n var allWhen = root.querySelectorAll('[data-visible-when]');\n allWhen.forEach(function (el) {\n var rule = el.getAttribute('data-visible-when') || '';\n var parts = rule.split(':');\n if (parts.length < 2) return;\n var fieldId = parts[0].trim();\n var expectedVal = parts.slice(1).join(':').trim();\n var sourceEl = queryById(fieldId, root);\n\n if (!sourceEl) {\n // 源字段不存在 → 隐藏(保守策略)\n el.classList.add('tokui-conditional-hidden');\n return;\n }\n\n var currentVal = String(sourceEl.value || '');\n var match = expectedVal.split('|').some(function (v) {\n return currentVal === v.trim();\n });\n\n if (match) {\n el.classList.remove('tokui-conditional-hidden');\n } else {\n el.classList.add('tokui-conditional-hidden');\n }\n });\n var allWhenNot = root.querySelectorAll('[data-visible-when-not]');\n allWhenNot.forEach(function (el) {\n var rule = el.getAttribute('data-visible-when-not') || '';\n var parts = rule.split(':');\n if (parts.length < 2) return;\n var fieldId = parts[0].trim();\n var excludedVal = parts.slice(1).join(':').trim();\n var sourceEl = queryById(fieldId, root);\n\n if (!sourceEl) {\n // 源字段不存在 → 显示(when-not 的保守策略:不排除则显示)\n el.classList.remove('tokui-conditional-hidden');\n return;\n }\n\n var currentVal = String(sourceEl.value || '');\n var match = excludedVal.split('|').some(function (v) {\n return currentVal === v.trim();\n });\n\n if (match) {\n el.classList.add('tokui-conditional-hidden');\n } else {\n el.classList.remove('tokui-conditional-hidden');\n }\n });\n } // 为指定字段注册条件监听(change + input + MutationObserver)\n\n\n function watchCondField(fieldId, scope) {\n if (_condWatchedFields[fieldId]) return;\n _condWatchedFields[fieldId] = true;\n var root = scope && scope.querySelectorAll ? scope : container && container.querySelectorAll ? container : document;\n var el = queryById(fieldId, root);\n if (!el) return;\n\n var _scope = scope || root; // change / input 事件\n\n\n el.addEventListener('change', function () {\n updateConditionalVisibility(_scope);\n });\n el.addEventListener('input', function () {\n updateConditionalVisibility(_scope);\n }); // MutationObserver 监听 value 属性变化(适用于 hidden input)\n\n if (el.type === 'hidden' || el.tagName === 'INPUT') {\n try {\n var obs = new MutationObserver(function (mutations) {\n for (var i = 0; i < mutations.length; i++) {\n if (mutations[i].attributeName === 'value') {\n updateConditionalVisibility(_scope);\n break;\n }\n }\n });\n obs.observe(el, {\n attributes: true,\n attributeFilter: ['value']\n });\n } catch (e) {}\n }\n } // 初始化条件显示:扫描所有 data-visible-when / data-visible-when-not 规则,\n // 收集被引用的字段 ID,为已存在的字段注册监听。\n // 由于 TokUI 表单是动态渲染的,此函数可安全重复调用。\n // ★ 应用 data-default-value:TokUI 渲染后可能清空 input value,需重新设置\n\n\n function applyDefaultValues(scope) {\n var root = scope && scope.querySelectorAll ? scope : container && container.querySelectorAll ? container : document; // ★ 开始/结束时段(1=上午, 2=下午):小时模式纯日期补全时按对应时段修正默认边界时间\n\n var startHalf = resolveHalfPeriod(root, 'startTimeHalf');\n var endHalf = resolveHalfPeriod(root, 'endTimeHalf');\n var els = root.querySelectorAll('[data-default-value]');\n els.forEach(function (el) {\n var val = el.getAttribute('data-default-value');\n if (!val) return; // 找到真正的表单控件(el 本身可能是 .tokui-field 包装层)\n\n var target = el;\n\n if (el.tagName !== 'INPUT' && el.tagName !== 'TEXTAREA' && el.tagName !== 'SELECT') {\n target = el.querySelector('input, textarea, select');\n if (!target) return;\n }\n\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {\n // ★ datetimepicker 且值为纯日期(YYYY-MM-DD)时,补全默认时间(按结束时段修正)\n var isDatetime = target.classList && target.classList.contains('tokui-datetimepicker-input') || target.closest && target.closest('.tokui-datetimepicker');\n\n if (isDatetime && /^\\d{4}-\\d{2}-\\d{2}$/.test(val)) {\n var fieldId = target.id || target.name || '';\n val = val + ' ' + getDefaultTimeForField(fieldId, target, startHalf, endHalf);\n }\n\n if (!target.value || target.value === '') {\n target.value = val; // 同步隐藏 input\n\n if (isDatetime && target.id) {\n var wrapper = target.closest('.tokui-datetimepicker');\n\n if (wrapper) {\n var hiddenInput = wrapper.querySelector('input[type=\"hidden\"]');\n if (hiddenInput) hiddenInput.value = val;\n }\n }\n }\n } else if (target.tagName === 'SELECT') {\n for (var i = 0; i < target.options.length; i++) {\n if (target.options[i].value === val || target.options[i].getAttribute('value') === val) {\n target.selectedIndex = i;\n break;\n }\n }\n }\n });\n } // 已提交表单的反显:把提交时保存的 formData 回填到对应表单控件。\n // 不触发 change/input 事件,因此不会触发校验;配合 lockFormWithOverlay 遮罩使用。\n\n\n function applyFormData(scope, formData) {\n if (!scope || !formData) return;\n var fd = formData;\n\n if (typeof fd === 'string') {\n try {\n fd = JSON.parse(fd);\n } catch (e) {\n return;\n }\n }\n\n if (!fd || (0, _typeof2.default)(fd) !== 'object') return; // 后端可能返回小写字段名(如 starttime → startTime),统一映射\n\n var fieldKeyMap = {\n starttime: 'startTime',\n endtime: 'endTime',\n entrytime: 'entryTime',\n starttimestr: 'startTimeStr',\n endtimestr: 'endTimeStr',\n durationday: 'durationDay',\n minleaveunit: 'minLeaveUnit',\n holidayno: 'holidayNo',\n holidayname: 'holidayName',\n departno: 'departNo',\n companyno: 'companyNo',\n departmentno: 'departmentNo'\n }; // select 的显示名称映射:提交数据中 value 与文本的对应关系\n\n var selectNameMap = {\n type: fd.typeStr || fd.holidayName || fd.holidayname || '',\n departNo: fd.departName || fd.departname || '',\n companyNo: fd.companyName || fd.companyname || ''\n }; // ★ 开始/结束时段(1=上午, 2=下午):按小时模式反显起止时间时,仅用于修正默认边界时间\n // (开始 00:00 / 结束 23:59),用户说的具体时间不受影响。\n // 开始时间按 startTimeHalf 判断,结束时间按 endTimeHalf 判断(全天=1+2 → 00:00-23:59)\n\n var startHalf = fd.startTimeHalf !== undefined && fd.startTimeHalf !== null && fd.startTimeHalf !== '' ? fd.startTimeHalf : fd.starttimehalf !== undefined && fd.starttimehalf !== null ? fd.starttimehalf : '';\n var endHalf = fd.endTimeHalf !== undefined && fd.endTimeHalf !== null && fd.endTimeHalf !== '' ? fd.endTimeHalf : fd.endtimehalf !== undefined && fd.endtimehalf !== null ? fd.endtimehalf : '';\n Object.keys(fd).forEach(function (rawKey) {\n var val = fd[rawKey];\n if (val === undefined || val === null || val === '') return; // 字段名归一化:后端可能返回小写,映射为表单元素的 camelCase id\n\n var key = fieldKeyMap[rawKey.toLowerCase()] || rawKey;\n var el = queryById(key, scope); // 回退:按 [name] 查找(TokUI 隐藏 input 有 name 无 id)\n\n if (!el) {\n var byName = scope.querySelectorAll('[name]');\n\n for (var ni = 0; ni < byName.length; ni++) {\n if (byName[ni].name === key) {\n el = byName[ni];\n break;\n }\n }\n }\n\n if (!el) {\n if (key === 'startTime' || key === 'endTime' || key === 'entryTime') {\n console.warn('[Validator] applyFormData: 字段 ' + rawKey + '→' + key + ' 未找到DOM元素, val=' + val);\n }\n\n return;\n }\n\n var tag = el.tagName; // 若 id 挂在外层 .tokui-field 包装层上,取内部真实控件\n\n if (tag !== 'INPUT' && tag !== 'SELECT' && tag !== 'TEXTAREA') {\n var inner = el.querySelector && el.querySelector('input,select,textarea');\n if (!inner) return;\n el = inner;\n tag = el.tagName;\n }\n\n if (tag === 'SELECT') {\n var strVal = String(val);\n var matched = false;\n\n for (var i = 0; i < el.options.length; i++) {\n if (el.options[i].value === strVal || el.options[i].getAttribute('value') === strVal) {\n el.selectedIndex = i;\n matched = true;\n break;\n }\n } // skipDataLoad 时下拉未加载选项:用 value + 名称动态补一个 option 反显\n\n\n if (!matched) {\n var label = selectNameMap[key] || strVal;\n var opt = document.createElement('option');\n opt.value = strVal;\n opt.textContent = label;\n el.appendChild(opt);\n el.value = strVal;\n }\n\n return;\n }\n\n if (tag === 'INPUT' || tag === 'TEXTAREA') {\n if (el.type === 'checkbox' || el.type === 'radio') {\n el.checked = val === true || String(val) === 'true' || el.value === String(val);\n return;\n }\n\n var isDateField = el.closest('.tokui-datepicker') || el.closest('.tokui-datetimepicker');\n var formattedVal = isDateField ? formatDateValue(val, el, key, startHalf, endHalf) : String(val);\n el.value = formattedVal; // ★ 同步 TokUI 隐藏 input(datetimepicker/datepicker 有两个独立 input:\n // 可见 readonly input 持 id,隐藏 input 持 name,两者不会自动同步)\n\n if (isDateField && el.id) {\n var wrapper = el.closest('.tokui-datetimepicker') || el.closest('.tokui-datepicker');\n\n if (wrapper) {\n var hiddenInput = wrapper.querySelector('input[type=\"hidden\"][name=\"' + el.id + '\"]');\n\n if (hiddenInput) {\n hiddenInput.value = formattedVal;\n }\n }\n }\n }\n }); // 补卡详情反显:参照原项目先按日期生成时间选项,再恢复完整补卡时间。\n // 后端仅返回 patchTime 时间戳时,同时从该值派生 patchDate。\n\n var patchTimeField = queryById('patchTime', scope);\n var patchDateField = queryById('patchDate', scope);\n\n if (patchTimeField && patchTimeField._updatePatchTimeOptions) {\n var reflectedPatchTime = fd.patchTime !== undefined ? fd.patchTime : fd.patchtime;\n var reflectedPatchDate = fd.patchDate || fd.patchdate || reflectedPatchTime;\n var patchDateControl = patchDateField;\n\n if (patchDateControl && patchDateControl.tagName !== 'INPUT') {\n patchDateControl = patchDateControl.querySelector('.tokui-datepicker-input, input:not([type=\"hidden\"])');\n }\n\n var formattedPatchDate = formatDateValue(reflectedPatchDate, patchDateControl, 'patchDate', '', '');\n\n if (patchDateControl && formattedPatchDate) {\n patchDateControl.value = formattedPatchDate;\n var patchDateWrapper = patchDateControl.closest('.tokui-datepicker');\n var patchDateHidden = patchDateWrapper && patchDateWrapper.querySelector('input[type=\"hidden\"]');\n if (patchDateHidden) patchDateHidden.value = formattedPatchDate;\n }\n\n patchTimeField._updatePatchTimeOptions(formattedPatchDate, reflectedPatchTime);\n } // 回填完成后,若表单配置了审批,触发一次审批预览刷新, \n // 确保审批变量(type/departNo/durationDay 等)齐全后审批模块能正确展示。\n\n\n try {\n var apprEl = scope.querySelector('[data-approval-type]');\n\n if (apprEl) {\n var apprForm = apprEl.closest('form') || apprEl.closest('[data-form-id]') || apprEl;\n if (apprForm) refreshApprovalFlow(apprForm);\n }\n } catch (e) {\n console.warn('[Validator] applyFormData 后审批刷新失败:', e.message);\n }\n }\n\n function initConditionalVisibility(scope) {\n var root = scope && scope.querySelectorAll ? scope : container && container.querySelectorAll ? container : document;\n var watchedFields = {};\n var allWhen = root.querySelectorAll('[data-visible-when]');\n allWhen.forEach(function (el) {\n var rule = el.getAttribute('data-visible-when') || '';\n var parts = rule.split(':');\n if (parts.length < 2) return;\n var fieldId = parts[0].trim();\n watchedFields[fieldId] = true;\n });\n var allWhenNot = root.querySelectorAll('[data-visible-when-not]');\n allWhenNot.forEach(function (el) {\n var rule = el.getAttribute('data-visible-when-not') || '';\n var parts = rule.split(':');\n if (parts.length < 2) return;\n var fieldId = parts[0].trim();\n watchedFields[fieldId] = true;\n });\n Object.keys(watchedFields).forEach(function (fieldId) {\n watchCondField(fieldId, root);\n }); // 初始执行一次\n\n updateConditionalVisibility(root);\n } // 全局 MutationObserver:监听 DOM 新增的 data-visible-when / data-visible-when-not 元素,\n // 以及新增的 hidden input(如 _leaveType, _balanceMode),自动注册监听并刷新条件显示。\n\n\n var _condDomObserver = null;\n\n function startCondDomObserver() {\n if (_condDomObserver) return;\n\n try {\n _condDomObserver = new MutationObserver(function (mutations) {\n var scopesToRefresh = [];\n\n function addScope(scope) {\n if (scope && scopesToRefresh.indexOf(scope) < 0) scopesToRefresh.push(scope);\n }\n\n for (var i = 0; i < mutations.length; i++) {\n var added = mutations[i].addedNodes;\n if (!added) continue;\n\n for (var j = 0; j < added.length; j++) {\n var node = added[j];\n if (node.nodeType !== 1) continue;\n var nodeScope = resolveScope(node); // 新增的条件元素\n\n if (node.hasAttribute && (node.hasAttribute('data-visible-when') || node.hasAttribute('data-visible-when-not'))) {\n addScope(nodeScope);\n } // 新增的 input(可能是动态创建的 hidden field,如 _leaveType)\n\n\n if (node.tagName === 'INPUT' && node.id) {\n watchCondField(node.id, nodeScope);\n addScope(nodeScope);\n } // 子元素中也检查\n\n\n if (node.querySelectorAll) {\n var condEls = node.querySelectorAll('[data-visible-when], [data-visible-when-not]');\n if (condEls.length > 0) addScope(nodeScope);\n var inputs = node.querySelectorAll('input[id]');\n inputs.forEach(function (inp) {\n if (inp.id) watchCondField(inp.id, nodeScope);\n });\n }\n }\n }\n\n if (scopesToRefresh.length > 0) {\n // 延迟一帧执行,确保 DOM 完全就绪\n setTimeout(function () {\n scopesToRefresh.forEach(function (scope) {\n initConditionalVisibility(scope);\n });\n }, 50);\n }\n });\n var condObserveRoot = container && container.nodeType === 1 ? container : document.body;\n\n _condDomObserver.observe(condObserveRoot, {\n childList: true,\n subtree: true\n });\n } catch (e) {\n console.warn('[CondVis] MutationObserver 启动失败:', e.message);\n }\n } // ★ datetimepicker 日期补全:扫描所有 datetimepicker 可见 input,\n // 1) 值为纯日期(YYYY-MM-DD)→ 补全默认时间(start→00:00,end→23:59;按结束时段修正)\n // 2) 值已是默认边界时间(开始 00:00 / 结束 23:59)→ 按结束时段(1=上午, 2=下午)修正\n // 上午 → 结束 12:00;下午 → 开始 12:00;其余具体时间不动\n // 这是一个兜底函数,在渲染、applyDefaultValues、applyFormData 之后运行,\n // 确保无论值从哪条路径设置,最终 datetimepicker 都显示正确的时间。\n\n\n function fixDateTimeDefaults(scope) {\n var root = scope && scope.querySelectorAll ? scope : container && container.querySelectorAll ? container : document; // ★ 开始/结束时段(1=上午, 2=下午):小时模式反显时用于修正默认边界时间\n // (开始时间按 startTimeHalf、结束时间按 endTimeHalf 判断)\n\n var startHalf = resolveHalfPeriod(root, 'startTimeHalf');\n var endHalf = resolveHalfPeriod(root, 'endTimeHalf');\n var datetimepickers = root.querySelectorAll('.tokui-datetimepicker');\n datetimepickers.forEach(function (picker) {\n var visibleInput = picker.querySelector('.tokui-datetimepicker-input');\n if (!visibleInput) return;\n var val = visibleInput.value;\n if (!val) return; // 仅处理带日期的值(YYYY-MM-DD 或 YYYY-MM-DD HH:mm)\n\n var m = String(val).match(/^(\\d{4}-\\d{2}-\\d{2})[ T]?(\\d{2}:\\d{2})?/);\n if (!m) return;\n var fieldId = visibleInput.id || visibleInput.name || '';\n var datePart = m[1];\n var timePart = m[2] || '';\n var formattedVal = timePart ? datePart + ' ' + applyPeriodToDefaultTime(timePart, fieldId, startHalf, endHalf) : datePart + ' ' + getDefaultTimeForField(fieldId, visibleInput, startHalf, endHalf);\n if (visibleInput.value === formattedVal) return; // 值未变化,跳过写 DOM\n\n visibleInput.value = formattedVal; // 同步隐藏 input\n\n var hiddenInput = picker.querySelector('input[type=\"hidden\"]');\n if (hiddenInput) hiddenInput.value = formattedVal; // 更新 data-default-value(避免 applyDefaultValues 后回退)\n\n visibleInput.setAttribute('data-default-value', formattedVal);\n });\n } // 在 DOM 就绪后初始化\n\n\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', function () {\n applyDefaultValues(container);\n fixDateTimeDefaults(container);\n initConditionalVisibility(container);\n startCondDomObserver();\n });\n } else {\n setTimeout(function () {\n applyDefaultValues(container);\n fixDateTimeDefaults(container);\n initConditionalVisibility(container);\n startCondDomObserver();\n }, 100);\n } // 延迟再次应用默认值(TokUI 可能异步渲染覆盖)\n\n\n setTimeout(function () {\n applyDefaultValues(container);\n fixDateTimeDefaults(container);\n }, 500);\n setTimeout(function () {\n applyDefaultValues(container);\n fixDateTimeDefaults(container);\n }, 1500); // 审批流程初始化(已存在的表单)\n\n function initExistingApprovalForms(scope) {\n // 每次调用时也重新初始化条件显示(TokUI 表单可能刚渲染完)\n var root = scope && scope.querySelectorAll ? scope : container && container.querySelectorAll ? container : document;\n applyDefaultValues(root);\n initConditionalVisibility(root); // 已提交表单:跳过审批流程初始化(会请求审批人/组织等接口)\n\n if (options.skipDataLoad) return;\n var forms = root.querySelectorAll('[data-approval-type]');\n forms.forEach(function (el) {\n var form = el.closest('form') || el.closest('[data-form-id]') || el;\n\n if (form && !form._tvApprovalInited) {\n form._tvApprovalInited = true;\n setTimeout(function () {\n initApprovalFlow(form);\n }, 800);\n }\n });\n }\n\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', function () {\n initExistingApprovalForms(container);\n });\n } else {\n setTimeout(function () {\n initExistingApprovalForms(container);\n }, 500); // 额外重试:TokUI 表单可能需要更长时间渲染\n\n setTimeout(function () {\n initConditionalVisibility(container);\n }, 1000);\n setTimeout(function () {\n initConditionalVisibility(container);\n }, 2000);\n setTimeout(function () {\n initConditionalVisibility(container);\n }, 3000);\n } // ---------- select 旧路径(无 repeat 的后向兼容,供无补丁场景兜底) ----------\n\n\n function loadDataFromAttr(_x36) {\n return _loadDataFromAttr.apply(this, arguments);\n } // ---------- MutationObserver 检测新元素 ----------\n\n\n function _loadDataFromAttr() {\n _loadDataFromAttr = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee27(el) {\n var loadEl, url, req, options, rawItems, json, vKeys;\n return regeneratorRuntime.wrap(function _callee27$(_context27) {\n while (1) {\n switch (_context27.prev = _context27.next) {\n case 0:\n // 如果 select 本身没有 data-load(在父 .tokui-field 上),向上找\n loadEl = el;\n\n if (loadEl && !loadEl.getAttribute('data-load') && loadEl.parentElement && loadEl.parentElement.getAttribute('data-load')) {\n loadEl = loadEl.parentElement;\n }\n\n url = loadEl.getAttribute('data-load');\n\n if (url) {\n _context27.next = 5;\n break;\n }\n\n return _context27.abrupt(\"return\");\n\n case 5:\n _context27.prev = 5;\n req = buildDataLoadRequest(loadEl, url);\n\n if (!req.skip) {\n _context27.next = 9;\n break;\n }\n\n return _context27.abrupt(\"return\");\n\n case 9:\n options = getCached(req.cacheKey);\n rawItems = null;\n\n if (options) {\n _context27.next = 18;\n break;\n }\n\n _context27.next = 14;\n return httpDataLoad(req);\n\n case 14:\n json = _context27.sent;\n rawItems = resolveList(json);\n options = mapOptions(json);\n setCached(req.cacheKey, options);\n\n case 18:\n el.innerHTML = options.map(function (o) {\n return '<option value=\"' + (o.v || '') + '\">' + (o.tx || '') + '</option>';\n }).join(''); // ★ 将 API 返回的原始数据以 data-* 属性存储到 option 上(同 selectListFirst 逻辑)\n\n if (rawItems && el.options) {\n vKeys = ['no', 'holidayNo', 'id', 'value', 'code', 'v'];\n rawItems.forEach(function (item) {\n var itemVal = '';\n\n for (var vi = 0; vi < vKeys.length; vi++) {\n if (item[vKeys[vi]] !== undefined) {\n itemVal = String(item[vKeys[vi]]);\n break;\n }\n }\n\n if (!itemVal) return;\n\n for (var oi = 0; oi < el.options.length; oi++) {\n if (el.options[oi].value === itemVal) {\n Object.keys(item).forEach(function (key) {\n var val = item[key];\n if (val === undefined || val === null || (0, _typeof2.default)(val) === 'object') return;\n var attrName = 'data-' + key.replace(/([A-Z])/g, '-$1').toLowerCase();\n el.options[oi].setAttribute(attrName, String(val));\n });\n break;\n }\n }\n });\n } // data-default-label:按文本匹配选中默认项\n\n\n selectDefaultByLabel(el); // 选中后始终触发 change(驱动级联和 detail-url),无论是否匹配到默认值\n\n if (el.options && el.options.length > 0) {\n el.dispatchEvent(new Event('change', {\n bubbles: true\n }));\n }\n\n _context27.next = 27;\n break;\n\n case 24:\n _context27.prev = 24;\n _context27.t0 = _context27[\"catch\"](5);\n console.warn('data-load 失败:', el.id, _context27.t0.message);\n\n case 27:\n case \"end\":\n return _context27.stop();\n }\n }\n }, _callee27, null, [[5, 24]]);\n }));\n return _loadDataFromAttr.apply(this, arguments);\n }\n\n if (typeof MutationObserver !== 'undefined') {\n var observer = new MutationObserver(function (mutations) {\n mutations.forEach(function (m) {\n m.addedNodes.forEach(function (node) {\n if (node.nodeType !== 1) return; // ☆ 立即隐藏 [repeat](重绘前扼制闪烁)\n\n if (node.matches && node.matches('script[type=\"text/tokui-template\"]')) {\n node.style.display = 'none';\n }\n\n node.querySelectorAll('script[type=\"text/tokui-template\"]').forEach(function (el) {\n el.style.display = 'none';\n }); // 已提交表单:跳过所有 data-load / data-load-list / 审批流程等接口请求(仅保留模板隐藏避免闪烁)\n\n if (options.skipDataLoad) return; // [data-load-list] → 列表模式\n\n if (node.hasAttribute('data-load-list')) {\n renderListFromTemplate(node.getAttribute('data-load-list'), node);\n return;\n } // select[data-load] → select 填充\n\n\n if (node.hasAttribute('data-load') && node.tagName === 'SELECT') {\n var tpl = node.querySelector('script[type=\"text/tokui-template\"]');\n\n if (tpl && tpl.textContent) {\n renderListFromTemplate(node.getAttribute('data-load'), node);\n } else {\n loadDataFromAttr(node);\n }\n\n setupCascade(node);\n return;\n } // tokui-field[data-load] 包裹 select → 路由到 select 加载逻辑\n // TokUI 将 [select] 渲染为 <div class=\"tokui-field\"> 包裹 <select>,\n // data-load-* 属性在外层 div 上,内层 <select> 没有这些属性。\n\n\n if (node.hasAttribute('data-load') && node.tagName !== 'SELECT' && node.querySelector('select')) {\n var sel = node.querySelector('select');\n\n if (!sel._tokuiDataLoaded) {\n sel._tokuiDataLoaded = true;\n var rtpl = sel.querySelector('script[type=\"text/tokui-template\"]');\n\n if (rtpl && rtpl.textContent) {\n renderListFromTemplate(node.getAttribute('data-load'), sel);\n } else {\n loadDataFromAttr(sel);\n }\n\n setupCascade(sel);\n }\n\n return;\n } // [data-load] 详情模式\n\n\n if (node.hasAttribute('data-load')) {\n var url = node.getAttribute('data-load');\n (function () {\n var _ref12 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee19(el) {\n var json, data;\n return regeneratorRuntime.wrap(function _callee19$(_context19) {\n while (1) {\n switch (_context19.prev = _context19.next) {\n case 0:\n _context19.prev = 0;\n _context19.next = 3;\n return _request.default.get(url);\n\n case 3:\n json = _context19.sent;\n data = resolveObject(json);\n applyDetailData(el, data);\n _context19.next = 11;\n break;\n\n case 8:\n _context19.prev = 8;\n _context19.t0 = _context19[\"catch\"](0);\n console.warn('data-load 详情失败:', el.id, _context19.t0.message);\n\n case 11:\n case \"end\":\n return _context19.stop();\n }\n }\n }, _callee19, null, [[0, 8]]);\n }));\n\n return function (_x37) {\n return _ref12.apply(this, arguments);\n };\n })()(node);\n return;\n } // 子元素中的 data-load / data-load-list\n\n\n node.querySelectorAll('[data-load-list]').forEach(function (el) {\n if (!el._tokuiDataLoaded) {\n el._tokuiDataLoaded = true;\n renderListFromTemplate(el.getAttribute('data-load-list'), el);\n }\n });\n node.querySelectorAll('select[data-load]').forEach(function (el) {\n if (!el._tokuiDataLoaded) {\n el._tokuiDataLoaded = true;\n var r = el.querySelector('script[type=\"text/tokui-template\"]');\n if (r && r.textContent) renderListFromTemplate(el.getAttribute('data-load'), el);else loadDataFromAttr(el);\n setupCascade(el);\n }\n }); // 静态 select(无 data-load)的 data-default-label 处理\n\n node.querySelectorAll('select[data-default-label]:not([data-load])').forEach(function (el) {\n if (!el._tokuiDefaultLabelSet) {\n el._tokuiDefaultLabelSet = true;\n var label = el.getAttribute('data-default-label');\n\n if (label && el.options) {\n for (var oi = 0; oi < el.options.length; oi++) {\n if ((el.options[oi].textContent || el.options[oi].innerText || '').trim() === label.trim()) {\n el.selectedIndex = oi;\n break;\n }\n }\n }\n }\n }); // 静态 select 的 data-load-detail-url(选择后获取详情)\n\n node.querySelectorAll('select[data-load-detail-url]:not([data-load])').forEach(function (el) {\n if (!el._tokuiDetailUrlSet) {\n el._tokuiDetailUrlSet = true;\n setupCascade(el);\n }\n });\n node.querySelectorAll(':not(select)[data-load]').forEach(function (el) {\n // select 的 data-load 由上面的 loadDataFromAttr / renderListFromTemplate 处理(走 data-load-method),\n // 这里只处理纯详情模式(元素内部不含 select),避免对 select 包装层重复发 GET 请求。\n if (el.querySelector('select')) return;\n\n if (!el._tokuiDataLoaded) {\n el._tokuiDataLoaded = true;\n var url = el.getAttribute('data-load');\n (function () {\n var _ref13 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee20(e) {\n var json;\n return regeneratorRuntime.wrap(function _callee20$(_context20) {\n while (1) {\n switch (_context20.prev = _context20.next) {\n case 0:\n _context20.prev = 0;\n _context20.next = 3;\n return _request.default.get(url);\n\n case 3:\n json = _context20.sent;\n applyDetailData(e, resolveObject(json));\n _context20.next = 10;\n break;\n\n case 7:\n _context20.prev = 7;\n _context20.t0 = _context20[\"catch\"](0);\n console.warn('data-load 详情失败:', e.id, _context20.t0.message);\n\n case 10:\n case \"end\":\n return _context20.stop();\n }\n }\n }, _callee20, null, [[0, 7]]);\n }));\n\n return function (_x38) {\n return _ref13.apply(this, arguments);\n };\n })()(el);\n }\n }); // ============ 补卡申请表单检测 ============\n // 检测补卡申请表单并初始化日历联动\n\n if (node.hasAttribute && node.hasAttribute('data-approval-type')) {\n var approvalType = node.getAttribute('data-approval-type');\n\n if (approvalType === 'patch') {\n var patchForm = node.closest('form') || node.closest('[data-form-id]') || node;\n console.log(patchForm, 'patchForm');\n\n if (patchForm && !patchForm._tvPatchCardInited) {\n patchForm._tvPatchCardInited = true; // ★ 传入表单元素,确保在该表单作用域内查找字段\n\n setTimeout(function () {\n setupPatchCardCalendar(patchForm);\n }, 800);\n }\n }\n } // 子元素中的补卡表单\n\n\n node.querySelectorAll('[data-approval-type=\"patch\"]').forEach(function (el) {\n var pf = el.closest('form') || el.closest('[data-form-id]') || el;\n\n if (pf && !pf._tvPatchCardInited) {\n pf._tvPatchCardInited = true; // ★ 传入表单元素,确保在该表单作用域内查找字段\n\n setTimeout(function () {\n setupPatchCardCalendar(pf);\n }, 800);\n }\n }); // ============ 审批流程初始化 ============\n // 检测新渲染的表单是否配置了 data-approval-type\n\n if (node.hasAttribute && node.hasAttribute('data-approval-type')) {\n var approvalForm = node.closest('form') || node.closest('[data-form-id]') || node;\n\n if (approvalForm && !approvalForm._tvApprovalInited) {\n approvalForm._tvApprovalInited = true; // 延迟初始化,等 select 数据加载完毕\n\n setTimeout(function () {\n initApprovalFlow(approvalForm);\n }, 500);\n }\n } // 子元素中的审批表单\n\n\n node.querySelectorAll('[data-approval-type]').forEach(function (el) {\n var af = el.closest('form') || el.closest('[data-form-id]') || el;\n\n if (af && !af._tvApprovalInited) {\n af._tvApprovalInited = true;\n setTimeout(function () {\n initApprovalFlow(af);\n }, 500);\n }\n });\n });\n });\n });\n observer.observe(container.body || container, {\n childList: true,\n subtree: true\n });\n } // ============ 最终回退:轮询扫描审批表单 ============\n // 某些情况下 MutationObserver 可能漏掉表单(如容器嵌套复杂),\n // 在多个时间点做全量扫描兜底\n\n\n [800, 1500, 3000].forEach(function (delay) {\n setTimeout(function () {\n // 已提交表单:跳过审批流程回退扫描\n if (options.skipDataLoad) return; // ★ 多表单隔离:审批表单扫描限定在本实例 container 内,避免跨消息串扰\n\n var scanRoot = container && container.querySelectorAll ? container : document;\n var forms = scanRoot.querySelectorAll('[data-approval-type]');\n forms.forEach(function (el) {\n var form = el.closest('form') || el.closest('[data-form-id]') || el;\n\n if (form && !form._tvApprovalInited) {\n form._tvApprovalInited = true;\n initApprovalFlow(form);\n } // 补卡表单初始化 ★ 传入表单元素确保隔离\n\n\n if (el.getAttribute('data-approval-type') === 'patch' && !form._tvPatchCardInited) {\n form._tvPatchCardInited = true;\n setupPatchCardCalendar(form);\n }\n }); // 也扫描所有 [data-form-id] 表单,检查是否有 data-approval-type\n\n if (forms.length === 0) {\n var allForms = scanRoot.querySelectorAll('[data-form-id]');\n allForms.forEach(function (f) {\n if (f.hasAttribute('data-approval-type') && !f._tvApprovalInited) {\n f._tvApprovalInited = true;\n initApprovalFlow(f);\n }\n\n if (f.getAttribute('data-approval-type') === 'patch' && !f._tvPatchCardInited) {\n f._tvPatchCardInited = true;\n setupPatchCardCalendar(f);\n }\n });\n }\n }, delay);\n }); // ============ 暴露 API 供外部调用(如 Vue 组件的收尾对齐) ============\n\n if (typeof window !== 'undefined') {\n if (!window.__tokuiValidatorBridge) window.__tokuiValidatorBridge = {};\n window.__tokuiValidatorBridge.initConditionalVisibility = initConditionalVisibility;\n window.__tokuiValidatorBridge.updateConditionalVisibility = updateConditionalVisibility;\n window.__tokuiValidatorBridge.initExistingApprovalForms = initExistingApprovalForms;\n window.__tokuiValidatorBridge.applyDefaultValues = applyDefaultValues;\n window.__tokuiValidatorBridge.applyFormData = applyFormData;\n window.__tokuiValidatorBridge.fixDateTimeDefaults = fixDateTimeDefaults;\n window.__tokuiValidatorBridge.markFormReady = markFormReady;\n window.__tokuiValidatorBridge.stopApprovalFlows = stopApprovalFlows;\n window.__tokuiValidatorBridge.setupPatchCardCalendar = setupPatchCardCalendar; // lockFormWithOverlay 已在 __validatorHandlersBound 守卫块内挂载(块级作用域,仅首次),此处不覆盖\n } // ============ 注入样式 ============\n\n\n var APPROVAL_STYLES = document.getElementById('tv-approval-styles');\n\n if (!APPROVAL_STYLES) {\n var styleEl = document.createElement('style');\n styleEl.id = 'tv-approval-styles';\n styleEl.textContent = '\\\r\n /* ── 审批人选择弹窗(列表模式) ── */\\\r\n .tv-approver-backdrop { position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,.3); z-index:9999; display:flex; align-items:center; justify-content:center; }\\\r\n .tv-approver-dialog { background:#fff; border-radius:8px; min-width:420px; max-width:520px; max-height:80vh; display:flex; flex-direction:column; box-shadow:0 4px 24px rgba(0,0,0,.15); }\\\r\n .tv-approver-dialog-header { display:flex; align-items:center; justify-content:space-between; padding:16px 20px; border-bottom:1px solid #ebeef5; }\\\r\n .tv-approver-dialog-title { font-size:16px; font-weight:600; color:#303133; }\\\r\n .tv-approver-dialog-close { font-size:20px; color:#909399; cursor:pointer; line-height:1; }\\\r\n .tv-approver-dialog-close:hover { color:#409EFF; }\\\r\n .tv-approver-dialog-body { padding:20px; overflow-y:auto; flex:1; }\\\r\n .tv-approver-dialog-body p { margin:0 0 16px; font-size:13px; color:#606266; }\\\r\n .tv-approver-dialog-footer { display:flex; justify-content:flex-end; gap:10px; padding:12px 20px; border-top:1px solid #ebeef5; }\\\r\n .tv-approver-dialog-confirm,.tv-approver-dialog-cancel { padding:8px 20px; border-radius:4px; font-size:13px; cursor:pointer; border:none; }\\\r\n .tv-approver-dialog-confirm { background:#409EFF; color:#fff; }\\\r\n .tv-approver-dialog-confirm:hover { background:#337ecc; }\\\r\n .tv-approver-dialog-cancel { background:#f5f7fa; color:#606266; }\\\r\n .tv-approver-dialog-cancel:hover { background:#e4e7ed; }\\\r\n .tv-approver-dialog-empty { text-align:center; padding:30px 0; color:#909399; font-size:14px; }\\\r\n .tv-approver-dialog .duox,.tv-approver-dialog .danx { display:block; margin-bottom:8px; font-size:14px; color:#303133; }\\\r\n .tv-approver-dialog .duox input,.tv-approver-dialog .danx input { margin-right:6px; vertical-align:middle; }\\\r\n /* ── 审批流程区域 ── */\\\r\n .tv-approval-flow { margin-top:8px; padding:8px 0; }\\\r\n .tv-approval-flow--hidden { display:none !important; }\\\r\n .tv-approval-wrapper { display:block; }\\\r\n .tv-approval-flow-label { font-size:12px; color:#909399; margin-bottom:6px; }\\\r\n .tv-approval-node { display:flex; align-items:flex-start; padding:6px 0; }\\\r\n .tv-approval-node+.tv-approval-node { border-top:1px dashed #e4e7ed; }\\\r\n .tv-approval-node-index { width:24px; height:24px; border-radius:50%; background:#409EFF; color:#fff; font-size:12px; display:flex; align-items:center; justify-content:center; flex-shrink:0; margin-right:10px; margin-top:2px; }\\\r\n .tv-approval-node-info { flex:1; min-width:0; }\\\r\n .tv-approval-node-name { font-size:13px; color:#303133; font-weight:500; margin-bottom:4px; }\\\r\n .tv-approval-node-name .tv-cc-badge { display:inline-block; font-size:11px; color:#409EFF; margin-left:4px; }\\\r\n .tv-approver-list { display:flex; flex-wrap:wrap; gap:4px; align-items:center; }\\\r\n .tv-approver-tag { display:inline-flex; align-items:center; padding:2px 8px; border-radius:12px; font-size:12px; white-space:nowrap; flex-wrap:nowrap; }\\\r\n .tv-approver-tag--normal { background:#ecf5ff; color:#409EFF; }\\\r\n .tv-approver-tag--auto { background:#f0f9eb; color:#67c23a; }\\\r\n .tv-approver-tag--empty { background:#fdf6ec; color:#e6a23c; }\\\r\n .tv-approver-tag .tv-approver-tag-del { margin-left:4px; cursor:pointer; font-weight:bold; }\\\r\n .tv-approval-node-arrow { position:absolute; left:-18px; top:50%; transform:translateY(-50%); font-size:18px; color:#409EFF; }\\\r\n /* ── 组织架构选人弹窗 ── */\\\r\n .tv-org-dialog { background:#fff; border-radius:8px; width:700px; max-height:80vh; display:flex; flex-direction:column; box-shadow:0 4px 24px rgba(0,0,0,.15); }\\\r\n .tv-org-dialog-body { display:flex; flex:1; min-height:360px; overflow:hidden; }\\\r\n .tv-org-left { width:60%; border-right:1px solid #ebeef5; display:flex; flex-direction:column; }\\\r\n .tv-org-right { width:40%; display:flex; flex-direction:column; padding:12px; }\\\r\n .tv-org-right .block-title { font-size:13px; color:#909399; margin-bottom:8px; }\\\r\n .tv-org-search { width:100%; padding:8px 12px; border:none; border-bottom:1px solid #ebeef5; font-size:13px; outline:none; box-sizing:border-box; }\\\r\n .tv-org-search:focus { border-bottom-color:#409EFF; }\\\r\n .tv-org-tree { flex:1; overflow-y:auto; padding:4px 0; }\\\r\n .tv-org-node { display:flex; align-items:center; padding:4px 8px; font-size:13px; cursor:default; }\\\r\n .tv-org-node:hover { background:#f5f7fa; }\\\r\n .tv-org-arrow { width:16px; font-size:10px; color:#909399; flex-shrink:0; text-align:center; }\\\r\n .tv-org-arrow.expanded { color:#409EFF; }\\\r\n .tv-org-label--dept { color:#303133; font-weight:500; }\\\r\n .tv-org-label { margin-left:4px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\\\r\n .tv-org-check { margin-right:4px; flex-shrink:0; }\\\r\n .tv-org-selected-list { flex:1; overflow-y:auto; list-style:none; padding:0; margin:0; }\\\r\n .tv-org-selected-item { display:flex; align-items:center; justify-content:space-between; padding:6px 8px; font-size:13px; border-radius:4px; }\\\r\n .tv-org-selected-item:hover { background:#f5f7fa; }\\\r\n .tv-org-selected-del { color:#f56c6c; cursor:pointer; font-size:14px; padding:0 4px; }\\\r\n .tv-org-selected-del:hover { color:#f00; }\\\r\n .tv-org-loading,.tv-org-error,.tv-org-empty { text-align:center; padding:30px 0; color:#909399; font-size:13px; }\\\r\n .tv-org-error { color:#f56c6c; }\\\r\n /* ── 审批人圆形头像占位(有头像/无头像统一展示) ── */\\\r\n .tv-approver-avatar { width:24px; height:24px; border-radius:50%; display:inline-flex; align-items:center; justify-content:center; overflow:hidden; flex-shrink:0; background:#409EFF; color:#fff; font-size:11px; line-height:1; }\\\r\n .tv-approver-avatar-img { width:100%; height:100%; object-fit:cover; display:block; }\\\r\n .tv-approver-tag { align-items:center; }\\\r\n /* ── 审批人选择弹窗(列表模式)列表项样式 ── */\\\r\n .tv-approver-dialog-item { display:flex; align-items:center; cursor:pointer; padding:8px 10px; border-radius:4px; font-size:14px; color:#303133; }\\\r\n .tv-approver-dialog-item:hover { background:#f5f7fa; }\\\r\n .tv-approver-dialog-item input[type=\"checkbox\"],.tv-approver-dialog-item input[type=\"radio\"] { margin-right:8px; }\\\r\n /* ── H5/移动端适配 ── */\\\r\n @media (max-width:640px){\\\r\n .tv-approver-backdrop { align-items:flex-start; overflow-y:auto; padding:6vh 4vw; box-sizing:border-box; }\\\r\n .tv-approver-dialog { width:100%; max-width:100%; min-width:0; }\\\r\n .tv-org-dialog { width:100%; max-width:100%; }\\\r\n .tv-approver-dialog-body { padding:14px; }\\\r\n .tv-approver-dialog-footer { padding:10px 14px; }\\\r\n .tv-org-dialog-body { flex-direction:column; min-height:0; }\\\r\n .tv-org-left { width:100%; border-right:none; border-bottom:1px solid #ebeef5; max-height:56vh; }\\\r\n .tv-org-right { width:100%; }\\\r\n }\\\r\n /* ── 图片上传组件样式 ── */\\\r\n .tokui-field.tokui-imageupload { display:block; }\\\r\n .tokui-imageupload-area { border:1px dashed #d9d9d9; border-radius:4px; padding:12px; transition:border-color .3s; }\\\r\n .tokui-imageupload-area:hover { border-color:#409EFF; }\\\r\n .tokui-imageupload-preview { display:flex; flex-wrap:wrap; gap:8px; margin-bottom:8px; }\\\r\n .tokui-imageupload-item { position:relative; width:100px; height:100px; border-radius:4px; overflow:hidden; border:1px solid #d9d9d9; }\\\r\n .tokui-imageupload-item img { width:100%; height:100%; object-fit:cover; display:block; }\\\r\n .tokui-imageupload-item-actions { position:absolute; top:0; right:0; background:rgba(0,0,0,.5); padding:2px 4px; display:flex; gap:4px; opacity:0; transition:opacity .3s; }\\\r\n .tokui-imageupload-item:hover .tokui-imageupload-item-actions { opacity:1; }\\\r\n .tokui-imageupload-item-del { color:#fff; cursor:pointer; font-size:12px; }\\\r\n .tokui-imageupload-item-del:hover { color:#f56c6c; }\\\r\n .tokui-imageupload-item-uploading { position:absolute; top:0; left:0; width:100%; height:100%; background:rgba(255,255,255,.9); display:flex; align-items:center; justify-content:center; font-size:12px; color:#409EFF; }\\\r\n .tokui-imageupload-add { width:100px; height:100px; border:1px dashed #d9d9d9; border-radius:4px; display:flex; align-items:center; justify-content:center; cursor:pointer; font-size:24px; color:#909399; transition:all .3s; background:#fafafa; }\\\r\n .tokui-imageupload-add:hover { border-color:#409EFF; color:#409EFF; }\\\r\n .tokui-imageupload-tip { font-size:12px; color:#909399; margin-top:8px; line-height:1.5; }\\\r\n ';\n document.head.appendChild(styleEl);\n } // ============ 返回清理与缓存控制 ============\n\n\n return {\n destroy: function destroy() {\n TRIGGER_EVENTS.forEach(function (evt) {\n container.removeEventListener(evt, handleTriggerEvent, true);\n });\n container.removeEventListener('change', handleClkOnChange, true);\n\n if (_condDomObserver) {\n _condDomObserver.disconnect();\n\n _condDomObserver = null;\n }\n\n if (observer) observer.disconnect();\n Object.keys(debounceTimers).forEach(function (k) {\n return cancelDebounce(k);\n });\n },\n clearCache: clearCache\n };\n}\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/tokui-validator/tokui-validator-bridge.js?");
|
|
15796
|
+
eval("\n\nvar _interopRequireWildcard = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/interopRequireWildcard */ \"./node_modules/@babel/runtime/helpers/interopRequireWildcard.js\").default;\n\nvar _interopRequireDefault = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/interopRequireDefault */ \"./node_modules/@babel/runtime/helpers/interopRequireDefault.js\").default;\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.setupValidator = setupValidator;\n\nvar _typeof2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/typeof */ \"./node_modules/@babel/runtime/helpers/typeof.js\"));\n\nvar _asyncToGenerator2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/asyncToGenerator */ \"./node_modules/@babel/runtime/helpers/asyncToGenerator.js\"));\n\nvar _message = __webpack_require__(/*! @lib/utils/message.js */ \"./src/lib/utils/message.js\");\n\nvar _request = _interopRequireWildcard(__webpack_require__(/*! @lib/utils/request.js */ \"./src/lib/utils/request.js\"));\n\n/**\r\n * TokUI Validator Bridge — 把 Validator 引擎绑定到 TokUI 事件系统\r\n *\r\n * 使用方式:\r\n * import V from './validators';\r\n * import { setupValidator } from './tokui-validator-bridge';\r\n * setupValidator(tokuiInstance, V, {\r\n * parseResult: (json) => json.valid ? '' : json.message,\r\n * buildOptions: (json) => json.data.map(i => ({ v: i.code, tx: i.name }))\r\n * });\r\n *\r\n * DSL 侧:\r\n * [input id:email l:邮箱 data-rule:\"required|email|remote:/api/validate/check-email\" data-trigger:input,blur]\r\n * [select id:city l:城市 data-load:\"/api/cities\"]\r\n * [select id:district l:区县 data-load:\"/api/districts?city=${city}\"]\r\n * [btn tx:提交 sub:formSubmit]\r\n *\r\n * 数据加载:DSL 中声明 data-load 属性即可,无需 JS 配置。\r\n * 校验配置:parseResult 控制远程校验的响应解析(可选,默认 json.valid)。\r\n * 加载映射:buildOptions 控制 JSON→options 格式映射(可选,默认从 data.code/name 或 data.id/label)。\r\n *\r\n * 本项目适配说明(zj-plugin-intelligent):\r\n * - 消息提示使用项目内置 showMessage(@lib/utils/message.js),不依赖 element-ui\r\n * - 所有网络请求统一走 @lib/utils/request.js,复用其 AK/SK 签名、token 注入与 code 校验\r\n */\n// 统一走 request.js(AK/SK 签名、token 注入、code 校验),不使用 http/index.js\n\n/**\r\n * 读取登录用户信息(名称 / 工号 / 头像 / IM 账号)统一封装。\r\n * 优先取 userinfo 接口缓存 window.__tokuiUserInfo(由宿主 ChatMain 在 getCurrentUserInfo\r\n * 拿到接口数据后写入),缺失字段依次回退 window.__tokuiParams 与 getAuth(),\r\n * 避免各表单组装处散落一长串 `xxx || xxx || ...` 兜底。\r\n * 头像字段:userinfo 中为 headImg,兼容 userImg / avatar。\r\n * @param {object} fallbackUser 可选,对应 options.user(登录态用户对象),作为回退来源\r\n */\nfunction getLoginUserInfo(fallbackUser) {\n var win = typeof window !== 'undefined' ? window : null;\n var ui = win && win.__tokuiUserInfo || {};\n var tp = win && win.__tokuiParams || {};\n var auth = (0, _request.getAuth)();\n var fu = fallbackUser || {};\n return {\n name: ui.nickName || ui.staffName || ui.userName || ui.realName || fu.staffName || fu.userName || tp.loginStaffName || tp.loginRealName || tp.loginAccountName || auth.staffName || auth.user || '',\n no: ui.no != null ? ui.no : ui.userNo || fu.no || fu.id || tp.loginUserNo || tp.loginUserno || auth.userno || auth.user || '',\n img: ui.headImg || ui.userImg || ui.avatar || fu.userUrl || fu.img || '',\n account: ui.account || ui.accountName || ui.userAccount || fu.account || tp.loginAccountName || auth.user || ''\n };\n}\n/**\r\n * 格式化时间 → YYYY-MM-dd HH:mm 字符串\r\n * 对齐参照项目 getTime 工具函数行为(workTimeApplyAdd.vue submitForm 中使用)\r\n * 输入:Date 对象、时间戳(数字/字符串)、Date 字符串\r\n */\n\n\nfunction formatToDateTimeStr(input) {\n var date;\n\n if (input instanceof Date) {\n date = input;\n } else if (typeof input === 'number' || typeof input === 'string' && /^\\d+$/.test(input.trim())) {\n date = new Date(Number(input));\n } else {\n date = new Date(input);\n }\n\n if (isNaN(date.getTime())) return String(input);\n var Y = date.getFullYear() + '-';\n var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-';\n var D = (date.getDate() < 10 ? '0' + date.getDate() : date.getDate()) + ' ';\n var h = date.getHours() < 10 ? '0' + date.getHours() + ':' : date.getHours() + ':';\n var m = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes();\n return Y + M + D + h + m;\n}\n\nfunction resolveForm(el) {\n // TokUI datepicker 调用 clk 处理器时只传 data 对象,element 为 undefined\n // 此时回退到 document 级别查找表单(单表单场景兜底,多表单场景下 element 始终存在)\n if (!el) return document.querySelector('[data-form-id]') || document.querySelector('form');\n var form = el.closest('[data-form-id]') || el.closest('form');\n if (form) return form; // ★ 多表单隔离:优先在元素所属的 TokUI 容器(.tokui-stream)内查找表单,\n // 避免 document 级查询返回第一个表单导致跨消息串扰\n\n var root = el.closest('.tokui-stream');\n\n if (root) {\n var scoped = root.querySelector('[data-form-id]') || root.querySelector('form');\n if (scoped) return scoped;\n }\n\n var formId = el.getAttribute('form') || el.getAttribute('data-tokui-form');\n\n if (formId) {\n // 多表单隔离:优先在元素所属 .tokui-stream 内查找,避免跨消息串扰\n var scopeRoot = el.closest('.tokui-stream');\n var byId = null;\n\n if (scopeRoot) {\n var allWithId = scopeRoot.querySelectorAll('[id]');\n\n for (var fi = 0; fi < allWithId.length; fi++) {\n if (allWithId[fi].id === formId) {\n byId = allWithId[fi];\n break;\n }\n }\n } else {\n byId = document.getElementById(formId);\n }\n\n if (byId) return byId;\n } // 回退:按按钮的 form 属性引用查找\n\n\n var formAttr = el.getAttribute && el.getAttribute('form');\n\n if (formAttr) {\n var scopeRoot2 = el.closest('.tokui-stream');\n var ref = null;\n\n if (scopeRoot2) {\n var allWithId2 = scopeRoot2.querySelectorAll('[id]');\n\n for (var fj = 0; fj < allWithId2.length; fj++) {\n if (allWithId2[fj].id === formAttr) {\n ref = allWithId2[fj];\n break;\n }\n }\n } else {\n ref = document.getElementById(formAttr);\n }\n\n if (ref) return ref;\n }\n\n return null;\n}\n\nfunction collectFormData(el) {\n var form = resolveForm(el);\n if (!form) return {};\n var data = {}; // 优先从 form 子树收集,再从更广范围补充(TokUI 可能将部分字段渲染在 form 外部)\n\n var scopes = [form]; // 向上找 TokUI 容器作为补充搜索范围(多表单隔离:不 fallback 到 document)\n\n var parentScope = form.closest('.tokui-stream') || form.closest('[data-tokui-tag]') || form;\n\n if (parentScope !== form) {\n scopes.push(parentScope);\n }\n\n scopes.forEach(function (scope) {\n scope.querySelectorAll('[id], [name]').forEach(function (f) {\n var key = f.id || f.name;\n if (!key) return; // 跳过被条件显示隐藏的元素(如 data-visible-when 隐藏的 datepicker/datetimepicker)\n\n if (f.closest('.tokui-conditional-hidden')) return; // 跳过非表单元素(如 div/span 上的 id)除非它们有 value 属性\n\n if (f.tagName !== 'INPUT' && f.tagName !== 'SELECT' && f.tagName !== 'TEXTAREA') {\n // tokui-field 包装层等非表单元素:尝试找内部表单控件\n var inner = f.querySelector && f.querySelector('input,select,textarea');\n if (!inner) return;\n f = inner;\n if (!f.id && !f.name) return;\n }\n\n var k = f.id || f.name;\n var newVal;\n\n if (f.type === 'checkbox' || f.type === 'radio') {\n if (f.checked) newVal = f.value;else return;\n } else if (f.tagName === 'SELECT' && f.multiple) {\n newVal = Array.from(f.selectedOptions).map(function (o) {\n return o.value;\n });\n } else {\n newVal = f.value;\n } // 防止同名元素空值覆盖已收集的非空值(如 readonly datepicker input 的值被另一个空 input 覆盖)\n\n\n if (data[k] !== undefined && data[k] !== '' && (newVal === '' || newVal === undefined)) return;\n data[k] = newVal;\n\n if (k === 'entryTime') {}\n });\n });\n return data;\n} // ============ 防抖 ============\n\n\nvar debounceTimers = {};\n\nfunction debounce(key, fn, ms) {\n if (debounceTimers[key]) clearTimeout(debounceTimers[key]);\n debounceTimers[key] = setTimeout(function () {\n delete debounceTimers[key];\n fn();\n }, ms);\n}\n\nfunction cancelDebounce(key) {\n if (debounceTimers[key]) {\n clearTimeout(debounceTimers[key]);\n delete debounceTimers[key];\n }\n}\n\nvar DEBOUNCED_EVENTS = ['input', 'keyup'];\nvar TRIGGER_EVENTS = ['blur', 'focusout', 'input', 'change', 'keyup']; // ★ 从元素向上查找所属 TokUI 表单作用域(.tokui-stream / data-tokui-scope)。\n// 仅做 DOM 级向上查找,绝不回退到任何闭包捕获的 container,避免多表单间串扰。\n\nfunction findNearestTokuiScope(el) {\n if (el && el.closest) {\n return el.closest('[data-tokui-scope]') || el.closest('.tokui-stream') || null;\n }\n\n return null;\n}\n\nfunction setupValidator(tokuiInstance, V) {\n var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};\n var _options$container = options.container,\n container = _options$container === void 0 ? document : _options$container,\n _options$debounceMs = options.debounceMs,\n debounceMs = _options$debounceMs === void 0 ? 400 : _options$debounceMs,\n onFormSuccess = options.onFormSuccess,\n _options$scopeKey = options.scopeKey,\n scopeKey = _options$scopeKey === void 0 ? '' : _options$scopeKey; // 成功回调挂在容器 DOM 上:多表单会话中各消息容器各自持有,handler 按元素归属向上查找\n\n if (container && container !== document) {\n container._onFormSuccess = onFormSuccess; // 用 msgKey 标识当前表单容器,作为 DOM 查询的唯一作用域,实现多表单隔离\n\n if (scopeKey) container.setAttribute('data-tokui-scope', scopeKey);\n } // 作用域解析:优先从元素向上查找所属表单容器,回退到本实例的 container。\n // 多表单场景下所有跨表单的 document 级查询都必须通过该函数收敛到当前消息容器。\n\n\n function resolveScope(el) {\n if (el && el.closest) {\n var scoped = el.closest('[data-tokui-scope]') || el.closest('.tokui-stream');\n if (scoped) return scoped;\n }\n\n return container && container !== document ? container : document;\n } // 在指定作用域内按 id 精确查找元素(避免 document.getElementById 跨表单串扰)\n\n\n function queryById(id, scope) {\n var root = scope || (container && container.querySelectorAll ? container : document);\n if (!root || !root.querySelectorAll || !id) return null;\n var all = root.querySelectorAll('[id]');\n\n for (var i = 0; i < all.length; i++) {\n if (all[i].id === id) return all[i];\n }\n\n return null;\n } // ============ clk handler 本地注册表 ============\n // 拦截 window.TokUI.registerHandler,将处理器函数存入本地 _clkHandlers,\n // 供 change 事件委托直接调用(绕过 TokUI 的 click-only 限制)\n\n\n var _clkHandlers = window.TokUI._clkHandlers = window.TokUI._clkHandlers || {};\n\n if (!window.TokUI.__registerHandlerPatched) {\n window.TokUI.__registerHandlerPatched = true;\n var _origRegisterHandler = window.TokUI.registerHandler;\n\n window.TokUI.registerHandler = function (name, fn) {\n _clkHandlers[name] = fn;\n return _origRegisterHandler.call(window.TokUI, name, fn);\n };\n } // 安全反馈:挂到指定宿主元素(通常为当前表单),保证多表单会话中反馈不串消息\n\n\n function safeFeed(dsl, hostEl) {\n try {\n // ★ 找到合适的容器:优先 btngroup 所在的容器,确保 callout 在表单最底部\n var host = null;\n\n if (hostEl) {\n // 找 btngroup 容器(可能不在 hostEl 内部),作用域限定在当前表单容器,避免跨消息串扰\n var scope = resolveScope(hostEl);\n var submitBtn = scope && scope.querySelector ? scope.querySelector('[data-tokui-sub=\"formSubmit\"]') : null;\n\n if (submitBtn) {\n // 找 hostEl 和 submitBtn 的共同父级(LCA),callout 放在共同父级的末尾\n var p = hostEl.parentElement;\n\n while (p && p !== document.body) {\n if (p.contains(submitBtn)) break;\n p = p.parentElement;\n }\n\n host = p || hostEl;\n } else {\n host = hostEl;\n }\n }\n\n if (!host) {\n host = container && container !== document ? container : null;\n }\n\n if (!host) return;\n var Ctor = tokuiInstance && tokuiInstance.constructor;\n if (!Ctor) return; // ★ 清除旧的 feedback,每次重新创建确保位置正确\n\n var oldFb = host.querySelector('.tokui-validator-feedback');\n if (oldFb) oldFb.remove();\n var fb = document.createElement('div');\n fb.className = 'tokui-validator-feedback';\n host.appendChild(fb);\n new Ctor({\n container: fb\n }).render(dsl);\n } catch (e) {\n console.warn('[Validator] feedback render error:', e);\n }\n } // 从元素向上查找宿主容器注册的表单成功回调\n\n\n function findFormSuccessCb(el) {\n for (var n = el; n; n = n.parentElement) {\n if (n._onFormSuccess) return n._onFormSuccess;\n }\n\n return null;\n } // ============ 数据请求缓存(data-load 用) ============\n\n\n var CACHE_TTL = options.cacheTtl !== undefined ? options.cacheTtl : 300000;\n\n var _dataCache = new Map();\n\n function getCached(url) {\n var entry = _dataCache.get(url);\n\n if (!entry) return null;\n\n if (CACHE_TTL && Date.now() - entry.ts > CACHE_TTL) {\n _dataCache.delete(url);\n\n return null;\n }\n\n return entry.data;\n }\n\n function setCached(url, data) {\n _dataCache.set(url, {\n data: data,\n ts: Date.now()\n });\n }\n\n var clearCache = function clearCache(url) {\n if (url !== undefined) _dataCache.delete(url);else _dataCache.clear();\n }; // =================================================\n // ============ 注册 [repeat] 组件 ============\n // 渲染为不可见 <script>,原始内容存在 textContent 中\n // 桥接层读取 textContent 作为模板,替换 $field$ 后 feed 给 TokUI\n\n\n if (tokuiInstance && tokuiInstance.renderer) {\n tokuiInstance.renderer.register('repeat', function (node) {\n var el = document.createElement('script');\n el.type = 'text/tokui-template';\n el.style.display = 'none';\n if (node.content) el.textContent = node.content;\n el._slot = el;\n el._tokuiType = 'repeat';\n return el;\n }); // ============ 注册 [imageupload] 组件 ============\n // 补卡申请需要的图片上传组件\n\n tokuiInstance.renderer.register('imageupload', function (node) {\n var wrapper = document.createElement('div');\n wrapper.className = 'tokui-field tokui-imageupload';\n wrapper.id = node.id || ''; // 标签\n\n if (node.l) {\n var label = document.createElement('label');\n label.className = 'tokui-field__label';\n label.textContent = node.l;\n wrapper.appendChild(label);\n } // 上传区域\n\n\n var uploadArea = document.createElement('div');\n uploadArea.className = 'tokui-imageupload-area'; // 文件input(隐藏)\n\n var fileInput = document.createElement('input');\n fileInput.type = 'file';\n fileInput.id = node.id || '';\n fileInput.name = node.n || node.id || '';\n fileInput.accept = node['data-accept'] || 'image/*';\n fileInput.multiple = true;\n fileInput.style.display = 'none'; // 预览容器\n\n var previewContainer = document.createElement('div');\n previewContainer.className = 'tokui-imageupload-preview'; // 添加按钮\n\n var addBtn = document.createElement('div');\n addBtn.className = 'tokui-imageupload-add';\n addBtn.innerHTML = '<span class=\"tokui-imageupload-icon\">+</span><span class=\"tokui-imageupload-text\">上传图片</span>';\n addBtn.addEventListener('click', function () {\n fileInput.click();\n }); // 提示文字\n\n var tip = document.createElement('div');\n tip.className = 'tokui-imageupload-tip';\n tip.textContent = node['data-tip'] || '支持 jpg、png、gif 格式,单张不超过3MB'; // 存储已上传的图片URL列表\n\n var uploadedUrls = [];\n var maxCount = parseInt(node['data-max-count']) || 5;\n var maxSize = parseInt(node['data-max-size']) || 3; // MB\n // 文件选择处理\n\n fileInput.addEventListener('change', function (e) {\n var files = Array.from(e.target.files);\n\n if (uploadedUrls.length + files.length > maxCount) {\n (0, _message.showMessage)('最多只能上传' + maxCount + '张图片', 'warning');\n return;\n }\n\n files.forEach(function (file) {\n var validTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/jpg', 'image/bmp'];\n\n if (!validTypes.includes(file.type)) {\n (0, _message.showMessage)('图片格式不支持', 'error');\n return;\n }\n\n if (file.size / 1024 / 1024 > maxSize) {\n (0, _message.showMessage)('图片大小不能超过' + maxSize + 'MB', 'error');\n return;\n }\n\n var reader = new FileReader();\n\n reader.onload = function (event) {\n addPreviewItem(event.target.result, file);\n };\n\n reader.readAsDataURL(file);\n });\n fileInput.value = '';\n });\n\n function addPreviewItem(dataUrl, file) {\n var item = document.createElement('div');\n item.className = 'tokui-imageupload-item';\n var img = document.createElement('img');\n img.src = dataUrl;\n img.className = 'tokui-imageupload-img';\n var removeBtn = document.createElement('span');\n removeBtn.className = 'tokui-imageupload-remove';\n removeBtn.innerHTML = '×';\n removeBtn.addEventListener('click', function () {\n var index = uploadedUrls.indexOf(dataUrl);\n if (index > -1) uploadedUrls.splice(index, 1);\n item.remove();\n updateAddBtnVisibility();\n updateHiddenInput();\n });\n var loading = document.createElement('div');\n loading.className = 'tokui-imageupload-loading';\n loading.textContent = '上传中...';\n item.appendChild(img);\n item.appendChild(removeBtn);\n item.appendChild(loading);\n previewContainer.insertBefore(item, addBtn);\n uploadToServer(file, item, dataUrl);\n }\n\n function uploadToServer(file, itemEl, dataUrl) {\n var formData = new FormData();\n formData.append('file', file);\n var uploadUrl = node['data-upload-url'] || '/api/file/upload';\n fetch(uploadUrl, {\n method: 'POST',\n body: formData,\n headers: {\n 'Authorization': 'Bearer ' + ((0, _request.getAuth)().token || '')\n }\n }).then(function (response) {\n return response.json();\n }).then(function (json) {\n var loading = itemEl.querySelector('.tokui-imageupload-loading');\n if (loading) loading.remove();\n\n if (json.code === 200) {\n var _json$data, _json$data2;\n\n var url = ((_json$data = json.data) === null || _json$data === void 0 ? void 0 : _json$data.obsUrl) || ((_json$data2 = json.data) === null || _json$data2 === void 0 ? void 0 : _json$data2.url) || json.data;\n uploadedUrls.push(url);\n itemEl.setAttribute('data-url', url);\n updateHiddenInput();\n updateAddBtnVisibility();\n } else {\n (0, _message.showMessage)('上传失败:' + (json.msg || json.message), 'error');\n itemEl.remove();\n }\n }).catch(function (err) {\n var loading = itemEl.querySelector('.tokui-imageupload-loading');\n if (loading) loading.remove();\n (0, _message.showMessage)('上传失败:' + err.message, 'error');\n itemEl.remove();\n });\n }\n\n function updateHiddenInput() {\n var hiddenInput = wrapper.querySelector('input[type=\"hidden\"]');\n\n if (!hiddenInput) {\n hiddenInput = document.createElement('input');\n hiddenInput.type = 'hidden';\n hiddenInput.name = node.n || node.id || '';\n hiddenInput.id = (node.id || '') + '_urls';\n wrapper.appendChild(hiddenInput);\n }\n\n hiddenInput.value = JSON.stringify(uploadedUrls);\n }\n\n function updateAddBtnVisibility() {\n if (uploadedUrls.length >= maxCount) {\n addBtn.style.display = 'none';\n } else {\n addBtn.style.display = 'flex';\n }\n }\n\n if (node.v) {\n try {\n var initUrls = typeof node.v === 'string' ? JSON.parse(node.v) : node.v;\n\n if (Array.isArray(initUrls)) {\n initUrls.forEach(function (url) {\n var item = document.createElement('div');\n item.className = 'tokui-imageupload-item';\n item.setAttribute('data-url', url);\n var img = document.createElement('img');\n img.src = url;\n img.className = 'tokui-imageupload-img';\n var removeBtn = document.createElement('span');\n removeBtn.className = 'tokui-imageupload-remove';\n removeBtn.innerHTML = '×';\n removeBtn.addEventListener('click', function () {\n var index = uploadedUrls.indexOf(url);\n if (index > -1) uploadedUrls.splice(index, 1);\n item.remove();\n updateAddBtnVisibility();\n updateHiddenInput();\n });\n item.appendChild(img);\n item.appendChild(removeBtn);\n previewContainer.insertBefore(item, addBtn);\n uploadedUrls.push(url);\n });\n updateAddBtnVisibility();\n updateHiddenInput();\n }\n } catch (e) {}\n }\n\n previewContainer.appendChild(addBtn);\n uploadArea.appendChild(fileInput);\n uploadArea.appendChild(previewContainer);\n wrapper.appendChild(uploadArea);\n wrapper.appendChild(tip);\n return wrapper;\n });\n } // ============ 补卡申请:日历加载与时间联动 ============\n\n\n function setupPatchCardCalendar(formElement) {\n // ★ 表单隔离:传入的可能不是 form,先向上找到最近的 form/[data-form-id] 容器\n var formScope = formElement && (formElement.tagName === 'FORM' || formElement.hasAttribute('data-form-id')) ? formElement : formElement ? formElement.closest('form') || formElement.closest('[data-form-id]') || formElement : document; // 通过 data-id 查找补卡字段(TokUI 渲染后元素是 data-id 而非 id),\n // 优先在 formScope 内找,未找到则向上级父元素递归查找\n\n function findField(dataId, scope, maxDepth) {\n if (!scope || !scope.querySelectorAll) return null;\n maxDepth = maxDepth || 5;\n var el = scope.querySelector('[data-id=\"' + dataId + '\"]');\n if (el) return el; // 向上递归查找\n\n var parent = scope.parentElement;\n var depth = 0;\n\n while (parent && parent !== document && parent !== document.body && depth < maxDepth) {\n el = parent.querySelector('[data-id=\"' + dataId + '\"]');\n if (el) return el;\n parent = parent.parentElement;\n depth++;\n }\n\n return null;\n }\n\n var patchDateEl = findField('patchDate', formScope);\n var patchTimeEl = findField('patchTime', formScope);\n if (!patchDateEl || !patchTimeEl) return; // data-id 通常挂在 TokUI 字段包装层,联动时必须操作内部真实控件\n\n var patchDateInput = patchDateEl.matches && patchDateEl.matches('input') ? patchDateEl : patchDateEl.querySelector('.tokui-datepicker-input, input:not([type=\"hidden\"])');\n var patchTimeSelect = patchTimeEl.matches && patchTimeEl.matches('select') ? patchTimeEl : patchTimeEl.querySelector('select');\n if (!patchDateInput || !patchTimeSelect) return; // ★ 防止重复初始化\n\n if (patchDateEl._patchCalendarInited) return;\n patchDateEl._patchCalendarInited = true;\n var calendarList = [];\n var patchRuleTypes = [];\n var patchRuleDays = 0;\n var calendarLoaded = false;\n var pendingPatchTimeValue = patchTimeSelect.getAttribute('data-pending-patch-time') || patchTimeSelect.value || patchTimeSelect.getAttribute('data-default-value') || '';\n var initialPatchDate = patchDateInput.value || getPatchDate(pendingPatchTimeValue);\n\n if (!patchDateInput.value && initialPatchDate) {\n syncPatchDateValue(initialPatchDate);\n } // 加载当月和上月考勤日历数据(供 updateTimeOptions 联动使用,\n // 日期禁用由 datepicker 的 data-load-calendar / data-disabled-date-from 自行处理)\n\n\n function loadPatchCalendar() {\n return _loadPatchCalendar.apply(this, arguments);\n } // 按 TokUI datepicker 的真实日期节点标记可补卡日期\n\n\n function _loadPatchCalendar() {\n _loadPatchCalendar = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {\n var _options$user, _options$user2, _options$user3, _options$user4, companyNoEl, departmentNoEl, formCompanyNo, formDepartmentNo, auth, userNo, companyNo, calendarUrl, now, year, month, dateStr, res, lastMonth, lastYear, lastDateStr, res2, lastCalendar, latestPendingValue, pendingDate, pendingYearMonth, hasData, res3, extraCalendar;\n\n return regeneratorRuntime.wrap(function _callee$(_context) {\n while (1) {\n switch (_context.prev = _context.next) {\n case 0:\n console.log('[loadPatchCalendar] 开始加载补卡日历');\n _context.prev = 1;\n // ★ 优先从表单字段读取当前选中的公司和部门(支持切换后重新加载)\n companyNoEl = findField('companyNo', formScope);\n departmentNoEl = findField('departmentNo', formScope);\n formCompanyNo = companyNoEl ? companyNoEl.value || '' : '';\n formDepartmentNo = departmentNoEl ? departmentNoEl.value || '' : ''; // 回退到 options.user 或全局 getAuth()\n\n auth = (0, _request.getAuth)();\n userNo = ((_options$user = options.user) === null || _options$user === void 0 ? void 0 : _options$user.no) || ((_options$user2 = options.user) === null || _options$user2 === void 0 ? void 0 : _options$user2.id) || auth.userno || auth.user;\n companyNo = formCompanyNo || ((_options$user3 = options.user) === null || _options$user3 === void 0 ? void 0 : _options$user3.cno) || ((_options$user4 = options.user) === null || _options$user4 === void 0 ? void 0 : _options$user4.comNo) || auth.companyNo;\n console.log('[loadPatchCalendar] 用户信息 - userNo:', userNo, 'companyNo:', companyNo, '(来源: 表单字段=' + formCompanyNo + ')');\n\n if (!(!userNo || !companyNo)) {\n _context.next = 13;\n break;\n }\n\n console.warn('补卡日历:用户信息不完整');\n return _context.abrupt(\"return\");\n\n case 13:\n // 优先从 patchDate 的 data-load-calendar 属性取日历 API 地址,回退到 options.api\n calendarUrl = patchDateEl.getAttribute('data-load-calendar') || options.api && options.api.patchQueryMonthClockStatus || '/api/patchQueryMonthClockStatus';\n console.log('[loadPatchCalendar] API 地址:', calendarUrl);\n now = new Date();\n year = now.getFullYear();\n month = now.getMonth() + 1;\n dateStr = year + '-' + (month < 10 ? '0' + month : month);\n console.log('[loadPatchCalendar] 加载当月:', dateStr);\n _context.next = 22;\n return _request.default.post(calendarUrl, {\n clientType: 'web',\n userNo: userNo,\n companyNo: companyNo,\n dateStr: dateStr,\n inType: '1'\n });\n\n case 22:\n res = _context.sent;\n console.log('[loadPatchCalendar] 当月响应:', res);\n\n if (!(res.code === 200 && res.data)) {\n _context.next = 62;\n break;\n }\n\n patchRuleDays = res.data.patchRuleDays || 0;\n patchRuleTypes = res.data.patchRuleTypes || [];\n console.log('[loadPatchCalendar] patchRuleDays:', patchRuleDays);\n console.log('[loadPatchCalendar] patchRuleTypes:', patchRuleTypes);\n console.log('[loadPatchCalendar] 原始 calendarList:', res.data.calendarList);\n calendarList = (res.data.calendarList || []).filter(function (v) {\n return v.status !== 'normal' && v.status !== '';\n });\n console.log('[loadPatchCalendar] 过滤后 calendarList:', calendarList); // 加载上月数据\n\n lastMonth = month === 1 ? 12 : month - 1;\n lastYear = month === 1 ? year - 1 : year;\n lastDateStr = lastYear + '-' + (lastMonth < 10 ? '0' + lastMonth : lastMonth);\n console.log('[loadPatchCalendar] 加载上月:', lastDateStr);\n _context.next = 38;\n return _request.default.post(calendarUrl, {\n clientType: 'web',\n userNo: userNo,\n companyNo: companyNo,\n dateStr: lastDateStr,\n inType: '1'\n });\n\n case 38:\n res2 = _context.sent;\n console.log('[loadPatchCalendar] 上月响应:', res2);\n\n if (res2.code === 200 && res2.data) {\n lastCalendar = (res2.data.calendarList || []).filter(function (v) {\n return v.status !== 'normal' && v.status !== '';\n });\n console.log('[loadPatchCalendar] 上月过滤后 calendarList:', lastCalendar);\n calendarList = calendarList.concat(lastCalendar);\n\n if (res2.data.patchRuleTypes) {\n patchRuleTypes = res2.data.patchRuleTypes;\n }\n } // 如果有待反显的补卡日期,且不在已加载的月份中,额外加载该月份\n\n\n latestPendingValue = patchTimeSelect.getAttribute('data-pending-patch-time') || pendingPatchTimeValue;\n pendingDate = getPatchDate(patchDateInput.value || latestPendingValue);\n console.log('[loadPatchCalendar] 待反显日期:', pendingDate);\n\n if (!pendingDate) {\n _context.next = 55;\n break;\n }\n\n pendingYearMonth = pendingDate.substring(0, 7); // YYYY-MM\n\n hasData = calendarList.some(function (item) {\n return item.day.startsWith(pendingYearMonth);\n });\n console.log('[loadPatchCalendar] 待反显月份:', pendingYearMonth, '已有数据:', hasData);\n\n if (hasData) {\n _context.next = 55;\n break;\n }\n\n console.log('[补卡日历] 补卡日期所属月份未加载,额外加载:', pendingYearMonth);\n _context.next = 52;\n return _request.default.post(calendarUrl, {\n clientType: 'web',\n userNo: userNo,\n companyNo: companyNo,\n dateStr: pendingYearMonth,\n inType: '1'\n });\n\n case 52:\n res3 = _context.sent;\n console.log('[loadPatchCalendar] 额外月份响应:', res3);\n\n if (res3.code === 200 && res3.data) {\n extraCalendar = (res3.data.calendarList || []).filter(function (v) {\n return v.status !== 'normal' && v.status !== '';\n });\n console.log('[loadPatchCalendar] 额外月份过滤后 calendarList:', extraCalendar);\n calendarList = calendarList.concat(extraCalendar);\n }\n\n case 55:\n console.log('[loadPatchCalendar] 最终合并后 calendarList 总数:', calendarList.length);\n console.log('[loadPatchCalendar] 最终 calendarList:', calendarList);\n calendarLoaded = true;\n setupDatePickerRestrictions(); // 使用最新的 data-pending-patch-time 或初始值\n\n updateTimeOptions(patchDateInput.value || getPatchDate(latestPendingValue), latestPendingValue);\n _context.next = 63;\n break;\n\n case 62:\n console.warn('补卡日历加载失败:', res.msg);\n\n case 63:\n _context.next = 68;\n break;\n\n case 65:\n _context.prev = 65;\n _context.t0 = _context[\"catch\"](1);\n console.warn('补卡日历加载异常:', _context.t0.message, _context.t0);\n\n case 68:\n case \"end\":\n return _context.stop();\n }\n }\n }, _callee, null, [[1, 65]]);\n }));\n return _loadPatchCalendar.apply(this, arguments);\n }\n\n function setupDatePickerRestrictions() {\n var validDates = calendarList.filter(function (item) {\n return patchRuleTypes.includes(item.status);\n }).map(function (item) {\n return item.day;\n });\n var root = patchDateEl.closest('.tokui-datepicker') || patchDateEl;\n var observer = new MutationObserver(function () {\n observer.disconnect();\n var dropdown = root.querySelector('.tokui-datepicker-dropdown');\n\n if (dropdown && dropdown.style.display !== 'none') {\n var title = dropdown.querySelector('.tokui-datepicker-title');\n var match = title && title.textContent.match(/(\\d{4}).*?(\\d{1,2})/);\n\n if (match) {\n var prefix = match[1] + '-' + String(parseInt(match[2])).padStart(2, '0') + '-';\n dropdown.querySelectorAll('.tokui-datepicker-day[data-day]').forEach(function (dayEl) {\n var date = prefix + String(parseInt(dayEl.getAttribute('data-day'))).padStart(2, '0');\n var available = validDates.includes(date);\n dayEl.classList.toggle('patch-date-available', available);\n dayEl.classList.toggle('patch-date-disabled', !available);\n dayEl.style.color = available ? '#f56c6c' : '#c0c4cc';\n dayEl.style.pointerEvents = available ? '' : 'none';\n });\n }\n }\n\n observer.observe(root, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['style', 'class']\n });\n });\n observer.observe(root, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['style', 'class']\n });\n patchDateEl.addEventListener('click', function () {\n setTimeout(function () {\n observer.takeRecords();\n var dropdown = root.querySelector('.tokui-datepicker-dropdown');\n\n if (dropdown && dropdown.style.display !== 'none') {\n observer.disconnect();\n observer.observe(root, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['style', 'class']\n });\n }\n }, 0);\n });\n patchDateEl._patchDateObserver = observer;\n } // TokUI datepicker 选择日期时只更新可见 input/隐藏 input,不派发 change,\n // 因此同时监听 click,并延迟读取组件写入后的日期值。\n\n\n patchDateEl.addEventListener('click', function (event) {\n console.log('[patchDateEl click] 事件触发:', event.target);\n var day = event.target && event.target.closest ? event.target.closest('.tokui-datepicker-day:not(.tokui-datepicker-day--other)') : null;\n console.log('[patchDateEl click] 找到的 day 元素:', day);\n if (!day) return;\n setTimeout(function () {\n console.log('[patchDateEl click] 延迟后读取 patchDateInput.value:', patchDateInput.value);\n updateTimeOptions(patchDateInput.value, '');\n }, 50);\n });\n patchDateInput.addEventListener('change', function () {\n console.log('[patchDateInput change] 事件触发, value:', this.value);\n updateTimeOptions(this.value, '');\n }); // ★ 增强监听:监听 patchDateEl 包装器的所有 input 事件(覆盖手动输入场景)\n\n patchDateEl.addEventListener('input', function (event) {\n if (event.target === patchDateInput) {\n console.log('[patchDateInput input] 事件触发, value:', event.target.value); // 使用防抖避免频繁触发\n\n if (patchDateEl._inputDebounceTimer) clearTimeout(patchDateEl._inputDebounceTimer);\n patchDateEl._inputDebounceTimer = setTimeout(function () {\n updateTimeOptions(patchDateInput.value, '');\n }, 300);\n }\n }); // ★ 监听隐藏 input 的值变化(TokUI datepicker 会同步更新隐藏 input)\n\n var wrapper = patchDateInput.closest('.tokui-datepicker');\n\n if (wrapper) {\n var hiddenInput = wrapper.querySelector('input[type=\"hidden\"]');\n\n if (hiddenInput) {\n var observer = new MutationObserver(function (mutations) {\n mutations.forEach(function (mutation) {\n if (mutation.type === 'attributes' && mutation.attributeName === 'value') {\n var newValue = hiddenInput.value || hiddenInput.getAttribute('value');\n console.log('[patchDate hidden input] 值变化:', newValue);\n\n if (newValue && newValue !== patchDateInput.value) {\n patchDateInput.value = newValue;\n } // 延迟触发,确保 patchDateInput.value 已同步\n\n\n setTimeout(function () {\n updateTimeOptions(patchDateInput.value, '');\n }, 50);\n }\n });\n });\n observer.observe(hiddenInput, {\n attributes: true,\n attributeFilter: ['value']\n });\n patchDateEl._hiddenInputObserver = observer;\n }\n } // 日期选择后生成补卡时间选项;反显时优先恢复已有值,新增时默认第一项\n\n\n function updateTimeOptions(selectedDate, reflectedValue) {\n console.log('[updateTimeOptions] 开始执行 - selectedDate:', selectedDate, 'reflectedValue:', reflectedValue);\n\n if (!patchTimeSelect) {\n console.warn('[updateTimeOptions] patchTimeSelect 不存在');\n return;\n }\n\n var previousValue = reflectedValue || patchTimeSelect.value || '';\n var normalizedDate = getPatchDate(selectedDate);\n console.log('[updateTimeOptions] normalizedDate:', normalizedDate, 'previousValue:', previousValue);\n patchTimeSelect.innerHTML = '';\n\n if (!normalizedDate) {\n console.log('[updateTimeOptions] 日期为空,显示\"请先选择补卡日期\"');\n appendTimeOption('', '请先选择补卡日期');\n patchTimeSelect.disabled = true;\n return;\n }\n\n var dateStr = normalizedDate;\n var timeOptions = [];\n var expired = false;\n console.log('[updateTimeOptions] calendarList 总数:', calendarList.length);\n console.log('[updateTimeOptions] patchRuleTypes:', patchRuleTypes);\n console.log('[updateTimeOptions] 目标日期:', dateStr);\n calendarList.forEach(function (item) {\n console.log('[updateTimeOptions] 遍历 calendarList 项:', {\n day: item.day,\n status: item.status,\n patchTime: item.patchTime,\n patchRuleFlag: item.patchRuleFlag\n });\n\n if (item.day !== dateStr) {\n console.log('[updateTimeOptions] 日期不匹配,跳过');\n return;\n }\n\n if (!patchRuleTypes.includes(item.status)) {\n console.log('[updateTimeOptions] 状态不在允许范围,跳过. status:', item.status);\n return;\n }\n\n if (Number(item.patchRuleFlag) === 0) {\n console.log('[updateTimeOptions] patchRuleFlag=0,已过期');\n expired = true;\n return;\n }\n\n if (item.patchTime) {\n var value = normalizePatchTime(item.patchTime);\n console.log('[updateTimeOptions] 找到可用时间:', value);\n\n if (!timeOptions.some(function (opt) {\n return opt.value === value;\n })) {\n timeOptions.push({\n value: value,\n label: value.slice(-5)\n });\n }\n } else {\n console.warn('[updateTimeOptions] 该项没有 patchTime 字段');\n }\n });\n console.log('[updateTimeOptions] 生成的 timeOptions:', timeOptions);\n console.log('[updateTimeOptions] expired:', expired);\n\n if (expired) {\n console.log('[updateTimeOptions] 显示\"超过可补卡时间范围\"');\n appendTimeOption('', '超过可补卡时间范围');\n patchTimeSelect.disabled = true;\n return;\n } // 如果有反显值,但 timeOptions 为空(数据不一致或跨月反显),尝试手动添加该值\n\n\n var normalizedPrevious = normalizePatchTime(previousValue);\n console.log('[updateTimeOptions] normalizedPrevious:', normalizedPrevious);\n\n if (normalizedPrevious && timeOptions.length === 0) {\n var previousDate = normalizedPrevious.substring(0, 10);\n console.log('[updateTimeOptions] previousDate:', previousDate, 'dateStr:', dateStr);\n\n if (previousDate === dateStr) {\n console.log('[补卡反显] calendarList 中无可用时间,但存在反显值,手动添加:', normalizedPrevious);\n timeOptions.push({\n value: normalizedPrevious,\n label: normalizedPrevious.slice(-5)\n });\n }\n }\n\n if (!timeOptions.length) {\n console.warn('[updateTimeOptions] 最终 timeOptions 为空,显示\"该日期无可补卡时间\"');\n appendTimeOption('', '该日期无可补卡时间');\n patchTimeSelect.disabled = true; // ★ 清空待反显的值,因为该日期没有可补卡时间\n\n if (previousValue) {\n console.warn('[updateTimeOptions] AI 返回的默认值无效(该日期无可补卡时间),已清空');\n patchTimeSelect.removeAttribute('data-pending-patch-time');\n }\n\n return;\n }\n\n console.log('[updateTimeOptions] 成功生成时间选项,启用下拉框');\n patchTimeSelect.disabled = false;\n appendTimeOption('', '请选择时间');\n timeOptions.forEach(function (opt) {\n appendTimeOption(opt.value, opt.label);\n }); // ★ 智能选择逻辑:优先精确匹配,失败则模糊匹配时间段,最后智能默认选择\n\n var reflectedOption = timeOptions.find(function (opt) {\n return opt.value === normalizedPrevious;\n }); // 如果精确匹配失败,尝试时间段模糊匹配\n\n if (normalizedPrevious && !reflectedOption) {\n console.log('[updateTimeOptions] 精确匹配失败,尝试时间段模糊匹配');\n console.log('[updateTimeOptions] AI 期望值:', normalizedPrevious); // 从 AI 返回的时间中提取小时数\n\n var aiTimeMatch = normalizedPrevious.match(/(\\d{2}):(\\d{2})/);\n\n if (aiTimeMatch) {\n var aiHour = parseInt(aiTimeMatch[1]);\n console.log('[updateTimeOptions] AI 期望小时:', aiHour); // 根据小时数判断时间段(早上:6-11, 下午/晚上:12-23 统一为下班卡)\n\n var aiPeriod = aiHour >= 6 && aiHour < 12 ? 'morning' : 'offwork';\n console.log('[updateTimeOptions] AI 时间段:', aiPeriod, aiPeriod === 'morning' ? '(上班卡)' : '(下班卡)'); // 在可用时间中查找同时间段的选项\n\n reflectedOption = timeOptions.find(function (opt) {\n var optTimeMatch = opt.value.match(/(\\d{2}):(\\d{2})/);\n\n if (optTimeMatch) {\n var optHour = parseInt(optTimeMatch[1]);\n var optPeriod = optHour >= 6 && optHour < 12 ? 'morning' : 'offwork';\n return optPeriod === aiPeriod;\n }\n\n return false;\n });\n\n if (reflectedOption) {\n console.log('[updateTimeOptions] ✅ 时间段模糊匹配成功!');\n console.log('[updateTimeOptions] AI 期望: ' + normalizedPrevious + ' (' + (aiPeriod === 'morning' ? '上班卡' : '下班卡') + ')');\n console.log('[updateTimeOptions] 实际选中: ' + reflectedOption.value);\n }\n }\n } // 如果还没有选中值(AI 没返回或匹配失败),使用智能默认选择\n\n\n if (!reflectedOption) {\n console.log('[updateTimeOptions] 未找到匹配项,使用智能默认选择'); // ★ 智能默认:优先选择下班卡(12点之后的时间)\n\n var offworkOption = timeOptions.find(function (opt) {\n var optTimeMatch = opt.value.match(/(\\d{2}):(\\d{2})/);\n\n if (optTimeMatch) {\n var optHour = parseInt(optTimeMatch[1]);\n return optHour >= 12; // 12点及之后的视为下班卡\n }\n\n return false;\n });\n\n if (offworkOption) {\n reflectedOption = offworkOption;\n console.log('[updateTimeOptions] ✅ 智能默认选择:下班卡', offworkOption.value);\n } else {\n // 如果没有下班卡,选择第一个(上班卡)\n reflectedOption = timeOptions[0];\n console.log('[updateTimeOptions] ⚠️ 没有下班卡,选择上班卡', timeOptions[0].value);\n }\n }\n\n if (normalizedPrevious && reflectedOption && reflectedOption.value !== normalizedPrevious) {\n console.log('[updateTimeOptions] AI 期望值未精确匹配,但已通过智能匹配选中');\n console.log('[updateTimeOptions] AI 期望:', normalizedPrevious);\n console.log('[updateTimeOptions] 实际选中:', reflectedOption.value);\n }\n\n patchTimeSelect.value = reflectedOption.value;\n console.log('[updateTimeOptions] 最终选中值:', patchTimeSelect.value);\n console.log('[updateTimeOptions] 选择方式:', !previousValue ? '智能默认(无AI默认值)' : reflectedOption.value === normalizedPrevious ? '精确匹配' : '智能匹配(时间段或默认规则)');\n patchTimeSelect.setAttribute('data-default-value', patchTimeSelect.value); // ★ 清空待反显标记,避免后续重复使用\n\n if (patchTimeSelect.hasAttribute('data-pending-patch-time')) {\n patchTimeSelect.removeAttribute('data-pending-patch-time');\n console.log('[updateTimeOptions] 已清空 data-pending-patch-time 标记');\n }\n\n patchTimeSelect.dispatchEvent(new Event('change', {\n bubbles: true\n }));\n }\n\n function appendTimeOption(value, label) {\n var option = document.createElement('option');\n option.value = value;\n option.textContent = label;\n patchTimeSelect.appendChild(option);\n } // 编辑反显可能返回时间戳;统一转成接口选项使用的 YYYY-MM-DD HH:mm\n\n\n function normalizePatchTime(value) {\n console.log('[normalizePatchTime] 输入值:', value, '类型:', (0, _typeof2.default)(value));\n if (value === undefined || value === null || value === '') return '';\n var text = String(value).trim();\n console.log('[normalizePatchTime] 转字符串:', text);\n var match = text.match(/^(\\d{4}-\\d{2}-\\d{2})[ T](\\d{2}):(\\d{2})/);\n\n if (match) {\n var result = match[1] + ' ' + match[2] + ':' + match[3];\n console.log('[normalizePatchTime] 匹配到日期时间格式,返回:', result);\n return result;\n }\n\n if (!/^\\d{10,13}$/.test(text)) {\n console.log('[normalizePatchTime] 不是时间戳,直接返回:', text);\n return text;\n }\n\n var timestamp = Number(text);\n var date = new Date(text.length === 10 ? timestamp * 1000 : timestamp);\n\n if (isNaN(date.getTime())) {\n console.log('[normalizePatchTime] 时间戳无效,返回原值:', text);\n return text;\n }\n\n var formatted = formatDate(date) + ' ' + String(date.getHours()).padStart(2, '0') + ':' + String(date.getMinutes()).padStart(2, '0');\n console.log('[normalizePatchTime] 时间戳转换结果:', formatted);\n return formatted;\n }\n\n function getPatchDate(value) {\n console.log('[getPatchDate] 输入值:', value);\n var normalized = normalizePatchTime(value);\n var result = normalized ? normalized.slice(0, 10) : '';\n console.log('[getPatchDate] 返回日期:', result);\n return result;\n }\n\n function syncPatchDateValue(value) {\n if (!value) return;\n patchDateInput.value = value;\n var wrapper = patchDateInput.closest('.tokui-datepicker');\n var hiddenInput = wrapper && wrapper.querySelector('input[type=\"hidden\"]');\n if (hiddenInput) hiddenInput.value = value;\n }\n\n function formatDate(date) {\n if (!date) return '';\n var d = new Date(date);\n var year = d.getFullYear();\n var month = d.getMonth() + 1;\n var day = d.getDate();\n return year + '-' + (month < 10 ? '0' + month : month) + '-' + (day < 10 ? '0' + day : day);\n } // 提供给 applyFormData:编辑详情回填发生在日历加载之后时,重新生成选项并恢复补卡时间\n\n\n var updatePatchTimeOptions = function updatePatchTimeOptions(selectedDate, reflectedValue) {\n pendingPatchTimeValue = reflectedValue || '';\n patchTimeSelect.setAttribute('data-pending-patch-time', pendingPatchTimeValue);\n var patchDate = getPatchDate(selectedDate || pendingPatchTimeValue);\n if (patchDate && !patchDateInput.value) syncPatchDateValue(patchDate);\n\n if (calendarLoaded) {\n updateTimeOptions(patchDate, pendingPatchTimeValue);\n } else {\n // 日历未加载完成,等待加载完成后会自动使用 data-pending-patch-time 反显\n console.log('[补卡反显] 日历数据加载中,已记录待反显值:', pendingPatchTimeValue);\n }\n };\n\n patchTimeEl._updatePatchTimeOptions = updatePatchTimeOptions;\n patchTimeSelect._updatePatchTimeOptions = updatePatchTimeOptions; // ★ 监听公司和部门字段变化,切换后重新加载日历数据\n\n var companyNoEl = findField('companyNo', formScope);\n var departmentNoEl = findField('departmentNo', formScope);\n\n function handleCompanyOrDeptChange() {\n console.log('[补卡日历] 检测到公司/部门切换,重新加载日历数据'); // 清空已选日期和时间\n\n if (patchDateInput) patchDateInput.value = '';\n var wrapper = patchDateInput && patchDateInput.closest('.tokui-datepicker');\n var hiddenInput = wrapper && wrapper.querySelector('input[type=\"hidden\"]');\n if (hiddenInput) hiddenInput.value = '';\n patchTimeSelect.innerHTML = '';\n appendTimeOption('', '请先选择补卡日期');\n patchTimeSelect.disabled = true; // 重新加载日历\n\n calendarLoaded = false;\n loadPatchCalendar();\n }\n\n if (companyNoEl && !companyNoEl._patchCalendarChangeListenerBound) {\n companyNoEl._patchCalendarChangeListenerBound = true;\n companyNoEl.addEventListener('change', handleCompanyOrDeptChange);\n }\n\n if (departmentNoEl && !departmentNoEl._patchCalendarChangeListenerBound) {\n departmentNoEl._patchCalendarChangeListenerBound = true;\n departmentNoEl.addEventListener('change', handleCompanyOrDeptChange);\n } // 初始化加载日历数据\n\n\n loadPatchCalendar();\n } // ============ 注入通用后端校验处理器 ============\n\n\n var buildBody = options.buildBody || function (value, field, formData) {\n return {\n value: value,\n field: field,\n formData: formData\n };\n };\n\n var parseResult = options.parseResult || function (json) {\n return json.valid !== false ? '' : json.message || '校验失败';\n };\n\n V._onRemote = /*#__PURE__*/function () {\n var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee2(url, value, field, formData) {\n var _options$remoteTimeou, json;\n\n return regeneratorRuntime.wrap(function _callee2$(_context2) {\n while (1) {\n switch (_context2.prev = _context2.next) {\n case 0:\n _context2.prev = 0;\n _context2.next = 3;\n return _request.default.post(url, buildBody(value, field, formData), {\n timeout: (_options$remoteTimeou = options.remoteTimeout) !== null && _options$remoteTimeou !== void 0 ? _options$remoteTimeou : 8000\n });\n\n case 3:\n json = _context2.sent;\n return _context2.abrupt(\"return\", parseResult(json));\n\n case 7:\n _context2.prev = 7;\n _context2.t0 = _context2[\"catch\"](0);\n return _context2.abrupt(\"return\", '网络请求失败,请重试');\n\n case 10:\n case \"end\":\n return _context2.stop();\n }\n }\n }, _callee2, null, [[0, 7]]);\n }));\n\n return function (_x, _x2, _x3, _x4) {\n return _ref.apply(this, arguments);\n };\n }(); // ============ 执行单字段校验 ============\n // 定位字段元素:优先按 id,查不到时按 name 在指定作用域(默认本消息容器)内查找\n // (DSL 只写 n:name 不写 id: 时,TokUI 不会给控件生成 id)\n\n\n function findFieldEl(key, scope) {\n if (!key) return null;\n var root = scope || (container && container.querySelectorAll ? container : document); // ★ 优先在当前作用域内按 id 精确查找,再按 name 查找,\n // 避免 document.getElementById 在多表单同 id 时串扰到其他消息\n\n if (root && root.querySelectorAll) {\n var byIdInScope = queryById(key, root);\n if (byIdInScope) return byIdInScope;\n var byName = root.querySelector('[name=\"' + key + '\"]');\n if (byName) return byName;\n } // 兜底(仅当 root 为 document 即单表单场景时才做全局查找,多表单 scoped 容器内查不到直接返回 null)\n\n\n return root === document ? document.getElementById(key) : null;\n } // 读取字段值:radio/checkbox 组按共享 name 聚合勾选值\n // (radio 单个 input 的 value 是选项值而非选中状态,直接读会导致 required 误判通过)\n\n\n function readFieldValue(el, inner) {\n var t = (inner.type || '').toLowerCase();\n if (t !== 'radio' && t !== 'checkbox') return inner.value;\n var name = inner.name || inner.id || '';\n if (!name) return inner.checked ? inner.value : '';\n var scope = el && el.closest && el.closest('form') || (container && container.querySelector ? container : document);\n var checked = [];\n Array.prototype.forEach.call(scope.querySelectorAll('input[name=\"' + name + '\"]'), function (n) {\n if (n.checked) checked.push(n.value);\n });\n return t === 'radio' ? checked.length ? checked[0] : '' : checked;\n } // 与 Validator._setFieldState 行为一致,但直接操作元素(用于无 id 场景)\n\n\n function setFieldStateLocal(el, isError, msg) {\n var wrapper = el.closest('.tokui-field') || el.parentNode;\n if (!wrapper) return;\n wrapper.classList.remove('tokui-field--error', 'tokui-field--success');\n var oldHint = wrapper.querySelector('.tokui-field__hint');\n if (oldHint) oldHint.remove();\n\n if (isError) {\n wrapper.classList.add('tokui-field--error');\n\n if (msg) {\n var hint = document.createElement('div');\n hint.className = 'tokui-field__hint tokui-field__hint--error';\n hint.textContent = msg;\n wrapper.appendChild(hint);\n }\n } else {\n wrapper.classList.add('tokui-field--success');\n }\n } // 报错展示:统一使用 setFieldStateLocal 直接操作元素,避免 Validator._setFieldState\n // 内部的 document.getElementById 在多表单同 id 场景下串扰到其他消息的错误提示\n\n\n function showError(key, msg, scope) {\n var el = findFieldEl(key, scope);\n if (!el) return;\n setFieldStateLocal(el, true, msg);\n }\n\n function clearError(key, scope) {\n var el = findFieldEl(key, scope);\n if (!el) return;\n setFieldStateLocal(el, false, '');\n }\n\n function runFieldValidation(_x5) {\n return _runFieldValidation.apply(this, arguments);\n } // ============ 注册 clk 驱动的显式校验 ============\n // 注意:registerHandler 是全局单例,每条消息渲染都会调用 setupValidator。\n // 全局 handler 只注册一次,内部全部按被点元素归属路由(回调/反馈/遮罩),避免旧表单的\n // 操作错挂到最新渲染的消息上。\n\n\n function _runFieldValidation() {\n _runFieldValidation = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee21(el) {\n var ruleStr, inner, targetKey, formData, msg, scope;\n return regeneratorRuntime.wrap(function _callee21$(_context21) {\n while (1) {\n switch (_context21.prev = _context21.next) {\n case 0:\n ruleStr = el.getAttribute('data-rule');\n\n if (ruleStr) {\n _context21.next = 3;\n break;\n }\n\n return _context21.abrupt(\"return\");\n\n case 3:\n inner = el.matches && el.matches('input,select,textarea') ? el : el.querySelector('input,select,textarea') || el;\n targetKey = inner.id || inner.name || el.id || '';\n\n if (targetKey) {\n _context21.next = 7;\n break;\n }\n\n return _context21.abrupt(\"return\");\n\n case 7:\n formData = collectFormData(inner);\n _context21.next = 10;\n return V.validateField(readFieldValue(el, inner), ruleStr, formData, targetKey);\n\n case 10:\n msg = _context21.sent;\n scope = resolveForm(el);\n\n if (msg) {\n showError(targetKey, msg, scope);\n } else {\n clearError(targetKey, scope);\n }\n\n case 13:\n case \"end\":\n return _context21.stop();\n }\n }\n }, _callee21);\n }));\n return _runFieldValidation.apply(this, arguments);\n }\n\n if (!window.TokUI.__validatorHandlersBound) {\n window.TokUI.__validatorHandlersBound = true; // ============ 建立 _eventBus 桥接 ============\n // TokUI 的 datetimepicker/datepicker/timepicker 等组件在确认按钮中通过\n // window.TokUI._eventBus.getHandler(n.clk)(...)\n // 调用 clk 处理器。但 TokUI 源码中 _eventBus 从未被赋值,导致 handler 静默失败。\n // 这里用拦截式代理:registerHandler 被调用时自动同步到 _eventBus.getHandler() 可用。\n\n (function setupEventBus() {\n if (window.TokUI._eventBus) return; // 已由外部设置,不覆盖\n\n var handlerMap = {};\n var orig = window.TokUI.registerHandler;\n\n window.TokUI.registerHandler = function (name, fn) {\n handlerMap[name] = fn;\n return orig.call(this, name, fn);\n };\n\n window.TokUI._eventBus = {\n getHandler: function getHandler(name) {\n return handlerMap[name] || null;\n }\n };\n })();\n\n window.TokUI.registerHandler('doValidate', function (data, event, element) {\n runFieldValidation(element);\n }); // ============ 注册表单重置 ============\n // 支持 [btn tx:重置 sub:formReset] 或 [btn tx:重置 clk:doReset]\n // (核心原生 [btn ... reset] 属性由 patch.js 包装的 _doReset 处理)\n\n function resetFormState(form, element) {\n // ★ 收集要清空的范围:form 本身 + TokUI 流式容器(不扩展到 document,避免跨消息串扰)\n var scopes = [form]; // 向上找 TokUI 容器\n\n var stream = form.closest('.tokui-stream') || form.closest('[data-tokui-tag=\"card\"]');\n if (stream) scopes.push(stream);\n var cleared = 0,\n skipped = 0; // TokUI 自定义组件的 CSS 类名(这些组件的可见 input 带有 readonly,但应该被清空)\n\n var tokuiComponentSelector = '.tokui-datepicker,.tokui-datetimepicker,.tokui-timepicker,.tokui-cascader,.tokui-picker';\n scopes.forEach(function (scope) {\n scope.querySelectorAll('input,select,textarea').forEach(function (el) {\n // ★ 跳过禁用字段;跳过只读字段——但 TokUI 组件内的 readonly input(如 datepicker)需要清空\n var inTokuiComponent = el.closest(tokuiComponentSelector);\n\n if (el.disabled || el.readOnly && !inTokuiComponent) {\n skipped++;\n return;\n }\n\n var t = (el.type || '').toLowerCase();\n\n if (t === 'checkbox' || t === 'radio') {\n el.checked = false;\n } else if (el.tagName === 'SELECT') {\n el.selectedIndex = 0;\n el.dispatchEvent(new Event('change', {\n bubbles: true\n }));\n } else {\n el.value = '';\n }\n\n cleared++;\n }); // ★ 清除 TokUI 自定义组件的选中状态样式\n\n scope.querySelectorAll(tokuiComponentSelector).forEach(function (comp) {\n // picker: 移除选项选中样式和标签\n comp.querySelectorAll('.tokui-picker-option--selected').forEach(function (opt) {\n opt.classList.remove('tokui-picker-option--selected');\n });\n comp.querySelectorAll('.tokui-picker-tag').forEach(function (tag) {\n tag.remove();\n }); // cascader: 移除选中样式\n\n comp.querySelectorAll('.tokui-cascader-menu-item--selected').forEach(function (opt) {\n opt.classList.remove('tokui-cascader-menu-item--selected');\n });\n });\n }); // 清除校验状态(所有 scope)\n\n scopes.forEach(function (scope) {\n scope.querySelectorAll('.tokui-field--error, .tokui-field--success').forEach(function (w) {\n w.classList.remove('tokui-field--error', 'tokui-field--success');\n });\n scope.querySelectorAll('.tokui-field__hint').forEach(function (h) {\n h.remove();\n });\n }); // 清除反馈信息\n\n if (form._tvFeedback) form._tvFeedback.innerHTML = ''; // 清除关联的 hidden 辅助字段(限定在当前表单容器内,避免跨消息串扰)\n\n var resetScope = stream || form;\n var auxFields = ['_holidayType', '_leaveType', '_balanceMode', '_durationDay', 'duration'];\n auxFields.forEach(function (id) {\n var el2 = queryById(id, resetScope);\n if (el2 && el2.type === 'hidden') el2.value = '';\n }); // 刷新条件显示(限定在当前表单作用域,避免跨消息串扰)\n\n try {\n updateConditionalVisibility(resolveScope(form));\n } catch (e2) {} // 移除遮罩(如果已提交后被重置)\n\n\n var lca = form.parentElement;\n\n while (lca && lca !== document.body) {\n var overlay = lca.querySelector('.tokui-form-overlay');\n if (overlay) overlay.remove();\n lca._tvOverlayLocked = false;\n lca = lca.parentElement;\n }\n }\n\n window.TokUI.registerHandler('formReset', function (data, event, element) {\n var f = resolveForm(element);\n resetFormState(f, element);\n });\n window.TokUI.registerHandler('doReset', function (data, event, element) {\n var f = resolveForm(element);\n resetFormState(f, element);\n }); // ============ 注册提交校验 ============\n\n function notifyFormSuccess(form, data, resultText) {\n var cb = findFormSuccessCb(form);\n if (typeof cb !== 'function') return;\n\n try {\n cb({\n formId: form ? form.id : null,\n formData: data,\n resultText: resultText,\n success: true\n });\n } catch (e) {\n console.warn('[Validator] onFormSuccess callback error:', e);\n }\n } // 提交成功后锁定表单:遮罩覆盖整个表单区域(含表单+审批流程+按钮组),禁止一切操作\n\n\n function lockFormWithOverlay(form) {\n try {\n // ★ 打印祖先链辅助调试\n var chain = [];\n var walker = form;\n\n while (walker && walker !== document.body) {\n chain.push(walker.tagName + (walker.id ? '#' + walker.id : '') + (walker.className ? '.' + walker.className.split(' ').slice(0, 3).join('.') : ''));\n walker = walker.parentElement;\n } // ★ 新策略:找到 form + btngroup + 审批流程 的 LCA(最小共同祖先)\n // 收集关键节点(作用域限定在当前表单容器,避免跨消息串扰)\n\n\n var keyNodes = [form];\n var overlayScope = resolveScope(form);\n var submitBtn = overlayScope && overlayScope.querySelector ? overlayScope.querySelector('[data-tokui-sub=\"formSubmit\"]') : null;\n if (submitBtn) keyNodes.push(submitBtn);\n var approvalFlow = form && form.parentElement ? form.parentElement.querySelector('.tv-approval-flow') : null;\n\n if (!approvalFlow && submitBtn) {\n // 审批流程可能不在 form 附近,尝试从 btngroup 附近找\n var btnParent = submitBtn.parentElement;\n\n while (btnParent && btnParent !== document.body) {\n approvalFlow = btnParent.querySelector('.tv-approval-flow');\n if (approvalFlow) break;\n btnParent = btnParent.parentElement;\n }\n }\n\n if (approvalFlow) keyNodes.push(approvalFlow);\n\n if (form) {\n var durationEl2 = queryById('duration', overlayScope);\n if (durationEl2 && durationEl2 !== form) keyNodes.push(durationEl2);\n var balanceEl2 = queryById('staffBalance', overlayScope);\n if (balanceEl2) keyNodes.push(balanceEl2);\n } // 对每个关键节点,收集其所有祖先(从自身到 body)\n\n\n var ancestorSets = keyNodes.map(function (node) {\n var ancestors = [];\n var n = node;\n\n while (n && n !== document.body) {\n ancestors.push(n);\n n = n.parentElement;\n }\n\n return ancestors;\n }); // 找 LCA:从 form 祖先往外走,找到第一个包含所有关键节点的\n\n var target = null;\n\n if (ancestorSets.length > 0) {\n var formAncestors = ancestorSets[0];\n\n for (var i = 0; i < formAncestors.length; i++) {\n var candidate = formAncestors[i];\n var containsAll = true;\n\n for (var j = 1; j < ancestorSets.length; j++) {\n if (ancestorSets[j].indexOf(candidate) === -1) {\n containsAll = false;\n break;\n }\n }\n\n if (containsAll) {\n target = candidate;\n break; // 找到最内层共同祖先\n }\n }\n } // 回退\n\n\n if (!target) {\n target = form && form.closest('.tokui-stream') || form && form.closest('[data-tokui-tag=\"card\"]') || (overlayScope && overlayScope !== document ? overlayScope : null) || form && form.parentElement || form;\n }\n\n if (!target || target._tvOverlayLocked || target === document.body) {\n return;\n }\n\n target._tvOverlayLocked = true; // ★ 确保 target 为 position: relative,使 absolute 子元素相对于它定位\n\n target.style.position = 'relative'; // 确保 target 有尺寸信息\n\n if (target.offsetHeight === 0) {\n target.style.minHeight = '200px';\n }\n\n var overlay = document.createElement('div');\n overlay.className = 'tokui-form-overlay';\n overlay.innerHTML = '<span class=\"tokui-form-overlay__text\"><svg t=\"1724150561000\" class=\"icon\" viewBox=\"0 0 1024 1024\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" p-id=\"2769\" width=\"16\" height=\"16\"><path d=\"M512 128c-106.037333 0-192 85.962667-192 192v128h-64a64 64 0 0 0-64 64v320a64 64 0 0 0 64 64h512a64 64 0 0 0 64-64v-320a64 64 0 0 0-64-64h-64V320c0-106.037333-85.962667-192-192-192z m-128 192a128 128 0 0 1 256 0v128H384V320z m-64 192h384v320H320V512z\" fill=\"currentColor\"></path></svg> 已提交</span>';\n target.appendChild(overlay); // 禁用容器内所有表单控件和按钮\n\n target.querySelectorAll('input,select,textarea,button').forEach(function (el) {\n el.disabled = true;\n });\n } catch (e) {\n console.warn('[Validator] overlay failed:', e.message);\n }\n } // 暴露 lockFormWithOverlay 到 bridge,供历史消息恢复遮罩复用(与提交后效果一致)。\n // 注意:此函数在 __validatorHandlersBound 守卫块内声明(块级作用域),\n // 只能在首次 setupValidator 时挂载,后续实例复用同一闭包(resolveScope 动态定位,作用域正确)。\n\n\n window.__tokuiValidatorBridge = window.__tokuiValidatorBridge || {};\n window.__tokuiValidatorBridge.lockFormWithOverlay = lockFormWithOverlay;\n window.TokUI.registerHandler('formSubmit', /*#__PURE__*/function () {\n var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee4(formData, event, element) {\n var _options$user6, _options$user7, _options$user8, _options$user9;\n\n var form, allFields, hasError, resolveField, tasks, submitUrl, allData, submitScope, auth, formType, typeAttrEl, meetingIdEl, meetingIdAttr, meetingTypeAttr, meetingTagEl, _durEl, balanceModeEl, balanceMode, _balEl, _balDom, rawBalance, leaveBalance, duration, typeEl_1, holidayName, holidayTypeEl, minLeaveUnit, durationDayEl, durationDay, stHalf, etHalf, halfLabels, stHalfEl, etHalfEl, stVal, etVal, sd, ed, stMs, stRaw, stDate, etMs, etRaw, etDate, applierName, companySel, departSel, tokuiParams, companyNoEl, departmentNoEl, _options$user5, tokuiParams_1, meetingToMs, meetingTokui, loginUserInfo, mUserNo, mHostName, mImAccount, mUserImg, mMeetingNo, mNoticeTime, mNoticeType, mMembers, mNewNotice, stStr, etStr, cfgDeptField, deptSelEl, outDeptVal, durationNum, _otListEl, approvalResult, method, resultJson, params, resultText;\n\n return regeneratorRuntime.wrap(function _callee4$(_context4) {\n while (1) {\n switch (_context4.prev = _context4.next) {\n case 0:\n resolveField = function _resolveField(el) {\n if (el.matches && el.matches('input,select,textarea')) return el;\n var inner = el.querySelector('input,select,textarea');\n return inner || el;\n };\n\n form = resolveForm(element);\n\n if (form) {\n _context4.next = 4;\n break;\n }\n\n return _context4.abrupt(\"return\");\n\n case 4:\n if (!form._tvSubmitting) {\n _context4.next = 8;\n break;\n }\n\n console.warn('[Validator] formSubmit: 正在提交中,跳过');\n (0, _message.showMessage)('正在提交中,请稍候...', 'warning');\n return _context4.abrupt(\"return\");\n\n case 8:\n form._tvSubmitting = true;\n allFields = form ? form.querySelectorAll('[data-rule]') : [];\n hasError = false;\n tasks = [];\n allFields.forEach(function (f) {\n tasks.push((0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee3() {\n var ruleStr, inner, targetKey, fd, fieldVal, msg;\n return regeneratorRuntime.wrap(function _callee3$(_context3) {\n while (1) {\n switch (_context3.prev = _context3.next) {\n case 0:\n ruleStr = f.getAttribute('data-rule');\n\n if (ruleStr) {\n _context3.next = 3;\n break;\n }\n\n return _context3.abrupt(\"return\");\n\n case 3:\n inner = resolveField(f);\n targetKey = inner.id || inner.name || f.id || '';\n\n if (targetKey) {\n _context3.next = 7;\n break;\n }\n\n return _context3.abrupt(\"return\");\n\n case 7:\n fd = collectFormData(f);\n fieldVal = readFieldValue(f, inner);\n _context3.next = 11;\n return V.validateField(fieldVal, ruleStr, fd, targetKey);\n\n case 11:\n msg = _context3.sent;\n\n if (msg) {\n showError(targetKey, msg, form);\n hasError = true;\n } else {\n clearError(targetKey, form);\n }\n\n case 13:\n case \"end\":\n return _context3.stop();\n }\n }\n }, _callee3);\n }))());\n });\n _context4.next = 15;\n return Promise.all(tasks);\n\n case 15:\n if (!hasError) {\n _context4.next = 19;\n break;\n }\n\n form._tvSubmitting = false;\n safeFeed('[callout t:error tt:校验未通过 tx:请修正红色标记的字段]', form);\n return _context4.abrupt(\"return\");\n\n case 19:\n // 校验通过 → 提交\n submitUrl = form ? form.getAttribute('data-url') : null;\n\n if (submitUrl) {\n _context4.next = 26;\n break;\n }\n\n // 无后端地址(纯聊天表单):校验通过即触发成功回调,由上层完成已提交标记和摘要回传\n form._tvSubmitting = false;\n safeFeed('[callout t:success tt:校验通过 tx:全部字段通过校验]', form);\n notifyFormSuccess(form, collectFormData(element), '校验通过');\n lockFormWithOverlay(form);\n return _context4.abrupt(\"return\");\n\n case 26:\n allData = collectFormData(element);\n submitScope = resolveScope(form) || form;\n auth = (0, _request.getAuth)(); // ============ 表单类型分发:不同表单走不同的校验/数据组装逻辑 ============\n // 通过 data-approval-type 区分表单类型(leave=请假, patch=补卡, 其他=无特殊处理)\n\n formType = (form.getAttribute('data-approval-type') || '').trim(); // 如果 data-approval-type 没有直接挂在 form 上,尝试在 form 内查找\n\n if (!formType) {\n typeAttrEl = form.querySelector('[data-approval-type]');\n if (typeAttrEl) formType = (typeAttrEl.getAttribute('data-approval-type') || '').trim();\n } // 非审批型表单(预约会议等直接提交、无审批流)无 data-approval-type,\n // 按 data-form-id(bookingForm)或 data-form-type(meeting)识别为预约会议。\n // ① form 自身属性 ② form 内元素属性(同 data-approval-type 的查找方式)\n\n\n if (!formType) {\n meetingIdEl = null;\n meetingIdAttr = (form.getAttribute('data-form-id') || '').trim();\n meetingTypeAttr = (form.getAttribute('data-form-type') || '').trim();\n\n if (!meetingIdAttr && !meetingTypeAttr) {\n meetingTagEl = form.querySelector('[data-form-id], [data-form-type]');\n\n if (meetingTagEl) {\n meetingIdAttr = (meetingTagEl.getAttribute('data-form-id') || '').trim();\n meetingTypeAttr = (meetingTagEl.getAttribute('data-form-type') || '').trim();\n }\n }\n\n if (meetingIdAttr === 'bookingForm' || meetingTypeAttr === 'meeting') {\n formType = 'meeting';\n } else if (!formType && form.querySelector('#meetingNO,#meetingNo,[id=\"meetingNO\"],[id=\"meetingNo\"],[name=\"meetingNO\"],[name=\"meetingNo\"]')) {\n // ② 兜底:表单含会议号字段(meetingNO/meetingNo)即视为预约会议\n formType = 'meeting';\n }\n }\n\n _context4.t0 = formType;\n _context4.next = _context4.t0 === 'leave' ? 35 : _context4.t0 === 'patch' ? 105 : _context4.t0 === 'meeting' ? 128 : _context4.t0 === 'out' ? 149 : _context4.t0 === 'overtime' ? 172 : _context4.t0 === 'overTimeApply' ? 172 : 210;\n break;\n\n case 35:\n // ============ 请假专用:余额校验 + 数据组装 ============\n // 调试:打印 duration 字段的实际状态\n _durEl = queryById('duration', submitScope);\n balanceModeEl = queryById('_balanceMode', submitScope);\n balanceMode = balanceModeEl ? parseInt(balanceModeEl.value, 10) : -1; // 余额数值读取:优先直接读 staffBalance 元素(可能是 readonly input 或展示型文本),\n // 其次取 formData(对齐源项目 leaveApply.vue 用 staffBalance 校验,而非不存在的 leaveBalance)。\n\n _balEl = queryById('staffBalance', submitScope);\n _balDom = '';\n\n if (_balEl) {\n _balDom = _balEl.tagName === 'INPUT' || _balEl.tagName === 'TEXTAREA' || _balEl.tagName === 'SELECT' ? _balEl.value || '' : (_balEl.textContent || '').trim();\n }\n\n rawBalance = _balDom !== '' && _balDom !== undefined ? _balDom : allData.staffBalance !== undefined && allData.staffBalance !== '' ? allData.staffBalance : allData.leaveBalance || '0';\n leaveBalance = parseFloat(rawBalance);\n duration = parseFloat(allData.duration || '0'); // balanceMode === 2:该假期对当前用户不适用\n\n if (!(balanceMode === 2)) {\n _context4.next = 49;\n break;\n }\n\n form._tvSubmitting = false;\n (0, _message.showMessage)('该假期类型对您不适用', 'warning');\n safeFeed('[callout t:warning tt:提交失败 tx:该假期类型对您不适用,请选择其他假期类型]', form);\n return _context4.abrupt(\"return\");\n\n case 49:\n if (!(balanceMode === 0 && duration > leaveBalance)) {\n _context4.next = 54;\n break;\n }\n\n form._tvSubmitting = false;\n (0, _message.showMessage)('假期余额不足,当前余额:' + leaveBalance + ',请假时长:' + duration, 'warning');\n safeFeed('[callout t:error tt:余额不足 tx:\"假期余额不足,当前余额 ' + leaveBalance + ',请假时长 ' + duration + '\"]', form);\n return _context4.abrupt(\"return\");\n\n case 54:\n if (!(!duration || isNaN(duration))) {\n _context4.next = 59;\n break;\n }\n\n form._tvSubmitting = false;\n (0, _message.showMessage)('请假时长不能为空,请先选择起止时间', 'warning');\n safeFeed('[callout t:warning tt:提交失败 tx:请假时长不能为空,请先选择起止时间]', form);\n return _context4.abrupt(\"return\");\n\n case 59:\n // 组装请假提交数据(对齐后端 tbManagementApplyLeave 表结构)\n // 获取选中假期类型的名称\n typeEl_1 = queryById('type', submitScope);\n holidayName = '';\n\n if (typeEl_1 && typeEl_1.selectedIndex >= 0 && typeEl_1.options[typeEl_1.selectedIndex]) {\n holidayName = typeEl_1.options[typeEl_1.selectedIndex].textContent || typeEl_1.options[typeEl_1.selectedIndex].innerText || '';\n }\n\n holidayTypeEl = queryById('_holidayType', submitScope);\n minLeaveUnit = holidayTypeEl && holidayTypeEl.value ? parseInt(holidayTypeEl.value, 10) : 0;\n durationDayEl = queryById('_durationDay', submitScope);\n durationDay = durationDayEl ? parseFloat(durationDayEl.value) : 0; // 半天模式:组装 startTimeStr / endTimeStr\n\n stHalf = 1, etHalf = 1;\n\n if (minLeaveUnit === 1) {\n halfLabels = {\n 1: '上午',\n 2: '下午'\n };\n stHalfEl = queryById('startTimeHalf', submitScope);\n etHalfEl = queryById('endTimeHalf', submitScope);\n stHalf = stHalfEl ? parseInt(stHalfEl.value, 10) : 1;\n etHalf = etHalfEl ? parseInt(etHalfEl.value, 10) : 1;\n stVal = allData.startTime || '';\n etVal = allData.endTime || '';\n\n if (stVal) {\n try {\n sd = new Date(stVal);\n allData.startTimeStr = sd.getFullYear() + '-' + String(sd.getMonth() + 1).padStart(2, '0') + '-' + String(sd.getDate()).padStart(2, '0') + ' ' + (halfLabels[stHalf] || '上午');\n } catch (e) {}\n }\n\n if (etVal) {\n try {\n ed = new Date(etVal);\n allData.endTimeStr = ed.getFullYear() + '-' + String(ed.getMonth() + 1).padStart(2, '0') + '-' + String(ed.getDate()).padStart(2, '0') + ' ' + (halfLabels[etHalf] || '下午');\n } catch (e) {}\n }\n } // 日期转时间戳(对齐 _calcLeaveDurationImpl:补全本地时间 + 上/下午调整)\n // ★ 纯日期 \"YYYY-MM-DD\" 需补全为本地时间 \"YYYY-MM-DDT00:00:00\",否则 new Date() 按 UTC 解析导致时区偏差\n // ★ 半天模式需按上/下午调整时间,与 startTimeStr/endTimeStr 保持一致\n\n\n if (allData.startTime) {\n try {\n if (typeof allData.startTime === 'number') {\n stMs = allData.startTime;\n } else {\n stRaw = String(allData.startTime);\n if (stRaw.length === 10) stRaw += 'T00:00:00';else stRaw = stRaw.replace(' ', 'T');\n stMs = new Date(stRaw).getTime();\n }\n\n stDate = new Date(stMs);\n\n if (minLeaveUnit === 1) {\n stDate.setHours(stHalf === 2 ? 12 : 0, 0, 0, 0);\n }\n\n allData.startTime = stDate.getTime();\n } catch (e) {}\n }\n\n if (allData.endTime) {\n try {\n if (typeof allData.endTime === 'number') {\n etMs = allData.endTime;\n } else {\n etRaw = String(allData.endTime);\n if (etRaw.length === 10) etRaw += 'T00:00:00';else etRaw = etRaw.replace(' ', 'T');\n etMs = new Date(etRaw).getTime();\n }\n\n etDate = new Date(etMs);\n\n if (minLeaveUnit === 1) {\n if (etHalf === 1) {\n etDate.setHours(12, 0, 0, 0);\n } else {\n etDate.setHours(23, 59, 59, 0);\n }\n } else if (minLeaveUnit === 2) {\n etDate.setHours(23, 59, 59, 999);\n }\n\n allData.endTime = etDate.getTime();\n } catch (e) {}\n }\n\n if (allData.entryTime) {\n try {\n allData.entryTime = new Date(allData.entryTime).getTime();\n } catch (e) {}\n } // 补充服务端需要的字段(对齐源项目 leaveApply.vue submitForm 的参数)\n\n\n allData.companyId = auth.companyNo || '';\n allData.userId = auth.userno || auth.user || '';\n allData.holidayNo = allData.type || '';\n allData.holidayName = holidayName;\n allData.minLeaveUnit = minLeaveUnit;\n allData.durationDay = durationDay; // ★ duration 转数字类型(表单收集为字符串 \"1.5\",后端需要数字 1.5)\n\n if (allData.duration !== undefined && allData.duration !== '') {\n allData.duration = parseFloat(allData.duration);\n }\n\n allData.saveFlag = 1; // 提交标记\n\n allData.createCompany = allData.companyNo || '';\n allData.departmentNo = allData.departNo || '1'; // 源项目: departNo == \"\" ? \"1\" : departNo\n // 源项目补充的空字段\n\n allData.isDelete = '';\n allData.processId = '';\n allData.status = '';\n allData.no = ''; // images:源项目提交 pictures 数组,详情页 JSON.parse(images) 要求合法 JSON\n // 如果为空则提交 \"[]\"(JSON 字符串),避免详情页 JSON.parse 报错\n\n if (!allData.images || allData.images === '' || allData.images === '[]') {\n allData.images = [];\n } // 源项目计算的附加字段\n\n\n applierName = allData.applierName || '';\n allData.businessName = applierName ? applierName + '的请假申请' : ''; // typeStr:假期类型名称\n\n allData.typeStr = holidayName; // companyName / departName:从 select 文本获取\n\n companySel = queryById('companyNo', submitScope);\n\n if (companySel && companySel.selectedIndex >= 0 && companySel.options[companySel.selectedIndex]) {\n allData.companyName = companySel.options[companySel.selectedIndex].textContent || '';\n }\n\n departSel = queryById('departNo', submitScope);\n\n if (departSel && departSel.selectedIndex >= 0 && departSel.options[departSel.selectedIndex]) {\n allData.departName = departSel.options[departSel.selectedIndex].textContent || '';\n } // operator:操作人(源项目: this.$store.state.user.realName || accountName)\n // 优先用 applierName(即 staffName),其次 tokuiParams 中的 realName/accountName\n\n\n tokuiParams = typeof window !== 'undefined' && window.__tokuiParams || {};\n allData.operator = allData.applierName || tokuiParams.loginRealName || tokuiParams.loginStaffName || tokuiParams.loginAccountName || auth.user || ''; // 清理隐藏辅助字段(不提交到后端)\n\n delete allData._holidayType;\n delete allData._leaveType;\n delete allData._balanceMode;\n delete allData._durationDay;\n delete allData.startTimeHalf;\n delete allData.endTimeHalf; // 源项目不提交以下字段(仅用于前端显示/校验)\n\n delete allData.leaveBalance;\n delete allData.staffBalance;\n delete allData.staffBalanceMode;\n return _context4.abrupt(\"break\", 211);\n\n case 105:\n // ============ 补卡申请专用:提交数据组装 ============\n console.log('[补卡提交] 检测到补卡表单,开始组装提交数据'); // 补卡时间处理:patchTime 选择框的值格式为 \"YYYY-MM-DD HH:mm\"\n // 需要转换为时间戳(毫秒)\n\n if (allData.patchTime) {\n try {\n allData.patchTime = new Date(allData.patchTime).getTime();\n } catch (e) {\n console.error('[补卡提交] 补卡时间转换失败:', e);\n }\n } // 图片数据处理:images 字段应该是数组,如果为空则传空数组\n\n\n if (!allData.images || allData.images === '' || allData.images === '[]') {\n allData.images = [];\n }\n\n if (typeof allData.images === 'string' && allData.images) {\n try {\n allData.images = JSON.parse(allData.images);\n } catch (e) {\n allData.images = [];\n }\n } // 补充公司、部门名称\n\n\n companyNoEl = queryById('companyNo', submitScope);\n\n if (companyNoEl && companyNoEl.selectedIndex >= 0 && companyNoEl.options[companyNoEl.selectedIndex]) {\n allData.companyName = companyNoEl.options[companyNoEl.selectedIndex].textContent || '';\n }\n\n departmentNoEl = queryById('departmentNo', submitScope);\n\n if (departmentNoEl && departmentNoEl.selectedIndex >= 0 && departmentNoEl.options[departmentNoEl.selectedIndex]) {\n allData.departName = departmentNoEl.options[departmentNoEl.selectedIndex].textContent || '';\n } // 申请人姓名\n\n\n if (!allData.applierName) {\n tokuiParams_1 = typeof window !== 'undefined' && window.__tokuiParams || {};\n allData.applierName = ((_options$user5 = options.user) === null || _options$user5 === void 0 ? void 0 : _options$user5.staffName) || tokuiParams_1.loginStaffName || auth.staffName || '';\n } // 补充服务端需要的字段\n\n\n allData.companyId = ((_options$user6 = options.user) === null || _options$user6 === void 0 ? void 0 : _options$user6.cno) || ((_options$user7 = options.user) === null || _options$user7 === void 0 ? void 0 : _options$user7.comNo) || auth.companyNo || '';\n allData.userNo = ((_options$user8 = options.user) === null || _options$user8 === void 0 ? void 0 : _options$user8.no) || ((_options$user9 = options.user) === null || _options$user9 === void 0 ? void 0 : _options$user9.id) || auth.userno || auth.user || '';\n allData.userId = allData.userNo;\n allData.empNo = '';\n allData.userName = '';\n allData.businessName = '';\n allData.isDelete = '';\n allData.processId = '';\n allData.status = '';\n allData.no = ''; // saveFlag:1=提交,0=暂存\n\n if (allData.saveFlag === undefined || allData.saveFlag === '') {\n allData.saveFlag = 1;\n }\n\n allData.createCompany = allData.companyNo || '';\n console.log('[补卡提交] 组装完成的提交数据:', allData);\n return _context4.abrupt(\"break\", 211);\n\n case 128:\n // ============ 预约会议专用:提交数据组装(对齐 sgb-im meeting/submitV1) ============\n // 表单字段:name(会议主题) / meetingNO(会议号) / startTime / endTime\n // 其余字段(hostNo/hostName/hostImAccount/members 等)从登录态补齐,可被表单隐藏字段覆盖\n console.log('[会议提交] 检测到预约会议表单,开始组装 submitV1 参数'); // 时间 → 时间戳(由 \"YYYY-MM-DD HH:mm\" 转为毫秒;已是数字则原样保留)\n\n meetingToMs = function meetingToMs(v) {\n if (v === undefined || v === null || v === '') return v;\n if (typeof v === 'number') return v;\n\n try {\n return new Date(String(v).replace(' ', 'T')).getTime();\n } catch (e) {\n return v;\n }\n };\n\n if (allData.startTime !== undefined) allData.startTime = meetingToMs(allData.startTime);\n if (allData.endTime !== undefined) allData.endTime = meetingToMs(allData.endTime); // 登录态用户信息(与请假/补卡同源约定,缺省可被表单字段覆盖)\n\n meetingTokui = typeof window !== 'undefined' && window.__tokuiParams || {};\n loginUserInfo = getLoginUserInfo(options.user);\n mUserNo = allData.userNo || allData.hostNo || loginUserInfo.no || '';\n mHostName = allData.hostName || loginUserInfo.name || meetingTokui.loginStaffName || meetingTokui.loginRealName || auth.staffName || '';\n mImAccount = allData.hostImAccount || loginUserInfo.account || meetingTokui.loginAccountName || auth.user || ''; // 头像:userinfo 接口取 headImg(兼容 userImg),无则留空即可\n\n mUserImg = allData.userImg || loginUserInfo.img || ''; // 会议号:表单字段为 meetingNO,兼容 meetingNo\n\n mMeetingNo = allData.meetingNO !== undefined ? allData.meetingNO : allData.meetingNo; // 通知规则:默认提前 15 分钟提醒一次(FIFTEEN_MINUTE / APPLICATION)\n\n mNoticeTime = allData.noticeTime !== undefined ? allData.noticeTime : ['FIFTEEN_MINUTE'];\n\n if (typeof mNoticeTime === 'string') {\n try {\n mNoticeTime = JSON.parse(mNoticeTime);\n } catch (e) {\n mNoticeTime = allData.noticeTime;\n }\n }\n\n mNoticeType = allData.noticeType || 'APPLICATION'; // 参会人:默认当前用户为主持人(host),可被表单 members 覆盖\n\n mMembers = allData.members !== undefined ? allData.members : [{\n imAccount: mImAccount,\n name: mHostName,\n userImg: mUserImg,\n userNo: mUserNo,\n status: 'ACCEPT',\n host: 'YES',\n isSelect: true\n }];\n\n if (typeof mMembers === 'string') {\n try {\n mMembers = JSON.parse(mMembers);\n } catch (e) {}\n } // 扩展字段(如 newNoticeTime)需在重建 allData 前捕获\n\n\n mNewNotice = allData.newNoticeTime;\n\n if (typeof mNewNotice === 'string') {\n try {\n mNewNotice = JSON.parse(mNewNotice);\n } catch (e) {\n mNewNotice = allData.newNoticeTime;\n }\n } // 组装 submitV1 提交体(替换原表单字段集合)\n\n\n allData = {\n hostNo: allData.hostNo || mUserNo,\n topic: allData.topic || allData.name || '',\n matter: allData.matter || '',\n startTime: allData.startTime,\n endTime: allData.endTime,\n meetingNo: mMeetingNo || '',\n meetingUnit: allData.meetingUnit || '',\n meetingNoType: allData.meetingNoType || 'RANDOM',\n hostName: mHostName,\n hostImAccount: mImAccount,\n openWaitRoom: allData.openWaitRoom === undefined ? false : !!allData.openWaitRoom,\n sendInvite: allData.sendInvite === undefined ? true : !!allData.sendInvite,\n noticeTime: mNoticeTime,\n noticeType: mNoticeType,\n members: mMembers || []\n }; // 保留表单扩展字段(如 newNoticeTime)\n\n if (mNewNotice !== undefined) allData.newNoticeTime = mNewNotice;\n console.log('[会议提交] 组装完成的 submitV1 参数:', allData);\n\n case 149:\n // ============ 外出申请专用:提交数据组装 ============\n // 参照源项目 goOutApplyAdd.vue submitForm 方法的字段结构\n console.log('[外出提交] 检测到外出表单,开始组装提交数据'); // 起止时间转为时间戳(毫秒)— 对齐 kqgl: new Date(startTime).getTime()\n\n if (allData.startTime) {\n try {\n if (typeof allData.startTime === 'number') {// 已是时间戳无需转换\n } else {\n stStr = String(allData.startTime);\n if (stStr.length === 10) stStr += 'T00:00:00';else stStr = stStr.replace(' ', 'T');\n allData.startTime = new Date(stStr).getTime();\n }\n } catch (e) {\n console.error('[外出提交] startTime 转换失败:', e);\n }\n }\n\n if (allData.endTime) {\n try {\n if (typeof allData.endTime === 'number') {// 已是时间戳无需转换\n } else {\n etStr = String(allData.endTime);\n if (etStr.length === 10) etStr += 'T00:00:00';else etStr = etStr.replace(' ', 'T');\n allData.endTime = new Date(etStr).getTime();\n }\n } catch (e) {\n console.error('[外出提交] endTime 转换失败:', e);\n }\n } // 图片处理:images 字段应为数组 — 对齐 kqgl: this.form.images = this.pictures\n\n\n if (!allData.images || allData.images === '' || allData.images === '[]') {\n allData.images = [];\n }\n\n if (typeof allData.images === 'string' && allData.images) {\n try {\n allData.images = JSON.parse(allData.images);\n } catch (e) {\n allData.images = [];\n }\n } // 补充服务端需要的字段(对齐 kqgl goOutApplyAdd.vue submitForm)\n\n\n tokuiParams = typeof window !== 'undefined' && window.__tokuiParams || {};\n allData.createUserNo = tokuiParams.loginUserNo || auth.userno || auth.user || '';\n allData.createCompanyNo = tokuiParams.loginCompanyNo || auth.companyNo || ''; // ★ 外出申请部门字段:\n // 部门 select 实际 id 可由 DSL 通过 data-apply-dept-field / data-dept-field 显式配置,\n // 未配置时按 departmentNo → applyDeptId 候选自动识别;\n // 有部门时同时提交 applyDeptId 与 departmentNo,无部门时仅 departmentNo 兜底 '1' 且不带 applyDeptId\n\n cfgDeptField = (form.getAttribute('data-apply-dept-field') || form.getAttribute('data-dept-field') || '').trim();\n deptSelEl = (cfgDeptField ? queryById(cfgDeptField, submitScope) : null) || queryById('departmentNo', submitScope) || queryById('applyDeptId', submitScope);\n outDeptVal = '';\n\n if (deptSelEl) {\n if (deptSelEl.tagName === 'SELECT' && deptSelEl.selectedIndex >= 0 && deptSelEl.options[deptSelEl.selectedIndex]) {\n outDeptVal = deptSelEl.options[deptSelEl.selectedIndex].getAttribute('value') || deptSelEl.options[deptSelEl.selectedIndex].value || '';\n } else {\n outDeptVal = deptSelEl.value || deptSelEl.getAttribute('value') || '';\n }\n }\n\n if (outDeptVal) {\n // 有部门:applyDeptId 与 departmentNo 均带部门值提交\n allData.applyDeptId = outDeptVal;\n allData.departmentNo = outDeptVal;\n } else {\n // 无部门:不带 applyDeptId,departmentNo 兜底 '1'\n delete allData.applyDeptId;\n allData.departmentNo = allData.departmentNo || '1';\n } // companyNo(select 的 value)只在 DSL 内部用,kqgl 提交的是 companyId,不提交 companyNo\n // ★ 先保存 companyId 再删除 companyNo,避免 companyNo 丢失后退化为 auth.companyNo\n\n\n allData.companyId = allData.companyNo || auth.companyNo || '';\n delete allData.companyNo;\n allData.userNo = tokuiParams.loginUserNo || auth.userno || auth.user || ''; // saveFlag:1=提交,0=暂存 — 对齐 kqgl: submitForm('form', saveFlag)\n\n if (allData.saveFlag === undefined || allData.saveFlag === '') {\n allData.saveFlag = 1;\n } // 编辑模式保留 id,新增模式设为 null — 对齐 kqgl: id = $route.query.id || null\n\n\n if (!allData.id) {\n allData.id = null;\n } // ★ 补充 kqgl 必传字段\n // duration/burningTime:对齐 kqgl 将 duration 作为外出时长提交\n\n\n if (allData.duration !== undefined && allData.duration !== '') {\n durationNum = parseFloat(allData.duration);\n allData.duration = durationNum;\n allData.burningTime = durationNum; // 对齐 kqgl: burningTime 与 duration 相同\n } // reasons:对齐 kqgl 使用 reasons 字段名(当前 DSL 可能用 reason)\n\n\n if (allData.reason && !allData.reasons) {\n allData.reasons = allData.reason;\n }\n\n delete allData.reason;\n console.log('[外出提交] 组装完成的提交数据:', allData);\n return _context4.abrupt(\"break\", 211);\n\n case 172:\n // ============ 加班申请专用:提交数据组装 ============\n // 参照源项目 workTimeApplyAdd.vue submitForm 方法的字段结构(v1/overTimeApply/save)\n console.log('[加班提交] 检测到加班表单,开始组装提交数据'); // 时长 timeLength:从 duration 字段取值,转为数字\n\n if (allData.duration !== undefined && allData.duration !== '') {\n allData.timeLength = parseFloat(allData.duration);\n } else {\n allData.timeLength = 0;\n }\n\n delete allData.duration; // 起止时间转为 YYYY-MM-dd HH:mm 格式字符串(对齐 getTime 工具函数行为)\n\n if (allData.startTime) {\n try {\n allData.startTime = formatToDateTimeStr(allData.startTime);\n } catch (e) {\n console.error('[加班提交] startTime 转换失败:', e);\n }\n }\n\n if (allData.endTime) {\n try {\n allData.endTime = formatToDateTimeStr(allData.endTime);\n } catch (e) {\n console.error('[加班提交] endTime 转换失败:', e);\n }\n } // 时长不能为零或空\n\n\n if (!(!allData.timeLength || allData.timeLength <= 0)) {\n _context4.next = 182;\n break;\n }\n\n form._tvSubmitting = false;\n (0, _message.showMessage)('加班时长不能为空,请先选择起止时间', 'warning');\n safeFeed('[callout t:warning tt:提交失败 tx:加班时长不能为空,请先选择起止时间]', form);\n return _context4.abrupt(\"return\");\n\n case 182:\n // 图片处理 → 参照项目用 file 字段提交图片数组\n if (!allData.images || allData.images === '' || allData.images === '[]') {\n allData.images = [];\n }\n\n if (typeof allData.images === 'string' && allData.images) {\n try {\n allData.images = JSON.parse(allData.images);\n } catch (e) {\n allData.images = [];\n }\n }\n\n allData.file = allData.images; // 参照项目用 file 字段名\n\n delete allData.images; // 跨日加班明细:从 overtimeList 隐藏字段读取,转为对象数组\n\n _otListEl = queryById('overtimeList', submitScope);\n\n if (_otListEl && _otListEl.value) {\n try {\n allData.overTimeList = typeof _otListEl.value === 'string' ? JSON.parse(_otListEl.value) : _otListEl.value;\n } catch (e) {\n console.warn('[加班提交] overTimeList 解析失败:', e);\n allData.overTimeList = [];\n }\n } else {\n // 没有跨天明细时,兜底提交空数组\n allData.overTimeList = [];\n } // type 字段:1=单天加班,2=跨天加班(对齐参照项目 type 字段)\n\n\n allData.type = allData.overTimeList && allData.overTimeList.length > 0 ? 2 : 1; // 公司、部门编号与名称\n // 参照项目:companyNo(编号)、companyName(文本)、deptNo(编号)、deptName(文本)\n\n companyNoEl = queryById('companyNo', submitScope);\n\n if (companyNoEl) {\n if (companyNoEl.selectedIndex >= 0 && companyNoEl.options[companyNoEl.selectedIndex]) {\n allData.companyName = companyNoEl.options[companyNoEl.selectedIndex].textContent || '';\n }\n\n allData.companyNo = companyNoEl.value || allData.companyNo || '';\n }\n\n departmentNoEl = queryById('departmentNo', submitScope) || queryById('deptNo', submitScope);\n\n if (departmentNoEl) {\n if (departmentNoEl.selectedIndex >= 0 && departmentNoEl.options[departmentNoEl.selectedIndex]) {\n allData.deptName = departmentNoEl.options[departmentNoEl.selectedIndex].textContent || '';\n }\n\n allData.deptNo = departmentNoEl.value || allData.deptNo || '';\n allData.departmentNo = allData.deptNo || '1';\n } // 申请人信息(参照项目:applyUserName、applyUserNo、createCompanyNo)\n\n\n tokuiParams = typeof window !== 'undefined' && window.__tokuiParams || {};\n allData.createCompanyNo = tokuiParams.loginCompanyNo || auth.companyNo || '';\n allData.applyUserName = tokuiParams.loginStaffName || tokuiParams.loginRealName || auth.user || '';\n allData.applyUserNo = tokuiParams.loginUserno || tokuiParams.loginUserno || auth.userno || auth.user || '';\n allData.operator = allData.applyUserName;\n allData.businessName = allData.applyUserName ? allData.applyUserName + '的加班申请' : ''; // 审批集成字段(参照项目会通过审批流程生成 uniqueId)\n\n allData.userNo = auth.userno || auth.user || '';\n allData.userId = allData.userNo;\n allData.createCompany = allData.companyNo || '';\n allData.isDelete = '';\n allData.processId = '';\n allData.status = '';\n allData.no = ''; // 清理辅助字段(防止误提交)\n\n delete allData._durationDay; // saveFlag:1=提交,0=暂存\n\n if (allData.saveFlag === undefined || allData.saveFlag === '') {\n allData.saveFlag = 1;\n }\n\n console.log('[加班提交] 组装完成的提交数据:', allData);\n return _context4.abrupt(\"break\", 211);\n\n case 210:\n return _context4.abrupt(\"break\", 211);\n\n case 211:\n _context4.next = 213;\n return runApprovalFlow(form, allData);\n\n case 213:\n approvalResult = _context4.sent;\n\n if (!(approvalResult === false)) {\n _context4.next = 217;\n break;\n }\n\n form._tvSubmitting = false;\n return _context4.abrupt(\"return\");\n\n case 217:\n if (approvalResult && approvalResult.uniqueId) {\n allData.uniqueId = approvalResult.uniqueId;\n }\n\n safeFeed('[callout t:info tt:提交中 tx:正在提交数据...]', form);\n _context4.prev = 219;\n method = (form.getAttribute('data-method') || 'POST').toUpperCase();\n resultJson = null;\n\n if (!(method === 'GET')) {\n _context4.next = 230;\n break;\n }\n\n params = new URLSearchParams(allData).toString();\n if (params) submitUrl += (submitUrl.includes('?') ? '&' : '?') + params;\n _context4.next = 227;\n return _request.default.get(submitUrl);\n\n case 227:\n resultJson = _context4.sent;\n _context4.next = 239;\n break;\n\n case 230:\n if (!(method === 'DELETE')) {\n _context4.next = 236;\n break;\n }\n\n _context4.next = 233;\n return _request.default.delete(submitUrl, {\n data: allData\n });\n\n case 233:\n resultJson = _context4.sent;\n _context4.next = 239;\n break;\n\n case 236:\n _context4.next = 238;\n return _request.default.post(submitUrl, allData);\n\n case 238:\n resultJson = _context4.sent;\n\n case 239:\n resultText = resultJson ? resultJson.message || resultJson.msg || '提交成功' : '提交成功';\n (0, _message.showMessage)(resultText || '提交成功', 'success');\n safeFeed('[callout t:success tt:提交成功 tx:\"' + resultText.replace(/\"/g, '') + '\"]', form); // 回调通知上层:表单提交成功后的业务钩子\n\n notifyFormSuccess(form, allData, resultText); // 遮罩锁定整个 TokUI 容器,防止二次提交\n\n lockFormWithOverlay(form);\n _context4.next = 249;\n break;\n\n case 246:\n _context4.prev = 246;\n _context4.t1 = _context4[\"catch\"](219);\n // 错误提示已由 http 拦截器统一处理,这里仅做表单内反馈\n safeFeed('[callout t:error tt:提交失败 tx:\"' + (_context4.t1.message || '').replace(/\"/g, '') + '\"]', form);\n\n case 249:\n _context4.prev = 249;\n form._tvSubmitting = false;\n return _context4.finish(249);\n\n case 252:\n case \"end\":\n return _context4.stop();\n }\n }\n }, _callee4, null, [[219, 246, 249, 252]]);\n }));\n\n return function (_x6, _x7, _x8) {\n return _ref2.apply(this, arguments);\n };\n }()); // ============ 注册请假时长计算 ============\n // 用于请假表单:选择开始/结束时间或半天选择器后,自动调用服务端计算时长\n // DSL: [datepicker ... clk:calcLeaveDuration] 或 [select ... clk:calcLeaveDuration]\n // 参数映射声明在 duration 字段上,和 data-load-body-dep-* 同一设计:\n // data-calc-duration-url → 计算时长 API 地址\n // data-calc-duration-body-dep-{param} → 参数值来源:可以是字段ID 或 \"login*\" 键\n // 示例:\n // data-calc-duration-body-dep-comNo:\"loginCompanyNo\"\n // data-calc-duration-body-dep-holidayNo:\"type\"\n // data-calc-duration-body-dep-startTime:\"startTime\"\n // data-calc-duration-body-dep-endTime:\"endTime\"\n // calcLeaveDuration 防抖:避免 datepicker 交互过程中频繁触发 API\n // ★ 多表单隔离:防抖计时器与并发锁挂在各自表单容器(scope)上,\n // 否则两个表单共用同一个全局 handler 时,A 表单的防抖会被 B 表单清掉/锁住。\n // ★ 兼容 change+input 双事件:输入事件去重窗口 800ms 内只执行一次 API。\n\n window.TokUI.registerHandler('calcLeaveDuration', /*#__PURE__*/function () {\n var _ref4 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee6(data, event, element) {\n var scope, now;\n return regeneratorRuntime.wrap(function _callee6$(_context6) {\n while (1) {\n switch (_context6.prev = _context6.next) {\n case 0:\n // ★ 多表单隔离:作用域只允许从当前元素(或事件目标)向上查找,\n // 绝不回退到本 handler 首次注册时闭包捕获的 container(首个表单)。\n // 否则 datepicker 确认按钮只传 data 不传 element 时会串扰到第一个表单。\n scope = findNearestTokuiScope(element);\n if (!scope && event && event.target) scope = findNearestTokuiScope(event.target);\n\n if (scope) {\n _context6.next = 4;\n break;\n }\n\n return _context6.abrupt(\"return\");\n\n case 4:\n // ★ 输入去重:change+input 双事件可能在 1s 内触发多次,\n // 用 scope._calcDurationDedup 记录最近一次执行时间戳,\n // 800ms 内不再重复执行(防抖仅延迟但不清掉时仍会触发)。\n now = Date.now();\n\n if (!(scope._calcDurationDedup && now - scope._calcDurationDedup < 800)) {\n _context6.next = 7;\n break;\n }\n\n return _context6.abrupt(\"return\");\n\n case 7:\n // 防抖:延迟 400ms 执行,期间如有新触发则取消前一次(仅在当前表单作用域内)\n if (scope._calcDurationTimer) clearTimeout(scope._calcDurationTimer);\n return _context6.abrupt(\"return\", new Promise(function (resolve) {\n scope._calcDurationTimer = setTimeout( /*#__PURE__*/(0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee5() {\n return regeneratorRuntime.wrap(function _callee5$(_context5) {\n while (1) {\n switch (_context5.prev = _context5.next) {\n case 0:\n scope._calcDurationTimer = null;\n\n if (!scope._calcDurationLock) {\n _context5.next = 4;\n break;\n }\n\n resolve();\n return _context5.abrupt(\"return\");\n\n case 4:\n scope._calcDurationLock = true;\n _context5.prev = 5;\n scope._calcDurationDedup = Date.now();\n _context5.next = 9;\n return _calcLeaveDurationImpl(element, scope);\n\n case 9:\n _context5.prev = 9;\n scope._calcDurationLock = false;\n return _context5.finish(9);\n\n case 12:\n resolve();\n\n case 13:\n case \"end\":\n return _context5.stop();\n }\n }\n }, _callee5, null, [[5,, 9, 12]]);\n })), 400);\n }));\n\n case 9:\n case \"end\":\n return _context6.stop();\n }\n }\n }, _callee6);\n }));\n\n return function (_x9, _x10, _x11) {\n return _ref4.apply(this, arguments);\n };\n }());\n\n function _calcLeaveDurationImpl(_x12, _x13) {\n return _calcLeaveDurationImpl2.apply(this, arguments);\n } // ============ 注册加班时长计算 ============\n // 用于加班表单:选择开始/结束时间后,自动调用服务端计算时长\n // DSL: [datetimepicker ... clk:calcOvertimeDuration] 或 [datetimepicker ... clk:calcOvertimeDuration]\n // 参数映射声明在 duration 字段上,和请假 data-calc-duration-body-dep-* 同一设计:\n // data-calc-duration-body-dep-startTime:\"startTime\"\n // data-calc-duration-body-dep-endTime:\"endTime\"\n // 实现逻辑(对齐源项目 workTimeApplyAdd.vue endChange 方法):\n // 1. 同一天(开始日期 === 结束日期)→ 单段加班,调用 calculateDuration API\n // 2. 跨天 → 多段加班,需要按天拆分并计算每天时长(简化为只计算总时长)\n // 3. 回填 duration 字段\n // ★ 兼容 change+input 双事件:800ms 去重窗口\n\n\n function _calcLeaveDurationImpl2() {\n _calcLeaveDurationImpl2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee12(element, explicitScope) {\n var calcScope, durationEl, durationField, bodyObj, bodyDeps, hasParams, i, attr, suffix, depParam, paramKey, depElId, htEl, typeSel, opt, minUnit, startTimeStr, endTimeStr, startTimeMs, endTimeMs, endEl, holidayType, stHalfEl, etHalfEl, stHalf, etHalf, startDate, endDate1, endDate, diffHours, endEl2, calcUrl, json, hasMinLeaveUnit, unitText, fieldWrapper, appendEl, children, ci, span, ddEl, form;\n return regeneratorRuntime.wrap(function _callee12$(_context12) {\n while (1) {\n switch (_context12.prev = _context12.next) {\n case 0:\n // ★ 多表单隔离:优先使用调用方传入的作用域,否则从 element 向上查找\n calcScope = explicitScope || findNearestTokuiScope(element);\n\n if (calcScope) {\n _context12.next = 3;\n break;\n }\n\n return _context12.abrupt(\"return\");\n\n case 3:\n durationEl = queryById('duration', calcScope);\n\n if (durationEl) {\n _context12.next = 6;\n break;\n }\n\n return _context12.abrupt(\"return\");\n\n case 6:\n // 从 duration 字段的 .tokui-field 包装层读配置(patch.js 将 data-* 放在包装层上)\n durationField = durationEl.closest('[data-calc-duration-url]');\n\n if (durationField) {\n _context12.next = 9;\n break;\n }\n\n return _context12.abrupt(\"return\");\n\n case 9:\n // 读取请求体参数映射(data-calc-duration-body-dep-{param})\n bodyObj = {};\n bodyDeps = {};\n hasParams = false;\n\n if (!durationField.attributes) {\n _context12.next = 31;\n break;\n }\n\n i = 0;\n\n case 14:\n if (!(i < durationField.attributes.length)) {\n _context12.next = 31;\n break;\n }\n\n attr = durationField.attributes[i];\n\n if (!(!attr.name || attr.name.indexOf('data-calc-duration-body-') !== 0)) {\n _context12.next = 18;\n break;\n }\n\n return _context12.abrupt(\"continue\", 28);\n\n case 18:\n suffix = attr.name.substring('data-calc-duration-body-'.length);\n\n if (suffix) {\n _context12.next = 21;\n break;\n }\n\n return _context12.abrupt(\"continue\", 28);\n\n case 21:\n if (!(suffix.indexOf('dep-') === 0)) {\n _context12.next = 25;\n break;\n }\n\n depParam = normalizeBodyKey(suffix.substring('dep-'.length));\n\n if (depParam) {\n bodyDeps[depParam] = attr.value.trim();\n hasParams = true;\n }\n\n return _context12.abrupt(\"continue\", 28);\n\n case 25:\n // data-calc-duration-body-{param} → 静态值\n paramKey = normalizeBodyKey(suffix);\n bodyObj[paramKey] = attr.value;\n hasParams = true;\n\n case 28:\n i++;\n _context12.next = 14;\n break;\n\n case 31:\n if (hasParams) {\n _context12.next = 34;\n break;\n }\n\n console.warn('[Validator] calcLeaveDuration: duration 字段上未配置 data-calc-duration-body-dep-* 参数映射,跳过计算');\n return _context12.abrupt(\"return\");\n\n case 34:\n _context12.t0 = regeneratorRuntime.keys(bodyDeps);\n\n case 35:\n if ((_context12.t1 = _context12.t0()).done) {\n _context12.next = 43;\n break;\n }\n\n depParam = _context12.t1.value;\n\n if (bodyDeps.hasOwnProperty(depParam)) {\n _context12.next = 39;\n break;\n }\n\n return _context12.abrupt(\"continue\", 35);\n\n case 39:\n depElId = bodyDeps[depParam];\n bodyObj[depParam] = _readFieldValueById(depElId, calcScope);\n _context12.next = 35;\n break;\n\n case 43:\n // ★ 兜底:确保 minLeaveUnit 始终存在(源项目始终发送 minLeaveUnit: this.holidayType)\n // 如果 DSL 未配置 data-calc-duration-body-dep-minLeaveUnit,从 _holidayType 隐藏字段读取\n // 注意:外出表单 DSL 没有 minLeaveUnit 依赖,不添加此字段\n if (bodyDeps.minLeaveUnit !== undefined) {\n if (bodyObj.minLeaveUnit === undefined || bodyObj.minLeaveUnit === '') {\n htEl = queryById('_holidayType', calcScope);\n bodyObj.minLeaveUnit = htEl ? htEl.value : ''; // ★ 增强兜底:如果 _holidayType 仍未取到,尝试从 type 下拉框选中项读取 data-min-leave-unit\n\n if (bodyObj.minLeaveUnit === '' || bodyObj.minLeaveUnit === undefined) {\n typeSel = queryById('type', calcScope);\n\n if (typeSel && typeSel.tagName === 'SELECT' && typeSel.selectedIndex >= 0) {\n opt = typeSel.options[typeSel.selectedIndex];\n minUnit = opt.getAttribute('data-min-leave-unit');\n\n if (minUnit !== null && minUnit !== undefined && String(minUnit) !== '') {\n bodyObj.minLeaveUnit = String(minUnit);\n }\n }\n }\n\n if (bodyObj.minLeaveUnit === '' || bodyObj.minLeaveUnit === undefined) {\n bodyObj.minLeaveUnit = '0';\n }\n }\n } // 校验:起止时间必须都有值\n\n\n if (!(!bodyObj.startTime || !bodyObj.endTime)) {\n _context12.next = 47;\n break;\n }\n\n if (durationEl) durationEl.value = '';\n return _context12.abrupt(\"return\");\n\n case 47:\n // 转换日期 \"YYYY-MM-DD\" 或 \"YYYY-MM-DD HH:mm\" → 毫秒时间戳\n // ★ 纯日期 \"YYYY-MM-DD\" 需要补全为本地时间 \"YYYY-MM-DDT00:00:00\",否则 new Date() 按 UTC 解析导致时区偏差\n startTimeStr = String(bodyObj.startTime);\n endTimeStr = String(bodyObj.endTime);\n if (startTimeStr.length === 10) startTimeStr += 'T00:00:00';else startTimeStr = startTimeStr.replace(' ', 'T');\n if (endTimeStr.length === 10) endTimeStr += 'T00:00:00';else endTimeStr = endTimeStr.replace(' ', 'T');\n startTimeMs = new Date(startTimeStr).getTime();\n endTimeMs = new Date(endTimeStr).getTime();\n\n if (!(isNaN(startTimeMs) || isNaN(endTimeMs))) {\n _context12.next = 55;\n break;\n }\n\n return _context12.abrupt(\"return\");\n\n case 55:\n if (!(endTimeMs < startTimeMs)) {\n _context12.next = 60;\n break;\n }\n\n (0, _message.showMessage)('结束时间不能早于开始时间', 'warning');\n endEl = queryById(bodyDeps.endTime, calcScope);\n if (endEl) endEl.value = '';\n return _context12.abrupt(\"return\");\n\n case 60:\n bodyObj.startTime = startTimeMs;\n bodyObj.endTime = endTimeMs; // 读取 minLeaveUnit(0=按小时, 1=按半天, 2=按天)\n // 外出表单无 minLeaveUnit 依赖,holidayType 为 NaN,不进入任何分支\n\n holidayType = parseInt(bodyObj.minLeaveUnit, 10) || 0; // ★ 半天模式:根据上/下午调整时间(对齐源项目 getDuration)\n // 上午 → 00:00:00,下午 → 23:59:59\n\n if (holidayType === 1) {\n stHalfEl = queryById('startTimeHalf', calcScope);\n etHalfEl = queryById('endTimeHalf', calcScope); // ★ 年假上/下午选择变化后需重新计算时长:这两个控件通常不带 clk:calcLeaveDuration,\n // 导致用户选完起止日期再改上/下午时接口不被触发、时长不反显。这里补绑定一次。\n\n if (stHalfEl && !stHalfEl._tvCalcHalfBound) {\n stHalfEl._tvCalcHalfBound = true;\n stHalfEl.addEventListener('change', function () {\n var h = _clkHandlers['calcLeaveDuration'];\n if (typeof h === 'function') h({}, new Event('change'), stHalfEl);\n });\n }\n\n if (etHalfEl && !etHalfEl._tvCalcHalfBound) {\n etHalfEl._tvCalcHalfBound = true;\n etHalfEl.addEventListener('change', function () {\n var h = _clkHandlers['calcLeaveDuration'];\n if (typeof h === 'function') h({}, new Event('change'), etHalfEl);\n });\n }\n\n stHalf = stHalfEl ? parseInt(stHalfEl.value, 10) : 1;\n etHalf = etHalfEl ? parseInt(etHalfEl.value, 10) : 1; // ★ 半天模式时间规则:\n // 开始时间:上午→00:00:00,下午→12:00:00\n // 结束时间:上午→12:00:00,下午→23:59:59\n\n startDate = new Date(startTimeMs);\n\n if (stHalf === 1) {\n startDate.setHours(0, 0, 0, 0);\n } else {\n startDate.setHours(12, 0, 0, 0);\n }\n\n bodyObj.startTime = startDate.getTime();\n endDate1 = new Date(endTimeMs);\n\n if (etHalf === 1) {\n endDate1.setHours(12, 0, 0, 0);\n } else {\n endDate1.setHours(23, 59, 59, 0);\n }\n\n bodyObj.endTime = endDate1.getTime();\n } // 按天模式:结束时间统一设为 23:59:59\n\n\n if (holidayType === 2) {\n endDate = new Date(endTimeMs);\n endDate.setHours(23, 59, 59, 999);\n bodyObj.endTime = endDate.getTime();\n } // 按小时模式:最少1小时校验\n\n\n if (!(holidayType === 0)) {\n _context12.next = 72;\n break;\n }\n\n diffHours = (bodyObj.endTime - bodyObj.startTime) / (1000 * 60 * 60);\n\n if (!(diffHours < 1)) {\n _context12.next = 72;\n break;\n }\n\n (0, _message.showMessage)('请假时长不得小于一小时', 'warning');\n endEl2 = queryById(bodyDeps.endTime, calcScope);\n if (endEl2) endEl2.value = '';\n return _context12.abrupt(\"return\");\n\n case 72:\n // ★ 仅在 DSL 声明了 startTimeStr/endTimeStr 依赖时才构造(请假表单需要,外出不需要)\n // 源项目: startTimeStr = `${dayjs(startTime).format(\"YYYY-MM-DD\")} ${startTimeHalf == 1 ? \"上午\" : \"下午\"}`\n if (bodyDeps.startTimeStr !== undefined) {\n bodyObj.startTimeStr = buildHalfDayTimeStr(bodyObj.startTime, 'startTimeHalf', calcScope);\n }\n\n if (bodyDeps.endTimeStr !== undefined) {\n bodyObj.endTimeStr = buildHalfDayTimeStr(bodyObj.endTime, 'endTimeHalf', calcScope);\n } // 调用服务端时长计算 API\n\n\n calcUrl = cleanUrl(durationField.getAttribute('data-calc-duration-url') || '');\n\n if (calcUrl) {\n _context12.next = 78;\n break;\n }\n\n console.warn('[Validator] calcLeaveDuration: duration 字段上未配置 data-calc-duration-url');\n return _context12.abrupt(\"return\");\n\n case 78:\n _context12.prev = 78;\n _context12.next = 81;\n return _request.default.post(calcUrl, bodyObj);\n\n case 81:\n json = _context12.sent;\n\n if (!(json.code === 200 && json.data)) {\n _context12.next = 117;\n break;\n }\n\n // ★ 外出表单(无 minLeaveUnit):显示 leaveTime(小时),值用 duration(天/数值)\n // 请假表单(有 minLeaveUnit):显示 duration,单位由 minLeaveUnit 决定\n hasMinLeaveUnit = bodyDeps.minLeaveUnit !== undefined;\n\n if (hasMinLeaveUnit) {\n if (durationEl) durationEl.value = String(json.data.duration || '');\n } else {\n // 外出表单:使用 leaveTime 作为显示值(如 3 小时),duration 作为数值(如 0.42 天)\n if (durationEl) durationEl.value = String(json.data.leaveTime || json.data.duration || '');\n }\n\n unitText = hasMinLeaveUnit ? getLeaveUnitText(bodyObj.minLeaveUnit) : '小时';\n fieldWrapper = durationEl.closest('.tokui-field') || durationEl.parentElement;\n\n if (!fieldWrapper) {\n _context12.next = 100;\n break;\n }\n\n appendEl = fieldWrapper.querySelector('.tokui-input-group__append, .tokui-append, .el-input-group__append');\n\n if (appendEl) {\n _context12.next = 99;\n break;\n }\n\n children = fieldWrapper.querySelectorAll('span, div');\n ci = children.length - 1;\n\n case 92:\n if (!(ci >= 0)) {\n _context12.next = 99;\n break;\n }\n\n if (!(children[ci].textContent && children[ci].textContent.trim() && !children[ci].querySelector('input'))) {\n _context12.next = 96;\n break;\n }\n\n appendEl = children[ci];\n return _context12.abrupt(\"break\", 99);\n\n case 96:\n ci--;\n _context12.next = 92;\n break;\n\n case 99:\n if (appendEl) {\n appendEl.textContent = unitText;\n } else {\n span = document.createElement('span');\n span.className = 'tokui-duration-unit';\n span.textContent = unitText;\n span.style.cssText = 'margin-left:6px;color:#909399;font-size:14px;';\n fieldWrapper.appendChild(span);\n }\n\n case 100:\n syncLeaveUnitSuffix('staffBalance', unitText, calcScope); // 存储 durationDay 用于审批流程变量\n\n ddEl = queryById('_durationDay', calcScope); // ★ 多表单隔离:表单必须限定在当前 calcScope 内查找。\n // datepicker 确认按钮可能只传 data 不传 element,此时 resolveForm(undefined)\n // 会退化为 document 级查询并返回文档第一个表单,导致 _durationDay 与审批流串扰到别的表单。\n\n form = calcScope.querySelector('[data-form-id=\"leaveForm\"]') || queryById('leaveForm', calcScope) || calcScope.querySelector('[data-form-id]') || calcScope.querySelector('form');\n\n if (!form && element) {\n form = resolveForm(element); // resolveForm 可能返回 document 级或 body 弹层的表单,必须校验其确实落在当前表单容器内\n\n if (form && !calcScope.contains(form)) form = null;\n }\n\n if (!form && ddEl && ddEl.form) form = ddEl.form;\n\n if (ddEl) {\n _context12.next = 113;\n break;\n }\n\n if (form) {\n _context12.next = 108;\n break;\n }\n\n return _context12.abrupt(\"return\");\n\n case 108:\n ddEl = document.createElement('input');\n ddEl.type = 'hidden';\n ddEl.id = '_durationDay';\n ddEl.name = '_durationDay';\n form.appendChild(ddEl);\n\n case 113:\n ddEl.value = String(json.data.durationDay || ''); // ★ 时长计算完毕后,刷新审批流程(durationDay 变量已就绪)\n\n if (form) {\n try {\n refreshApprovalFlow(form);\n } catch (e2) {}\n }\n\n _context12.next = 118;\n break;\n\n case 117:\n if (holidayType !== 1) {\n (0, _message.showMessage)(json.msg || json.message || '计算时长失败', 'warning');\n }\n\n case 118:\n _context12.next = 123;\n break;\n\n case 120:\n _context12.prev = 120;\n _context12.t2 = _context12[\"catch\"](78);\n console.warn('[Validator] calcLeaveDuration API error:', _context12.t2.message);\n\n case 123:\n case \"end\":\n return _context12.stop();\n }\n }\n }, _callee12, null, [[78, 120]]);\n }));\n return _calcLeaveDurationImpl2.apply(this, arguments);\n }\n\n window.TokUI.registerHandler('calcOvertimeDuration', /*#__PURE__*/function () {\n var _ref6 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee8(data, event, element) {\n var scope, now;\n return regeneratorRuntime.wrap(function _callee8$(_context8) {\n while (1) {\n switch (_context8.prev = _context8.next) {\n case 0:\n scope = findNearestTokuiScope(element);\n if (!scope && event && event.target) scope = findNearestTokuiScope(event.target);\n\n if (scope) {\n _context8.next = 4;\n break;\n }\n\n return _context8.abrupt(\"return\");\n\n case 4:\n // ★ 输入去重:change+input 双事件 800ms 内不再重复执行\n now = Date.now();\n\n if (!(scope._calcOvertimeDedup && now - scope._calcOvertimeDedup < 800)) {\n _context8.next = 7;\n break;\n }\n\n return _context8.abrupt(\"return\");\n\n case 7:\n // 防抖:延迟 400ms\n if (scope._calcOvertimeTimer) clearTimeout(scope._calcOvertimeTimer);\n return _context8.abrupt(\"return\", new Promise(function (resolve) {\n scope._calcOvertimeTimer = setTimeout( /*#__PURE__*/(0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee7() {\n return regeneratorRuntime.wrap(function _callee7$(_context7) {\n while (1) {\n switch (_context7.prev = _context7.next) {\n case 0:\n scope._calcOvertimeTimer = null;\n\n if (!scope._calcOvertimeLock) {\n _context7.next = 4;\n break;\n }\n\n resolve();\n return _context7.abrupt(\"return\");\n\n case 4:\n scope._calcOvertimeLock = true;\n _context7.prev = 5;\n scope._calcOvertimeDedup = Date.now();\n _context7.next = 9;\n return _calcOvertimeDurationImpl(element, scope);\n\n case 9:\n _context7.prev = 9;\n scope._calcOvertimeLock = false;\n return _context7.finish(9);\n\n case 12:\n resolve();\n\n case 13:\n case \"end\":\n return _context7.stop();\n }\n }\n }, _callee7, null, [[5,, 9, 12]]);\n })), 400);\n }));\n\n case 9:\n case \"end\":\n return _context8.stop();\n }\n }\n }, _callee8);\n }));\n\n return function (_x14, _x15, _x16) {\n return _ref6.apply(this, arguments);\n };\n }());\n\n function _calcOvertimeDurationImpl(_x17, _x18) {\n return _calcOvertimeDurationImpl2.apply(this, arguments);\n } // ============ 注册外出时长计算 ============\n // 用于外出表单:选择开始/结束时间后,自动调用服务端计算时长\n // DSL: [datetimepicker ... clk:calcGoOutDuration]\n // 参数映射声明在 duration 字段上,与 calcLeaveDuration 同一设计:\n // data-calc-duration-body-dep-startTime:\"startTime\"\n // data-calc-duration-body-dep-endTime:\"endTime\"\n // 实现逻辑:\n // 1. 从 DSL 配置读取所有参数(完全配置驱动)\n // 2. 调用服务端 API 计算时长\n // 3. 回填 duration 字段\n\n\n function _calcOvertimeDurationImpl2() {\n _calcOvertimeDurationImpl2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee13(element, explicitScope) {\n var calcScope, durationEl, durationField, bodyObj, bodyDeps, hasParams, i, attr, suffix, depParam, paramKey, depElId, startTimeStr, endTimeStr, startTimeMs, endTimeMs, endEl, startDate, endDate, isSameDay, calcUrl, json, leaveTime, fieldWrapper, appendEl, children, ci, span, overtimeList, overtimeListEl, form;\n return regeneratorRuntime.wrap(function _callee13$(_context13) {\n while (1) {\n switch (_context13.prev = _context13.next) {\n case 0:\n console.log('[Validator] _calcOvertimeDurationImpl 被调用', element ? element.tagName || element.id || '' : '无element');\n calcScope = explicitScope || findNearestTokuiScope(element);\n\n if (calcScope) {\n _context13.next = 5;\n break;\n }\n\n console.warn('[Validator] calcOvertimeDuration: 未找到 scope');\n return _context13.abrupt(\"return\");\n\n case 5:\n durationEl = queryById('duration', calcScope);\n\n if (durationEl) {\n _context13.next = 9;\n break;\n }\n\n console.warn('[Validator] calcOvertimeDuration: 未找到 duration 字段');\n return _context13.abrupt(\"return\");\n\n case 9:\n // 从 duration 字段的 .tokui-field 包装层读配置\n durationField = durationEl.closest('[data-calc-duration-url]');\n\n if (durationField) {\n _context13.next = 13;\n break;\n }\n\n console.warn('[Validator] calcOvertimeDuration: duration 字段父级未找到 data-calc-duration-url');\n return _context13.abrupt(\"return\");\n\n case 13:\n // 读取请求体参数映射(data-calc-duration-body-dep-{param})\n bodyObj = {};\n bodyDeps = {};\n hasParams = false;\n\n if (!durationField.attributes) {\n _context13.next = 35;\n break;\n }\n\n i = 0;\n\n case 18:\n if (!(i < durationField.attributes.length)) {\n _context13.next = 35;\n break;\n }\n\n attr = durationField.attributes[i];\n\n if (!(!attr.name || attr.name.indexOf('data-calc-duration-body-') !== 0)) {\n _context13.next = 22;\n break;\n }\n\n return _context13.abrupt(\"continue\", 32);\n\n case 22:\n suffix = attr.name.substring('data-calc-duration-body-'.length);\n\n if (suffix) {\n _context13.next = 25;\n break;\n }\n\n return _context13.abrupt(\"continue\", 32);\n\n case 25:\n if (!(suffix.indexOf('dep-') === 0)) {\n _context13.next = 29;\n break;\n }\n\n depParam = normalizeBodyKey(suffix.substring('dep-'.length));\n\n if (depParam) {\n bodyDeps[depParam] = attr.value.trim();\n hasParams = true;\n }\n\n return _context13.abrupt(\"continue\", 32);\n\n case 29:\n paramKey = normalizeBodyKey(suffix);\n bodyObj[paramKey] = attr.value;\n hasParams = true;\n\n case 32:\n i++;\n _context13.next = 18;\n break;\n\n case 35:\n if (hasParams) {\n _context13.next = 38;\n break;\n }\n\n console.warn('[Validator] calcOvertimeDuration: duration 字段上未配置 data-calc-duration-body-dep-* 参数映射,跳过计算');\n return _context13.abrupt(\"return\");\n\n case 38:\n _context13.t0 = regeneratorRuntime.keys(bodyDeps);\n\n case 39:\n if ((_context13.t1 = _context13.t0()).done) {\n _context13.next = 47;\n break;\n }\n\n depParam = _context13.t1.value;\n\n if (bodyDeps.hasOwnProperty(depParam)) {\n _context13.next = 43;\n break;\n }\n\n return _context13.abrupt(\"continue\", 39);\n\n case 43:\n depElId = bodyDeps[depParam];\n bodyObj[depParam] = _readFieldValueById(depElId, calcScope);\n _context13.next = 39;\n break;\n\n case 47:\n if (!(!bodyObj.startTime || !bodyObj.endTime)) {\n _context13.next = 51;\n break;\n }\n\n console.log('[Validator] calcOvertimeDuration: 起止时间不完整', bodyObj.startTime, bodyObj.endTime);\n if (durationEl) durationEl.value = '';\n return _context13.abrupt(\"return\");\n\n case 51:\n // 转换日期 → 毫秒时间戳\n console.log('[Validator] calcOvertimeDuration: bodyObj=', JSON.stringify(bodyObj), 'bodyDeps=', JSON.stringify(bodyDeps));\n startTimeStr = String(bodyObj.startTime);\n endTimeStr = String(bodyObj.endTime);\n if (startTimeStr.length === 10) startTimeStr += 'T00:00:00';else startTimeStr = startTimeStr.replace(' ', 'T');\n if (endTimeStr.length === 10) endTimeStr += 'T00:00:00';else endTimeStr = endTimeStr.replace(' ', 'T');\n startTimeMs = new Date(startTimeStr).getTime();\n endTimeMs = new Date(endTimeStr).getTime();\n\n if (!(isNaN(startTimeMs) || isNaN(endTimeMs))) {\n _context13.next = 60;\n break;\n }\n\n return _context13.abrupt(\"return\");\n\n case 60:\n if (!(endTimeMs < startTimeMs)) {\n _context13.next = 65;\n break;\n }\n\n (0, _message.showMessage)('结束时间不能早于开始时间', 'warning');\n endEl = queryById(bodyDeps.endTime, calcScope);\n if (endEl) endEl.value = '';\n return _context13.abrupt(\"return\");\n\n case 65:\n bodyObj.startTime = startTimeMs;\n bodyObj.endTime = endTimeMs; // 判断是否同一天\n\n startDate = new Date(startTimeMs);\n endDate = new Date(endTimeMs);\n isSameDay = startDate.getFullYear() === endDate.getFullYear() && startDate.getMonth() === endDate.getMonth() && startDate.getDate() === endDate.getDate();\n calcUrl = cleanUrl(durationField.getAttribute('data-calc-duration-url') || '');\n\n if (calcUrl) {\n _context13.next = 74;\n break;\n }\n\n console.warn('[Validator] calcOvertimeDuration: duration 字段上未配置 data-calc-duration-url');\n return _context13.abrupt(\"return\");\n\n case 74:\n console.log('[Validator] calcOvertimeDuration: 开始计算, URL=', calcUrl, 'startTimeMs=', startTimeMs, 'endTimeMs=', endTimeMs, 'isSameDay=', isSameDay);\n _context13.prev = 75;\n // 同天:调用计算时长 API\n console.log('[Validator] calcOvertimeDuration: 同天 API 请求', calcUrl, JSON.stringify(bodyObj));\n _context13.next = 79;\n return _request.default.post(calcUrl, bodyObj);\n\n case 79:\n json = _context13.sent;\n console.log('[Validator] calcOvertimeDuration: 同天 API 响应', json.code, JSON.stringify(json.data));\n\n if (!(json.code === 200 && json.data)) {\n _context13.next = 101;\n break;\n }\n\n leaveTime = json.data.leaveTime || json.data.duration || '0';\n if (durationEl) durationEl.value = String(leaveTime); // 更新单位后缀\n\n fieldWrapper = durationEl.closest('.tokui-field') || durationEl.parentElement;\n\n if (!fieldWrapper) {\n _context13.next = 98;\n break;\n }\n\n appendEl = fieldWrapper.querySelector('.tokui-input-group__append, .tokui-append, .el-input-group__append');\n\n if (appendEl) {\n _context13.next = 97;\n break;\n }\n\n children = fieldWrapper.querySelectorAll('span, div');\n ci = children.length - 1;\n\n case 90:\n if (!(ci >= 0)) {\n _context13.next = 97;\n break;\n }\n\n if (!(children[ci].textContent && children[ci].textContent.trim() && !children[ci].querySelector('input'))) {\n _context13.next = 94;\n break;\n }\n\n appendEl = children[ci];\n return _context13.abrupt(\"break\", 97);\n\n case 94:\n ci--;\n _context13.next = 90;\n break;\n\n case 97:\n if (appendEl) {\n appendEl.textContent = '小时';\n } else {\n span = document.createElement('span');\n span.className = 'tokui-duration-unit';\n span.textContent = '小时';\n span.style.cssText = 'margin-left:6px;color:#909399;font-size:14px;';\n fieldWrapper.appendChild(span);\n }\n\n case 98:\n // 跨天时,将 overtimeList 明细存储到隐藏字段供提交使用\n if (!isSameDay) {\n overtimeList = json.data.dateList || json.data.overTimeList || [];\n\n if (overtimeList.length > 0) {\n overtimeListEl = queryById('overtimeList', calcScope);\n\n if (overtimeListEl) {\n overtimeListEl.value = JSON.stringify(overtimeList);\n }\n }\n }\n\n _context13.next = 102;\n break;\n\n case 101:\n (0, _message.showMessage)(json.msg || json.message || '计算时长失败', 'warning');\n\n case 102:\n _context13.next = 107;\n break;\n\n case 104:\n _context13.prev = 104;\n _context13.t2 = _context13[\"catch\"](75);\n console.warn('[Validator] calcOvertimeDuration API error:', _context13.t2.message);\n\n case 107:\n // 时长计算后刷新审批\n form = calcScope.querySelector('[data-form-id=\"overtimeForm\"]') || queryById('overtimeForm', calcScope) || calcScope.querySelector('[data-form-id]') || calcScope.querySelector('form');\n\n if (form) {\n try {\n refreshApprovalFlow(form);\n } catch (e2) {}\n }\n\n case 109:\n case \"end\":\n return _context13.stop();\n }\n }\n }, _callee13, null, [[75, 104]]);\n }));\n return _calcOvertimeDurationImpl2.apply(this, arguments);\n }\n\n window.TokUI.registerHandler('calcGoOutDuration', /*#__PURE__*/function () {\n var _ref8 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee10(data, event, element) {\n var scope;\n return regeneratorRuntime.wrap(function _callee10$(_context10) {\n while (1) {\n switch (_context10.prev = _context10.next) {\n case 0:\n scope = findNearestTokuiScope(element);\n if (!scope && event && event.target) scope = findNearestTokuiScope(event.target);\n\n if (scope) {\n _context10.next = 4;\n break;\n }\n\n return _context10.abrupt(\"return\");\n\n case 4:\n // 防抖:延迟 400ms\n if (scope._calcGoOutTimer) clearTimeout(scope._calcGoOutTimer);\n return _context10.abrupt(\"return\", new Promise(function (resolve) {\n scope._calcGoOutTimer = setTimeout( /*#__PURE__*/(0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee9() {\n return regeneratorRuntime.wrap(function _callee9$(_context9) {\n while (1) {\n switch (_context9.prev = _context9.next) {\n case 0:\n scope._calcGoOutTimer = null;\n\n if (!scope._calcGoOutLock) {\n _context9.next = 4;\n break;\n }\n\n resolve();\n return _context9.abrupt(\"return\");\n\n case 4:\n scope._calcGoOutLock = true;\n _context9.prev = 5;\n _context9.next = 8;\n return _calcGoOutDurationImpl(element, scope);\n\n case 8:\n _context9.prev = 8;\n scope._calcGoOutLock = false;\n return _context9.finish(8);\n\n case 11:\n resolve();\n\n case 12:\n case \"end\":\n return _context9.stop();\n }\n }\n }, _callee9, null, [[5,, 8, 11]]);\n })), 400);\n }));\n\n case 6:\n case \"end\":\n return _context10.stop();\n }\n }\n }, _callee10);\n }));\n\n return function (_x19, _x20, _x21) {\n return _ref8.apply(this, arguments);\n };\n }());\n\n function _calcGoOutDurationImpl(_x22, _x23) {\n return _calcGoOutDurationImpl2.apply(this, arguments);\n }\n /**\r\n * 从表单字段读取值,兼容 select / input / hidden\r\n * - \"login*\" 前缀值从 window.__tokuiParams 读取\r\n * - 否则在当前作用域内按 id 精确查找(多表单隔离)\r\n */\n\n\n function _calcGoOutDurationImpl2() {\n _calcGoOutDurationImpl2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee14(element, explicitScope) {\n var calcScope, durationEl, durationField, bodyObj, bodyDeps, hasParams, i, attr, suffix, depParam, paramKey, depElId, startTimeStr, endTimeStr, startTimeMs, endTimeMs, endEl, calcUrl, json, goOutTime, fieldWrapper, appendEl, children, ci, span, form;\n return regeneratorRuntime.wrap(function _callee14$(_context14) {\n while (1) {\n switch (_context14.prev = _context14.next) {\n case 0:\n console.log('[Validator] _calcGoOutDurationImpl 被调用', element ? element.tagName || element.id || '' : '无element');\n calcScope = explicitScope || findNearestTokuiScope(element);\n\n if (calcScope) {\n _context14.next = 5;\n break;\n }\n\n console.warn('[Validator] calcGoOutDuration: 未找到 scope');\n return _context14.abrupt(\"return\");\n\n case 5:\n durationEl = queryById('duration', calcScope);\n\n if (durationEl) {\n _context14.next = 9;\n break;\n }\n\n console.warn('[Validator] calcGoOutDuration: 未找到 duration 字段');\n return _context14.abrupt(\"return\");\n\n case 9:\n // 从 duration 字段的 .tokui-field 包装层读配置\n durationField = durationEl.closest('[data-calc-duration-url]');\n\n if (durationField) {\n _context14.next = 13;\n break;\n }\n\n console.warn('[Validator] calcGoOutDuration: duration 字段父级未找到 data-calc-duration-url');\n return _context14.abrupt(\"return\");\n\n case 13:\n // 读取请求体参数映射(data-calc-duration-body-dep-{param})\n bodyObj = {};\n bodyDeps = {};\n hasParams = false;\n\n if (!durationField.attributes) {\n _context14.next = 35;\n break;\n }\n\n i = 0;\n\n case 18:\n if (!(i < durationField.attributes.length)) {\n _context14.next = 35;\n break;\n }\n\n attr = durationField.attributes[i];\n\n if (!(!attr.name || attr.name.indexOf('data-calc-duration-body-') !== 0)) {\n _context14.next = 22;\n break;\n }\n\n return _context14.abrupt(\"continue\", 32);\n\n case 22:\n suffix = attr.name.substring('data-calc-duration-body-'.length);\n\n if (suffix) {\n _context14.next = 25;\n break;\n }\n\n return _context14.abrupt(\"continue\", 32);\n\n case 25:\n if (!(suffix.indexOf('dep-') === 0)) {\n _context14.next = 29;\n break;\n }\n\n depParam = normalizeBodyKey(suffix.substring('dep-'.length));\n\n if (depParam) {\n bodyDeps[depParam] = attr.value.trim();\n hasParams = true;\n }\n\n return _context14.abrupt(\"continue\", 32);\n\n case 29:\n paramKey = normalizeBodyKey(suffix);\n bodyObj[paramKey] = attr.value;\n hasParams = true;\n\n case 32:\n i++;\n _context14.next = 18;\n break;\n\n case 35:\n if (hasParams) {\n _context14.next = 38;\n break;\n }\n\n console.warn('[Validator] calcGoOutDuration: duration 字段上未配置 data-calc-duration-body-dep-* 参数映射,跳过计算');\n return _context14.abrupt(\"return\");\n\n case 38:\n _context14.t0 = regeneratorRuntime.keys(bodyDeps);\n\n case 39:\n if ((_context14.t1 = _context14.t0()).done) {\n _context14.next = 47;\n break;\n }\n\n depParam = _context14.t1.value;\n\n if (bodyDeps.hasOwnProperty(depParam)) {\n _context14.next = 43;\n break;\n }\n\n return _context14.abrupt(\"continue\", 39);\n\n case 43:\n depElId = bodyDeps[depParam];\n bodyObj[depParam] = _readFieldValueById(depElId, calcScope);\n _context14.next = 39;\n break;\n\n case 47:\n if (!(!bodyObj.startTime || !bodyObj.endTime)) {\n _context14.next = 51;\n break;\n }\n\n console.log('[Validator] calcGoOutDuration: 起止时间不完整', bodyObj.startTime, bodyObj.endTime);\n if (durationEl) durationEl.value = '';\n return _context14.abrupt(\"return\");\n\n case 51:\n // 转换日期 → 毫秒时间戳\n startTimeStr = String(bodyObj.startTime);\n endTimeStr = String(bodyObj.endTime);\n if (startTimeStr.length === 10) startTimeStr += 'T00:00:00';else startTimeStr = startTimeStr.replace(' ', 'T');\n if (endTimeStr.length === 10) endTimeStr += 'T00:00:00';else endTimeStr = endTimeStr.replace(' ', 'T');\n startTimeMs = new Date(startTimeStr).getTime();\n endTimeMs = new Date(endTimeStr).getTime();\n\n if (!(isNaN(startTimeMs) || isNaN(endTimeMs))) {\n _context14.next = 59;\n break;\n }\n\n return _context14.abrupt(\"return\");\n\n case 59:\n if (!(endTimeMs < startTimeMs)) {\n _context14.next = 64;\n break;\n }\n\n (0, _message.showMessage)('结束时间不能早于开始时间', 'warning');\n endEl = queryById(bodyDeps.endTime, calcScope);\n if (endEl) endEl.value = '';\n return _context14.abrupt(\"return\");\n\n case 64:\n bodyObj.startTime = startTimeMs;\n bodyObj.endTime = endTimeMs;\n calcUrl = cleanUrl(durationField.getAttribute('data-calc-duration-url') || '');\n\n if (calcUrl) {\n _context14.next = 70;\n break;\n }\n\n console.warn('[Validator] calcGoOutDuration: duration 字段上未配置 data-calc-duration-url');\n return _context14.abrupt(\"return\");\n\n case 70:\n console.log('[Validator] calcGoOutDuration: 开始计算, URL=', calcUrl, JSON.stringify(bodyObj));\n _context14.prev = 71;\n _context14.next = 74;\n return _request.default.post(calcUrl, bodyObj);\n\n case 74:\n json = _context14.sent;\n console.log('[Validator] calcGoOutDuration: API 响应', json.code, JSON.stringify(json.data));\n\n if (!(json.code === 200 && json.data)) {\n _context14.next = 95;\n break;\n }\n\n goOutTime = json.data.leaveTime || json.data.duration || '0';\n if (durationEl) durationEl.value = String(goOutTime); // 更新单位后缀\n\n fieldWrapper = durationEl.closest('.tokui-field') || durationEl.parentElement;\n\n if (!fieldWrapper) {\n _context14.next = 93;\n break;\n }\n\n appendEl = fieldWrapper.querySelector('.tokui-input-group__append, .tokui-append, .el-input-group__append');\n\n if (appendEl) {\n _context14.next = 92;\n break;\n }\n\n children = fieldWrapper.querySelectorAll('span, div');\n ci = children.length - 1;\n\n case 85:\n if (!(ci >= 0)) {\n _context14.next = 92;\n break;\n }\n\n if (!(children[ci].textContent && children[ci].textContent.trim() && !children[ci].querySelector('input'))) {\n _context14.next = 89;\n break;\n }\n\n appendEl = children[ci];\n return _context14.abrupt(\"break\", 92);\n\n case 89:\n ci--;\n _context14.next = 85;\n break;\n\n case 92:\n if (appendEl) {\n appendEl.textContent = '小时';\n } else {\n span = document.createElement('span');\n span.className = 'tokui-duration-unit';\n span.textContent = '小时';\n span.style.cssText = 'margin-left:6px;color:#909399;font-size:14px;';\n fieldWrapper.appendChild(span);\n }\n\n case 93:\n _context14.next = 96;\n break;\n\n case 95:\n (0, _message.showMessage)(json.msg || json.message || '计算时长失败', 'warning');\n\n case 96:\n _context14.next = 101;\n break;\n\n case 98:\n _context14.prev = 98;\n _context14.t2 = _context14[\"catch\"](71);\n console.warn('[Validator] calcGoOutDuration API error:', _context14.t2.message);\n\n case 101:\n // 时长计算后刷新审批\n form = calcScope.querySelector('[data-form-id=\"goOutForm\"]') || queryById('goOutForm', calcScope) || calcScope.querySelector('[data-form-id]') || calcScope.querySelector('form');\n\n if (form) {\n try {\n refreshApprovalFlow(form);\n } catch (e2) {}\n }\n\n case 103:\n case \"end\":\n return _context14.stop();\n }\n }\n }, _callee14, null, [[71, 98]]);\n }));\n return _calcGoOutDurationImpl2.apply(this, arguments);\n }\n\n function _readFieldValueById(elId, scope) {\n if (!elId) return ''; // 确保 elId 是字符串\n\n if (typeof elId !== 'string') elId = String(elId); // 登录参数\n\n if (elId.indexOf('login') === 0) {\n return window.__tokuiParams && window.__tokuiParams[elId] !== undefined ? String(window.__tokuiParams[elId]) : '';\n }\n\n var root = scope && scope.querySelectorAll ? scope : container && container.querySelectorAll ? container : document; // 表单字段:优先取未被条件隐藏的元素(DSL 中可能有多组同 id 元素,如 datetimepicker + datepicker)\n\n var el = queryById(elId, root);\n\n if (el && el.closest('.tokui-conditional-hidden')) {\n // 第一个匹配被隐藏,查找同 id 的其他可见元素\n var allSameId = root.querySelectorAll('[id=\"' + elId + '\"]');\n\n for (var si = 0; si < allSameId.length; si++) {\n if (!allSameId[si].closest('.tokui-conditional-hidden')) {\n el = allSameId[si];\n break;\n }\n }\n }\n\n if (!el) return '';\n\n if (el.tagName === 'SELECT') {\n if (el.selectedIndex >= 0 && el.options[el.selectedIndex]) {\n return el.options[el.selectedIndex].getAttribute('value') || el.options[el.selectedIndex].value || '';\n }\n\n return '';\n }\n\n return el.value || '';\n }\n /**\r\n * 半天模式:拼接 \"YYYY-MM-DD 上午/下午\" 字符串\r\n */\n\n\n function buildHalfDayTimeStr(timestampMs, halfElId, scope) {\n var d = new Date(timestampMs);\n var y = d.getFullYear();\n var m = String(d.getMonth() + 1).padStart(2, '0');\n var day = String(d.getDate()).padStart(2, '0');\n\n var halfVal = _readFieldValueById(halfElId, scope);\n\n var halfLabel = {\n 1: '上午',\n 2: '下午'\n }[halfVal] || '上午';\n return y + '-' + m + '-' + day + ' ' + halfLabel;\n } // ============ 注册暂存处理 ============\n // DSL: [btn tx:暂存 clk:saveDraft]\n\n\n window.TokUI.registerHandler('saveDraft', /*#__PURE__*/function () {\n var _ref10 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee11(data, event, element) {\n var form, allData, submitUrl;\n return regeneratorRuntime.wrap(function _callee11$(_context11) {\n while (1) {\n switch (_context11.prev = _context11.next) {\n case 0:\n form = resolveForm(element);\n\n if (form) {\n _context11.next = 3;\n break;\n }\n\n return _context11.abrupt(\"return\");\n\n case 3:\n if (!form._tvSubmitting) {\n _context11.next = 6;\n break;\n }\n\n (0, _message.showMessage)('正在处理中,请稍候...', 'warning');\n return _context11.abrupt(\"return\");\n\n case 6:\n form._tvSubmitting = true;\n allData = collectFormData(element); // 清理隐藏辅助字段\n\n delete allData._holidayType;\n delete allData._leaveType;\n delete allData._balanceMode;\n delete allData._durationDay; // 暂存标记\n\n allData.saveFlag = 0;\n submitUrl = form.getAttribute('data-url');\n\n if (submitUrl) {\n _context11.next = 18;\n break;\n }\n\n form._tvSubmitting = false;\n (0, _message.showMessage)('暂存失败:未配置提交地址', 'error');\n return _context11.abrupt(\"return\");\n\n case 18:\n safeFeed('[callout t:info tt:暂存中 tx:正在保存草稿...]', form);\n _context11.prev = 19;\n _context11.next = 22;\n return _request.default.post(submitUrl, allData);\n\n case 22:\n (0, _message.showMessage)('草稿已保存', 'success');\n safeFeed('[callout t:success tt:保存成功 tx:\"草稿已保存\"]', form);\n _context11.next = 28;\n break;\n\n case 26:\n _context11.prev = 26;\n _context11.t0 = _context11[\"catch\"](19);\n\n case 28:\n _context11.prev = 28;\n form._tvSubmitting = false;\n return _context11.finish(28);\n\n case 31:\n case \"end\":\n return _context11.stop();\n }\n }\n }, _callee11, null, [[19, 26, 28, 31]]);\n }));\n\n return function (_x24, _x25, _x26) {\n return _ref10.apply(this, arguments);\n };\n }());\n } // end window.TokUI.__validatorHandlersBound 全局守卫\n // ============ DOM 事件委托校验(data-trigger) ============\n\n\n function handleTriggerEvent(e) {\n var el = e.target.hasAttribute('data-trigger') ? e.target : e.target.closest('[data-trigger]');\n if (!el) return;\n var triggers = (el.getAttribute('data-trigger') || '').split(',').map(function (t) {\n return t.trim();\n });\n if (!triggers.includes(e.type)) return;\n var key = el.id || el.name || String(Math.random());\n\n if (DEBOUNCED_EVENTS.includes(e.type)) {\n cancelDebounce(key);\n debounce(key, function () {\n return runFieldValidation(el);\n }, debounceMs);\n } else {\n cancelDebounce(key);\n runFieldValidation(el);\n }\n }\n\n TRIGGER_EVENTS.forEach(function (evt) {\n container.addEventListener(evt, handleTriggerEvent, true);\n }); // ============ change 事件委托:触发 clk:* 处理器 ============\n // datepicker/select 等组件选择值后触发 change 事件,但 TokUI 的 clk:\n // 只在 click 时触发,导致 calcLeaveDuration 等处理器不执行。\n // 这里在容器上代理 change 事件,查找元素及其祖先上的 clk: 属性并调用对应 handler。\n\n function handleClkOnChange(e) {\n // 从事件目标向上查找带 data-tokui-clk 属性的元素\n // (patch.js 将 DSL 的 clk: 透传为 DOM 上的 data-tokui-clk)\n var target = e.target;\n var matched = false;\n\n while (target && target !== container) {\n if (target.hasAttribute && target.getAttribute) {\n var clkVal = target.getAttribute('data-tokui-clk');\n\n if (clkVal) {\n clkVal.split(',').forEach(function (name) {\n name = name.trim();\n if (!name) return;\n var handler = _clkHandlers[name];\n\n if (typeof handler === 'function') {\n try {\n handler({}, e, target);\n } catch (ex) {\n console.warn('[Validator] clk handler error:', name, ex);\n }\n }\n });\n matched = true;\n break;\n } // 也检查父级 .tokui-field 上的 data-tokui-clk\n\n\n if (target.closest) {\n var field = target.closest('.tokui-field');\n\n if (field && field !== target) {\n var fieldClk = field.getAttribute('data-tokui-clk');\n\n if (fieldClk) {\n fieldClk.split(',').forEach(function (name) {\n name = name.trim();\n if (!name) return;\n var handler = _clkHandlers[name];\n\n if (typeof handler === 'function') {\n try {\n handler({}, e, field);\n } catch (ex) {\n console.warn('[Validator] clk handler error:', name, ex);\n }\n }\n });\n matched = true;\n break;\n }\n }\n }\n }\n\n target = target.parentElement;\n } // ★ fallback:未匹配 clk,但 change 源在 data-calc-duration-url 表单内 → 自动触发时长计算\n // 解决 TokUI datetimepicker 内部 change 事件不冒泡到 container 的问题\n\n\n if (!matched && e.target) {\n var calcField = e.target.closest('[data-calc-duration-url]');\n\n if (!calcField) {\n // 尝试从 .tokui-field 或 form 内查找\n var calcForm = e.target.closest('[data-form-id]') || e.target.closest('form') || e.target.closest('.tokui-stream');\n\n if (calcForm) {\n calcField = calcForm.querySelector('[data-calc-duration-url]');\n }\n }\n\n if (calcField) {\n var calcUrl = calcField.getAttribute('data-calc-duration-url');\n\n if (calcUrl) {\n // ★ fallback 只调用 calcLeaveDuration:data-calc-duration-body-dep-* 参数映射\n // 是为请假/外出设计的,加班表单有独立的 clk:calcOvertimeDuration 直接匹配。\n // 同时 calcOvertimeDuration 内部校验逻辑不同,传错参数会报警。\n var leaveHandler = _clkHandlers['calcLeaveDuration'];\n\n if (typeof leaveHandler === 'function') {\n try {\n leaveHandler({}, e, calcField);\n } catch (ex) {\n console.warn('[Validator] calcLeaveDuration fallback error:', ex);\n }\n }\n }\n }\n }\n } // 在容器上监听 change 事件(捕获阶段,确保优先处理)\n\n\n container.addEventListener('change', handleClkOnChange, true); // ★ 补充 input 事件监听:TokUI datetimepicker 值变化可能只触发 input 不触发 change\n\n container.addEventListener('input', handleClkOnChange, true); // ============ click 事件委托:处理 sub:formSubmit / sub:formReset / clk:* ============\n // TokUI 内部的按钮点击机制可能未正确调用 registerHandler 注册的 handler,\n // 这里在容器上代理 click 事件,确保 sub: 和 clk: 属性的按钮能正确触发。\n\n container.addEventListener('click', function (e) {\n var target = e.target; // 向上查找 button 元素或带 data-tokui-sub / data-tokui-clk 的元素\n\n while (target && target !== container) {\n if (target.hasAttribute && target.getAttribute) {\n // ★★ 处理 TokUI 原生 reset 按钮([btn tx:重置 reset] → <button type=\"reset\">)\n // 这种按钮没有 data-tokui-sub/clk 属性,需单独处理\n if (target.tagName === 'BUTTON' && target.getAttribute('type') === 'reset') {\n var resetHandler = _clkHandlers['formReset'] || _clkHandlers['doReset'];\n\n if (typeof resetHandler === 'function') {\n e.preventDefault();\n e.stopPropagation();\n\n try {\n resetHandler({}, e, target);\n } catch (ex) {\n console.warn('[Validator] reset handler error:', ex);\n }\n\n return;\n }\n } // 检查 sub: 属性(TokUI 可能透传为 data-tokui-sub 或 sub)\n\n\n var subVal = target.getAttribute('data-tokui-sub') || target.getAttribute('sub') || '';\n\n if (subVal) {\n var subHandler = _clkHandlers[subVal];\n\n if (typeof subHandler === 'function') {\n e.preventDefault();\n e.stopPropagation();\n\n try {\n subHandler({}, e, target);\n } catch (ex) {\n console.warn('[Validator] sub handler error:', subVal, ex);\n }\n\n return;\n }\n } // 检查 clk: 属性(通过 data-tokui-clk 透传)\n\n\n var clkVal = target.getAttribute('data-tokui-clk') || '';\n\n if (clkVal) {\n clkVal.split(',').forEach(function (name) {\n name = name.trim();\n if (!name) return;\n var handler = _clkHandlers[name];\n\n if (typeof handler === 'function') {\n try {\n handler({}, e, target);\n } catch (ex) {\n console.warn('[Validator] clk handler error:', name, ex);\n }\n }\n }); // 不 return,让 TokUI 原有处理也执行\n }\n }\n\n target = target.parentElement;\n }\n }, true); // ============ 数据预加载(data-load / data-load-list) ============\n //\n // 两种模式,共用 $字段名$ 语法:\n //\n // ★ 详情模式(data-load) — 取单条对象,替换 DOM 中的 $字段名$\n // [card id:detail data-load:\"/api/user/1\"]\n // [h3 \"$title$\"]\n // [p \"姓名: $name$\"]\n // [p \"邮箱: $email$\"]\n // [img s:\"$avatar$\"]\n // [/card]\n //\n // ★ 列表模式(data-load-list)— 取数组,按 [repeat] 模板重复渲染\n // [div id:cardList data-load-list:\"/api/users\"]\n // [repeat]\n // [card tt:\"$name$\" tx:\"$dept$\"]\n // [p \"$intro$\"]\n // [btn tx:\"编辑\" clk:\"edit-$id$\"]\n // [/card]\n // [/repeat]\n // [/div]\n //\n // ★ Select 兼容 — 列表模式特例,直接填充 <option>\n // [select id:city data-load:\"/api/cities\"]\n // [repeat][opt v:\"$code$\" tx:\"$name$\"][/repeat]\n // [/select]\n //\n // 注意:[repeat] 通过 patchTokUIParser() 注册为原始内容容器,不修改核心源码。\n // =========================================================\n // ---------- JSON 解析工具 ----------\n\n function resolveList(json) {\n var raw = json.data !== undefined ? json.data : json;\n if (Array.isArray(raw)) return raw;\n\n if (raw && (0, _typeof2.default)(raw) === 'object') {\n for (var _i = 0, _Object$keys = Object.keys(raw); _i < _Object$keys.length; _i++) {\n var key = _Object$keys[_i];\n if (Array.isArray(raw[key])) return raw[key];\n }\n }\n\n return [];\n }\n\n function resolveObject(json) {\n var raw = json.data !== undefined ? json.data : json;\n return raw && (0, _typeof2.default)(raw) === 'object' && !Array.isArray(raw) ? raw : raw;\n }\n\n var mapOptions = options.buildOptions || function (json) {\n var list = resolveList(json);\n return list.map(function (item) {\n // v 字段:优先 v,然后尝试 code/id/value/no/holidayNo 等常见字段\n var vKeys = ['v', 'code', 'id', 'value', 'no', 'No', 'NO', 'holidayNo', 'holidayId', 'userNo', 'companyNo', 'departNo', 'departmentNo'];\n var txKeys = ['tx', 'name', 'label', 'text', 'holidayName', 'companyName', 'departName', 'departmentName'];\n var v = '';\n var tx = '';\n\n for (var _i2 = 0, _vKeys = vKeys; _i2 < _vKeys.length; _i2++) {\n var k = _vKeys[_i2];\n\n if (item[k] !== undefined && item[k] !== null && item[k] !== '') {\n v = item[k];\n break;\n }\n }\n\n for (var _i3 = 0, _txKeys = txKeys; _i3 < _txKeys.length; _i3++) {\n var _k = _txKeys[_i3];\n\n if (item[_k] !== undefined && item[_k] !== null && item[_k] !== '') {\n tx = item[_k];\n break;\n }\n } // 如果仍然没有匹配到,从所有字段中取第一个非空值\n\n\n if (!v) {\n for (var _k2 in item) {\n if (item[_k2] !== undefined && item[_k2] !== null && item[_k2] !== '') {\n v = item[_k2];\n break;\n }\n }\n }\n\n if (!tx) {\n for (var _k3 in item) {\n if (item[_k3] !== undefined && item[_k3] !== null && item[_k3] !== '' && item[_k3] !== v) {\n tx = item[_k3];\n break;\n }\n }\n }\n\n return {\n v: v !== undefined && v !== null ? String(v) : '',\n tx: tx !== undefined && tx !== null ? String(tx) : ''\n };\n });\n };\n /**\r\n * 清除 TokUI DSL 解析后残留在 URL 值上的反引号与空格\r\n * DSL 中 data-load:\" `https://...` \" → 解析后 DOM 属性值为 `https://...`\r\n * 需要去掉首尾反引号才是有效 URL\r\n */\n\n\n function cleanUrl(raw) {\n return String(raw || '').trim().replace(/^`/, '').replace(/`$/, '');\n }\n\n function resolveUrl(tmpl, scope) {\n tmpl = cleanUrl(tmpl);\n var root = scope && scope.querySelectorAll ? scope : container && container.querySelectorAll ? container : document;\n return tmpl.replace(/\\$\\{(\\w+)\\}/g, function (_, id) {\n // 优先查 DOM 元素(前端级联引用,如 ${companyNo}),限定在当前表单容器避免跨消息串扰\n var el = queryById(id, root);\n\n if (el) {\n // <select>:始终取选中 option 的 value 属性,避免浏览器返回 innerText 的差异\n if (el.tagName === 'SELECT' && el.selectedIndex >= 0 && el.options[el.selectedIndex]) {\n return el.options[el.selectedIndex].getAttribute('value') || el.options[el.selectedIndex].value || '';\n }\n\n return el.value || '';\n } // 其次直接从 getAuth() 取值(loginUserNo / loginCompanyNo),始终读取最新值\n\n\n if (id === 'loginUserNo' || id === 'loginCompanyNo') {\n var auth = (0, _request.getAuth)();\n if (id === 'loginUserNo') return auth.userno || auth.user || '';\n if (id === 'loginCompanyNo') return auth.companyNo || '';\n } // 再查全局参数映射兜底\n\n\n if (window.__tokuiParams && window.__tokuiParams[id] !== undefined) {\n return String(window.__tokuiParams[id]);\n } // 最后查前端环境变量(src/lib/env/index.js 自动按域名匹配)\n\n\n try {\n var env = window.__tokuiEnv || null;\n\n if (!env) {\n // 动态引入避免循环依赖\n var envModule = window.__TOKUI_ENV_CACHE;\n\n if (!envModule) {\n // 通过 require 或全局挂载获取,这里用 script 引入方式:env 已挂载到 window\n env = window.__tokuiEnv;\n } else {\n env = envModule;\n }\n }\n\n if (env && env[id] !== undefined) {\n return String(env[id]);\n }\n } catch (e) {// ignore\n }\n\n return '';\n });\n } // ============ 审批流程集成(Approval Flow) ============\n // 当表单配置 data-approval-type 时,采用三阶段架构:\n // 1. initApprovalFlow → processIsOpen(获取审批状态和变量列表)\n // 2. refreshApprovalFlow → processPreview(字段变更时重新预览审批节点)+ 可视化渲染\n // 3. saveApprovalFlowState → processSave(提交时保存审批配置,获取 uniqueId)\n //\n // DSL 配置示例(form 标签上):\n // data-approval-type:\"leave\"\n // data-approval-prefix:\"https://api.qgbtech.cn/sgb-meta-design\"\n // data-approval-variable-map:\"type:type,departNo:departmentNo,durationDay:durationDay\"\n // 审批 handler 全局只注册一次,但 setupValidator 可能为多个消息容器重复调用。\n // 状态必须跨 setupValidator 闭包共享,否则界面选择与全局提交 handler 会读取不同 WeakMap。\n\n\n var _approvalStates = window.__tokuiApprovalStates = window.__tokuiApprovalStates || new WeakMap();\n\n function getApprovalState(form) {\n if (!_approvalStates.has(form)) {\n var approvalType = (form.getAttribute('data-approval-type') || '').trim();\n var approvalPrefix = cleanUrl(form.getAttribute('data-approval-prefix') || ''); // 解析 variable-map\n\n var variableMap = {};\n var variableMapStr = (form.getAttribute('data-approval-variable-map') || '').trim();\n\n if (variableMapStr) {\n variableMapStr.split(',').forEach(function (s) {\n var parts = s.trim().split(':');\n if (parts.length >= 2) variableMap[parts[0].trim()] = parts[1].trim();\n });\n }\n\n _approvalStates.set(form, {\n _form: form,\n approvalType: approvalType,\n approvalPrefix: approvalPrefix,\n variableMap: variableMap,\n approvaStatus: -1,\n variableList: [],\n processIdFromOpen: '',\n processId: '',\n nextNode: '',\n conditionMap: {},\n processList: [],\n // 用户手工选择的审批人独立于 processPreview,避免异步刷新覆盖选择结果\n selectedApprovers: {},\n container: null,\n inited: false\n });\n }\n\n return _approvalStates.get(form);\n }\n\n function getApproverStateKey(node, nodeIndex) {\n // processPreview 可能为同一节点返回不同 nodeId/id,使用稳定的节点序号和名称匹配\n return String(nodeIndex) + ':' + (node && (node.nodeName || node.nodeId || node.id || ''));\n }\n\n function rememberSelectedApprovers(state, node, nodeIndex) {\n if (!state || !node) return;\n var approvers = (node.approvers || []).map(function (approver) {\n return Object.assign({}, approver);\n });\n state.selectedApprovers[getApproverStateKey(node, nodeIndex)] = approvers;\n }\n\n function restoreSelectedApprovers(state, processList) {\n if (!state || !processList) return;\n processList.forEach(function (node, nodeIndex) {\n var key = getApproverStateKey(node, nodeIndex);\n\n if (Object.prototype.hasOwnProperty.call(state.selectedApprovers, key)) {\n node.approvers = state.selectedApprovers[key].map(function (approver) {\n return Object.assign({}, approver);\n });\n }\n });\n }\n\n function syncSelectedApproversToProcessList(state) {\n if (!state || !state.processList) return;\n restoreSelectedApprovers(state, state.processList);\n }\n\n function buildProcessSaveList(state) {\n if (!state || !state.processList) return [];\n syncSelectedApproversToProcessList(state);\n return state.processList.map(function (node) {\n var saveNode = Object.assign({}, node);\n\n if (Array.isArray(node.approvers)) {\n saveNode.approvers = node.approvers.map(function (approver) {\n return Object.assign({}, approver);\n });\n }\n\n return saveNode;\n });\n } // 从表单构建 typeList(用于 processPreview)\n\n\n function buildTypeListFromForm(form, state) {\n var auth = (0, _request.getAuth)();\n var companyNo = auth.companyNo || '';\n var userno = auth.userno || auth.user || '';\n var allData = collectFormData(form);\n var typeScope = resolveScope(form);\n var typeList = {}; // 按 variableMap 映射表单字段 → 审批变量\n\n Object.keys(state.variableMap).forEach(function (formFieldId) {\n var approvalVarName = state.variableMap[formFieldId];\n var el = queryById(formFieldId, typeScope); // 回退:durationDay 可能是隐藏字段 _durationDay\n\n if (!el && (formFieldId === 'durationDay' || formFieldId === '_durationDay')) {\n el = queryById('_durationDay', typeScope) || queryById('durationDay', typeScope);\n } // ★ 部门字段:各表单 DSL 的部门 select id 可能是 departNo / departmentNo / applyDeptId 之一,\n // 需跨候选 id 查找,避免外出表单(applyDeptId)取不到部门值\n\n\n if (!el && (formFieldId === 'departNo' || formFieldId === '_departNo' || formFieldId === 'departmentNo' || formFieldId === '_departmentNo')) {\n el = queryById('applyDeptId', typeScope) || queryById('_applyDeptId', typeScope) || queryById('departmentNo', typeScope) || queryById('_departmentNo', typeScope) || queryById('departNo', typeScope) || queryById('_departNo', typeScope);\n }\n\n var val = '';\n\n if (el) {\n if (el.tagName === 'SELECT' && el.selectedIndex >= 0 && el.options[el.selectedIndex]) {\n val = el.options[el.selectedIndex].getAttribute('value') || el.options[el.selectedIndex].value || '';\n } else {\n val = el.value || '';\n }\n }\n\n if (!val && allData[formFieldId] !== undefined) val = String(allData[formFieldId]); // 回退:也尝试 _ 前缀版本的 hidden 字段\n\n if (!val && formFieldId.indexOf('_') !== 0) {\n var hiddenVal = allData['_' + formFieldId];\n if (hiddenVal !== undefined) val = String(hiddenVal);\n }\n\n typeList[approvalVarName] = val;\n }); // 确保必有字段\n\n if (!typeList.proposer) typeList.proposer = userno;\n if (!typeList.companyId) typeList.companyId = companyNo;\n if (!typeList.userId) typeList.userId = userno; // ★ 对齐源项目 leaveApply.vue / goOutApplyAdd.vue 的取值:\n // departmentNo = 部门值(departNo / departmentNo / applyDeptId),为空时回退 companyNo(申请单位)\n\n if (!typeList.departmentNo) {\n typeList.departmentNo = allData.applyDeptId || allData.departNo || allData.departmentNo || allData.companyNo || '';\n }\n\n return typeList;\n } // ============ 审批字段变更监听辅助 ============\n // TokUI 的 datetimepicker/datepicker/picker 等组件在确认时触发 change 事件,\n // 但事件目标往往是内部只读 input(无 id),真正的字段 id 在其兄弟隐藏 input 上。\n // 因此不能只依赖 e.target.id,需在事件目标祖先与所属组件容器内定位相关字段。\n\n\n function getApprovalRelevantIds(state) {\n var ids = ['type', 'departNo', 'departmentNo', 'startTime', 'endTime', 'startTimeHalf', 'endTimeHalf', 'duration', 'durationDay', '_durationDay'];\n Object.keys(state.variableMap).forEach(function (f) {\n if (ids.indexOf(f) < 0) ids.push(f);\n if (ids.indexOf('_' + f) < 0) ids.push('_' + f);\n });\n return ids;\n }\n\n function findRelevantFieldId(target, state) {\n if (!target) return '';\n var scope = resolveScope(target);\n var ids = getApprovalRelevantIds(state); // 1) 从事件目标逐级向上找匹配的 id/name\n\n var node = target;\n\n while (node && node !== scope && node !== document) {\n if (node.id && ids.indexOf(node.id) >= 0) return node.id;\n if (node.name && ids.indexOf(node.name) >= 0) return node.name;\n node = node.parentElement;\n } // 2) 在事件目标所属组件容器内查找相关字段(覆盖 datepicker 内部只读 input 无 id 的情况)\n\n\n var comp = target.closest ? target.closest('.tokui-datepicker,.tokui-datetimepicker,.tokui-timepicker,.tokui-picker,.tokui-select,.tokui-field') : null;\n\n if (comp) {\n for (var i = 0; i < ids.length; i++) {\n if (comp.querySelector('#' + ids[i]) || comp.querySelector('[name=\"' + ids[i] + '\"]')) return ids[i];\n }\n }\n\n return '';\n } // ============ 表单就绪检测(审批流程前置门槛) ============\n // SSE 流式渲染是增量构建 DOM 的,[form data-approval-type] 先于 btngroup、\n // 时间选择器、下拉选项等出现。若在 DOM 半成品上初始化审批流程,会:\n // 1) processPreview 读不到完整 typeList,甚至 collectFormData 报错;\n // 2) renderApprovalFlow 找不到 btngroup,把审批节点插入错误位置。\n // 因此审批初始化/预览必须以「表单完全渲染」为前提。\n\n\n function isFormReady(form) {\n var scope = resolveScope(form) || form;\n if (!scope || !scope.getAttribute) return false; // 显式标记优先:ChatDetail endStream + reconcile 后调用 markFormReady 设置\n\n if (scope.getAttribute('data-tokui-ready') === '1') return true; // 仅在无 scope 标记(非 ChatDetail 直接调用 setupValidator)时才启用启发式回退,\n // 避免 SSE 流式渲染期间按钮刚出现就被误判为「已就绪」而提前初始化审批。\n\n if (!scope.getAttribute('data-tokui-scope')) {\n return !!scope.querySelector('[data-tokui-sub=\"formSubmit\"], button');\n }\n\n return false;\n } // ChatDetail._reconcileAfterStream 在 endStream 且所有对齐工作完成后调用,\n // 标记表单就绪,并据此初始化 / 刷新审批流程。\n\n\n function markFormReady(scope) {\n if (!scope || !scope.setAttribute) return;\n scope.setAttribute('data-tokui-ready', '1');\n\n try {\n var forms = scope.querySelectorAll('[data-approval-type]');\n forms.forEach(function (el) {\n var form = el.closest('form') || el.closest('[data-form-id]') || el;\n if (!form) return; // 停止可能存在的就绪轮询,避免与本次初始化重复\n\n form._approvalReadyWaiter = false;\n var st = getApprovalState(form);\n\n if (!st.inited) {\n initApprovalFlow(form);\n } else {\n // 已初始化:字段回填完成后再次刷新审批预览\n refreshApprovalFlow(form);\n }\n });\n } catch (e) {\n console.warn('[Approval] markFormReady error:', e.message);\n }\n\n bindPickerSafeArea();\n } // ============ Picker 弹层安全区守卫(H5):跟随字段,安全距离不足才回弹 ============\n // tokui 的日期/时间选择弹层为 position:fixed,由 tokui 按触发字段原生定位(跟随)。\n // 但当字段贴近屏幕底部/右侧时,弹层会超出安全区被遮挡。此处不改变默认跟随定位,\n // 只在弹层实际超出 底部/右侧安全区 时才用 JS 回弹贴合安全区,其余情况维持跟随。\n\n\n function getSafeInset(name, def) {\n try {\n var v = getComputedStyle(document.documentElement).getPropertyValue(name);\n var n = parseFloat(v);\n return isNaN(n) ? def : n;\n } catch (e) {\n return def;\n }\n }\n\n function clampPickerDropdown() {\n var sel = '.tokui-datepicker-dropdown,.tokui-timepicker-dropdown,.tokui-datetimepicker-dropdown';\n var nodes = document.querySelectorAll(sel);\n if (!nodes.length) return; // ★ 弹层打开期间隐藏聊天界面的\"向下箭头\"滚动按钮(.scroll-bottom-wrap / .scroll-btns):\n // 它在 .chat-body(overflow-y:auto) 内是 absolute/sticky+z-index:99,\n // 与弹层同处 .chat-body 堆叠上下文,在 iOS 上会叠在弹层\"确定\"按钮上方遮挡点击。\n // 弹层关闭后(display:none)恢复显示。\n\n var anyVisible = false;\n\n for (var _v = 0; _v < nodes.length; _v++) {\n if (nodes[_v].style.display !== 'none' && nodes[_v].offsetHeight > 0) {\n anyVisible = true;\n break;\n }\n }\n\n var scrollBtns = document.querySelectorAll('.scroll-bottom-wrap,.scroll-btns');\n\n for (var _s = 0; _s < scrollBtns.length; _s++) {\n scrollBtns[_s].style.display = anyVisible ? 'none' : '';\n }\n\n var vw = window.innerWidth || document.documentElement.clientWidth; // 用布局视口高度(clientHeight)而非 innerHeight:position:fixed 弹层是贴合\n // clientHeight 对齐渲染的。iOS 上 innerHeight 会把工具栏/底栏也算进去导致比\n // 实际可视区高,据此判定会漏掉\"底部已超界\",弹层仍被遮挡;安卓两者通常相等。\n\n var vh = document.documentElement.clientHeight || window.innerHeight;\n var safeB = getSafeInset('safe-area-inset-bottom', 12);\n var safeR = getSafeInset('safe-area-inset-right', 4);\n var safeT = getSafeInset('safe-area-inset-top', 0);\n var maxBottom = vh - safeB - 6;\n var minTop = safeT + 6;\n var maxRight = vw - safeR - 6; // ★ 底部聊天输入框/发送框(.chat-footer)占据屏底:弹层\"确定\"按钮若只贴安全区,\n // 仍会与该输入框重叠。需要把弹层底边收敛到聊天底栏的上沿之上。\n\n var vBarTop = vh;\n var vBars = document.querySelectorAll('.chat-footer');\n\n for (var vb = 0; vb < vBars.length; vb++) {\n var vbRect = vBars[vb].getBoundingClientRect(); // 仅在底栏当前处于可视区(bottom 在屏内)时参与计算,否则退化为全屏判定\n\n if (vbRect.top >= 0 && vbRect.bottom > 0 && vbRect.top < vBarTop) {\n vBarTop = vbRect.top;\n }\n }\n\n if (vBarTop <= minTop) vBarTop = vh;\n var bottomLimit = Math.min(vh - safeB - 6, vBarTop - 6); // ★ 顶部聊天标题栏(.chat-header)占据屏顶:弹层向上展开时顶部会被它遮住。\n // 需要把弹层顶边收敛到聊天顶栏的下沿之下。\n\n var vHeaderBottom = 0;\n var vHeaders = document.querySelectorAll('.chat-header');\n\n for (var vhIdx = 0; vhIdx < vHeaders.length; vhIdx++) {\n var vhRect = vHeaders[vhIdx].getBoundingClientRect();\n\n if (vhRect.top >= 0 && vhRect.bottom > 0 && vhRect.bottom > vHeaderBottom) {\n vHeaderBottom = vhRect.bottom;\n }\n }\n\n var topLimit = Math.max(minTop, vHeaderBottom + 6);\n var availH = bottomLimit - topLimit; // 上下栏之间的可用高度\n\n for (var i = 0; i < nodes.length; i++) {\n var el = nodes[i];\n if (!el.style || el.style.display === 'none' || el.offsetHeight === 0) continue; // ★ iOS 稳健兜底:显式固定弹层并抬到最上层(弹层底部不被底部输入框/发送框 DOM 遮盖)\n\n el.style.position = 'fixed';\n el.style.zIndex = '99999';\n var h = el.offsetHeight;\n var r = el.getBoundingClientRect();\n var overBottom = r.bottom > bottomLimit;\n var overTop = r.top < topLimit;\n var overRight = r.right > maxRight;\n\n if (overBottom || overTop) {\n // 弹层高度超过上下栏之间的可用空间 → 强制贴顶栏下沿并内部滚动\n if (h >= availH) {\n el.style.top = topLimit + 'px';\n el.style.bottom = 'auto';\n el.style.maxHeight = availH + 'px';\n el.style.overflowY = 'auto';\n continue;\n } // 场景1:字段下方放不下,但上方有足够空间 → 改为向上展开(弹层底部贴近字段上沿)\n\n\n var upwardY = r.top - h - 6;\n\n if (upwardY >= topLimit && !overTop && upwardY < r.top) {\n el.style.top = upwardY + 'px';\n el.style.bottom = 'auto';\n el.style.maxHeight = '';\n } else {\n // 场景2:上下空间都不足 → 贴到底栏(或安全区)之上,保证\"确定\"按钮完全在屏内、\n // 且不被底部输入框/发送框遮挡\n el.style.top = 'auto';\n el.style.bottom = vh - bottomLimit + 'px'; // 若高度仍超可视区:回顶栏下沿对齐并允许弹层内部滚动\n\n var r2 = el.getBoundingClientRect();\n\n if (r2.top < topLimit) {\n el.style.bottom = 'auto';\n el.style.top = topLimit + 'px';\n el.style.maxHeight = availH + 'px';\n el.style.overflowY = 'auto';\n }\n }\n } // 超出右侧安全边界 → 贴右侧安全区内\n\n\n if (overRight) {\n el.style.left = 'auto';\n el.style.right = Math.max(6, safeR + 6) + 'px';\n r = el.getBoundingClientRect();\n if (r.right > maxRight) el.style.transform = 'none';\n }\n }\n }\n\n function bindPickerSafeArea() {\n if (window._tokuiPickerSafeBound) return;\n window._tokuiPickerSafeBound = true;\n if (typeof MutationObserver === 'undefined') return;\n var clamp = clampPickerDropdown;\n var _clampT = null; // 弹层打开带 0.3s 淡入动画,首帧 offsetHeight 可能为 0 被跳过,\n // 故在多个时间点各夹一次,确保以\"最终布局尺寸\"判定超界并最终落位在屏内\n\n function schedule() {\n clamp();\n clearTimeout(_clampT);\n _clampT = setTimeout(function () {\n clamp();\n setTimeout(clamp, 220); // 0.3s 淡入结束后再夹一次,收敛最终位置\n }, 120);\n }\n\n var obs = new MutationObserver(schedule);\n\n try {\n obs.observe(document.body, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['style', 'class']\n });\n } catch (e) {\n /* 观察失败则退化为仅监听滚动/缩放 */\n }\n\n window.addEventListener('resize', schedule);\n window.addEventListener('orientationchange', schedule);\n window.addEventListener('scroll', schedule, true);\n } // ============ 阶段 1:initApprovalFlow — 表单加载时初始化 ============\n\n\n function initApprovalFlow(_x27) {\n return _initApprovalFlow.apply(this, arguments);\n } // ============ 阶段 2:refreshApprovalFlow — 字段变更时重新预览 ============\n\n\n function _initApprovalFlow() {\n _initApprovalFlow = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee22(form) {\n var state, _readyTries, auth, companyNo, processIsOpenUrl, openJson, openData, _approvalDebounceTimer, scheduleRefresh, _deptFieldIds, _deptScope, _dfi, _dfEl, _approvalScope, _approvalObs, mapKeys;\n\n return regeneratorRuntime.wrap(function _callee22$(_context22) {\n while (1) {\n switch (_context22.prev = _context22.next) {\n case 0:\n state = getApprovalState(form);\n\n if (!state.inited) {\n _context22.next = 3;\n break;\n }\n\n return _context22.abrupt(\"return\");\n\n case 3:\n if (state.approvalType) {\n _context22.next = 5;\n break;\n }\n\n return _context22.abrupt(\"return\");\n\n case 5:\n if (isFormReady(form)) {\n _context22.next = 8;\n break;\n }\n\n if (!form._approvalReadyWaiter) {\n form._approvalReadyWaiter = true;\n _readyTries = 0;\n\n (function waitReady() {\n if (!form._approvalReadyWaiter) return; // 已被 markFormReady 接管\n\n if (isFormReady(form)) {\n form._approvalReadyWaiter = false;\n initApprovalFlow(form);\n return;\n }\n\n if (++_readyTries > 100) {\n // 约 10s 兜底\n form._approvalReadyWaiter = false;\n console.warn('[Approval] 表单长时间未就绪,跳过审批初始化:', form.id || form.getAttribute('data-form-id'));\n return;\n }\n\n setTimeout(waitReady, 100);\n })();\n }\n\n return _context22.abrupt(\"return\");\n\n case 8:\n state.inited = true;\n auth = (0, _request.getAuth)();\n companyNo = auth.companyNo || '';\n _context22.prev = 11;\n processIsOpenUrl = state.approvalPrefix + '/v1/pc/process/processIsOpen';\n _context22.next = 15;\n return _request.default.post(processIsOpenUrl, {\n companyNo: companyNo,\n type: state.approvalType\n });\n\n case 15:\n openJson = _context22.sent;\n openData = openJson.data || {};\n state.approvaStatus = openData.status;\n state.variableList = openData.variableList || [];\n state.processIdFromOpen = openData.id || ''; // 审批未开启,跳过\n\n if (!(state.approvaStatus !== 0)) {\n _context22.next = 22;\n break;\n }\n\n return _context22.abrupt(\"return\");\n\n case 22:\n if (!(state.variableList.length === 0)) {\n _context22.next = 25;\n break;\n }\n\n _context22.next = 25;\n return refreshApprovalFlow(form);\n\n case 25:\n if (form._approvalChangeBound) {\n _context22.next = 44;\n break;\n }\n\n form._approvalChangeBound = true;\n _approvalDebounceTimer = null;\n\n scheduleRefresh = function scheduleRefresh() {\n state._retryCount = 0; // 用户已变更关键字段,重置自动重试计数,保证后续变更可持续刷新\n // 审批变量变化后,旧的手工选人只属于上一版流程,不能覆盖新 preview 返回的审批人\n\n state.selectedApprovers = {};\n clearTimeout(_approvalDebounceTimer);\n _approvalDebounceTimer = setTimeout(function () {\n refreshApprovalFlow(form);\n }, 150);\n }; // change:select / datepicker 等组件选择后触发\n\n\n form.addEventListener('change', function (e) {\n var fid = findRelevantFieldId(e.target, state);\n if (!fid) return;\n scheduleRefresh();\n }); // input:文本输入即时变更\n\n form.addEventListener('input', function (e) {\n var fid = findRelevantFieldId(e.target, state);\n if (!fid) return;\n scheduleRefresh();\n }); // ★ 关联\"申请部门/申请单位\"与审批流程:切换部门后重新调用审批预览接口。\n // 通用 change/input 委托依赖 findRelevantFieldId 从事件目标匹配到相关字段 id,\n // H5 移动端部门/单位可能是自定义控件、事件目标拿不到 departNo 等 id 而漏触发。\n // 这里直接对部门/单位相关字段绑定 change/input,一旦变化立即 scheduleRefresh\n // (scheduleRefresh 内部已做 150ms 防抖,多次触发不会重复调用,安全无副作用)。\n\n _deptFieldIds = ['departNo', '_departNo', 'departmentNo', '_departmentNo', 'department', 'dept', 'deptName', 'applyDept', 'companyNo', '_companyNo'];\n _deptScope = resolveScope(form);\n _dfi = 0;\n\n case 34:\n if (!(_dfi < _deptFieldIds.length)) {\n _context22.next = 43;\n break;\n }\n\n _dfEl = (_deptScope && _deptScope.querySelector ? _deptScope.querySelector('#' + _deptFieldIds[_dfi]) : null) || (_deptScope && _deptScope.querySelector ? _deptScope.querySelector('[name=\"' + _deptFieldIds[_dfi] + '\"]') : null);\n\n if (_dfEl) {\n _context22.next = 38;\n break;\n }\n\n return _context22.abrupt(\"continue\", 40);\n\n case 38:\n _dfEl.addEventListener('change', scheduleRefresh);\n\n _dfEl.addEventListener('input', scheduleRefresh);\n\n case 40:\n _dfi++;\n _context22.next = 34;\n break;\n\n case 43:\n // MutationObserver:捕获 hidden input 等不触发 change 的 value 属性变更\n try {\n _approvalScope = resolveScope(form);\n _approvalObs = new MutationObserver(function (mutations) {\n for (var mi = 0; mi < mutations.length; mi++) {\n var m = mutations[mi];\n\n if (m.type === 'attributes' && m.attributeName === 'value' && m.target && m.target.id) {\n if (getApprovalRelevantIds(state).indexOf(m.target.id) >= 0) {\n scheduleRefresh();\n break;\n }\n }\n }\n });\n\n _approvalObs.observe(_approvalScope, {\n subtree: true,\n attributes: true,\n attributeFilter: ['value']\n });\n\n form._approvalObserver = _approvalObs;\n } catch (e2) {\n console.warn('[Approval] MutationObserver 监听失败:', e2.message);\n }\n\n case 44:\n // ★ 关键修复:data-default-label 的初始 change 事件早于监听器注册,\n // 需要立即检查字段是否已有值,如有则直接触发 preview\n mapKeys = Object.keys(state.variableMap);\n refreshApprovalFlow(form);\n _context22.next = 52;\n break;\n\n case 48:\n _context22.prev = 48;\n _context22.t0 = _context22[\"catch\"](11);\n console.warn('[Approval] init error:', _context22.t0.message);\n safeFeed('[callout t:error tt:审批异常 tx:\"' + _context22.t0.message + '\"]', form);\n\n case 52:\n case \"end\":\n return _context22.stop();\n }\n }\n }, _callee22, null, [[11, 48]]);\n }));\n return _initApprovalFlow.apply(this, arguments);\n }\n\n function refreshApprovalFlow(_x28) {\n return _refreshApprovalFlow.apply(this, arguments);\n } // 清除审批可视化渲染\n\n\n function _refreshApprovalFlow() {\n _refreshApprovalFlow = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee23(form) {\n var state, typeList, typeScope, missingList, complete, fixedKeys, requiredApprovalVars, deptAlreadyMissing, auth, companyNo, previewUrl, previewBody, previewJson, previewData, feedbackHost, oldFeedbacks;\n return regeneratorRuntime.wrap(function _callee23$(_context23) {\n while (1) {\n switch (_context23.prev = _context23.next) {\n case 0:\n state = getApprovalState(form);\n\n if (!state._stopped) {\n _context23.next = 3;\n break;\n }\n\n return _context23.abrupt(\"return\");\n\n case 3:\n if (!(state.approvaStatus !== 0)) {\n _context23.next = 5;\n break;\n }\n\n return _context23.abrupt(\"return\");\n\n case 5:\n if (!state._rendering) {\n _context23.next = 8;\n break;\n }\n\n state._refreshPending = true;\n return _context23.abrupt(\"return\");\n\n case 8:\n state._rendering = true;\n _context23.prev = 9;\n // 检查 typeList 是否完整 — 以 variableMap 为准\n // variableMap: formFieldId → typeListKey,typeList 的 key 就是 typeListKey\n typeList = buildTypeListFromForm(form, state); // ★ 当前表单作用域:后续缺失字段查询必须限定在此作用域内,避免跨表单串扰\n\n typeScope = resolveScope(form);\n missingList = [];\n complete = true; // 固定必填但由 buildTypeListFromForm 自动补齐的字段(无需等待用户填写)\n\n fixedKeys = ['proposer', 'companyId', 'userId']; // ★ 审批变量必填项:departmentNo 与 durationDay 都必须有值才允许调用 processPreview\n\n requiredApprovalVars = ['departmentNo', 'durationDay'];\n Object.keys(state.variableMap).forEach(function (formFieldId) {\n var typeListKey = state.variableMap[formFieldId];\n if (fixedKeys.indexOf(typeListKey) >= 0) return;\n\n if (!typeList[typeListKey] && typeList[typeListKey] !== 0) {\n var el = queryById(formFieldId, typeScope) || queryById('_' + formFieldId, typeScope);\n var isRequired = requiredApprovalVars.indexOf(typeListKey) >= 0; // ★ 必填字段(departmentNo/durationDay)即使 hidden/不存在也视为缺失,\n // 等待用户选择部门 / calcLeaveDuration 计算时长后由 change 事件再次触发刷新\n\n if (!isRequired && (!el || el.type === 'hidden')) {\n return;\n }\n\n missingList.push({\n varName: typeListKey,\n fieldId: formFieldId,\n hasEl: !!el,\n elTag: el ? el.tagName : '',\n elValue: el ? el.value || '' : ''\n });\n complete = false;\n }\n }); // ★ departmentNo 若未通过 variableMap 映射(仅由 buildTypeListFromForm 兜底填充),单独校验\n\n if (requiredApprovalVars.indexOf('departmentNo') >= 0 && !typeList.departmentNo) {\n deptAlreadyMissing = missingList.some(function (m) {\n return m.varName === 'departmentNo';\n });\n\n if (!deptAlreadyMissing) {\n missingList.push({\n varName: 'departmentNo',\n fieldId: 'departNo',\n hasEl: false,\n elTag: '',\n elValue: ''\n });\n complete = false;\n }\n }\n\n if (complete) {\n _context23.next = 23;\n break;\n }\n\n // 审批变量尚未全部回填,先隐藏审批模块(避免展示不完整节点)。\n clearApprovalRender(form, state); // 不在此处向用户报错;等待 change 事件 / markFormReady / calcLeaveDuration 在\n // 字段回填后再次触发 refreshApprovalFlow。仅保留静默兜底重试,覆盖无事件回填的偶发场景。\n\n state._retryCount = (state._retryCount || 0) + 1;\n\n if (state._retryCount <= 10 && !state._retryScheduled) {\n state._retryScheduled = true;\n state._retryTimer = setTimeout(function () {\n state._retryScheduled = false;\n state._retryTimer = null;\n if (state._stopped) return;\n refreshApprovalFlow(form);\n }, 1500);\n }\n\n return _context23.abrupt(\"return\");\n\n case 23:\n // ★ 字段齐全,重置重试计数(成功调用后计数归零,避免影响后续刷新)\n state._retryCount = 0;\n auth = (0, _request.getAuth)();\n companyNo = auth.companyNo || '';\n previewUrl = state.approvalPrefix + '/v1/pc/process/processPreview';\n previewBody = {\n companyNo: companyNo,\n type: state.approvalType,\n id: state.processIdFromOpen,\n typeList: typeList\n };\n _context23.next = 30;\n return _request.default.post(previewUrl, previewBody);\n\n case 30:\n previewJson = _context23.sent;\n previewData = previewJson.data || {};\n state.processId = previewData.processId || '';\n state.nextNode = previewData.nextNode || '';\n state.conditionMap = previewData.conditionMap || {};\n state.processList = previewData.processList || []; // processPreview 可能在用户选人后异步返回,优先恢复本地已确认的审批人\n\n restoreSelectedApprovers(state, state.processList); // 清除旧的报错/警告 callout(如之前 typeList 不完整时的等待提示)\n\n feedbackHost = form.closest('[data-form-id]') || form.parentElement || form;\n oldFeedbacks = feedbackHost.querySelectorAll('.tokui-validator-feedback');\n oldFeedbacks.forEach(function (el) {\n el.remove();\n });\n renderApprovalFlow(form, state);\n _context23.next = 46;\n break;\n\n case 43:\n _context23.prev = 43;\n _context23.t0 = _context23[\"catch\"](9);\n console.warn('[Approval] refresh error:', _context23.t0.message);\n\n case 46:\n _context23.prev = 46;\n state._rendering = false; // 字段在 processPreview 请求期间发生变化时,补做一次最新预览,避免界面停留在旧节点\n\n if (state._refreshPending && !state._stopped) {\n state._refreshPending = false;\n setTimeout(function () {\n refreshApprovalFlow(form);\n }, 0);\n }\n\n return _context23.finish(46);\n\n case 50:\n case \"end\":\n return _context23.stop();\n }\n }\n }, _callee23, null, [[9, 43, 46, 50]]);\n }));\n return _refreshApprovalFlow.apply(this, arguments);\n }\n\n function clearApprovalRender(form, state) {\n // 清除追踪的 container\n if (state.container && state.container.parentNode) {\n state.container.parentNode.removeChild(state.container);\n state.container = null;\n } // 清除可能残留的孤立审批流程容器(防止并发渲染导致重复)\n // 从更广范围搜索:审批流程可能被插入到 form 外部的 wrapper 中\n\n\n var formScope = form.closest('[data-form-id]') || form.parentElement || form; // 向上再找一层,确保覆盖 wrapper 所在的容器\n\n var searchScope = formScope.parentElement || formScope;\n var orphans = searchScope.querySelectorAll('.tv-approval-flow');\n orphans.forEach(function (el) {\n if (el.parentNode) el.parentNode.removeChild(el);\n }); // 同时清除可能残留的空 wrapper\n\n var emptyWrappers = searchScope.querySelectorAll('.tv-approval-wrapper:empty');\n emptyWrappers.forEach(function (el) {\n el.remove();\n });\n } // 停止单个表单的审批流程(暂停 SSE / 放弃生成时调用):\n // 清除待执行的重试定时器、就绪轮询,并移除已渲染的审批节点,避免继续输出刷新日志。\n\n\n function stopApprovalFlow(form) {\n if (!form) return;\n var state = getApprovalState(form);\n state._stopped = true;\n state._retryCount = 0;\n state._retryScheduled = false;\n\n if (state._retryTimer) {\n clearTimeout(state._retryTimer);\n state._retryTimer = null;\n } // 停止 initApprovalFlow 的就绪轮询\n\n\n if (form._approvalReadyWaiter) {\n form._approvalReadyWaiter = false;\n }\n\n clearApprovalRender(form, state);\n } // 停止 scope 内所有审批表单的审批流程\n\n\n function stopApprovalFlows(scope) {\n var root = scope && scope.querySelectorAll ? scope : document;\n var forms = root.querySelectorAll('[data-approval-type]');\n forms.forEach(function (el) {\n var form = el.closest('form') || el.closest('[data-form-id]') || el;\n if (form) stopApprovalFlow(form);\n });\n } // ============ 阶段 3:saveApprovalFlowState — 提交时保存审批 ============\n\n\n function saveApprovalFlowState(_x29) {\n return _saveApprovalFlowState.apply(this, arguments);\n } // ============ 审批流程可视化渲染 ============\n\n\n function _saveApprovalFlowState() {\n _saveApprovalFlowState = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee24(form) {\n var state, processSaveList, saveBody, saveJson, saveData;\n return regeneratorRuntime.wrap(function _callee24$(_context24) {\n while (1) {\n switch (_context24.prev = _context24.next) {\n case 0:\n state = getApprovalState(form);\n\n if (!(!state.approvalType || state.approvaStatus !== 0)) {\n _context24.next = 3;\n break;\n }\n\n return _context24.abrupt(\"return\", null);\n\n case 3:\n _context24.prev = 3;\n // 提交前再次同步,并复制节点,避免预览重绘或弹窗操作改写请求中的引用\n processSaveList = buildProcessSaveList(state);\n saveBody = {\n nextNode: state.nextNode,\n conditionMap: state.conditionMap,\n processId: state.processId,\n processList: processSaveList\n };\n _context24.next = 8;\n return _request.default.post(state.approvalPrefix + '/v1/pc/process/processSave', saveBody);\n\n case 8:\n saveJson = _context24.sent;\n saveData = saveJson.data || {};\n return _context24.abrupt(\"return\", {\n uniqueId: saveData.uniqueId || ''\n });\n\n case 13:\n _context24.prev = 13;\n _context24.t0 = _context24[\"catch\"](3);\n console.warn('[Approval] save error:', _context24.t0.message);\n (0, _message.showMessage)('审批流程异常:' + _context24.t0.message, 'error');\n return _context24.abrupt(\"return\", false);\n\n case 18:\n case \"end\":\n return _context24.stop();\n }\n }\n }, _callee24, null, [[3, 13]]);\n }));\n return _saveApprovalFlowState.apply(this, arguments);\n }\n\n function renderApprovalFlow(form, state) {\n clearApprovalRender(form, state);\n if (!state.processList || state.processList.length === 0) return; // 创建容器(初始隐藏,等定位完成后再显示)\n\n var container = document.createElement('div');\n container.className = 'tv-approval-flow tv-approval-flow--hidden';\n container.setAttribute('data-tv-approval', '1'); // 标题\n\n var title = document.createElement('div');\n title.className = 'tv-approval-title';\n title.innerHTML = '<span class=\"tv-approval-title-bar\"></span>审批流程';\n container.appendChild(title); // 渲染节点\n\n var processList = state.processList;\n processList.forEach(function (node, index) {\n // 跳过头尾标记节点\n if (node.nodeType === 'START' || node.nodeType === 'TEMP') return;\n var isEnd = node.nodeType === 'END';\n var isLast = index === processList.length - 1 && isEnd; // 结束节点:显示简单的结束标记\n\n if (isEnd) {\n var endNode = document.createElement('div');\n endNode.className = 'tv-approval-node tv-approval-node--end';\n endNode.innerHTML = '<div class=\"tv-approval-node-dot end-dot\"></div><div class=\"tv-approval-node-name\">流程结束</div>';\n container.appendChild(endNode);\n return;\n }\n\n var nodeEl = document.createElement('div');\n nodeEl.className = 'tv-approval-node ' + (node.nodeType === 'CC_EVENT' ? 'tv-approval-node--cc' : ''); // 左侧图标\n\n var iconArea = document.createElement('div');\n iconArea.className = 'tv-approval-node-icon';\n\n if (node.nodeType === 'CC_EVENT') {\n iconArea.innerHTML = '<svg viewBox=\"0 0 44 44\" width=\"44\" height=\"44\"><rect width=\"44\" height=\"44\" rx=\"10\" fill=\"#909399\"/><text x=\"22\" y=\"28\" text-anchor=\"middle\" fill=\"#fff\" font-size=\"20\">📋</text></svg>';\n } else {\n iconArea.innerHTML = '<svg viewBox=\"0 0 44 44\" width=\"44\" height=\"44\"><rect width=\"44\" height=\"44\" rx=\"10\" fill=\"#0286df\"/><text x=\"22\" y=\"28\" text-anchor=\"middle\" fill=\"#fff\" font-size=\"20\">👤</text></svg>';\n } // 右侧内容\n\n\n var body = document.createElement('div');\n body.className = 'tv-approval-node-body'; // 节点名\n\n var nodeName = document.createElement('div');\n nodeName.className = 'tv-approval-node-name';\n nodeName.textContent = node.nodeName || '';\n body.appendChild(nodeName); // 审批方式提示\n\n if (node.nodeType === 'USER_EVENT') {\n var typeHint = document.createElement('div');\n typeHint.className = 'tv-approval-node-type';\n\n if (node.approvers && node.approvers.length > 0) {\n if (node.approverType === '3') typeHint.textContent = node.approvers.length + '人依次审批';else if (node.approverType === '1') typeHint.textContent = node.approvers.length + '人会签';else if (node.approverType === '2') typeHint.textContent = node.approvers.length + '人或签';\n }\n\n if (node.directorType === '2' || node.directorType === '4') {\n typeHint.textContent = node.noDirectorContent || '系统自动指定';\n }\n\n body.appendChild(typeHint); // 审批人列表\n\n var approverList = document.createElement('div');\n approverList.className = 'tv-approval-approvers';\n\n if (node.approvers && node.approvers.length > 0) {\n node.approvers.forEach(function (approver, ai) {\n var tag = createApproverTag(approver, state, index, ai, node);\n approverList.appendChild(tag);\n });\n } else if (node.directorType === '2' || node.directorType === '4') {\n var autoTag = document.createElement('span');\n autoTag.className = 'tv-approver-tag tv-approver-tag--auto';\n autoTag.textContent = node.noDirectorContent || '自动';\n approverList.appendChild(autoTag);\n } else {\n // 检查是否有可选审批人列表(selectRange==='1' 走组织架构树,selectRange!=='1' 走 optionalUserList)\n var isOrgMode = node.selectRange === '1';\n var hasOptionalList = node.optionalUserList && node.optionalUserList.length > 0;\n var emptyTag = document.createElement('span');\n emptyTag.className = 'tv-approver-tag tv-approver-tag--empty';\n\n if (isOrgMode || hasOptionalList) {\n emptyTag.textContent = '待选择审批人';\n emptyTag.title = '点击选择审批人';\n emptyTag.style.cursor = 'pointer'; // 点击打开对应弹窗:组织架构模式用 showOrgSelector,列表模式用 showApproverSelector\n\n emptyTag.addEventListener('click', function () {\n if (isOrgMode) {\n showOrgSelector(state, index, node);\n } else {\n showApproverSelector(state, index, node);\n }\n });\n } else {\n emptyTag.textContent = '无可用审批人';\n emptyTag.title = '后端未返回可选审批人列表(optionalUserList 为空),请检查审批流程配置';\n emptyTag.style.color = '#C0C4CC';\n }\n\n approverList.appendChild(emptyTag);\n } // \"+ 选择审批人\" 按钮(对应 ApprovalCustomization.vue 的 handlexinzeng)\n // 参考项目中仅 directorType == '2' 不显示选择按钮\n // directorType == '1'(直接主管)在没有审批人时仍然允许选择\n\n\n var isDirectorAuto = node.directorType === '2';\n var isOrgSelect = node.selectRange === '1'; // 组织架构选人模式\n\n var canSelect = false;\n\n if (isOrgSelect) {\n // 组织架构模式:不需要 optionalUserList,直接从组织树选择\n canSelect = node.directorType === '3' && node.selectType !== '1' || !node.approvers || node.approvers.length === 0;\n } else if (!isDirectorAuto && node.optionalUserList && node.optionalUserList.length > 0) {\n // 列表模式:需要 optionalUserList\n canSelect = node.selectType === '2' || node.selectType === '1' && (!node.approvers || node.approvers.length === 0);\n }\n\n if (canSelect) {\n var addBtn = document.createElement('span');\n addBtn.className = 'tv-approver-add';\n addBtn.textContent = '+ 选择';\n addBtn.title = '选择审批人';\n addBtn.addEventListener('click', function (e) {\n e.stopPropagation();\n\n if (isOrgSelect) {\n showOrgSelector(state, index, node);\n } else {\n showApproverSelector(state, index, node);\n }\n });\n approverList.appendChild(addBtn);\n }\n\n body.appendChild(approverList);\n } // 抄送节点\n\n\n if (node.nodeType === 'CC_EVENT') {\n var ccHint = document.createElement('div');\n ccHint.className = 'tv-approval-node-type';\n ccHint.textContent = '抄送' + (node.approvers ? node.approvers.length : 0) + '人';\n body.appendChild(ccHint);\n\n if (node.approvers && node.approvers.length > 0) {\n var ccList = document.createElement('div');\n ccList.className = 'tv-approval-approvers';\n node.approvers.forEach(function (approver, ai) {\n var tag = createApproverTag(approver, state, index, ai, node);\n ccList.appendChild(tag);\n });\n body.appendChild(ccList);\n } // 抄送人选择(ccSelfSelectFlag == '1')\n\n\n if (node.ccSelfSelectFlag === '1') {\n var addCc = document.createElement('span');\n addCc.className = 'tv-approver-add';\n addCc.textContent = '+ 选择抄送人';\n addCc.addEventListener('click', function () {\n // 使用组织架构选择器(showOrgSelector 不依赖 optionalUserList,\n // 后端未返回可选列表时也能正常弹出组织树,避免点击无响应)\n showOrgSelector(state, index, node);\n });\n body.appendChild(addCc);\n }\n }\n\n nodeEl.appendChild(iconArea);\n nodeEl.appendChild(body);\n container.appendChild(nodeEl); // 连接线(非最后一个节点)\n\n if (!isLast && index < processList.length - 1) {\n var nextNode = processList[index + 1];\n\n if (nextNode && nextNode.nodeType !== 'END') {\n var line = document.createElement('div');\n line.className = 'tv-approval-line';\n container.appendChild(line);\n } else if (nextNode && nextNode.nodeType === 'END') {\n var endLine = document.createElement('div');\n endLine.className = 'tv-approval-line tv-approval-line--end';\n container.appendChild(endLine);\n }\n }\n }); // 插入到提交按钮前面\n // 策略:找到 btngroup,用一个包裹层同时容纳审批流程和按钮组,内部顺序固定不受 CSS 影响\n\n function findBtngroup() {\n var btnScope = resolveScope(form);\n var allBtns = btnScope.querySelectorAll('button');\n\n for (var i = 0; i < allBtns.length; i++) {\n var txt = (allBtns[i].textContent || '').trim();\n\n if (txt === '提交' || txt === '暂存' || txt === '重置') {\n var parent = allBtns[i].parentElement; // 确认父级包含多个操作按钮\n\n if (parent && parent.querySelectorAll('button').length >= 2) return parent;\n return parent;\n }\n } // 回退:找 tokui 按钮属性\n\n\n var btn = btnScope.querySelector('[data-tokui-tag=\"btn\"], [class*=\"tokui-btn\"]');\n if (btn && btn.tagName !== 'BUTTON') btn = btn.querySelector('button') || btn;\n return btn ? btn.parentElement : null;\n }\n\n function insertBeforeBtngroup() {\n var btngroup = findBtngroup();\n if (!btngroup) return false; // 如果 btngroup 已在 wrapper 中,复用现有 wrapper\n\n var existingWrapper = btngroup.parentNode;\n\n if (existingWrapper && existingWrapper.classList.contains('tv-approval-wrapper')) {\n // 检查 wrapper 中是否已有审批流程\n var existingFlow = existingWrapper.querySelector('.tv-approval-flow');\n\n if (existingFlow && existingFlow !== container) {\n existingFlow.remove();\n } // 确保审批流程在 btngroup 前面\n\n\n if (container.parentNode !== existingWrapper) {\n existingWrapper.insertBefore(container, btngroup);\n } else if (btngroup.previousElementSibling !== container) {\n existingWrapper.insertBefore(container, btngroup);\n }\n\n container.classList.remove('tv-approval-flow--hidden');\n return true;\n } // 清除可能残留的旧 wrapper(避免嵌套)\n\n\n var formScope = form.closest('[data-form-id]') || form.parentElement || form;\n var searchScope = formScope.parentElement || formScope;\n var oldWrappers = searchScope.querySelectorAll('.tv-approval-wrapper');\n oldWrappers.forEach(function (w) {\n if (w === existingWrapper) return; // 把 wrapper 内的 btngroup 放回原位\n\n var wp = w.parentNode;\n\n if (wp) {\n while (w.firstChild) {\n wp.insertBefore(w.firstChild, w);\n }\n\n w.remove();\n }\n }); // 创建新包裹层,把审批流程和按钮组合并,确保审批流程在按钮组上方\n\n var wrapper = document.createElement('div');\n wrapper.className = 'tv-approval-wrapper';\n btngroup.parentNode.insertBefore(wrapper, btngroup);\n wrapper.appendChild(container);\n wrapper.appendChild(btngroup); // 定位完成,移除隐藏类\n\n container.classList.remove('tv-approval-flow--hidden');\n return true;\n }\n\n try {\n if (!insertBeforeBtngroup()) {\n (form.querySelector('.tokui-form-content') || form).appendChild(container);\n [500, 1500].forEach(function (d) {\n setTimeout(function () {\n if (container.parentNode) insertBeforeBtngroup();\n }, d);\n });\n }\n } catch (insertErr) {\n console.warn('[Approval] render insert error:', insertErr.message);\n\n try {\n (form.querySelector('.tokui-form-content') || form).appendChild(container);\n } catch (e2) {}\n }\n\n state.container = container;\n } // 创建审批人标签 DOM\n\n\n function createApproverTag(approver, state, nodeIndex, approverIndex, node) {\n var tag = document.createElement('span');\n tag.className = 'tv-approver-tag';\n var name = approver.userName || approver.name || ''; // 有头像 → 圆形头像 <img>;无头像(icon 为空/破图) → 直接显示姓名末尾 2 字占位,不包圆形底色\n\n var fallbackText = function fallbackText() {\n return name && name.length >= 2 ? name.substring(name.length - 2) : name || '?';\n };\n\n if (approver.icon) {\n var avatar = document.createElement('span');\n avatar.className = 'tv-approver-avatar';\n avatar.title = name;\n var img = document.createElement('img');\n img.className = 'tv-approver-avatar-img';\n img.src = approver.icon;\n img.alt = '';\n avatar.appendChild(img);\n\n img.onerror = function () {\n avatar.remove();\n tag.textContent = fallbackText();\n tag.title = name;\n };\n\n tag.appendChild(avatar);\n } else {\n tag.textContent = fallbackText();\n tag.title = name;\n } // 删除按钮(非固定审批人)\n\n\n var canDelete = node.directorType !== '1' && node.directorType !== '2' && node.directorType !== '4';\n\n if (node.nodeType === 'CC_EVENT') {\n canDelete = node.ccSelfSelectFlag === '1' && approver.settingType === '1';\n }\n\n if (canDelete) {\n var delBtn = document.createElement('span');\n delBtn.className = 'tv-approver-remove';\n delBtn.textContent = '×';\n delBtn.title = '删除';\n delBtn.addEventListener('click', function (e) {\n e.stopPropagation();\n node.approvers.splice(approverIndex, 1);\n rememberSelectedApprovers(state, node, nodeIndex);\n if (node.optionalUserList) node.optionalUserList.push(approver);\n renderApprovalFlow(state._form, state);\n });\n tag.appendChild(delBtn);\n }\n\n return tag;\n } // ============ 组织架构选人弹窗(selectRange === '1' 时使用) ============\n // 对应源项目 nodeForm.vue,从组织架构树中选择审批人\n\n\n function showOrgSelector(state, nodeIndex, node) {\n // 移除已存在的选择器\n var oldBackdrop = document.querySelector('.tv-approver-backdrop');\n if (oldBackdrop) oldBackdrop.remove();\n var isSingle = node.selectType === '1'; // 单选\n\n var isCC = node.nodeType === 'CC_EVENT';\n var dialogTitle = isCC ? '请选择抄送人' : '请选择审批人';\n var selectedList = (node.approvers || []).slice(); // 已选列表\n // 企业 API 前缀:优先从 env.VUE_APP_EMTERPRISE 获取,兼容旧 approvalPrefix 推导\n\n var enterpriseBase = state.approvalPrefix.replace(/\\/sgb-meta-design$/, \"/enterprise-managerment\");\n if (!enterpriseBase) enterpriseBase = state.approvalPrefix.replace(/\\/[^/]+$/, \"/enterprise-managerment\");\n var auth = (0, _request.getAuth)();\n var userNo = auth.userno || auth.user || '';\n var companyNo = auth.companyNo || ''; // ── backdrop ──\n\n var backdrop = document.createElement('div');\n backdrop.className = 'tv-approver-backdrop';\n backdrop.addEventListener('click', function () {\n backdrop.remove();\n }); // ── dialog ──\n\n var dialog = document.createElement('div');\n dialog.className = 'tv-org-dialog';\n dialog.addEventListener('click', function (e) {\n e.stopPropagation();\n }); // header\n\n var header = document.createElement('div');\n header.className = 'tv-approver-dialog-header';\n var titleEl = document.createElement('span');\n titleEl.className = 'tv-approver-dialog-title';\n titleEl.textContent = dialogTitle;\n var closeBtn = document.createElement('span');\n closeBtn.className = 'tv-approver-dialog-close';\n closeBtn.textContent = '×';\n closeBtn.addEventListener('click', function () {\n backdrop.remove();\n });\n header.appendChild(titleEl);\n header.appendChild(closeBtn);\n dialog.appendChild(header); // body: left (tree) + right (selected)\n\n var body = document.createElement('div');\n body.className = 'tv-org-dialog-body'; // ── 左侧树 ──\n\n var leftPanel = document.createElement('div');\n leftPanel.className = 'tv-org-left';\n var searchInput = document.createElement('input');\n searchInput.type = 'text';\n searchInput.className = 'tv-org-search';\n searchInput.placeholder = '输入姓名搜索';\n leftPanel.appendChild(searchInput);\n var treeContainer = document.createElement('div');\n treeContainer.className = 'tv-org-tree';\n treeContainer.innerHTML = '<div class=\"tv-org-loading\">加载组织架构中...</div>';\n leftPanel.appendChild(treeContainer);\n body.appendChild(leftPanel); // ── 右侧已选 ──\n\n var rightPanel = document.createElement('div');\n rightPanel.className = 'tv-org-right';\n var rightTitle = document.createElement('div');\n rightTitle.className = 'block-title';\n rightTitle.textContent = '已选:';\n rightPanel.appendChild(rightTitle);\n var rightList = document.createElement('ul');\n rightList.className = 'tv-org-selected-list';\n rightPanel.appendChild(rightList);\n body.appendChild(rightPanel);\n dialog.appendChild(body); // footer\n\n var footer = document.createElement('div');\n footer.className = 'tv-approver-dialog-footer';\n var cancelBtn = document.createElement('button');\n cancelBtn.className = 'tv-approver-dialog-cancel';\n cancelBtn.textContent = '取消';\n cancelBtn.addEventListener('click', function () {\n backdrop.remove();\n });\n footer.appendChild(cancelBtn);\n var confirmBtn = document.createElement('button');\n confirmBtn.className = 'tv-approver-dialog-confirm';\n confirmBtn.textContent = '确定';\n confirmBtn.addEventListener('click', function () {\n // 更新 node.approvers\n node.approvers = selectedList.map(function (item) {\n return {\n companyId: item.comNo || item.companyId || '',\n userId: item.memberNo || item.userId || '',\n userName: item.name || item.userName || '',\n memberNo: item.memberNo || item.userId || '',\n icon: item.icon || '',\n settingType: item.comNo ? '1' : '2'\n };\n });\n rememberSelectedApprovers(state, node, nodeIndex);\n var form = state._form;\n if (form) renderApprovalFlow(form, state);\n backdrop.remove();\n });\n footer.appendChild(confirmBtn);\n dialog.appendChild(footer);\n backdrop.appendChild(dialog);\n document.body.appendChild(backdrop); // ========== 渲染右侧已选列表 ==========\n\n function renderSelected() {\n rightList.innerHTML = '';\n selectedList.forEach(function (item, idx) {\n var li = document.createElement('li');\n li.className = 'tv-org-selected-item';\n var nameEl = document.createElement('span');\n nameEl.textContent = item.name || item.userName || '';\n li.appendChild(nameEl);\n var delBtn = document.createElement('span');\n delBtn.className = 'tv-org-selected-del';\n delBtn.textContent = '×';\n delBtn.addEventListener('click', function () {\n selectedList.splice(idx, 1);\n renderSelected();\n renderTree(); // 刷新左侧勾选状态\n });\n li.appendChild(delBtn);\n rightList.appendChild(li);\n });\n } // ========== 渲染左侧树 ==========\n\n\n var loadedData = null; // 根节点数据\n\n var deptChildrenMap = {}; // 已加载的子部门数据 { deptId: { orgInfoList: [...], contacts: [...] } }\n\n var expandedDepts = {}; // 已展开的部门\n\n var loadingDepts = {}; // 正在加载中的部门\n\n function renderTree() {\n if (!loadedData) {\n treeContainer.innerHTML = '<div class=\"tv-org-loading\">加载组织架构中...</div>';\n return;\n }\n\n var selectedIds = {};\n selectedList.forEach(function (s) {\n selectedIds[s.memberNo || s.userId] = true;\n });\n\n function renderNode(item, level) {\n var row = document.createElement('div');\n row.className = 'tv-org-node';\n row.style.paddingLeft = level * 20 + 8 + 'px';\n\n if (item.holidayType === 2 || item.leaf) {\n // 联系人(可勾选)\n var uid = item.memberNo || item.userId;\n var isChecked = selectedIds[uid];\n var cb = document.createElement('input');\n cb.type = isSingle ? 'radio' : 'checkbox';\n if (isSingle) cb.name = 'tv-org-radio';\n cb.checked = isChecked;\n cb.setAttribute('data-uid', uid);\n cb.className = 'tv-org-check';\n cb.addEventListener('change', function () {\n if (cb.checked) {\n if (isSingle) {\n selectedList = [item];\n treeContainer.querySelectorAll('input[type=\"radio\"]').forEach(function (r) {\n if (r !== cb) r.checked = false;\n });\n } else {\n selectedList.push(item);\n }\n } else {\n selectedList = selectedList.filter(function (s) {\n return (s.memberNo || s.userId) !== uid;\n });\n }\n\n renderSelected();\n });\n var label = document.createElement('span');\n label.className = 'tv-org-label';\n label.textContent = item.name || '';\n label.style.cursor = 'pointer';\n label.addEventListener('click', function () {\n cb.checked = !cb.checked;\n cb.dispatchEvent(new Event('change'));\n });\n row.appendChild(cb);\n row.appendChild(label);\n } else {\n // 部门(可展开,懒加载子节点)\n var deptId = item.id;\n var hasChildren = deptChildrenMap[deptId] !== undefined;\n var isExpanded = expandedDepts[deptId];\n var isLoading = loadingDepts[deptId];\n var arrow = document.createElement('span');\n arrow.className = 'tv-org-arrow ' + (isExpanded ? 'expanded' : '');\n arrow.textContent = isExpanded ? '▼' : '▶';\n var deptName = document.createElement('span');\n deptName.className = 'tv-org-label tv-org-label--dept';\n deptName.textContent = (isLoading ? '加载中... ' : '') + (item.name || '');\n row.appendChild(arrow);\n row.appendChild(deptName);\n row.style.cursor = 'pointer';\n row.addEventListener('click', function () {\n if (expandedDepts[deptId]) {\n // 收起\n delete expandedDepts[deptId];\n renderTree();\n } else {\n // 展开\n if (hasChildren) {\n // 已有数据,直接展开\n expandedDepts[deptId] = true;\n renderTree();\n } else if (!isLoading) {\n // 需要懒加载\n loadDeptChildren(deptId, item);\n }\n }\n });\n } // 统一追加行\n\n\n treeContainer.appendChild(row); // 渲染子节点(展开的部门)\n\n if (item.holidayType !== 2 && !item.leaf) {\n var _isExpanded = expandedDepts[item.id];\n\n if (_isExpanded) {\n var childData = deptChildrenMap[item.id];\n\n if (childData) {\n // 先渲染子部门,再渲染联系人\n (childData.orgInfoList || []).forEach(function (child) {\n renderNode(child, level + 1);\n });\n (childData.contacts || []).forEach(function (child) {\n child.leaf = true;\n child.holidayType = 2;\n renderNode(child, level + 1);\n });\n }\n }\n }\n }\n\n treeContainer.innerHTML = '';\n renderNode(loadedData, 0);\n } // ========== 懒加载子部门 ==========\n\n\n function loadDeptChildren(_x30, _x31) {\n return _loadDeptChildren.apply(this, arguments);\n } // ========== 加载组织架构(根节点) ==========\n\n\n function _loadDeptChildren() {\n _loadDeptChildren = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee15(deptId, deptItem) {\n var orgJson, orgData, contacts, orgInfoList;\n return regeneratorRuntime.wrap(function _callee15$(_context15) {\n while (1) {\n switch (_context15.prev = _context15.next) {\n case 0:\n if (!loadingDepts[deptId]) {\n _context15.next = 2;\n break;\n }\n\n return _context15.abrupt(\"return\");\n\n case 2:\n loadingDepts[deptId] = true;\n renderTree(); // 显示加载中状态\n\n _context15.prev = 4;\n _context15.next = 7;\n return _request.default.get(enterpriseBase + '/v1/organization/getOrgInfoV2?userNo=' + userNo + '&companyNo=' + companyNo + '&deptId=' + deptId);\n\n case 7:\n orgJson = _context15.sent;\n orgData = orgJson.data || {};\n contacts = orgData.contacts || [];\n orgInfoList = orgData.orgInfoList || []; // 标记叶子节点\n\n contacts.forEach(function (c) {\n c.leaf = true;\n c.holidayType = 2;\n }); // 标记子部门\n\n orgInfoList.forEach(function (d) {\n d.disabled = true;\n });\n deptChildrenMap[deptId] = {\n orgInfoList: orgInfoList,\n contacts: contacts\n };\n delete loadingDepts[deptId];\n expandedDepts[deptId] = true; // 展开已加载的部门\n\n renderTree();\n _context15.next = 24;\n break;\n\n case 19:\n _context15.prev = 19;\n _context15.t0 = _context15[\"catch\"](4);\n console.warn('[Validator] 加载子部门失败:', _context15.t0.message);\n delete loadingDepts[deptId];\n renderTree();\n\n case 24:\n case \"end\":\n return _context15.stop();\n }\n }\n }, _callee15, null, [[4, 19]]);\n }));\n return _loadDeptChildren.apply(this, arguments);\n }\n\n function loadOrgData() {\n return _loadOrgData.apply(this, arguments);\n } // ========== 搜索 ==========\n\n\n function _loadOrgData() {\n _loadOrgData = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee16() {\n var companyJson, companies, mainCompany, orgJson, orgData, contacts, orgInfoList;\n return regeneratorRuntime.wrap(function _callee16$(_context16) {\n while (1) {\n switch (_context16.prev = _context16.next) {\n case 0:\n _context16.prev = 0;\n _context16.next = 3;\n return _request.default.get(enterpriseBase + '/v1/organization/getCompanyInfo?userNo=' + userNo + '&companyNo=' + companyNo);\n\n case 3:\n companyJson = _context16.sent;\n companies = companyJson.data && Array.isArray(companyJson.data) ? companyJson.data : [];\n mainCompany = companies.filter(function (c) {\n return c.isMainCompany === 1 || c.isMainCompany === true;\n })[0] || companies[0];\n _context16.next = 8;\n return _request.default.get(enterpriseBase + '/v1/organization/getOrgInfoV2?userNo=' + userNo + '&companyNo=' + companyNo + '&deptId=1');\n\n case 8:\n orgJson = _context16.sent;\n orgData = orgJson.data || {};\n contacts = orgData.contacts || [];\n orgInfoList = orgData.orgInfoList || []; // 标记叶子节点\n\n contacts.forEach(function (c) {\n c.leaf = true;\n c.holidayType = 2;\n }); // 标记子部门\n\n orgInfoList.forEach(function (d) {\n d.disabled = true;\n }); // 根节点数据\n\n deptChildrenMap['1'] = {\n orgInfoList: orgInfoList,\n contacts: contacts\n };\n loadedData = {\n name: mainCompany.name || '组织架构',\n id: '1',\n disabled: true\n };\n expandedDepts['1'] = true; // 默认展开根节点\n\n treeContainer.innerHTML = '';\n renderTree();\n _context16.next = 25;\n break;\n\n case 21:\n _context16.prev = 21;\n _context16.t0 = _context16[\"catch\"](0);\n console.warn('[Validator] 加载组织架构失败:', _context16.t0.message);\n treeContainer.innerHTML = '<div class=\"tv-org-error\">加载组织架构失败: ' + _context16.t0.message + '</div>';\n\n case 25:\n case \"end\":\n return _context16.stop();\n }\n }\n }, _callee16, null, [[0, 21]]);\n }));\n return _loadOrgData.apply(this, arguments);\n }\n\n var searchTimer = null;\n searchInput.addEventListener('input', function () {\n clearTimeout(searchTimer);\n var keyword = searchInput.value.trim();\n\n if (keyword.length === 0) {\n renderTree();\n return;\n }\n\n searchTimer = setTimeout(function () {\n doSearch(keyword);\n }, 300);\n });\n\n function doSearch(_x32) {\n return _doSearch.apply(this, arguments);\n } // 初始化\n\n\n function _doSearch() {\n _doSearch = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee17(keyword) {\n var searchJson, results, selectedIds;\n return regeneratorRuntime.wrap(function _callee17$(_context17) {\n while (1) {\n switch (_context17.prev = _context17.next) {\n case 0:\n if (keyword) {\n _context17.next = 2;\n break;\n }\n\n return _context17.abrupt(\"return\");\n\n case 2:\n treeContainer.innerHTML = '<div class=\"tv-org-loading\">搜索中...</div>';\n _context17.prev = 3;\n _context17.next = 6;\n return _request.default.get(enterpriseBase + '/v2/api/contractBasicInfo/searchKeyWord?keyWord=' + encodeURIComponent(keyword) + '&comNo=' + companyNo);\n\n case 6:\n searchJson = _context17.sent;\n results = searchJson.data || [];\n selectedIds = {};\n selectedList.forEach(function (s) {\n selectedIds[s.memberNo || s.userId] = true;\n selectedIds[s.id] = true;\n });\n treeContainer.innerHTML = '';\n\n if (!(results.length === 0)) {\n _context17.next = 14;\n break;\n }\n\n treeContainer.innerHTML = '<div class=\"tv-org-empty\">无匹配结果</div>';\n return _context17.abrupt(\"return\");\n\n case 14:\n results.forEach(function (item) {\n var row = document.createElement('div');\n row.className = 'tv-org-node';\n row.style.paddingLeft = '8px';\n var uid = item.memberNo || item.userId;\n var isChecked = selectedIds[uid] || selectedIds[item.id];\n var cb = document.createElement('input');\n cb.type = isSingle ? 'radio' : 'checkbox';\n if (isSingle) cb.name = 'tv-org-radio';\n cb.checked = isChecked;\n cb.className = 'tv-org-check';\n cb.addEventListener('change', function () {\n if (cb.checked) {\n if (isSingle) {\n selectedList = [item];\n treeContainer.querySelectorAll('input[type=\"radio\"]').forEach(function (r) {\n if (r !== cb) r.checked = false;\n });\n } else {\n selectedList.push(item);\n }\n } else {\n selectedList = selectedList.filter(function (s) {\n return (s.memberNo || s.userId || s.id) !== uid;\n });\n }\n\n renderSelected();\n });\n var label = document.createElement('span');\n label.className = 'tv-org-label';\n label.textContent = item.name || '';\n label.style.cursor = 'pointer';\n label.addEventListener('click', function () {\n cb.checked = !cb.checked;\n cb.dispatchEvent(new Event('change'));\n });\n row.appendChild(cb);\n row.appendChild(label);\n treeContainer.appendChild(row);\n });\n _context17.next = 21;\n break;\n\n case 17:\n _context17.prev = 17;\n _context17.t0 = _context17[\"catch\"](3);\n console.warn('[Validator] 搜索失败:', _context17.t0.message);\n treeContainer.innerHTML = '<div class=\"tv-org-error\">搜索失败: ' + _context17.t0.message + '</div>';\n\n case 21:\n case \"end\":\n return _context17.stop();\n }\n }\n }, _callee17, null, [[3, 17]]);\n }));\n return _doSearch.apply(this, arguments);\n }\n\n renderSelected();\n loadOrgData();\n } // 审批人选择弹窗(「请选择审批人」dialog,对应 ApprovalCustomization.vue 的 el-dialog)\n\n\n function showApproverSelector(state, nodeIndex, node) {\n // 移除已存在的选择器\n var oldBackdrop = document.querySelector('.tv-approver-backdrop');\n if (oldBackdrop) oldBackdrop.remove();\n var isMulti = node.selectType === '2'; // 多选\n\n var isCC = node.nodeType === 'CC_EVENT';\n var dialogTitle = isCC ? '请选择抄送人' : '请选择审批人'; // ── backdrop ──\n\n var backdrop = document.createElement('div');\n backdrop.className = 'tv-approver-backdrop';\n backdrop.addEventListener('click', function () {\n backdrop.remove();\n }); // ── dialog ──\n\n var dialog = document.createElement('div');\n dialog.className = 'tv-approver-dialog';\n dialog.addEventListener('click', function (e) {\n e.stopPropagation();\n }); // header\n\n var header = document.createElement('div');\n header.className = 'tv-approver-dialog-header';\n var titleEl = document.createElement('span');\n titleEl.className = 'tv-approver-dialog-title';\n titleEl.textContent = dialogTitle;\n var closeBtn = document.createElement('span');\n closeBtn.className = 'tv-approver-dialog-close';\n closeBtn.textContent = '×';\n closeBtn.addEventListener('click', function () {\n backdrop.remove();\n });\n header.appendChild(titleEl);\n header.appendChild(closeBtn);\n dialog.appendChild(header); // body\n\n var body = document.createElement('div');\n body.className = 'tv-approver-dialog-body';\n var desc = document.createElement('p');\n desc.className = 'tv-approver-dialog-desc';\n desc.textContent = isMulti ? '请从指定以下成员中选择' : '请从指定以下成员中选择一人';\n body.appendChild(desc); // 过滤已选中的\n\n var selectedIds = {};\n\n if (node.approvers) {\n node.approvers.forEach(function (a) {\n selectedIds[a.userId || a.memberNo] = true;\n });\n }\n\n var listContainer = document.createElement('div');\n listContainer.className = 'tv-approver-dialog-list';\n\n if (node.optionalUserList && node.optionalUserList.length > 0) {\n var availableUsers = node.optionalUserList.filter(function (u) {\n return !selectedIds[u.userId || u.memberNo];\n });\n\n if (availableUsers.length === 0) {\n var emptyMsg = document.createElement('div');\n emptyMsg.className = 'tv-approver-dialog-empty';\n emptyMsg.textContent = '暂无可选成员';\n listContainer.appendChild(emptyMsg);\n } else {\n // 收集所有已选项用于批量确认\n var selectedCheckboxes = [];\n availableUsers.forEach(function (user) {\n var uid = user.userId || user.memberNo;\n var item = document.createElement('label');\n item.className = 'tv-approver-dialog-item';\n\n if (isMulti) {\n var cb = document.createElement('input');\n cb.type = 'checkbox';\n cb.value = uid;\n cb.setAttribute('data-uid', uid);\n item.appendChild(cb);\n selectedCheckboxes.push(cb);\n } else {\n var radio = document.createElement('input');\n radio.type = 'radio';\n radio.name = 'tv-approver-radio';\n radio.value = uid;\n radio.setAttribute('data-uid', uid);\n item.appendChild(radio);\n selectedCheckboxes.push(radio);\n }\n\n var label = document.createElement('span');\n label.textContent = user.userName || user.name || uid;\n item.appendChild(label); // 点击整行选中(仅多选时可用,筛选模式用 label 本身的 interaction)\n\n if (isMulti) {\n item.addEventListener('click', function (e) {\n if (e.target.tagName === 'INPUT') return;\n var cb = item.querySelector('input[type=\"checkbox\"]');\n if (cb) cb.checked = !cb.checked;\n });\n } else {\n // 单选模式:点击整行直接确认\n item.addEventListener('click', function () {\n confirmApproverSelection(state, nodeIndex, node, [user]);\n backdrop.remove();\n });\n }\n\n listContainer.appendChild(item);\n });\n }\n } else {\n var emptyMsg = document.createElement('div');\n emptyMsg.className = 'tv-approver-dialog-empty';\n emptyMsg.textContent = '暂无可选成员(后端未返回 optionalUserList,请检查审批流程配置或确认 API 调用是否成功)';\n console.warn('[Validator] showApproverSelector: optionalUserList 为空,请检查 processPreview 返回的 processList 节点数据');\n listContainer.appendChild(emptyMsg);\n }\n\n body.appendChild(listContainer);\n dialog.appendChild(body); // footer\n\n var footer = document.createElement('div');\n footer.className = 'tv-approver-dialog-footer';\n var cancelBtn = document.createElement('button');\n cancelBtn.className = 'tv-approver-dialog-cancel';\n cancelBtn.textContent = '取消';\n cancelBtn.addEventListener('click', function () {\n backdrop.remove();\n });\n footer.appendChild(cancelBtn);\n\n if (isMulti) {\n var confirmBtn = document.createElement('button');\n confirmBtn.className = 'tv-approver-dialog-confirm';\n confirmBtn.textContent = '确定';\n confirmBtn.addEventListener('click', function () {\n var selected = [];\n listContainer.querySelectorAll('input[type=\"checkbox\"]:checked').forEach(function (cb) {\n var uid = cb.getAttribute('data-uid');\n node.optionalUserList.forEach(function (u) {\n if ((u.userId || u.memberNo) === uid) selected.push(u);\n });\n });\n\n if (selected.length > 0) {\n confirmApproverSelection(state, nodeIndex, node, selected);\n }\n\n backdrop.remove();\n });\n footer.appendChild(confirmBtn);\n }\n\n dialog.appendChild(footer);\n backdrop.appendChild(dialog);\n document.body.appendChild(backdrop);\n }\n\n function confirmApproverSelection(state, nodeIndex, node, selectedUsers) {\n if (!node.approvers) node.approvers = [];\n selectedUsers.forEach(function (user) {\n // 从 optionalUserList 移除\n if (node.optionalUserList) {\n var idx = node.optionalUserList.indexOf(user);\n if (idx >= 0) node.optionalUserList.splice(idx, 1);\n } // 添加到 approvers\n\n\n var approverObj = {\n companyId: user.companyId || user.comNo || '',\n userId: user.userId || user.memberNo || '',\n userName: user.userName || user.name || '',\n icon: user.icon || '',\n memberNo: user.userId || user.memberNo || '',\n settingType: user.settingType || '2'\n };\n node.approvers.push(approverObj);\n });\n rememberSelectedApprovers(state, node, nodeIndex); // 重新渲染\n\n var form = state._form;\n if (form) renderApprovalFlow(form, state);\n } // ============ 提交时调用的审批入口(替代原 runApprovalFlow) ============\n\n\n function runApprovalFlow(_x33, _x34) {\n return _runApprovalFlow.apply(this, arguments);\n } // ---------- 还原 data-load-body-* key 的驼峰写法 ----------\n\n\n function _runApprovalFlow() {\n _runApprovalFlow = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee25(form, allData) {\n var state, _retry, _wait;\n\n return regeneratorRuntime.wrap(function _callee25$(_context25) {\n while (1) {\n switch (_context25.prev = _context25.next) {\n case 0:\n state = getApprovalState(form);\n\n if (state.approvalType) {\n _context25.next = 3;\n break;\n }\n\n return _context25.abrupt(\"return\", null);\n\n case 3:\n if (state.inited) {\n _context25.next = 6;\n break;\n }\n\n _context25.next = 6;\n return initApprovalFlow(form);\n\n case 6:\n if (!(state.approvaStatus !== 0)) {\n _context25.next = 8;\n break;\n }\n\n return _context25.abrupt(\"return\", {\n uniqueId: '',\n skipped: true\n });\n\n case 8:\n if (!(!state.processList || state.processList.length === 0)) {\n _context25.next = 11;\n break;\n }\n\n _context25.next = 11;\n return refreshApprovalFlow(form);\n\n case 11:\n if (!(!state.processList || state.processList.length === 0)) {\n _context25.next = 31;\n break;\n }\n\n _retry = 0;\n\n case 13:\n if (!(_retry < 3)) {\n _context25.next = 31;\n break;\n }\n\n // 等待 _rendering 锁释放\n _wait = 0;\n\n case 15:\n if (!(state._rendering && _wait < 2000)) {\n _context25.next = 21;\n break;\n }\n\n _context25.next = 18;\n return new Promise(function (r) {\n setTimeout(r, 100);\n });\n\n case 18:\n _wait += 100;\n _context25.next = 15;\n break;\n\n case 21:\n // 强制重置渲染锁并重试\n state._rendering = false;\n _context25.next = 24;\n return refreshApprovalFlow(form);\n\n case 24:\n if (!(state.processList && state.processList.length > 0)) {\n _context25.next = 26;\n break;\n }\n\n return _context25.abrupt(\"break\", 31);\n\n case 26:\n _context25.next = 28;\n return new Promise(function (r) {\n setTimeout(r, 500);\n });\n\n case 28:\n _retry++;\n _context25.next = 13;\n break;\n\n case 31:\n if (!(!state.processList || state.processList.length === 0)) {\n _context25.next = 35;\n break;\n }\n\n console.warn('[Approval] runApprovalFlow: processList 为空,中止提交');\n safeFeed('[callout t:error tt:审批流程未就绪 tx:\"审批流程数据未就绪,请检查请假类型和时长后重试\"]', form);\n return _context25.abrupt(\"return\", false);\n\n case 35:\n _context25.next = 37;\n return saveApprovalFlowState(form);\n\n case 37:\n return _context25.abrupt(\"return\", _context25.sent);\n\n case 38:\n case \"end\":\n return _context25.stop();\n }\n }\n }, _callee25);\n }));\n return _runApprovalFlow.apply(this, arguments);\n }\n\n function normalizeBodyKey(key) {\n // HTML 属性名不区分大小写,TokUI DSL 中 data-load-body-userNo 渲染为 data-load-body-userno\n // 常见 API 字段映射表\n var map = {\n userno: 'userNo',\n companyno: 'companyNo',\n comno: 'comNo',\n parentid: 'parentId',\n userid: 'userId',\n companyid: 'companyId',\n departmentid: 'departmentId',\n departmentno: 'departmentNo',\n holidayno: 'holidayNo',\n starttime: 'startTime',\n endtime: 'endTime',\n minleaveunit: 'minLeaveUnit',\n starttimestr: 'startTimeStr',\n endtimestr: 'endTimeStr'\n };\n return map[key.toLowerCase()] || key;\n } // ---------- data-load 请求构造(支持 POST:data-load-method / data-load-body-*) ----------\n //\n // 标准 POST 传参方式:用 data-load-body-{字段名} 声明每个 body 字段,\n // 避免 TokUI DSL 解析器遇到 JSON 内嵌引号时破坏属性值。\n //\n // 级联依赖(推荐):data-load-body-dep-{字段名}:\"{目标元素ID}\"\n // data-load-body-dep- 的值不会被 TokUI 篡改(不含 ${...}),\n // 运行时桥接层自动从目标 DOM 元素读取选中 option 的 value 属性。\n //\n // 示例 DSL:\n // data-load-method:POST\n // data-load-body-userNo:\"\" data-load-body-dep-userNo:\"loginUserNo\"\n // data-load-body-companyNo:\"\" data-load-body-dep-companyNo:\"loginCompanyNo\"\n // data-load-body-parentId:\"\" data-load-body-dep-parentId:\"companyNo\"\n //\n // 生成的请求体:{\"userNo\":\"202007...\",\"companyNo\":\"201912...\",\"parentId\":\"1\"}\n //\n\n\n function buildDataLoadRequest(el, rawUrl) {\n var reqScope = resolveScope(el); // 自动注入登录用户参数供 resolveUrl 使用(必须在 resolveUrl 之前)\n\n if (!window.__tokuiParams) {\n var auth = (0, _request.getAuth)();\n window.__tokuiParams = {};\n if (auth.userno) window.__tokuiParams.loginUserNo = auth.userno;else if (auth.user) window.__tokuiParams.loginUserNo = auth.user;\n if (auth.companyNo) window.__tokuiParams.loginCompanyNo = auth.companyNo;\n }\n\n var resolvedUrl = resolveUrl(rawUrl, reqScope); // TokUI 把 data-* 属性渲染到外层 .tokui-field 上,而不是内部 <select> 上。\n // 所以如果当前元素没有 data-load-method,向上找父级 .tokui-field。\n\n var target = el;\n\n if (target && !target.getAttribute('data-load-method') && target.parentElement && target.parentElement.getAttribute('data-load')) {\n target = target.parentElement;\n }\n\n var method = (target && target.getAttribute('data-load-method') || 'GET').toUpperCase();\n var opts = {\n method: method\n };\n\n if (method !== 'GET') {\n // 优先收集 data-load-body-* 前缀属性(从 target 上读)\n // 两种子类型:\n // data-load-body-{field} → 静态值 / 来自 resolveUrl 的 ${...}\n // data-load-body-dep-{field} → 级联依赖声明,值 = 目标元素ID,运行时从该元素读取选中值\n var bodyObj = {};\n var bodyDeps = {}; // { fieldName: depElementId }\n\n var hasBodyFields = false;\n\n if (target && target.attributes) {\n for (var i = 0; i < target.attributes.length; i++) {\n var attr = target.attributes[i];\n if (!attr.name || attr.name.indexOf('data-load-body-') !== 0) continue;\n var suffix = attr.name.substring('data-load-body-'.length);\n if (!suffix) continue; // data-load-body-dep-{field} → 级联依赖\n\n if (suffix.indexOf('dep-') === 0) {\n var depField = suffix.substring('dep-'.length);\n\n if (depField) {\n bodyDeps[normalizeBodyKey(depField)] = attr.value.trim();\n }\n\n continue;\n } // data-load-body-{field} → 普通 body 字段\n\n\n var key = normalizeBodyKey(suffix);\n bodyObj[key] = resolveUrl(attr.value, reqScope);\n hasBodyFields = true;\n }\n } // 用级联依赖覆盖 body 字段:从 DOM 读选中 option 的 value\n\n\n for (var depField in bodyDeps) {\n if (bodyDeps.hasOwnProperty(depField)) {\n var depElId = bodyDeps[depField];\n var depEl = queryById(depElId, reqScope);\n\n if (depEl && depEl.tagName === 'SELECT') {\n // 从 select 取选中项 value\n var depVal = '';\n\n if (depEl.selectedIndex >= 0 && depEl.options[depEl.selectedIndex]) {\n depVal = depEl.options[depEl.selectedIndex].getAttribute('value') || depEl.options[depEl.selectedIndex].value || '';\n }\n\n bodyObj[depField] = depVal;\n hasBodyFields = true;\n } else if (window.__tokuiParams && window.__tokuiParams[depElId] !== undefined) {\n // 从全局参数映射取(loginUserNo / loginCompanyNo 等)\n bodyObj[depField] = String(window.__tokuiParams[depElId]);\n hasBodyFields = true;\n }\n }\n }\n\n if (hasBodyFields) {\n // 如果任一 body 字段解析后为空(如级联依赖尚未加载完成),跳过此次请求\n var hasEmpty = false;\n\n for (var bk in bodyObj) {\n if (bodyObj.hasOwnProperty(bk) && bodyObj[bk] === '') {\n hasEmpty = true;\n break;\n }\n }\n\n if (hasEmpty) {\n return {\n url: resolvedUrl,\n opts: opts,\n cacheKey: cacheKey,\n skip: true\n };\n }\n\n opts.body = JSON.stringify(bodyObj);\n } else {\n // 降级:兼容旧的 data-load-body 完整 JSON 语法\n var fullBody = target ? target.getAttribute('data-load-body') : null;\n\n if (fullBody) {\n opts.body = resolveUrl(fullBody, reqScope);\n }\n }\n }\n\n var cacheKey = method + ' ' + resolvedUrl + (opts.body ? ' ' + opts.body : '');\n return {\n url: resolvedUrl,\n opts: opts,\n cacheKey: cacheKey\n };\n } // ---------- 用统一 http 封装执行 data-load 请求(复用 token/解密/错误拦截) ----------\n\n\n function httpDataLoad(_x35) {\n return _httpDataLoad.apply(this, arguments);\n } // ---------- 替换 $字段名$ ----------\n\n\n function _httpDataLoad() {\n _httpDataLoad = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee26(req) {\n var method, body;\n return regeneratorRuntime.wrap(function _callee26$(_context26) {\n while (1) {\n switch (_context26.prev = _context26.next) {\n case 0:\n method = req.opts && req.opts.method || 'GET';\n\n if (!(method === 'GET')) {\n _context26.next = 3;\n break;\n }\n\n return _context26.abrupt(\"return\", _request.default.get(req.url));\n\n case 3:\n body = null;\n\n if (req.opts && req.opts.body) {\n try {\n body = JSON.parse(req.opts.body);\n } catch (e) {\n body = req.opts.body;\n }\n }\n\n if (!(method === 'DELETE')) {\n _context26.next = 7;\n break;\n }\n\n return _context26.abrupt(\"return\", _request.default.delete(req.url, {\n data: body\n }));\n\n case 7:\n if (!(method === 'PUT')) {\n _context26.next = 9;\n break;\n }\n\n return _context26.abrupt(\"return\", _request.default.put(req.url, body));\n\n case 9:\n return _context26.abrupt(\"return\", _request.default.post(req.url, body));\n\n case 10:\n case \"end\":\n return _context26.stop();\n }\n }\n }, _callee26);\n }));\n return _httpDataLoad.apply(this, arguments);\n }\n\n function replaceFieldTokens(text, data) {\n return text.replace(/\\$(\\w+)\\$/g, function (_, key) {\n return data[key] !== undefined && data[key] !== null ? String(data[key]) : '$' + key + '$';\n });\n } // ---------- 详情模式:替换 DOM 文本节点 + 属性 ----------\n\n\n function applyDetailData(container, data) {\n // 替换文本节点\n var walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, null, false);\n var node;\n\n while (node = walker.nextNode()) {\n if (/\\$\\w+\\$/.test(node.textContent)) {\n node.textContent = replaceFieldTokens(node.textContent, data);\n }\n } // 替换 src / href / poster 等属性中的 $字段$\n\n\n container.querySelectorAll('[src]').forEach(function (el) {\n var v = el.getAttribute('src');\n if (v && /\\$\\w+\\$/.test(v)) el.setAttribute('src', replaceFieldTokens(v, data));\n });\n container.querySelectorAll('[href]').forEach(function (el) {\n var v = el.getAttribute('href');\n if (v && /\\$\\w+\\$/.test(v)) el.setAttribute('href', replaceFieldTokens(v, data));\n });\n container.querySelectorAll('[poster]').forEach(function (el) {\n var v = el.getAttribute('poster');\n if (v && /\\$\\w+\\$/.test(v)) el.setAttribute('poster', replaceFieldTokens(v, data));\n });\n } // ---------- 列表模式:读取 [repeat] 模板 → 生成 DSL → feed ----------\n\n\n function renderListFromTemplate(url, container) {\n if (!url) return; // 如果 container(select)本身没有 data-load-* 属性,切换为父 .tokui-field\n\n var ctn = container;\n\n if (ctn && !ctn.getAttribute('data-load') && ctn.parentElement && ctn.parentElement.getAttribute('data-load')) {\n ctn = ctn.parentElement;\n }\n\n var tplEl = container.querySelector('script[type=\"text/tokui-template\"]');\n if (!tplEl || !tplEl.textContent) return;\n var template = tplEl.textContent;\n if (!template) return;\n (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee18() {\n var req, cached, json, items;\n return regeneratorRuntime.wrap(function _callee18$(_context18) {\n while (1) {\n switch (_context18.prev = _context18.next) {\n case 0:\n _context18.prev = 0;\n req = buildDataLoadRequest(ctn, url);\n\n if (!req.skip) {\n _context18.next = 4;\n break;\n }\n\n return _context18.abrupt(\"return\");\n\n case 4:\n cached = getCached(req.cacheKey);\n\n if (!cached) {\n _context18.next = 8;\n break;\n }\n\n applyListTemplate(container, tplEl, template, cached);\n return _context18.abrupt(\"return\");\n\n case 8:\n _context18.next = 10;\n return httpDataLoad(req);\n\n case 10:\n json = _context18.sent;\n items = resolveList(json);\n setCached(req.cacheKey, items);\n applyListTemplate(container, tplEl, template, items);\n _context18.next = 19;\n break;\n\n case 16:\n _context18.prev = 16;\n _context18.t0 = _context18[\"catch\"](0);\n console.warn('data-load-list 失败:', container.id, _context18.t0.message);\n\n case 19:\n case \"end\":\n return _context18.stop();\n }\n }\n }, _callee18, null, [[0, 16]]);\n }))();\n }\n\n function applyListTemplate(container, tplEl, template, items) {\n if (!items || !items.length) {\n console.warn('applyListTemplate: items 为空, id=' + container.id + ', tpl=', template);\n return;\n } // 自动检测模板 $key$ → API 返回字段名映射\n\n\n var fieldMap = {};\n var phs = template.match(/\\$(\\w+)\\$/g);\n\n if (phs) {\n phs.forEach(function (ph) {\n var key = ph.slice(1, -1);\n\n if (items[0].hasOwnProperty(key)) {\n fieldMap[key] = key;\n return;\n } // 大写不敏感匹配\n\n\n var lk = key.toLowerCase();\n\n for (var ik in items[0]) {\n if (ik.toLowerCase() === lk) {\n fieldMap[key] = ik;\n return;\n }\n } // 后缀匹配:$no$ → holidayNo, $name$ → holidayName\n\n\n for (var ik in items[0]) {\n if (ik.toLowerCase().indexOf(lk) >= 0 && ik.toLowerCase().endsWith(lk)) {\n fieldMap[key] = ik;\n return;\n }\n } // 无匹配则记录,后续自动替换为空字符串\n\n\n fieldMap[key] = key;\n });\n } // 打印映射关系,便于排查字段名不一致的问题\n\n\n var dsl = '';\n items.forEach(function (item) {\n dsl += template.replace(/\\$(\\w+)\\$/g, function (_, key) {\n var mapped = fieldMap[key] || key;\n var raw = item[mapped]; // 为 undefined/null 统一替换为空字符串,避免模板里出现 \"undefined\" 文本\n\n if (raw === undefined || raw === null || raw === '') return '';\n return String(raw).replace(/\"/g, '\\\\\"');\n });\n });\n\n if (!dsl) {\n console.warn('applyListTemplate: 生成的 DSL 为空, items=', items, 'template=', template);\n return;\n } // 移除模板元素\n\n\n if (tplEl && tplEl.parentNode) tplEl.parentNode.removeChild(tplEl);\n\n if (container.tagName === 'SELECT') {\n selectListFirst(container, dsl, items);\n } else {\n generalListFirst(container, tplEl, dsl);\n }\n }\n\n function selectListFirst(container, dsl, items) {\n // select → 直接生成 <option>\n var optHtml = '';\n dsl.replace(/\\[opt[^\\]]*\\]/g, function (m) {\n var v = (m.match(/v:\"([^\"]*)\"/) || m.match(/v:(\\S+)/) || [])[1] || '';\n var tx = (m.match(/tx:\"([^\"]*)\"/) || m.match(/tx:(\\S+)/) || [])[1] || '';\n optHtml += '<option value=\"' + v.replace(/\"/g, '"') + '\">' + tx + '</option>\\n ';\n });\n if (optHtml) container.innerHTML = optHtml; // ★ 将 API 返回的 items 数据以 data-* 属性存储到对应的 option 上\n // 按 value 匹配 option 与 item(避免 placeholder 导致索引错位)\n\n if (items && container.options) {\n var vKeys = ['no', 'holidayNo', 'id', 'value', 'code', 'v'];\n items.forEach(function (item) {\n var itemVal = '';\n\n for (var vi = 0; vi < vKeys.length; vi++) {\n if (item[vKeys[vi]] !== undefined) {\n itemVal = String(item[vKeys[vi]]);\n break;\n }\n }\n\n var matched = false;\n\n for (var oi = 0; oi < container.options.length; oi++) {\n if (container.options[oi].value === itemVal) {\n matched = true;\n Object.keys(item).forEach(function (key) {\n var val = item[key];\n if (val === undefined || val === null || (0, _typeof2.default)(val) === 'object') return;\n var attrName = 'data-' + key.replace(/([A-Z])/g, '-$1').toLowerCase();\n container.options[oi].setAttribute(attrName, String(val));\n });\n break;\n }\n }\n });\n } // data-default-label:按文本匹配选中默认项\n\n\n selectDefaultByLabel(container); // 选中后始终触发 change(驱动级联和 detail-url),无论是否匹配到默认值\n\n if (container.options && container.options.length > 0) {\n container.dispatchEvent(new Event('change', {\n bubbles: true\n }));\n }\n } // 按 data-default-label 选中匹配项\n\n\n function selectDefaultByLabel(el) {\n // TokUI 把 data-* 属性渲染在外层 .tokui-field 上,不在 <select> 上,所以优先从父级读取\n var label = el.getAttribute('data-default-label');\n\n if (!label && el.parentElement) {\n label = el.parentElement.getAttribute('data-default-label');\n }\n\n if (!label || !el.options) return; // 如果 label 仍含 ${} 占位符(后端未替换),跳过\n\n if (label.indexOf('${') >= 0) {\n console.warn('selectDefaultByLabel: label 未被替换, label=' + label + ', el.id=' + el.id);\n return;\n }\n\n for (var i = 0; i < el.options.length; i++) {\n if ((el.options[i].textContent || el.options[i].innerText || '').trim() === label.trim()) {\n el.selectedIndex = i;\n return;\n }\n }\n\n console.warn('selectDefaultByLabel: 未找到匹配项, label=\"%s\", options=', label, Array.prototype.map.call(el.options, function (o) {\n return o.textContent;\n }));\n }\n\n function generalListFirst(container, repeatEl, dsl) {\n // 清空容器(只保留 repeat 模板),feed 新 DSL\n var parent = container.parentNode;\n var idx = -1;\n\n if (parent) {\n var children = parent.children;\n\n for (var i = 0; i < children.length; i++) {\n if (children[i] === container) {\n idx = i;\n break;\n }\n }\n }\n\n container.innerHTML = '';\n if (repeatEl) container.appendChild(repeatEl);\n safeFeed(dsl);\n } // ---------- select 级联 ----------\n // 将 API 返回的值转为适合目标元素展示的格式\n // - 数字时间戳(毫秒/秒)→ YYYY-MM-DD(用于 datepicker)\n // - datetimepicker(按小时模式)→ YYYY-MM-DD HH:mm\n // - 值中带明确时间(非 00:00:00)→ 保留该时间\n // - 值只有日期(yyyy-MM-dd / 零点时间戳)→ 补全默认时间(开始 00:00,结束 23:59)\n // - 其他值 → 原样转字符串\n // ★ startHalf / endHalf(时段 1=上午 2=下午):按小时模式反显时,仅在默认边界时间\n // (开始 00:00 / 结束 23:59)上按对应时段修正(开始用 startHalf,结束用 endHalf:\n // 上午→结束 12:00,下午→开始 12:00,全天→00:00-23:59),其余具体时间不做修改\n\n\n function formatDateValue(rawVal, tgtEl, fieldKey, startHalf, endHalf) {\n if (rawVal === undefined || rawVal === null) return ''; // 目标是否为 datetimepicker(按小时模式,需要 yyyy-MM-dd HH:mm)\n\n var isDatetime = !!(tgtEl && (tgtEl.closest && tgtEl.closest('.tokui-datetimepicker') || tgtEl.querySelector && tgtEl.querySelector('.tokui-datetimepicker'))); // 字符串值(后端可能返回 \"yyyy-MM-dd\" / \"yyyy-MM-dd HH:mm\" / \"yyyy-MM-dd HH:mm:ss\")\n\n if (typeof rawVal === 'string') {\n var s = String(rawVal).trim();\n if (!s) return ''; // 带时间的字符串 → 统一为 yyyy-MM-dd HH:mm(默认边界时间按对应时段修正)\n\n var dtMatch = s.match(/^(\\d{4}-\\d{2}-\\d{2})[ T](\\d{2}):(\\d{2})/);\n if (dtMatch) return dtMatch[1] + ' ' + applyPeriodToDefaultTime(dtMatch[2] + ':' + dtMatch[3], fieldKey, startHalf, endHalf); // 纯日期字符串:datetimepicker 补全默认时间,datepicker 保持日期\n\n var dateMatch = s.match(/^(\\d{4}-\\d{2}-\\d{2})$/);\n\n if (dateMatch) {\n return isDatetime ? dateMatch[1] + ' ' + getDefaultTimeForField(fieldKey, tgtEl, startHalf, endHalf) : dateMatch[1];\n } // 非日期字符串:尝试按数字时间戳处理\n\n\n var numFromStr = Number(s);\n if (isNaN(numFromStr)) return s;\n rawVal = numFromStr;\n } // 数字时间戳\n\n\n var numVal = Number(rawVal);\n if (isNaN(numVal) || typeof rawVal === 'boolean') return String(rawVal); // 0 表示无日期(如无入职日期),返回空\n\n if (numVal === 0) return ''; // 判断是否为合理的时间戳范围(2000-01-01 ~ 2100-01-01)\n // 秒级时间戳:946684800 ~ 4102444800(10位)\n // 毫秒级时间戳:946684800000 ~ 4102444800000(13位)\n\n var isTimestamp = numVal > 946684800 && numVal < 4102444800000;\n if (!isTimestamp) return String(rawVal); // 秒级转毫秒\n\n var ms = numVal < 10000000000 ? numVal * 1000 : numVal;\n var d = new Date(ms);\n if (isNaN(d.getTime())) return String(rawVal);\n var yyyy = d.getFullYear();\n var mm = String(d.getMonth() + 1).padStart(2, '0');\n var dd = String(d.getDate()).padStart(2, '0');\n\n if (isDatetime) {\n // 有明确时间(非零点)→ 保留;否则用默认时间补全\n var hasTime = !(d.getHours() === 0 && d.getMinutes() === 0 && d.getSeconds() === 0);\n var hh = String(d.getHours()).padStart(2, '0');\n var mi = String(d.getMinutes()).padStart(2, '0');\n var timePart = hasTime ? hh + ':' + mi : getDefaultTimeForField(fieldKey, tgtEl, startHalf, endHalf); // 默认边界时间(00:00 / 23:59)按对应时段修正,其余具体时间不动\n\n return yyyy + '-' + mm + '-' + dd + ' ' + applyPeriodToDefaultTime(timePart, fieldKey, startHalf, endHalf);\n }\n\n return yyyy + '-' + mm + '-' + dd;\n } // 按小时模式 datetimepicker 的默认补全时间:\n // 开始时间 → 00:00,结束时间 → 23:59(对应默认范围 \"0.00-23.59\")\n // ★ startHalf / endHalf(时段 1=上午 2=下午):默认边界时间按对应时段修正,\n // 开始时间按 startHalf、结束时间按 endHalf 判断:\n // 上午(1) → 开始 00:00、结束 12:00;下午(2) → 开始 12:00、结束 23:59;\n // 全天(开始时段 1 + 结束时段 2) → 00:00-23:59\n\n\n function getDefaultTimeForField(fieldKey, tgtEl, startHalf, endHalf) {\n var key = fieldKey;\n\n if (!key && tgtEl) {\n key = tgtEl.id || tgtEl.name || '';\n\n if (!key) {\n var wrap = tgtEl.closest && (tgtEl.closest('[id]') || tgtEl.closest('[name]'));\n if (wrap) key = wrap.id || wrap.name || '';\n }\n }\n\n var k = key ? String(key).toLowerCase() : '';\n var baseTime = k.indexOf('end') >= 0 ? '23:59' : '00:00';\n return applyPeriodToDefaultTime(baseTime, k, startHalf, endHalf);\n } // ★ 小时模式反显:仅当补全/返回的时间是默认边界(开始 00:00 / 结束 23:59)时,\n // 按对应时段调整(开始时间用 startHalf、结束时间用 endHalf,1=上午, 2=下午):\n // 上午 → 结束时间改为 12:00;下午 → 开始时间改为 12:00;\n // 其余具体时间(非 00:00 / 23:59)原样返回,不做修改\n\n\n function applyPeriodToDefaultTime(timePart, fieldKey, startHalf, endHalf) {\n if (!timePart) return timePart;\n var k = fieldKey ? String(fieldKey).toLowerCase() : ''; // 仅对请假起止时间字段(startTime / endTime)修正,避免误伤 entryTime 等其他日期字段\n\n if (k.indexOf('starttime') < 0 && k.indexOf('endtime') < 0) return timePart;\n var isEnd = k.indexOf('endtime') >= 0;\n var p = parseInt(isEnd ? endHalf : startHalf, 10);\n if (p !== 1 && p !== 2) return timePart;\n if (isEnd && timePart === '23:59' && p === 1) return '12:00';\n if (!isEnd && timePart === '00:00' && p === 2) return '12:00';\n return timePart;\n } // ★ 从作用域 DOM 读取指定时段控件(startTimeHalf / endTimeHalf,1=上午, 2=下午)。\n // 实时表单的时段控件由 DSL 渲染成 select/hidden,reconcile 后即可查到,\n // 用于 applyDefaultValues/fixDateTimeDefaults 修正小时模式的默认边界时间\n\n\n function resolveHalfPeriod(scope, halfFieldId) {\n var root = scope && scope.querySelectorAll ? scope : container && container.querySelectorAll ? container : document;\n var el = queryById(halfFieldId, root);\n if (!el) return '';\n\n if (el.tagName === 'SELECT') {\n if (el.selectedIndex >= 0 && el.options[el.selectedIndex]) {\n return el.options[el.selectedIndex].getAttribute('value') || el.options[el.selectedIndex].value || '';\n }\n\n return '';\n }\n\n return el.value || '';\n }\n\n function getLeaveUnitText(minLeaveUnit) {\n if (minLeaveUnit === undefined || minLeaveUnit === null || minLeaveUnit === '') {\n return null;\n }\n\n var unit = String(minLeaveUnit).trim();\n if (unit === '0' || unit === '小时') return '小时';\n return '天';\n }\n\n function syncLeaveUnitSuffix(fieldId, unitText, scope) {\n if (!fieldId || !unitText) return;\n var root = scope || container; // ★ 定位字段元素:优先按 id,其次按 name。\n // 假期余额在 DSL 中可能只存在 name/数据字段而无 id=\"staffBalance\",\n // 若仅按 id 查找会导致找不到元素而直接 return,单位永远不更新。\n\n var fieldEl = queryById(fieldId, root);\n\n if (!fieldEl && root && root.querySelector) {\n fieldEl = root.querySelector('[name=\"' + fieldId + '\"]') || root.querySelector('[id=\"' + fieldId + '\"]');\n }\n\n if (!fieldEl) {\n // ★ staffBalance 在 tokui 表单里可能只是纯数据字段,没有 id/name 对应的 DOM 元素,\n // 页面上可见的余额块由标签\"假期余额\"渲染。改按标签定位其容器,再替换容器内\"天/小时\"单位文本。\n if (fieldId === 'staffBalance' && root && root.querySelectorAll) {\n var labelText = '假期余额';\n var labelEls = root.querySelectorAll('*');\n\n for (var liA = 0; liA < labelEls.length; liA++) {\n var leA = labelEls[liA];\n if (leA.children && leA.children.length) continue; // 只匹配叶子标签\n\n if ((leA.textContent || '').replace(/[::]/g, '').trim() !== labelText) continue;\n var balWrap = leA.closest('.tokui-field') || leA.parentElement;\n if (!balWrap) continue; // 先找叶子文本正好是\"天\"/\"小时\"的单位\n\n var unitEls = balWrap.querySelectorAll('*');\n\n for (var uiA = 0; uiA < unitEls.length; uiA++) {\n var ueA = unitEls[uiA];\n if (ueA.children && ueA.children.length) continue;\n var utA = (ueA.textContent || '').trim();\n\n if (utA === '天' || utA === '小时') {\n ueA.textContent = unitText;\n return;\n }\n } // 再兼容\"200 天\"在同叶子的情况\n\n\n for (var ujA = 0; ujA < unitEls.length; ujA++) {\n var ueB = unitEls[ujA];\n if (ueB.children && ueB.children.length) continue;\n var tB = (ueB.textContent || '').trim();\n\n if (/[\\d.,]+\\s*(天|小时)$/.test(tB)) {\n ueB.textContent = tB.replace(/[\\s]*(天|小时)$/, ' ' + unitText);\n return;\n }\n }\n\n break;\n }\n }\n\n return;\n }\n\n var fieldWrapper = fieldEl.closest('.tokui-field') || fieldEl.parentElement;\n if (!fieldWrapper) return; // ★ 移除旧的自定义单位后缀,避免多次切换叠加多个单位\n\n var oldUnits = fieldWrapper.querySelectorAll('.tokui-duration-unit');\n\n for (var oi = 0; oi < oldUnits.length; oi++) {\n oldUnits[oi].remove();\n }\n\n var appendEl = fieldWrapper.querySelector('.tokui-input-group__append, .tokui-append, .el-input-group__append');\n\n if (!appendEl) {\n var children = fieldWrapper.querySelectorAll('span, div');\n\n for (var ci = children.length - 1; ci >= 0; ci--) {\n if (children[ci].textContent && children[ci].textContent.trim() && !children[ci].querySelector('input')) {\n appendEl = children[ci];\n break;\n }\n }\n }\n\n if (appendEl) {\n appendEl.textContent = unitText;\n return;\n } // ★ 纯文本型字段(如放假余额以 \"N 天\" 文本展示,无独立 append):保留数字,仅替换单位\n\n\n var isFormControl = fieldEl.tagName === 'INPUT' || fieldEl.tagName === 'SELECT' || fieldEl.tagName === 'TEXTAREA';\n\n if (!isFormControl) {\n var cur = (fieldEl.textContent || '').trim();\n var numMatch = cur.match(/^([\\d.,]+)\\s*(天|小时)?/);\n\n if (numMatch && numMatch[1]) {\n fieldEl.textContent = numMatch[1] + ' ' + unitText;\n return;\n }\n } // ★ 遍历字段容器:找一个叶子节点文本正好是\"天\"/\"小时\"的单位标签并覆盖\n // (余额为 input,但其单位的\"天\"由 tokui 用不定 class 的文本节点渲染,上面的选择器匹配不到,\n // 这里直接按文本内容命中,才能替换掉页面上真正显示的旧单位)\n\n\n var leafNodes = fieldWrapper.querySelectorAll('*');\n\n for (var li = 0; li < leafNodes.length; li++) {\n var leaf = leafNodes[li];\n if (leaf.children && leaf.children.length) continue; // 只处理叶子\n\n var lt = (leaf.textContent || '').trim();\n\n if (lt === '天' || lt === '小时') {\n leaf.textContent = unitText;\n return;\n }\n }\n\n if (!isFormControl && fieldEl.textContent && /^[\\d.,]+\\s*(天|小时)/.test(fieldEl.textContent.trim())) {\n var cur2 = fieldEl.textContent.trim();\n fieldEl.textContent = cur2.replace(/[\\s]*(天|小时)$/, ' ' + unitText);\n return;\n } // ★ 无现成后缀元素时兜底新增后缀 span,保证假期余额与时长使用同一单位文本\n\n\n appendEl = document.createElement('span');\n appendEl.className = 'tokui-duration-unit';\n appendEl.textContent = unitText;\n appendEl.style.cssText = 'margin-left:6px;color:#909399;font-size:14px;white-space:nowrap;';\n fieldWrapper.appendChild(appendEl);\n }\n\n function refreshHolidayUnitSuffixFromHiddenField(scope) {\n // ★ 余额单位与时长计算保持同源:优先取 duration 字段 data-calc-duration-body-dep-minLeaveUnit\n // 指向的字段值,其次 _holidayType(与 calcLeaveDuration 的 minLeaveUnit 兜底一致),\n // 避免切换请假类型后\"时长单位变了、假期余额单位没变\"。\n var unitText = getLeaveUnitText(resolveLeaveMinUnit(scope));\n if (!unitText) return null;\n syncLeaveUnitSuffix('duration', unitText, scope);\n syncLeaveUnitSuffix('staffBalance', unitText, scope);\n return unitText;\n } // 与 calcLeaveDuration 的 minLeaveUnit 解析保持一致,作为时长/余额单位的唯一来源\n\n\n function resolveLeaveMinUnit(scope) {\n var root = scope || container;\n var durationField = queryById('duration', root);\n\n if (durationField) {\n var depMinId = durationField.getAttribute('data-calc-duration-body-dep-minLeaveUnit');\n\n if (depMinId) {\n var v = readFieldValueById(depMinId, root);\n if (v !== undefined && v !== null && String(v) !== '') return String(v);\n }\n }\n\n var htEl = queryById('_holidayType', root);\n if (htEl && htEl.value !== undefined && htEl.value !== null && String(htEl.value) !== '') return String(htEl.value);\n return null;\n }\n\n function setupCascade(el) {\n // 如果 select 本身没有 data-load(在父 .tokui-field 上),切换为父元素读取属性\n var origin = el;\n\n if (el && !el.getAttribute('data-load') && el.parentElement && el.parentElement.getAttribute('data-load')) {\n el = el.parentElement;\n } // ★ 多表单隔离:解析当前元素所属的表单容器作用域,所有 DOM 查询都限定在此作用域内,\n // 避免 document.getElementById/querySelector 跨消息串扰\n\n\n var cascadeScope = resolveScope(origin || el); // 处理 data-load-detail-url:选择后获取详情(如假期余额)\n // data-load-detail-target 指定单个目标元素 ID\n // data-load-detail-field 指定用返回数据的哪个字段值\n // data-load-detail-map 支持多字段映射,逗号分隔:\"fromField1:toElementId1,fromField2:toElementId2\"\n\n var detailUrl = el.getAttribute('data-load-detail-url');\n var detailTarget = el.getAttribute('data-load-detail-target');\n var detailField = el.getAttribute('data-load-detail-field') || 'value';\n var detailMapRaw = el.getAttribute('data-load-detail-map');\n var detailMap = null;\n\n if (detailMapRaw) {\n // 尝试 JSON 解析;失败则按逗号分隔的 key:value 格式解析\n try {\n detailMap = JSON.parse(detailMapRaw);\n } catch (e) {\n detailMap = {};\n detailMapRaw.split(',').forEach(function (pair) {\n var colonIdx = pair.indexOf(':');\n\n if (colonIdx > 0) {\n var k = pair.substring(0, colonIdx).trim();\n var v = pair.substring(colonIdx + 1).trim();\n if (k && v) detailMap[k] = v;\n }\n });\n }\n }\n\n if (detailUrl && (detailTarget || detailMap)) {\n (function (sel, urlTpl, tgtId, field, dMap) {\n sel.addEventListener('change', function () {\n if (sel.selectedIndex < 0 || !sel.options[sel.selectedIndex]) return;\n var val = sel.options[sel.selectedIndex].getAttribute('value') || sel.options[sel.selectedIndex].value || '';\n if (!val) return;\n var finalUrl = urlTpl.replace('{value}', encodeURIComponent(val));\n\n _request.default.get(finalUrl).then(function (json) {\n if (!json) {\n console.warn('[CondVis] detail fetch 返回空, url=' + finalUrl);\n return;\n }\n\n var data = json.data !== undefined ? json.data : json;\n\n if (!data || (0, _typeof2.default)(data) !== 'object') {\n console.warn('[CondVis] detail data 不是对象:', data);\n return;\n } // 优先处理多字段映射 data-load-detail-map\n\n\n if (dMap) {\n Object.keys(dMap).forEach(function (srcKey) {\n var tgtElId = dMap[srcKey];\n if (!tgtElId) return;\n var tgtEl = queryById(tgtElId, cascadeScope);\n var srcVal = data[srcKey]; // ★ 修复:detail 返回空值时清空 _leaveType 旧值,\n // 避免切换非年假类型后\"入职时间\"(data-visible-when:_leaveType:年假)仍显示\n\n if (tgtElId === '_leaveType' && (srcVal === undefined || srcVal === null || srcVal === '')) {\n if (tgtEl) {\n tgtEl.value = '';\n\n try {\n tgtEl.dispatchEvent(new Event('change', {\n bubbles: true\n }));\n } catch (e2) {}\n }\n\n return;\n }\n\n if (srcVal === undefined || srcVal === null) return;\n\n if (tgtEl) {\n // 仅对日期选择器字段做时间戳→日期格式转换(如 entryTime)\n // .closest 只查祖先,若 id 在外层 .tokui-field 上则需查后代\n var isDateField = tgtEl.closest('.tokui-datepicker') || tgtEl.closest('.tokui-datetimepicker') || tgtEl.querySelector('.tokui-datepicker') || tgtEl.querySelector('.tokui-datetimepicker');\n var tagName = tgtEl.tagName;\n var isFormControl = tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA';\n\n if (isDateField) {\n var formattedVal = formatDateValue(srcVal, tgtEl, tgtElId); // 找到日期选择器内部真正的 input 元素,设置值并触发 change\n\n var dateInput = isFormControl ? tgtEl : tgtEl.querySelector('.tokui-datepicker input') || tgtEl.querySelector('.tokui-datetimepicker input') || tgtEl.querySelector('input');\n\n if (dateInput) {\n dateInput.value = formattedVal;\n dateInput.dispatchEvent(new Event('change', {\n bubbles: true\n }));\n dateInput.dispatchEvent(new Event('input', {\n bubbles: true\n }));\n } else {\n tgtEl.value = formattedVal;\n tgtEl.dispatchEvent(new Event('change', {\n bubbles: true\n }));\n }\n } else if (isFormControl) {\n tgtEl.value = String(srcVal);\n tgtEl.dispatchEvent(new Event('change', {\n bubbles: true\n }));\n } else {\n tgtEl.textContent = String(srcVal);\n }\n } else if (tgtElId.charAt(0) === '_') {\n // 隐藏辅助字段(如 _holidayType, _leaveType, _balanceMode):创建 hidden input\n var hiddenEl = queryById(tgtElId, cascadeScope);\n\n if (!hiddenEl) {\n hiddenEl = document.createElement('input');\n hiddenEl.type = 'hidden';\n hiddenEl.id = tgtElId;\n hiddenEl.name = tgtElId;\n var formEl = sel.closest('form') || sel.closest('[data-form-id]');\n if (formEl) formEl.appendChild(hiddenEl);else (cascadeScope && cascadeScope.appendChild ? cascadeScope : document.body).appendChild(hiddenEl);\n }\n\n hiddenEl.value = String(srcVal); // 触发 change 事件 + 手动调用条件更新(hidden input 不自动触发 change)\n\n try {\n hiddenEl.dispatchEvent(new Event('change', {\n bubbles: true\n }));\n } catch (e2) {}\n }\n }); // 多字段映射完成后触发条件显示更新\n\n updateConditionalVisibility(cascadeScope);\n\n if (dMap.minLeaveUnit || dMap._holidayType) {\n refreshHolidayUnitSuffixFromHiddenField(cascadeScope);\n var stEl = queryById('startTime', cascadeScope);\n var etEl = queryById('endTime', cascadeScope);\n var durEl = queryById('duration', cascadeScope); // ★ 只在原有值为空时清空,避免编辑模式下清除已回填的起止时间\n\n if (stEl && !stEl.value) stEl.value = '';\n if (etEl && !etEl.value) etEl.value = '';\n if (durEl) durEl.value = '';\n } // ★ 起止时间回填后,主动触发时长计算\n // (data-load-detail-map 的 change 事件委托可能因 DOM 结构未找到 clk: 处理器,\n // 此处作为兜底,确保反显后 duration 自动计算)\n\n\n var _stEl = queryById('startTime', cascadeScope);\n\n var _etEl = queryById('endTime', cascadeScope);\n\n if (_stEl && _etEl && _stEl.value && _etEl.value) {\n var clkHandler = _clkHandlers['calcLeaveDuration'];\n\n if (clkHandler) {\n setTimeout(function () {\n try {\n clkHandler({}, new Event('change'), _etEl);\n } catch (e2) {}\n }, 100);\n }\n }\n\n return; // dMap 模式完成,跳过单字段逻辑\n } // 单字段模式(data-load-detail-target)\n\n\n if (!tgtId) return;\n var tgt = queryById(tgtId, cascadeScope);\n if (!tgt) return; // 目标可能是 input/select/textarea,也可能是 span/div 等文本元素\n\n var tgName = tgt.tagName;\n var isFC = tgName === 'INPUT' || tgName === 'SELECT' || tgName === 'TEXTAREA';\n\n if (field !== 'value' && (0, _typeof2.default)(data) === 'object' && data !== null) {\n if (data[field] !== undefined) {\n if (isFC) {\n tgt.value = String(data[field]);\n } else {\n tgt.textContent = String(data[field]);\n }\n }\n\n return;\n } // 默认:取第一个数值字段\n\n\n var keys = Object.keys(data);\n\n for (var ki = 0; ki < keys.length; ki++) {\n if (typeof data[keys[ki]] === 'number' || typeof data[keys[ki]] === 'string' && !isNaN(Number(data[keys[ki]]))) {\n if (isFC) {\n tgt.value = String(data[keys[ki]]);\n } else {\n tgt.textContent = String(data[keys[ki]]);\n }\n\n break;\n }\n }\n }).catch(function (e) {\n console.warn('data-load-detail-url 失败:', e.message);\n });\n });\n })(origin, detailUrl, detailTarget, detailField, detailMap);\n } // ★ 从 option 的 data-* 属性填充隐藏辅助字段(如 _holidayType 的 minLeaveUnit)\n // 当 data-load 已返回完整数据时,无需依赖 data-load-detail-url 再次请求\n\n\n (function (sel) {\n sel.addEventListener('change', function () {\n if (!sel || !sel.options || sel.selectedIndex < 0 || !sel.options[sel.selectedIndex]) return; // 切换请假类型时,清除底部旧的报错/警告提示\n // safeFeed 可能将 feedback 挂到 form 和 submitBtn 的 LCA 上,需从更高层级搜索\n\n var _submitBtn = cascadeScope.querySelector('[data-tokui-sub=\"formSubmit\"]');\n\n var _searchRoot = sel.closest('[data-form-id]') || sel.closest('form') || sel.parentElement;\n\n if (_submitBtn) {\n var _p = sel.parentElement;\n\n while (_p && _p !== document.body) {\n if (_p.contains(_submitBtn)) break;\n _p = _p.parentElement;\n }\n\n if (_p) _searchRoot = _p;\n }\n\n if (_searchRoot) {\n var _oldFbs = _searchRoot.querySelectorAll('.tokui-validator-feedback');\n\n _oldFbs.forEach(function (el) {\n el.remove();\n });\n }\n\n var opt = sel.options[sel.selectedIndex]; // 已知的隐藏字段映射:data-属性名 → 目标元素 ID\n\n var hiddenFieldMap = {\n 'min-leave-unit': '_holidayType',\n 'leave-type': '_leaveType',\n 'balance-mode': '_balanceMode'\n }; // ★ 仅当本下拉是请假类型选择器(任一 option 带请假相关 data 属性)时才处理 _leaveType,\n // 避免误伤 dept/company 等其他级联下拉(它们的 option 无这些属性,会误清 _leaveType)\n\n var _isLeaveTypeSelect = false;\n\n for (var _loi = 0; _loi < sel.options.length; _loi++) {\n var _opt2 = sel.options[_loi];\n\n if (_opt2.getAttribute('data-leave-type') || _opt2.getAttribute('data-holiday-name') || _opt2.getAttribute('data-balance-mode')) {\n _isLeaveTypeSelect = true;\n break;\n }\n }\n\n Object.keys(hiddenFieldMap).forEach(function (dataKey) {\n var tgtId = hiddenFieldMap[dataKey];\n var attrVal = opt.getAttribute('data-' + dataKey);\n var isLeaveTypeField = tgtId === '_leaveType';\n if (isLeaveTypeField && !_isLeaveTypeSelect) return; // ★ 修复:切换请假类型时 _leaveType 旧值残留(如仍是\"年假\"),\n // 会导致非年假类型下\"入职时间\"(data-visible-when:_leaveType:年假)仍显示。\n // 新选中项缺 data-leave-type 时清空旧值,条件显示据此正确隐藏。\n\n if (isLeaveTypeField && (attrVal === null || attrVal === undefined || String(attrVal) === '')) {\n var _ltEl0 = queryById(tgtId, cascadeScope);\n\n if (_ltEl0) {\n _ltEl0.value = '';\n\n try {\n _ltEl0.dispatchEvent(new Event('change', {\n bubbles: true\n }));\n } catch (e2) {}\n }\n\n return;\n }\n\n if (attrVal === null || attrVal === undefined) return;\n var tgtEl = queryById(tgtId, cascadeScope);\n\n if (!tgtEl) {\n tgtEl = document.createElement('input');\n tgtEl.type = 'hidden';\n tgtEl.id = tgtId;\n tgtEl.name = tgtId;\n var formEl = sel.closest('form') || sel.closest('[data-form-id]');\n if (formEl) formEl.appendChild(tgtEl);else (cascadeScope && cascadeScope.appendChild ? cascadeScope : document.body).appendChild(tgtEl);\n }\n\n tgtEl.value = attrVal;\n\n try {\n tgtEl.dispatchEvent(new Event('change', {\n bubbles: true\n }));\n } catch (e2) {}\n }); // ★ 修复(安卓):_leaveType 的 change 监听由 MutationObserver/定时器异步注册,\n // 安卓上可能因监听未就绪或 `.value` 属性不触发 MutationObserver 而漏刷新,\n // 导致切到非年假时\"入职时间\"(data-visible-when:_leaveType:年假)仍显示。\n // 这里直接同步刷新一次条件显示,消除对事件监听时序的依赖。\n\n updateConditionalVisibility(cascadeScope); // 从 option data 属性读取 holidayName 并填充 _holidayName 隐藏字段\n\n var holidayName = opt.getAttribute('data-holiday-name');\n\n if (holidayName) {\n var hnEl = queryById('_holidayName', cascadeScope);\n\n if (!hnEl) {\n hnEl = document.createElement('input');\n hnEl.type = 'hidden';\n hnEl.id = '_holidayName';\n hnEl.name = '_holidayName';\n var fe = sel.closest('form') || sel.closest('[data-form-id]');\n if (fe) fe.appendChild(hnEl);else (cascadeScope && cascadeScope.appendChild ? cascadeScope : document.body).appendChild(hnEl);\n }\n\n hnEl.value = holidayName;\n } // 同步假期单位后缀显示\n\n\n refreshHolidayUnitSuffixFromHiddenField(cascadeScope);\n });\n })(origin); // 收集所有级联依赖的元素 ID\n\n\n var depIds = []; // 方式1(推荐):data-load-body-dep-{field} 属性,值 = 目标元素ID,TokUI 不会篡改\n\n if (el && el.attributes) {\n for (var i = 0; i < el.attributes.length; i++) {\n var attr = el.attributes[i];\n\n if (attr.name && attr.name.indexOf('data-load-body-dep-') === 0) {\n var id = attr.value.trim();\n if (id && depIds.indexOf(id) < 0) depIds.push(id);\n }\n }\n } // 方式2(降级):从 data-load URL、data-load-body、data-load-body-* 中扫描 ${元素ID}\n\n\n var fallbackAttrs = [el.getAttribute('data-load'), el.getAttribute('data-load-body')];\n\n if (el && el.attributes) {\n for (var i = 0; i < el.attributes.length; i++) {\n var a = el.attributes[i];\n\n if (a.name && a.name.indexOf('data-load-body-') === 0 && a.name.indexOf('data-load-body-dep-') !== 0) {\n fallbackAttrs.push(a.value);\n }\n }\n }\n\n fallbackAttrs.forEach(function (src) {\n if (!src) return;\n src.replace(/\\$\\{(\\w+)\\}/g, function (_, id) {\n if (depIds.indexOf(id) < 0) depIds.push(id);\n });\n }); // 注册 change 监听\n\n depIds.forEach(function (depId) {\n var depEl = queryById(depId, cascadeScope);\n if (depEl) depEl.addEventListener('change', function () {\n var r = origin.querySelector('script[type=\"text/tokui-template\"]');\n\n if (r && r.textContent) {\n renderListFromTemplate(el.getAttribute('data-load'), origin);\n } else {\n loadDataFromAttr(origin);\n }\n });\n });\n } // ============ 条件显示/隐藏 ============\n // 支持 data-visible-when:\"fieldId:value\" — 当指定字段值等于 value 时显示\n // 支持 data-visible-when-not:\"fieldId:value\" — 当指定字段值不等于 value 时显示\n // value 支持 | 分隔多值,如 \"_leaveType:年假|产假\" 表示匹配任一值即显示\n // 隐藏的元素添加 .tokui-conditional-hidden 类\n // 已监听的字段集合,避免重复绑定\n\n\n var _condWatchedFields = {};\n\n function updateConditionalVisibility(scope) {\n var root = scope && scope.querySelectorAll ? scope : container && container.querySelectorAll ? container : document;\n var allWhen = root.querySelectorAll('[data-visible-when]');\n allWhen.forEach(function (el) {\n var rule = el.getAttribute('data-visible-when') || '';\n var parts = rule.split(':');\n if (parts.length < 2) return;\n var fieldId = parts[0].trim();\n var expectedVal = parts.slice(1).join(':').trim();\n var sourceEl = queryById(fieldId, root);\n\n if (!sourceEl) {\n // 源字段不存在 → 隐藏(保守策略)\n el.classList.add('tokui-conditional-hidden');\n return;\n }\n\n var currentVal = String(sourceEl.value || '');\n var match = expectedVal.split('|').some(function (v) {\n return currentVal === v.trim();\n });\n\n if (match) {\n el.classList.remove('tokui-conditional-hidden');\n } else {\n el.classList.add('tokui-conditional-hidden');\n }\n });\n var allWhenNot = root.querySelectorAll('[data-visible-when-not]');\n allWhenNot.forEach(function (el) {\n var rule = el.getAttribute('data-visible-when-not') || '';\n var parts = rule.split(':');\n if (parts.length < 2) return;\n var fieldId = parts[0].trim();\n var excludedVal = parts.slice(1).join(':').trim();\n var sourceEl = queryById(fieldId, root);\n\n if (!sourceEl) {\n // 源字段不存在 → 显示(when-not 的保守策略:不排除则显示)\n el.classList.remove('tokui-conditional-hidden');\n return;\n }\n\n var currentVal = String(sourceEl.value || '');\n var match = excludedVal.split('|').some(function (v) {\n return currentVal === v.trim();\n });\n\n if (match) {\n el.classList.add('tokui-conditional-hidden');\n } else {\n el.classList.remove('tokui-conditional-hidden');\n }\n });\n } // 为指定字段注册条件监听(change + input + MutationObserver)\n\n\n function watchCondField(fieldId, scope) {\n if (_condWatchedFields[fieldId]) return;\n _condWatchedFields[fieldId] = true;\n var root = scope && scope.querySelectorAll ? scope : container && container.querySelectorAll ? container : document;\n var el = queryById(fieldId, root);\n if (!el) return;\n\n var _scope = scope || root; // change / input 事件\n\n\n el.addEventListener('change', function () {\n updateConditionalVisibility(_scope);\n });\n el.addEventListener('input', function () {\n updateConditionalVisibility(_scope);\n }); // MutationObserver 监听 value 属性变化(适用于 hidden input)\n\n if (el.type === 'hidden' || el.tagName === 'INPUT') {\n try {\n var obs = new MutationObserver(function (mutations) {\n for (var i = 0; i < mutations.length; i++) {\n if (mutations[i].attributeName === 'value') {\n updateConditionalVisibility(_scope);\n break;\n }\n }\n });\n obs.observe(el, {\n attributes: true,\n attributeFilter: ['value']\n });\n } catch (e) {}\n }\n } // 初始化条件显示:扫描所有 data-visible-when / data-visible-when-not 规则,\n // 收集被引用的字段 ID,为已存在的字段注册监听。\n // 由于 TokUI 表单是动态渲染的,此函数可安全重复调用。\n // ★ 应用 data-default-value:TokUI 渲染后可能清空 input value,需重新设置\n\n\n function applyDefaultValues(scope) {\n var root = scope && scope.querySelectorAll ? scope : container && container.querySelectorAll ? container : document; // ★ 开始/结束时段(1=上午, 2=下午):小时模式纯日期补全时按对应时段修正默认边界时间\n\n var startHalf = resolveHalfPeriod(root, 'startTimeHalf');\n var endHalf = resolveHalfPeriod(root, 'endTimeHalf');\n var els = root.querySelectorAll('[data-default-value]');\n els.forEach(function (el) {\n var val = el.getAttribute('data-default-value');\n if (!val) return; // 找到真正的表单控件(el 本身可能是 .tokui-field 包装层)\n\n var target = el;\n\n if (el.tagName !== 'INPUT' && el.tagName !== 'TEXTAREA' && el.tagName !== 'SELECT') {\n target = el.querySelector('input, textarea, select');\n if (!target) return;\n }\n\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {\n // ★ datetimepicker 且值为纯日期(YYYY-MM-DD)时,补全默认时间(按结束时段修正)\n var isDatetime = target.classList && target.classList.contains('tokui-datetimepicker-input') || target.closest && target.closest('.tokui-datetimepicker');\n\n if (isDatetime && /^\\d{4}-\\d{2}-\\d{2}$/.test(val)) {\n var fieldId = target.id || target.name || '';\n val = val + ' ' + getDefaultTimeForField(fieldId, target, startHalf, endHalf);\n }\n\n if (!target.value || target.value === '') {\n target.value = val; // 同步隐藏 input\n\n if (isDatetime && target.id) {\n var wrapper = target.closest('.tokui-datetimepicker');\n\n if (wrapper) {\n var hiddenInput = wrapper.querySelector('input[type=\"hidden\"]');\n if (hiddenInput) hiddenInput.value = val;\n }\n }\n }\n } else if (target.tagName === 'SELECT') {\n for (var i = 0; i < target.options.length; i++) {\n if (target.options[i].value === val || target.options[i].getAttribute('value') === val) {\n target.selectedIndex = i;\n break;\n }\n }\n }\n });\n } // 已提交表单的反显:把提交时保存的 formData 回填到对应表单控件。\n // 不触发 change/input 事件,因此不会触发校验;配合 lockFormWithOverlay 遮罩使用。\n\n\n function applyFormData(scope, formData) {\n if (!scope || !formData) return;\n var fd = formData;\n\n if (typeof fd === 'string') {\n try {\n fd = JSON.parse(fd);\n } catch (e) {\n return;\n }\n }\n\n if (!fd || (0, _typeof2.default)(fd) !== 'object') return; // 后端可能返回小写字段名(如 starttime → startTime),统一映射\n\n var fieldKeyMap = {\n starttime: 'startTime',\n endtime: 'endTime',\n entrytime: 'entryTime',\n starttimestr: 'startTimeStr',\n endtimestr: 'endTimeStr',\n durationday: 'durationDay',\n minleaveunit: 'minLeaveUnit',\n holidayno: 'holidayNo',\n holidayname: 'holidayName',\n departno: 'departNo',\n companyno: 'companyNo',\n departmentno: 'departmentNo'\n }; // select 的显示名称映射:提交数据中 value 与文本的对应关系\n\n var selectNameMap = {\n type: fd.typeStr || fd.holidayName || fd.holidayname || '',\n departNo: fd.departName || fd.departname || '',\n companyNo: fd.companyName || fd.companyname || ''\n }; // ★ 开始/结束时段(1=上午, 2=下午):按小时模式反显起止时间时,仅用于修正默认边界时间\n // (开始 00:00 / 结束 23:59),用户说的具体时间不受影响。\n // 开始时间按 startTimeHalf 判断,结束时间按 endTimeHalf 判断(全天=1+2 → 00:00-23:59)\n\n var startHalf = fd.startTimeHalf !== undefined && fd.startTimeHalf !== null && fd.startTimeHalf !== '' ? fd.startTimeHalf : fd.starttimehalf !== undefined && fd.starttimehalf !== null ? fd.starttimehalf : '';\n var endHalf = fd.endTimeHalf !== undefined && fd.endTimeHalf !== null && fd.endTimeHalf !== '' ? fd.endTimeHalf : fd.endtimehalf !== undefined && fd.endtimehalf !== null ? fd.endtimehalf : '';\n Object.keys(fd).forEach(function (rawKey) {\n var val = fd[rawKey];\n if (val === undefined || val === null || val === '') return; // 字段名归一化:后端可能返回小写,映射为表单元素的 camelCase id\n\n var key = fieldKeyMap[rawKey.toLowerCase()] || rawKey;\n var el = queryById(key, scope); // 回退:按 [name] 查找(TokUI 隐藏 input 有 name 无 id)\n\n if (!el) {\n var byName = scope.querySelectorAll('[name]');\n\n for (var ni = 0; ni < byName.length; ni++) {\n if (byName[ni].name === key) {\n el = byName[ni];\n break;\n }\n }\n }\n\n if (!el) {\n if (key === 'startTime' || key === 'endTime' || key === 'entryTime') {\n console.warn('[Validator] applyFormData: 字段 ' + rawKey + '→' + key + ' 未找到DOM元素, val=' + val);\n }\n\n return;\n }\n\n var tag = el.tagName; // 若 id 挂在外层 .tokui-field 包装层上,取内部真实控件\n\n if (tag !== 'INPUT' && tag !== 'SELECT' && tag !== 'TEXTAREA') {\n var inner = el.querySelector && el.querySelector('input,select,textarea');\n if (!inner) return;\n el = inner;\n tag = el.tagName;\n }\n\n if (tag === 'SELECT') {\n var strVal = String(val);\n var matched = false;\n\n for (var i = 0; i < el.options.length; i++) {\n if (el.options[i].value === strVal || el.options[i].getAttribute('value') === strVal) {\n el.selectedIndex = i;\n matched = true;\n break;\n }\n } // skipDataLoad 时下拉未加载选项:用 value + 名称动态补一个 option 反显\n\n\n if (!matched) {\n var label = selectNameMap[key] || strVal;\n var opt = document.createElement('option');\n opt.value = strVal;\n opt.textContent = label;\n el.appendChild(opt);\n el.value = strVal;\n }\n\n return;\n }\n\n if (tag === 'INPUT' || tag === 'TEXTAREA') {\n if (el.type === 'checkbox' || el.type === 'radio') {\n el.checked = val === true || String(val) === 'true' || el.value === String(val);\n return;\n }\n\n var isDateField = el.closest('.tokui-datepicker') || el.closest('.tokui-datetimepicker');\n var formattedVal = isDateField ? formatDateValue(val, el, key, startHalf, endHalf) : String(val);\n el.value = formattedVal; // ★ 同步 TokUI 隐藏 input(datetimepicker/datepicker 有两个独立 input:\n // 可见 readonly input 持 id,隐藏 input 持 name,两者不会自动同步)\n\n if (isDateField && el.id) {\n var wrapper = el.closest('.tokui-datetimepicker') || el.closest('.tokui-datepicker');\n\n if (wrapper) {\n var hiddenInput = wrapper.querySelector('input[type=\"hidden\"][name=\"' + el.id + '\"]');\n\n if (hiddenInput) {\n hiddenInput.value = formattedVal;\n }\n }\n }\n }\n }); // 补卡详情反显:参照原项目先按日期生成时间选项,再恢复完整补卡时间。\n // 后端仅返回 patchTime 时间戳时,同时从该值派生 patchDate。\n\n var patchTimeField = queryById('patchTime', scope);\n var patchDateField = queryById('patchDate', scope);\n\n if (patchTimeField && patchTimeField._updatePatchTimeOptions) {\n var reflectedPatchTime = fd.patchTime !== undefined ? fd.patchTime : fd.patchtime;\n var reflectedPatchDate = fd.patchDate || fd.patchdate || reflectedPatchTime;\n var patchDateControl = patchDateField;\n\n if (patchDateControl && patchDateControl.tagName !== 'INPUT') {\n patchDateControl = patchDateControl.querySelector('.tokui-datepicker-input, input:not([type=\"hidden\"])');\n }\n\n var formattedPatchDate = formatDateValue(reflectedPatchDate, patchDateControl, 'patchDate', '', '');\n\n if (patchDateControl && formattedPatchDate) {\n patchDateControl.value = formattedPatchDate;\n var patchDateWrapper = patchDateControl.closest('.tokui-datepicker');\n var patchDateHidden = patchDateWrapper && patchDateWrapper.querySelector('input[type=\"hidden\"]');\n if (patchDateHidden) patchDateHidden.value = formattedPatchDate;\n }\n\n patchTimeField._updatePatchTimeOptions(formattedPatchDate, reflectedPatchTime);\n } // 回填完成后,若表单配置了审批,触发一次审批预览刷新, \n // 确保审批变量(type/departNo/durationDay 等)齐全后审批模块能正确展示。\n\n\n try {\n var apprEl = scope.querySelector('[data-approval-type]');\n\n if (apprEl) {\n var apprForm = apprEl.closest('form') || apprEl.closest('[data-form-id]') || apprEl;\n if (apprForm) refreshApprovalFlow(apprForm);\n }\n } catch (e) {\n console.warn('[Validator] applyFormData 后审批刷新失败:', e.message);\n }\n }\n\n function initConditionalVisibility(scope) {\n var root = scope && scope.querySelectorAll ? scope : container && container.querySelectorAll ? container : document;\n var watchedFields = {};\n var allWhen = root.querySelectorAll('[data-visible-when]');\n allWhen.forEach(function (el) {\n var rule = el.getAttribute('data-visible-when') || '';\n var parts = rule.split(':');\n if (parts.length < 2) return;\n var fieldId = parts[0].trim();\n watchedFields[fieldId] = true;\n });\n var allWhenNot = root.querySelectorAll('[data-visible-when-not]');\n allWhenNot.forEach(function (el) {\n var rule = el.getAttribute('data-visible-when-not') || '';\n var parts = rule.split(':');\n if (parts.length < 2) return;\n var fieldId = parts[0].trim();\n watchedFields[fieldId] = true;\n });\n Object.keys(watchedFields).forEach(function (fieldId) {\n watchCondField(fieldId, root);\n }); // 初始执行一次\n\n updateConditionalVisibility(root);\n } // 全局 MutationObserver:监听 DOM 新增的 data-visible-when / data-visible-when-not 元素,\n // 以及新增的 hidden input(如 _leaveType, _balanceMode),自动注册监听并刷新条件显示。\n\n\n var _condDomObserver = null;\n\n function startCondDomObserver() {\n if (_condDomObserver) return;\n\n try {\n _condDomObserver = new MutationObserver(function (mutations) {\n var scopesToRefresh = [];\n\n function addScope(scope) {\n if (scope && scopesToRefresh.indexOf(scope) < 0) scopesToRefresh.push(scope);\n }\n\n for (var i = 0; i < mutations.length; i++) {\n var added = mutations[i].addedNodes;\n if (!added) continue;\n\n for (var j = 0; j < added.length; j++) {\n var node = added[j];\n if (node.nodeType !== 1) continue;\n var nodeScope = resolveScope(node); // 新增的条件元素\n\n if (node.hasAttribute && (node.hasAttribute('data-visible-when') || node.hasAttribute('data-visible-when-not'))) {\n addScope(nodeScope);\n } // 新增的 input(可能是动态创建的 hidden field,如 _leaveType)\n\n\n if (node.tagName === 'INPUT' && node.id) {\n watchCondField(node.id, nodeScope);\n addScope(nodeScope);\n } // 子元素中也检查\n\n\n if (node.querySelectorAll) {\n var condEls = node.querySelectorAll('[data-visible-when], [data-visible-when-not]');\n if (condEls.length > 0) addScope(nodeScope);\n var inputs = node.querySelectorAll('input[id]');\n inputs.forEach(function (inp) {\n if (inp.id) watchCondField(inp.id, nodeScope);\n });\n }\n }\n }\n\n if (scopesToRefresh.length > 0) {\n // 延迟一帧执行,确保 DOM 完全就绪\n setTimeout(function () {\n scopesToRefresh.forEach(function (scope) {\n initConditionalVisibility(scope);\n });\n }, 50);\n }\n });\n var condObserveRoot = container && container.nodeType === 1 ? container : document.body;\n\n _condDomObserver.observe(condObserveRoot, {\n childList: true,\n subtree: true\n });\n } catch (e) {\n console.warn('[CondVis] MutationObserver 启动失败:', e.message);\n }\n } // ★ datetimepicker 日期补全:扫描所有 datetimepicker 可见 input,\n // 1) 值为纯日期(YYYY-MM-DD)→ 补全默认时间(start→00:00,end→23:59;按结束时段修正)\n // 2) 值已是默认边界时间(开始 00:00 / 结束 23:59)→ 按结束时段(1=上午, 2=下午)修正\n // 上午 → 结束 12:00;下午 → 开始 12:00;其余具体时间不动\n // 这是一个兜底函数,在渲染、applyDefaultValues、applyFormData 之后运行,\n // 确保无论值从哪条路径设置,最终 datetimepicker 都显示正确的时间。\n\n\n function fixDateTimeDefaults(scope) {\n var root = scope && scope.querySelectorAll ? scope : container && container.querySelectorAll ? container : document; // ★ 开始/结束时段(1=上午, 2=下午):小时模式反显时用于修正默认边界时间\n // (开始时间按 startTimeHalf、结束时间按 endTimeHalf 判断)\n\n var startHalf = resolveHalfPeriod(root, 'startTimeHalf');\n var endHalf = resolveHalfPeriod(root, 'endTimeHalf');\n var datetimepickers = root.querySelectorAll('.tokui-datetimepicker');\n datetimepickers.forEach(function (picker) {\n var visibleInput = picker.querySelector('.tokui-datetimepicker-input');\n if (!visibleInput) return;\n var val = visibleInput.value;\n if (!val) return; // 仅处理带日期的值(YYYY-MM-DD 或 YYYY-MM-DD HH:mm)\n\n var m = String(val).match(/^(\\d{4}-\\d{2}-\\d{2})[ T]?(\\d{2}:\\d{2})?/);\n if (!m) return;\n var fieldId = visibleInput.id || visibleInput.name || '';\n var datePart = m[1];\n var timePart = m[2] || '';\n var formattedVal = timePart ? datePart + ' ' + applyPeriodToDefaultTime(timePart, fieldId, startHalf, endHalf) : datePart + ' ' + getDefaultTimeForField(fieldId, visibleInput, startHalf, endHalf);\n if (visibleInput.value === formattedVal) return; // 值未变化,跳过写 DOM\n\n visibleInput.value = formattedVal; // 同步隐藏 input\n\n var hiddenInput = picker.querySelector('input[type=\"hidden\"]');\n if (hiddenInput) hiddenInput.value = formattedVal; // 更新 data-default-value(避免 applyDefaultValues 后回退)\n\n visibleInput.setAttribute('data-default-value', formattedVal);\n });\n } // 在 DOM 就绪后初始化\n\n\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', function () {\n applyDefaultValues(container);\n fixDateTimeDefaults(container);\n initConditionalVisibility(container);\n startCondDomObserver();\n });\n } else {\n setTimeout(function () {\n applyDefaultValues(container);\n fixDateTimeDefaults(container);\n initConditionalVisibility(container);\n startCondDomObserver();\n }, 100);\n } // 延迟再次应用默认值(TokUI 可能异步渲染覆盖)\n\n\n setTimeout(function () {\n applyDefaultValues(container);\n fixDateTimeDefaults(container);\n }, 500);\n setTimeout(function () {\n applyDefaultValues(container);\n fixDateTimeDefaults(container);\n }, 1500); // 审批流程初始化(已存在的表单)\n\n function initExistingApprovalForms(scope) {\n // 每次调用时也重新初始化条件显示(TokUI 表单可能刚渲染完)\n var root = scope && scope.querySelectorAll ? scope : container && container.querySelectorAll ? container : document;\n applyDefaultValues(root);\n initConditionalVisibility(root); // 已提交表单:跳过审批流程初始化(会请求审批人/组织等接口)\n\n if (options.skipDataLoad) return;\n var forms = root.querySelectorAll('[data-approval-type]');\n forms.forEach(function (el) {\n var form = el.closest('form') || el.closest('[data-form-id]') || el;\n\n if (form && !form._tvApprovalInited) {\n form._tvApprovalInited = true;\n setTimeout(function () {\n initApprovalFlow(form);\n }, 800);\n }\n });\n }\n\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', function () {\n initExistingApprovalForms(container);\n });\n } else {\n setTimeout(function () {\n initExistingApprovalForms(container);\n }, 500); // 额外重试:TokUI 表单可能需要更长时间渲染\n\n setTimeout(function () {\n initConditionalVisibility(container);\n }, 1000);\n setTimeout(function () {\n initConditionalVisibility(container);\n }, 2000);\n setTimeout(function () {\n initConditionalVisibility(container);\n }, 3000);\n } // ---------- select 旧路径(无 repeat 的后向兼容,供无补丁场景兜底) ----------\n\n\n function loadDataFromAttr(_x36) {\n return _loadDataFromAttr.apply(this, arguments);\n } // ---------- MutationObserver 检测新元素 ----------\n\n\n function _loadDataFromAttr() {\n _loadDataFromAttr = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee27(el) {\n var loadEl, url, dslLabelField, dslValueField, req, cached, options, rawItems, json, vKeys;\n return regeneratorRuntime.wrap(function _callee27$(_context27) {\n while (1) {\n switch (_context27.prev = _context27.next) {\n case 0:\n // 如果 select 本身没有 data-load(在父 .tokui-field 上),向上找\n loadEl = el;\n\n if (loadEl && !loadEl.getAttribute('data-load') && loadEl.parentElement && loadEl.parentElement.getAttribute('data-load')) {\n loadEl = loadEl.parentElement;\n }\n\n url = loadEl.getAttribute('data-load');\n\n if (url) {\n _context27.next = 5;\n break;\n }\n\n return _context27.abrupt(\"return\");\n\n case 5:\n // ★ DSL 可配置选项字段:data-label-field 指定响应数据中作为选项展示文本的字段,\n // data-value-field 指定作为选项 value 的字段(如会议号下拉展示 nameDetail)\n dslLabelField = (loadEl.getAttribute('data-label-field') || el.getAttribute('data-label-field') || '').trim();\n dslValueField = (loadEl.getAttribute('data-value-field') || el.getAttribute('data-value-field') || '').trim();\n _context27.prev = 7;\n req = buildDataLoadRequest(loadEl, url);\n\n if (!req.skip) {\n _context27.next = 11;\n break;\n }\n\n return _context27.abrupt(\"return\");\n\n case 11:\n // 缓存结构:旧版为纯数组(options),新版升级为 {options, items}(保留原始数据以支持字段覆盖)\n cached = getCached(req.cacheKey);\n options = null;\n rawItems = null;\n\n if (cached && Array.isArray(cached)) {\n options = cached;\n } else if (cached) {\n options = cached.options;\n rawItems = cached.items;\n }\n\n if (options) {\n _context27.next = 22;\n break;\n }\n\n _context27.next = 18;\n return httpDataLoad(req);\n\n case 18:\n json = _context27.sent;\n rawItems = resolveList(json);\n options = mapOptions(json);\n setCached(req.cacheKey, {\n options: options,\n items: rawItems\n });\n\n case 22:\n // ★ 按 DSL 配置覆盖展示字段/值字段(命中缓存也生效)\n if (rawItems && rawItems.length && (dslLabelField || dslValueField)) {\n options = rawItems.map(function (item, idx) {\n var base = options[idx] || {};\n var o = {\n v: base.v !== undefined && base.v !== null ? base.v : '',\n tx: base.tx !== undefined && base.tx !== null ? base.tx : ''\n };\n\n if (dslValueField && item[dslValueField] !== undefined && item[dslValueField] !== null && item[dslValueField] !== '') {\n o.v = String(item[dslValueField]);\n }\n\n if (dslLabelField && item[dslLabelField] !== undefined && item[dslLabelField] !== null && item[dslLabelField] !== '') {\n o.tx = String(item[dslLabelField]);\n }\n\n return o;\n });\n }\n\n el.innerHTML = options.map(function (o) {\n return '<option value=\"' + (o.v || '') + '\">' + (o.tx || '') + '</option>';\n }).join(''); // ★ 将 API 返回的原始数据以 data-* 属性存储到 option 上(同 selectListFirst 逻辑)\n\n if (rawItems && el.options) {\n // ★ 原始数据匹配的 value 候选:并入 DSL data-value-field,确保会议号(meetingNo)等自定义值字段能附加 data-*\n vKeys = ['no', 'holidayNo', 'id', 'value', 'code', 'v'];\n if (dslValueField) vKeys.unshift(dslValueField);\n rawItems.forEach(function (item) {\n var itemVal = '';\n\n for (var vi = 0; vi < vKeys.length; vi++) {\n if (item[vKeys[vi]] !== undefined) {\n itemVal = String(item[vKeys[vi]]);\n break;\n }\n }\n\n if (!itemVal) return;\n\n for (var oi = 0; oi < el.options.length; oi++) {\n if (el.options[oi].value === itemVal) {\n Object.keys(item).forEach(function (key) {\n var val = item[key];\n if (val === undefined || val === null || (0, _typeof2.default)(val) === 'object') return;\n var attrName = 'data-' + key.replace(/([A-Z])/g, '-$1').toLowerCase();\n el.options[oi].setAttribute(attrName, String(val));\n });\n break;\n }\n }\n });\n } // data-default-label:按文本匹配选中默认项\n\n\n selectDefaultByLabel(el); // 选中后始终触发 change(驱动级联和 detail-url),无论是否匹配到默认值\n\n if (el.options && el.options.length > 0) {\n el.dispatchEvent(new Event('change', {\n bubbles: true\n }));\n }\n\n _context27.next = 32;\n break;\n\n case 29:\n _context27.prev = 29;\n _context27.t0 = _context27[\"catch\"](7);\n console.warn('data-load 失败:', el.id, _context27.t0.message);\n\n case 32:\n case \"end\":\n return _context27.stop();\n }\n }\n }, _callee27, null, [[7, 29]]);\n }));\n return _loadDataFromAttr.apply(this, arguments);\n }\n\n if (typeof MutationObserver !== 'undefined') {\n var observer = new MutationObserver(function (mutations) {\n mutations.forEach(function (m) {\n m.addedNodes.forEach(function (node) {\n if (node.nodeType !== 1) return; // ☆ 立即隐藏 [repeat](重绘前扼制闪烁)\n\n if (node.matches && node.matches('script[type=\"text/tokui-template\"]')) {\n node.style.display = 'none';\n }\n\n node.querySelectorAll('script[type=\"text/tokui-template\"]').forEach(function (el) {\n el.style.display = 'none';\n }); // 已提交表单:跳过所有 data-load / data-load-list / 审批流程等接口请求(仅保留模板隐藏避免闪烁)\n\n if (options.skipDataLoad) return; // [data-load-list] → 列表模式\n\n if (node.hasAttribute('data-load-list')) {\n renderListFromTemplate(node.getAttribute('data-load-list'), node);\n return;\n } // select[data-load] → select 填充\n\n\n if (node.hasAttribute('data-load') && node.tagName === 'SELECT') {\n var tpl = node.querySelector('script[type=\"text/tokui-template\"]');\n\n if (tpl && tpl.textContent) {\n renderListFromTemplate(node.getAttribute('data-load'), node);\n } else {\n loadDataFromAttr(node);\n }\n\n setupCascade(node);\n return;\n } // tokui-field[data-load] 包裹 select → 路由到 select 加载逻辑\n // TokUI 将 [select] 渲染为 <div class=\"tokui-field\"> 包裹 <select>,\n // data-load-* 属性在外层 div 上,内层 <select> 没有这些属性。\n\n\n if (node.hasAttribute('data-load') && node.tagName !== 'SELECT' && node.querySelector('select')) {\n var sel = node.querySelector('select');\n\n if (!sel._tokuiDataLoaded) {\n sel._tokuiDataLoaded = true;\n var rtpl = sel.querySelector('script[type=\"text/tokui-template\"]');\n\n if (rtpl && rtpl.textContent) {\n renderListFromTemplate(node.getAttribute('data-load'), sel);\n } else {\n loadDataFromAttr(sel);\n }\n\n setupCascade(sel);\n }\n\n return;\n } // [data-load] 详情模式\n\n\n if (node.hasAttribute('data-load')) {\n var url = node.getAttribute('data-load');\n (function () {\n var _ref12 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee19(el) {\n var json, data;\n return regeneratorRuntime.wrap(function _callee19$(_context19) {\n while (1) {\n switch (_context19.prev = _context19.next) {\n case 0:\n _context19.prev = 0;\n _context19.next = 3;\n return _request.default.get(url);\n\n case 3:\n json = _context19.sent;\n data = resolveObject(json);\n applyDetailData(el, data);\n _context19.next = 11;\n break;\n\n case 8:\n _context19.prev = 8;\n _context19.t0 = _context19[\"catch\"](0);\n console.warn('data-load 详情失败:', el.id, _context19.t0.message);\n\n case 11:\n case \"end\":\n return _context19.stop();\n }\n }\n }, _callee19, null, [[0, 8]]);\n }));\n\n return function (_x37) {\n return _ref12.apply(this, arguments);\n };\n })()(node);\n return;\n } // 子元素中的 data-load / data-load-list\n\n\n node.querySelectorAll('[data-load-list]').forEach(function (el) {\n if (!el._tokuiDataLoaded) {\n el._tokuiDataLoaded = true;\n renderListFromTemplate(el.getAttribute('data-load-list'), el);\n }\n });\n node.querySelectorAll('select[data-load]').forEach(function (el) {\n if (!el._tokuiDataLoaded) {\n el._tokuiDataLoaded = true;\n var r = el.querySelector('script[type=\"text/tokui-template\"]');\n if (r && r.textContent) renderListFromTemplate(el.getAttribute('data-load'), el);else loadDataFromAttr(el);\n setupCascade(el);\n }\n }); // 静态 select(无 data-load)的 data-default-label 处理\n\n node.querySelectorAll('select[data-default-label]:not([data-load])').forEach(function (el) {\n if (!el._tokuiDefaultLabelSet) {\n el._tokuiDefaultLabelSet = true;\n var label = el.getAttribute('data-default-label');\n\n if (label && el.options) {\n for (var oi = 0; oi < el.options.length; oi++) {\n if ((el.options[oi].textContent || el.options[oi].innerText || '').trim() === label.trim()) {\n el.selectedIndex = oi;\n break;\n }\n }\n }\n }\n }); // 静态 select 的 data-load-detail-url(选择后获取详情)\n\n node.querySelectorAll('select[data-load-detail-url]:not([data-load])').forEach(function (el) {\n if (!el._tokuiDetailUrlSet) {\n el._tokuiDetailUrlSet = true;\n setupCascade(el);\n }\n });\n node.querySelectorAll(':not(select)[data-load]').forEach(function (el) {\n // select 的 data-load 由上面的 loadDataFromAttr / renderListFromTemplate 处理(走 data-load-method),\n // 这里只处理纯详情模式(元素内部不含 select),避免对 select 包装层重复发 GET 请求。\n if (el.querySelector('select')) return;\n\n if (!el._tokuiDataLoaded) {\n el._tokuiDataLoaded = true;\n var url = el.getAttribute('data-load');\n (function () {\n var _ref13 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee20(e) {\n var json;\n return regeneratorRuntime.wrap(function _callee20$(_context20) {\n while (1) {\n switch (_context20.prev = _context20.next) {\n case 0:\n _context20.prev = 0;\n _context20.next = 3;\n return _request.default.get(url);\n\n case 3:\n json = _context20.sent;\n applyDetailData(e, resolveObject(json));\n _context20.next = 10;\n break;\n\n case 7:\n _context20.prev = 7;\n _context20.t0 = _context20[\"catch\"](0);\n console.warn('data-load 详情失败:', e.id, _context20.t0.message);\n\n case 10:\n case \"end\":\n return _context20.stop();\n }\n }\n }, _callee20, null, [[0, 7]]);\n }));\n\n return function (_x38) {\n return _ref13.apply(this, arguments);\n };\n })()(el);\n }\n }); // ============ 补卡申请表单检测 ============\n // 检测补卡申请表单并初始化日历联动\n\n if (node.hasAttribute && node.hasAttribute('data-approval-type')) {\n var approvalType = node.getAttribute('data-approval-type');\n\n if (approvalType === 'patch') {\n var patchForm = node.closest('form') || node.closest('[data-form-id]') || node;\n console.log(patchForm, 'patchForm');\n\n if (patchForm && !patchForm._tvPatchCardInited) {\n patchForm._tvPatchCardInited = true; // ★ 传入表单元素,确保在该表单作用域内查找字段\n\n setTimeout(function () {\n setupPatchCardCalendar(patchForm);\n }, 800);\n }\n }\n } // 子元素中的补卡表单\n\n\n node.querySelectorAll('[data-approval-type=\"patch\"]').forEach(function (el) {\n var pf = el.closest('form') || el.closest('[data-form-id]') || el;\n\n if (pf && !pf._tvPatchCardInited) {\n pf._tvPatchCardInited = true; // ★ 传入表单元素,确保在该表单作用域内查找字段\n\n setTimeout(function () {\n setupPatchCardCalendar(pf);\n }, 800);\n }\n }); // ============ 审批流程初始化 ============\n // 检测新渲染的表单是否配置了 data-approval-type\n\n if (node.hasAttribute && node.hasAttribute('data-approval-type')) {\n var approvalForm = node.closest('form') || node.closest('[data-form-id]') || node;\n\n if (approvalForm && !approvalForm._tvApprovalInited) {\n approvalForm._tvApprovalInited = true; // 延迟初始化,等 select 数据加载完毕\n\n setTimeout(function () {\n initApprovalFlow(approvalForm);\n }, 500);\n }\n } // 子元素中的审批表单\n\n\n node.querySelectorAll('[data-approval-type]').forEach(function (el) {\n var af = el.closest('form') || el.closest('[data-form-id]') || el;\n\n if (af && !af._tvApprovalInited) {\n af._tvApprovalInited = true;\n setTimeout(function () {\n initApprovalFlow(af);\n }, 500);\n }\n });\n });\n });\n });\n observer.observe(container.body || container, {\n childList: true,\n subtree: true\n });\n } // ============ 最终回退:轮询扫描审批表单 ============\n // 某些情况下 MutationObserver 可能漏掉表单(如容器嵌套复杂),\n // 在多个时间点做全量扫描兜底\n\n\n [800, 1500, 3000].forEach(function (delay) {\n setTimeout(function () {\n // 已提交表单:跳过审批流程回退扫描\n if (options.skipDataLoad) return; // ★ 多表单隔离:审批表单扫描限定在本实例 container 内,避免跨消息串扰\n\n var scanRoot = container && container.querySelectorAll ? container : document;\n var forms = scanRoot.querySelectorAll('[data-approval-type]');\n forms.forEach(function (el) {\n var form = el.closest('form') || el.closest('[data-form-id]') || el;\n\n if (form && !form._tvApprovalInited) {\n form._tvApprovalInited = true;\n initApprovalFlow(form);\n } // 补卡表单初始化 ★ 传入表单元素确保隔离\n\n\n if (el.getAttribute('data-approval-type') === 'patch' && !form._tvPatchCardInited) {\n form._tvPatchCardInited = true;\n setupPatchCardCalendar(form);\n }\n }); // 也扫描所有 [data-form-id] 表单,检查是否有 data-approval-type\n\n if (forms.length === 0) {\n var allForms = scanRoot.querySelectorAll('[data-form-id]');\n allForms.forEach(function (f) {\n if (f.hasAttribute('data-approval-type') && !f._tvApprovalInited) {\n f._tvApprovalInited = true;\n initApprovalFlow(f);\n }\n\n if (f.getAttribute('data-approval-type') === 'patch' && !f._tvPatchCardInited) {\n f._tvPatchCardInited = true;\n setupPatchCardCalendar(f);\n }\n });\n }\n }, delay);\n }); // ============ 暴露 API 供外部调用(如 Vue 组件的收尾对齐) ============\n\n if (typeof window !== 'undefined') {\n if (!window.__tokuiValidatorBridge) window.__tokuiValidatorBridge = {};\n window.__tokuiValidatorBridge.initConditionalVisibility = initConditionalVisibility;\n window.__tokuiValidatorBridge.updateConditionalVisibility = updateConditionalVisibility;\n window.__tokuiValidatorBridge.initExistingApprovalForms = initExistingApprovalForms;\n window.__tokuiValidatorBridge.applyDefaultValues = applyDefaultValues;\n window.__tokuiValidatorBridge.applyFormData = applyFormData;\n window.__tokuiValidatorBridge.fixDateTimeDefaults = fixDateTimeDefaults;\n window.__tokuiValidatorBridge.markFormReady = markFormReady;\n window.__tokuiValidatorBridge.stopApprovalFlows = stopApprovalFlows;\n window.__tokuiValidatorBridge.setupPatchCardCalendar = setupPatchCardCalendar; // lockFormWithOverlay 已在 __validatorHandlersBound 守卫块内挂载(块级作用域,仅首次),此处不覆盖\n } // ============ 注入样式 ============\n\n\n var APPROVAL_STYLES = document.getElementById('tv-approval-styles');\n\n if (!APPROVAL_STYLES) {\n var styleEl = document.createElement('style');\n styleEl.id = 'tv-approval-styles';\n styleEl.textContent = '\\\r\n /* ── 审批人选择弹窗(列表模式) ── */\\\r\n .tv-approver-backdrop { position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,.3); z-index:9999; display:flex; align-items:center; justify-content:center; }\\\r\n .tv-approver-dialog { background:#fff; border-radius:8px; min-width:420px; max-width:520px; max-height:80vh; display:flex; flex-direction:column; box-shadow:0 4px 24px rgba(0,0,0,.15); }\\\r\n .tv-approver-dialog-header { display:flex; align-items:center; justify-content:space-between; padding:16px 20px; border-bottom:1px solid #ebeef5; }\\\r\n .tv-approver-dialog-title { font-size:16px; font-weight:600; color:#303133; }\\\r\n .tv-approver-dialog-close { font-size:20px; color:#909399; cursor:pointer; line-height:1; }\\\r\n .tv-approver-dialog-close:hover { color:#409EFF; }\\\r\n .tv-approver-dialog-body { padding:20px; overflow-y:auto; flex:1; }\\\r\n .tv-approver-dialog-body p { margin:0 0 16px; font-size:13px; color:#606266; }\\\r\n .tv-approver-dialog-footer { display:flex; justify-content:flex-end; gap:10px; padding:12px 20px; border-top:1px solid #ebeef5; }\\\r\n .tv-approver-dialog-confirm,.tv-approver-dialog-cancel { padding:8px 20px; border-radius:4px; font-size:13px; cursor:pointer; border:none; }\\\r\n .tv-approver-dialog-confirm { background:#409EFF; color:#fff; }\\\r\n .tv-approver-dialog-confirm:hover { background:#337ecc; }\\\r\n .tv-approver-dialog-cancel { background:#f5f7fa; color:#606266; }\\\r\n .tv-approver-dialog-cancel:hover { background:#e4e7ed; }\\\r\n .tv-approver-dialog-empty { text-align:center; padding:30px 0; color:#909399; font-size:14px; }\\\r\n .tv-approver-dialog .duox,.tv-approver-dialog .danx { display:block; margin-bottom:8px; font-size:14px; color:#303133; }\\\r\n .tv-approver-dialog .duox input,.tv-approver-dialog .danx input { margin-right:6px; vertical-align:middle; }\\\r\n /* ── 审批流程区域 ── */\\\r\n .tv-approval-flow { margin-top:8px; padding:8px 0; }\\\r\n .tv-approval-flow--hidden { display:none !important; }\\\r\n .tv-approval-wrapper { display:block; }\\\r\n .tv-approval-flow-label { font-size:12px; color:#909399; margin-bottom:6px; }\\\r\n .tv-approval-node { display:flex; align-items:flex-start; padding:6px 0; }\\\r\n .tv-approval-node+.tv-approval-node { border-top:1px dashed #e4e7ed; }\\\r\n .tv-approval-node-index { width:24px; height:24px; border-radius:50%; background:#409EFF; color:#fff; font-size:12px; display:flex; align-items:center; justify-content:center; flex-shrink:0; margin-right:10px; margin-top:2px; }\\\r\n .tv-approval-node-info { flex:1; min-width:0; }\\\r\n .tv-approval-node-name { font-size:13px; color:#303133; font-weight:500; margin-bottom:4px; }\\\r\n .tv-approval-node-name .tv-cc-badge { display:inline-block; font-size:11px; color:#409EFF; margin-left:4px; }\\\r\n .tv-approver-list { display:flex; flex-wrap:wrap; gap:4px; align-items:center; }\\\r\n .tv-approver-tag { display:inline-flex; align-items:center; padding:2px 8px; border-radius:12px; font-size:12px; white-space:nowrap; flex-wrap:nowrap; }\\\r\n .tv-approver-tag--normal { background:#ecf5ff; color:#409EFF; }\\\r\n .tv-approver-tag--auto { background:#f0f9eb; color:#67c23a; }\\\r\n .tv-approver-tag--empty { background:#fdf6ec; color:#e6a23c; }\\\r\n .tv-approver-tag .tv-approver-tag-del { margin-left:4px; cursor:pointer; font-weight:bold; }\\\r\n .tv-approval-node-arrow { position:absolute; left:-18px; top:50%; transform:translateY(-50%); font-size:18px; color:#409EFF; }\\\r\n /* ── 组织架构选人弹窗 ── */\\\r\n .tv-org-dialog { background:#fff; border-radius:8px; width:700px; max-height:80vh; display:flex; flex-direction:column; box-shadow:0 4px 24px rgba(0,0,0,.15); }\\\r\n .tv-org-dialog-body { display:flex; flex:1; min-height:360px; overflow:hidden; }\\\r\n .tv-org-left { width:60%; border-right:1px solid #ebeef5; display:flex; flex-direction:column; }\\\r\n .tv-org-right { width:40%; display:flex; flex-direction:column; padding:12px; }\\\r\n .tv-org-right .block-title { font-size:13px; color:#909399; margin-bottom:8px; }\\\r\n .tv-org-search { width:100%; padding:8px 12px; border:none; border-bottom:1px solid #ebeef5; font-size:13px; outline:none; box-sizing:border-box; }\\\r\n .tv-org-search:focus { border-bottom-color:#409EFF; }\\\r\n .tv-org-tree { flex:1; overflow-y:auto; padding:4px 0; }\\\r\n .tv-org-node { display:flex; align-items:center; padding:4px 8px; font-size:13px; cursor:default; }\\\r\n .tv-org-node:hover { background:#f5f7fa; }\\\r\n .tv-org-arrow { width:16px; font-size:10px; color:#909399; flex-shrink:0; text-align:center; }\\\r\n .tv-org-arrow.expanded { color:#409EFF; }\\\r\n .tv-org-label--dept { color:#303133; font-weight:500; }\\\r\n .tv-org-label { margin-left:4px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\\\r\n .tv-org-check { margin-right:4px; flex-shrink:0; }\\\r\n .tv-org-selected-list { flex:1; overflow-y:auto; list-style:none; padding:0; margin:0; }\\\r\n .tv-org-selected-item { display:flex; align-items:center; justify-content:space-between; padding:6px 8px; font-size:13px; border-radius:4px; }\\\r\n .tv-org-selected-item:hover { background:#f5f7fa; }\\\r\n .tv-org-selected-del { color:#f56c6c; cursor:pointer; font-size:14px; padding:0 4px; }\\\r\n .tv-org-selected-del:hover { color:#f00; }\\\r\n .tv-org-loading,.tv-org-error,.tv-org-empty { text-align:center; padding:30px 0; color:#909399; font-size:13px; }\\\r\n .tv-org-error { color:#f56c6c; }\\\r\n /* ── 审批人圆形头像占位(有头像/无头像统一展示) ── */\\\r\n .tv-approver-avatar { width:24px; height:24px; border-radius:50%; display:inline-flex; align-items:center; justify-content:center; overflow:hidden; flex-shrink:0; background:#409EFF; color:#fff; font-size:11px; line-height:1; }\\\r\n .tv-approver-avatar-img { width:100%; height:100%; object-fit:cover; display:block; }\\\r\n .tv-approver-tag { align-items:center; }\\\r\n /* ── 审批人选择弹窗(列表模式)列表项样式 ── */\\\r\n .tv-approver-dialog-item { display:flex; align-items:center; cursor:pointer; padding:8px 10px; border-radius:4px; font-size:14px; color:#303133; }\\\r\n .tv-approver-dialog-item:hover { background:#f5f7fa; }\\\r\n .tv-approver-dialog-item input[type=\"checkbox\"],.tv-approver-dialog-item input[type=\"radio\"] { margin-right:8px; }\\\r\n /* ── H5/移动端适配 ── */\\\r\n @media (max-width:640px){\\\r\n .tv-approver-backdrop { align-items:flex-start; overflow-y:auto; padding:6vh 4vw; box-sizing:border-box; }\\\r\n .tv-approver-dialog { width:100%; max-width:100%; min-width:0; }\\\r\n .tv-org-dialog { width:100%; max-width:100%; }\\\r\n .tv-approver-dialog-body { padding:14px; }\\\r\n .tv-approver-dialog-footer { padding:10px 14px; }\\\r\n .tv-org-dialog-body { flex-direction:column; min-height:0; }\\\r\n .tv-org-left { width:100%; border-right:none; border-bottom:1px solid #ebeef5; max-height:56vh; }\\\r\n .tv-org-right { width:100%; }\\\r\n }\\\r\n /* ── 图片上传组件样式 ── */\\\r\n .tokui-field.tokui-imageupload { display:block; }\\\r\n .tokui-imageupload-area { border:1px dashed #d9d9d9; border-radius:4px; padding:12px; transition:border-color .3s; }\\\r\n .tokui-imageupload-area:hover { border-color:#409EFF; }\\\r\n .tokui-imageupload-preview { display:flex; flex-wrap:wrap; gap:8px; margin-bottom:8px; }\\\r\n .tokui-imageupload-item { position:relative; width:100px; height:100px; border-radius:4px; overflow:hidden; border:1px solid #d9d9d9; }\\\r\n .tokui-imageupload-item img { width:100%; height:100%; object-fit:cover; display:block; }\\\r\n .tokui-imageupload-item-actions { position:absolute; top:0; right:0; background:rgba(0,0,0,.5); padding:2px 4px; display:flex; gap:4px; opacity:0; transition:opacity .3s; }\\\r\n .tokui-imageupload-item:hover .tokui-imageupload-item-actions { opacity:1; }\\\r\n .tokui-imageupload-item-del { color:#fff; cursor:pointer; font-size:12px; }\\\r\n .tokui-imageupload-item-del:hover { color:#f56c6c; }\\\r\n .tokui-imageupload-item-uploading { position:absolute; top:0; left:0; width:100%; height:100%; background:rgba(255,255,255,.9); display:flex; align-items:center; justify-content:center; font-size:12px; color:#409EFF; }\\\r\n .tokui-imageupload-add { width:100px; height:100px; border:1px dashed #d9d9d9; border-radius:4px; display:flex; align-items:center; justify-content:center; cursor:pointer; font-size:24px; color:#909399; transition:all .3s; background:#fafafa; }\\\r\n .tokui-imageupload-add:hover { border-color:#409EFF; color:#409EFF; }\\\r\n .tokui-imageupload-tip { font-size:12px; color:#909399; margin-top:8px; line-height:1.5; }\\\r\n ';\n document.head.appendChild(styleEl);\n } // ============ 返回清理与缓存控制 ============\n\n\n return {\n destroy: function destroy() {\n TRIGGER_EVENTS.forEach(function (evt) {\n container.removeEventListener(evt, handleTriggerEvent, true);\n });\n container.removeEventListener('change', handleClkOnChange, true);\n\n if (_condDomObserver) {\n _condDomObserver.disconnect();\n\n _condDomObserver = null;\n }\n\n if (observer) observer.disconnect();\n Object.keys(debounceTimers).forEach(function (k) {\n return cancelDebounce(k);\n });\n },\n clearCache: clearCache\n };\n}\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/tokui-validator/tokui-validator-bridge.js?");
|
|
15763
15797
|
|
|
15764
15798
|
/***/ }),
|
|
15765
15799
|
|
|
@@ -15879,7 +15913,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n
|
|
|
15879
15913
|
/***/ (function(module, exports, __webpack_require__) {
|
|
15880
15914
|
|
|
15881
15915
|
"use strict";
|
|
15882
|
-
eval("\n\nvar _interopRequireDefault = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/interopRequireDefault */ \"./node_modules/@babel/runtime/helpers/interopRequireDefault.js\").default;\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.setAuth = setAuth;\nexports.setToken = setToken;\nexports.setUserNo = setUserNo;\nexports.getAuth = getAuth;\nexports.setBaseURL = setBaseURL;\nexports.signQueryParams = signQueryParams;\nexports.default = void 0;\n\nvar _typeof2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/typeof */ \"./node_modules/@babel/runtime/helpers/typeof.js\"));\n\nvar _axios = _interopRequireDefault(__webpack_require__(/*! axios */ \"./node_modules/axios/index.js\"));\n\nvar _cookie = __webpack_require__(/*! @lib/utils/cookie.js */ \"./src/lib/utils/cookie.js\");\n\nvar _message = __webpack_require__(/*! @lib/utils/message.js */ \"./src/lib/utils/message.js\");\n\n/*\r\n * @Author: 高瑞廷 2419056691@qq.com\r\n * @Date: 2026-06-04 11:27:13\r\n * @LastEditors: 高瑞廷 2419056691@qq.com\r\n * @LastEditTime: 2026-
|
|
15916
|
+
eval("\n\nvar _interopRequireDefault = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/interopRequireDefault */ \"./node_modules/@babel/runtime/helpers/interopRequireDefault.js\").default;\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.setAuth = setAuth;\nexports.setKbAuth = setKbAuth;\nexports.setToken = setToken;\nexports.setUserNo = setUserNo;\nexports.getAuth = getAuth;\nexports.setBaseURL = setBaseURL;\nexports.signQueryParams = signQueryParams;\nexports.default = void 0;\n\nvar _typeof2 = _interopRequireDefault(__webpack_require__(/*! ./node_modules/@babel/runtime/helpers/typeof */ \"./node_modules/@babel/runtime/helpers/typeof.js\"));\n\nvar _axios = _interopRequireDefault(__webpack_require__(/*! axios */ \"./node_modules/axios/index.js\"));\n\nvar _cookie = __webpack_require__(/*! @lib/utils/cookie.js */ \"./src/lib/utils/cookie.js\");\n\nvar _message = __webpack_require__(/*! @lib/utils/message.js */ \"./src/lib/utils/message.js\");\n\n/*\r\n * @Author: 高瑞廷 2419056691@qq.com\r\n * @Date: 2026-06-04 11:27:13\r\n * @LastEditors: 高瑞廷 2419056691@qq.com\r\n * @LastEditTime: 2026-08-21 08:55:45\r\n * @FilePath: \\zjkj-nodejs-npm_customer-client\\src\\lib\\utils\\request.js\r\n * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE\r\n */\nvar runtimeAK = Object({\"NODE_ENV\":\"test\",\"VUE_APP_API\":\"https://api.qgbtech.cn\",\"VUE_APP_BASE_API\":\"https://ai.jybtech.cn\",\"VUE_APP_EMTERPRISE\":\"/enterprise-managerment\",\"VUE_APP_NAME\":\"development\",\"BASE_URL\":\"/\"}).VUE_APP_BASE_AK || '';\nvar runtimeSK = Object({\"NODE_ENV\":\"test\",\"VUE_APP_API\":\"https://api.qgbtech.cn\",\"VUE_APP_BASE_API\":\"https://ai.jybtech.cn\",\"VUE_APP_EMTERPRISE\":\"/enterprise-managerment\",\"VUE_APP_NAME\":\"development\",\"BASE_URL\":\"/\"}).VUE_APP_BASE_SK || ''; // let runtimeToken = ''\n\nvar runtimeToken = '';\nvar _user = '';\nvar _companyNo = '';\nvar _userNo = '';\nvar runtimeBaseURL = \"https://ai.jybtech.cn\" || false;\n\nvar CryptoJS = __webpack_require__(/*! crypto-js */ \"./node_modules/crypto-js/index.js\");\n\nvar service = _axios.default.create({\n baseURL: runtimeBaseURL,\n timeout: 15000\n}); // 归一化 external_user:后端 tb_company_kb_chat_session.external_user 为字符串。\n// imAccount 存在传入对象(而非用户标识字符串)的情况,直接作为 header 会被\n// 序列化成 \"[object Object]\",导致数据库写入脏数据。这里统一转成稳定的用户标识字符串。\n\n\nfunction normalizeExternalUser(user) {\n if (user == null) return '';\n if (typeof user === 'string') return user;\n if (typeof user === 'number') return String(user);\n\n if ((0, _typeof2.default)(user) === 'object') {\n var keys = ['userNo', 'user_no', 'userno', 'userId', 'user_id', 'imAccount', 'account', 'accountNo', 'id', 'no'];\n\n for (var _i = 0, _keys = keys; _i < _keys.length; _i++) {\n var key = _keys[_i];\n var v = user[key];\n if (v != null && String(v).trim() !== '') return String(v).trim();\n }\n\n return '';\n }\n\n return '';\n}\n\nfunction hmacSha256Base64(rawSignStr, sk) {\n var hmac = CryptoJS.HmacSHA256(rawSignStr, sk); // 转为 Base64\n\n return CryptoJS.enc.Base64.stringify(hmac);\n}\n\nfunction setAuth(ak, sk, user, companyNo, token, userno) {\n runtimeAK = ak || '';\n runtimeSK = sk || '';\n _user = user;\n _companyNo = companyNo;\n runtimeToken = token || '';\n} // 知识库专用:把 external_user 归一化成稳定字符串(后端会写库),并清理公司号\n\n\nfunction setKbAuth(ak, sk, user, companyNo, token, userno) {\n runtimeAK = ak || '';\n runtimeSK = sk || '';\n _user = normalizeExternalUser(userno);\n _companyNo = String(companyNo || '').trim();\n _userNo = normalizeExternalUser(userno);\n runtimeToken = token || '';\n}\n\nfunction setToken(token) {\n runtimeToken = token || '';\n}\n\nfunction setUserNo(userno) {\n _userNo = userno || '';\n}\n\nfunction getAuth() {\n return {\n ak: runtimeAK,\n sk: runtimeSK,\n token: runtimeToken,\n user: _user,\n companyNo: _companyNo,\n userno: _userNo\n };\n}\n\nfunction setBaseURL(url) {\n runtimeBaseURL = url || '';\n service.defaults.baseURL = runtimeBaseURL;\n}\n\nfunction signQueryParams(params, secretKey) {\n var keys = Object.keys(params).filter(function (k) {\n return k !== 'ak' && k !== 'sign';\n }).sort();\n var raw = keys.map(function (k) {\n return \"\".concat(k, \"=\").concat(params[k] || '');\n }).join('&');\n return hmacSha256Base64(raw, secretKey);\n}\n\nservice.interceptors.request.use(function (config) {\n // 流式接口跳过 Header 签名\n if (config.url.includes('/chat/stream')) {\n return config;\n }\n\n var ts = Date.now().toString();\n var token = runtimeToken || (0, _cookie.getToken)();\n var externalUser = _user || '';\n var signParts = []; // 1. 第一位:token(有值才加入)\n\n if (_companyNo) {\n signParts.push(\"companyNo=\".concat(_companyNo));\n }\n\n if (token) {\n signParts.push(\"token=\".concat(token));\n } // 2. 第二位:user(有值才加入)\n\n\n if (externalUser) {\n signParts.push(\"user=\".concat(externalUser));\n } // 3. 第三位:ts(必选,永远存在)\n\n\n signParts.push(\"ts=\".concat(ts)); // 拼接最终待签字符串\n\n var signStr = signParts.join('&'); // 计算签名\n\n var sign = hmacSha256Base64(signStr, runtimeSK);\n\n if (_companyNo) {\n config.headers['X-Company-No'] = _companyNo;\n } // 设置请求头\n\n\n config.headers['X-AK'] = runtimeAK;\n config.headers['X-Ts'] = ts;\n config.headers['X-Sign'] = sign;\n var rawToken = (token || '').trim();\n\n if (rawToken) {\n config.headers['X-Auth-Token'] = \"\".concat(rawToken);\n config.headers.Authorization = \"Bearer \".concat(rawToken);\n }\n\n config.headers['X-External-User'] = externalUser; // FormData(文件上传)时由浏览器自动生成 multipart 边界头,禁止覆盖\n\n if (!(config.data instanceof FormData)) {\n config.headers['Content-Type'] = 'application/json';\n } // console.log('排序后key:', sortedKeys)\n\n\n return config;\n}, function (error) {\n return Promise.reject(error);\n});\nservice.interceptors.response.use(function (response) {\n // 解密返回数据(迁移自 http/index.js)\n if (response.headers.isencryption && response.headers.isencryption == 1 && response.data.payload) {\n var param = ['data', 'result', 'records'];\n var current = param.find(function (i) {\n return i in response.data;\n });\n\n if (current) {\n var decryptData = window.spiderHack.decryptJson(response.data.payload);\n var dataType = response.headers.datatype;\n var handler = {\n object: JSON.parse,\n list: JSON.parse,\n long: parseFloat,\n integer: parseFloat,\n boolean: function boolean(parse) {\n return typeof parse === 'boolean' ? parse : !!(typeof parse === 'string' && parse === 'true');\n }\n };\n response.data[current] = handler[dataType] ? handler[dataType](decryptData) : decryptData;\n }\n }\n\n var res = response.data;\n\n if (res.code === 200) {\n return res;\n } else if (res.code === 40101) {\n (0, _message.showMessage)('登录已过期,请重新登录');\n return Promise.reject(new Error(res.message));\n } else {\n (0, _message.showMessage)(res.message || res.msg || '请求失败');\n return Promise.reject(new Error(res.message));\n }\n}, function (error) {\n (0, _message.showMessage)(error.message || '网络异常');\n return Promise.reject(error);\n});\nvar _default = service;\nexports.default = _default;\n\n//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/utils/request.js?");
|
|
15883
15917
|
|
|
15884
15918
|
/***/ }),
|
|
15885
15919
|
|