diff --git a/src/api/system/dept.js b/src/api/system/dept.js index de1edf6..37b1aba 100644 --- a/src/api/system/dept.js +++ b/src/api/system/dept.js @@ -48,6 +48,15 @@ export const syncIamOrganizations = () => { }); }; +export const clearNonTopDept = signal => { + return request({ + url: '/blade-system/dept/clear-non-top', + method: 'post', + timeout: 60000, + signal, + }); +}; + export const syncOaCompany = (current = 1, size = 20, signal) => { return request({ url: '/blade-system/dept/sync-oa-company', diff --git a/src/components/address-map-picker/main.vue b/src/components/address-map-picker/main.vue index 851d9eb..aab6ba6 100644 --- a/src/components/address-map-picker/main.vue +++ b/src/components/address-map-picker/main.vue @@ -42,6 +42,14 @@ export default { type: String, default: '', }, + longitude: { + type: [String, Number], + default: '', + }, + latitude: { + type: [String, Number], + default: '', + }, }, emits: ['update:modelValue', 'confirm'], data() { @@ -66,9 +74,11 @@ export default { this.visible = value; if (value) { this.keyword = this.address || ''; - this.selected = {}; - this.status = '可搜索地址或点击地图选点'; this.searchResults = []; + this.selected = this.buildInitialSelection(); + this.status = this.selected.longitude + ? this.selected.address || '已选点,可确认回填' + : '可搜索地址或点击地图选点'; } }, }, @@ -82,6 +92,18 @@ export default { } }, methods: { + buildInitialSelection() { + const longitude = Number(this.longitude); + const latitude = Number(this.latitude); + if (!Number.isFinite(longitude) || !Number.isFinite(latitude)) { + return {}; + } + return { + longitude, + latitude, + address: this.address || '', + }; + }, loadAmap() { if (window.AMap && window.AMap.Map) { return Promise.resolve(); @@ -110,15 +132,27 @@ export default { return new Promise(resolve => { this.$nextTick(() => { if (!this.$refs.map) return resolve(null); + const initial = this.buildInitialSelection(); + const center = + Number.isFinite(initial.longitude) && Number.isFinite(initial.latitude) + ? [initial.longitude, initial.latitude] + : [116.40769, 39.89945]; if (!this.amap) { this.amap = new window.AMap.Map(this.$refs.map, { - center: [116.40769, 39.89945], - zoom: 11, + center, + zoom: initial.longitude ? 14 : 11, }); this.amap.on('click', event => this.pickPoint(event.lnglat)); } else { this.amap.resize(); this.clearMarker(); + this.amap.setZoomAndCenter(initial.longitude ? 14 : 11, center); + } + if (initial.longitude) { + const point = new window.AMap.LngLat(initial.longitude, initial.latitude); + this.renderMarker(point); + this.selected = { ...initial }; + this.status = initial.address || '已选点,可确认回填'; } resolve(this.amap); }); @@ -254,11 +288,19 @@ export default { } }, confirm() { + if (!this.selected.longitude || !this.selected.latitude) { + this.$message.warning('请先搜索或点击地图完成选点'); + return; + } if (!this.selected.address) { this.$message.warning('请等待地址解析完成后再确认'); return; } - this.$emit('confirm', this.selected.address); + this.$emit('confirm', { + address: this.selected.address, + longitude: this.selected.longitude, + latitude: this.selected.latitude, + }); this.visible = false; }, }, diff --git a/src/option/base/measurement-unit.js b/src/option/base/measurement-unit.js index 5f02982..7c68b87 100644 --- a/src/option/base/measurement-unit.js +++ b/src/option/base/measurement-unit.js @@ -42,6 +42,7 @@ export const createOption = () => ({ label: '计量单位编码', prop: 'unitCode', minWidth: 150, + span: 24, search: true, searchOrder: 4, searchSpan: 6, diff --git a/src/option/business/temporary-credit-limit.js b/src/option/business/temporary-credit-limit.js index 4ad6fb4..e069f7d 100644 --- a/src/option/business/temporary-credit-limit.js +++ b/src/option/business/temporary-credit-limit.js @@ -334,7 +334,7 @@ export const option = { }, ...auditColumns.map(column => ({ ...column, hide: true })), ])), - dialogWidth: '96%', + dialogWidth: '1100px', menuFixed: 'right', menuWidth: 320, index: false, diff --git a/src/option/system/authlog.js b/src/option/system/authlog.js index d550790..3e937f1 100644 --- a/src/option/system/authlog.js +++ b/src/option/system/authlog.js @@ -5,6 +5,7 @@ export const GRANT_TYPE_DIC = [ { label: '社交登录', value: 'social' }, { label: '客户端凭证', value: 'client_credentials' }, { label: '刷新令牌', value: 'refresh_token' }, + { label: '退出登录', value: 'logout' }, ]; export const authLogOption = { @@ -38,7 +39,7 @@ export const authLogOption = { prop: 'realName', }, { - label: '授权类型', + label: '操作类型', prop: 'grantType', type: 'select', search: true, @@ -80,7 +81,7 @@ export const authLogOption = { width: 120, }, { - label: '登录时间', + label: '操作时间', prop: 'loginTime', sortable: true, span: 24, diff --git a/src/option/system/user.js b/src/option/system/user.js index 197198b..9cc07c3 100644 --- a/src/option/system/user.js +++ b/src/option/system/user.js @@ -1,12 +1,9 @@ import { getDeptLazyTree } from '@/api/system/dept'; +import { validateLoginPassword } from '@/utils/validate'; export const userOption = safe => { const validatePass = (rule, value, callback) => { - if (value === '') { - callback(new Error('请输入密码')); - } else { - callback(); - } + validateLoginPassword(rule, value, callback); }; const validatePass2 = (rule, value, callback) => { if (value === '') { diff --git a/src/page/login/index.vue b/src/page/login/index.vue index a9ee607..0ab99ca 100644 --- a/src/page/login/index.vue +++ b/src/page/login/index.vue @@ -24,6 +24,7 @@ --> + @@ -61,7 +63,7 @@ export default { return { website: website, time: '', - activeName: 'iam', + activeName: 'user', socialForm: { tenantId: '000000', source: '', diff --git a/src/router/page/index.js b/src/router/page/index.js index cbb2e72..ab93bb9 100644 --- a/src/router/page/index.js +++ b/src/router/page/index.js @@ -87,7 +87,7 @@ export default [ { path: '/business/project-apply/public-view', name: '查看项目信息', - component: () => import('@/views/mk/public-biz-view.vue'), + component: () => import('@/views/business/project-apply-public-view.vue'), meta: { keepAlive: false, isTab: false, @@ -98,7 +98,7 @@ export default [ { path: '/business/contract-manage/public-view', name: '查看合同信息', - component: () => import('@/views/mk/public-biz-view.vue'), + component: () => import('@/views/business/contract-manage-public-view.vue'), meta: { keepAlive: false, isTab: false, @@ -109,7 +109,7 @@ export default [ { path: '/business/waybill-manage/public-view', name: '查看运单信息', - component: () => import('@/views/mk/public-biz-view.vue'), + component: () => import('@/views/business/waybill-manage-public-view.vue'), meta: { keepAlive: false, isTab: false, @@ -120,7 +120,7 @@ export default [ { path: '/settlement/pre-settlement/public-view', name: '查看预结算信息', - component: () => import('@/views/mk/public-biz-view.vue'), + component: () => import('@/views/settlement/pre-settlement-public-view.vue'), meta: { keepAlive: false, isTab: false, @@ -131,7 +131,7 @@ export default [ { path: '/settlement/formal-settlement/public-view', name: '查看正式结算信息', - component: () => import('@/views/mk/public-biz-view.vue'), + component: () => import('@/views/settlement/formal-settlement-public-view.vue'), meta: { keepAlive: false, isTab: false, @@ -142,7 +142,7 @@ export default [ { path: '/payment/payment-application/public-view', name: '查看付款申请信息', - component: () => import('@/views/mk/public-biz-view.vue'), + component: () => import('@/views/payment/payment-application-public-view.vue'), meta: { keepAlive: false, isTab: false, diff --git a/src/utils/mk-approval.js b/src/utils/mk-approval.js index a8544e7..5b02da7 100644 --- a/src/utils/mk-approval.js +++ b/src/utils/mk-approval.js @@ -40,10 +40,22 @@ export const MK_BIZ = { }, }; -export async function resolveMkTemplateCode(dictName) { +const MK_SWITCH_NAME = '开启MK'; + +async function loadMkTemplateList() { const res = await getDictionary({ code: 'mk_template' }); - const list = res?.data?.data || []; - const matched = list.find(item => String(item.dictValue || '').trim() === dictName); + return res?.data?.data || []; +} + +/** 业务字典 mk_template:名称「开启MK」且键值为 1 时才请求 MK */ +export function isMkEnabled(list = []) { + const matched = list.find(item => String(item.dictValue || '').trim() === MK_SWITCH_NAME); + return String(matched?.dictKey ?? '').trim() === '1'; +} + +export async function resolveMkTemplateCode(dictName, list) { + const dictList = list || (await loadMkTemplateList()); + const matched = dictList.find(item => String(item.dictValue || '').trim() === dictName); const templateCode = matched?.dictKey; if (!templateCode) { ElMessage.warning(`未配置业务字典 mk_template「${dictName}」,无法提交审核流`); @@ -62,10 +74,14 @@ export async function submitMkApprovalFlow({ if (!conf) { return Promise.reject(new Error(`不支持的MK业务类型:${bizType}`)); } + const list = await loadMkTemplateList(); + if (!isMkEnabled(list)) { + return; + } if ((conf.rejected || []).includes(approvalStatus)) { await processDelete({ formInstanceId: String(formInstanceId) }); } - const templateCode = await resolveMkTemplateCode(conf.dictName); + const templateCode = await resolveMkTemplateCode(conf.dictName, list); const subject = subjectName ? `${conf.subjectPrefix}:${subjectName}` : `${conf.subjectPrefix}:${formInstanceId}`; diff --git a/src/utils/validate.js b/src/utils/validate.js index ccba6e7..6aed154 100644 --- a/src/utils/validate.js +++ b/src/utils/validate.js @@ -283,3 +283,31 @@ export function validatejson(val) { // 非对象、非数组、非字符串,或者字符串不是 JSON return false; } + +/** 登录密码规则提示 */ +export const LOGIN_PASSWORD_RULE_MESSAGE = + '密码须大于8位,且同时包含字母、数字和特殊字符(.!@#$%^&*)'; + +/** + * 校验登录密码强度:大于8位,同时包含字母、数字、特殊字符(.!@#$%^&*) + * @param {string} password + * @returns {boolean} + */ +export function isValidLoginPassword(password) { + return /^(?=.*[A-Za-z])(?=.*\d)(?=.*[.!@#$%^&*]).{9,}$/.test(String(password || '')); +} + +/** + * Element Plus / Avue 表单校验器:登录密码强度 + */ +export function validateLoginPassword(rule, value, callback) { + if (value === undefined || value === null || String(value).trim() === '') { + callback(new Error('请输入登录密码')); + return; + } + if (!isValidLoginPassword(value)) { + callback(new Error(LOGIN_PASSWORD_RULE_MESSAGE)); + return; + } + callback(); +} diff --git a/src/views/base/airport-master.vue b/src/views/base/airport-master.vue index ad78455..a4f0632 100644 --- a/src/views/base/airport-master.vue +++ b/src/views/base/airport-master.vue @@ -94,6 +94,17 @@ /> + @@ -134,6 +152,7 @@ import { openImportDialog } from '@/utils/import-excel'; import { formatUpdateUserName } from '@/utils/audit'; import { getToken } from '@/utils/auth'; import { getCoordinateValidationMessage, normalizeCoordinateInput } from '@/utils/coordinate'; +import AddressMapPicker from '@/components/address-map-picker/main.vue'; import NProgress from 'nprogress'; import 'nprogress/nprogress.css'; @@ -181,6 +200,9 @@ const addExcelRequiredHeaderMarks = async blob => { }; export default { + components: { + AddressMapPicker, + }, data() { const validateIataCode = (rule, value, callback) => { if (!/^[A-Z]{3}$/.test(String(value || '').toUpperCase())) { @@ -220,6 +242,7 @@ export default { loading: true, data: [], excelBox: false, + addressMapPickerVisible: false, excelForm: {}, provinceOptions: [], cityOptions: [], @@ -368,15 +391,14 @@ export default { { label: '详细地址', prop: 'detailAddress', - type: 'textarea', - minRows: 2, + formslot: true, span: 24, minWidth: 220, overHidden: false, maxlength: 255, showWordLimit: true, rules: [ - { required: true, message: '请输入详细地址', trigger: 'blur' }, + { required: true, message: '请选择详细地址', trigger: 'change' }, { max: 255, message: '详细地址不能超过255字', trigger: 'blur' }, ], }, @@ -669,6 +691,29 @@ export default { handleCoordinateInput(prop, value) { this.form[prop] = normalizeCoordinateInput(value); }, + openAddressMapPicker() { + this.addressMapPickerVisible = true; + }, + handleAddressMapConfirm(payload) { + const selection = typeof payload === 'string' ? { address: payload } : payload || {}; + if (selection.address) { + this.form.detailAddress = selection.address; + } + if ( + selection.longitude !== undefined && + selection.longitude !== null && + selection.longitude !== '' + ) { + this.form.longitude = Number(selection.longitude).toFixed(6); + } + if ( + selection.latitude !== undefined && + selection.latitude !== null && + selection.latitude !== '' + ) { + this.form.latitude = Number(selection.latitude).toFixed(6); + } + }, normalizeRow(row) { row.code = row.iataCode ? `JC-${row.iataCode}` : row.code; row.regionCode = String(row.regionCode || '').trim(); @@ -917,4 +962,13 @@ export default { white-space: nowrap; } } + +.detail-address-input { + cursor: pointer; + + :deep(.el-input__wrapper), + :deep(.el-input__inner) { + cursor: pointer; + } +} diff --git a/src/views/base/port-terminal.vue b/src/views/base/port-terminal.vue index 125e786..dc0e763 100644 --- a/src/views/base/port-terminal.vue +++ b/src/views/base/port-terminal.vue @@ -194,10 +194,11 @@ + @@ -264,12 +272,16 @@ import { openImportDialog } from '@/utils/import-excel'; import { formatUpdateUserName } from '@/utils/audit'; import { getToken } from '@/utils/auth'; import { getCoordinateValidationMessage, normalizeCoordinateInput } from '@/utils/coordinate'; +import AddressMapPicker from '@/components/address-map-picker/main.vue'; import NProgress from 'nprogress'; import 'nprogress/nprogress.css'; const DEFAULT_COUNTRY_CODE = '+86'; const newLocal = '请选择类型'; export default { + components: { + AddressMapPicker, + }, data() { const validateCode = (rule, value, callback) => { const code = String(value || '').toUpperCase(); @@ -315,6 +327,7 @@ export default { loading: true, data: [], excelBox: false, + addressMapPickerVisible: false, excelForm: {}, portOptions: [], countryOptions: [], @@ -572,11 +585,11 @@ export default { minWidth: 220, overHidden: false, order: 85, - placeholder: '请输入', + placeholder: '点击选择地图地址', maxlength: 255, showWordLimit: true, rules: [ - { required: true, message: '请输入详细地址', trigger: 'blur' }, + { required: true, message: '请选择详细地址', trigger: 'change' }, { max: 255, message: '详细地址不能超过255字', trigger: 'blur' }, ], }, @@ -1040,6 +1053,21 @@ export default { handleCoordinateInput(prop, value) { this.form[prop] = normalizeCoordinateInput(value); }, + openAddressMapPicker() { + this.addressMapPickerVisible = true; + }, + handleAddressMapConfirm(payload) { + const selection = typeof payload === 'string' ? { address: payload } : payload || {}; + if (selection.address) { + this.form.detailAddress = selection.address; + } + if (selection.longitude !== undefined && selection.longitude !== null && selection.longitude !== '') { + this.form.longitude = Number(selection.longitude).toFixed(6); + } + if (selection.latitude !== undefined && selection.latitude !== null && selection.latitude !== '') { + this.form.latitude = Number(selection.latitude).toFixed(6); + } + }, resolveCountryCode(country) { if (!country) { return Promise.resolve(''); @@ -1107,7 +1135,7 @@ export default { return false; } if (isBlank(row.detailAddress)) { - this.$message.warning('请输入详细地址'); + this.$message.warning('请选择详细地址'); return false; } if (isBlank(row.longitude)) { @@ -1399,6 +1427,15 @@ export default { line-height: 20px; padding: 4px 0; } + +.detail-address-input { + cursor: pointer; + + :deep(.el-input__wrapper), + :deep(.el-input__inner) { + cursor: pointer; + } +} + + diff --git a/src/views/business/project-apply.vue b/src/views/business/project-apply.vue index 12d2e22..abcac61 100644 --- a/src/views/business/project-apply.vue +++ b/src/views/business/project-apply.vue @@ -1,5 +1,5 @@ + + diff --git a/src/views/business/temporary-credit-limit.vue b/src/views/business/temporary-credit-limit.vue index 735909e..fc66576 100644 --- a/src/views/business/temporary-credit-limit.vue +++ b/src/views/business/temporary-credit-limit.vue @@ -99,13 +99,25 @@ - + + + + @@ -172,7 +184,7 @@ title="查看临时额度申请" append-to-body destroy-on-close - width="96%" + width="1100px" class="temporary-credit-limit-dialog temporary-credit-limit-detail-dialog" >
@@ -204,13 +216,16 @@
- + + + + @@ -783,24 +798,37 @@ export default { const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss'); this.attachmentRows = (list || []).map(item => ({ ...item, + description: item.description || '', uploadUserName: item.uploadUserName || uploadUserName, uploadTime: item.uploadTime || uploadTime, })); - this.form.attachmentsJson = JSON.stringify(this.attachmentRows); + this.syncAttachmentsJson(); }, removeAttachment(index) { this.attachmentRows.splice(index, 1); - this.form.attachmentsJson = JSON.stringify(this.attachmentRows); + this.syncAttachmentsJson(); + }, + syncAttachmentsJson() { + this.form.attachmentsJson = JSON.stringify(this.attachmentRows || []); }, parseJsonArray(value) { - if (Array.isArray(value)) return value; - if (!value) return []; - try { - const data = JSON.parse(value); - return Array.isArray(data) ? data : []; - } catch (error) { + let list = []; + if (Array.isArray(value)) { + list = value; + } else if (!value) { return []; + } else { + try { + const data = JSON.parse(value); + list = Array.isArray(data) ? data : []; + } catch (error) { + return []; + } } + return list.map(item => ({ + ...item, + description: item?.description || '', + })); }, attachmentName(row = {}) { return row.originalName || row.name || row.fileName || '附件'; @@ -907,6 +935,7 @@ export default { .temporary-credit-limit-page { &__field { width: 100%; + max-width: 240px; } &__attachment-head { @@ -993,7 +1022,7 @@ export default { align-items: center; gap: 8px; width: 100%; - padding: 14px 16px 4px; + padding: 14px 0 4px; margin-bottom: 0; color: #303133; font-size: 15px; @@ -1086,9 +1115,36 @@ export default { background: transparent !important; box-shadow: none !important; margin: 0 !important; - padding: 0 !important; + padding: 0 16px 8px !important; border-radius: 0 !important; } + + // 新增/编辑:控件限宽,避免宽屏下撑满列宽贴到弹窗右边缘 + &:not(.temporary-credit-limit-detail-dialog) { + .el-form-item__content { + min-width: 0; + } + + .el-form-item__content > .el-input, + .el-form-item__content > .el-select, + .el-form-item__content > .el-date-editor, + .el-form-item__content > .el-cascader, + .el-form-item__content > .el-textarea, + .temporary-credit-limit-page__field { + width: 100%; + max-width: 240px; + } + + .el-form-item__content > .el-textarea { + max-width: 100%; + } + + .el-date-editor.el-input, + .el-date-editor.el-input__wrapper { + width: 100%; + max-width: 240px; + } + } } .temporary-credit-limit-dialog .business-crud-page__detail-content .el-descriptions__label { diff --git a/src/views/business/waybill-manage-public-view.vue b/src/views/business/waybill-manage-public-view.vue new file mode 100644 index 0000000..bcb3714 --- /dev/null +++ b/src/views/business/waybill-manage-public-view.vue @@ -0,0 +1,38 @@ + + + diff --git a/src/views/mk/mk-public-shell.vue b/src/views/mk/mk-public-shell.vue new file mode 100644 index 0000000..5e42e40 --- /dev/null +++ b/src/views/mk/mk-public-shell.vue @@ -0,0 +1,155 @@ + + + + + + + diff --git a/src/views/payment/payment-application-form.vue b/src/views/payment/payment-application-form.vue index 6adff7c..7d68571 100644 --- a/src/views/payment/payment-application-form.vue +++ b/src/views/payment/payment-application-form.vue @@ -364,7 +364,7 @@ @click="submitForm" >提交 - 返回 + 返回 + + + + + + diff --git a/src/views/settlement/components/formal-settlement-editor.vue b/src/views/settlement/components/formal-settlement-editor.vue index 93cd0bd..896c2a0 100644 --- a/src/views/settlement/components/formal-settlement-editor.vue +++ b/src/views/settlement/components/formal-settlement-editor.vue @@ -211,8 +211,15 @@ - - + + +
+ +
@@ -623,7 +640,7 @@ >提交 -
+
取消 + + diff --git a/src/views/settlement/pre-settlement-form.vue b/src/views/settlement/pre-settlement-form.vue index b70ec75..9bd3549 100644 --- a/src/views/settlement/pre-settlement-form.vue +++ b/src/views/settlement/pre-settlement-form.vue @@ -50,6 +50,7 @@ export default { }, goBack() { removeSettlementTransfer(this.$route.query.transferToken); + this.$router.$avueRouter.closeTag(); this.$router.push('/settlement/pre-settlement'); }, syncTagTitle() { diff --git a/src/views/settlement/pre-settlement-public-view.vue b/src/views/settlement/pre-settlement-public-view.vue new file mode 100644 index 0000000..1ddec11 --- /dev/null +++ b/src/views/settlement/pre-settlement-public-view.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/src/views/settlement/receivable-payable-detail.vue b/src/views/settlement/receivable-payable-detail.vue index 0d92d07..faf5b97 100644 --- a/src/views/settlement/receivable-payable-detail.vue +++ b/src/views/settlement/receivable-payable-detail.vue @@ -1153,28 +1153,16 @@ export default { minWidth: 130, align: 'right', }; - const dynamicColumns = this.isPayable - ? [ - { - label: '运输费', - prop: 'tableFreightAmount', - feeSummaryType: 'freight', - minWidth: 130, - align: 'right', - }, - otherFeeColumn, - ] - : [ - ...this.tableFeeItemNames.map((name, index) => ({ - label: name, - prop: `tableFeeItem${index}`, - feeItemName: name, - dynamic: true, - minWidth: 130, - align: 'right', - })), - otherFeeColumn, - ]; + const dynamicColumns = [ + { + label: '运输费', + prop: 'tableFreightAmount', + feeSummaryType: 'freight', + minWidth: 130, + align: 'right', + }, + otherFeeColumn, + ]; if (totalIndex < 0) return [...columns, ...dynamicColumns]; columns.splice(totalIndex, 0, ...dynamicColumns); return columns; @@ -1498,7 +1486,7 @@ export default { const res = await api.getList(this.page.current, this.page.size, params); const data = this.unwrapPage(res); this.rows = (data.records || []).map(this.decorateRow); - this.tableFeeItemNames = this.isPayable ? [] : this.collectFeeItemNames(this.rows); + this.tableFeeItemNames = []; this.page.total = data.total || 0; } finally { this.loading = false; @@ -1738,7 +1726,7 @@ export default { }); const adjusted = { ...item, - transportQuantity: Number(item.transportQuantity || 0), + transportQuantity: this.normalizeAdjustTransportQuantity(item), mileage: item.mileage === null || item.mileage === undefined || Number(item.mileage) === -1 ? null @@ -1767,6 +1755,21 @@ export default { feeSourceLabel(value) { return ['手动录入', '手动添加', '手工录入'].includes(value) ? '手动录入' : '自动生成'; }, + normalizeAdjustTransportQuantity(item = {}) { + const value = item.transportQuantity; + if (value === undefined || value === null || value === '' || Number(value) === -1) { + return ''; + } + const element = item.billingFactor; + // 按车辆/固定金额生成时运输量会被写成占位 1,调整弹窗无实际运输量时不展示 + if ( + (element === '按车辆' || element === '固定金额(整单一口价)') && + Number(value) === 1 + ) { + return ''; + } + return Number(value); + }, adjustBillingTypes(row) { return ADJUST_BILLING_TYPE_MAP[row.billingFactor] || []; }, @@ -1894,7 +1897,12 @@ export default { model: row.model, billingFactor: row.billingFactor, billingType: row.billingType, - transportQuantity: row.transportQuantity, + transportQuantity: + row.transportQuantity === '' || + row.transportQuantity === null || + row.transportQuantity === undefined + ? null + : Number(row.transportQuantity), priceUnit: row.priceUnit, unitPrice: row.unitPrice, mileage: row.mileage, diff --git a/src/views/system/dept.vue b/src/views/system/dept.vue index e30e593..565ad53 100644 --- a/src/views/system/dept.vue +++ b/src/views/system/dept.vue @@ -143,7 +143,8 @@ :status="oaSyncProgressStatus" :stroke-width="12" /> -
+
当前阶段:{{ oaSyncStageLabel }}
+
当前阶段:{{ oaSyncStageLabel }},第 {{ oaSyncProgress.current || 0 }} / {{ oaSyncTotalPage }} 页
@@ -177,6 +178,7 @@ import { getDeptTree, syncOaCompany, syncOaDepartment, + clearNonTopDept, } from '@/api/system/dept'; import { getLeaderList } from '@/api/system/user'; import { getList as getCustomerArchiveList } from '@/api/vehicle/customer-archive'; @@ -215,6 +217,7 @@ export default { oaSyncCancelled: false, oaSyncStatus: 'running', oaSyncStage: 'company', + oaSyncClearNonTop: false, oaSyncController: null, oaSyncProgress: { current: 0, @@ -287,8 +290,9 @@ export default { return; } const parentId = this.normalizeParentId(this.form?.parentId); + // 顶级组织不选择上级时,编码不依赖上级编码 if (!parentId) { - callback(new Error('请先选择上级组织')); + callback(); return; } if (!this.parentDept || String(this.parentDept.id) !== String(parentId)) { @@ -429,13 +433,6 @@ export default { props: { label: 'title', }, - rules: [ - { - required: true, - message: '请选择上级组织', - trigger: 'click', - }, - ], }, { label: '所属租户', @@ -520,6 +517,9 @@ export default { return '自动同步组织'; }, oaSyncStageLabel() { + if (this.oaSyncStage === 'clear') { + return '清除非顶级组织'; + } return this.oaSyncStage === 'department' ? '同步部门' : '同步公司'; }, oaSyncTotalPage() { @@ -555,21 +555,34 @@ export default { }, methods: { handleOaOrgSync() { - this.$confirm('确定从OA先同步公司、再同步部门?', '提示', { - confirmButtonText: '确定', - cancelButtonText: '取消', - type: 'warning', - }).then(() => { - this.startOaOrgSync(); - }); + this.$confirm( + '是否清除非顶级组织?选择“是”将先删除顶级以外的组织后再同步;选择“否”则直接同步。', + '提示', + { + confirmButtonText: '是', + cancelButtonText: '否', + distinguishCancelAndClose: true, + closeOnClickModal: false, + type: 'warning', + } + ) + .then(() => { + this.startOaOrgSync(true); + }) + .catch(action => { + if (action === 'cancel') { + this.startOaOrgSync(false); + } + }); }, - startOaOrgSync() { + startOaOrgSync(clearNonTop) { this.oaSyncLoading = true; this.oaSyncVisible = true; this.oaSyncRunning = true; this.oaSyncCancelled = false; this.oaSyncStatus = 'running'; - this.oaSyncStage = 'company'; + this.oaSyncClearNonTop = !!clearNonTop; + this.oaSyncStage = clearNonTop ? 'clear' : 'company'; this.oaSyncController = new AbortController(); this.oaSyncProgress = { current: 0, @@ -607,6 +620,14 @@ export default { }, async runOaOrgSyncPages() { try { + if (this.oaSyncClearNonTop) { + this.oaSyncStage = 'clear'; + await clearNonTopDept(this.oaSyncController?.signal); + if (this.oaSyncCancelled) { + this.oaSyncStatus = 'cancelled'; + return; + } + } this.oaSyncStage = 'company'; await this.runOaOrgSyncStage(syncOaCompany); if (this.oaSyncCancelled) { @@ -821,6 +842,11 @@ export default { .filter(Boolean); return result.length ? result.join('、') : '-'; }, + normalizeTopParent(row) { + if (!this.normalizeParentId(row?.parentId)) { + row.parentId = 0; + } + }, isRootDept(row) { return row && (row.parentId === 0 || String(row.parentId) === '0'); }, @@ -836,6 +862,7 @@ export default { row.tenantId = row.tenantId || this.userInfo.tenantId || website.tenantId; row.deptCode = String(row.deptCode || '').trim(); row.leaderId = func.join(row.leaderId); + this.normalizeTopParent(row); if (Number(row.deptCategory) === 6 && !row.carrierCustomerId) { this.$message.warning('请选择承运商'); loading(); @@ -865,6 +892,7 @@ export default { row.tenantId = row.tenantId || this.userInfo.tenantId || website.tenantId; row.deptCode = String(row.deptCode || '').trim(); row.leaderId = func.join(row.leaderId); + this.normalizeTopParent(row); if (Number(row.deptCategory) === 6 && !row.carrierCustomerId) { this.$message.warning('请选择承运商'); loading(); diff --git a/src/views/system/user.vue b/src/views/system/user.vue index 107f5be..6d400fb 100644 --- a/src/views/system/user.vue +++ b/src/views/system/user.vue @@ -205,7 +205,7 @@ type="password" show-password autocomplete="new-password" - placeholder="请输入登录密码" + placeholder="大于8位,含字母、数字、特殊字符.!@#$%^&*" /> @@ -332,7 +332,12 @@ - + @@ -424,6 +429,7 @@ import { mapGetters } from 'vuex'; import { h } from 'vue'; import { getToken } from '@/utils/auth'; import { downloadXls } from '@/utils/util'; +import { validateLoginPassword } from '@/utils/validate'; import NProgress from 'nprogress'; import 'nprogress/nprogress.css'; import func from '@/utils/func'; @@ -487,8 +493,7 @@ export default { passwordForm: {}, passwordRules: { password: [ - { required: true, message: '请输入新密码', trigger: 'blur' }, - { min: 6, max: 32, message: '密码长度在6到32个字符', trigger: 'blur' }, + { required: true, validator: validateLoginPassword, trigger: 'blur' }, ], password2: [ { required: true, message: '请再次输入新密码', trigger: 'blur' }, @@ -497,6 +502,7 @@ export default { }, userRules: { account: [{ required: true, message: '请输入账号名', trigger: 'blur' }], + password: [{ required: true, validator: validateLoginPassword, trigger: 'blur' }], realName: [{ required: true, message: '请输入姓名', trigger: 'blur' }], phone: [{ required: true, message: '请输入手机号', trigger: 'blur' }], deptId: [{ required: true, message: '请选择所属组织', trigger: 'change' }], diff --git a/src/views/system/userinfo.vue b/src/views/system/userinfo.vue index a0d55f8..002ff50 100644 --- a/src/views/system/userinfo.vue +++ b/src/views/system/userinfo.vue @@ -91,7 +91,7 @@