diff --git a/.env.production b/.env.production index ff69b18..6e01c9d 100644 --- a/.env.production +++ b/.env.production @@ -5,10 +5,10 @@ VITE_APP_TITLE = 短信发送小工具 VITE_APP_ENV = 'production' # 短信发送小工具/生产环境 -VITE_APP_BASE_API = '/ynDigitalGadgets-prod-api' +VITE_APP_BASE_API = '/ynMessage-prod-api/ynMessage' #静态资源路径(服务器目录) -VITE_APP_PUBLIC_PATH = '/ynDigitalGadgets' +VITE_APP_PUBLIC_PATH = '/ynMessage' # 是否在打包时开启压缩,支持 gzip 和 brotli VITE_BUILD_COMPRESS = gzip \ No newline at end of file diff --git a/public/favicon.ico b/public/favicon.ico index 84ea865..d4e04d3 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/src/api/monitor/job.js b/src/api/monitor/job.js index 30b9897..cb08f4f 100644 --- a/src/api/monitor/job.js +++ b/src/api/monitor/job.js @@ -29,8 +29,8 @@ export function addJob(data) { // 修改定时任务调度 export function updateJob(data) { return request({ - url: '/job', - method: 'put', + url: '/job/edit', + method: 'POST', data: data }) } @@ -38,8 +38,11 @@ export function updateJob(data) { // 删除定时任务调度 export function delJob(jobId) { return request({ - url: '/job/' + jobId, - method: 'delete' + url: '/job/delete/', + method: 'POST', + data: { + jobId: jobId + } }) } diff --git a/src/assets/logo/logo.png b/src/assets/logo/logo.png index 84ea865..d4e04d3 100644 Binary files a/src/assets/logo/logo.png and b/src/assets/logo/logo.png differ diff --git a/src/components/PersonPicker/index.vue b/src/components/PersonPicker/index.vue index 499cd9e..d30b183 100644 --- a/src/components/PersonPicker/index.vue +++ b/src/components/PersonPicker/index.vue @@ -277,8 +277,17 @@ const onPersonSelectionChange = (selection) => { return } - // 更新直接选择的人员ID列表 - directSelectedPersonIds.value = selection.map((item) => item.id) + // 获取当前过滤后可见的人员ID集合 + const visiblePersonIds = new Set(filteredPersonList.value.map((item) => item.id)) + + // 获取当前选中的可见人员ID集合 + const selectedVisibleIds = new Set(selection.map((item) => item.id)) + + // 保留不在当前过滤列表中的已选人员(这些人员被搜索过滤掉了,但应该保留) + const preservedIds = directSelectedPersonIds.value.filter((id) => !visiblePersonIds.has(id)) + + // 合并保留的ID和当前选中的可见ID + directSelectedPersonIds.value = [...preservedIds, ...selectedVisibleIds] // 触发更新,合并所有选中的人员 updateSelectedPersons() @@ -333,7 +342,17 @@ const onGroupSelectionChange = (selection) => { return } - selectedGroupIds.value = selection.map((group) => group.id) + // 获取当前过滤后可见的分组ID集合 + const visibleGroupIds = new Set(filteredGroupList.value.map((item) => item.id)) + + // 获取当前选中的可见分组ID集合 + const selectedVisibleIds = new Set(selection.map((group) => group.id)) + + // 保留不在当前过滤列表中的已选分组(这些分组被搜索过滤掉了,但应该保留) + const preservedIds = selectedGroupIds.value.filter((id) => !visibleGroupIds.has(id)) + + // 合并保留的ID和当前选中的可见ID + selectedGroupIds.value = [...preservedIds, ...selectedVisibleIds] // 触发更新,合并所有选中的人员 updateSelectedPersons() @@ -401,34 +420,38 @@ const syncGroupTableSelection = () => { // 移除单个人员 const handleRemovePerson = (personId) => { - // 检查该人员是否来自分组 - const groupsToRemove = [] + // 检查该人员是否在直接选择列表中 + const isDirectSelected = directSelectedPersonIds.value.includes(personId) + // 检查该人员是否来自分组 + const groupsContainingPerson = [] selectedGroupIds.value.forEach((groupId) => { const group = groupList.value.find((g) => g.id === groupId) if (group && group.workerList && Array.isArray(group.workerList)) { const hasPerson = group.workerList.some((w) => w.id === personId) if (hasPerson) { - groupsToRemove.push(groupId) + groupsContainingPerson.push(groupId) } } }) - if (groupsToRemove.length > 0) { - // 如果人员来自分组,移除对应的分组 - selectedGroupIds.value = selectedGroupIds.value.filter((id) => !groupsToRemove.includes(id)) - updateSelectedPersons() - nextTick(() => { - syncGroupTableSelection() - }) - } else { - // 如果人员是直接选择的,从直接选择列表中移除 + // 如果人员既在直接选择列表中,又在分组中,只移除直接选择,保留分组 + if (isDirectSelected) { directSelectedPersonIds.value = directSelectedPersonIds.value.filter( (id) => id !== personId, ) - updateSelectedPersons() + } else if (groupsContainingPerson.length > 0) { + // 如果人员只来自分组(不在直接选择列表中),移除对应的分组 + selectedGroupIds.value = selectedGroupIds.value.filter( + (id) => !groupsContainingPerson.includes(id), + ) + nextTick(() => { + syncGroupTableSelection() + }) } + updateSelectedPersons() + // 同步人员表格 nextTick(() => { syncPersonTableSelection() @@ -482,42 +505,31 @@ const getGroupList = async () => { } } -// 监听 modelValue 变化,同步表格选中状态和区分直接选择的人员 +// 监听 modelValue 变化,仅在初始化时同步(避免干扰用户操作) watch( () => props.modelValue, (newVal) => { + // 如果内部状态已经有数据,说明是用户操作导致的,不需要反向同步 + // 只在初始化时(两个列表都为空)才同步 + if (directSelectedPersonIds.value.length > 0 || selectedGroupIds.value.length > 0) { + return + } + if (!newVal || newVal.length === 0) { directSelectedPersonIds.value = [] return } - // 区分哪些是直接选择的人员,哪些是来自分组的 - const directSelectedIds = [] - const groupPersonIds = new Set() - - // 收集所有分组中的人员ID - selectedGroupIds.value.forEach((groupId) => { - const group = groupList.value.find((g) => g.id === groupId) - if (group && group.workerList && Array.isArray(group.workerList)) { - group.workerList.forEach((worker) => { - if (worker && worker.id) { - groupPersonIds.add(worker.id) - } - }) - } - }) - - // 如果人员不在分组中,则认为是直接选择的 - newVal.forEach((person) => { - if (person && person.id && !groupPersonIds.has(person.id)) { - directSelectedIds.push(person.id) - } - }) - - directSelectedPersonIds.value = directSelectedIds + // 初始化时,尝试区分哪些是直接选择的人员 + // 由于此时还没有分组数据,先假设所有人员都是直接选择的 + // 等分组数据加载后,会通过 updateSelectedPersons 重新计算 + const personIds = newVal.map((p) => p.id).filter(Boolean) + directSelectedPersonIds.value = personIds if (activeTab.value === 'person') { - syncPersonTableSelection() + nextTick(() => { + syncPersonTableSelection() + }) } }, { deep: true, immediate: true }, diff --git a/src/utils/bus.js b/src/utils/bus.js index 3d4cb73..5e55b39 100644 --- a/src/utils/bus.js +++ b/src/utils/bus.js @@ -4,4 +4,5 @@ export const bus = mitt() // 定义事件名称常量 export const BUS_EVENTS = { REFRESH_OPTIONS: 'REFRESH_OPTIONS', // 刷新下拉选项信号 + REFRESH_LOOP_SEND_LIST: 'REFRESH_LOOP_SEND_LIST', // 刷新循环发送任务列表 } diff --git a/src/views/basicManage/personManage/addAndEditForm.vue b/src/views/basicManage/personManage/addAndEditForm.vue index 8b5d4e5..294776b 100644 --- a/src/views/basicManage/personManage/addAndEditForm.vue +++ b/src/views/basicManage/personManage/addAndEditForm.vue @@ -69,6 +69,7 @@ const rules = { workerName: [{ required: true, message: '请输入姓名', trigger: 'blur' }], sex: [{ required: true, message: '请选择性别', trigger: 'change' }], phone: [ + { required: true, message: '请输入电话', trigger: 'blur' }, { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', diff --git a/src/views/basicManage/personManage/config.js b/src/views/basicManage/personManage/config.js index 14862c3..1459842 100644 --- a/src/views/basicManage/personManage/config.js +++ b/src/views/basicManage/personManage/config.js @@ -3,41 +3,33 @@ import CryptoUtil from "../../../api/crypto.js"; // 根据下拉数据构建搜索表单配置 // 注意:这里不直接发请求,只依赖调用方传入的 options,避免在模块顶层使用组合式 API -export const buildFormColumns = ( - positionOptions = [], - natureOptions = [], - categoryOptions = [], -) => [ - { - type: 'input', - prop: 'name', - placeholder: '请输入姓名', - }, - { - type: 'select', - prop: 'sex', - placeholder: '请选择性别', - options: [ - { - label: '男', - value: 1, - }, - { - label: '女', - value: 0, - }, - ], - }, - { - type: 'select', - prop: 'positionId', - placeholder: '请选择部门', - options: positionOptions.map((item) => ({ - label: item.value, - value: item.id, - })), - }, - ] +export const buildFormColumns = [ + { + type: 'input', + prop: 'orgName', + placeholder: '请输入部门名称', + }, + { + type: 'input', + prop: 'workerName', + placeholder: '请输入姓名', + }, + { + type: 'select', + prop: 'sex', + placeholder: '请选择性别', + options: [ + { + label: '男', + value: 1, + }, + { + label: '女', + value: 0, + }, + ], + }, +] export const tableColumns = [ { diff --git a/src/views/basicManage/personManage/index.vue b/src/views/basicManage/personManage/index.vue index fab4fe0..2714dad 100644 --- a/src/views/basicManage/personManage/index.vue +++ b/src/views/basicManage/personManage/index.vue @@ -3,7 +3,7 @@ @@ -211,6 +210,7 @@ import { import ComButton from '@/components/ComButton/index.vue' import ComDialog from '@/components/ComDialog/index.vue' import PersonPicker from '@/components/PersonPicker/index.vue' +import { bus, BUS_EVENTS } from '@/utils/bus' const route = useRoute() const router = useRouter() @@ -373,6 +373,8 @@ const onSubmit = async () => { const result = await API(params) if (result.code === 200) { proxy.$modal.msgSuccess(mode.value === 'edit' ? '编辑成功' : '新增成功') + // 通知父页面刷新列表 + bus.emit(BUS_EVENTS.REFRESH_LOOP_SEND_LIST) onBack() } } catch (error) { @@ -563,7 +565,12 @@ onMounted(() => { } .page-footer { - margin-top: 12px; + position: sticky; + bottom: 10px; + background: #fff; + z-index: 10; + border-top: 1px solid #e4e7ed; + padding-top: 15px; display: flex; justify-content: flex-end; gap: 12px; diff --git a/src/views/sMsSendManage/loopSend/index.vue b/src/views/sMsSendManage/loopSend/index.vue index ffd94e9..a9ff56e 100644 --- a/src/views/sMsSendManage/loopSend/index.vue +++ b/src/views/sMsSendManage/loopSend/index.vue @@ -45,7 +45,7 @@ diff --git a/src/views/system/dept/index.vue b/src/views/system/dept/index.vue index c82e24d..5b1d77e 100644 --- a/src/views/system/dept/index.vue +++ b/src/views/system/dept/index.vue @@ -1,282 +1,361 @@