From d7180a53349dad2d30c9a65f1fe1e0f3f98c4308 Mon Sep 17 00:00:00 2001 From: "zhonghua.li" Date: Thu, 21 May 2026 09:08:33 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=BC=E5=87=BA=E6=8B=9B=E5=95=86=E5=82=AC?= =?UTF-8?q?=E5=8A=9E=E7=9A=84=E7=94=A8=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/lb-department-user.js | 12 + src/api/lb-order-row.js | 1 + .../lb-business/assessment-apply/index.vue | 100 +++++- .../lb-business/department-user/index.vue | 249 +++++++++++++- .../lb-business/purchase-apply/index.vue | 306 ++++++++++++++++-- 5 files changed, 622 insertions(+), 46 deletions(-) diff --git a/src/api/lb-department-user.js b/src/api/lb-department-user.js index 4b2e684..b2b2af6 100644 --- a/src/api/lb-department-user.js +++ b/src/api/lb-department-user.js @@ -62,3 +62,15 @@ export function getLbDepartmentUserTree(params) { params }) } + +/** 按租户ID与用户ID列表导出 Excel */ +export function exportLbDepartmentUsers(tenantId, userIds) { + return request({ + url: '/lbDepartmentUser/exportUsers', + method: 'post', + params: { tenantId }, + data: userIds, + responseType: 'blob', + timeout: 120000 + }) +} diff --git a/src/api/lb-order-row.js b/src/api/lb-order-row.js index 99fafd2..d3c7d6a 100644 --- a/src/api/lb-order-row.js +++ b/src/api/lb-order-row.js @@ -36,6 +36,7 @@ export function syncLbOrderRowFromThird(data) { return request({ url: '/lbOrderRow/sync-order-from-third', method: 'post', + timeout: 2 * 60 * 1000, data }) } diff --git a/src/views/lb-business/assessment-apply/index.vue b/src/views/lb-business/assessment-apply/index.vue index 266190b..55122dd 100644 --- a/src/views/lb-business/assessment-apply/index.vue +++ b/src/views/lb-business/assessment-apply/index.vue @@ -27,7 +27,7 @@ - + @@ -301,7 +301,11 @@ export default { { label: '李海民老师', value: '李海民老师' }, { label: '闫丽娜老师', value: '闫丽娜老师' }, { label: '张凌峰老师', value: '张凌峰老师' }, - { label: '张宝凤老师', value: '张宝凤老师' } + { label: '张宝凤老师', value: '张宝凤老师' }, + { label: '付韾蕊老师', value: '付韾蕊老师' }, + { label: '陈晓琴老师', value: '陈晓琴老师' }, + { label: '李军老师', value: '李军老师' }, + { label: '刘伟翔老师', value: '刘伟翔老师' } ], assessmentTeacherOptions: [ { label: '张健老师', value: '张健老师' }, @@ -774,6 +778,98 @@ export default { } .table-container { + $table-border: #000; + $header-bg: #ffd966; + + ::v-deep .assessment-apply-table { + border: 1px solid $table-border; + color: #000; + font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif; + + &::before, + &::after { + background-color: $table-border; + } + + th, + td { + border-color: $table-border !important; + vertical-align: middle; + } + + thead th { + background-color: $header-bg; + color: #000 !important; + font-weight: 700 !important; + padding: 10px 0; + } + + thead th .cell { + color: #000 !important; + font-weight: 700 !important; + white-space: nowrap; + text-align: center; + line-height: 1.4; + } + + tbody td { + background-color: #fff; + color: #000; + padding: 8px 0; + } + + tbody td .cell { + color: #000; + text-align: center; + line-height: 1.4; + } + + tbody tr:hover > td { + background-color: #fff; + } + } + + ::v-deep .assessment-apply-table .el-table__fixed-right, + ::v-deep .assessment-apply-table .el-table__fixed-header-wrapper { + thead th { + background-color: $header-bg; + border-color: $table-border !important; + color: #000 !important; + font-weight: 700 !important; + padding: 10px 0; + } + + thead th .cell { + color: #000 !important; + font-weight: 700 !important; + white-space: nowrap; + text-align: center; + line-height: 1.4; + } + + tbody td { + background-color: #fff; + border-color: $table-border !important; + color: #000; + padding: 8px 0; + } + + tbody td .cell { + color: #000; + text-align: center; + line-height: 1.4; + } + + tbody tr:hover > td { + background-color: #fff; + } + } + + ::v-deep .assessment-apply-table .el-table__fixed-right-patch { + background-color: $header-bg; + border-color: $table-border !important; + } + .el-pagination { margin-top: 20px; text-align: right; diff --git a/src/views/lb-business/department-user/index.vue b/src/views/lb-business/department-user/index.vue index da4c843..dd82630 100644 --- a/src/views/lb-business/department-user/index.vue +++ b/src/views/lb-business/department-user/index.vue @@ -95,6 +95,48 @@ + + + + + + + + + + + + + + + + + 查询 + + 清空 + + + +
刷新 + + 导出 + + + 取消勾选 + + + 已选 {{ urgeTreeCheckedUserIds.length }} 人 +
@@ -128,13 +189,24 @@ :props="urgeTreeProps" :filter-node-method="filterUrgeTreeNode" node-key="userId" + show-checkbox + check-strictly highlight-current + @check="handleUrgeTreeCheck" > - + {{ data.name || '-' }} 用户ID:{{ data.userId || '-' }} 电话:{{ data.phone || '-' }} 加入日期:{{ data.joinDate || '-' }} + 最后成交日期:{{ data.lastTradeDate || '-' }} @@ -269,6 +341,7 @@ import { addLbDepartmentUser, bindLbDepartmentUserParent, deleteLbDepartmentUser, + exportLbDepartmentUsers, getLbDepartmentUserList, getLbDepartmentUserTree, syncLbDepartmentUserFromDailyUserTrade, @@ -277,6 +350,13 @@ import { } from '@/api/lb-department-user' import { getBusinessHeaders } from '@/utils/business-headers' +function getDefaultUrgeTreeQueryParams() { + return { + isActive: 1, + lastTradeDate: '2026-04-26' + } +} + export default { name: 'LbDepartmentUser', data() { @@ -287,9 +367,13 @@ export default { syncLoading: false, syncParentChainLoading: false, urgeTreeLoading: false, + urgeTreeExportLoading: false, urgeTreeData: [], urgeTreeTotal: 0, urgeTreeFilterText: '', + urgeTreeCheckedUserIds: [], + urgeTreeSyncingChecks: false, + urgeTreeQueryParams: getDefaultUrgeTreeQueryParams(), urgeTreeProps: { children: 'children', label: 'name' @@ -480,6 +564,28 @@ export default { if (!tab || tab.name !== 'urge') return this.fetchUrgeTree() }, + buildUrgeTreeParams() { + const tenantId = this.getTenantIdFromBusinessHeaders() + const params = { tenantId } + const { isActive, joinDate, lastTradeDate } = this.urgeTreeQueryParams + if (isActive === 0 || isActive === 1) { + params.is_active = isActive + } + if (joinDate) { + params.join_date = joinDate + } + if (lastTradeDate) { + params.last_trade_date = lastTradeDate + } + return params + }, + handleUrgeTreeQuery() { + this.fetchUrgeTree() + }, + resetUrgeTreeQuery() { + this.urgeTreeQueryParams = getDefaultUrgeTreeQueryParams() + this.fetchUrgeTree() + }, fetchUrgeTree() { const tenantId = this.getTenantIdFromBusinessHeaders() if (!tenantId) { @@ -487,7 +593,7 @@ export default { return } this.urgeTreeLoading = true - getLbDepartmentUserTree({ tenantId }) + getLbDepartmentUserTree(this.buildUrgeTreeParams()) .then((res) => { const hasData = res && Array.isArray(res.data) const isSuccess = res && (res.success === true || res.code === 200 || res.code === 20000 || hasData) @@ -498,11 +604,7 @@ export default { this.urgeTreeData = hasData ? res.data : [] this.urgeTreeTotal = typeof res.total === 'number' ? res.total : this.urgeTreeData.length this.urgeTreeFilterText = '' - this.$nextTick(() => { - if (this.$refs.urgeTree) { - this.$refs.urgeTree.filter('') - } - }) + this.applyUrgeTreeOverdueChecks() if (res && res.message) { this.$message.success(res.message) } @@ -518,9 +620,117 @@ export default { if (!value) return true const keyword = String(value).trim().toLowerCase() if (!keyword) return true - const fields = [data.name, data.userId, data.phone, data.joinDate] + const fields = [data.name, data.userId, data.phone, data.joinDate, data.lastTradeDate] return fields.some((field) => field != null && String(field).toLowerCase().includes(keyword)) }, + isUrgeTreeLeafNode(data) { + return !data.children || !data.children.length + }, + isJoinDateOverOneMonth(joinDate) { + if (!joinDate) return false + const join = new Date(String(joinDate).replace(/T.*$/, '')) + if (Number.isNaN(join.getTime())) return false + const oneMonthAgo = new Date() + oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1) + oneMonthAgo.setHours(0, 0, 0, 0) + join.setHours(0, 0, 0, 0) + return join < oneMonthAgo + }, + isUrgeTreeLeafOverdue(data) { + return this.isUrgeTreeLeafNode(data) && this.isJoinDateOverOneMonth(data.joinDate) + }, + collectUrgeTreeOverdueLeafUserIds(nodes, result = []) { + if (!nodes || !nodes.length) return result + nodes.forEach((node) => { + if (this.isUrgeTreeLeafOverdue(node) && node.userId) { + result.push(node.userId) + } + if (node.children && node.children.length) { + this.collectUrgeTreeOverdueLeafUserIds(node.children, result) + } + }) + return result + }, + normalizeUrgeTreeUserId(userId) { + return userId == null || userId === '' ? '' : String(userId) + }, + isUrgeTreeKeyInCheckedList(userId, checkedKeys) { + const key = this.normalizeUrgeTreeUserId(userId) + if (!key) return false + return (checkedKeys || []).some((k) => this.normalizeUrgeTreeUserId(k) === key) + }, + syncUrgeTreeCheckedFromTree() { + const tree = this.$refs.urgeTree + if (!tree) return + this.urgeTreeCheckedUserIds = tree.getCheckedKeys().map((k) => this.normalizeUrgeTreeUserId(k)) + }, + applyUrgeTreeOverdueChecks() { + const overdueIds = this.collectUrgeTreeOverdueLeafUserIds(this.urgeTreeData) + this.urgeTreeSyncingChecks = true + this.$nextTick(() => { + const tree = this.$refs.urgeTree + if (tree) { + tree.setCheckedKeys([...new Set(overdueIds)]) + tree.filter(this.urgeTreeFilterText || '') + this.syncUrgeTreeCheckedFromTree() + } + this.$nextTick(() => { + this.urgeTreeSyncingChecks = false + }) + }) + }, + isUrgeTreeNodeChecked(userId) { + return this.isUrgeTreeKeyInCheckedList(userId, this.urgeTreeCheckedUserIds) + }, + handleUrgeTreeCheck() { + if (this.urgeTreeSyncingChecks) return + this.syncUrgeTreeCheckedFromTree() + }, + handleUrgeTreeUncheckAll() { + const tree = this.$refs.urgeTree + if (!tree) return + this.urgeTreeSyncingChecks = true + tree.setCheckedKeys([]) + this.urgeTreeCheckedUserIds = [] + this.$nextTick(() => { + this.urgeTreeSyncingChecks = false + }) + }, + handleUrgeTreeExport() { + const tenantId = this.getTenantIdFromBusinessHeaders() + if (!tenantId) { + this.$message.error('未获取到租户ID,请先登录或配置业务请求头中的租户') + return + } + this.syncUrgeTreeCheckedFromTree() + const userIds = [...new Set(this.urgeTreeCheckedUserIds.filter((id) => id))] + if (!userIds.length) { + this.$message.warning('请先勾选要导出的用户') + return + } + this.urgeTreeExportLoading = true + exportLbDepartmentUsers(tenantId, userIds) + .then((blob) => { + const safeBlob = blob instanceof Blob ? blob : new Blob([blob], { + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + }) + const url = window.URL.createObjectURL(safeBlob) + const link = document.createElement('a') + link.href = url + link.download = `lbDepartmentUser_export_${tenantId}_${Date.now()}.xlsx` + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + window.URL.revokeObjectURL(url) + this.$message.success('导出成功') + }) + .catch(() => { + this.$message.error('导出失败') + }) + .finally(() => { + this.urgeTreeExportLoading = false + }) + }, handleEdit(row) { this.dialogTitle = '编辑部门人员' this.isEdit = true @@ -774,6 +984,15 @@ export default { .urge-tree-panel { .urge-tree-toolbar { margin-bottom: 12px; + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 12px; + } + + .urge-tree-selected-tip { + color: #606266; + font-size: 14px; } } @@ -867,6 +1086,18 @@ export default { font-size: 13px; } + .urge-tree-node--alert, + .urge-tree-node--checked { + .urge-tree-node-name, + .urge-tree-node-meta { + color: #f56c6c; + } + + .urge-tree-node-name { + font-weight: 600; + } + } + .selected-tip { float: left; color: #606266; diff --git a/src/views/lb-business/purchase-apply/index.vue b/src/views/lb-business/purchase-apply/index.vue index 8f0cdf1..8c2593e 100644 --- a/src/views/lb-business/purchase-apply/index.vue +++ b/src/views/lb-business/purchase-apply/index.vue @@ -70,30 +70,50 @@
- - - - - - - - - - - - - - - - - +
+ + + + + + + + + + + + + + + + + + +
日期和人数序号昵称手机号特权开通日期推荐人手机号领导人开通日期操作
+
暂无数据
+
- - - - - - - - - - + +
+ + + + + + + + + + + + + + + + + +
日期和人数序号昵称手机号特权开通日期推荐人手机号领导人开通日期
+
暂无数据
+
@@ -464,6 +520,14 @@ export default { } } }, + computed: { + groupedList() { + return this.buildGroupedList(this.list) + }, + privilegeGroupedList() { + return this.buildGroupedList(this.privilegeList) + } + }, created() { this.fetchList() }, @@ -475,6 +539,75 @@ export default { const day = String(date.getDate()).padStart(2, '0') return `${year}-${month}-${day}` }, + buildGroupedList(records) { + if (!Array.isArray(records) || !records.length) return [] + const sorted = [...records].sort((a, b) => { + const da = this.normalizeApplyDateValue(a.applyDate) + const db = this.normalizeApplyDateValue(b.applyDate) + if (da !== db) return db.localeCompare(da) + const idA = a.id != null ? String(a.id) : '' + const idB = b.id != null ? String(b.id) : '' + return idA.localeCompare(idB) + }) + const map = new Map() + sorted.forEach((row) => { + const dateKey = this.normalizeApplyDateValue(row.applyDate) || '__no_date__' + if (!map.has(dateKey)) { + map.set(dateKey, []) + } + map.get(dateKey).push(row) + }) + return Array.from(map.entries()).map(([dateKey, rows]) => ({ + key: dateKey, + label: this.formatDateGroupLabel(dateKey, rows.length), + rows + })) + }, + formatDateGroupLabel(applyDate, count) { + const displayDate = this.subtractOneDay(applyDate) + const md = this.formatShortMd(displayDate, false) + const n = typeof count === 'number' && count > 0 ? count : 0 + return md ? `${md}招商${n}人` : `招商${n}人` + }, + subtractOneDay(dateValue) { + const normalized = this.normalizeApplyDateValue(dateValue) + if (!normalized) return '' + const date = new Date(`${normalized}T12:00:00`) + if (Number.isNaN(date.getTime())) return normalized + date.setDate(date.getDate() - 1) + const year = date.getFullYear() + const month = String(date.getMonth() + 1).padStart(2, '0') + const day = String(date.getDate()).padStart(2, '0') + return `${year}-${month}-${day}` + }, + formatShortMd(dateValue, padMonthDay = true) { + const normalized = this.normalizeApplyDateValue(dateValue) + if (!normalized) return '' + const parts = normalized.split('-') + if (parts.length < 3) return normalized + const month = Number(parts[1]) + const day = Number(parts[2]) + if (Number.isNaN(month) || Number.isNaN(day)) return normalized + if (padMonthDay) { + return `${String(month).padStart(2, '0')}.${String(day).padStart(2, '0')}` + } + return `${month}.${day}` + }, + formatTryDate(tryDate) { + const text = tryDate != null ? String(tryDate).trim() : '' + return text || '-' + }, + formatOpenDate(dateValue) { + const normalized = this.normalizeApplyDateValue(dateValue) + if (!normalized) return '-' + const parts = normalized.split('-') + if (parts.length < 3) return normalized + const year = parts[0] + const month = Number(parts[1]) + const day = Number(parts[2]) + if (Number.isNaN(month) || Number.isNaN(day)) return normalized + return `${year}/${month}/${day}` + }, fetchList() { this.loading = true getLbPurchaseApplyList(this.buildListParams()) @@ -1038,6 +1171,10 @@ export default { + +