zj-plugin-intelligent 1.2.8-beta.21 → 1.2.8-beta.23
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.
|
@@ -872,7 +872,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n
|
|
|
872
872
|
/***/ (function(module, exports, __webpack_require__) {
|
|
873
873
|
|
|
874
874
|
"use strict";
|
|
875
|
-
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 if (this.Ak || this.Sk) {\n (0, _apichat.setApiAuth)(this.Ak, this.Sk, this.imAccount, this.companyNo, this.chatImToken, this.userno);\n }\n\n this.loadAgents();\n this.loadSessions();\n this.getCurrentUserInfo();\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 }).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 this._swipeStartX = e.touches[0].clientX;\n this._swipeStartY = e.touches[0].clientY;\n this._swipeHandled = false;\n },\n // 在 touchmove 中实时检测\n // 注意:不限制触摸起点位置(移除边缘限制),以兼容 Android 系统手势导航在左边缘的拦截\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; // 向右滑动超过 60px,且水平距离明显大于垂直距离(排除上下滚动干扰)\n\n if (diffX < 60) return;\n if (Math.abs(diffX) < Math.abs(diffY) * 2) 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; // 向右滑动超过 60px,且水平距离明显大于垂直距离\n\n if (diffX < 60) return;\n if (Math.abs(diffX) < Math.abs(diffY) * 2) 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.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 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.createTime ? 'msg_' + m.createTime : '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");
|
|
875
|
+
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 this._swipeStartX = e.touches[0].clientX;\n this._swipeStartY = e.touches[0].clientY;\n this._swipeHandled = false;\n },\n // 在 touchmove 中实时检测\n // 注意:不限制触摸起点位置(移除边缘限制),以兼容 Android 系统手势导航在左边缘的拦截\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; // 向右滑动超过 60px,且水平距离明显大于垂直距离(排除上下滚动干扰)\n\n if (diffX < 60) return;\n if (Math.abs(diffX) < Math.abs(diffY) * 2) 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; // 向右滑动超过 60px,且水平距离明显大于垂直距离\n\n if (diffX < 60) return;\n if (Math.abs(diffX) < Math.abs(diffY) * 2) 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.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 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.createTime ? 'msg_' + m.createTime : '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");
|
|
876
876
|
|
|
877
877
|
/***/ }),
|
|
878
878
|
|
|
@@ -11066,7 +11066,7 @@ eval("module.exports = function(module) {\n\tif (!module.webpackPolyfill) {\n\t\
|
|
|
11066
11066
|
/*! exports provided: name, version, main, private, scripts, dependencies, devDependencies, browserslist, directories, keywords, license, files, default */
|
|
11067
11067
|
/***/ (function(module) {
|
|
11068
11068
|
|
|
11069
|
-
eval("module.exports = JSON.parse(\"{\\\"name\\\":\\\"zj-plugin-intelligent\\\",\\\"version\\\":\\\"1.2.8-beta.
|
|
11069
|
+
eval("module.exports = JSON.parse(\"{\\\"name\\\":\\\"zj-plugin-intelligent\\\",\\\"version\\\":\\\"1.2.8-beta.23\\\",\\\"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\\\":\\\"set NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build --target lib --name ZjPluginIntelligent --dest lib src/lib/index.js\\\",\\\"lib:test\\\":\\\"set NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build --mode test --target lib --name ZjPluginIntelligent --dest lib src/lib/index.js\\\"},\\\"dependencies\\\":{\\\"@jboltai/tokui\\\":\\\"^0.1.6\\\",\\\"crypto-js\\\":\\\"^4.2.0\\\",\\\"html2canvas\\\":\\\"^1.4.1\\\",\\\"markdown-it\\\":\\\"^14.1.0\\\"},\\\"devDependencies\\\":{\\\"@babel/preset-env\\\":\\\"^7.14.9\\\",\\\"@vue/cli-plugin-babel\\\":\\\"~4.5.0\\\",\\\"@vue/cli-plugin-eslint\\\":\\\"^4.5.13\\\",\\\"@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\\\":\\\"^7.30.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?");
|
|
11070
11070
|
|
|
11071
11071
|
/***/ }),
|
|
11072
11072
|
|
|
@@ -11101,7 +11101,7 @@ eval("\n\nvar _interopRequireDefault = __webpack_require__(/*! ./node_modules/@b
|
|
|
11101
11101
|
/***/ (function(module, exports, __webpack_require__) {
|
|
11102
11102
|
|
|
11103
11103
|
"use strict";
|
|
11104
|
-
eval("\n\nvar _interopRequireWildcard = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/interopRequireWildcard */ \"./node_modules/@babel/runtime/helpers/interopRequireWildcard.js\").default;\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.setApiAuth = setApiAuth;\nexports.setApiToken = setApiToken;\nexports.setApiUserNo = setApiUserNo;\nexports.getChatAgents = getChatAgents;\nexports.startSession = startSession;\nexports.syncToken = syncToken;\nexports.createStreamUrl = createStreamUrl;\nexports.getSessionList = getSessionList;\nexports.getSessionMessages = getSessionMessages;\nexports.deleteSession = deleteSession;\nexports.stopStream = stopStream;\nexports.markFormSubmitted = markFormSubmitted;\nexports.getCurrentUserInfo = getCurrentUserInfo;\n\nvar _request = _interopRequireWildcard(__webpack_require__(/*! @lib/utils/request */ \"./src/lib/utils/request.js\"));\n\nvar _cookie = __webpack_require__(/*! @lib/utils/cookie.js */ \"./src/lib/utils/cookie.js\");\n\n/*\n * @Author: 高瑞廷 2419056691@qq.com\n * @Date: 2026-06-04 11:13:33\n * @LastEditors: 高瑞廷 2419056691@qq.com\n * @LastEditTime: 2026-08-18 09:53:04\n * @FilePath: \\zjkj-nodejs-npm_customer-client\\src\\lib\\api\\apichat.js\n * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE\n */\nvar CryptoJS = __webpack_require__(/*! crypto-js */ \"./node_modules/crypto-js/index.js\");\n\nfunction setApiAuth(ak, sk, user, companyNo, token, userno) {\n (0, _request.setAuth)(ak, sk, user, companyNo, token, userno);\n}\n\nfunction setApiToken(token) {\n (0, _request.setToken)(token);\n}\n\nfunction setApiUserNo(userno) {\n (0, _request.setUserNo)(userno);\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 getChatAgents(keyword) {\n return (0, _request.default)({\n url: '/api/v1/open/ai/agents',\n method: 'get',\n params: keyword ? {\n keyword: keyword\n } : {}\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n\nfunction startSession(agentCode) {\n return (0, _request.default)({\n url: '/api/v1/open/ai/chat/start',\n method: 'post',\n data: {\n agentCode: agentCode\n }\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n\nfunction syncToken(token, sessionId) {\n return (0, _request.default)({\n url: '/api/v1/open/ai/token',\n method: 'post',\n data: {\n token: token,\n sessionId: sessionId\n }\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n} // export function createStreamUrl(sessionId, message) {\n// const ts = Date.now().toString();\n// const params = { sessionId, message, ts };\n// const sign = signQueryParams(params, SK);\n// const params = new URLSearchParams({\n// sessionId,\n// message,\n// ak: AK,\n// ts,\n// sign\n// });\n// // const params = new URLSearchParams({ sessionId, message })\n// const base = process.env.VUE_APP_BACKEND_URL || process.env.VUE_APP_BASE_API || ''\n// return `${base}/api/v1/open/ai/chat/stream?${params}`\n// }\n\n\nfunction createStreamUrl(sessionId, message) {\n var ts = Date.now().toString();\n\n var _getAuth = (0, _request.getAuth)(),\n ak = _getAuth.ak,\n sk = _getAuth.sk,\n runtimeToken = _getAuth.token,\n user = _getAuth.user,\n companyNo = _getAuth.companyNo,\n userno = _getAuth.userno;\n\n var token = runtimeToken || (0, _cookie.getToken)(); // 1. 参与签名的参数(字段顺序对应要求)\n\n var signParams = {\n companyNo: companyNo,\n message: message,\n sessionId: sessionId,\n token: token,\n ts: ts,\n user: user,\n userNo: userno\n }; // 2. 强制按指定顺序拼接签名字符串(彻底保证顺序:message > sessionId > token > ts > user)\n\n var signKeyOrder = ['companyNo', 'message', 'sessionId', 'token', 'ts', 'user', 'userNo'];\n var signParts = [];\n signKeyOrder.forEach(function (key) {\n var val = signParams[key]; // 空值也保留字段(按后端规则按需调整)\n\n signParts.push(\"\".concat(key, \"=\").concat(val !== null && val !== void 0 ? val : ''));\n });\n var rawSignStr = signParts.join('&'); // 3. 计算签名\n\n var sig = hmacSha256Base64(rawSignStr, sk); // const BASE = 'http://localhost:8080' || process.env.VUE_APP_BACKEND_URL || process.env.VUE_APP_BASE_API || ''\n\n var BASE = 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_BACKEND_URL || \"https://ai.jybtech.cn\" || ''; // 4. 拼接最终URL,所有参数做URL编码\n\n var urlParams = new URLSearchParams();\n urlParams.append('companyNo', companyNo);\n urlParams.append('sessionId', sessionId);\n urlParams.append('message', message);\n urlParams.append('ak', ak);\n urlParams.append('ts', ts);\n urlParams.append('sign', sig);\n if (token) urlParams.append('token', token);\n urlParams.append('user', user);\n urlParams.append('userNo', userno);\n return \"\".concat(BASE, \"/api/v1/open/ai/chat/stream?\").concat(urlParams.toString());\n}\n\nfunction getSessionList() {\n return (0, _request.default)({\n url: '/api/v1/open/ai/chat/sessions',\n method: 'get'\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n\nfunction getSessionMessages(sessionId, page, pageSize) {\n var params = {};\n if (page) params.page = page;\n if (pageSize) params.pageSize = pageSize;\n return (0, _request.default)({\n url: \"/api/v1/open/ai/chat/sessions/\".concat(sessionId, \"/messages\"),\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\nfunction deleteSession(sessionId) {\n return (0, _request.default)({\n url: \"/api/v1/open/ai/chat/sessions/\".concat(sessionId),\n method: 'delete'\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n\nfunction stopStream(sessionId) {\n return (0, _request.default)({\n url: '/api/v1/open/ai/chat/stop',\n method: 'post',\n data: {\n sessionId: sessionId\n }\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n\nfunction markFormSubmitted(sessionId, msgKey, formData) {\n return (0, _request.default)({\n url: '/api/v1/open/ai/chat/form-submitted',\n method: 'post',\n data: {\n sessionId: sessionId,\n msgKey: msgKey,\n formData: formData ? JSON.stringify(formData) : null\n }\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n\nfunction getCurrentUserInfo() {\n var api = \"https://api.qgbtech.cn\" || false;\n var enterprise = \"/enterprise-managerment\" || false;\n return (0, _request.default)({\n url: \"\".concat(api).concat(enterprise, \"/api/user/getCurrentUserInfo\"),\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//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/api/apichat.js?");
|
|
11104
|
+
eval("\n\nvar _interopRequireWildcard = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/interopRequireWildcard */ \"./node_modules/@babel/runtime/helpers/interopRequireWildcard.js\").default;\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.setApiAuth = setApiAuth;\nexports.setApiToken = setApiToken;\nexports.setApiUserNo = setApiUserNo;\nexports.getChatAgents = getChatAgents;\nexports.startSession = startSession;\nexports.syncToken = syncToken;\nexports.createStreamUrl = createStreamUrl;\nexports.getSessionList = getSessionList;\nexports.getSessionMessages = getSessionMessages;\nexports.deleteSession = deleteSession;\nexports.stopStream = stopStream;\nexports.markFormSubmitted = markFormSubmitted;\nexports.getCurrentUserInfo = getCurrentUserInfo;\n\nvar _request = _interopRequireWildcard(__webpack_require__(/*! @lib/utils/request */ \"./src/lib/utils/request.js\"));\n\nvar _cookie = __webpack_require__(/*! @lib/utils/cookie.js */ \"./src/lib/utils/cookie.js\");\n\n/*\r\n * @Author: 高瑞廷 2419056691@qq.com\r\n * @Date: 2026-06-04 11:13:33\r\n * @LastEditors: 高瑞廷 2419056691@qq.com\r\n * @LastEditTime: 2026-08-18 09:53:04\r\n * @FilePath: \\zjkj-nodejs-npm_customer-client\\src\\lib\\api\\apichat.js\r\n * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE\r\n */\nvar CryptoJS = __webpack_require__(/*! crypto-js */ \"./node_modules/crypto-js/index.js\");\n\nfunction setApiAuth(ak, sk, user, companyNo, token, userno) {\n (0, _request.setAuth)(ak, sk, user, companyNo, token, userno);\n}\n\nfunction setApiToken(token) {\n (0, _request.setToken)(token);\n}\n\nfunction setApiUserNo(userno) {\n (0, _request.setUserNo)(userno);\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 getChatAgents(keyword) {\n return (0, _request.default)({\n url: '/api/v1/open/ai/agents',\n method: 'get',\n params: keyword ? {\n keyword: keyword\n } : {}\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n\nfunction startSession(agentCode) {\n return (0, _request.default)({\n url: '/api/v1/open/ai/chat/start',\n method: 'post',\n data: {\n agentCode: agentCode\n }\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n\nfunction syncToken(token, sessionId) {\n return (0, _request.default)({\n url: '/api/v1/open/ai/token',\n method: 'post',\n data: {\n token: token,\n sessionId: sessionId\n }\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n} // export function createStreamUrl(sessionId, message) {\n// const ts = Date.now().toString();\n// const params = { sessionId, message, ts };\n// const sign = signQueryParams(params, SK);\n// const params = new URLSearchParams({\n// sessionId,\n// message,\n// ak: AK,\n// ts,\n// sign\n// });\n// // const params = new URLSearchParams({ sessionId, message })\n// const base = process.env.VUE_APP_BACKEND_URL || process.env.VUE_APP_BASE_API || ''\n// return `${base}/api/v1/open/ai/chat/stream?${params}`\n// }\n\n\nfunction createStreamUrl(sessionId, message) {\n var ts = Date.now().toString();\n\n var _getAuth = (0, _request.getAuth)(),\n ak = _getAuth.ak,\n sk = _getAuth.sk,\n runtimeToken = _getAuth.token,\n user = _getAuth.user,\n companyNo = _getAuth.companyNo,\n userno = _getAuth.userno;\n\n var token = runtimeToken || (0, _cookie.getToken)(); // 1. 参与签名的参数(字段顺序对应要求)\n\n var signParams = {\n companyNo: companyNo,\n message: message,\n sessionId: sessionId,\n token: token,\n ts: ts,\n user: user,\n userNo: userno\n }; // 2. 强制按指定顺序拼接签名字符串(彻底保证顺序:message > sessionId > token > ts > user)\n\n var signKeyOrder = ['companyNo', 'message', 'sessionId', 'token', 'ts', 'user', 'userNo'];\n var signParts = [];\n signKeyOrder.forEach(function (key) {\n var val = signParams[key]; // 空值也保留字段(按后端规则按需调整)\n\n signParts.push(\"\".concat(key, \"=\").concat(val !== null && val !== void 0 ? val : ''));\n });\n var rawSignStr = signParts.join('&'); // 3. 计算签名\n\n var sig = hmacSha256Base64(rawSignStr, sk); // const BASE = 'http://localhost:8080' || process.env.VUE_APP_BACKEND_URL || process.env.VUE_APP_BASE_API || ''\n\n var BASE = 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_BACKEND_URL || \"https://ai.jybtech.cn\" || ''; // 4. 拼接最终URL,所有参数做URL编码\n\n var urlParams = new URLSearchParams();\n urlParams.append('companyNo', companyNo);\n urlParams.append('sessionId', sessionId);\n urlParams.append('message', message);\n urlParams.append('ak', ak);\n urlParams.append('ts', ts);\n urlParams.append('sign', sig);\n if (token) urlParams.append('token', token);\n urlParams.append('user', user);\n urlParams.append('userNo', userno);\n return \"\".concat(BASE, \"/api/v1/open/ai/chat/stream?\").concat(urlParams.toString());\n}\n\nfunction getSessionList() {\n return (0, _request.default)({\n url: '/api/v1/open/ai/chat/sessions',\n method: 'get'\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n\nfunction getSessionMessages(sessionId, page, pageSize) {\n var params = {};\n if (page) params.page = page;\n if (pageSize) params.pageSize = pageSize;\n return (0, _request.default)({\n url: \"/api/v1/open/ai/chat/sessions/\".concat(sessionId, \"/messages\"),\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\nfunction deleteSession(sessionId) {\n return (0, _request.default)({\n url: \"/api/v1/open/ai/chat/sessions/\".concat(sessionId),\n method: 'delete'\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n\nfunction stopStream(sessionId) {\n return (0, _request.default)({\n url: '/api/v1/open/ai/chat/stop',\n method: 'post',\n data: {\n sessionId: sessionId\n }\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n\nfunction markFormSubmitted(sessionId, msgKey, formData) {\n return (0, _request.default)({\n url: '/api/v1/open/ai/chat/form-submitted',\n method: 'post',\n data: {\n sessionId: sessionId,\n msgKey: msgKey,\n formData: formData ? JSON.stringify(formData) : null\n }\n }).then(function (res) {\n if (res.code === 200) return res.data;\n return Promise.reject(res.message);\n });\n}\n\nfunction getCurrentUserInfo() {\n var api = \"https://api.qgbtech.cn\" || false;\n var enterprise = \"/enterprise-managerment\" || false;\n return (0, _request.default)({\n url: \"\".concat(api).concat(enterprise, \"/api/user/getCurrentUserInfo\"),\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//# sourceURL=webpack://ZjPluginIntelligent/./src/lib/api/apichat.js?");
|
|
11105
11105
|
|
|
11106
11106
|
/***/ }),
|
|
11107
11107
|
|
|
@@ -11982,7 +11982,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n
|
|
|
11982
11982
|
/***/ (function(module, exports, __webpack_require__) {
|
|
11983
11983
|
|
|
11984
11984
|
"use strict";
|
|
11985
|
-
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\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 });\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 _callee(url, value, field, formData) {\n var _options$remoteTimeou, json;\n\n return regeneratorRuntime.wrap(function _callee$(_context) {\n while (1) {\n switch (_context.prev = _context.next) {\n case 0:\n _context.prev = 0;\n _context.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 = _context.sent;\n return _context.abrupt(\"return\", parseResult(json));\n\n case 7:\n _context.prev = 7;\n _context.t0 = _context[\"catch\"](0);\n return _context.abrupt(\"return\", '网络请求失败,请重试');\n\n case 10:\n case \"end\":\n return _context.stop();\n }\n }\n }, _callee, 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 _callee14(el) {\n var ruleStr, inner, targetKey, formData, msg, scope;\n return regeneratorRuntime.wrap(function _callee14$(_context14) {\n while (1) {\n switch (_context14.prev = _context14.next) {\n case 0:\n ruleStr = el.getAttribute('data-rule');\n\n if (ruleStr) {\n _context14.next = 3;\n break;\n }\n\n return _context14.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 _context14.next = 7;\n break;\n }\n\n return _context14.abrupt(\"return\");\n\n case 7:\n formData = collectFormData(inner);\n _context14.next = 10;\n return V.validateField(readFieldValue(el, inner), ruleStr, formData, targetKey);\n\n case 10:\n msg = _context14.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 _context14.stop();\n }\n }\n }, _callee14);\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 _callee3(formData, event, element) {\n var form, allFields, hasError, resolveField, tasks, submitUrl, allData, submitScope, _durEl, balanceModeEl, balanceMode, leaveBalance, duration, auth, typeEl, holidayName, holidayTypeEl, minLeaveUnit, durationDayEl, durationDay, halfLabels, stHalfEl, etHalfEl, stHalf, etHalf, stVal, etVal, sd, ed, applierName, companySel, departSel, tokuiParams, approvalResult, method, resultJson, params, resultText;\n\n return regeneratorRuntime.wrap(function _callee3$(_context3) {\n while (1) {\n switch (_context3.prev = _context3.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 _context3.next = 4;\n break;\n }\n\n return _context3.abrupt(\"return\");\n\n case 4:\n if (!form._tvSubmitting) {\n _context3.next = 8;\n break;\n }\n\n console.warn('[Validator] formSubmit: 正在提交中,跳过');\n (0, _message.showMessage)('正在提交中,请稍候...', 'warning');\n return _context3.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 _callee2() {\n var ruleStr, inner, targetKey, fd, fieldVal, msg;\n return regeneratorRuntime.wrap(function _callee2$(_context2) {\n while (1) {\n switch (_context2.prev = _context2.next) {\n case 0:\n ruleStr = f.getAttribute('data-rule');\n\n if (ruleStr) {\n _context2.next = 3;\n break;\n }\n\n return _context2.abrupt(\"return\");\n\n case 3:\n inner = resolveField(f);\n targetKey = inner.id || inner.name || f.id || '';\n\n if (targetKey) {\n _context2.next = 7;\n break;\n }\n\n return _context2.abrupt(\"return\");\n\n case 7:\n fd = collectFormData(f);\n fieldVal = readFieldValue(f, inner);\n _context2.next = 11;\n return V.validateField(fieldVal, ruleStr, fd, targetKey);\n\n case 11:\n msg = _context2.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 _context2.stop();\n }\n }\n }, _callee2);\n }))());\n });\n _context3.next = 15;\n return Promise.all(tasks);\n\n case 15:\n if (!hasError) {\n _context3.next = 19;\n break;\n }\n\n form._tvSubmitting = false;\n safeFeed('[callout t:error tt:校验未通过 tx:请修正红色标记的字段]', form);\n return _context3.abrupt(\"return\");\n\n case 19:\n // 校验通过 → 提交\n submitUrl = form ? form.getAttribute('data-url') : null;\n\n if (submitUrl) {\n _context3.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 _context3.abrupt(\"return\");\n\n case 26:\n // ============ 请假专用:余额校验 ============\n allData = collectFormData(element);\n submitScope = resolveScope(form) || form; // 调试:打印 duration 字段的实际状态\n\n _durEl = queryById('duration', submitScope);\n balanceModeEl = queryById('_balanceMode', submitScope);\n balanceMode = balanceModeEl ? parseInt(balanceModeEl.value, 10) : -1;\n leaveBalance = parseFloat(allData.leaveBalance || '0');\n duration = parseFloat(allData.duration || '0'); // balanceMode === 2:该假期对当前用户不适用\n\n if (!(balanceMode === 2)) {\n _context3.next = 38;\n break;\n }\n\n form._tvSubmitting = false;\n (0, _message.showMessage)('该假期类型对您不适用', 'warning');\n safeFeed('[callout t:warning tt:提交失败 tx:该假期类型对您不适用,请选择其他假期类型]', form);\n return _context3.abrupt(\"return\");\n\n case 38:\n if (!(balanceMode === 0 && duration > leaveBalance)) {\n _context3.next = 43;\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 _context3.abrupt(\"return\");\n\n case 43:\n if (!(!duration || isNaN(duration))) {\n _context3.next = 48;\n break;\n }\n\n form._tvSubmitting = false;\n (0, _message.showMessage)('请假时长不能为空,请先选择起止时间', 'warning');\n safeFeed('[callout t:warning tt:提交失败 tx:请假时长不能为空,请先选择起止时间]', form);\n return _context3.abrupt(\"return\");\n\n case 48:\n // 组装请假提交数据(对齐后端 tbManagementApplyLeave 表结构)\n auth = (0, _request.getAuth)(); // 获取选中假期类型的名称\n\n typeEl = queryById('type', submitScope);\n holidayName = '';\n\n if (typeEl && typeEl.selectedIndex >= 0 && typeEl.options[typeEl.selectedIndex]) {\n holidayName = typeEl.options[typeEl.selectedIndex].textContent || typeEl.options[typeEl.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 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 } // 日期转时间戳(对齐源项目 leaveApply.vue 的处理)\n\n\n if (allData.startTime) {\n try {\n allData.startTime = new Date(allData.startTime).getTime();\n } catch (e) {}\n }\n\n if (allData.endTime) {\n try {\n allData.endTime = new Date(allData.endTime).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 // 如果表单配置了 data-approval-type,则调用审批引擎获取 uniqueId 并注入提交数据\n\n _context3.next = 95;\n return runApprovalFlow(form, allData);\n\n case 95:\n approvalResult = _context3.sent;\n\n if (!(approvalResult === false)) {\n _context3.next = 99;\n break;\n }\n\n form._tvSubmitting = false;\n return _context3.abrupt(\"return\");\n\n case 99:\n if (approvalResult && approvalResult.uniqueId) {\n allData.uniqueId = approvalResult.uniqueId;\n }\n\n safeFeed('[callout t:info tt:提交中 tx:正在提交数据...]', form);\n _context3.prev = 101;\n method = (form.getAttribute('data-method') || 'POST').toUpperCase();\n resultJson = null;\n\n if (!(method === 'GET')) {\n _context3.next = 112;\n break;\n }\n\n params = new URLSearchParams(allData).toString();\n if (params) submitUrl += (submitUrl.includes('?') ? '&' : '?') + params;\n _context3.next = 109;\n return _request.default.get(submitUrl);\n\n case 109:\n resultJson = _context3.sent;\n _context3.next = 121;\n break;\n\n case 112:\n if (!(method === 'DELETE')) {\n _context3.next = 118;\n break;\n }\n\n _context3.next = 115;\n return _request.default.delete(submitUrl, {\n data: allData\n });\n\n case 115:\n resultJson = _context3.sent;\n _context3.next = 121;\n break;\n\n case 118:\n _context3.next = 120;\n return _request.default.post(submitUrl, allData);\n\n case 120:\n resultJson = _context3.sent;\n\n case 121:\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 _context3.next = 131;\n break;\n\n case 128:\n _context3.prev = 128;\n _context3.t0 = _context3[\"catch\"](101);\n // 错误提示已由 http 拦截器统一处理,这里仅做表单内反馈\n safeFeed('[callout t:error tt:提交失败 tx:\"' + (_context3.t0.message || '').replace(/\"/g, '') + '\"]', form);\n\n case 131:\n _context3.prev = 131;\n form._tvSubmitting = false;\n return _context3.finish(131);\n\n case 134:\n case \"end\":\n return _context3.stop();\n }\n }\n }, _callee3, null, [[101, 128, 131, 134]]);\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\n window.TokUI.registerHandler('calcLeaveDuration', /*#__PURE__*/function () {\n var _ref4 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee5(data, event, element) {\n var scope;\n return regeneratorRuntime.wrap(function _callee5$(_context5) {\n while (1) {\n switch (_context5.prev = _context5.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 _context5.next = 4;\n break;\n }\n\n return _context5.abrupt(\"return\");\n\n case 4:\n // 防抖:延迟 400ms 执行,期间如有新触发则取消前一次(仅在当前表单作用域内)\n if (scope._calcDurationTimer) clearTimeout(scope._calcDurationTimer);\n return _context5.abrupt(\"return\", new Promise(function (resolve) {\n scope._calcDurationTimer = setTimeout( /*#__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 scope._calcDurationTimer = null;\n\n if (!scope._calcDurationLock) {\n _context4.next = 4;\n break;\n }\n\n resolve();\n return _context4.abrupt(\"return\");\n\n case 4:\n scope._calcDurationLock = true;\n _context4.prev = 5;\n _context4.next = 8;\n return _calcLeaveDurationImpl(element, scope);\n\n case 8:\n _context4.prev = 8;\n scope._calcDurationLock = false;\n return _context4.finish(8);\n\n case 11:\n resolve();\n\n case 12:\n case \"end\":\n return _context4.stop();\n }\n }\n }, _callee4, null, [[5,, 8, 11]]);\n })), 400);\n }));\n\n case 6:\n case \"end\":\n return _context5.stop();\n }\n }\n }, _callee5);\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 /**\r\n * 从表单字段读取值,兼容 select / input / hidden\r\n * - \"login*\" 前缀值从 window.__tokuiParams 读取\r\n * - 否则在当前作用域内按 id 精确查找(多表单隔离)\r\n */\n\n\n function _calcLeaveDurationImpl2() {\n _calcLeaveDurationImpl2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee7(element, explicitScope) {\n var calcScope, durationEl, durationField, bodyObj, bodyDeps, hasParams, i, attr, suffix, depParam, paramKey, depElId, htEl, startTimeStr, endTimeStr, startTimeMs, endTimeMs, endEl, holidayType, stHalfEl, etHalfEl, stHalf, etHalf, startDate, endDate1, endDate, diffHours, endEl2, calcUrl, json, unitText, fieldWrapper, appendEl, children, ci, span, ddEl, form;\n return regeneratorRuntime.wrap(function _callee7$(_context7) {\n while (1) {\n switch (_context7.prev = _context7.next) {\n case 0:\n // ★ 多表单隔离:优先使用调用方传入的作用域,否则从 element 向上查找\n calcScope = explicitScope || findNearestTokuiScope(element);\n\n if (calcScope) {\n _context7.next = 3;\n break;\n }\n\n return _context7.abrupt(\"return\");\n\n case 3:\n durationEl = queryById('duration', calcScope);\n\n if (durationEl) {\n _context7.next = 6;\n break;\n }\n\n return _context7.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 _context7.next = 9;\n break;\n }\n\n return _context7.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 _context7.next = 31;\n break;\n }\n\n i = 0;\n\n case 14:\n if (!(i < durationField.attributes.length)) {\n _context7.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 _context7.next = 18;\n break;\n }\n\n return _context7.abrupt(\"continue\", 28);\n\n case 18:\n suffix = attr.name.substring('data-calc-duration-body-'.length);\n\n if (suffix) {\n _context7.next = 21;\n break;\n }\n\n return _context7.abrupt(\"continue\", 28);\n\n case 21:\n if (!(suffix.indexOf('dep-') === 0)) {\n _context7.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 _context7.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 _context7.next = 14;\n break;\n\n case 31:\n if (hasParams) {\n _context7.next = 34;\n break;\n }\n\n console.warn('[Validator] calcLeaveDuration: duration 字段上未配置 data-calc-duration-body-dep-* 参数映射,跳过计算');\n return _context7.abrupt(\"return\");\n\n case 34:\n _context7.t0 = regeneratorRuntime.keys(bodyDeps);\n\n case 35:\n if ((_context7.t1 = _context7.t0()).done) {\n _context7.next = 43;\n break;\n }\n\n depParam = _context7.t1.value;\n\n if (bodyDeps.hasOwnProperty(depParam)) {\n _context7.next = 39;\n break;\n }\n\n return _context7.abrupt(\"continue\", 35);\n\n case 39:\n depElId = bodyDeps[depParam];\n bodyObj[depParam] = readFieldValueById(depElId, calcScope);\n _context7.next = 35;\n break;\n\n case 43:\n // ★ 兜底:确保 minLeaveUnit 始终存在(源项目始终发送 minLeaveUnit: this.holidayType)\n // 如果 DSL 未配置 data-calc-duration-body-dep-minLeaveUnit,从 _holidayType 隐藏字段读取\n if (bodyObj.minLeaveUnit === undefined || bodyObj.minLeaveUnit === '') {\n htEl = queryById('_holidayType', calcScope);\n bodyObj.minLeaveUnit = htEl ? htEl.value : '0';\n if (bodyObj.minLeaveUnit === '') bodyObj.minLeaveUnit = '0';\n } // 校验:起止时间必须都有值\n\n\n if (!(!bodyObj.startTime || !bodyObj.endTime)) {\n _context7.next = 47;\n break;\n }\n\n if (durationEl) durationEl.value = '';\n return _context7.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 _context7.next = 55;\n break;\n }\n\n return _context7.abrupt(\"return\");\n\n case 55:\n if (!(endTimeMs < startTimeMs)) {\n _context7.next = 60;\n break;\n }\n\n (0, _message.showMessage)('结束时间不能早于开始时间', 'warning');\n endEl = queryById(bodyDeps.endTime, calcScope);\n if (endEl) endEl.value = '';\n return _context7.abrupt(\"return\");\n\n case 60:\n bodyObj.startTime = startTimeMs;\n bodyObj.endTime = endTimeMs; // 读取 minLeaveUnit(0=按小时, 1=按半天, 2=按天)\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 _context7.next = 72;\n break;\n }\n\n diffHours = (bodyObj.endTime - bodyObj.startTime) / (1000 * 60 * 60);\n\n if (!(diffHours < 1)) {\n _context7.next = 72;\n break;\n }\n\n (0, _message.showMessage)('请假时长不得小于一小时', 'warning');\n endEl2 = queryById(bodyDeps.endTime, calcScope);\n if (endEl2) endEl2.value = '';\n return _context7.abrupt(\"return\");\n\n case 72:\n // ★ 始终构造 startTimeStr / endTimeStr(与源项目 getDuration 保持一致)\n // 源项目: startTimeStr = `${dayjs(startTime).format(\"YYYY-MM-DD\")} ${startTimeHalf == 1 ? \"上午\" : \"下午\"}`\n bodyObj.startTimeStr = buildHalfDayTimeStr(bodyObj.startTime, 'startTimeHalf', calcScope);\n bodyObj.endTimeStr = buildHalfDayTimeStr(bodyObj.endTime, 'endTimeHalf', calcScope); // 调用服务端时长计算 API\n\n calcUrl = cleanUrl(durationField.getAttribute('data-calc-duration-url') || '');\n\n if (calcUrl) {\n _context7.next = 78;\n break;\n }\n\n console.warn('[Validator] calcLeaveDuration: duration 字段上未配置 data-calc-duration-url');\n return _context7.abrupt(\"return\");\n\n case 78:\n _context7.prev = 78;\n _context7.next = 81;\n return _request.default.post(calcUrl, bodyObj);\n\n case 81:\n json = _context7.sent;\n\n if (!(json.code === 200 && json.data)) {\n _context7.next = 116;\n break;\n }\n\n if (durationEl) durationEl.value = String(json.data.duration || '');\n unitText = getLeaveUnitText(bodyObj.minLeaveUnit);\n fieldWrapper = durationEl.closest('.tokui-field') || durationEl.parentElement;\n\n if (!fieldWrapper) {\n _context7.next = 99;\n break;\n }\n\n appendEl = fieldWrapper.querySelector('.tokui-input-group__append, .tokui-append, .el-input-group__append');\n\n if (appendEl) {\n _context7.next = 98;\n break;\n }\n\n children = fieldWrapper.querySelectorAll('span, div');\n ci = children.length - 1;\n\n case 91:\n if (!(ci >= 0)) {\n _context7.next = 98;\n break;\n }\n\n if (!(children[ci].textContent && children[ci].textContent.trim() && !children[ci].querySelector('input'))) {\n _context7.next = 95;\n break;\n }\n\n appendEl = children[ci];\n return _context7.abrupt(\"break\", 98);\n\n case 95:\n ci--;\n _context7.next = 91;\n break;\n\n case 98:\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 99:\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 _context7.next = 112;\n break;\n }\n\n if (form) {\n _context7.next = 107;\n break;\n }\n\n return _context7.abrupt(\"return\");\n\n case 107:\n ddEl = document.createElement('input');\n ddEl.type = 'hidden';\n ddEl.id = '_durationDay';\n ddEl.name = '_durationDay';\n form.appendChild(ddEl);\n\n case 112:\n ddEl.value = String(json.data.durationDay || ''); // ★ 时长计算完毕后,刷新审批流程(durationDay 变量已就绪)\n\n if (form) {\n try {\n refreshApprovalFlow(form);\n } catch (e2) {}\n }\n\n _context7.next = 117;\n break;\n\n case 116:\n if (holidayType !== 1) {\n (0, _message.showMessage)(json.msg || json.message || '计算时长失败', 'warning');\n }\n\n case 117:\n _context7.next = 122;\n break;\n\n case 119:\n _context7.prev = 119;\n _context7.t2 = _context7[\"catch\"](78);\n console.warn('[Validator] calcLeaveDuration API error:', _context7.t2.message);\n\n case 122:\n case \"end\":\n return _context7.stop();\n }\n }\n }, _callee7, null, [[78, 119]]);\n }));\n return _calcLeaveDurationImpl2.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 var halfVal = readFieldValueById(halfElId, scope);\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 _ref6 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee6(data, event, element) {\n var form, allData, submitUrl;\n return regeneratorRuntime.wrap(function _callee6$(_context6) {\n while (1) {\n switch (_context6.prev = _context6.next) {\n case 0:\n form = resolveForm(element);\n\n if (form) {\n _context6.next = 3;\n break;\n }\n\n return _context6.abrupt(\"return\");\n\n case 3:\n if (!form._tvSubmitting) {\n _context6.next = 6;\n break;\n }\n\n (0, _message.showMessage)('正在处理中,请稍候...', 'warning');\n return _context6.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 _context6.next = 18;\n break;\n }\n\n form._tvSubmitting = false;\n (0, _message.showMessage)('暂存失败:未配置提交地址', 'error');\n return _context6.abrupt(\"return\");\n\n case 18:\n safeFeed('[callout t:info tt:暂存中 tx:正在保存草稿...]', form);\n _context6.prev = 19;\n _context6.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 _context6.next = 28;\n break;\n\n case 26:\n _context6.prev = 26;\n _context6.t0 = _context6[\"catch\"](19);\n\n case 28:\n _context6.prev = 28;\n form._tvSubmitting = false;\n return _context6.finish(28);\n\n case 31:\n case \"end\":\n return _context6.stop();\n }\n }\n }, _callee6, null, [[19, 26, 28, 31]]);\n }));\n\n return function (_x14, _x15, _x16) {\n return _ref6.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\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 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 break;\n }\n }\n }\n }\n\n target = target.parentElement;\n }\n } // 在容器上监听 change 事件(捕获阶段,确保优先处理)\n\n\n container.addEventListener('change', 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 }\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 }\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 的取值:\n // departmentNo = departNo(申请部门),为空时回退 companyNo(申请单位)\n\n if (!typeList.departmentNo) typeList.departmentNo = allData.departNo || allData.companyNo || allData.departmentNo || '';\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 } // ============ 阶段 1:initApprovalFlow — 表单加载时初始化 ============\n\n\n function initApprovalFlow(_x17) {\n return _initApprovalFlow.apply(this, arguments);\n } // ============ 阶段 2:refreshApprovalFlow — 字段变更时重新预览 ============\n\n\n function _initApprovalFlow() {\n _initApprovalFlow = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee15(form) {\n var state, _readyTries, auth, companyNo, processIsOpenUrl, openJson, openData, _approvalDebounceTimer, scheduleRefresh, _approvalScope, _approvalObs, mapKeys;\n\n return regeneratorRuntime.wrap(function _callee15$(_context15) {\n while (1) {\n switch (_context15.prev = _context15.next) {\n case 0:\n state = getApprovalState(form);\n\n if (!state.inited) {\n _context15.next = 3;\n break;\n }\n\n return _context15.abrupt(\"return\");\n\n case 3:\n if (state.approvalType) {\n _context15.next = 5;\n break;\n }\n\n return _context15.abrupt(\"return\");\n\n case 5:\n if (isFormReady(form)) {\n _context15.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 _context15.abrupt(\"return\");\n\n case 8:\n state.inited = true;\n auth = (0, _request.getAuth)();\n companyNo = auth.companyNo || '';\n _context15.prev = 11;\n processIsOpenUrl = state.approvalPrefix + '/v1/pc/process/processIsOpen';\n _context15.next = 15;\n return _request.default.post(processIsOpenUrl, {\n companyNo: companyNo,\n type: state.approvalType\n });\n\n case 15:\n openJson = _context15.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 _context15.next = 22;\n break;\n }\n\n return _context15.abrupt(\"return\");\n\n case 22:\n if (!(state.variableList.length === 0)) {\n _context15.next = 25;\n break;\n }\n\n _context15.next = 25;\n return refreshApprovalFlow(form);\n\n case 25:\n // ★ 实时监听:审批变量涉及的字段变更 → 重新调用 processPreview\n // - calcLeaveDuration 中也会在时长计算后触发 refreshApprovalFlow\n // - 这里通过 form 级 change/input 委托 + MutationObserver + 防抖确保不漏触发\n if (!form._approvalChangeBound) {\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 }); // MutationObserver:捕获 hidden input 等不触发 change 的 value 属性变更\n\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 } // ★ 关键修复:data-default-label 的初始 change 事件早于监听器注册,\n // 需要立即检查字段是否已有值,如有则直接触发 preview\n\n\n mapKeys = Object.keys(state.variableMap);\n refreshApprovalFlow(form);\n _context15.next = 34;\n break;\n\n case 30:\n _context15.prev = 30;\n _context15.t0 = _context15[\"catch\"](11);\n console.warn('[Approval] init error:', _context15.t0.message);\n safeFeed('[callout t:error tt:审批异常 tx:\"' + _context15.t0.message + '\"]', form);\n\n case 34:\n case \"end\":\n return _context15.stop();\n }\n }\n }, _callee15, null, [[11, 30]]);\n }));\n return _initApprovalFlow.apply(this, arguments);\n }\n\n function refreshApprovalFlow(_x18) {\n return _refreshApprovalFlow.apply(this, arguments);\n } // 清除审批可视化渲染\n\n\n function _refreshApprovalFlow() {\n _refreshApprovalFlow = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee16(form) {\n var state, typeList, typeScope, missingList, complete, fixedKeys, requiredApprovalVars, deptAlreadyMissing, auth, companyNo, previewUrl, previewBody, previewJson, previewData, feedbackHost, oldFeedbacks;\n return regeneratorRuntime.wrap(function _callee16$(_context16) {\n while (1) {\n switch (_context16.prev = _context16.next) {\n case 0:\n state = getApprovalState(form);\n\n if (!state._stopped) {\n _context16.next = 3;\n break;\n }\n\n return _context16.abrupt(\"return\");\n\n case 3:\n if (!(state.approvaStatus !== 0)) {\n _context16.next = 5;\n break;\n }\n\n return _context16.abrupt(\"return\");\n\n case 5:\n if (!state._rendering) {\n _context16.next = 8;\n break;\n }\n\n state._refreshPending = true;\n return _context16.abrupt(\"return\");\n\n case 8:\n state._rendering = true;\n _context16.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 _context16.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 _context16.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 _context16.next = 30;\n return _request.default.post(previewUrl, previewBody);\n\n case 30:\n previewJson = _context16.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 _context16.next = 46;\n break;\n\n case 43:\n _context16.prev = 43;\n _context16.t0 = _context16[\"catch\"](9);\n console.warn('[Approval] refresh error:', _context16.t0.message);\n\n case 46:\n _context16.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 _context16.finish(46);\n\n case 50:\n case \"end\":\n return _context16.stop();\n }\n }\n }, _callee16, 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(_x19) {\n return _saveApprovalFlowState.apply(this, arguments);\n } // ============ 审批流程可视化渲染 ============\n\n\n function _saveApprovalFlowState() {\n _saveApprovalFlowState = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee17(form) {\n var state, processSaveList, saveBody, saveJson, saveData;\n return regeneratorRuntime.wrap(function _callee17$(_context17) {\n while (1) {\n switch (_context17.prev = _context17.next) {\n case 0:\n state = getApprovalState(form);\n\n if (!(!state.approvalType || state.approvaStatus !== 0)) {\n _context17.next = 3;\n break;\n }\n\n return _context17.abrupt(\"return\", null);\n\n case 3:\n _context17.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 _context17.next = 8;\n return _request.default.post(state.approvalPrefix + '/v1/pc/process/processSave', saveBody);\n\n case 8:\n saveJson = _context17.sent;\n saveData = saveJson.data || {};\n return _context17.abrupt(\"return\", {\n uniqueId: saveData.uniqueId || ''\n });\n\n case 13:\n _context17.prev = 13;\n _context17.t0 = _context17[\"catch\"](3);\n console.warn('[Approval] save error:', _context17.t0.message);\n (0, _message.showMessage)('审批流程异常:' + _context17.t0.message, 'error');\n return _context17.abrupt(\"return\", false);\n\n case 18:\n case \"end\":\n return _context17.stop();\n }\n }\n }, _callee17, 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 // 使用组织架构选择器\n openOrgSelectorForNode(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 || '';\n\n if (approver.icon) {\n var img = document.createElement('img');\n img.src = approver.icon;\n img.className = 'tv-approver-avatar';\n img.alt = name;\n img.title = name;\n img.style.width = '28px';\n img.style.height = '28px';\n img.style.borderRadius = '50%';\n tag.appendChild(img);\n } else {\n var abbr = name.length >= 2 ? name.substring(name.length - 2) : name;\n tag.textContent = abbr;\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 前缀:从 approvalPrefix 推导(sgb-meta-design → enterprise-managerment)\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(_x20, _x21) {\n return _loadDeptChildren.apply(this, arguments);\n } // ========== 加载组织架构(根节点) ==========\n\n\n function _loadDeptChildren() {\n _loadDeptChildren = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee8(deptId, deptItem) {\n var orgJson, orgData, contacts, orgInfoList;\n return regeneratorRuntime.wrap(function _callee8$(_context8) {\n while (1) {\n switch (_context8.prev = _context8.next) {\n case 0:\n if (!loadingDepts[deptId]) {\n _context8.next = 2;\n break;\n }\n\n return _context8.abrupt(\"return\");\n\n case 2:\n loadingDepts[deptId] = true;\n renderTree(); // 显示加载中状态\n\n _context8.prev = 4;\n _context8.next = 7;\n return _request.default.get(enterpriseBase + '/v1/organization/getOrgInfoV2?userNo=' + userNo + '&companyNo=' + companyNo + '&deptId=' + deptId);\n\n case 7:\n orgJson = _context8.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 _context8.next = 24;\n break;\n\n case 19:\n _context8.prev = 19;\n _context8.t0 = _context8[\"catch\"](4);\n console.warn('[Validator] 加载子部门失败:', _context8.t0.message);\n delete loadingDepts[deptId];\n renderTree();\n\n case 24:\n case \"end\":\n return _context8.stop();\n }\n }\n }, _callee8, 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 _callee9() {\n var companyJson, companies, mainCompany, orgJson, orgData, contacts, orgInfoList;\n return regeneratorRuntime.wrap(function _callee9$(_context9) {\n while (1) {\n switch (_context9.prev = _context9.next) {\n case 0:\n _context9.prev = 0;\n _context9.next = 3;\n return _request.default.get(enterpriseBase + '/v1/organization/getCompanyInfo?userNo=' + userNo + '&companyNo=' + companyNo);\n\n case 3:\n companyJson = _context9.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 _context9.next = 8;\n return _request.default.get(enterpriseBase + '/v1/organization/getOrgInfoV2?userNo=' + userNo + '&companyNo=' + companyNo + '&deptId=1');\n\n case 8:\n orgJson = _context9.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 _context9.next = 25;\n break;\n\n case 21:\n _context9.prev = 21;\n _context9.t0 = _context9[\"catch\"](0);\n console.warn('[Validator] 加载组织架构失败:', _context9.t0.message);\n treeContainer.innerHTML = '<div class=\"tv-org-error\">加载组织架构失败: ' + _context9.t0.message + '</div>';\n\n case 25:\n case \"end\":\n return _context9.stop();\n }\n }\n }, _callee9, 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(_x22) {\n return _doSearch.apply(this, arguments);\n } // 初始化\n\n\n function _doSearch() {\n _doSearch = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee10(keyword) {\n var searchJson, results, selectedIds;\n return regeneratorRuntime.wrap(function _callee10$(_context10) {\n while (1) {\n switch (_context10.prev = _context10.next) {\n case 0:\n if (keyword) {\n _context10.next = 2;\n break;\n }\n\n return _context10.abrupt(\"return\");\n\n case 2:\n treeContainer.innerHTML = '<div class=\"tv-org-loading\">搜索中...</div>';\n _context10.prev = 3;\n _context10.next = 6;\n return _request.default.get(enterpriseBase + '/v2/api/contractBasicInfo/searchKeyWord?keyWord=' + encodeURIComponent(keyword) + '&comNo=' + companyNo);\n\n case 6:\n searchJson = _context10.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 _context10.next = 14;\n break;\n }\n\n treeContainer.innerHTML = '<div class=\"tv-org-empty\">无匹配结果</div>';\n return _context10.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 _context10.next = 21;\n break;\n\n case 17:\n _context10.prev = 17;\n _context10.t0 = _context10[\"catch\"](3);\n console.warn('[Validator] 搜索失败:', _context10.t0.message);\n treeContainer.innerHTML = '<div class=\"tv-org-error\">搜索失败: ' + _context10.t0.message + '</div>';\n\n case 21:\n case \"end\":\n return _context10.stop();\n }\n }\n }, _callee10, 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 }\n\n function openOrgSelectorForNode(state, nodeIndex, node) {\n // 抄送人组织架构选择(简化版:从 optionalUserList 中选择)\n // 实际项目中可能需要完整的组织架构树,这里用 optionalUserList 下拉替代\n if (node.optionalUserList && node.optionalUserList.length > 0) {\n showApproverSelector(state, nodeIndex, node);\n }\n } // ============ 提交时调用的审批入口(替代原 runApprovalFlow) ============\n\n\n function runApprovalFlow(_x23, _x24) {\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 _callee18(form, allData) {\n var state, _retry, _wait;\n\n return regeneratorRuntime.wrap(function _callee18$(_context18) {\n while (1) {\n switch (_context18.prev = _context18.next) {\n case 0:\n state = getApprovalState(form);\n\n if (state.approvalType) {\n _context18.next = 3;\n break;\n }\n\n return _context18.abrupt(\"return\", null);\n\n case 3:\n if (state.inited) {\n _context18.next = 6;\n break;\n }\n\n _context18.next = 6;\n return initApprovalFlow(form);\n\n case 6:\n if (!(state.approvaStatus !== 0)) {\n _context18.next = 8;\n break;\n }\n\n return _context18.abrupt(\"return\", {\n uniqueId: '',\n skipped: true\n });\n\n case 8:\n if (!(!state.processList || state.processList.length === 0)) {\n _context18.next = 11;\n break;\n }\n\n _context18.next = 11;\n return refreshApprovalFlow(form);\n\n case 11:\n if (!(!state.processList || state.processList.length === 0)) {\n _context18.next = 31;\n break;\n }\n\n _retry = 0;\n\n case 13:\n if (!(_retry < 3)) {\n _context18.next = 31;\n break;\n }\n\n // 等待 _rendering 锁释放\n _wait = 0;\n\n case 15:\n if (!(state._rendering && _wait < 2000)) {\n _context18.next = 21;\n break;\n }\n\n _context18.next = 18;\n return new Promise(function (r) {\n setTimeout(r, 100);\n });\n\n case 18:\n _wait += 100;\n _context18.next = 15;\n break;\n\n case 21:\n // 强制重置渲染锁并重试\n state._rendering = false;\n _context18.next = 24;\n return refreshApprovalFlow(form);\n\n case 24:\n if (!(state.processList && state.processList.length > 0)) {\n _context18.next = 26;\n break;\n }\n\n return _context18.abrupt(\"break\", 31);\n\n case 26:\n _context18.next = 28;\n return new Promise(function (r) {\n setTimeout(r, 500);\n });\n\n case 28:\n _retry++;\n _context18.next = 13;\n break;\n\n case 31:\n if (!(!state.processList || state.processList.length === 0)) {\n _context18.next = 35;\n break;\n }\n\n console.warn('[Approval] runApprovalFlow: processList 为空,中止提交');\n safeFeed('[callout t:error tt:审批流程未就绪 tx:\"审批流程数据未就绪,请检查请假类型和时长后重试\"]', form);\n return _context18.abrupt(\"return\", false);\n\n case 35:\n _context18.next = 37;\n return saveApprovalFlowState(form);\n\n case 37:\n return _context18.abrupt(\"return\", _context18.sent);\n\n case 38:\n case \"end\":\n return _context18.stop();\n }\n }\n }, _callee18);\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(_x25) {\n return _httpDataLoad.apply(this, arguments);\n } // ---------- 替换 $字段名$ ----------\n\n\n function _httpDataLoad() {\n _httpDataLoad = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee19(req) {\n var method, body;\n return regeneratorRuntime.wrap(function _callee19$(_context19) {\n while (1) {\n switch (_context19.prev = _context19.next) {\n case 0:\n method = req.opts && req.opts.method || 'GET';\n\n if (!(method === 'GET')) {\n _context19.next = 3;\n break;\n }\n\n return _context19.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 _context19.next = 7;\n break;\n }\n\n return _context19.abrupt(\"return\", _request.default.delete(req.url, {\n data: body\n }));\n\n case 7:\n if (!(method === 'PUT')) {\n _context19.next = 9;\n break;\n }\n\n return _context19.abrupt(\"return\", _request.default.put(req.url, body));\n\n case 9:\n return _context19.abrupt(\"return\", _request.default.post(req.url, body));\n\n case 10:\n case \"end\":\n return _context19.stop();\n }\n }\n }, _callee19);\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 _callee11() {\n var req, cached, json, items;\n return regeneratorRuntime.wrap(function _callee11$(_context11) {\n while (1) {\n switch (_context11.prev = _context11.next) {\n case 0:\n _context11.prev = 0;\n req = buildDataLoadRequest(ctn, url);\n\n if (!req.skip) {\n _context11.next = 4;\n break;\n }\n\n return _context11.abrupt(\"return\");\n\n case 4:\n cached = getCached(req.cacheKey);\n\n if (!cached) {\n _context11.next = 8;\n break;\n }\n\n applyListTemplate(container, tplEl, template, cached);\n return _context11.abrupt(\"return\");\n\n case 8:\n _context11.next = 10;\n return httpDataLoad(req);\n\n case 10:\n json = _context11.sent;\n items = resolveList(json);\n setCached(req.cacheKey, items);\n applyListTemplate(container, tplEl, template, items);\n _context11.next = 19;\n break;\n\n case 16:\n _context11.prev = 16;\n _context11.t0 = _context11[\"catch\"](0);\n console.warn('data-load-list 失败:', container.id, _context11.t0.message);\n\n case 19:\n case \"end\":\n return _context11.stop();\n }\n }\n }, _callee11, 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,结束 11:59)\n // - 其他值 → 原样转字符串\n\n\n function formatDateValue(rawVal, tgtEl, fieldKey) {\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] + ' ' + dtMatch[2] + ':' + dtMatch[3]; // 纯日期字符串: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) : 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);\n return yyyy + '-' + mm + '-' + dd + ' ' + timePart;\n }\n\n return yyyy + '-' + mm + '-' + dd;\n } // 按小时模式 datetimepicker 的默认补全时间:\n // 开始时间 → 00:00,结束时间 → 23:59(对应默认范围 \"0.00-23.59\")\n\n\n function getDefaultTimeForField(fieldKey, tgtEl) {\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 if (k.indexOf('end') >= 0) return '23:59';\n return '00:00';\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 fieldEl = queryById(fieldId, scope || container);\n if (!fieldEl) return;\n var fieldWrapper = fieldEl.closest('.tokui-field') || fieldEl.parentElement;\n if (!fieldWrapper) return;\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 }\n }\n\n function refreshHolidayUnitSuffixFromHiddenField(scope) {\n var holidayTypeEl = queryById('_holidayType', scope || container);\n\n if (!holidayTypeEl || holidayTypeEl.value === '' || holidayTypeEl.value === undefined || holidayTypeEl.value === null) {\n return null;\n }\n\n var unitText = getLeaveUnitText(holidayTypeEl.value);\n if (!unitText) return null;\n syncLeaveUnitSuffix('duration', unitText, scope);\n syncLeaveUnitSuffix('staffBalance', unitText, scope);\n return unitText;\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];\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 };\n Object.keys(hiddenFieldMap).forEach(function (dataKey) {\n var tgtId = hiddenFieldMap[dataKey];\n var attrVal = opt.getAttribute('data-' + dataKey);\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 }); // 从 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;\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 + ' ' + (fieldId.toLowerCase().indexOf('end') >= 0 ? '23:59' : '00:00');\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 };\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) : 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 // 确保审批变量(type/departNo/durationDay 等)齐全后审批模块能正确展示。\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 // 若值为纯日期(YYYY-MM-DD)则补全默认时间(start→00:00,end→23:59)\n // 这是一个兜底函数,在渲染、applyDefaultValues、applyFormData 之后运行,\n // 确保无论值从哪条路径设置,最终 datetimepicker 都显示完整的时间。\n\n\n function fixDateTimeDefaults(scope) {\n var root = scope && scope.querySelectorAll ? scope : container && container.querySelectorAll ? container : document;\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 || !/^\\d{4}-\\d{2}-\\d{2}$/.test(val)) return; // 只处理纯日期\n\n var fieldId = visibleInput.id || visibleInput.name || '';\n var defaultTime = fieldId.toLowerCase().indexOf('end') >= 0 ? '23:59' : '00:00';\n var formattedVal = val + ' ' + defaultTime;\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(_x26) {\n return _loadDataFromAttr.apply(this, arguments);\n } // ---------- MutationObserver 检测新元素 ----------\n\n\n function _loadDataFromAttr() {\n _loadDataFromAttr = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee20(el) {\n var loadEl, url, req, options, rawItems, json, vKeys;\n return regeneratorRuntime.wrap(function _callee20$(_context20) {\n while (1) {\n switch (_context20.prev = _context20.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 _context20.next = 5;\n break;\n }\n\n return _context20.abrupt(\"return\");\n\n case 5:\n _context20.prev = 5;\n req = buildDataLoadRequest(loadEl, url);\n\n if (!req.skip) {\n _context20.next = 9;\n break;\n }\n\n return _context20.abrupt(\"return\");\n\n case 9:\n options = getCached(req.cacheKey);\n rawItems = null;\n\n if (options) {\n _context20.next = 18;\n break;\n }\n\n _context20.next = 14;\n return httpDataLoad(req);\n\n case 14:\n json = _context20.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 _context20.next = 27;\n break;\n\n case 24:\n _context20.prev = 24;\n _context20.t0 = _context20[\"catch\"](5);\n console.warn('data-load 失败:', el.id, _context20.t0.message);\n\n case 27:\n case \"end\":\n return _context20.stop();\n }\n }\n }, _callee20, 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 _ref8 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee12(el) {\n var json, data;\n return regeneratorRuntime.wrap(function _callee12$(_context12) {\n while (1) {\n switch (_context12.prev = _context12.next) {\n case 0:\n _context12.prev = 0;\n _context12.next = 3;\n return _request.default.get(url);\n\n case 3:\n json = _context12.sent;\n data = resolveObject(json);\n applyDetailData(el, data);\n _context12.next = 11;\n break;\n\n case 8:\n _context12.prev = 8;\n _context12.t0 = _context12[\"catch\"](0);\n console.warn('data-load 详情失败:', el.id, _context12.t0.message);\n\n case 11:\n case \"end\":\n return _context12.stop();\n }\n }\n }, _callee12, null, [[0, 8]]);\n }));\n\n return function (_x27) {\n return _ref8.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 _ref9 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee13(e) {\n var json;\n return regeneratorRuntime.wrap(function _callee13$(_context13) {\n while (1) {\n switch (_context13.prev = _context13.next) {\n case 0:\n _context13.prev = 0;\n _context13.next = 3;\n return _request.default.get(url);\n\n case 3:\n json = _context13.sent;\n applyDetailData(e, resolveObject(json));\n _context13.next = 10;\n break;\n\n case 7:\n _context13.prev = 7;\n _context13.t0 = _context13[\"catch\"](0);\n console.warn('data-load 详情失败:', e.id, _context13.t0.message);\n\n case 10:\n case \"end\":\n return _context13.stop();\n }\n }\n }, _callee13, null, [[0, 7]]);\n }));\n\n return function (_x28) {\n return _ref9.apply(this, arguments);\n };\n })()(el);\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 }); // 也扫描所有 [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 }\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; // 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; }\\\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 ';\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?");
|
|
11985
|
+
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\nvar _index = _interopRequireDefault(__webpack_require__(/*! @lib/env/index */ \"./src/lib/env/index.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\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 });\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 _callee(url, value, field, formData) {\n var _options$remoteTimeou, json;\n\n return regeneratorRuntime.wrap(function _callee$(_context) {\n while (1) {\n switch (_context.prev = _context.next) {\n case 0:\n _context.prev = 0;\n _context.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 = _context.sent;\n return _context.abrupt(\"return\", parseResult(json));\n\n case 7:\n _context.prev = 7;\n _context.t0 = _context[\"catch\"](0);\n return _context.abrupt(\"return\", '网络请求失败,请重试');\n\n case 10:\n case \"end\":\n return _context.stop();\n }\n }\n }, _callee, 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 _callee14(el) {\n var ruleStr, inner, targetKey, formData, msg, scope;\n return regeneratorRuntime.wrap(function _callee14$(_context14) {\n while (1) {\n switch (_context14.prev = _context14.next) {\n case 0:\n ruleStr = el.getAttribute('data-rule');\n\n if (ruleStr) {\n _context14.next = 3;\n break;\n }\n\n return _context14.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 _context14.next = 7;\n break;\n }\n\n return _context14.abrupt(\"return\");\n\n case 7:\n formData = collectFormData(inner);\n _context14.next = 10;\n return V.validateField(readFieldValue(el, inner), ruleStr, formData, targetKey);\n\n case 10:\n msg = _context14.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 _context14.stop();\n }\n }\n }, _callee14);\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 _callee3(formData, event, element) {\n var form, allFields, hasError, resolveField, tasks, submitUrl, allData, submitScope, _durEl, balanceModeEl, balanceMode, leaveBalance, duration, auth, typeEl, holidayName, holidayTypeEl, minLeaveUnit, durationDayEl, durationDay, halfLabels, stHalfEl, etHalfEl, stHalf, etHalf, stVal, etVal, sd, ed, applierName, companySel, departSel, tokuiParams, approvalResult, method, resultJson, params, resultText;\n\n return regeneratorRuntime.wrap(function _callee3$(_context3) {\n while (1) {\n switch (_context3.prev = _context3.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 _context3.next = 4;\n break;\n }\n\n return _context3.abrupt(\"return\");\n\n case 4:\n if (!form._tvSubmitting) {\n _context3.next = 8;\n break;\n }\n\n console.warn('[Validator] formSubmit: 正在提交中,跳过');\n (0, _message.showMessage)('正在提交中,请稍候...', 'warning');\n return _context3.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 _callee2() {\n var ruleStr, inner, targetKey, fd, fieldVal, msg;\n return regeneratorRuntime.wrap(function _callee2$(_context2) {\n while (1) {\n switch (_context2.prev = _context2.next) {\n case 0:\n ruleStr = f.getAttribute('data-rule');\n\n if (ruleStr) {\n _context2.next = 3;\n break;\n }\n\n return _context2.abrupt(\"return\");\n\n case 3:\n inner = resolveField(f);\n targetKey = inner.id || inner.name || f.id || '';\n\n if (targetKey) {\n _context2.next = 7;\n break;\n }\n\n return _context2.abrupt(\"return\");\n\n case 7:\n fd = collectFormData(f);\n fieldVal = readFieldValue(f, inner);\n _context2.next = 11;\n return V.validateField(fieldVal, ruleStr, fd, targetKey);\n\n case 11:\n msg = _context2.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 _context2.stop();\n }\n }\n }, _callee2);\n }))());\n });\n _context3.next = 15;\n return Promise.all(tasks);\n\n case 15:\n if (!hasError) {\n _context3.next = 19;\n break;\n }\n\n form._tvSubmitting = false;\n safeFeed('[callout t:error tt:校验未通过 tx:请修正红色标记的字段]', form);\n return _context3.abrupt(\"return\");\n\n case 19:\n // 校验通过 → 提交\n submitUrl = form ? form.getAttribute('data-url') : null;\n\n if (submitUrl) {\n _context3.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 _context3.abrupt(\"return\");\n\n case 26:\n // ============ 请假专用:余额校验 ============\n allData = collectFormData(element);\n submitScope = resolveScope(form) || form; // 调试:打印 duration 字段的实际状态\n\n _durEl = queryById('duration', submitScope);\n balanceModeEl = queryById('_balanceMode', submitScope);\n balanceMode = balanceModeEl ? parseInt(balanceModeEl.value, 10) : -1;\n leaveBalance = parseFloat(allData.leaveBalance || '0');\n duration = parseFloat(allData.duration || '0'); // balanceMode === 2:该假期对当前用户不适用\n\n if (!(balanceMode === 2)) {\n _context3.next = 38;\n break;\n }\n\n form._tvSubmitting = false;\n (0, _message.showMessage)('该假期类型对您不适用', 'warning');\n safeFeed('[callout t:warning tt:提交失败 tx:该假期类型对您不适用,请选择其他假期类型]', form);\n return _context3.abrupt(\"return\");\n\n case 38:\n if (!(balanceMode === 0 && duration > leaveBalance)) {\n _context3.next = 43;\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 _context3.abrupt(\"return\");\n\n case 43:\n if (!(!duration || isNaN(duration))) {\n _context3.next = 48;\n break;\n }\n\n form._tvSubmitting = false;\n (0, _message.showMessage)('请假时长不能为空,请先选择起止时间', 'warning');\n safeFeed('[callout t:warning tt:提交失败 tx:请假时长不能为空,请先选择起止时间]', form);\n return _context3.abrupt(\"return\");\n\n case 48:\n // 组装请假提交数据(对齐后端 tbManagementApplyLeave 表结构)\n auth = (0, _request.getAuth)(); // 获取选中假期类型的名称\n\n typeEl = queryById('type', submitScope);\n holidayName = '';\n\n if (typeEl && typeEl.selectedIndex >= 0 && typeEl.options[typeEl.selectedIndex]) {\n holidayName = typeEl.options[typeEl.selectedIndex].textContent || typeEl.options[typeEl.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 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 } // 日期转时间戳(对齐源项目 leaveApply.vue 的处理)\n\n\n if (allData.startTime) {\n try {\n allData.startTime = new Date(allData.startTime).getTime();\n } catch (e) {}\n }\n\n if (allData.endTime) {\n try {\n allData.endTime = new Date(allData.endTime).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 // 如果表单配置了 data-approval-type,则调用审批引擎获取 uniqueId 并注入提交数据\n\n _context3.next = 95;\n return runApprovalFlow(form, allData);\n\n case 95:\n approvalResult = _context3.sent;\n\n if (!(approvalResult === false)) {\n _context3.next = 99;\n break;\n }\n\n form._tvSubmitting = false;\n return _context3.abrupt(\"return\");\n\n case 99:\n if (approvalResult && approvalResult.uniqueId) {\n allData.uniqueId = approvalResult.uniqueId;\n }\n\n safeFeed('[callout t:info tt:提交中 tx:正在提交数据...]', form);\n _context3.prev = 101;\n method = (form.getAttribute('data-method') || 'POST').toUpperCase();\n resultJson = null;\n\n if (!(method === 'GET')) {\n _context3.next = 112;\n break;\n }\n\n params = new URLSearchParams(allData).toString();\n if (params) submitUrl += (submitUrl.includes('?') ? '&' : '?') + params;\n _context3.next = 109;\n return _request.default.get(submitUrl);\n\n case 109:\n resultJson = _context3.sent;\n _context3.next = 121;\n break;\n\n case 112:\n if (!(method === 'DELETE')) {\n _context3.next = 118;\n break;\n }\n\n _context3.next = 115;\n return _request.default.delete(submitUrl, {\n data: allData\n });\n\n case 115:\n resultJson = _context3.sent;\n _context3.next = 121;\n break;\n\n case 118:\n _context3.next = 120;\n return _request.default.post(submitUrl, allData);\n\n case 120:\n resultJson = _context3.sent;\n\n case 121:\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 _context3.next = 131;\n break;\n\n case 128:\n _context3.prev = 128;\n _context3.t0 = _context3[\"catch\"](101);\n // 错误提示已由 http 拦截器统一处理,这里仅做表单内反馈\n safeFeed('[callout t:error tt:提交失败 tx:\"' + (_context3.t0.message || '').replace(/\"/g, '') + '\"]', form);\n\n case 131:\n _context3.prev = 131;\n form._tvSubmitting = false;\n return _context3.finish(131);\n\n case 134:\n case \"end\":\n return _context3.stop();\n }\n }\n }, _callee3, null, [[101, 128, 131, 134]]);\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\n window.TokUI.registerHandler('calcLeaveDuration', /*#__PURE__*/function () {\n var _ref4 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee5(data, event, element) {\n var scope;\n return regeneratorRuntime.wrap(function _callee5$(_context5) {\n while (1) {\n switch (_context5.prev = _context5.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 _context5.next = 4;\n break;\n }\n\n return _context5.abrupt(\"return\");\n\n case 4:\n // 防抖:延迟 400ms 执行,期间如有新触发则取消前一次(仅在当前表单作用域内)\n if (scope._calcDurationTimer) clearTimeout(scope._calcDurationTimer);\n return _context5.abrupt(\"return\", new Promise(function (resolve) {\n scope._calcDurationTimer = setTimeout( /*#__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 scope._calcDurationTimer = null;\n\n if (!scope._calcDurationLock) {\n _context4.next = 4;\n break;\n }\n\n resolve();\n return _context4.abrupt(\"return\");\n\n case 4:\n scope._calcDurationLock = true;\n _context4.prev = 5;\n _context4.next = 8;\n return _calcLeaveDurationImpl(element, scope);\n\n case 8:\n _context4.prev = 8;\n scope._calcDurationLock = false;\n return _context4.finish(8);\n\n case 11:\n resolve();\n\n case 12:\n case \"end\":\n return _context4.stop();\n }\n }\n }, _callee4, null, [[5,, 8, 11]]);\n })), 400);\n }));\n\n case 6:\n case \"end\":\n return _context5.stop();\n }\n }\n }, _callee5);\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 /**\r\n * 从表单字段读取值,兼容 select / input / hidden\r\n * - \"login*\" 前缀值从 window.__tokuiParams 读取\r\n * - 否则在当前作用域内按 id 精确查找(多表单隔离)\r\n */\n\n\n function _calcLeaveDurationImpl2() {\n _calcLeaveDurationImpl2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee7(element, explicitScope) {\n var calcScope, durationEl, durationField, bodyObj, bodyDeps, hasParams, i, attr, suffix, depParam, paramKey, depElId, htEl, startTimeStr, endTimeStr, startTimeMs, endTimeMs, endEl, holidayType, stHalfEl, etHalfEl, stHalf, etHalf, startDate, endDate1, endDate, diffHours, endEl2, calcUrl, json, unitText, fieldWrapper, appendEl, children, ci, span, ddEl, form;\n return regeneratorRuntime.wrap(function _callee7$(_context7) {\n while (1) {\n switch (_context7.prev = _context7.next) {\n case 0:\n // ★ 多表单隔离:优先使用调用方传入的作用域,否则从 element 向上查找\n calcScope = explicitScope || findNearestTokuiScope(element);\n\n if (calcScope) {\n _context7.next = 3;\n break;\n }\n\n return _context7.abrupt(\"return\");\n\n case 3:\n durationEl = queryById('duration', calcScope);\n\n if (durationEl) {\n _context7.next = 6;\n break;\n }\n\n return _context7.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 _context7.next = 9;\n break;\n }\n\n return _context7.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 _context7.next = 31;\n break;\n }\n\n i = 0;\n\n case 14:\n if (!(i < durationField.attributes.length)) {\n _context7.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 _context7.next = 18;\n break;\n }\n\n return _context7.abrupt(\"continue\", 28);\n\n case 18:\n suffix = attr.name.substring('data-calc-duration-body-'.length);\n\n if (suffix) {\n _context7.next = 21;\n break;\n }\n\n return _context7.abrupt(\"continue\", 28);\n\n case 21:\n if (!(suffix.indexOf('dep-') === 0)) {\n _context7.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 _context7.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 _context7.next = 14;\n break;\n\n case 31:\n if (hasParams) {\n _context7.next = 34;\n break;\n }\n\n console.warn('[Validator] calcLeaveDuration: duration 字段上未配置 data-calc-duration-body-dep-* 参数映射,跳过计算');\n return _context7.abrupt(\"return\");\n\n case 34:\n _context7.t0 = regeneratorRuntime.keys(bodyDeps);\n\n case 35:\n if ((_context7.t1 = _context7.t0()).done) {\n _context7.next = 43;\n break;\n }\n\n depParam = _context7.t1.value;\n\n if (bodyDeps.hasOwnProperty(depParam)) {\n _context7.next = 39;\n break;\n }\n\n return _context7.abrupt(\"continue\", 35);\n\n case 39:\n depElId = bodyDeps[depParam];\n bodyObj[depParam] = readFieldValueById(depElId, calcScope);\n _context7.next = 35;\n break;\n\n case 43:\n // ★ 兜底:确保 minLeaveUnit 始终存在(源项目始终发送 minLeaveUnit: this.holidayType)\n // 如果 DSL 未配置 data-calc-duration-body-dep-minLeaveUnit,从 _holidayType 隐藏字段读取\n if (bodyObj.minLeaveUnit === undefined || bodyObj.minLeaveUnit === '') {\n htEl = queryById('_holidayType', calcScope);\n bodyObj.minLeaveUnit = htEl ? htEl.value : '0';\n if (bodyObj.minLeaveUnit === '') bodyObj.minLeaveUnit = '0';\n } // 校验:起止时间必须都有值\n\n\n if (!(!bodyObj.startTime || !bodyObj.endTime)) {\n _context7.next = 47;\n break;\n }\n\n if (durationEl) durationEl.value = '';\n return _context7.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 _context7.next = 55;\n break;\n }\n\n return _context7.abrupt(\"return\");\n\n case 55:\n if (!(endTimeMs < startTimeMs)) {\n _context7.next = 60;\n break;\n }\n\n (0, _message.showMessage)('结束时间不能早于开始时间', 'warning');\n endEl = queryById(bodyDeps.endTime, calcScope);\n if (endEl) endEl.value = '';\n return _context7.abrupt(\"return\");\n\n case 60:\n bodyObj.startTime = startTimeMs;\n bodyObj.endTime = endTimeMs; // 读取 minLeaveUnit(0=按小时, 1=按半天, 2=按天)\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 _context7.next = 72;\n break;\n }\n\n diffHours = (bodyObj.endTime - bodyObj.startTime) / (1000 * 60 * 60);\n\n if (!(diffHours < 1)) {\n _context7.next = 72;\n break;\n }\n\n (0, _message.showMessage)('请假时长不得小于一小时', 'warning');\n endEl2 = queryById(bodyDeps.endTime, calcScope);\n if (endEl2) endEl2.value = '';\n return _context7.abrupt(\"return\");\n\n case 72:\n // ★ 始终构造 startTimeStr / endTimeStr(与源项目 getDuration 保持一致)\n // 源项目: startTimeStr = `${dayjs(startTime).format(\"YYYY-MM-DD\")} ${startTimeHalf == 1 ? \"上午\" : \"下午\"}`\n bodyObj.startTimeStr = buildHalfDayTimeStr(bodyObj.startTime, 'startTimeHalf', calcScope);\n bodyObj.endTimeStr = buildHalfDayTimeStr(bodyObj.endTime, 'endTimeHalf', calcScope); // 调用服务端时长计算 API\n\n calcUrl = cleanUrl(durationField.getAttribute('data-calc-duration-url') || '');\n\n if (calcUrl) {\n _context7.next = 78;\n break;\n }\n\n console.warn('[Validator] calcLeaveDuration: duration 字段上未配置 data-calc-duration-url');\n return _context7.abrupt(\"return\");\n\n case 78:\n _context7.prev = 78;\n _context7.next = 81;\n return _request.default.post(calcUrl, bodyObj);\n\n case 81:\n json = _context7.sent;\n\n if (!(json.code === 200 && json.data)) {\n _context7.next = 116;\n break;\n }\n\n if (durationEl) durationEl.value = String(json.data.duration || '');\n unitText = getLeaveUnitText(bodyObj.minLeaveUnit);\n fieldWrapper = durationEl.closest('.tokui-field') || durationEl.parentElement;\n\n if (!fieldWrapper) {\n _context7.next = 99;\n break;\n }\n\n appendEl = fieldWrapper.querySelector('.tokui-input-group__append, .tokui-append, .el-input-group__append');\n\n if (appendEl) {\n _context7.next = 98;\n break;\n }\n\n children = fieldWrapper.querySelectorAll('span, div');\n ci = children.length - 1;\n\n case 91:\n if (!(ci >= 0)) {\n _context7.next = 98;\n break;\n }\n\n if (!(children[ci].textContent && children[ci].textContent.trim() && !children[ci].querySelector('input'))) {\n _context7.next = 95;\n break;\n }\n\n appendEl = children[ci];\n return _context7.abrupt(\"break\", 98);\n\n case 95:\n ci--;\n _context7.next = 91;\n break;\n\n case 98:\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 99:\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 _context7.next = 112;\n break;\n }\n\n if (form) {\n _context7.next = 107;\n break;\n }\n\n return _context7.abrupt(\"return\");\n\n case 107:\n ddEl = document.createElement('input');\n ddEl.type = 'hidden';\n ddEl.id = '_durationDay';\n ddEl.name = '_durationDay';\n form.appendChild(ddEl);\n\n case 112:\n ddEl.value = String(json.data.durationDay || ''); // ★ 时长计算完毕后,刷新审批流程(durationDay 变量已就绪)\n\n if (form) {\n try {\n refreshApprovalFlow(form);\n } catch (e2) {}\n }\n\n _context7.next = 117;\n break;\n\n case 116:\n if (holidayType !== 1) {\n (0, _message.showMessage)(json.msg || json.message || '计算时长失败', 'warning');\n }\n\n case 117:\n _context7.next = 122;\n break;\n\n case 119:\n _context7.prev = 119;\n _context7.t2 = _context7[\"catch\"](78);\n console.warn('[Validator] calcLeaveDuration API error:', _context7.t2.message);\n\n case 122:\n case \"end\":\n return _context7.stop();\n }\n }\n }, _callee7, null, [[78, 119]]);\n }));\n return _calcLeaveDurationImpl2.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 var halfVal = readFieldValueById(halfElId, scope);\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 _ref6 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee6(data, event, element) {\n var form, allData, submitUrl;\n return regeneratorRuntime.wrap(function _callee6$(_context6) {\n while (1) {\n switch (_context6.prev = _context6.next) {\n case 0:\n form = resolveForm(element);\n\n if (form) {\n _context6.next = 3;\n break;\n }\n\n return _context6.abrupt(\"return\");\n\n case 3:\n if (!form._tvSubmitting) {\n _context6.next = 6;\n break;\n }\n\n (0, _message.showMessage)('正在处理中,请稍候...', 'warning');\n return _context6.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 _context6.next = 18;\n break;\n }\n\n form._tvSubmitting = false;\n (0, _message.showMessage)('暂存失败:未配置提交地址', 'error');\n return _context6.abrupt(\"return\");\n\n case 18:\n safeFeed('[callout t:info tt:暂存中 tx:正在保存草稿...]', form);\n _context6.prev = 19;\n _context6.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 _context6.next = 28;\n break;\n\n case 26:\n _context6.prev = 26;\n _context6.t0 = _context6[\"catch\"](19);\n\n case 28:\n _context6.prev = 28;\n form._tvSubmitting = false;\n return _context6.finish(28);\n\n case 31:\n case \"end\":\n return _context6.stop();\n }\n }\n }, _callee6, null, [[19, 26, 28, 31]]);\n }));\n\n return function (_x14, _x15, _x16) {\n return _ref6.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\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 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 break;\n }\n }\n }\n }\n\n target = target.parentElement;\n }\n } // 在容器上监听 change 事件(捕获阶段,确保优先处理)\n\n\n container.addEventListener('change', 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 }\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 的取值:\n // departmentNo = departNo(申请部门),为空时回退 companyNo(申请单位)\n\n if (!typeList.departmentNo) typeList.departmentNo = allData.departNo || allData.companyNo || allData.departmentNo || '';\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 } // ============ 阶段 1:initApprovalFlow — 表单加载时初始化 ============\n\n\n function initApprovalFlow(_x17) {\n return _initApprovalFlow.apply(this, arguments);\n } // ============ 阶段 2:refreshApprovalFlow — 字段变更时重新预览 ============\n\n\n function _initApprovalFlow() {\n _initApprovalFlow = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee15(form) {\n var state, _readyTries, auth, companyNo, processIsOpenUrl, openJson, openData, _approvalDebounceTimer, scheduleRefresh, _approvalScope, _approvalObs, mapKeys;\n\n return regeneratorRuntime.wrap(function _callee15$(_context15) {\n while (1) {\n switch (_context15.prev = _context15.next) {\n case 0:\n state = getApprovalState(form);\n\n if (!state.inited) {\n _context15.next = 3;\n break;\n }\n\n return _context15.abrupt(\"return\");\n\n case 3:\n if (state.approvalType) {\n _context15.next = 5;\n break;\n }\n\n return _context15.abrupt(\"return\");\n\n case 5:\n if (isFormReady(form)) {\n _context15.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 _context15.abrupt(\"return\");\n\n case 8:\n state.inited = true;\n auth = (0, _request.getAuth)();\n companyNo = auth.companyNo || '';\n _context15.prev = 11;\n processIsOpenUrl = state.approvalPrefix + '/v1/pc/process/processIsOpen';\n _context15.next = 15;\n return _request.default.post(processIsOpenUrl, {\n companyNo: companyNo,\n type: state.approvalType\n });\n\n case 15:\n openJson = _context15.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 _context15.next = 22;\n break;\n }\n\n return _context15.abrupt(\"return\");\n\n case 22:\n if (!(state.variableList.length === 0)) {\n _context15.next = 25;\n break;\n }\n\n _context15.next = 25;\n return refreshApprovalFlow(form);\n\n case 25:\n // ★ 实时监听:审批变量涉及的字段变更 → 重新调用 processPreview\n // - calcLeaveDuration 中也会在时长计算后触发 refreshApprovalFlow\n // - 这里通过 form 级 change/input 委托 + MutationObserver + 防抖确保不漏触发\n if (!form._approvalChangeBound) {\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 }); // MutationObserver:捕获 hidden input 等不触发 change 的 value 属性变更\n\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 } // ★ 关键修复:data-default-label 的初始 change 事件早于监听器注册,\n // 需要立即检查字段是否已有值,如有则直接触发 preview\n\n\n mapKeys = Object.keys(state.variableMap);\n refreshApprovalFlow(form);\n _context15.next = 34;\n break;\n\n case 30:\n _context15.prev = 30;\n _context15.t0 = _context15[\"catch\"](11);\n console.warn('[Approval] init error:', _context15.t0.message);\n safeFeed('[callout t:error tt:审批异常 tx:\"' + _context15.t0.message + '\"]', form);\n\n case 34:\n case \"end\":\n return _context15.stop();\n }\n }\n }, _callee15, null, [[11, 30]]);\n }));\n return _initApprovalFlow.apply(this, arguments);\n }\n\n function refreshApprovalFlow(_x18) {\n return _refreshApprovalFlow.apply(this, arguments);\n } // 清除审批可视化渲染\n\n\n function _refreshApprovalFlow() {\n _refreshApprovalFlow = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee16(form) {\n var state, typeList, typeScope, missingList, complete, fixedKeys, requiredApprovalVars, deptAlreadyMissing, auth, companyNo, previewUrl, previewBody, previewJson, previewData, feedbackHost, oldFeedbacks;\n return regeneratorRuntime.wrap(function _callee16$(_context16) {\n while (1) {\n switch (_context16.prev = _context16.next) {\n case 0:\n state = getApprovalState(form);\n\n if (!state._stopped) {\n _context16.next = 3;\n break;\n }\n\n return _context16.abrupt(\"return\");\n\n case 3:\n if (!(state.approvaStatus !== 0)) {\n _context16.next = 5;\n break;\n }\n\n return _context16.abrupt(\"return\");\n\n case 5:\n if (!state._rendering) {\n _context16.next = 8;\n break;\n }\n\n state._refreshPending = true;\n return _context16.abrupt(\"return\");\n\n case 8:\n state._rendering = true;\n _context16.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 _context16.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 _context16.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 _context16.next = 30;\n return _request.default.post(previewUrl, previewBody);\n\n case 30:\n previewJson = _context16.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 _context16.next = 46;\n break;\n\n case 43:\n _context16.prev = 43;\n _context16.t0 = _context16[\"catch\"](9);\n console.warn('[Approval] refresh error:', _context16.t0.message);\n\n case 46:\n _context16.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 _context16.finish(46);\n\n case 50:\n case \"end\":\n return _context16.stop();\n }\n }\n }, _callee16, 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(_x19) {\n return _saveApprovalFlowState.apply(this, arguments);\n } // ============ 审批流程可视化渲染 ============\n\n\n function _saveApprovalFlowState() {\n _saveApprovalFlowState = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee17(form) {\n var state, processSaveList, saveBody, saveJson, saveData;\n return regeneratorRuntime.wrap(function _callee17$(_context17) {\n while (1) {\n switch (_context17.prev = _context17.next) {\n case 0:\n state = getApprovalState(form);\n\n if (!(!state.approvalType || state.approvaStatus !== 0)) {\n _context17.next = 3;\n break;\n }\n\n return _context17.abrupt(\"return\", null);\n\n case 3:\n _context17.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 _context17.next = 8;\n return _request.default.post(state.approvalPrefix + '/v1/pc/process/processSave', saveBody);\n\n case 8:\n saveJson = _context17.sent;\n saveData = saveJson.data || {};\n return _context17.abrupt(\"return\", {\n uniqueId: saveData.uniqueId || ''\n });\n\n case 13:\n _context17.prev = 13;\n _context17.t0 = _context17[\"catch\"](3);\n console.warn('[Approval] save error:', _context17.t0.message);\n (0, _message.showMessage)('审批流程异常:' + _context17.t0.message, 'error');\n return _context17.abrupt(\"return\", false);\n\n case 18:\n case \"end\":\n return _context17.stop();\n }\n }\n }, _callee17, 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 // 使用组织架构选择器\n openOrgSelectorForNode(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 || '';\n\n if (approver.icon) {\n var img = document.createElement('img');\n img.src = approver.icon;\n img.className = 'tv-approver-avatar';\n img.alt = name;\n img.title = name;\n img.style.width = '28px';\n img.style.height = '28px';\n img.style.borderRadius = '50%';\n tag.appendChild(img);\n } else {\n var abbr = name.length >= 2 ? name.substring(name.length - 2) : name;\n tag.textContent = abbr;\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$/, _index.default.VUE_APP_EMTERPRISE);\n if (!enterpriseBase) enterpriseBase = state.approvalPrefix.replace(/\\/[^/]+$/, _index.default.VUE_APP_EMTERPRISE);\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(_x20, _x21) {\n return _loadDeptChildren.apply(this, arguments);\n } // ========== 加载组织架构(根节点) ==========\n\n\n function _loadDeptChildren() {\n _loadDeptChildren = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee8(deptId, deptItem) {\n var orgJson, orgData, contacts, orgInfoList;\n return regeneratorRuntime.wrap(function _callee8$(_context8) {\n while (1) {\n switch (_context8.prev = _context8.next) {\n case 0:\n if (!loadingDepts[deptId]) {\n _context8.next = 2;\n break;\n }\n\n return _context8.abrupt(\"return\");\n\n case 2:\n loadingDepts[deptId] = true;\n renderTree(); // 显示加载中状态\n\n _context8.prev = 4;\n _context8.next = 7;\n return _request.default.get(enterpriseBase + '/v1/organization/getOrgInfoV2?userNo=' + userNo + '&companyNo=' + companyNo + '&deptId=' + deptId);\n\n case 7:\n orgJson = _context8.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 _context8.next = 24;\n break;\n\n case 19:\n _context8.prev = 19;\n _context8.t0 = _context8[\"catch\"](4);\n console.warn('[Validator] 加载子部门失败:', _context8.t0.message);\n delete loadingDepts[deptId];\n renderTree();\n\n case 24:\n case \"end\":\n return _context8.stop();\n }\n }\n }, _callee8, 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 _callee9() {\n var companyJson, companies, mainCompany, orgJson, orgData, contacts, orgInfoList;\n return regeneratorRuntime.wrap(function _callee9$(_context9) {\n while (1) {\n switch (_context9.prev = _context9.next) {\n case 0:\n _context9.prev = 0;\n _context9.next = 3;\n return _request.default.get(enterpriseBase + '/v1/organization/getCompanyInfo?userNo=' + userNo + '&companyNo=' + companyNo);\n\n case 3:\n companyJson = _context9.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 _context9.next = 8;\n return _request.default.get(enterpriseBase + '/v1/organization/getOrgInfoV2?userNo=' + userNo + '&companyNo=' + companyNo + '&deptId=1');\n\n case 8:\n orgJson = _context9.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 _context9.next = 25;\n break;\n\n case 21:\n _context9.prev = 21;\n _context9.t0 = _context9[\"catch\"](0);\n console.warn('[Validator] 加载组织架构失败:', _context9.t0.message);\n treeContainer.innerHTML = '<div class=\"tv-org-error\">加载组织架构失败: ' + _context9.t0.message + '</div>';\n\n case 25:\n case \"end\":\n return _context9.stop();\n }\n }\n }, _callee9, 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(_x22) {\n return _doSearch.apply(this, arguments);\n } // 初始化\n\n\n function _doSearch() {\n _doSearch = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee10(keyword) {\n var searchJson, results, selectedIds;\n return regeneratorRuntime.wrap(function _callee10$(_context10) {\n while (1) {\n switch (_context10.prev = _context10.next) {\n case 0:\n if (keyword) {\n _context10.next = 2;\n break;\n }\n\n return _context10.abrupt(\"return\");\n\n case 2:\n treeContainer.innerHTML = '<div class=\"tv-org-loading\">搜索中...</div>';\n _context10.prev = 3;\n _context10.next = 6;\n return _request.default.get(enterpriseBase + '/v2/api/contractBasicInfo/searchKeyWord?keyWord=' + encodeURIComponent(keyword) + '&comNo=' + companyNo);\n\n case 6:\n searchJson = _context10.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 _context10.next = 14;\n break;\n }\n\n treeContainer.innerHTML = '<div class=\"tv-org-empty\">无匹配结果</div>';\n return _context10.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 _context10.next = 21;\n break;\n\n case 17:\n _context10.prev = 17;\n _context10.t0 = _context10[\"catch\"](3);\n console.warn('[Validator] 搜索失败:', _context10.t0.message);\n treeContainer.innerHTML = '<div class=\"tv-org-error\">搜索失败: ' + _context10.t0.message + '</div>';\n\n case 21:\n case \"end\":\n return _context10.stop();\n }\n }\n }, _callee10, 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 }\n\n function openOrgSelectorForNode(state, nodeIndex, node) {\n // 抄送人组织架构选择(简化版:从 optionalUserList 中选择)\n // 实际项目中可能需要完整的组织架构树,这里用 optionalUserList 下拉替代\n if (node.optionalUserList && node.optionalUserList.length > 0) {\n showApproverSelector(state, nodeIndex, node);\n }\n } // ============ 提交时调用的审批入口(替代原 runApprovalFlow) ============\n\n\n function runApprovalFlow(_x23, _x24) {\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 _callee18(form, allData) {\n var state, _retry, _wait;\n\n return regeneratorRuntime.wrap(function _callee18$(_context18) {\n while (1) {\n switch (_context18.prev = _context18.next) {\n case 0:\n state = getApprovalState(form);\n\n if (state.approvalType) {\n _context18.next = 3;\n break;\n }\n\n return _context18.abrupt(\"return\", null);\n\n case 3:\n if (state.inited) {\n _context18.next = 6;\n break;\n }\n\n _context18.next = 6;\n return initApprovalFlow(form);\n\n case 6:\n if (!(state.approvaStatus !== 0)) {\n _context18.next = 8;\n break;\n }\n\n return _context18.abrupt(\"return\", {\n uniqueId: '',\n skipped: true\n });\n\n case 8:\n if (!(!state.processList || state.processList.length === 0)) {\n _context18.next = 11;\n break;\n }\n\n _context18.next = 11;\n return refreshApprovalFlow(form);\n\n case 11:\n if (!(!state.processList || state.processList.length === 0)) {\n _context18.next = 31;\n break;\n }\n\n _retry = 0;\n\n case 13:\n if (!(_retry < 3)) {\n _context18.next = 31;\n break;\n }\n\n // 等待 _rendering 锁释放\n _wait = 0;\n\n case 15:\n if (!(state._rendering && _wait < 2000)) {\n _context18.next = 21;\n break;\n }\n\n _context18.next = 18;\n return new Promise(function (r) {\n setTimeout(r, 100);\n });\n\n case 18:\n _wait += 100;\n _context18.next = 15;\n break;\n\n case 21:\n // 强制重置渲染锁并重试\n state._rendering = false;\n _context18.next = 24;\n return refreshApprovalFlow(form);\n\n case 24:\n if (!(state.processList && state.processList.length > 0)) {\n _context18.next = 26;\n break;\n }\n\n return _context18.abrupt(\"break\", 31);\n\n case 26:\n _context18.next = 28;\n return new Promise(function (r) {\n setTimeout(r, 500);\n });\n\n case 28:\n _retry++;\n _context18.next = 13;\n break;\n\n case 31:\n if (!(!state.processList || state.processList.length === 0)) {\n _context18.next = 35;\n break;\n }\n\n console.warn('[Approval] runApprovalFlow: processList 为空,中止提交');\n safeFeed('[callout t:error tt:审批流程未就绪 tx:\"审批流程数据未就绪,请检查请假类型和时长后重试\"]', form);\n return _context18.abrupt(\"return\", false);\n\n case 35:\n _context18.next = 37;\n return saveApprovalFlowState(form);\n\n case 37:\n return _context18.abrupt(\"return\", _context18.sent);\n\n case 38:\n case \"end\":\n return _context18.stop();\n }\n }\n }, _callee18);\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(_x25) {\n return _httpDataLoad.apply(this, arguments);\n } // ---------- 替换 $字段名$ ----------\n\n\n function _httpDataLoad() {\n _httpDataLoad = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee19(req) {\n var method, body;\n return regeneratorRuntime.wrap(function _callee19$(_context19) {\n while (1) {\n switch (_context19.prev = _context19.next) {\n case 0:\n method = req.opts && req.opts.method || 'GET';\n\n if (!(method === 'GET')) {\n _context19.next = 3;\n break;\n }\n\n return _context19.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 _context19.next = 7;\n break;\n }\n\n return _context19.abrupt(\"return\", _request.default.delete(req.url, {\n data: body\n }));\n\n case 7:\n if (!(method === 'PUT')) {\n _context19.next = 9;\n break;\n }\n\n return _context19.abrupt(\"return\", _request.default.put(req.url, body));\n\n case 9:\n return _context19.abrupt(\"return\", _request.default.post(req.url, body));\n\n case 10:\n case \"end\":\n return _context19.stop();\n }\n }\n }, _callee19);\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 _callee11() {\n var req, cached, json, items;\n return regeneratorRuntime.wrap(function _callee11$(_context11) {\n while (1) {\n switch (_context11.prev = _context11.next) {\n case 0:\n _context11.prev = 0;\n req = buildDataLoadRequest(ctn, url);\n\n if (!req.skip) {\n _context11.next = 4;\n break;\n }\n\n return _context11.abrupt(\"return\");\n\n case 4:\n cached = getCached(req.cacheKey);\n\n if (!cached) {\n _context11.next = 8;\n break;\n }\n\n applyListTemplate(container, tplEl, template, cached);\n return _context11.abrupt(\"return\");\n\n case 8:\n _context11.next = 10;\n return httpDataLoad(req);\n\n case 10:\n json = _context11.sent;\n items = resolveList(json);\n setCached(req.cacheKey, items);\n applyListTemplate(container, tplEl, template, items);\n _context11.next = 19;\n break;\n\n case 16:\n _context11.prev = 16;\n _context11.t0 = _context11[\"catch\"](0);\n console.warn('data-load-list 失败:', container.id, _context11.t0.message);\n\n case 19:\n case \"end\":\n return _context11.stop();\n }\n }\n }, _callee11, 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,结束 11:59)\n // - 其他值 → 原样转字符串\n\n\n function formatDateValue(rawVal, tgtEl, fieldKey) {\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] + ' ' + dtMatch[2] + ':' + dtMatch[3]; // 纯日期字符串: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) : 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);\n return yyyy + '-' + mm + '-' + dd + ' ' + timePart;\n }\n\n return yyyy + '-' + mm + '-' + dd;\n } // 按小时模式 datetimepicker 的默认补全时间:\n // 开始时间 → 00:00,结束时间 → 23:59(对应默认范围 \"0.00-23.59\")\n\n\n function getDefaultTimeForField(fieldKey, tgtEl) {\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 if (k.indexOf('end') >= 0) return '23:59';\n return '00:00';\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 fieldEl = queryById(fieldId, scope || container);\n if (!fieldEl) return;\n var fieldWrapper = fieldEl.closest('.tokui-field') || fieldEl.parentElement;\n if (!fieldWrapper) return;\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 }\n }\n\n function refreshHolidayUnitSuffixFromHiddenField(scope) {\n var holidayTypeEl = queryById('_holidayType', scope || container);\n\n if (!holidayTypeEl || holidayTypeEl.value === '' || holidayTypeEl.value === undefined || holidayTypeEl.value === null) {\n return null;\n }\n\n var unitText = getLeaveUnitText(holidayTypeEl.value);\n if (!unitText) return null;\n syncLeaveUnitSuffix('duration', unitText, scope);\n syncLeaveUnitSuffix('staffBalance', unitText, scope);\n return unitText;\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];\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 };\n Object.keys(hiddenFieldMap).forEach(function (dataKey) {\n var tgtId = hiddenFieldMap[dataKey];\n var attrVal = opt.getAttribute('data-' + dataKey);\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 }); // 从 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;\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 + ' ' + (fieldId.toLowerCase().indexOf('end') >= 0 ? '23:59' : '00:00');\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 };\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) : 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 // 确保审批变量(type/departNo/durationDay 等)齐全后审批模块能正确展示。\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 // 若值为纯日期(YYYY-MM-DD)则补全默认时间(start→00:00,end→23:59)\n // 这是一个兜底函数,在渲染、applyDefaultValues、applyFormData 之后运行,\n // 确保无论值从哪条路径设置,最终 datetimepicker 都显示完整的时间。\n\n\n function fixDateTimeDefaults(scope) {\n var root = scope && scope.querySelectorAll ? scope : container && container.querySelectorAll ? container : document;\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 || !/^\\d{4}-\\d{2}-\\d{2}$/.test(val)) return; // 只处理纯日期\n\n var fieldId = visibleInput.id || visibleInput.name || '';\n var defaultTime = fieldId.toLowerCase().indexOf('end') >= 0 ? '23:59' : '00:00';\n var formattedVal = val + ' ' + defaultTime;\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(_x26) {\n return _loadDataFromAttr.apply(this, arguments);\n } // ---------- MutationObserver 检测新元素 ----------\n\n\n function _loadDataFromAttr() {\n _loadDataFromAttr = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee20(el) {\n var loadEl, url, req, options, rawItems, json, vKeys;\n return regeneratorRuntime.wrap(function _callee20$(_context20) {\n while (1) {\n switch (_context20.prev = _context20.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 _context20.next = 5;\n break;\n }\n\n return _context20.abrupt(\"return\");\n\n case 5:\n _context20.prev = 5;\n req = buildDataLoadRequest(loadEl, url);\n\n if (!req.skip) {\n _context20.next = 9;\n break;\n }\n\n return _context20.abrupt(\"return\");\n\n case 9:\n options = getCached(req.cacheKey);\n rawItems = null;\n\n if (options) {\n _context20.next = 18;\n break;\n }\n\n _context20.next = 14;\n return httpDataLoad(req);\n\n case 14:\n json = _context20.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 _context20.next = 27;\n break;\n\n case 24:\n _context20.prev = 24;\n _context20.t0 = _context20[\"catch\"](5);\n console.warn('data-load 失败:', el.id, _context20.t0.message);\n\n case 27:\n case \"end\":\n return _context20.stop();\n }\n }\n }, _callee20, 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 _ref8 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee12(el) {\n var json, data;\n return regeneratorRuntime.wrap(function _callee12$(_context12) {\n while (1) {\n switch (_context12.prev = _context12.next) {\n case 0:\n _context12.prev = 0;\n _context12.next = 3;\n return _request.default.get(url);\n\n case 3:\n json = _context12.sent;\n data = resolveObject(json);\n applyDetailData(el, data);\n _context12.next = 11;\n break;\n\n case 8:\n _context12.prev = 8;\n _context12.t0 = _context12[\"catch\"](0);\n console.warn('data-load 详情失败:', el.id, _context12.t0.message);\n\n case 11:\n case \"end\":\n return _context12.stop();\n }\n }\n }, _callee12, null, [[0, 8]]);\n }));\n\n return function (_x27) {\n return _ref8.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 _ref9 = (0, _asyncToGenerator2.default)( /*#__PURE__*/regeneratorRuntime.mark(function _callee13(e) {\n var json;\n return regeneratorRuntime.wrap(function _callee13$(_context13) {\n while (1) {\n switch (_context13.prev = _context13.next) {\n case 0:\n _context13.prev = 0;\n _context13.next = 3;\n return _request.default.get(url);\n\n case 3:\n json = _context13.sent;\n applyDetailData(e, resolveObject(json));\n _context13.next = 10;\n break;\n\n case 7:\n _context13.prev = 7;\n _context13.t0 = _context13[\"catch\"](0);\n console.warn('data-load 详情失败:', e.id, _context13.t0.message);\n\n case 10:\n case \"end\":\n return _context13.stop();\n }\n }\n }, _callee13, null, [[0, 7]]);\n }));\n\n return function (_x28) {\n return _ref9.apply(this, arguments);\n };\n })()(el);\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 }); // 也扫描所有 [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 }\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; // 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; }\\\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 ';\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?");
|
|
11986
11986
|
|
|
11987
11987
|
/***/ }),
|
|
11988
11988
|
|
|
@@ -12102,7 +12102,7 @@ eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n
|
|
|
12102
12102
|
/***/ (function(module, exports, __webpack_require__) {
|
|
12103
12103
|
|
|
12104
12104
|
"use strict";
|
|
12105
|
-
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 _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-
|
|
12105
|
+
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 _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});\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}\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;\n config.headers['Content-Type'] = 'application/json'; // console.log('排序后key:', sortedKeys)\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?");
|
|
12106
12106
|
|
|
12107
12107
|
/***/ }),
|
|
12108
12108
|
|