Huanyi 3 недель назад
Родитель
Сommit
9f927db40c

+ 3 - 14
src/api/service/list/index.ts

@@ -63,23 +63,12 @@ export const delService = (id: string | number | Array<string | number>) => {
 };
 
 /**
- * 获取服务项目列表
- * @Author Huanyi
- */
-export const listOnStore = (): AxiosPromise<ServiceOnStoreVo[]> => {
-  return request({
-    url: '/service/list/listOnStore',
-    method: 'GET'
-  });
-};
-
-/**
- * 查询下单页的服务列表
+ * 获取所有服务项目
  * @returns {*}
  */
-export const listServiceOnOrder = (): AxiosPromise<ServiceOrderVO[]> => {
+export const listAllService = (): AxiosPromise<ServiceVO[]> => {
   return request({
-    url: '/service/list/listOnOrder',
+    url: '/service/list/listAll',
     method: 'get'
   });
 };

+ 2 - 2
src/components/CustomerDetailDrawer/index.vue

@@ -125,7 +125,7 @@ import { listPetByUser } from '@/api/archieves/pet';
 import { listAllChangeLog } from '@/api/archieves/changeLog';
 import { listOnStore } from '@/api/system/areaStation';
 import { listSubOrderOnCustomer } from '@/api/order/subOrder/index';
-import { listServiceOnOrder } from '@/api/service/list/index';
+import { listAllService } from '@/api/service/list/index';
 
 const props = defineProps({
   visible: {
@@ -161,7 +161,7 @@ const detailActiveTab = ref('info');
 const allNodes = ref([]);
 
 const getServiceList = () => {
-  listServiceOnOrder().then((res) => {
+  listAllService().then((res) => {
     serviceOptions.value = res.data || [];
   });
 };

+ 2 - 2
src/components/PetDetailDrawer/index.vue

@@ -120,7 +120,7 @@ import { ref, computed, watch, onMounted, getCurrentInstance, toRefs } from 'vue
 import { getPet, updatePet } from '@/api/archieves/pet'
 import { listAllChangeLog } from '@/api/archieves/changeLog'
 import { listSubOrderOnPet } from '@/api/order/subOrder/index'
-import { listServiceOnOrder } from '@/api/service/list/index'
+import { listAllService } from '@/api/service/list/index'
 import { ElMessage } from 'element-plus'
 
 const props = defineProps({
@@ -159,7 +159,7 @@ const remarkDialogVisible = ref(false)
 const remarkContent = ref('')
 
 const getServiceList = () => {
-  listServiceOnOrder().then((res) => {
+  listAllService().then((res) => {
     serviceOptions.value = res.data || []
   })
 }

+ 15 - 3
src/views/archieves/customer/index.vue

@@ -115,6 +115,14 @@
       @pet-delete="handlePetDelete"
     />
 
+    <!-- Pet Detail Drawer -->
+    <PetDetailDrawer
+      v-model:visible="petDrawerVisible"
+      :pet-id="currentPet.id"
+      editable
+      @remark-saved="getList"
+    />
+
     <!-- Add/Edit User Dialog -->
     <el-dialog v-model="dialogVisible" :title="isEdit ? '编辑用户' : '新增用户'" width="700px" destroy-on-close>
       <el-form :model="form" label-width="90px" class="user-form">
@@ -384,6 +392,7 @@ import { listAllChangeLog } from '@/api/archieves/changeLog'
 import { listOnStore } from '@/api/system/areaStation'
 import { listOnStore as listBrandOnStore } from '@/api/system/tenant'
 import CustomerDetailDrawer from '@/components/CustomerDetailDrawer/index.vue'
+import PetDetailDrawer from '@/components/PetDetailDrawer/index.vue'
 import { regionData, codeToText } from 'element-china-area-data'
 import PageSelect from '@/components/PageSelect/index.vue'
 
@@ -443,6 +452,7 @@ const searchForm = reactive({
 
 const dialogVisible = ref(false)
 const drawerVisible = ref(false)
+const petDrawerVisible = ref(false)
 const remarkDialogVisible = ref(false)
 const petDialogVisible = ref(false)
 const isEdit = ref(false)
@@ -451,6 +461,7 @@ const petDialogActiveTab = ref('basic')
 
 const selectedTagIds = ref([])
 const currentUser = ref({})
+const currentPet = ref({})
 const currentPets = ref([])
 const tableData = ref([])
 
@@ -857,7 +868,8 @@ const openAddPet = () => {
 }
 
 const handlePetDetail = (row) => {
-  ElMessage.info(`查看宠物 [${row.name}] 详情`)
+  currentPet.value = row
+  petDrawerVisible.value = true
 }
 
 const handlePetEdit = (row) => {
@@ -880,8 +892,8 @@ const handlePetEdit = (row) => {
 }
 
 const handlePetRemark = (row) => {
-  remarkForm.content = `[宠物:${row.name}] `
-  remarkDialogVisible.value = true
+  currentPet.value = row
+  petDrawerVisible.value = true
 }
 
 const handlePetDelete = (row) => {

+ 2 - 2
src/views/fulfiller/level/index.vue

@@ -343,10 +343,10 @@ const saveLevel = async () => {
 
   // 图片处理:ImageUpload 返回的是逗号分隔的 ID 字符串,后端需要数字
   if (submitData.icon && typeof submitData.icon === 'string') {
-    submitData.icon = parseInt(submitData.icon.split(',')[0]);
+    submitData.icon = submitData.icon.split(',')[0];
   }
   if (submitData.background && typeof submitData.background === 'string') {
-    submitData.background = parseInt(submitData.background.split(',')[0]);
+    submitData.background = submitData.background.split(',')[0];
   }
 
   try {

+ 4 - 4
src/views/fulfiller/pool/index.vue

@@ -420,7 +420,7 @@
                 </el-table-column>
                 <el-table-column prop="count" label="违规次数" width="100" />
                 <el-table-column prop="reason" label="违规原因" show-overflow-tooltip />
-                <el-table-column prop="operatorName" label="操作人" width="100" />
+<!--                <el-table-column prop="operatorName" label="操作人" width="100" />-->
               </el-table>
             </div>
           </el-tab-pane>
@@ -432,7 +432,7 @@
                 <el-table-column prop="createTime" label="投诉时间" width="180" />
                 <el-table-column prop="orderCode" label="订单号" width="160" show-overflow-tooltip />
                 <el-table-column prop="reason" label="投诉原因" show-overflow-tooltip />
-                <el-table-column prop="createBy" label="操作人" width="100" />
+<!--                <el-table-column prop="createBy" label="操作人" width="100" />-->
               </el-table>
               <div style="margin-top: 20px; display: flex; justify-content: flex-end;">
                 <el-pagination
@@ -705,7 +705,7 @@ import { addViolation, listViolationByFulfiller } from '@/api/fulfiller/violatio
 import type { FlfViolationVO } from '@/api/fulfiller/violation/types'
 import { pageComplaintByFulfiller } from '@/api/fulfiller/complaint'
 import { listSubOrderOnFulfiller } from '@/api/order/subOrder/index'
-import { listOnStore as listServiceOnStore } from '@/api/service/list/index'
+import { listAllService } from '@/api/service/list/index'
 import type {
   FlfFulfillerVO, FlfFulfillerForm, FlfFulfillerQuery,
   FlfRewardForm, FlfAdjustPointsForm, FlfAdjustBalanceForm,
@@ -859,7 +859,7 @@ const getRewardBizTypeName = (type: string) => {
 /** 加载服务项目列表用于名称映射 */
 const loadServiceOptions = async () => {
   try {
-    const res = await listServiceOnStore()
+    const res = await listAllService()
     serviceOptions.value = res.data || []
   } catch { /* ignore */ }
 }

+ 2 - 2
src/views/order/dispatch/components/CustomerDetailDrawer.vue

@@ -111,7 +111,7 @@ import { listPetByUser } from '@/api/archieves/pet'
 import { listAllChangeLog } from '@/api/archieves/changeLog'
 import { listOnStore } from '@/api/system/areaStation'
 import { listSubOrderOnCustomer } from '@/api/order/subOrder/index'
-import { listServiceOnOrder } from '@/api/service/list/index'
+import { listAllService } from '@/api/service/list/index'
 
 const props = defineProps({
   visible: {
@@ -146,7 +146,7 @@ const historyOrders = ref([])
 const serviceOptions = ref([])
 
 const getServiceList = () => {
-  listServiceOnOrder().then((res) => {
+  listAllService().then((res) => {
     serviceOptions.value = res.data || []
   })
 }

+ 2 - 2
src/views/order/dispatch/components/DispatchDialog.vue

@@ -159,7 +159,7 @@ import { ElMessage } from 'element-plus'
 import { pageFulfillerOnOrder } from '@/api/fulfiller/pool'
 import { listAllTag } from '@/api/fulfiller/tag'
 import { getSubOrderInfo } from '@/api/order/subOrder/index'
-import { listServiceOnOrder } from '@/api/service/list/index'
+import { listAllService } from '@/api/service/list/index'
 import CustomerDetailDrawer from '@/components/CustomerDetailDrawer/index.vue'
 import PetDetailDrawer from '@/components/PetDetailDrawer/index.vue'
 
@@ -216,7 +216,7 @@ const serviceOptions = ref([])
 const loadServiceOptions = async () => {
     if (serviceOptions.value.length > 0) return
     try {
-        const res = await listServiceOnOrder()
+        const res = await listAllService()
         serviceOptions.value = res?.data || []
     } catch { /* ignore */ }
 }

+ 2 - 2
src/views/order/dispatch/index.vue

@@ -58,7 +58,7 @@
 <script setup>
 import { ref, computed, reactive, onMounted, watch } from 'vue';
 import { ElMessage } from 'element-plus';
-import { listServiceOnOrder } from '@/api/service/list/index'
+import { listAllService } from '@/api/service/list/index'
 import { listOnStore as listAreaStationOnStore } from '@/api/system/areaStation'
 import { listStoreOnDispatch } from '@/api/system/store/index'
 import { listSubOrderOnDispatch, dispatchSubOrder } from '@/api/order/subOrder/index';
@@ -102,7 +102,7 @@ const riderStats = computed(() => ({
 const serviceOnOrderList = ref([])
 
 const getServiceList = () => {
-  listServiceOnOrder().then(res => {
+  listAllService().then(res => {
     serviceOnOrderList.value = res?.data || []
   }).catch(() => {
     serviceOnOrderList.value = []

+ 2 - 2
src/views/order/orderList/components/CustomerDetailDrawer.vue

@@ -111,7 +111,7 @@ import { listPetByUser } from '@/api/archieves/pet'
 import { listAllChangeLog } from '@/api/archieves/changeLog'
 import { listOnStore } from '@/api/system/areaStation'
 import { listSubOrderOnCustomer } from '@/api/order/subOrder/index'
-import { listServiceOnOrder } from '@/api/service/list/index'
+import { listAllService } from '@/api/service/list/index'
 
 const props = defineProps({
   visible: {
@@ -146,7 +146,7 @@ const historyOrders = ref([])
 const serviceOptions = ref([])
 
 const getServiceList = () => {
-  listServiceOnOrder().then((res) => {
+  listAllService().then((res) => {
     serviceOptions.value = res.data || []
   })
 }

+ 2 - 2
src/views/order/orderList/components/DispatchDialog.vue

@@ -159,7 +159,7 @@ import { ElMessage } from 'element-plus'
 import { pageFulfillerOnOrder } from '@/api/fulfiller/pool'
 import { listAllTag } from '@/api/fulfiller/tag'
 import { getSubOrderInfo } from '@/api/order/subOrder/index'
-import { listServiceOnOrder } from '@/api/service/list/index'
+import { listAllService } from '@/api/service/list/index'
 import CustomerDetailDrawer from '@/components/CustomerDetailDrawer/index.vue'
 import PetDetailDrawer from '@/components/PetDetailDrawer/index.vue'
 
@@ -216,7 +216,7 @@ const serviceOptions = ref([])
 const loadServiceOptions = async () => {
     if (serviceOptions.value.length > 0) return
     try {
-        const res = await listServiceOnOrder()
+        const res = await listAllService()
         serviceOptions.value = res?.data || []
     } catch { /* ignore */ }
 }

+ 2 - 2
src/views/order/orderList/index.vue

@@ -193,7 +193,7 @@ import DispatchDialog from './components/DispatchDialog.vue';
 import CareSummaryDrawer from './components/CareSummaryDrawer.vue';
 import RewardDialog from './components/RewardDialog.vue';
 import RemarkDialog from './components/RemarkDialog.vue';
-import { listServiceOnOrder } from '@/api/service/list/index';
+import { listAllService } from '@/api/service/list/index';
 import { listSubOrder } from '@/api/order/subOrder/index';
 import { dispatchSubOrder } from '@/api/order/subOrder/index';
 import { getSubOrderInfo } from '@/api/order/subOrder/index';
@@ -235,7 +235,7 @@ onMounted(() => {
 });
 
 const getServiceList = () => {
-  listServiceOnOrder().then((res) => {
+  listAllService().then((res) => {
     serviceOptions.value = res.data || [];
   });
 };

+ 4 - 3
src/views/order/purchase/components/FeedingForm.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="business-form">
     <div style="margin-bottom: 20px;">
-      <div class="section-label">上门服务地址</div>
+      <div class="section-label required">上门服务地址</div>
       <el-row :gutter="10">
         <el-col :span="8">
           <el-cascader v-model="feedingData.region" :options="pcaOptions" placeholder="省/市/区" style="width: 100%" />
@@ -13,7 +13,7 @@
     </div>
 
     <div style="margin-bottom: 20px;">
-      <div class="section-label" style="display:flex; align-items:center; margin-bottom:10px;">
+      <div class="section-label required" style="display:flex; align-items:center; margin-bottom:10px;">
         预约服务时间
         <el-tag type="info" size="small" style="margin-left:10px;">共 {{ feedingData.appointments.length }} 次</el-tag>
       </div>
@@ -30,7 +30,7 @@
         <el-date-picker
           v-model="item.endTime"
           type="datetime"
-          placeholder="结束时间 (可选)"
+          placeholder="结束时间"
           style="width: 200px; margin-right: 15px;"
           format="YYYY-MM-DD HH:mm"
         />
@@ -76,4 +76,5 @@ const removeAppointment = (index) => {
 .business-form { padding-top: 5px; }
 .remark-section { background: #fdfdfd; border: 1px dashed #dcdfe6; padding: 15px; border-radius: 6px; margin-top: 20px; }
 .section-label { font-size: 13px; font-weight: bold; color: #606266; margin-bottom: 12px; }
+.section-label.required::before { content: '*'; color: #f56c6c; margin-right: 4px; }
 </style>

+ 12 - 8
src/views/order/purchase/components/TransportForm.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="business-form">
-    <el-form-item label="接送模式">
+    <el-form-item label="接送模式" required>
       <el-radio-group v-model="transportData.subType" size="large" @change="$emit('change', 'transport')">
         <el-radio-button label="round">往返接送</el-radio-button>
         <el-radio-button label="pick">单程接 (到店)</el-radio-button>
@@ -15,7 +15,7 @@
           <div class="seg-badge start">接</div>
           <div class="seg-content">
             <el-row :gutter="10" align="middle" class="address-row" style="margin-bottom: 10px;">
-              <el-col :span="2"><div class="addr-label">起点</div></el-col>
+              <el-col :span="2"><div class="addr-label required">起点</div></el-col>
               <el-col :span="6">
                 <el-cascader v-model="transportData.pickStartRegion" :options="pcaOptions" placeholder="省/市/区" style="width: 100%" />
               </el-col>
@@ -23,8 +23,8 @@
                 <el-input v-model="transportData.pickStartDetail" placeholder="详细地址 (街道/门牌号)" prefix-icon="Location" />
               </el-col>
             </el-row>
-            <el-row :gutter="10" align="middle" class="address-row">
-              <el-col :span="2"><div class="addr-label">终点</div></el-col>
+            <el-row :gutter="10" align="middle" class="address-row" style="margin-bottom: 20px;">
+              <el-col :span="2"><div class="addr-label required">终点</div></el-col>
               <el-col :span="6">
                 <el-cascader v-model="transportData.pickEndRegion" :options="pcaOptions" placeholder="省/市/区" style="width: 100%" />
               </el-col>
@@ -32,11 +32,11 @@
                 <el-input v-model="transportData.pickEndDetail" placeholder="详细地址 (街道/门牌号)" prefix-icon="Location" />
               </el-col>
             </el-row>
-            <el-row :gutter="10">
+            <el-row :gutter="10" class="required-field">
               <el-col :span="12"><el-input v-model="transportData.pickContact" placeholder="联系人" /></el-col>
               <el-col :span="12"><el-input v-model="transportData.pickPhone" placeholder="电话" /></el-col>
             </el-row>
-            <el-row :gutter="10">
+            <el-row :gutter="10" class="required-field">
               <el-col :span="24">
                 <el-date-picker v-model="transportData.pickTime" type="datetime" placeholder="选择接宠时间" style="width: 100%" />
               </el-col>
@@ -74,9 +74,9 @@
               <el-col :span="12"><el-input v-model="transportData.dropContact" placeholder="联系人" /></el-col>
               <el-col :span="12"><el-input v-model="transportData.dropPhone" placeholder="电话" /></el-col>
             </el-row>
-            <el-row :gutter="10">
+            <el-row :gutter="10" class="required-field">
               <el-col :span="24">
-                <el-date-picker v-model="transportData.dropTime" type="datetime" placeholder="预计送回时间 (可选)" style="width: 100%" />
+                <el-date-picker v-model="transportData.dropTime" type="datetime" placeholder="预计送回时间" style="width: 100%" />
               </el-col>
             </el-row>
           </div>
@@ -119,4 +119,8 @@ const emit = defineEmits(['change'])
   font-size: 14px;
   padding-right: 5px;
 }
+.addr-label.required::before { content: '*'; color: #f56c6c; margin-right: 4px; }
+.required-field { margin-bottom: 10px; }
+.required-field :deep(.el-input__wrapper) { box-shadow: 0 0 0 1px #f56c6c inset; }
+.required-field :deep(.el-input__wrapper):hover { box-shadow: 0 0 0 1px #f56c6c inset !important; }
 </style>

+ 4 - 3
src/views/order/purchase/components/WashingForm.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="business-form">
     <div style="margin-bottom: 20px;">
-      <div class="section-label">上门服务地址</div>
+      <div class="section-label required">上门服务地址</div>
       <el-row :gutter="10">
         <el-col :span="8">
           <el-cascader v-model="washingData.region" :options="pcaOptions" placeholder="省/市/区" style="width: 100%" />
@@ -13,7 +13,7 @@
     </div>
 
     <div style="margin-bottom: 20px;">
-      <div class="section-label" style="display:flex; align-items:center; margin-bottom:10px;">
+      <div class="section-label required" style="display:flex; align-items:center; margin-bottom:10px;">
         预约服务时间
         <el-tag type="info" size="small" style="margin-left:10px;">共 {{ washingData.appointments.length }} 次</el-tag>
       </div>
@@ -30,7 +30,7 @@
         <el-date-picker
           v-model="item.endTime"
           type="datetime"
-          placeholder="结束时间 (可选)"
+          placeholder="结束时间"
           style="width: 200px; margin-right: 15px;"
           format="YYYY-MM-DD HH:mm"
         />
@@ -74,4 +74,5 @@ const removeAppointment = (index) => {
 .business-form { padding-top: 5px; }
 .remark-section { background: #fdfdfd; border: 1px dashed #dcdfe6; padding: 15px; border-radius: 6px; margin-top: 20px; }
 .section-label { font-size: 13px; font-weight: bold; color: #606266; margin-bottom: 12px; }
+.section-label.required::before { content: '*'; color: #f56c6c; margin-right: 4px; }
 </style>

+ 61 - 16
src/views/order/purchase/index.vue

@@ -15,7 +15,7 @@
             <el-form label-position="top" class="base-form">
               <el-row :gutter="20">
                 <el-col :span="12">
-                  <el-form-item>
+                  <el-form-item required>
                     <template #label>
                       <div style="display:flex; align-items:center; height: 24px;">
                         <span>服务门店 (平台代下单)</span>
@@ -27,7 +27,7 @@
                   </el-form-item>
                 </el-col>
                 <el-col :span="12">
-                  <el-form-item>
+                  <el-form-item required>
                     <template #label>
                       <div
                         style="display:flex; justify-content:space-between; align-items:center; width:100%; height: 24px;">
@@ -44,7 +44,7 @@
                 </el-col>
               </el-row>
 
-              <el-form-item label="选择宠物" v-if="form.userId">
+              <el-form-item label="选择宠物" v-if="form.userId" required>
                 <div class="pet-select-row">
                   <div v-for="p in currentPets" :key="p.id" class="pet-card" :class="{ active: form.petId === p.id }"
                     @click="form.petId = p.id">
@@ -78,12 +78,7 @@
             :class="[getServiceType(item.name), { active: form.serviceId === item.id }]"
             @click="handleServiceChange(item)">
             <div class="icon-box">
-              <img
-                v-if="item.icon && (item.icon.startsWith('http') || item.icon.startsWith('//') || item.icon.startsWith('/profile'))"
-                :src="item.icon" class="service-icon-img" />
-              <el-icon v-else-if="item.icon">
-                <component :is="item.icon" />
-              </el-icon>
+              <img v-if="item.iconUrl" :src="item.iconUrl" class="service-icon-img" />
               <el-icon v-else>
                 <component :is="getServiceIcon(item.name)" />
               </el-icon>
@@ -207,7 +202,7 @@ import AddUserDialog from './components/AddUserDialog.vue'
 import AddPetDialog from './components/AddPetDialog.vue'
 import PageSelect from '@/components/PageSelect/index.vue'
 import { listStoreOnOrder } from '@/api/system/store'
-import { listServiceOnOrder } from '@/api/service/list'
+import { listAllService } from '@/api/service/list'
 import { listCustomerOnOrder } from '@/api/archieves/customer'
 import { listPetByUser } from '@/api/archieves/pet'
 import { regionData as pcaOptions } from 'element-china-area-data'
@@ -570,7 +565,61 @@ const resetForm = () => {
   currentPets.value = []
 }
 
+const validateForm = () => {
+  // 1. 基础信息校验
+  if (!form.merchantId) return '请选择服务门店';
+  if (!form.userId) return '请选择宠主用户';
+  if (!form.petId) return '请选择宠物';
+  if (!form.serviceId) return '请选择服务项目';
+
+  // 2. 业务详情校验
+  if (form.type === 'transport') {
+    const td = form.transport;
+    if (['round', 'pick'].includes(td.subType)) {
+      if (!td.pickStartRegion || td.pickStartRegion.length === 0) return '请选择接宠起点区域';
+      if (!td.pickStartDetail) return '请填写接宠起点详细地址';
+      if (!td.pickEndRegion || td.pickEndRegion.length === 0) return '请选择接宠终点区域';
+      if (!td.pickEndDetail) return '请填写接宠终点详细地址';
+      if (!td.pickContact) return '请填写接宠联系人';
+      if (!td.pickPhone) return '请填写接宠电话';
+      if (!td.pickTime) return '请选择接宠时间';
+    }
+    if (['round', 'drop'].includes(td.subType)) {
+      if (!td.dropStartRegion || td.dropStartRegion.length === 0) return '请选择送宠起点区域';
+      if (!td.dropStartDetail) return '请填写送宠起点详细地址';
+      if (!td.dropEndRegion || td.dropEndRegion.length === 0) return '请选择送宠终点区域';
+      if (!td.dropEndDetail) return '请填写送宠终点详细地址';
+      if (!td.dropContact) return '请填写送宠联系人';
+      if (!td.dropPhone) return '请填写送宠电话';
+      if (!td.dropTime) return '请选择预计送回时间';
+    }
+  } else if (form.type === 'feeding' || form.type === 'washing') {
+    const data = form[form.type];
+    if (!data.region || data.region.length === 0) return '请选择服务区域';
+    if (!data.addressDetail) return '请填写服务详细地址';
+    if (!data.appointments || data.appointments.length === 0) return '请至少添加一个预约时间';
+    
+    for (let i = 0; i < data.appointments.length; i++) {
+      if (!data.appointments[i].startTime) {
+        return `请选择第 ${i + 1} 个预约的开始时间`;
+      }
+      if (!data.appointments[i].endTime) {
+        return `请选择第 ${i + 1} 个预约的结束时间`;
+      }
+    }
+  }
+
+  return null;
+};
+
 const handleSubmit = async () => {
+  // 执行校验
+  const errorMsg = validateForm();
+  if (errorMsg) {
+    ElMessage.warning(errorMsg);
+    return;
+  }
+
   try {
     const storeObj = stores.value.find(s => s.id === form.merchantId)
     if (!storeObj) {
@@ -653,7 +702,7 @@ const handleSubmit = async () => {
       pet: form.petId,
       groupPurchasePackageName: form.groupBuyPackage || '',
       service: form.serviceId,
-      remark: "", // 表单目前暂无备注字段
+      remark: "", 
       tenantId: storeObj.tenantId || "",
       subOrders: subOrders
     }
@@ -662,10 +711,6 @@ const handleSubmit = async () => {
     if (res && res.code === 200) {
       ElMessage.success('下单成功')
       resetForm()
-    } else {
-      // 如果没有抛异常,走这里
-      ElMessage.success('下单成功')
-      resetForm()
     }
   } catch (error) {
     console.error('Create order error: ', error)
@@ -676,7 +721,7 @@ const handleSubmit = async () => {
 onMounted(() => {
   fetchStores()
   // fetchUsers() // 移除初始加载,改为输入后触发
-  listServiceOnOrder().then(res => {
+  listAllService().then(res => {
     allServices.value = res.data || []
   })
 })

+ 352 - 196
src/views/system/dict/index.vue

@@ -1,106 +1,111 @@
 <template>
-  <div class="p-2">
-    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
-      <div v-show="showSearch" class="mb-[10px]">
-        <el-card shadow="hover">
-          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
-            <el-form-item label="字典名称" prop="dictName">
-              <el-input v-model="queryParams.dictName" placeholder="请输入字典名称" clearable @keyup.enter="handleQuery" />
-            </el-form-item>
-            <el-form-item label="字典类型" prop="dictType">
-              <el-input v-model="queryParams.dictType" placeholder="请输入字典类型" clearable @keyup.enter="handleQuery" />
-            </el-form-item>
-            <el-form-item label="创建时间" style="width: 308px">
-              <el-date-picker
-                v-model="dateRange"
-                value-format="YYYY-MM-DD HH:mm:ss"
-                type="daterange"
-                range-separator="-"
-                start-placeholder="开始日期"
-                end-placeholder="结束日期"
-                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
-              ></el-date-picker>
-            </el-form-item>
-            <el-form-item>
-              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-            </el-form-item>
-          </el-form>
+  <div class="page-container">
+    <el-row :gutter="16" class="full-height">
+      <!-- 左侧字典类型 -->
+      <el-col :span="6" class="full-height">
+        <el-card shadow="never" class="type-card full-height">
+          <template #header>
+            <div class="card-header">
+              <span class="title">字典类型</span>
+              <el-button link type="primary" icon="Plus" @click="handleAddType()">新增</el-button>
+            </div>
+            <div class="search-box">
+              <el-input v-model="typeSearch" placeholder="搜索类型名称/编码" prefix-icon="Search" clearable />
+            </div>
+          </template>
+
+          <el-scrollbar>
+            <div v-for="item in filteredTypeList" :key="item.dictId" :class="['type-item', { active: activeType?.dictId === item.dictId }]" @click="handleTypeClick(item)">
+              <div class="type-info">
+                <div class="type-name">{{ item.dictName }}</div>
+                <div class="type-code">{{ item.dictType }}</div>
+              </div>
+              <div class="type-ops">
+                <el-button link type="primary" icon="Edit" @click.stop="handleUpdateType(item)"></el-button>
+                <el-button link type="danger" icon="Delete" @click.stop="handleDeleteType(item)"></el-button>
+              </div>
+            </div>
+          </el-scrollbar>
         </el-card>
-      </div>
-    </transition>
-    <el-card shadow="hover">
-      <template #header>
-        <el-row :gutter="10" class="mb8">
-          <el-col :span="1.5">
-            <el-button v-hasPermi="['system:dict:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button v-hasPermi="['system:dict:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button v-hasPermi="['system:dict:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
-              删除
-            </el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button v-hasPermi="['system:dict:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button v-hasPermi="['system:dict:remove']" type="danger" plain icon="Refresh" @click="handleRefreshCache">刷新缓存</el-button>
-          </el-col>
-          <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
-        </el-row>
-      </template>
+      </el-col>
 
-      <el-table v-loading="loading" border :data="typeList" @selection-change="handleSelectionChange">
-        <el-table-column type="selection" width="55" align="center" />
-        <el-table-column v-if="false" label="字典编号" align="center" prop="dictId" />
-        <el-table-column label="字典名称" align="center" prop="dictName" :show-overflow-tooltip="true" />
-        <el-table-column label="字典类型" align="center" :show-overflow-tooltip="true">
-          <template #default="scope">
-            <router-link :to="'/system/dict-data/index/' + scope.row.dictId" class="link-type">
-              <span>{{ scope.row.dictType }}</span>
-            </router-link>
-          </template>
-        </el-table-column>
-        <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
-        <el-table-column label="创建时间" align="center" prop="createTime" width="180">
-          <template #default="scope">
-            <span>{{ proxy.parseTime(scope.row.createTime) }}</span>
-          </template>
-        </el-table-column>
-        <el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
-          <template #default="scope">
-            <el-tooltip content="修改" placement="top">
-              <el-button v-hasPermi="['system:dict:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
-            </el-tooltip>
-            <el-tooltip content="删除" placement="top">
-              <el-button v-hasPermi="['system:dict:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
-            </el-tooltip>
+      <!-- 右侧字典数据 -->
+      <el-col :span="18" class="full-height">
+        <el-card shadow="never" class="table-card full-height">
+          <template #header>
+            <div class="card-header">
+              <div class="header-left">
+                <span class="title">{{ activeType?.dictName }}</span>
+                <el-tag size="small" type="info" class="ml8">{{ activeType?.dictType }}</el-tag>
+              </div>
+              <div class="header-right">
+                <el-button type="primary" icon="Plus" @click="handleAddData()">新增数据</el-button>
+              </div>
+            </div>
           </template>
-        </el-table-column>
-      </el-table>
-
-      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
-    </el-card>
-    <!-- 添加或修改参数配置对话框 -->
-    <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
-      <el-form ref="dictFormRef" :model="form" :rules="rules" label-width="80px">
+
+          <el-table v-loading="dataLoading" :data="dataList" style="width: 100%" :header-cell-style="{ background: '#f8f9fb', color: '#606266' }">
+            <el-table-column label="字典标签" prop="dictLabel" min-width="120" />
+            <el-table-column label="字典键值" prop="dictValue" min-width="120" />
+            <el-table-column label="排序" prop="dictSort" width="80" align="center" />
+            <el-table-column label="备注" prop="remark" min-width="150" :show-overflow-tooltip="true" />
+            <el-table-column label="操作" width="160" align="right" fixed="right">
+              <template #default="scope">
+                <div class="op-btns">
+                  <el-button link type="primary" @click="handleUpdateData(scope.row)">编辑</el-button>
+                  <el-button link type="danger" @click="handleDeleteData(scope.row)">删除</el-button>
+                </div>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <!-- 字典类型对话框 -->
+    <el-dialog v-model="typeDialog.visible" :title="typeDialog.title" width="450px" append-to-body>
+      <el-form ref="typeFormRef" :model="typeForm" :rules="typeRules" label-width="80px">
         <el-form-item label="字典名称" prop="dictName">
-          <el-input v-model="form.dictName" placeholder="请输入字典名称" />
+          <el-input v-model="typeForm.dictName" placeholder="请输入字典名称" />
         </el-form-item>
         <el-form-item label="字典类型" prop="dictType">
-          <el-input v-model="form.dictType" placeholder="请输入字典类型" />
+          <el-input v-model="typeForm.dictType" placeholder="请输入字典类型" />
         </el-form-item>
         <el-form-item label="备注" prop="remark">
-          <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
+          <el-input v-model="typeForm.remark" type="textarea" placeholder="请输入内容" />
         </el-form-item>
       </el-form>
       <template #footer>
         <div class="dialog-footer">
-          <el-button type="primary" @click="submitForm">确 定</el-button>
-          <el-button @click="cancel">取 消</el-button>
+          <el-button type="primary" @click="submitTypeForm">确 定</el-button>
+          <el-button @click="typeDialog.visible = false">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 字典数据对话框 -->
+    <el-dialog v-model="dataDialog.visible" :title="dataDialog.title" width="500px" append-to-body>
+      <el-form ref="dataFormRef" :model="dataForm" :rules="dataRules" label-width="80px">
+        <el-form-item label="字典类型">
+          <el-input v-model="dataForm.dictType" :disabled="true" />
+        </el-form-item>
+        <el-form-item label="数据标签" prop="dictLabel">
+          <el-input v-model="dataForm.dictLabel" placeholder="请输入数据标签" />
+        </el-form-item>
+        <el-form-item label="数据键值" prop="dictValue">
+          <el-input v-model="dataForm.dictValue" placeholder="请输入数据键值" />
+        </el-form-item>
+        <el-form-item label="显示排序" prop="dictSort">
+          <el-input-number v-model="dataForm.dictSort" controls-position="right" :min="0" />
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="dataForm.remark" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitDataForm">确 定</el-button>
+          <el-button @click="dataDialog.visible = false">取 消</el-button>
         </div>
       </template>
     </el-dialog>
@@ -108,139 +113,290 @@
 </template>
 
 <script setup name="Dict" lang="ts">
-import { useDictStore } from '@/store/modules/dict';
-import { listType, getType, delType, addType, updateType, refreshCache } from '@/api/system/dict/type';
-import { DictTypeForm, DictTypeQuery, DictTypeVO } from '@/api/system/dict/type/types';
+import { listType, getType, delType, addType, updateType } from '@/api/system/dict/type';
+import { listData, getData, delData, addData, updateData } from '@/api/system/dict/data';
+import { DictTypeForm, DictTypeVO } from '@/api/system/dict/type/types';
+import { DictDataForm, DictDataVO } from '@/api/system/dict/data/types';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
 
+// 字典类型相关
 const typeList = ref<DictTypeVO[]>([]);
-const loading = ref(true);
-const showSearch = ref(true);
-const ids = ref<Array<number | string>>([]);
-const single = ref(true);
-const multiple = ref(true);
-const total = ref(0);
-const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
-
-const dictFormRef = ref<ElFormInstance>();
-const queryFormRef = ref<ElFormInstance>();
-
-const dialog = reactive<DialogOption>({
-  visible: false,
-  title: ''
-});
-
-const initFormData: DictTypeForm = {
+const activeType = ref<DictTypeVO | null>(null);
+const typeSearch = ref('');
+const typeLoading = ref(false);
+const typeDialog = reactive({ visible: false, title: '' });
+const typeFormRef = ref<ElFormInstance>();
+const typeForm = ref<DictTypeForm>({
   dictId: undefined,
   dictName: '',
   dictType: '',
   remark: ''
+});
+const typeRules = {
+  dictName: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
+  dictType: [{ required: true, message: '编码不能为空', trigger: 'blur' }]
 };
-const data = reactive<PageData<DictTypeForm, DictTypeQuery>>({
-  form: { ...initFormData },
-  queryParams: {
-    pageNum: 1,
-    pageSize: 10,
-    dictName: '',
-    dictType: ''
-  },
-  rules: {
-    dictName: [{ required: true, message: '字典名称不能为空', trigger: 'blur' }],
-    dictType: [{ required: true, message: '字典类型不能为空', trigger: 'blur' }]
-  }
+
+// 字典数据相关
+const dataList = ref<DictDataVO[]>([]);
+const dataLoading = ref(false);
+const dataDialog = reactive({ visible: false, title: '' });
+const dataFormRef = ref<ElFormInstance>();
+const dataForm = ref<DictDataForm>({
+  dictCode: undefined,
+  dictLabel: '',
+  dictValue: '',
+  dictSort: 0,
+  status: '0',
+  remark: '',
+  dictType: ''
 });
+const dataRules = {
+  dictLabel: [{ required: true, message: '标签不能为空', trigger: 'blur' }],
+  dictValue: [{ required: true, message: '键值不能为空', trigger: 'blur' }]
+};
 
-const { queryParams, form, rules } = toRefs(data);
+const filteredTypeList = computed(() => {
+  if (!typeSearch.value) return typeList.value;
+  return typeList.value.filter(item => 
+    item.dictName.includes(typeSearch.value) || 
+    item.dictType.includes(typeSearch.value)
+  );
+});
 
-/** 查询字典类型列表 */
-const getList = () => {
-  loading.value = true;
-  listType(proxy?.addDateRange(queryParams.value, dateRange.value)).then((res) => {
-    typeList.value = res.rows;
-    total.value = res.total;
-    loading.value = false;
-  });
-};
-/** 取消按钮 */
-const cancel = () => {
-  reset();
-  dialog.visible = false;
-};
-/** 表单重置 */
-const reset = () => {
-  form.value = { ...initFormData };
-  dictFormRef.value?.resetFields();
+// 获取字典类型列表
+const getTypeList = async () => {
+  typeLoading.value = true;
+  const res = await listType({ pageNum: 1, pageSize: 100 } as any);
+  typeList.value = res.rows;
+  typeLoading.value = false;
+  if (!activeType.value && typeList.value.length > 0) {
+    handleTypeClick(typeList.value[0]);
+  }
 };
-/** 搜索按钮操作 */
-const handleQuery = () => {
-  queryParams.value.pageNum = 1;
-  getList();
+
+// 点击字典类型
+const handleTypeClick = (item: DictTypeVO) => {
+  activeType.value = item;
+  getDataList();
 };
-/** 重置按钮操作 */
-const resetQuery = () => {
-  dateRange.value = ['', ''];
-  queryFormRef.value?.resetFields();
-  handleQuery();
+
+// 获取字典数据列表
+const getDataList = async () => {
+  if (!activeType.value) return;
+  dataLoading.value = true;
+  const res = await listData({ 
+    pageNum: 1, 
+    pageSize: 100, 
+    dictType: activeType.value.dictType 
+  } as any);
+  dataList.value = res.rows;
+  dataLoading.value = false;
 };
-/** 新增按钮操作 */
-const handleAdd = () => {
-  reset();
-  dialog.visible = true;
-  dialog.title = '添加字典类型';
+
+// 字典类型操作
+const handleAddType = () => {
+  typeForm.value = { dictId: undefined, dictName: '', dictType: '', remark: '' };
+  typeDialog.title = '添加字典类型';
+  typeDialog.visible = true;
 };
-/** 多选框选中数据 */
-const handleSelectionChange = (selection: DictTypeVO[]) => {
-  ids.value = selection.map((item) => item.dictId);
-  single.value = selection.length != 1;
-  multiple.value = !selection.length;
+const handleUpdateType = async (item: DictTypeVO) => {
+  const res = await getType(item.dictId);
+  typeForm.value = res.data;
+  typeDialog.title = '修改字典类型';
+  typeDialog.visible = true;
 };
-/** 修改按钮操作 */
-const handleUpdate = async (row?: DictTypeVO) => {
-  reset();
-  const dictId = row?.dictId || ids.value[0];
-  const res = await getType(dictId);
-  Object.assign(form.value, res.data);
-  dialog.visible = true;
-  dialog.title = '修改字典类型';
+const handleDeleteType = async (item: DictTypeVO) => {
+  await proxy?.$modal.confirm(`确认删除字典类型"${item.dictName}"吗?`);
+  await delType(item.dictId);
+  if (activeType.value?.dictId === item.dictId) activeType.value = null;
+  proxy?.$modal.msgSuccess('删除成功');
+  getTypeList();
 };
-/** 提交按钮 */
-const submitForm = () => {
-  dictFormRef.value?.validate(async (valid: boolean) => {
+const submitTypeForm = () => {
+  typeFormRef.value?.validate(async (valid) => {
     if (valid) {
-      form.value.dictId ? await updateType(form.value) : await addType(form.value);
+      typeForm.value.dictId ? await updateType(typeForm.value) : await addType(typeForm.value);
       proxy?.$modal.msgSuccess('操作成功');
-      dialog.visible = false;
-      getList();
+      typeDialog.visible = false;
+      getTypeList();
     }
   });
 };
-/** 删除按钮操作 */
-const handleDelete = async (row?: DictTypeVO) => {
-  const dictIds = row?.dictId || ids.value;
-  await proxy?.$modal.confirm('是否确认删除字典编号为"' + dictIds + '"的数据项?');
-  await delType(dictIds);
-  getList();
-  proxy?.$modal.msgSuccess('删除成功');
+
+// 字典数据操作
+const handleAddData = () => {
+  dataForm.value = {
+    dictCode: undefined,
+    dictLabel: '',
+    dictValue: '',
+    dictSort: 0,
+    status: '0',
+    remark: '',
+    dictType: activeType.value?.dictType || ''
+  };
+  dataDialog.title = '添加字典数据';
+  dataDialog.visible = true;
 };
-/** 导出按钮操作 */
-const handleExport = () => {
-  proxy?.download(
-    'system/dict/type/export',
-    {
-      ...queryParams.value
-    },
-    `dict_${new Date().getTime()}.xlsx`
-  );
+const handleUpdateData = async (item: DictDataVO) => {
+  const res = await getData(item.dictCode);
+  Object.assign(dataForm.value, res.data);
+  dataDialog.title = '修改字典数据';
+  dataDialog.visible = true;
+};
+const handleDeleteData = async (item: DictDataVO) => {
+  await proxy?.$modal.confirm('确认删除吗?');
+  await delData(item.dictCode);
+  proxy?.$modal.msgSuccess('删除成功');
+  getDataList();
 };
-/** 刷新缓存按钮操作 */
-const handleRefreshCache = async () => {
-  await refreshCache();
-  proxy?.$modal.msgSuccess('刷新成功');
-  useDictStore().cleanDict();
+const submitDataForm = () => {
+  dataFormRef.value?.validate(async (valid) => {
+    if (valid) {
+      dataForm.value.dictCode ? await updateData(dataForm.value) : await addData(dataForm.value);
+      proxy?.$modal.msgSuccess('操作成功');
+      dataDialog.visible = false;
+      getDataList();
+    }
+  });
 };
 
 onMounted(() => {
-  getList();
+  getTypeList();
 });
 </script>
+
+<style scoped lang="scss">
+.page-container {
+  padding: 20px;
+  background-color: #f5f7f9;
+  height: calc(100vh - 84px);
+  overflow: hidden;
+}
+
+.full-height {
+  height: 100%;
+}
+
+.type-card {
+  display: flex;
+  flex-direction: column;
+  
+  :deep(.el-card__header) {
+    padding: 16px;
+    border-bottom: none;
+  }
+  
+  :deep(.el-card__body) {
+    padding: 0;
+    flex: 1;
+    overflow: hidden;
+  }
+}
+
+.table-card {
+  :deep(.el-card__header) {
+    padding: 18px 24px;
+    border-bottom: 1px solid #f0f0f0;
+  }
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  
+  .header-left {
+    display: flex;
+    align-items: center;
+  }
+  
+  .title {
+    font-size: 16px;
+    font-weight: 600;
+    color: #333;
+  }
+}
+
+.search-box {
+  margin-top: 12px;
+  
+  :deep(.el-input__wrapper) {
+    background-color: #f4f5f7;
+    box-shadow: none;
+  }
+}
+
+.type-item {
+  padding: 12px 16px;
+  cursor: pointer;
+  transition: all 0.2s;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  border-left: 3px solid transparent;
+
+  &:hover {
+    background-color: #f0f7ff;
+    
+    .type-ops {
+      display: flex;
+    }
+  }
+
+  &.active {
+    background-color: #e6f1fc;
+    border-left-color: #409eff;
+    
+    .type-name {
+      color: #409eff;
+    }
+  }
+}
+
+.type-info {
+  flex: 1;
+  overflow: hidden;
+  
+  .type-name {
+    font-size: 14px;
+    font-weight: 500;
+    color: #333;
+    margin-bottom: 4px;
+  }
+  
+  .type-code {
+    font-size: 12px;
+    color: #909399;
+  }
+}
+
+.type-ops {
+  display: none;
+  gap: 4px;
+}
+
+.op-btns {
+  display: flex;
+  justify-content: flex-end;
+  gap: 8px;
+  
+  .el-button {
+    font-weight: 400;
+  }
+}
+
+.ml8 {
+  margin-left: 8px;
+}
+
+:deep(.el-table) {
+  --el-table-border-color: #f0f0f0;
+  
+  th.el-table__cell {
+    font-weight: 600;
+  }
+}
+</style>

+ 99 - 136
src/views/system/role/index.vue

@@ -1,102 +1,44 @@
 <template>
-  <div class="p-2">
-    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
-      <div v-show="showSearch" class="mb-[10px]">
-        <el-card shadow="hover">
-          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
-            <el-form-item label="角色名称" prop="roleName">
-              <el-input v-model="queryParams.roleName" placeholder="请输入角色名称" clearable @keyup.enter="handleQuery" />
-            </el-form-item>
-            <el-form-item label="权限字符" prop="roleKey">
-              <el-input v-model="queryParams.roleKey" placeholder="请输入权限字符" clearable @keyup.enter="handleQuery" />
-            </el-form-item>
-            <el-form-item label="状态" prop="status">
-              <el-select v-model="queryParams.status" placeholder="角色状态" clearable>
-                <el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
-              </el-select>
-            </el-form-item>
-            <el-form-item label="创建时间" style="width: 308px">
-              <el-date-picker
-                v-model="dateRange"
-                value-format="YYYY-MM-DD HH:mm:ss"
-                type="daterange"
-                range-separator="-"
-                start-placeholder="开始日期"
-                end-placeholder="结束日期"
-                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
-              ></el-date-picker>
-            </el-form-item>
-
-            <el-form-item>
-              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-            </el-form-item>
-          </el-form>
-        </el-card>
-      </div>
-    </transition>
-
-    <el-card shadow="hover">
+  <div class="page-container">
+    <el-card shadow="never" class="table-card">
       <template #header>
-        <el-row :gutter="10">
-          <el-col :span="1.5">
-            <el-button v-hasPermi="['system:role:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button v-hasPermi="['system:role:edit']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()">修改</el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button v-hasPermi="['system:role:remove']" type="danger" plain :disabled="ids.length === 0" @click="handleDelete()">删除</el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button v-hasPermi="['system:role:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
-          </el-col>
-          <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
-        </el-row>
+        <div class="card-header">
+          <div class="header-left">
+            <span class="title">角色与权限管理</span>
+          </div>
+          <div class="header-right">
+            <el-button type="primary" icon="Plus" @click="handleAdd()">新增角色</el-button>
+          </div>
+        </div>
       </template>
 
-      <el-table ref="roleTableRef" border v-loading="loading" :data="roleList" @selection-change="handleSelectionChange">
-        <el-table-column type="selection" width="55" align="center" />
-        <el-table-column v-if="false" label="角色编号" prop="roleId" width="120" />
-        <el-table-column label="角色名称" prop="roleName" :show-overflow-tooltip="true" width="150" />
-        <el-table-column label="权限字符" prop="roleKey" :show-overflow-tooltip="true" width="200" />
-        <el-table-column label="显示顺序" prop="roleSort" width="100" />
-        <el-table-column label="状态" align="center" width="100">
+      <el-table ref="roleTableRef" v-loading="loading" :data="roleList" style="width: 100%" :header-cell-style="{ background: '#f8f9fb', color: '#606266' }">
+        <el-table-column label="角色名称" prop="roleName" min-width="150" />
+        <el-table-column label="描述" prop="remark" min-width="300" :show-overflow-tooltip="true" />
+        <el-table-column label="状态" width="100" align="center">
           <template #default="scope">
             <el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
           </template>
         </el-table-column>
-        <el-table-column label="创建时间" align="center" prop="createTime">
-          <template #default="scope">
-            <span>{{ proxy.parseTime(scope.row.createTime) }}</span>
-          </template>
-        </el-table-column>
-
-        <el-table-column fixed="right" label="操作" width="180">
+        <el-table-column label="操作" width="160" align="right" fixed="right">
           <template #default="scope">
-            <el-tooltip v-if="scope.row.roleId !== 1" content="修改" placement="top">
-              <el-button v-hasPermi="['system:role:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
-            </el-tooltip>
-            <el-tooltip v-if="scope.row.roleId !== 1" content="删除" placement="top">
-              <el-button v-hasPermi="['system:role:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
-            </el-tooltip>
-            <el-tooltip v-if="scope.row.roleId !== 1" content="数据权限" placement="top">
-              <el-button v-hasPermi="['system:role:edit']" link type="primary" icon="CircleCheck" @click="handleDataScope(scope.row)"></el-button>
-            </el-tooltip>
-            <el-tooltip v-if="scope.row.roleId !== 1" content="分配用户" placement="top">
-              <el-button v-hasPermi="['system:role:edit']" link type="primary" icon="User" @click="handleAuthUser(scope.row)"></el-button>
-            </el-tooltip>
+            <div class="op-btns" v-if="scope.row.roleId !== 1">
+              <el-button v-hasPermi="['system:role:edit']" link type="primary" @click="handleUpdate(scope.row)">编辑</el-button>
+              <el-button v-hasPermi="['system:role:remove']" link type="danger" @click="handleDelete(scope.row)">删除</el-button>
+            </div>
           </template>
         </el-table-column>
       </el-table>
 
-      <pagination
-        v-if="total > 0"
-        v-model:total="total"
-        v-model:page="queryParams.pageNum"
-        v-model:limit="queryParams.pageSize"
-        @pagination="getList"
-      />
+      <div class="pagination-container">
+        <pagination
+          v-if="total > 0"
+          v-model:total="total"
+          v-model:page="queryParams.pageNum"
+          v-model:limit="queryParams.pageSize"
+          @pagination="getList"
+        />
+      </div>
     </el-card>
 
     <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
@@ -104,17 +46,6 @@
         <el-form-item label="角色名称" prop="roleName">
           <el-input v-model="form.roleName" placeholder="请输入角色名称" />
         </el-form-item>
-        <el-form-item prop="roleKey">
-          <template #label>
-            <span>
-              <el-tooltip content="控制器中定义的权限字符,如:@SaCheckRole('admin')" placement="top">
-                <el-icon><question-filled /></el-icon>
-              </el-tooltip>
-              权限字符
-            </span>
-          </template>
-          <el-input v-model="form.roleKey" placeholder="请输入权限字符" />
-        </el-form-item>
         <el-form-item label="角色顺序" prop="roleSort">
           <el-input-number v-model="form.roleSort" controls-position="right" :min="0" />
         </el-form-item>
@@ -156,9 +87,6 @@
         <el-form-item label="角色名称">
           <el-input v-model="form.roleName" :disabled="true" />
         </el-form-item>
-        <el-form-item label="权限字符">
-          <el-input v-model="form.roleKey" :disabled="true" />
-        </el-form-item>
         <el-form-item label="权限范围">
           <el-select v-model="form.dataScope" @change="dataScopeSelectChange">
             <el-option v-for="item in dataScopeOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
@@ -203,12 +131,7 @@ const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'))
 
 const roleList = ref<RoleVO[]>();
 const loading = ref(true);
-const showSearch = ref(true);
-const ids = ref<Array<string | number>>([]);
-const single = ref(true);
-const multiple = ref(true);
 const total = ref(0);
-const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
 const menuOptions = ref<MenuTreeOption[]>([]);
 const menuExpand = ref(false);
 const menuNodeAll = ref(false);
@@ -258,7 +181,6 @@ const data = reactive<PageData<RoleForm, RoleQuery>>({
   },
   rules: {
     roleName: [{ required: true, message: '角色名称不能为空', trigger: 'blur' }],
-    roleKey: [{ required: true, message: '权限字符不能为空', trigger: 'blur' }],
     roleSort: [{ required: true, message: '角色顺序不能为空', trigger: 'blur' }]
   }
 });
@@ -274,7 +196,7 @@ const dialog = reactive<DialogOption>({
  */
 const getList = () => {
   loading.value = true;
-  listRole(proxy?.addDateRange(queryParams.value, dateRange.value)).then((res) => {
+  listRole(queryParams.value).then((res) => {
     roleList.value = res.rows;
     total.value = res.total;
     loading.value = false;
@@ -289,37 +211,14 @@ const handleQuery = () => {
   getList();
 };
 
-/** 重置 */
-const resetQuery = () => {
-  dateRange.value = ['', ''];
-  queryFormRef.value?.resetFields();
-  handleQuery();
-};
 /**删除按钮操作 */
-const handleDelete = async (row?: RoleVO) => {
-  const roleids = row?.roleId || ids.value;
-  await proxy?.$modal.confirm('是否确认删除角色编号为' + roleids + '数据项目');
-  await delRole(roleids);
+const handleDelete = async (row: RoleVO) => {
+  await proxy?.$modal.confirm('是否确认删除角色名称为"' + row.roleName + '"的数据项?');
+  await delRole(row.roleId);
   getList();
   proxy?.$modal.msgSuccess('删除成功');
 };
 
-/** 导出按钮操作 */
-const handleExport = () => {
-  proxy?.download(
-    'system/role/export',
-    {
-      ...queryParams.value
-    },
-    `role_${new Date().getTime()}.xlsx`
-  );
-};
-/** 多选框选中数据 */
-const handleSelectionChange = (selection: RoleVO[]) => {
-  ids.value = selection.map((item: RoleVO) => item.roleId);
-  single.value = selection.length != 1;
-  multiple.value = !selection.length;
-};
 
 /** 角色状态修改 */
 const handleStatusChange = async (row: RoleVO) => {
@@ -372,10 +271,9 @@ const handleAdd = () => {
   dialog.visible = true;
   dialog.title = '添加角色';
 };
-/** 修改角色 */
-const handleUpdate = async (row?: RoleVO) => {
+const handleUpdate = async (row: RoleVO) => {
   reset();
-  const roleId = row?.roleId || ids.value[0];
+  const roleId = row.roleId;
   const { data } = await getRole(roleId);
   Object.assign(form.value, data);
   form.value.roleSort = Number(form.value.roleSort);
@@ -501,3 +399,68 @@ onMounted(() => {
   getList();
 });
 </script>
+
+<style scoped lang="scss">
+.page-container {
+  padding: 20px;
+  background-color: #f5f7f9;
+  min-height: 100%;
+}
+
+.table-card {
+  border: none;
+  border-radius: 8px;
+  
+  :deep(.el-card__header) {
+    padding: 20px 24px;
+    border-bottom: 1px solid #f0f0f0;
+  }
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.header-left {
+  .title {
+    font-size: 16px;
+    font-weight: 600;
+    color: #333;
+  }
+}
+
+.header-right {
+  display: flex;
+  gap: 12px;
+}
+
+.op-btns {
+  display: flex;
+  justify-content: flex-end;
+  gap: 8px;
+  
+  .el-button {
+    font-weight: 400;
+    padding: 0;
+    
+    &.el-button--primary { color: #409eff; }
+    &.el-button--danger { color: #f56c6c; }
+  }
+}
+
+.pagination-container {
+  margin-top: 24px;
+  display: flex;
+  justify-content: flex-end;
+}
+
+:deep(.el-table) {
+  --el-table-border-color: #f0f0f0;
+  
+  th.el-table__cell {
+    font-weight: 600;
+  }
+}
+</style>

+ 3 - 3
src/views/system/store/index.vue

@@ -247,7 +247,7 @@
             <el-descriptions-item label="有效期至">{{ parseTime(detailData.validity, '{y}-{m}-{d}') }}</el-descriptions-item>
             <el-descriptions-item label="联系人">{{ detailData.contact }}</el-descriptions-item>
             <el-descriptions-item label="联系电话">{{ detailData.contactNumber }}</el-descriptions-item>
-            <el-descriptions-item label="所在区域">{{ detailData.regionName || '北京市朝阳区' }}</el-descriptions-item>
+            <el-descriptions-item label="归属站点">{{ detailData.siteName }}</el-descriptions-item>
             <el-descriptions-item label="详细地址">{{ getFullAddress(detailData) }}</el-descriptions-item>
             <el-descriptions-item label="营业执照">
               <image-preview v-if="detailData.businessLicenseUrl" :src="detailData.businessLicenseUrl" :width="80" :height="60" />
@@ -316,7 +316,7 @@ import { listSubOrderOnStore } from '@/api/order/subOrder/index';
 import { StoreVO, StoreForm, StoreQuery, StoreStatusVO, SysStorePageBo } from '@/api/system/store/types';
 import { listOnStore } from '@/api/system/tenant';
 import { listOnStore as listTenantCategoriesOnStore } from '@/api/system/tenantCategories';
-import { listOnStore as listServiceOnStore } from '@/api/service/list';
+import { listAllService } from '@/api/service/list';
 import { listOnStore as listAreaStationOnStore } from '@/api/system/areaStation';
 import { SysAreaStationOnStoreVo } from '@/api/system/areaStation/types';
 import { regionData, codeToText, textToCode } from 'element-china-area-data';
@@ -811,7 +811,7 @@ const getBrandList = async (pageNum = 1, keyword = '', append = false) => {
 /** 获取服务项目列表 */
 const getServiceList = async () => {
   try {
-    const res = await listServiceOnStore();
+    const res = await listAllService();
     // 转换数据格式,适配checkbox组件
     serviceList.value = res.data || res;
   } catch (error) {

+ 244 - 234
src/views/system/user/index.vue

@@ -1,240 +1,179 @@
 <template>
-  <div class="p-2">
-    <el-row :gutter="20">
-      <!-- 部门树 -->
-      <el-col :lg="4" :xs="24" style="">
-        <el-card shadow="hover">
-          <el-input v-model="deptName" placeholder="请输入部门名称" prefix-icon="Search" clearable />
-          <el-tree
-            ref="deptTreeRef"
-            class="mt-2"
-            node-key="id"
-            :data="deptOptions"
-            :props="{ label: 'label', children: 'children' } as any"
-            :expand-on-click-node="false"
-            :filter-node-method="filterNode"
-            highlight-current
-            default-expand-all
-            @node-click="handleNodeClick"
-          />
-        </el-card>
-      </el-col>
-      <el-col :lg="20" :xs="24">
-        <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
-          <div v-show="showSearch" class="mb-[10px]">
-            <el-card shadow="hover">
-              <el-form ref="queryFormRef" :model="queryParams" :inline="true">
-                <el-form-item label="用户名称" prop="userName">
-                  <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" />
-                </el-form-item>
-                <el-form-item label="用户昵称" prop="nickName">
-                  <el-input v-model="queryParams.nickName" placeholder="请输入用户昵称" clearable @keyup.enter="handleQuery" />
-                </el-form-item>
-                <el-form-item label="手机号码" prop="phonenumber">
-                  <el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable @keyup.enter="handleQuery" />
-                </el-form-item>
-
-                <el-form-item label="状态" prop="status">
-                  <el-select v-model="queryParams.status" placeholder="用户状态" clearable>
-                    <el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
-                  </el-select>
-                </el-form-item>
-                <el-form-item label="创建时间" style="width: 308px">
-                  <el-date-picker
-                    v-model="dateRange"
-                    value-format="YYYY-MM-DD HH:mm:ss"
-                    type="daterange"
-                    range-separator="-"
-                    start-placeholder="开始日期"
-                    end-placeholder="结束日期"
-                    :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
-                  ></el-date-picker>
-                </el-form-item>
-                <el-form-item>
-                  <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-                  <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-                </el-form-item>
-              </el-form>
-            </el-card>
+  <div class="page-container">
+    <el-card shadow="never" class="table-card">
+      <template #header>
+        <div class="card-header">
+          <div class="header-left">
+            <span class="title">账号管理</span>
           </div>
-        </transition>
-
-        <el-card shadow="hover">
-          <template #header>
-            <el-row :gutter="10">
-              <el-col :span="1.5">
-                <el-button v-has-permi="['system:user:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
-              </el-col>
-              <el-col :span="1.5">
-                <el-button v-has-permi="['system:user:edit']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()">
-                  修改
-                </el-button>
-              </el-col>
-              <el-col :span="1.5">
-                <el-button v-has-permi="['system:user:remove']" type="danger" plain :disabled="multiple" icon="Delete" @click="handleDelete()">
-                  删除
-                </el-button>
-              </el-col>
-              <el-col :span="1.5">
-                <el-dropdown class="mt-[1px]">
-                  <el-button plain type="info">
-                    更多
-                    <el-icon class="el-icon--right"><arrow-down /></el-icon
-                  ></el-button>
-                  <template #dropdown>
-                    <el-dropdown-menu>
-                      <el-dropdown-item icon="Download" @click="importTemplate">下载模板</el-dropdown-item>
-                      <!-- 注意 由于el-dropdown-item标签是延迟加载的 所以v-has-permi自定义标签不生效 需要使用v-if调用方法执行 -->
-                      <el-dropdown-item v-if="checkPermi(['system:user:import'])" icon="Top" @click="handleImport">导入数据</el-dropdown-item>
-                      <el-dropdown-item v-if="checkPermi(['system:user:export'])" icon="Download" @click="handleExport">导出数据</el-dropdown-item>
-                    </el-dropdown-menu>
-                  </template>
-                </el-dropdown>
-              </el-col>
-              <right-toolbar v-model:show-search="showSearch" :columns="columns" :search="true" @query-table="getList"></right-toolbar>
-            </el-row>
-          </template>
+          <div class="header-right">
+            <el-input
+              v-model="queryParams.content"
+              placeholder="搜索姓名/手机号"
+              class="search-input"
+              prefix-icon="Search"
+              clearable
+              @keyup.enter="handleQuery"
+            />
+            <el-button type="primary" icon="Plus" @click="handleAdd()">新增账号</el-button>
+          </div>
+        </div>
+      </template>
 
-          <el-table v-loading="loading" border :data="userList" @selection-change="handleSelectionChange">
-            <el-table-column type="selection" width="50" align="center" />
-            <el-table-column v-if="columns[0].visible" key="userId" label="用户编号" align="center" prop="userId" />
-            <el-table-column v-if="columns[1].visible" key="userName" label="用户名称" align="center" prop="userName" :show-overflow-tooltip="true" />
-            <el-table-column v-if="columns[2].visible" key="nickName" label="用户昵称" align="center" prop="nickName" :show-overflow-tooltip="true" />
-            <el-table-column v-if="columns[3].visible" key="deptName" label="部门" align="center" prop="deptName" :show-overflow-tooltip="true" />
-            <el-table-column v-if="columns[4].visible" key="phonenumber" label="手机号码" align="center" prop="phonenumber" width="120" />
-            <el-table-column v-if="columns[5].visible" key="status" label="状态" align="center">
-              <template #default="scope">
-                <el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
-              </template>
-            </el-table-column>
-
-            <el-table-column v-if="columns[6].visible" label="创建时间" align="center" prop="createTime" width="160">
-              <template #default="scope">
-                <span>{{ scope.row.createTime }}</span>
-              </template>
-            </el-table-column>
-
-            <el-table-column label="操作" fixed="right" width="180" class-name="small-padding fixed-width">
-              <template #default="scope">
-                <el-tooltip v-if="scope.row.userId !== 1" content="修改" placement="top">
-                  <el-button v-hasPermi="['system:user:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
-                </el-tooltip>
-                <el-tooltip v-if="scope.row.userId !== 1" content="删除" placement="top">
-                  <el-button v-hasPermi="['system:user:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
-                </el-tooltip>
-
-                <el-tooltip v-if="scope.row.userId !== 1" content="重置密码" placement="top">
-                  <el-button v-hasPermi="['system:user:resetPwd']" link type="primary" icon="Key" @click="handleResetPwd(scope.row)"></el-button>
-                </el-tooltip>
-
-                <el-tooltip v-if="scope.row.userId !== 1" content="分配角色" placement="top">
-                  <el-button v-hasPermi="['system:user:edit']" link type="primary" icon="CircleCheck" @click="handleAuthRole(scope.row)"></el-button>
-                </el-tooltip>
-              </template>
-            </el-table-column>
-          </el-table>
-
-          <pagination
-            v-show="total > 0"
-            v-model:page="queryParams.pageNum"
-            v-model:limit="queryParams.pageSize"
-            :total="total"
-            @pagination="getList"
-          />
-        </el-card>
-      </el-col>
-    </el-row>
+      <el-table v-loading="loading" :data="userList" style="width: 100%" :header-cell-style="{ background: '#f8f9fb', color: '#606266' }">
+        <el-table-column label="头像" width="100" align="center">
+          <template #default="scope">
+            <el-avatar :size="40" :src="scope.row.avatar" icon="UserFilled" />
+          </template>
+        </el-table-column>
+        <el-table-column label="姓名" min-width="120">
+          <template #default="scope">
+            <span class="nick-name">{{ scope.row.nickName }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column key="phonenumber" label="手机号" prop="phonenumber" width="150" />
+        <el-table-column label="角色" min-width="150">
+          <template #default="scope">
+            <template v-if="scope.row.roles && scope.row.roles.length">
+              <el-tag v-for="role in scope.row.roles" :key="role.roleId" size="small" class="role-tag" effect="light" type="primary">
+                {{ role.roleName }}
+              </el-tag>
+            </template>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+        <el-table-column key="deptName" label="所属区域/站点" prop="deptName" min-width="200" />
+        <el-table-column label="状态" width="100" align="center">
+          <template #default="scope">
+            <el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="220" align="right" fixed="right">
+          <template #default="scope">
+            <div class="op-btns" v-if="scope.row.userId !== 1">
+              <el-button v-hasPermi="['system:user:edit']" link type="primary" @click="handleUpdate(scope.row)">编辑</el-button>
+              <el-button v-hasPermi="['system:user:resetPwd']" link type="warning" @click="handleResetPwd(scope.row)">重置密码</el-button>
+              <el-button v-hasPermi="['system:user:remove']" link type="danger" @click="handleDelete(scope.row)">删除</el-button>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div class="pagination-container">
+        <pagination
+          v-show="total > 0"
+          v-model:page="queryParams.pageNum"
+          v-model:limit="queryParams.pageSize"
+          :total="total"
+          @pagination="getList"
+        />
+      </div>
+    </el-card>
 
     <!-- 添加或修改用户配置对话框 -->
     <el-dialog ref="formDialogRef" v-model="dialog.visible" :title="dialog.title" width="600px" append-to-body @close="closeDialog">
       <el-form ref="userFormRef" :model="form" :rules="rules" label-width="80px">
-        <el-row>
+        <!-- 第一行:昵称 / 手机号 -->
+        <el-row :gutter="20">
           <el-col :span="12">
             <el-form-item label="用户昵称" prop="nickName">
               <el-input v-model="form.nickName" placeholder="请输入用户昵称" maxlength="30" />
             </el-form-item>
           </el-col>
-          <el-col :span="12" v-if="form.userId == null || form.userId != useUserStore().userId">
-            <el-form-item label="归属部门" prop="deptId">
-              <el-tree-select
-                v-model="form.deptId"
-                :data="enabledDeptOptions"
-                :props="{ value: 'id', label: 'label', children: 'children' } as any"
-                value-key="id"
-                placeholder="请选择归属部门"
-                check-strictly
-                @change="handleDeptChange"
-              />
-            </el-form-item>
-          </el-col>
-        </el-row>
-        <el-row>
           <el-col :span="12">
             <el-form-item label="手机号码" prop="phonenumber">
               <el-input v-model="form.phonenumber" placeholder="请输入手机号码" maxlength="11" />
             </el-form-item>
           </el-col>
+        </el-row>
+
+        <!-- 第二行:邮箱 / 登录账号 -->
+        <el-row :gutter="20">
           <el-col :span="12">
             <el-form-item label="邮箱" prop="email">
               <el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" />
             </el-form-item>
           </el-col>
-        </el-row>
-        <el-row>
-          <el-col :span="12">
-            <el-form-item v-if="form.userId == undefined" label="用户名称" prop="userName">
-              <el-input v-model="form.userName" placeholder="请输入用户名称" maxlength="30" />
-            </el-form-item>
-          </el-col>
           <el-col :span="12">
-            <el-form-item v-if="form.userId == undefined" label="用户密码" prop="password">
-              <el-input v-model="form.password" placeholder="请输入用户密码" type="password" maxlength="20" show-password />
+            <el-form-item label="登录账号" prop="userName">
+              <el-input v-model="form.userName" placeholder="请输入登录账号" maxlength="30" :disabled="form.userId !== undefined" />
             </el-form-item>
           </el-col>
         </el-row>
-        <el-row>
-          <el-col :span="12">
-            <el-form-item label="用户性别">
-              <el-select v-model="form.sex" placeholder="请选择">
-                <el-option v-for="dict in sys_user_sex" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
-              </el-select>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="状态">
-              <el-radio-group v-model="form.status">
-                <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
-              </el-radio-group>
-            </el-form-item>
-          </el-col>
+
+        <!-- 第三行:登录密码(新增) or 用户性别(修改) / 用户性别(新增) or 状态(修改) -->
+        <el-row :gutter="20">
+          <template v-if="form.userId === undefined">
+            <el-col :span="12">
+              <el-form-item label="登录密码" prop="password">
+                <el-input v-model="form.password" placeholder="请输入登录密码" type="password" maxlength="20" show-password />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="用户性别">
+                <el-select v-model="form.sex" placeholder="请对象" style="width: 100%">
+                  <el-option v-for="dict in sys_user_sex" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+          </template>
+          <template v-else>
+            <el-col :span="12">
+              <el-form-item label="用户性别">
+                <el-select v-model="form.sex" placeholder="请选择" style="width: 100%">
+                  <el-option v-for="dict in sys_user_sex" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="状态">
+                <el-radio-group v-model="form.status">
+                  <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
+                </el-radio-group>
+              </el-form-item>
+            </el-col>
+          </template>
         </el-row>
-        <el-row>
-          <el-col :span="12" v-if="form.userId == null || form.userId != useUserStore().userId">
-            <el-form-item label="岗位">
-              <el-select v-model="form.postIds" multiple placeholder="请选择">
-                <el-option
-                  v-for="item in postOptions"
-                  :key="item.postId"
-                  :label="item.postName"
-                  :value="item.postId"
-                  :disabled="item.status == '1'"
-                ></el-option>
-              </el-select>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12" v-if="form.userId == null || form.userId != useUserStore().userId">
-            <el-form-item label="角色" prop="roleIds">
-              <el-select v-model="form.roleIds" filterable multiple placeholder="请选择">
-                <el-option
-                  v-for="item in roleOptions"
-                  :key="item.roleId"
-                  :label="item.roleName"
-                  :value="item.roleId"
-                  :disabled="item.status == '1'"
-                ></el-option>
-              </el-select>
-            </el-form-item>
-          </el-col>
+
+        <!-- 第四行:角色(新增) or 角色(修改) / 状态(新增) or 空白(修改时备注占满) -->
+        <el-row :gutter="20">
+          <template v-if="form.userId === undefined">
+            <el-col :span="12">
+              <el-form-item label="角色" prop="roleIds">
+                <el-select v-model="form.roleIds" filterable multiple placeholder="请选择" style="width: 100%">
+                  <el-option
+                    v-for="item in roleOptions"
+                    :key="item.roleId"
+                    :label="item.roleName"
+                    :value="item.roleId"
+                    :disabled="item.status == '1'"
+                  ></el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="状态">
+                <el-radio-group v-model="form.status">
+                  <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
+                </el-radio-group>
+              </el-form-item>
+            </el-col>
+          </template>
+          <template v-else>
+            <el-col :span="12">
+              <el-form-item label="角色" prop="roleIds">
+                <el-select v-model="form.roleIds" filterable multiple placeholder="请选择" style="width: 100%">
+                  <el-option
+                    v-for="item in roleOptions"
+                    :key="item.roleId"
+                    :label="item.roleName"
+                    :value="item.roleId"
+                    :disabled="item.status == '1'"
+                  ></el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+          </template>
         </el-row>
         <el-row>
           <el-col :span="24">
@@ -311,8 +250,6 @@ const single = ref(true);
 const multiple = ref(true);
 const total = ref(0);
 const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
-const deptName = ref('');
-const deptOptions = ref<DeptTreeVO[]>([]);
 const enabledDeptOptions = ref<DeptTreeVO[]>([]);
 const initPassword = ref<string>('');
 const postOptions = ref<PostVO[]>([]);
@@ -343,7 +280,6 @@ const columns = ref<FieldOption[]>([
   { key: 6, label: `创建时间`, visible: true, children: [] }
 ]);
 
-const deptTreeRef = ref<ElTreeInstance>();
 const queryFormRef = ref<ElFormInstance>();
 const userFormRef = ref<ElFormInstance>();
 const uploadRef = ref<ElUploadInstance>();
@@ -374,6 +310,7 @@ const initData: PageData<UserForm, UserQuery> = {
   queryParams: {
     pageNum: 1,
     pageSize: 10,
+    content: '',
     userName: '',
     phonenumber: '',
     status: '',
@@ -422,20 +359,6 @@ const data = reactive<PageData<UserForm, UserQuery>>(initData);
 
 const { queryParams, form, rules } = toRefs<PageData<UserForm, UserQuery>>(data);
 
-/** 通过条件过滤节点  */
-const filterNode = (value: string, data: any) => {
-  if (!value) return true;
-  return data.label.indexOf(value) !== -1;
-};
-/** 根据名称筛选部门树 */
-watchEffect(
-  () => {
-    deptTreeRef.value?.filter(deptName.value);
-  },
-  {
-    flush: 'post' // watchEffect会在DOM挂载或者更新之前就会触发,此属性控制在DOM元素更新后运行
-  }
-);
 
 /** 查询用户列表 */
 const getList = async () => {
@@ -449,7 +372,6 @@ const getList = async () => {
 /** 查询部门下拉树结构 */
 const getDeptTree = async () => {
   const res = await api.deptTreeSelect();
-  deptOptions.value = res.data;
   enabledDeptOptions.value = filterDisabledDept(res.data);
 };
 
@@ -466,11 +388,6 @@ const filterDisabledDept = (deptList: DeptTreeVO[]) => {
   });
 };
 
-/** 节点单击事件 */
-const handleNodeClick = (data: DeptVO) => {
-  queryParams.value.deptId = data.id;
-  handleQuery();
-};
 
 /** 搜索按钮操作 */
 const handleQuery = () => {
@@ -483,7 +400,6 @@ const resetQuery = () => {
   queryFormRef.value?.resetFields();
   queryParams.value.pageNum = 1;
   queryParams.value.deptId = undefined;
-  deptTreeRef.value?.setCurrentKey(undefined);
   handleQuery();
 };
 
@@ -677,3 +593,97 @@ async function handleDeptChange(value: number | string) {
   form.value.postIds = [];
 }
 </script>
+
+<style scoped lang="scss">
+.page-container {
+  padding: 20px;
+  background-color: #f5f7f9;
+  min-height: 100%;
+}
+
+.table-card {
+  border: none;
+  border-radius: 8px;
+  
+  :deep(.el-card__header) {
+    padding: 20px 24px;
+    border-bottom: 1px solid #f0f0f0;
+  }
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.header-left {
+  .title {
+    font-size: 16px;
+    font-weight: 600;
+    color: #333;
+  }
+}
+
+.header-right {
+  display: flex;
+  gap: 12px;
+  
+  .search-input {
+    width: 240px;
+    
+    :deep(.el-input__wrapper) {
+      background-color: #f4f5f7;
+      box-shadow: none;
+      border: 1px solid transparent;
+      
+      &:hover, &.is-focus {
+        border-color: #409eff;
+        background-color: #fff;
+      }
+    }
+  }
+}
+
+.nick-name {
+  font-weight: 600;
+  color: #333;
+}
+
+.role-tag {
+  margin-right: 4px;
+  margin-bottom: 4px;
+  border: none;
+  background-color: #edf5ff;
+  color: #409eff;
+}
+
+.op-btns {
+  display: flex;
+  justify-content: flex-end;
+  gap: 8px;
+  
+  .el-button {
+    font-weight: 400;
+    padding: 0;
+    
+    &.el-button--primary { color: #409eff; }
+    &.el-button--warning { color: #e6a23c; }
+    &.el-button--danger { color: #f56c6c; }
+  }
+}
+
+.pagination-container {
+  margin-top: 24px;
+  display: flex;
+  justify-content: flex-end;
+}
+
+:deep(.el-table) {
+  --el-table-border-color: #f0f0f0;
+  
+  th.el-table__cell {
+    font-weight: 600;
+  }
+}
+</style>