diff --git a/src/components/admin/PermissionPathSelector.vue b/src/components/admin/PermissionPathSelector.vue new file mode 100644 index 0000000..c5bcaf7 --- /dev/null +++ b/src/components/admin/PermissionPathSelector.vue @@ -0,0 +1,360 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 搜索 + + + 重置 + + + + + + + + + + + + + {{ row.method }} + + - + + + + + + + + + + + + + + + + + + 已选择: + + {{ selectedPermission.method }} + + {{ selectedPermission.path }} + - {{ selectedPermission.name }} + + + + + + + + 取消 + + 确认选择 + + + + + + + + diff --git a/src/views/system/PermissionAdmin.vue b/src/views/system/PermissionAdmin.vue index b489b16..9f5f473 100644 --- a/src/views/system/PermissionAdmin.vue +++ b/src/views/system/PermissionAdmin.vue @@ -24,9 +24,15 @@ - - - + + + {{ getQueryGroupName() }} + + + + {{ queryParams.admin_group_id ? '重新选择' : '选择管理员组' }} + + @@ -115,14 +121,34 @@ - - + + + + + + + + + + 重置 - - + - 取消 + 取消 确定选择 - + --> 如果是 user 则填写 user_id,如果是 group 则填写 admin_group_id - - - {{ getFormUserName() }} - - - - {{ permissionForm.user_id ? '重新选择' : '选择用户' }} + + + + + + + + + + 清除 - - - + + + + + + + + + + 清除 + + - - + - - - - {{ item.method }} - {{ item.path }} - - {{ item.note || item.name || `ID: ${item.id}` }} - - - - 刷新 + + + + + + + + 清除 + 共 {{ permissionOptions.length }} 个路径权限可选 @@ -283,6 +343,9 @@ import { ref, reactive, onMounted } from 'vue' import { ElMessage, ElMessageBox } from 'element-plus' import { Plus, Search, Refresh, User } from '@element-plus/icons-vue' +import UserListSelector from '@/components/admin/UserListSelector.vue' +import UserGroupSelector from '@/components/admin/UserGroupSelector.vue' +import PermissionPathSelector from '@/components/admin/PermissionPathSelector.vue' import { getPermissionListByAdmin, addPermissionAdmin, @@ -297,6 +360,8 @@ import { formatDate ,timeToTimestamp} from '@/utils/tool' const selectorType = ref('query') const userSelectorVisible = ref(false) +const groupSelectorVisible = ref(false) +const permissionSelectorVisible = ref(false) const userSelectorList = ref([]) const userSelectorTotal = ref(0) const userSearchParams = reactive({ @@ -307,6 +372,8 @@ const userSearchParams = reactive({ const selectedUserTemp = ref(null) const userSelectorLoading = ref(false) const UserOptions = ref([]) +const GroupOptions = ref([]) +const selectedPermission = ref(null) // 查询参数 const queryParams = reactive({ owner_type: '', @@ -324,16 +391,122 @@ const getQueryUserName = () => { const user = UserOptions.value.find(u => u.UserId === queryParams.user_id) return user ? `${user.UserName} (ID: ${user.UserId})` : `用户ID: ${queryParams.user_id}` } +// 清除查询管理员组 +const clearQueryGroup = () => { + queryParams.admin_group_id = undefined +} +// 获取查询管理员组名称 +const getQueryGroupName = () => { + const group = GroupOptions.value.find(g => g.id === queryParams.admin_group_id) || + adminGroupOptions.value.find(g => g.id === queryParams.admin_group_id) + return group ? `${group.name} (ID: ${group.id})` : `管理员组ID: ${queryParams.admin_group_id}` +} +// 打开查询管理员组选择器 +const openQueryGroupSelector = () => { + selectorType.value = 'query' + groupSelectorVisible.value = true +} // 表单:清除用户 const clearFormUser = () => { permissionForm.user_id = undefined } // 表单:获取显示名称 const getFormUserName = () => { + if (!permissionForm.user_id) return '' const user = UserOptions.value.find(u => u.UserId === permissionForm.user_id) return user ? `${user.UserName} (ID: ${user.UserId})` : `用户ID: ${permissionForm.user_id}` } -// 确认用户选择 + +// 表单:获取管理员组显示名称 +const getFormGroupName = () => { + if (!permissionForm.admin_group_id) return '' + const group = GroupOptions.value.find(g => g.id === permissionForm.admin_group_id) + return group ? `${group.name} (ID: ${group.id})` : `管理员组ID: ${permissionForm.admin_group_id}` +} + +// 表单:获取路径权限显示名称 +const getFormPermissionName = () => { + if (!permissionForm.permission_id) return '' + if (selectedPermission.value && selectedPermission.value.id === permissionForm.permission_id) { + const p = selectedPermission.value + return `${p.method || ''} ${p.path}${p.name ? ' - ' + p.name : ''}` + } + const perm = permissionOptions.value.find(p => p.id === permissionForm.permission_id) + return perm ? `${perm.method || ''} ${perm.path}${perm.name ? ' - ' + perm.name : ''}` : `权限ID: ${permissionForm.permission_id}` +} + +// 清除表单管理员组 +const clearFormGroup = () => { + permissionForm.admin_group_id = undefined +} + +// 清除表单路径权限 +const clearFormPermission = () => { + permissionForm.permission_id = undefined + selectedPermission.value = null +} + +// 打开管理员组选择器 +const openFormGroupSelector = () => { + selectorType.value = 'form' + groupSelectorVisible.value = true +} + +// 打开路径权限选择器 +const openPermissionSelector = () => { + permissionSelectorVisible.value = true +} + +// 管理员组选择确认 +const handleGroupSelectorConfirm = (group) => { + if (group) { + const groupId = group.id || group.Id + const groupName = group.name || group.Name + + if (selectorType.value === 'query') { + queryParams.admin_group_id = groupId + } else { + permissionForm.admin_group_id = groupId + } + + if (!GroupOptions.value.find(g => g.id === groupId)) { + GroupOptions.value.push({ id: groupId, name: groupName }) + } + } + groupSelectorVisible.value = false +} + +// 路径权限选择确认 +const handlePermissionSelectorConfirm = (permission) => { + if (permission) { + permissionForm.permission_id = permission.id + selectedPermission.value = permission + } + permissionSelectorVisible.value = false +} +// UserListSelector 组件确认回调 +const handleUserSelectorConfirm = (user) => { + if (user) { + const userId = user.user_id || user.UserId + const userName = user.user_name || user.UserName + + if (selectorType.value === 'query') { + queryParams.user_id = userId + // 添加到 UserOptions 用于显示名称 + if (!UserOptions.value.find(u => u.UserId === userId)) { + UserOptions.value.push({ UserId: userId, UserName: userName }) + } + } else if (selectorType.value === 'form') { + permissionForm.user_id = userId + if (!UserOptions.value.find(u => u.UserId === userId)) { + UserOptions.value.push({ UserId: userId, UserName: userName }) + } + } + } + userSelectorVisible.value = false +} + +// 确认用户选择(旧方法,保留兼容) const confirmUserSelection = () => { if (!selectedUserTemp.value) { ElMessage.warning('请选择一个用户') @@ -684,7 +857,7 @@ const fetchUserList = async () => { try { const res = await getUserList({ page: 1, - count: 10000, + count: 10, key: '' }) if (res.data.code === 200) { @@ -700,7 +873,7 @@ const fetchAdminGroupList = async () => { try { const res = await getAdminGroupList({ page: 1, - count: 1000 + count: 10 }) if (res.data.code === 200) { adminGroupOptions.value = res.data.data?.data || [] @@ -716,7 +889,7 @@ const fetchPermissionList = async () => { try { const res = await getPermissionList({ page: 1, - count: 10000 + count: 10 }) if (res.data.code === 200) { permissionOptions.value = res.data.data?.list || [] @@ -837,4 +1010,34 @@ onMounted(() => { :deep(.el-card__body) { padding: 0; } + +/* 推介人选择器样式 - 与UserList.vue保持一致 */ +.recommend-user-selector { + display: flex; + align-items: center; + gap: 8px; + width: 100%; +} + +.recommend-user-selector .el-input { + flex: 1; +} + +.recommend-user-selector .clear-btn { + flex-shrink: 0; +} + +.selector-inline { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; +} + +.user_selector-inline { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; +} diff --git a/src/views/system/SettingManage.vue b/src/views/system/SettingManage.vue index 15c682b..3499c0d 100644 --- a/src/views/system/SettingManage.vue +++ b/src/views/system/SettingManage.vue @@ -437,7 +437,7 @@ const fetchGroupList = async () => { const fetchAllGroupList = async () => { try { - const res = await getSettingGroupList({ page: 1, count: 1000 }) + const res = await getSettingGroupList({ page: 1, count: 10 }) if (res.data.code === 200) { allGroupList.value = res.data.data.data || [] } @@ -635,8 +635,10 @@ const handleTypeChange = (type) => { } } -const handleAddSetting = () => { +const handleAddSetting = async () => { settingDialogTitle.value = '新增配置' + // 刷新配置组列表以确保数据最新 + await fetchAllGroupList() Object.assign(settingForm, { id: undefined, name: '', @@ -780,6 +782,7 @@ watch(activeTab, (newVal) => { if (newVal === 'group') { fetchGroupList() } else if (newVal === 'setting') { + fetchAllGroupList() // 切换到配置管理时刷新配置组列表用于下拉筛选 fetchSettingList() } }) diff --git a/src/views/ticket/TicketDetail.vue b/src/views/ticket/TicketDetail.vue index c66579f..c16178a 100644 --- a/src/views/ticket/TicketDetail.vue +++ b/src/views/ticket/TicketDetail.vue @@ -1322,4 +1322,202 @@ onBeforeUnmount(() => { font-size: 12px; margin-top: 4px; } + +/* 平板尺寸响应式样式 */ +@media (max-width: 1024px) and (min-width: 769px) { + .page-header { + padding: 12px 16px; + flex-wrap: wrap; + gap: 12px; + } + + .header-left { + flex-wrap: wrap; + gap: 8px; + } + + .ticket-title { + max-width: 200px; + } + + .header-right { + flex-wrap: wrap; + gap: 8px; + } + + .message-content { + max-width: 70%; + } + + .quick-replies { + flex-wrap: wrap; + } + + .input-area { + gap: 10px; + } +} + +/* 移动端响应式样式 */ +@media (max-width: 768px) { + .ticket-detail-page { + padding: 0; + } + + .page-header { + flex-direction: column; + align-items: flex-start; + gap: 12px; + padding: 12px; + height: auto; + } + + .header-left { + flex-wrap: wrap; + gap: 8px; + width: 100%; + } + + .header-left .el-button { + padding: 8px 12px; + font-size: 13px; + } + + .user-info { + order: 3; + width: 100%; + margin-top: 8px; + } + + .ticket-title { + order: 4; + width: 100%; + max-width: none; + margin-top: 8px; + white-space: normal; + line-height: 1.4; + } + + .header-right { + width: 100%; + justify-content: space-between; + flex-wrap: wrap; + gap: 8px; + } + + .header-right .ticket-id { + font-size: 12px; + } + + .header-right .el-select { + width: 100px !important; + } + + .header-right .el-button { + font-size: 12px; + padding: 6px 10px; + } + + .chat-container { + min-height: 300px; + } + + .chat-messages { + padding: 12px; + } + + .message-content { + max-width: 80%; + } + + .message-text { + padding: 10px 12px; + font-size: 14px; + } + + .message-image { + max-width: 150px; + max-height: 150px; + } + + .reply-container { + padding: 12px; + } + + .quick-replies { + gap: 6px; + } + + .quick-replies .el-button { + font-size: 12px; + padding: 6px 10px; + } + + .input-actions { + flex-direction: column; + gap: 12px; + } + + .left-actions { + width: 100%; + justify-content: flex-start; + } + + .input-actions .el-button--primary { + width: 100%; + } + + .hint-text { + display: none; + } + + /* 用户信息弹窗 */ + .user-popover { + width: 100% !important; + } + + .popover-header { + flex-direction: column; + text-align: center; + } + + .popover-info { + margin-top: 8px; + } + + /* 编辑消息弹窗 */ + :deep(.el-dialog) { + width: 90% !important; + margin: 5vh auto !important; + } + + .edit-images-container { + gap: 8px; + } + + .edit-preview-item, + .add-image-btn { + width: 80px; + height: 80px; + } +} + +@media (max-width: 480px) { + .header-left .el-button span { + display: none; + } + + .header-left .el-button .el-icon { + margin: 0; + } + + .message-content { + max-width: 85%; + } + + .preview-item { + width: 60px; + height: 60px; + } +} diff --git a/src/views/ticket/TicketList.vue b/src/views/ticket/TicketList.vue index d918b57..936bb2a 100644 --- a/src/views/ticket/TicketList.vue +++ b/src/views/ticket/TicketList.vue @@ -63,13 +63,14 @@ - + @@ -107,6 +108,41 @@ + + + + + #{{ ticket.id }} + + {{ getStatusText(ticket.status) }} + + + + {{ ticket.username?.charAt(0) }} + {{ ticket.username }} + + {{ ticket.title }} + + + + + { :deep(.el-table tr) { cursor: pointer; } + +/* 移动端卡片列表 */ +.mobile-ticket-list { + display: none; + flex-direction: column; + gap: 12px; + padding: 12px; + overflow-y: auto; + flex: 1; +} + +.ticket-card { + background: #fff; + border: 1px solid #ebeef5; + border-radius: 8px; + padding: 12px; + cursor: pointer; + transition: all 0.2s; +} + +.ticket-card:active { + background: #f5f7fa; +} + +.ticket-card-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +.ticket-card-id { + font-size: 12px; + color: #909399; +} + +.ticket-card-user { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; +} + +.ticket-card-username { + font-size: 14px; + font-weight: 500; + color: #303133; +} + +.ticket-card-title { + font-size: 14px; + color: #303133; + margin-bottom: 8px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.ticket-card-footer { + display: flex; + justify-content: space-between; + align-items: center; +} + +.ticket-card-time { + font-size: 12px; + color: #909399; +} + +.ticket-card-actions { + display: flex; + gap: 8px; +} + +/* 大屏平板尺寸响应式样式 (1020px - 1280px) */ +@media (max-width: 1280px) and (min-width: 1021px) { + .toolbar { + padding: 12px 16px; + gap: 12px; + } + + .toolbar-right { + flex-wrap: wrap; + gap: 8px; + } + + .toolbar-right .el-select { + width: 120px !important; + } + + .toolbar-right .el-input { + min-width: 160px; + } + + /* 表格列宽调整 */ + :deep(.el-table) { + font-size: 13px; + } + + :deep(.el-table .el-table__cell) { + padding: 10px 8px; + } +} + +/* 平板尺寸响应式样式 (769px - 1020px) */ +@media (max-width: 1020px) and (min-width: 769px) { + .toolbar { + flex-direction: column; + height: auto; + padding: 12px 16px; + gap: 12px; + align-items: stretch; + } + + .status-tabs { + width: 100%; + justify-content: flex-start; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + + .status-tabs::-webkit-scrollbar { + height: 4px; + } + + .status-tabs::-webkit-scrollbar-thumb { + background: #dcdfe6; + border-radius: 2px; + } + + .toolbar-right { + width: 100%; + flex-wrap: wrap; + gap: 8px; + } + + .toolbar-right .el-select { + width: 120px !important; + } + + .toolbar-right .el-input { + flex: 1; + min-width: 150px; + } + + /* 表格列宽调整 */ + :deep(.el-table) { + font-size: 13px; + } + + :deep(.el-table .el-table__cell) { + padding: 8px 6px; + } +} + +/* 移动端响应式样式 */ +@media (max-width: 768px) { + .ticket-list-page { + height: auto; + min-height: calc(100vh - 60px); + } + + .toolbar { + flex-direction: column; + height: auto; + padding: 12px; + gap: 12px; + } + + .status-tabs { + width: 100%; + overflow-x: auto; + padding-bottom: 4px; + -webkit-overflow-scrolling: touch; + } + + .status-tabs::-webkit-scrollbar { + display: none; + } + + .tab-item { + flex-shrink: 0; + padding: 8px 12px; + font-size: 13px; + } + + .toolbar-right { + width: 100%; + flex-wrap: wrap; + gap: 8px; + } + + .toolbar-right .el-select, + .toolbar-right .el-input { + flex: 1; + min-width: 120px; + } + + .toolbar-right .el-button { + flex-shrink: 0; + } + + /* 隐藏PC端表格,显示移动端卡片 */ + :deep(.el-table) { + display: none !important; + } + + .mobile-ticket-list { + display: flex; + } + + .pagination-wrapper { + padding: 12px; + justify-content: center; + } + + .pagination-wrapper :deep(.el-pagination) { + flex-wrap: wrap; + justify-content: center; + gap: 8px; + } + + .pagination-wrapper :deep(.el-pagination__sizes), + .pagination-wrapper :deep(.el-pagination__jump) { + display: none; + } + + /* 用户选择弹窗移动端适配 */ + :deep(.el-dialog) { + width: 90% !important; + margin: 5vh auto !important; + } +} + +@media (max-width: 480px) { + .tab-item { + padding: 6px 10px; + font-size: 12px; + } + + .tab-item .count { + display: none; + } +} diff --git a/问题.MD b/问题.MD index ea370ee..b921a48 100644 --- a/问题.MD +++ b/问题.MD @@ -1,16 +1,6 @@ ✅已完成、⚠️部分完成、❌未完成这样显示 -----------------------------------------------------------------------------------------------需要解决 -1.点击仪表盘的最近工单中的其中一项跳转到工单详情需要请求对应接口数据进行赋值/api/v1/admin/work_order/detail这个接口,传递work_id参数,在src/api/ticket.js文件下的getTicketDetail函数 - -### TODO List -- [x] ✅ TicketDetail.vue兼容work_id参数(原来只支持id参数) -- [x] ✅ fetchTicketDetail函数使用 route.query.id || route.query.work_id -- [x] ✅ sendMessage函数使用 route.query.id || route.query.work_id -- [x] ✅ handleStatusChange函数使用 route.query.id || route.query.work_id -- [x] ✅ handleComplete函数使用 route.query.id || route.query.work_id -- [x] ✅ 定时刷新和watch监听兼容work_id参数 - -----------------------------------------------------------------------------------------------需要解决 规则限制: @@ -23,6 +13,7 @@ - 提交时转换为秒级时间戳 4.后续每次写的页面,组件都需要兼容移动端 5.只要是关于选择用户,文件,优惠卷,代金卷的都使用组件 +6.只要是弹窗需要使用选择组件的都参照用户列表的编辑用户的推介人的选择样式在弹窗中