Browse Source

律师中台: 菜单列表增删改等功能完善等

sgc 4 weeks ago
parent
commit
3b70c03cd6
27 changed files with 1622 additions and 3646 deletions
  1. 2 1
      src/api/interface/index.ts
  2. 78 0
      src/api/modules/lawyer.ts
  3. 17 0
      src/api/modules/user.ts
  4. 188 0
      src/views/lawyerManagement/lawFirm/components/LawFirmDialog.vue
  5. 0 161
      src/views/lawyerManagement/lawFirm/detailDialog.vue
  6. 90 102
      src/views/lawyerManagement/lawFirm/index.vue
  7. 0 328
      src/views/lawyerManagement/lawFirm/reviewDialog.vue
  8. 154 0
      src/views/lawyerManagement/lawyer/components/LawyerDialog.vue
  9. 0 161
      src/views/lawyerManagement/lawyer/detailDialog.vue
  10. 146 147
      src/views/lawyerManagement/lawyer/index.vue
  11. 0 328
      src/views/lawyerManagement/lawyer/reviewDialog.vue
  12. 107 0
      src/views/lawyerManagement/legalScene/components/SceneDialog.vue
  13. 0 161
      src/views/lawyerManagement/legalScene/detailDialog.vue
  14. 286 174
      src/views/lawyerManagement/legalScene/index.vue
  15. 0 328
      src/views/lawyerManagement/legalScene/reviewDialog.vue
  16. 80 0
      src/views/lawyerManagement/professionalField/components/ProfessionalDialog.vue
  17. 0 161
      src/views/lawyerManagement/professionalField/detailDialog.vue
  18. 51 164
      src/views/lawyerManagement/professionalField/index.vue
  19. 0 328
      src/views/lawyerManagement/professionalField/reviewDialog.vue
  20. 0 1
      src/views/lawyerManagement/reconciliation/detailDialog.vue
  21. 215 138
      src/views/lawyerManagement/reconciliation/index.vue
  22. 0 328
      src/views/lawyerManagement/reconciliation/reviewDialog.vue
  23. 1 0
      src/views/login/components/LoginForm.vue
  24. 160 0
      src/views/userManagement/components/UserDialog.vue
  25. 0 161
      src/views/userManagement/detailDialog.vue
  26. 47 146
      src/views/userManagement/index.vue
  27. 0 328
      src/views/userManagement/reviewDialog.vue

+ 2 - 1
src/api/interface/index.ts

@@ -87,12 +87,13 @@ export namespace User {
     user: { detail: { age: number } }; // 用户详细信息
     userPhone?: string; // 联系电话
     loginAccount?: string; // 登录账号
-    roleId?: string; // 角色ID
+    loginPassword?: string; // 登录密码
     idCard: string; // 身份证号
     email: string; // 邮箱
     address: string; // 地址
     createTime: string; // 创建时间
     status: number; // 状态
+    roleId?: string; // 关联律所ID
     roleName?: string; // 角色名称
     remark?: string; // 备注
     avatar: string; // 头像

+ 78 - 0
src/api/modules/lawyer.ts

@@ -0,0 +1,78 @@
+import http from "@/api";
+
+/**
+ * @name 律师管理模块
+ */
+
+// 律所列表
+export const getLawFirmPage = (params: any) => {
+  return http.get(`/lawyer/firm/getPage`, params);
+};
+// 新增律所
+export const addLawFirm = (params: any) => {
+  return http.post(`/lawyer/firm/addLawFirm`, params);
+};
+// 删除律所
+export const deleteLawFirm = (params: any) => {
+  return http.delete(`/lawyer/firm/deleteLawFirm`, params);
+};
+// 编辑律所
+export const editLawFirm = (params: any) => {
+  return http.post(`/lawyer/firm/editLawFirm`, params);
+};
+
+// 律师列表
+export const getLawyerPage = (params: any) => {
+  return http.get(`/lawyer/user/getLawyerList`, params);
+};
+// 新增律师
+export const addLawyerUser = (params: any) => {
+  return http.post(`/lawyer/user/addLawyerUser`, params);
+};
+// 删除律师
+export const deleteLawyerUser = (params: any) => {
+  return http.delete(`/lawyer/user/deleteLawyerUser`, params);
+};
+// 编辑律师
+export const editLawyerUser = (params: any) => {
+  return http.post(`/lawyer/user/editLawyerUser`, params);
+};
+
+// 法律问题场景 列表查询
+export const getScenePage = (params: any) => {
+  return http.get(`/lawyer/legalProblemScenar/getPage`, params);
+};
+// 新增法律问题场景
+export const addScene = (params: any) => {
+  return http.post(`/lawyer/legalProblemScenar/addLawyerLegalProblemScenar`, params);
+};
+//  删除法律问题场景
+export const deleteScene = (params: any) => {
+  return http.delete(`/lawyer/legalProblemScenar/deleteLawyerLegalProblemScenar`, params);
+};
+// 编辑法律问题场景
+export const editScene = (params: any) => {
+  return http.post(`/lawyer/legalProblemScenar/editLawyerLegalProblemScenar`, params);
+};
+
+// 擅长领域 列表查询
+export const getExpertiseAreaPage = (params: any) => {
+  return http.get(`/lawyer/expertiseArea/getPage`, params);
+};
+// 新增擅长领域
+export const addExpertiseArea = (params: any) => {
+  return http.post(`/lawyer/expertiseArea/addExpertiseArea`, params);
+};
+//  删除擅长领域
+export const deleteExpertiseArea = (params: any) => {
+  return http.delete(`/lawyer/expertiseArea/deleteExpertiseArea`, params);
+};
+//  编辑擅长领域
+export const editExpertiseArea = (params: any) => {
+  return http.post(`/lawyer/expertiseArea/editExpertiseArea`, params);
+};
+
+//  律师账单/申请列表
+export const getApplicationExpertList = (params: any) => {
+  return http.get(`/lifeUserExpert/getApplicationExpertList`, params);
+};

+ 17 - 0
src/api/modules/user.ts

@@ -0,0 +1,17 @@
+import http from "@/api";
+
+/**
+ * @name 用户管理模块
+ */
+// 获取用户列表
+export const getUserList = (params: any) => {
+  return http.get(`/platformLifeUser/getUserList`, params);
+};
+// 新增用户
+export const addUser = (params: any) => {
+  return http.get(`/platformLifeUser/getUserList`, params);
+};
+// 编辑用户列表
+export const editUser = (params: any) => {
+  return http.get(`/platformLifeUser/getUserList`, params);
+};

+ 188 - 0
src/views/lawyerManagement/lawFirm/components/LawFirmDialog.vue

@@ -0,0 +1,188 @@
+<template>
+  <el-dialog v-model="visible" :title="dialogTitle" width="520px" destroy-on-close>
+    <el-form ref="formRef" :model="form" :rules="rules" label-width="140px" label-suffix=":">
+      <el-form-item label="律所名称" prop="firmName">
+        <el-input v-model="form.firmName" placeholder="请输入律所名称" clearable />
+      </el-form-item>
+      <el-form-item label="负责人姓名" prop="directorName">
+        <el-input v-model="form.directorName" placeholder="请输入负责人姓名" clearable />
+      </el-form-item>
+      <el-form-item label="律所位置" prop="address">
+        <el-input v-model="form.address" placeholder="请输入律所位置" clearable />
+      </el-form-item>
+      <el-form-item label="收款账号" prop="paymentAccount">
+        <div class="bank-card-input">
+          <el-input v-model="bankCardDisplay" placeholder="请输入银行卡号" maxlength="23" clearable />
+          <el-button type="primary" link @click="handleAddCard"> 添加 </el-button>
+        </div>
+        <div class="bank-card-tags">
+          <el-tag v-for="(card, index) in bankCardList" :key="card" closable @close="removeCard(index)">
+            {{ formatCard(card) }}
+          </el-tag>
+        </div>
+        <div class="bank-card-tip">最多可添加 3 张银行卡</div>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="visible = false"> 取消 </el-button>
+      <el-button type="primary" :loading="loading" @click="handleSubmit"> 确定 </el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, computed, nextTick } from "vue";
+import { ElMessage, type FormInstance } from "element-plus";
+
+interface DialogOptions {
+  title: string;
+  row?: Record<string, any>;
+  onSubmit: (params: Record<string, any>) => Promise<any>;
+}
+
+const visible = ref(false);
+const loading = ref(false);
+const formRef = ref<FormInstance>();
+const options = ref<DialogOptions>({
+  title: "",
+  row: {},
+  onSubmit: async () => Promise.resolve()
+});
+const form = reactive({
+  id: "",
+  firmName: "",
+  directorName: "",
+  address: "",
+  paymentAccount: ""
+});
+const bankCardInput = ref("");
+const bankCardList = ref<string[]>([]);
+const bankCardReg = /^([1-9]\d{15}|\d{17}|\d{18}|\d{19})$/;
+
+const rules = reactive({
+  firmName: [{ required: true, message: "请输入律所名称", trigger: "blur" }],
+  directorName: [{ required: true, message: "请输入负责人姓名", trigger: "blur" }],
+  address: [{ required: true, message: "请输入律所位置", trigger: "blur" }],
+  paymentAccount: [
+    {
+      validator: (_rule, _value, callback) => {
+        if (!bankCardList.value.length) {
+          callback(new Error("请至少添加一张银行卡"));
+        } else {
+          callback();
+        }
+      },
+      trigger: "change"
+    }
+  ]
+});
+
+const dialogTitle = computed(() => options.value.title);
+
+const resetForm = () => {
+  form.id = "";
+  form.firmName = "";
+  form.directorName = "";
+  form.address = "";
+  form.paymentAccount = "";
+  bankCardInput.value = "";
+  bankCardList.value = [];
+};
+
+const open = (payload: DialogOptions) => {
+  resetForm();
+  options.value = payload;
+  if (payload.row) {
+    Object.assign(form, payload.row);
+    const cards = (form.paymentAccount || "")
+      .split(",")
+      .map(card => card.trim())
+      .filter(card => !!card);
+    bankCardList.value = cards.slice(0, 3);
+    form.paymentAccount = bankCardList.value.join(",");
+  }
+  visible.value = true;
+  nextTick(() => formRef.value?.clearValidate());
+};
+
+const emits = defineEmits<{
+  success: [];
+}>();
+
+const syncPaymentAccount = () => {
+  form.paymentAccount = bankCardList.value.join(",");
+  formRef.value?.validateField("paymentAccount").catch(() => {});
+};
+
+const bankCardDisplay = computed({
+  get: () => formatCard(bankCardInput.value),
+  set: val => {
+    bankCardInput.value = val.replace(/\s+/g, "");
+  }
+});
+
+const handleAddCard = () => {
+  const card = bankCardInput.value.trim();
+  if (!card) return ElMessage.warning("请输入银行卡号");
+  if (!bankCardReg.test(card)) return ElMessage.error("请输入正确的银行卡号");
+  if (bankCardList.value.includes(card)) return ElMessage.warning("银行卡号已存在");
+  if (bankCardList.value.length >= 3) return ElMessage.warning("最多可添加 3 张银行卡");
+  bankCardList.value.push(card);
+  bankCardInput.value = "";
+  syncPaymentAccount();
+};
+
+const removeCard = (index: number) => {
+  bankCardList.value.splice(index, 1);
+  syncPaymentAccount();
+};
+
+const formatCard = (card: string) => {
+  return card
+    .replace(/\s+/g, "")
+    .replace(/(.{4})/g, "$1 ")
+    .trim();
+};
+
+const handleSubmit = () => {
+  syncPaymentAccount();
+  formRef.value?.validate(async valid => {
+    if (!valid) return;
+    loading.value = true;
+    try {
+      await options.value.onSubmit({ ...form });
+      emits("success");
+      visible.value = false;
+    } finally {
+      loading.value = false;
+    }
+  });
+};
+
+defineExpose({
+  open
+});
+</script>
+
+<style scoped lang="scss">
+.bank-card-input {
+  display: flex;
+  gap: 12px;
+  align-items: stretch;
+  margin-bottom: 8px;
+}
+.bank-card-input .el-input {
+  flex: 1;
+}
+.bank-card-tags {
+  display: flex;
+  flex-wrap: wrap;
+}
+.bank-card-tip {
+  display: flex;
+  padding-bottom: 0;
+  margin-left: 4px;
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+}
+</style>

+ 0 - 161
src/views/lawyerManagement/lawFirm/detailDialog.vue

@@ -1,161 +0,0 @@
-<template>
-  <el-dialog v-model="dialogShow" title="达人详情" draggable width="700px" @close="handleClose">
-    <div v-if="detailData.imgUrl.length > 0" class="image-gallery">
-      <div class="image-grid">
-        <div v-for="(url, index) in detailData.imgUrl" :key="index" class="image-item">
-          <el-image
-            :src="url"
-            :preview-src-list="detailData.imgUrl"
-            fit="cover"
-            class="gallery-image"
-            :hide-on-click-modal="false"
-            :preview-teleported="true"
-          />
-        </div>
-      </div>
-    </div>
-
-    <!-- 详情 -->
-    <div class="review-detail">
-      <el-descriptions :column="1" border>
-        <el-descriptions-item label="用户ID">
-          {{ detailData.id }}
-        </el-descriptions-item>
-        <el-descriptions-item label="用户昵称">
-          {{ detailData.userName }}
-        </el-descriptions-item>
-        <el-descriptions-item label="姓名">
-          {{ detailData.realName }}
-        </el-descriptions-item>
-        <el-descriptions-item label="身份证号码">
-          {{ detailData.idCard }}
-        </el-descriptions-item>
-        <el-descriptions-item label="联系电话">
-          {{ detailData.userPhone }}
-        </el-descriptions-item>
-
-        <el-descriptions-item label="推广模块">
-          <el-tag v-for="(item, index) in detailData.promoteType" :key="index" style="margin-right: 5px; margin-bottom: 5px">
-            {{ item }}
-          </el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="状态">
-          <el-tag v-if="detailData.expertStatus == 0" type="success"> 已通过 </el-tag>
-          <el-tag v-if="detailData.expertStatus == 1" type="primary"> 待审核 </el-tag>
-          <el-tag v-if="detailData.expertStatus == 2" type="danger"> 已驳回 </el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="审核时间" v-if="detailData.reviewTime">
-          {{ detailData.reviewTime || "未审核" }}
-        </el-descriptions-item>
-        <el-descriptions-item label="驳回原因" v-if="detailData.refuseBecause">
-          {{ detailData.refuseBecause }}
-        </el-descriptions-item>
-        <el-descriptions-item label="简介">
-          <div class="introduction">
-            {{ detailData.addExplanation || "--" }}
-          </div>
-        </el-descriptions-item>
-      </el-descriptions>
-    </div>
-  </el-dialog>
-</template>
-
-<script setup lang="ts">
-import { ref, defineExpose, defineEmits } from "vue";
-import { getViewDetails } from "@/api/modules/masterManagemen";
-import { ElMessage } from "element-plus";
-
-// 定义弹窗状态
-const dialogShow = ref(false);
-
-// 详情数据
-const detailData: any = ref({});
-
-const emit = defineEmits(["close"]);
-// 显示弹窗方法
-const open = async (userId: string | number) => {
-  try {
-    const res: any = await getViewDetails(userId);
-    detailData.value = {
-      ...res.data
-    };
-    if (res.data.imgUrl) {
-      detailData.value.imgUrl = res.data.imgUrl.split(",");
-    }
-    if (res.data.promoteType) {
-      detailData.value.promoteType = res.data.promoteType.split(",");
-    }
-    dialogShow.value = true;
-  } catch (error) {
-    ElMessage.error("加载详情失败,请重试");
-    handleClose();
-  }
-};
-
-// 关闭弹窗
-const handleClose = () => {
-  dialogShow.value = false;
-};
-
-// 暴露给父组件的方法
-defineExpose({
-  open
-});
-</script>
-
-<style lang="scss" scoped>
-/* 样式保持不变 */
-.review-detail {
-  margin-bottom: 20px;
-  .introduction {
-    line-height: 1.5;
-    word-break: break-word;
-    white-space: pre-wrap;
-  }
-}
-.form-tip {
-  margin-top: 5px;
-  font-size: 12px;
-  color: #909399;
-}
-.image-gallery {
-  padding: 10px;
-  margin-bottom: 20px;
-  background-color: #f5f7fa;
-  border-radius: 4px;
-  .image-grid {
-    display: flex;
-    flex-wrap: wrap;
-    gap: 10px;
-    .image-item {
-      width: 100px;
-      height: 100px;
-      cursor: pointer;
-      transition: all 0.3s;
-      &:hover {
-        box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
-        transform: scale(1.05);
-      }
-      .gallery-image {
-        width: 100%;
-        height: 100%;
-        overflow: hidden;
-        border-radius: 4px;
-        :deep(.el-image__inner) {
-          width: 100%;
-          height: 100%;
-          object-fit: cover;
-        }
-        :deep(.el-image__error) {
-          display: flex;
-          align-items: center;
-          justify-content: center;
-          font-size: 12px;
-          color: #909399;
-          background-color: #f0f2f5;
-        }
-      }
-    }
-  }
-}
-</style>

+ 90 - 102
src/views/lawyerManagement/lawFirm/index.vue

@@ -1,138 +1,126 @@
 <template>
   <div class="table-box">
     <ProTable ref="proTable" :columns="columns" :request-api="getTableList" :data-callback="dataCallback">
-      <template #expertStatus="scope">
-        <el-tag v-if="scope.row.expertStatus == 0" type="success"> 已通过 </el-tag>
-        <el-tag v-if="scope.row.expertStatus == 1" type="primary"> 待审核 </el-tag>
-        <el-tag v-if="scope.row.expertStatus == 2" type="danger"> 已驳回 </el-tag>
+      <template #tableHeader>
+        <el-button type="primary" :icon="CirclePlus" @click="handleCreate"> 新增律所 </el-button>
       </template>
-      <template #promoteType="scope">
-        <el-tag v-for="(item, index) in getPromoteTypes(scope.row.promoteType)" :key="index">
-          {{ item }}
-        </el-tag>
+      <template #paymentAccount="scope">
+        <div class="payment-tags">
+          <el-tag v-for="card in splitPayment(scope.row.paymentAccount)" :key="card">
+            {{ formatCard(card) }}
+          </el-tag>
+        </div>
       </template>
-
       <template #operation="scope">
-        <el-button type="primary" :icon="Search" link @click="handleDetail(scope.row)"> 查看详情 </el-button>
+        <el-button type="primary" link :icon="EditPen" @click="handleEdit(scope.row)"> 编辑 </el-button>
+        <el-button type="danger" link :icon="Delete" @click="handleDelete(scope.row)"> 删除 </el-button>
       </template>
     </ProTable>
-
-    <DetailDialog ref="detailDialog" />
+    <LawFirmDialog ref="lawFirmDialogRef" @success="refreshTable" />
   </div>
 </template>
 
 <script setup lang="ts">
-import DetailDialog from "./detailDialog.vue";
 import { ref, reactive, onActivated } from "vue";
-import type { Course } from "@/api/interface";
-import { ElMessage } from "element-plus";
+import { ElMessage, ElMessageBox } from "element-plus";
 import ProTable from "@/components/ProTable/index.vue";
 import type { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
-import { becomeExpert } from "@/api/modules/user";
-import { getApplicationExpertList } from "@/api/modules/masterManagemen";
-import { Search, Setting } from "@element-plus/icons-vue";
+import { getLawFirmPage, addLawFirm, deleteLawFirm, editLawFirm } from "@/api/modules/lawyer";
+import { CirclePlus, Delete, EditPen } from "@element-plus/icons-vue";
+import LawFirmDialog from "./components/LawFirmDialog.vue";
 
 const proTable = ref<ProTableInstance>();
+const lawFirmDialogRef = ref<InstanceType<typeof LawFirmDialog>>();
 
-const detailDialog = ref<any>(null);
-
-const columns = reactive<ColumnProps<Course.ReqCourseParams>[]>([
-  {
-    label: "序号",
-    type: "index",
-    width: 60,
-    align: "center"
-  },
-  {
-    label: "ID",
-    prop: "id",
-    width: 80
-  },
-  {
-    label: "律师姓名",
-    prop: "userName",
-    search: { el: "input", tooltip: "请输入用户昵称" }
-  },
-  {
-    label: "律师头像",
-    prop: "realName"
-  },
-  {
-    label: "律师执业证号",
-    prop: "userPhone"
-  },
-  {
-    label: "订单数量",
-    prop: "promoteType"
-  },
-  {
-    label: "订单金额",
-    prop: "createdTime"
-  },
+const columns = reactive<ColumnProps<any>[]>([
+  { label: "序号", type: "index", width: 60, align: "center" },
+  { label: "律所名称", prop: "firmName", search: { el: "input", props: { placeholder: "请输入律所名称" } } },
   {
-    label: "平台信息服务费",
-    prop: "createdTime"
+    label: "负责人姓名",
+    prop: "directorName",
+    width: 160,
+    search: { el: "input", props: { placeholder: "请输入负责人姓名" } }
   },
-  {
-    label: "订单时间",
-    prop: "time",
-    isShow: false, // 关键:不在表格中显示
-    search: {
-      label: "申请时间范围", // 搜索区域显示的标签
-      el: "date-picker",
-      props: {
-        type: "datetimerange",
-        valueFormat: "YYYY-MM-DD HH:mm:ss",
-        rangeSeparator: "至",
-        startPlaceholder: "开始时间",
-        endPlaceholder: "结束时间"
-      }
-    }
-  },
-  {
-    label: "操作",
-    prop: "operation",
-    width: 200
-  }
+  { label: "位置", prop: "address" },
+  { label: "收款账号", prop: "paymentAccount", align: "center" },
+  { label: "操作", prop: "operation", width: 200, fixed: "right" }
 ]);
+
 const getTableList = async (params: any) => {
-  let tempParams = JSON.parse(JSON.stringify(params));
-  delete tempParams.time;
-  // 深拷贝原始参数
-  let newParams = JSON.parse(JSON.stringify(tempParams));
-  newParams.page = newParams.pageNum;
-  newParams.size = newParams.pageSize;
+  const newParams = { ...params, page: params.pageNum, size: params.pageSize };
   delete newParams.pageNum;
   delete newParams.pageSize;
-  if (params.time) {
-    newParams.createdTime = params.time[0];
-    newParams.endTime = params.time[1];
-  }
+  return getLawFirmPage(newParams);
+};
+
+const dataCallback = (data: any) => ({
+  list: data.records,
+  total: data.total
+});
 
-  const res = await getApplicationExpertList(newParams);
-  return res;
+const refreshTable = () => {
+  proTable.value?.getTableList();
 };
-const dataCallback = (data: any) => {
-  return {
-    list: data.records,
-    total: data.total
-  };
+
+const splitPayment = (value: string) => {
+  if (!value) return [];
+  return value
+    .split(",")
+    .map(card => card.trim())
+    .filter(Boolean);
+};
+
+const formatCard = (card: string) => {
+  return card
+    .replace(/\s+/g, "")
+    .replace(/(.{4})/g, "$1 ")
+    .trim();
+};
+
+const handleCreate = () => {
+  lawFirmDialogRef.value?.open({
+    title: "新增律所",
+    onSubmit: async payload => {
+      await addLawFirm(payload);
+      ElMessage.success("新增成功");
+    }
+  });
 };
 
-// 处理推广板块字符串
-const getPromoteTypes = (promoteType: string) => {
-  if (!promoteType) return [];
-  return promoteType.split(",");
+const handleEdit = (row: any) => {
+  lawFirmDialogRef.value?.open({
+    title: "编辑律所",
+    row,
+    onSubmit: async payload => {
+      await editLawFirm(payload);
+      ElMessage.success("编辑成功");
+    }
+  });
 };
 
-//详情
-const handleDetail = row => {
-  detailDialog.value?.open(row.userId);
+const handleDelete = (row: any) => {
+  ElMessageBox.confirm(`确定删除律所【${row.firmName}】吗?`, "提示", {
+    type: "warning"
+  })
+    .then(async () => {
+      await deleteLawFirm({ id: row.id });
+      ElMessage.success("删除成功");
+      refreshTable();
+    })
+    .catch(() => {});
 };
 
 onActivated(() => {
-  proTable.value?.getTableList();
+  refreshTable();
 });
 </script>
 
-<style scoped lang="scss"></style>
+<style scoped lang="scss">
+.payment-tags {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 16px;
+  justify-content: center;
+  width: 100%;
+}
+</style>

+ 0 - 328
src/views/lawyerManagement/lawFirm/reviewDialog.vue

@@ -1,328 +0,0 @@
-<template>
-  <el-dialog v-model="dialogShow" title="达人审核" draggable width="700px" @close="handleClose">
-    <div v-if="imageUrls.length > 0" class="image-gallery">
-      <div class="image-grid">
-        <div v-for="(url, index) in imageUrls" :key="index" class="image-item">
-          <el-image
-            :src="url"
-            :preview-src-list="imageUrls"
-            fit="cover"
-            class="gallery-image"
-            :hide-on-click-modal="false"
-            :preview-teleported="true"
-          />
-        </div>
-      </div>
-    </div>
-
-    <!-- 详情展示区域 -->
-    <div class="review-detail">
-      <el-descriptions :column="1" border>
-        <el-descriptions-item label="ID">
-          {{ detailData.id }}
-        </el-descriptions-item>
-        <el-descriptions-item label="用户昵称">
-          {{ detailData.userName }}
-        </el-descriptions-item>
-        <el-descriptions-item label="姓名">
-          {{ detailData.realName }}
-        </el-descriptions-item>
-        <el-descriptions-item label="身份证号码">
-          {{ detailData.idCard }}
-        </el-descriptions-item>
-        <el-descriptions-item label="联系电话">
-          {{ detailData.userPhone }}
-        </el-descriptions-item>
-
-        <el-descriptions-item label="推广模块">
-          <el-tag
-            v-for="(item, index) in getPromoteTypes(detailData.promoteType)"
-            :key="index"
-            style="margin-right: 5px; margin-bottom: 5px"
-          >
-            {{ item }}
-          </el-tag>
-        </el-descriptions-item>
-
-        <el-descriptions-item label="状态">
-          <el-tag v-if="detailData.expertStatus == 0" type="success"> 已通过 </el-tag>
-          <el-tag v-if="detailData.expertStatus == 1" type="primary"> 待审核 </el-tag>
-          <el-tag v-if="detailData.expertStatus == 2" type="danger"> 已驳回 </el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="申请时间">
-          {{ detailData.createdTime }}
-        </el-descriptions-item>
-        <el-descriptions-item label="简介">
-          <div class="introduction">
-            {{ detailData.addExplanation }}
-          </div>
-        </el-descriptions-item>
-      </el-descriptions>
-    </div>
-
-    <!-- 操作按钮区域 -->
-    <template #footer>
-      <span class="dialog-footer">
-        <el-button type="success" @click="openCommissionDialog"> 同意 </el-button>
-        <el-button type="warning" @click="openRejectDialog"> 驳回 </el-button>
-        <el-button @click="handleClose">关闭</el-button>
-      </span>
-    </template>
-
-    <!-- 设置佣金及预付款弹窗 -->
-    <el-dialog title="设置佣金及预付款" v-model="commissionDialogVisible" width="400px" append-to-body>
-      <el-form :model="commissionForm" :rules="commissionRules" ref="commissionFormRef" label-width="120px">
-        <el-form-item label="佣金比例" prop="commissionRate">
-          <el-input-number
-            v-model="commissionForm.commissionRate"
-            :min="0"
-            :max="99"
-            :precision="0"
-            controls-position="right"
-            style="width: 80%"
-          />
-          <div class="form-tip">范围:0-99的整数,表示百分比</div>
-        </el-form-item>
-        <el-form-item label="预付款比例" prop="advanceRate">
-          <el-input-number
-            v-model="commissionForm.advanceRate"
-            :min="1"
-            :max="99"
-            :precision="0"
-            controls-position="right"
-            style="width: 80%"
-          />
-          <div class="form-tip">范围:1-99的整数,表示百分比</div>
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="cancelCommissionDialog">取消</el-button>
-          <el-button type="primary" @click="submitCommission">确认</el-button>
-        </span>
-      </template>
-    </el-dialog>
-
-    <!-- 驳回原因弹窗 -->
-    <el-dialog title="驳回原因" v-model="rejectDialogVisible" width="400px" append-to-body>
-      <el-form :model="rejectForm" :rules="rejectRules" ref="rejectFormRef" label-width="80px">
-        <el-form-item label="原因" prop="comment">
-          <el-input v-model="rejectForm.comment" type="textarea" :rows="3" placeholder="请输入驳回原因" />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="cancelRejectDialog">取消</el-button>
-          <el-button type="primary" @click="submitReject">确认</el-button>
-        </span>
-      </template>
-    </el-dialog>
-  </el-dialog>
-</template>
-
-<script setup lang="ts">
-import { ref, reactive, defineExpose, defineEmits, computed } from "vue";
-import { ElMessageBox } from "element-plus";
-import type { FormInstance, FormRules } from "element-plus";
-
-// 定义弹窗状态
-const dialogShow = ref(false);
-const commissionDialogVisible = ref(false);
-const rejectDialogVisible = ref(false);
-
-// 详情数据
-const detailData: any = ref({});
-
-// 处理图片URL
-const imageUrls = computed(() => {
-  if (!detailData.value.imgUrl) return [];
-  return detailData.value.imgUrl.split(",");
-});
-
-// 处理推广板块字符串
-const getPromoteTypes = (promoteType: string) => {
-  if (!promoteType) return [];
-  return promoteType.split(",");
-};
-
-// 佣金表单
-const commissionForm = reactive({
-  commissionRate: 0,
-  advanceRate: 1
-});
-
-// 驳回表单
-const rejectForm = reactive({
-  comment: ""
-});
-
-// 佣金表单验证规则
-const commissionRules: FormRules = {
-  commissionRate: [
-    { required: true, message: "请输入佣金比例", trigger: "blur" },
-    { type: "number", min: 0, max: 99, message: "佣金比例必须是0-99的整数", trigger: "blur" }
-  ],
-  advanceRate: [
-    { required: true, message: "请输入预付款比例", trigger: "blur" },
-    { type: "number", min: 1, max: 99, message: "预付款比例必须是1-99的整数", trigger: "blur" }
-  ]
-};
-
-// 驳回表单验证规则
-const rejectRules: FormRules = {
-  comment: [
-    { required: true, message: "请输入驳回原因", trigger: "blur" },
-    { min: 2, max: 200, message: "驳回原因长度为2-200个字符", trigger: "blur" }
-  ]
-};
-
-// 表单引用
-const commissionFormRef = ref<FormInstance>();
-const rejectFormRef = ref<FormInstance>();
-
-// 事件触发
-const emit = defineEmits(["approve", "reject", "close"]);
-
-// 显示弹窗方法
-const open = (data: any) => {
-  detailData.value = { ...data };
-  dialogShow.value = true;
-};
-
-// 关闭弹窗
-const handleClose = () => {
-  dialogShow.value = false;
-  emit("close");
-};
-
-// 打开佣金设置弹窗
-const openCommissionDialog = () => {
-  commissionForm.commissionRate = 0;
-  commissionForm.advanceRate = 1;
-  commissionDialogVisible.value = true;
-};
-
-// 取消佣金设置
-const cancelCommissionDialog = () => {
-  commissionDialogVisible.value = false;
-};
-
-// 提交佣金设置
-const submitCommission = async () => {
-  if (!commissionFormRef.value) return;
-
-  const valid = await commissionFormRef.value.validate();
-  if (!valid) return;
-
-  // 显示确认提示
-  try {
-    await ElMessageBox.confirm(
-      `确认同意申请并设置佣金比例为 ${commissionForm.commissionRate}%,预付款比例为 ${commissionForm.advanceRate}%?`,
-      "确认同意",
-      {
-        confirmButtonText: "确认",
-        cancelButtonText: "取消",
-        type: "warning"
-      }
-    );
-
-    // 触发同意事件
-    emit("approve", {
-      id: detailData.value.id,
-      commissionRate: commissionForm.commissionRate,
-      advanceRate: commissionForm.advanceRate,
-      userPhone: detailData.value.userPhone
-    });
-
-    // 关闭所有弹窗
-    commissionDialogVisible.value = false;
-    dialogShow.value = false;
-  } catch (error) {}
-};
-
-// 打开驳回原因弹窗
-const openRejectDialog = () => {
-  rejectForm.comment = "";
-  rejectDialogVisible.value = true;
-};
-
-// 取消驳回
-const cancelRejectDialog = () => {
-  rejectDialogVisible.value = false;
-};
-
-// 提交驳回
-const submitReject = async () => {
-  if (!rejectFormRef.value) return;
-
-  const valid = await rejectFormRef.value.validate();
-  if (!valid) return;
-
-  // 显示确认提示
-  try {
-    await ElMessageBox.confirm("确认驳回申请?", "确认驳回", {
-      confirmButtonText: "确认",
-      cancelButtonText: "取消",
-      type: "warning"
-    });
-
-    // 触发驳回事件
-    emit("reject", {
-      id: detailData.value.id,
-      comment: rejectForm.comment,
-      userPhone: detailData.value.userPhone
-    });
-
-    // 关闭所有弹窗
-    rejectDialogVisible.value = false;
-    dialogShow.value = false;
-  } catch (error) {}
-};
-
-// 暴露给父组件的方法
-defineExpose({
-  open,
-  close: handleClose
-});
-</script>
-
-<style lang="scss" scoped>
-.review-detail {
-  margin-bottom: 20px;
-  .introduction {
-    line-height: 1.5;
-    word-break: break-word;
-    white-space: pre-wrap;
-  }
-}
-.form-tip {
-  margin-top: 5px;
-  font-size: 12px;
-  color: #909399;
-}
-
-/* 隐藏预览时的操作栏 */
-:deep(.el-image-viewer__btn) {
-  display: none !important;
-}
-
-/* 或者更精确地隐藏操作栏区域 */
-:deep(.el-image-viewer__actions) {
-  display: none !important;
-}
-.image-grid {
-  display: grid;
-  grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
-  gap: 8px;
-}
-.image-item {
-  aspect-ratio: 1;
-  overflow: hidden;
-}
-.gallery-image {
-  width: 100%;
-  height: 100%;
-  cursor: pointer;
-  object-fit: cover;
-}
-</style>

+ 154 - 0
src/views/lawyerManagement/lawyer/components/LawyerDialog.vue

@@ -0,0 +1,154 @@
+<template>
+  <el-dialog v-model="visible" :title="dialogTitle" width="600px" destroy-on-close>
+    <el-form ref="formRef" :model="form" :rules="rules" label-width="120px" label-suffix=":">
+      <el-form-item label="律师姓名" prop="name">
+        <el-input v-model="form.name" placeholder="请输入律师姓名" clearable />
+      </el-form-item>
+      <el-form-item label="联系电话" prop="phone">
+        <el-input v-model="form.phone" placeholder="请输入联系电话" maxlength="11" clearable />
+      </el-form-item>
+      <el-form-item label="从业时间" prop="practiceStartDate">
+        <el-date-picker
+          v-model="form.practiceStartDate"
+          type="date"
+          placeholder="请选择从业时间"
+          value-format="YYYY-MM-DD"
+          style="width: 100%"
+        />
+      </el-form-item>
+      <el-form-item label="专业领域" prop="specialtyFields">
+        <el-select v-model="form.specialtyFields" placeholder="请选择专业领域" filterable style="width: 100%">
+          <el-option v-for="item in props.professionalOptions" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="法律场景" prop="expertiseCases">
+        <el-tree-select
+          v-model="form.expertiseCases"
+          :data="props.sceneOptions"
+          :props="sceneTreeProps"
+          check-strictly
+          placeholder="请选择法律场景"
+          style="width: 100%"
+          clearable
+        />
+      </el-form-item>
+      <el-form-item label="接单状态" prop="status">
+        <el-switch v-model="form.status" :active-value="1" :inactive-value="0" />
+      </el-form-item>
+      <el-form-item label="收款账号" prop="paymentAccount">
+        <el-input v-model="form.paymentAccount" placeholder="请输入收款账号" clearable />
+      </el-form-item>
+      <el-form-item label="所属律所" prop="lawFirmId">
+        <el-select v-model="form.lawFirmId" placeholder="请选择所属律所" filterable>
+          <el-option v-for="item in lawFirmOptions" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="visible = false"> 取消 </el-button>
+      <el-button type="primary" :loading="loading" @click="handleSubmit"> 确定 </el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, computed, nextTick } from "vue";
+import type { FormInstance } from "element-plus";
+
+interface DialogOptions {
+  title: string;
+  row?: Record<string, any>;
+  onSubmit: (params: Record<string, any>) => Promise<any>;
+}
+
+const props = defineProps<{
+  lawFirmOptions: { label: string; value: string | number }[];
+  sceneOptions: any[];
+  professionalOptions: { label: string | number; value: string | number }[];
+}>();
+
+const visible = ref(false);
+const loading = ref(false);
+const formRef = ref<FormInstance>();
+const options = ref<DialogOptions>({
+  title: "",
+  row: {},
+  onSubmit: async () => Promise.resolve()
+});
+
+const form = reactive({
+  id: "",
+  name: "",
+  phone: "",
+  practiceStartDate: "",
+  specialtyFields: "",
+  expertiseCases: "",
+  status: 1,
+  paymentAccount: "",
+  lawFirmId: ""
+});
+
+const rules = reactive({
+  name: [{ required: true, message: "请输入律师姓名", trigger: "blur" }],
+  phone: [
+    { required: true, message: "请输入联系电话", trigger: "blur" },
+    { pattern: /^1\d{10}$/, message: "请输入正确手机号", trigger: "blur" }
+  ],
+  practiceStartDate: [{ required: true, message: "请输入从业时间", trigger: "blur" }],
+  specialtyFields: [{ required: true, message: "请输入专业领域", trigger: "blur" }],
+  expertiseCases: [{ required: true, message: "请选择法律场景", trigger: "change" }],
+  paymentAccount: [{ required: true, message: "请输入收款账号", trigger: "blur" }],
+  lawFirmId: [{ required: true, message: "请选择所属律所", trigger: "change" }]
+});
+
+const dialogTitle = computed(() => options.value.title);
+
+const resetForm = () => {
+  form.id = "";
+  form.name = "";
+  form.phone = "";
+  form.practiceStartDate = "";
+  form.specialtyFields = "";
+  form.expertiseCases = "";
+  form.status = 1;
+  form.paymentAccount = "";
+  form.lawFirmId = "";
+};
+
+const sceneTreeProps = { value: "id", label: "name", children: "children" };
+
+const open = (payload: DialogOptions) => {
+  resetForm();
+  options.value = payload;
+  if (payload.row) {
+    Object.assign(form, payload.row);
+    if (payload.row.firmId && !payload.row.lawFirmId) {
+      form.lawFirmId = payload.row.firmId;
+    }
+  }
+  visible.value = true;
+  nextTick(() => formRef.value?.clearValidate());
+};
+
+const emits = defineEmits<{
+  success: [];
+}>();
+
+const handleSubmit = () => {
+  formRef.value?.validate(async valid => {
+    if (!valid) return;
+    loading.value = true;
+    try {
+      await options.value.onSubmit({ ...form });
+      emits("success");
+      visible.value = false;
+    } finally {
+      loading.value = false;
+    }
+  });
+};
+
+defineExpose({
+  open
+});
+</script>

+ 0 - 161
src/views/lawyerManagement/lawyer/detailDialog.vue

@@ -1,161 +0,0 @@
-<template>
-  <el-dialog v-model="dialogShow" title="达人详情" draggable width="700px" @close="handleClose">
-    <div v-if="detailData.imgUrl.length > 0" class="image-gallery">
-      <div class="image-grid">
-        <div v-for="(url, index) in detailData.imgUrl" :key="index" class="image-item">
-          <el-image
-            :src="url"
-            :preview-src-list="detailData.imgUrl"
-            fit="cover"
-            class="gallery-image"
-            :hide-on-click-modal="false"
-            :preview-teleported="true"
-          />
-        </div>
-      </div>
-    </div>
-
-    <!-- 详情 -->
-    <div class="review-detail">
-      <el-descriptions :column="1" border>
-        <el-descriptions-item label="用户ID">
-          {{ detailData.id }}
-        </el-descriptions-item>
-        <el-descriptions-item label="用户昵称">
-          {{ detailData.userName }}
-        </el-descriptions-item>
-        <el-descriptions-item label="姓名">
-          {{ detailData.realName }}
-        </el-descriptions-item>
-        <el-descriptions-item label="身份证号码">
-          {{ detailData.idCard }}
-        </el-descriptions-item>
-        <el-descriptions-item label="联系电话">
-          {{ detailData.userPhone }}
-        </el-descriptions-item>
-
-        <el-descriptions-item label="推广模块">
-          <el-tag v-for="(item, index) in detailData.promoteType" :key="index" style="margin-right: 5px; margin-bottom: 5px">
-            {{ item }}
-          </el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="状态">
-          <el-tag v-if="detailData.expertStatus == 0" type="success"> 已通过 </el-tag>
-          <el-tag v-if="detailData.expertStatus == 1" type="primary"> 待审核 </el-tag>
-          <el-tag v-if="detailData.expertStatus == 2" type="danger"> 已驳回 </el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="审核时间" v-if="detailData.reviewTime">
-          {{ detailData.reviewTime || "未审核" }}
-        </el-descriptions-item>
-        <el-descriptions-item label="驳回原因" v-if="detailData.refuseBecause">
-          {{ detailData.refuseBecause }}
-        </el-descriptions-item>
-        <el-descriptions-item label="简介">
-          <div class="introduction">
-            {{ detailData.addExplanation || "--" }}
-          </div>
-        </el-descriptions-item>
-      </el-descriptions>
-    </div>
-  </el-dialog>
-</template>
-
-<script setup lang="ts">
-import { ref, defineExpose, defineEmits } from "vue";
-import { getViewDetails } from "@/api/modules/masterManagemen";
-import { ElMessage } from "element-plus";
-
-// 定义弹窗状态
-const dialogShow = ref(false);
-
-// 详情数据
-const detailData: any = ref({});
-
-const emit = defineEmits(["close"]);
-// 显示弹窗方法
-const open = async (userId: string | number) => {
-  try {
-    const res: any = await getViewDetails(userId);
-    detailData.value = {
-      ...res.data
-    };
-    if (res.data.imgUrl) {
-      detailData.value.imgUrl = res.data.imgUrl.split(",");
-    }
-    if (res.data.promoteType) {
-      detailData.value.promoteType = res.data.promoteType.split(",");
-    }
-    dialogShow.value = true;
-  } catch (error) {
-    ElMessage.error("加载详情失败,请重试");
-    handleClose();
-  }
-};
-
-// 关闭弹窗
-const handleClose = () => {
-  dialogShow.value = false;
-};
-
-// 暴露给父组件的方法
-defineExpose({
-  open
-});
-</script>
-
-<style lang="scss" scoped>
-/* 样式保持不变 */
-.review-detail {
-  margin-bottom: 20px;
-  .introduction {
-    line-height: 1.5;
-    word-break: break-word;
-    white-space: pre-wrap;
-  }
-}
-.form-tip {
-  margin-top: 5px;
-  font-size: 12px;
-  color: #909399;
-}
-.image-gallery {
-  padding: 10px;
-  margin-bottom: 20px;
-  background-color: #f5f7fa;
-  border-radius: 4px;
-  .image-grid {
-    display: flex;
-    flex-wrap: wrap;
-    gap: 10px;
-    .image-item {
-      width: 100px;
-      height: 100px;
-      cursor: pointer;
-      transition: all 0.3s;
-      &:hover {
-        box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
-        transform: scale(1.05);
-      }
-      .gallery-image {
-        width: 100%;
-        height: 100%;
-        overflow: hidden;
-        border-radius: 4px;
-        :deep(.el-image__inner) {
-          width: 100%;
-          height: 100%;
-          object-fit: cover;
-        }
-        :deep(.el-image__error) {
-          display: flex;
-          align-items: center;
-          justify-content: center;
-          font-size: 12px;
-          color: #909399;
-          background-color: #f0f2f5;
-        }
-      }
-    }
-  }
-}
-</style>

+ 146 - 147
src/views/lawyerManagement/lawyer/index.vue

@@ -1,197 +1,196 @@
 <template>
   <div class="table-box">
     <ProTable ref="proTable" :columns="columns" :request-api="getTableList" :data-callback="dataCallback">
-      <template #expertStatus="scope">
-        <el-tag v-if="scope.row.expertStatus == 0" type="success"> 已通过 </el-tag>
-        <el-tag v-if="scope.row.expertStatus == 1" type="primary"> 待审核 </el-tag>
-        <el-tag v-if="scope.row.expertStatus == 2" type="danger"> 已驳回 </el-tag>
+      <template #tableHeader>
+        <el-button type="primary" :icon="CirclePlus" @click="handleCreate"> 新增律师 </el-button>
       </template>
-      <template #promoteType="scope">
-        <el-tag v-for="(item, index) in getPromoteTypes(scope.row.promoteType)" :key="index">
-          {{ item }}
-        </el-tag>
-      </template>
-
       <template #operation="scope">
-        <el-button type="primary" :icon="Search" link @click="handleDetail(scope.row)"> 查看详情 </el-button>
-        <el-button type="primary" :icon="Setting" v-if="scope.row.expertStatus == 1" link @click="handleReview(scope.row)">
-          审核
-        </el-button>
+        <el-button type="primary" link :icon="EditPen" @click="handleEdit(scope.row)"> 编辑 </el-button>
+        <el-button type="danger" link :icon="Delete" @click="handleDelete(scope.row)"> 删除 </el-button>
       </template>
     </ProTable>
-
-    <ReviewDialog ref="reviewDialog" @approve="handleApprove" @reject="handleReject" />
-    <DetailDialog ref="detailDialog" />
+    <LawyerDialog
+      ref="lawyerDialogRef"
+      :law-firm-options="lawFirmOptions"
+      :scene-options="sceneOptions"
+      :professional-options="professionalOptions"
+      @success="refreshTable"
+    />
   </div>
 </template>
 
 <script setup lang="ts">
-import ReviewDialog from "./reviewDialog.vue";
-import DetailDialog from "./detailDialog.vue";
-import { ref, reactive, onActivated } from "vue";
-import type { Course } from "@/api/interface";
-import { ElMessage } from "element-plus";
+import { ref, reactive, onMounted } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
 import ProTable from "@/components/ProTable/index.vue";
 import type { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
-import { becomeExpert } from "@/api/modules/user";
-import { getApplicationExpertList } from "@/api/modules/masterManagemen";
-import { Search, Setting } from "@element-plus/icons-vue";
+import {
+  getLawyerPage,
+  addLawyerUser,
+  deleteLawyerUser,
+  editLawyerUser,
+  getLawFirmPage,
+  getScenePage,
+  getExpertiseAreaPage
+} from "@/api/modules/lawyer";
+import { CirclePlus, Delete, EditPen } from "@element-plus/icons-vue";
+import LawyerDialog from "./components/LawyerDialog.vue";
 
 const proTable = ref<ProTableInstance>();
-
-const reviewDialog = ref<any>(null);
-const detailDialog = ref<any>(null);
-// 门店审核状态列表
-const applyStatus = ref<any[]>([
-  { value: 0, label: "审核通过" },
-  { value: 1, label: "待审核" },
-  { value: 2, label: "审核拒绝" }
-]);
-
-const columns = reactive<ColumnProps<Course.ReqCourseParams>[]>([
-  {
-    label: "序号",
-    type: "index",
-    width: 60,
-    align: "center"
-  },
-  {
-    label: "ID",
-    prop: "id",
-    width: 80
-  },
-  {
-    label: "用户昵称",
-    prop: "userName",
-    search: { el: "input", tooltip: "请输入用户昵称" }
-  },
-  {
-    label: "姓名",
-    prop: "realName",
-    search: { el: "input", tooltip: "请输入姓名" }
-  },
-  {
-    label: "联系电话",
-    prop: "userPhone",
-    search: { el: "input", tooltip: "请输入手机号码" }
-  },
-  {
-    label: "推广板块",
-    prop: "promoteType"
-  },
-  {
-    label: "状态",
-    prop: "expertStatus",
-    search: { el: "select", tooltip: "请输入状态" },
-    enum: applyStatus,
-    fieldNames: { label: "label", value: "value" }
-  },
-  {
-    label: "申请时间",
-    prop: "createdTime"
-  },
+const lawyerDialogRef = ref<InstanceType<typeof LawyerDialog>>();
+const lawFirmOptions = ref<{ label: string; value: string | number }[]>([]);
+const sceneOptions = ref<any[]>([]);
+const professionalOptions = ref<{ label: string; value: string | number }[]>([]);
+
+const columns = reactive<ColumnProps<any>[]>([
+  { label: "序号", type: "index", width: 60, align: "center" },
+  { label: "律师姓名", prop: "name", width: 110, search: { el: "input", props: { placeholder: "请输入律师姓名" } } },
+  { label: "联系电话", prop: "phone", width: 140, search: { el: "input", props: { placeholder: "请输入联系电话" } } },
   {
-    label: "申请时间",
-    prop: "time",
-    isShow: false, // 关键:不在表格中显示
+    label: "从业时间",
+    prop: "practiceStartDate",
+    width: 140,
     search: {
-      label: "申请时间范围", // 搜索区域显示的标签
       el: "date-picker",
       props: {
-        type: "datetimerange",
-        valueFormat: "YYYY-MM-DD HH:mm:ss",
-        rangeSeparator: "至",
-        startPlaceholder: "开始时间",
-        endPlaceholder: "结束时间"
+        type: "date",
+        valueFormat: "YYYY-MM-DD",
+        placeholder: "请选择从业时间"
       }
     }
   },
+  { label: "专业领域", prop: "areaInfo", width: 140 },
+  { label: "法律场景", prop: "scenarioNames" },
   {
-    label: "操作",
-    prop: "operation",
-    width: 200
-  }
+    label: "接单状态",
+    prop: "status",
+    width: 120,
+    tag: true,
+    enum: [
+      { label: "接单中", value: 1, tagType: "success" },
+      { label: "暂停接单", value: 0, tagType: "info" }
+    ],
+    fieldNames: { label: "label", value: "value" }
+  },
+  { label: "收款账号", prop: "paymentAccount" },
+  { label: "所属律所", prop: "lawFirm" },
+  { label: "操作", prop: "operation", width: 200, fixed: "right" }
 ]);
+
 const getTableList = async (params: any) => {
-  let tempParams = JSON.parse(JSON.stringify(params));
-  delete tempParams.time;
-  // 深拷贝原始参数
-  let newParams = JSON.parse(JSON.stringify(tempParams));
-  newParams.page = newParams.pageNum;
-  newParams.size = newParams.pageSize;
+  const newParams = { ...params, page: params.pageNum, size: params.pageSize };
   delete newParams.pageNum;
   delete newParams.pageSize;
-  if (params.time) {
-    newParams.createdTime = params.time[0];
-    newParams.endTime = params.time[1];
-  }
-
-  const res = await getApplicationExpertList(newParams);
-  return res;
+  return getLawyerPage(newParams);
 };
-const dataCallback = (data: any) => {
-  return {
-    list: data.records,
-    total: data.total
-  };
+
+const dataCallback = (data: any) => ({
+  list: data.records,
+  total: data.total
+});
+
+const refreshTable = () => {
+  proTable.value?.getTableList();
 };
 
-// 处理推广板块字符串
-const getPromoteTypes = (promoteType: string) => {
-  if (!promoteType) return [];
-  return promoteType.split(",");
+const handleCreate = () => {
+  lawyerDialogRef.value?.open({
+    title: "新增律师",
+    onSubmit: async payload => {
+      await addLawyerUser(payload);
+      ElMessage.success("新增成功");
+      refreshTable();
+    }
+  });
 };
 
-//详情
-const handleDetail = row => {
-  detailDialog.value?.open(row.userId);
+const handleEdit = (row: any) => {
+  lawyerDialogRef.value?.open({
+    title: "编辑律师",
+    row,
+    onSubmit: async payload => {
+      await editLawyerUser(payload);
+      ElMessage.success("编辑成功");
+      refreshTable();
+    }
+  });
 };
 
-// 审核
-const handleReview = (row: any) => {
-  // 打开弹窗并传入当前行数据
-  reviewDialog.value?.open(row);
+const handleDelete = (row: any) => {
+  ElMessageBox.confirm(`确定删除律师【${row.userName}】吗?`, "提示", {
+    type: "warning"
+  })
+    .then(async () => {
+      await deleteLawyerUser({ id: row.id });
+      ElMessage.success("删除成功");
+      refreshTable();
+    })
+    .catch(() => {});
 };
 
-// 处理同意操作 expertStatus: 0  包含佣金和预付款比例
-const handleApprove = async (payload: { id: number; commissionRate: number; advanceRate: number; userPhone: string }) => {
+const fetchLawFirmOptions = async () => {
   try {
-    await becomeExpert({
-      id: payload.id,
-      expertStatus: 0,
-      commissionRate: payload.commissionRate,
-      advanceRate: payload.advanceRate,
-      userPhone: payload.userPhone
-    });
-
-    ElMessage.success("审核通过");
-    proTable.value?.getTableList();
+    const res: any = await getLawFirmPage({ page: 1, size: 999 });
+    const listSource = res.data.records || [];
+    lawFirmOptions.value = listSource.map((item: any) => ({
+      label: item.firmName,
+      value: item.id
+    }));
+    console.log("lawFirmOptions.value", lawFirmOptions.value);
   } catch (error) {
-    console.error("审核通过失败:", error);
-    ElMessage.error("审核通过失败");
+    console.error("获取律所列表失败", error);
+    lawFirmOptions.value = [];
   }
 };
 
-// 处理驳回操作 expertStatus: 2 包含驳回原因
-const handleReject = async (payload: { id: number; comment: string; userPhone: string }) => {
+const buildSceneTree = (list: any[] = []) => {
+  const map = new Map<string | number, any>();
+  const roots: any[] = [];
+  list.forEach(item => {
+    map.set(item.id, { ...item, children: [] });
+  });
+  list.forEach(item => {
+    const current = map.get(item.id);
+    if (item.parentId && map.has(item.parentId)) {
+      map.get(item.parentId).children.push(current);
+    } else {
+      roots.push(current);
+    }
+  });
+  return roots;
+};
+
+const fetchSceneOptions = async () => {
   try {
-    await becomeExpert({
-      id: payload.id,
-      expertStatus: 2,
-      comment: payload.comment,
-      userPhone: payload.userPhone
-    });
+    const res: any = await getScenePage({ page: 1, size: 999 });
+    const list = res?.records || res?.data?.records || [];
+    sceneOptions.value = buildSceneTree(list);
+  } catch (error) {
+    console.error("获取法律场景失败", error);
+    sceneOptions.value = [];
+  }
+};
 
-    ElMessage.success("审核驳回");
-    proTable.value?.getTableList();
+const fetchProfessionalOptions = async () => {
+  try {
+    const res: any = await getExpertiseAreaPage({ page: 1, size: 999 });
+    const list = res?.records || res?.data?.records || [];
+    professionalOptions.value = list.map((item: any) => ({
+      label: item.expertiseAreaInfo,
+      value: item.id
+    }));
+    console.log("professionalOptions.value", professionalOptions.value);
   } catch (error) {
-    console.error("审核驳回失败:", error);
-    ElMessage.error("审核驳回失败");
+    console.error("获取专业领域失败", error);
+    professionalOptions.value = [];
   }
 };
 
-onActivated(() => {
-  proTable.value?.getTableList();
+onMounted(() => {
+  fetchLawFirmOptions();
+  fetchSceneOptions();
+  fetchProfessionalOptions();
+  refreshTable();
 });
 </script>
 

+ 0 - 328
src/views/lawyerManagement/lawyer/reviewDialog.vue

@@ -1,328 +0,0 @@
-<template>
-  <el-dialog v-model="dialogShow" title="达人审核" draggable width="700px" @close="handleClose">
-    <div v-if="imageUrls.length > 0" class="image-gallery">
-      <div class="image-grid">
-        <div v-for="(url, index) in imageUrls" :key="index" class="image-item">
-          <el-image
-            :src="url"
-            :preview-src-list="imageUrls"
-            fit="cover"
-            class="gallery-image"
-            :hide-on-click-modal="false"
-            :preview-teleported="true"
-          />
-        </div>
-      </div>
-    </div>
-
-    <!-- 详情展示区域 -->
-    <div class="review-detail">
-      <el-descriptions :column="1" border>
-        <el-descriptions-item label="ID">
-          {{ detailData.id }}
-        </el-descriptions-item>
-        <el-descriptions-item label="用户昵称">
-          {{ detailData.userName }}
-        </el-descriptions-item>
-        <el-descriptions-item label="姓名">
-          {{ detailData.realName }}
-        </el-descriptions-item>
-        <el-descriptions-item label="身份证号码">
-          {{ detailData.idCard }}
-        </el-descriptions-item>
-        <el-descriptions-item label="联系电话">
-          {{ detailData.userPhone }}
-        </el-descriptions-item>
-
-        <el-descriptions-item label="推广模块">
-          <el-tag
-            v-for="(item, index) in getPromoteTypes(detailData.promoteType)"
-            :key="index"
-            style="margin-right: 5px; margin-bottom: 5px"
-          >
-            {{ item }}
-          </el-tag>
-        </el-descriptions-item>
-
-        <el-descriptions-item label="状态">
-          <el-tag v-if="detailData.expertStatus == 0" type="success"> 已通过 </el-tag>
-          <el-tag v-if="detailData.expertStatus == 1" type="primary"> 待审核 </el-tag>
-          <el-tag v-if="detailData.expertStatus == 2" type="danger"> 已驳回 </el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="申请时间">
-          {{ detailData.createdTime }}
-        </el-descriptions-item>
-        <el-descriptions-item label="简介">
-          <div class="introduction">
-            {{ detailData.addExplanation }}
-          </div>
-        </el-descriptions-item>
-      </el-descriptions>
-    </div>
-
-    <!-- 操作按钮区域 -->
-    <template #footer>
-      <span class="dialog-footer">
-        <el-button type="success" @click="openCommissionDialog"> 同意 </el-button>
-        <el-button type="warning" @click="openRejectDialog"> 驳回 </el-button>
-        <el-button @click="handleClose">关闭</el-button>
-      </span>
-    </template>
-
-    <!-- 设置佣金及预付款弹窗 -->
-    <el-dialog title="设置佣金及预付款" v-model="commissionDialogVisible" width="400px" append-to-body>
-      <el-form :model="commissionForm" :rules="commissionRules" ref="commissionFormRef" label-width="120px">
-        <el-form-item label="佣金比例" prop="commissionRate">
-          <el-input-number
-            v-model="commissionForm.commissionRate"
-            :min="0"
-            :max="99"
-            :precision="0"
-            controls-position="right"
-            style="width: 80%"
-          />
-          <div class="form-tip">范围:0-99的整数,表示百分比</div>
-        </el-form-item>
-        <el-form-item label="预付款比例" prop="advanceRate">
-          <el-input-number
-            v-model="commissionForm.advanceRate"
-            :min="1"
-            :max="99"
-            :precision="0"
-            controls-position="right"
-            style="width: 80%"
-          />
-          <div class="form-tip">范围:1-99的整数,表示百分比</div>
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="cancelCommissionDialog">取消</el-button>
-          <el-button type="primary" @click="submitCommission">确认</el-button>
-        </span>
-      </template>
-    </el-dialog>
-
-    <!-- 驳回原因弹窗 -->
-    <el-dialog title="驳回原因" v-model="rejectDialogVisible" width="400px" append-to-body>
-      <el-form :model="rejectForm" :rules="rejectRules" ref="rejectFormRef" label-width="80px">
-        <el-form-item label="原因" prop="comment">
-          <el-input v-model="rejectForm.comment" type="textarea" :rows="3" placeholder="请输入驳回原因" />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="cancelRejectDialog">取消</el-button>
-          <el-button type="primary" @click="submitReject">确认</el-button>
-        </span>
-      </template>
-    </el-dialog>
-  </el-dialog>
-</template>
-
-<script setup lang="ts">
-import { ref, reactive, defineExpose, defineEmits, computed } from "vue";
-import { ElMessageBox } from "element-plus";
-import type { FormInstance, FormRules } from "element-plus";
-
-// 定义弹窗状态
-const dialogShow = ref(false);
-const commissionDialogVisible = ref(false);
-const rejectDialogVisible = ref(false);
-
-// 详情数据
-const detailData: any = ref({});
-
-// 处理图片URL
-const imageUrls = computed(() => {
-  if (!detailData.value.imgUrl) return [];
-  return detailData.value.imgUrl.split(",");
-});
-
-// 处理推广板块字符串
-const getPromoteTypes = (promoteType: string) => {
-  if (!promoteType) return [];
-  return promoteType.split(",");
-};
-
-// 佣金表单
-const commissionForm = reactive({
-  commissionRate: 0,
-  advanceRate: 1
-});
-
-// 驳回表单
-const rejectForm = reactive({
-  comment: ""
-});
-
-// 佣金表单验证规则
-const commissionRules: FormRules = {
-  commissionRate: [
-    { required: true, message: "请输入佣金比例", trigger: "blur" },
-    { type: "number", min: 0, max: 99, message: "佣金比例必须是0-99的整数", trigger: "blur" }
-  ],
-  advanceRate: [
-    { required: true, message: "请输入预付款比例", trigger: "blur" },
-    { type: "number", min: 1, max: 99, message: "预付款比例必须是1-99的整数", trigger: "blur" }
-  ]
-};
-
-// 驳回表单验证规则
-const rejectRules: FormRules = {
-  comment: [
-    { required: true, message: "请输入驳回原因", trigger: "blur" },
-    { min: 2, max: 200, message: "驳回原因长度为2-200个字符", trigger: "blur" }
-  ]
-};
-
-// 表单引用
-const commissionFormRef = ref<FormInstance>();
-const rejectFormRef = ref<FormInstance>();
-
-// 事件触发
-const emit = defineEmits(["approve", "reject", "close"]);
-
-// 显示弹窗方法
-const open = (data: any) => {
-  detailData.value = { ...data };
-  dialogShow.value = true;
-};
-
-// 关闭弹窗
-const handleClose = () => {
-  dialogShow.value = false;
-  emit("close");
-};
-
-// 打开佣金设置弹窗
-const openCommissionDialog = () => {
-  commissionForm.commissionRate = 0;
-  commissionForm.advanceRate = 1;
-  commissionDialogVisible.value = true;
-};
-
-// 取消佣金设置
-const cancelCommissionDialog = () => {
-  commissionDialogVisible.value = false;
-};
-
-// 提交佣金设置
-const submitCommission = async () => {
-  if (!commissionFormRef.value) return;
-
-  const valid = await commissionFormRef.value.validate();
-  if (!valid) return;
-
-  // 显示确认提示
-  try {
-    await ElMessageBox.confirm(
-      `确认同意申请并设置佣金比例为 ${commissionForm.commissionRate}%,预付款比例为 ${commissionForm.advanceRate}%?`,
-      "确认同意",
-      {
-        confirmButtonText: "确认",
-        cancelButtonText: "取消",
-        type: "warning"
-      }
-    );
-
-    // 触发同意事件
-    emit("approve", {
-      id: detailData.value.id,
-      commissionRate: commissionForm.commissionRate,
-      advanceRate: commissionForm.advanceRate,
-      userPhone: detailData.value.userPhone
-    });
-
-    // 关闭所有弹窗
-    commissionDialogVisible.value = false;
-    dialogShow.value = false;
-  } catch (error) {}
-};
-
-// 打开驳回原因弹窗
-const openRejectDialog = () => {
-  rejectForm.comment = "";
-  rejectDialogVisible.value = true;
-};
-
-// 取消驳回
-const cancelRejectDialog = () => {
-  rejectDialogVisible.value = false;
-};
-
-// 提交驳回
-const submitReject = async () => {
-  if (!rejectFormRef.value) return;
-
-  const valid = await rejectFormRef.value.validate();
-  if (!valid) return;
-
-  // 显示确认提示
-  try {
-    await ElMessageBox.confirm("确认驳回申请?", "确认驳回", {
-      confirmButtonText: "确认",
-      cancelButtonText: "取消",
-      type: "warning"
-    });
-
-    // 触发驳回事件
-    emit("reject", {
-      id: detailData.value.id,
-      comment: rejectForm.comment,
-      userPhone: detailData.value.userPhone
-    });
-
-    // 关闭所有弹窗
-    rejectDialogVisible.value = false;
-    dialogShow.value = false;
-  } catch (error) {}
-};
-
-// 暴露给父组件的方法
-defineExpose({
-  open,
-  close: handleClose
-});
-</script>
-
-<style lang="scss" scoped>
-.review-detail {
-  margin-bottom: 20px;
-  .introduction {
-    line-height: 1.5;
-    word-break: break-word;
-    white-space: pre-wrap;
-  }
-}
-.form-tip {
-  margin-top: 5px;
-  font-size: 12px;
-  color: #909399;
-}
-
-/* 隐藏预览时的操作栏 */
-:deep(.el-image-viewer__btn) {
-  display: none !important;
-}
-
-/* 或者更精确地隐藏操作栏区域 */
-:deep(.el-image-viewer__actions) {
-  display: none !important;
-}
-.image-grid {
-  display: grid;
-  grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
-  gap: 8px;
-}
-.image-item {
-  aspect-ratio: 1;
-  overflow: hidden;
-}
-.gallery-image {
-  width: 100%;
-  height: 100%;
-  cursor: pointer;
-  object-fit: cover;
-}
-</style>

+ 107 - 0
src/views/lawyerManagement/legalScene/components/SceneDialog.vue

@@ -0,0 +1,107 @@
+<template>
+  <el-dialog v-model="visible" :title="dialogTitle" width="520px" destroy-on-close>
+    <el-form ref="formRef" :model="form" :rules="rules" label-width="100px" label-suffix=":">
+      <el-form-item label="场景分类" prop="name">
+        <el-input v-model="form.name" placeholder="请输入场景分类" clearable />
+      </el-form-item>
+      <el-form-item v-if="parentSceneName" label="父级场景">
+        <el-input v-model="parentSceneName" disabled />
+      </el-form-item>
+      <el-form-item label="是否启用" prop="status">
+        <el-switch v-model="form.status" :active-value="1" :inactive-value="0" />
+      </el-form-item>
+      <el-form-item label="描述" prop="remark">
+        <el-input v-model="form.remark" type="textarea" :autosize="{ minRows: 2, maxRows: 4 }" placeholder="请输入描述" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="visible = false"> 取 消 </el-button>
+      <el-button type="primary" :loading="loading" @click="handleSubmit"> 保 存 </el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, computed, nextTick } from "vue";
+import type { FormInstance } from "element-plus";
+
+interface DialogOptions {
+  title: string;
+  type: string;
+  parentId?: string | number;
+  parentName?: string;
+  row?: Record<string, any>;
+  onSubmit: (params: Record<string, any>) => Promise<any>;
+}
+
+const visible = ref(false);
+const loading = ref(false);
+const formRef = ref<FormInstance>();
+const options = ref<DialogOptions>({
+  title: "",
+  type: "",
+  onSubmit: async () => Promise.resolve()
+});
+
+const form = reactive({
+  id: "",
+  name: "",
+  status: 1,
+  remark: "",
+  parentId: "",
+  parentName: ""
+});
+const parentSceneName = ref("");
+
+const rules = reactive({
+  name: [{ required: true, message: "请输入场景分类", trigger: "blur" }]
+});
+
+const dialogTitle = computed(() => options.value.title);
+
+const resetForm = () => {
+  form.id = "";
+  form.name = "";
+  form.status = 1;
+  form.remark = "";
+  form.parentId = "";
+  parentSceneName.value = "";
+};
+
+const open = (payload: DialogOptions) => {
+  resetForm();
+  options.value = payload;
+  if (payload.row) {
+    Object.assign(form, payload.row);
+  }
+  // 新增不需要回显名字
+  if (payload.type === "add") {
+    form.parentName = form.name;
+    form.name = "";
+  }
+  visible.value = true;
+  nextTick(() => formRef.value?.clearValidate());
+};
+
+const emits = defineEmits<{
+  success: [];
+}>();
+
+const handleSubmit = () => {
+  formRef.value?.validate(async valid => {
+    if (!valid) return;
+    loading.value = true;
+    try {
+      await options.value.onSubmit({ ...form });
+      emits("success");
+      visible.value = false;
+    } finally {
+      loading.value = false;
+    }
+  });
+};
+
+defineExpose({
+  open
+});
+</script>

+ 0 - 161
src/views/lawyerManagement/legalScene/detailDialog.vue

@@ -1,161 +0,0 @@
-<template>
-  <el-dialog v-model="dialogShow" title="达人详情" draggable width="700px" @close="handleClose">
-    <div v-if="detailData.imgUrl.length > 0" class="image-gallery">
-      <div class="image-grid">
-        <div v-for="(url, index) in detailData.imgUrl" :key="index" class="image-item">
-          <el-image
-            :src="url"
-            :preview-src-list="detailData.imgUrl"
-            fit="cover"
-            class="gallery-image"
-            :hide-on-click-modal="false"
-            :preview-teleported="true"
-          />
-        </div>
-      </div>
-    </div>
-
-    <!-- 详情 -->
-    <div class="review-detail">
-      <el-descriptions :column="1" border>
-        <el-descriptions-item label="用户ID">
-          {{ detailData.id }}
-        </el-descriptions-item>
-        <el-descriptions-item label="用户昵称">
-          {{ detailData.userName }}
-        </el-descriptions-item>
-        <el-descriptions-item label="姓名">
-          {{ detailData.realName }}
-        </el-descriptions-item>
-        <el-descriptions-item label="身份证号码">
-          {{ detailData.idCard }}
-        </el-descriptions-item>
-        <el-descriptions-item label="联系电话">
-          {{ detailData.userPhone }}
-        </el-descriptions-item>
-
-        <el-descriptions-item label="推广模块">
-          <el-tag v-for="(item, index) in detailData.promoteType" :key="index" style="margin-right: 5px; margin-bottom: 5px">
-            {{ item }}
-          </el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="状态">
-          <el-tag v-if="detailData.expertStatus == 0" type="success"> 已通过 </el-tag>
-          <el-tag v-if="detailData.expertStatus == 1" type="primary"> 待审核 </el-tag>
-          <el-tag v-if="detailData.expertStatus == 2" type="danger"> 已驳回 </el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="审核时间" v-if="detailData.reviewTime">
-          {{ detailData.reviewTime || "未审核" }}
-        </el-descriptions-item>
-        <el-descriptions-item label="驳回原因" v-if="detailData.refuseBecause">
-          {{ detailData.refuseBecause }}
-        </el-descriptions-item>
-        <el-descriptions-item label="简介">
-          <div class="introduction">
-            {{ detailData.addExplanation || "--" }}
-          </div>
-        </el-descriptions-item>
-      </el-descriptions>
-    </div>
-  </el-dialog>
-</template>
-
-<script setup lang="ts">
-import { ref, defineExpose, defineEmits } from "vue";
-import { getViewDetails } from "@/api/modules/masterManagemen";
-import { ElMessage } from "element-plus";
-
-// 定义弹窗状态
-const dialogShow = ref(false);
-
-// 详情数据
-const detailData: any = ref({});
-
-const emit = defineEmits(["close"]);
-// 显示弹窗方法
-const open = async (userId: string | number) => {
-  try {
-    const res: any = await getViewDetails(userId);
-    detailData.value = {
-      ...res.data
-    };
-    if (res.data.imgUrl) {
-      detailData.value.imgUrl = res.data.imgUrl.split(",");
-    }
-    if (res.data.promoteType) {
-      detailData.value.promoteType = res.data.promoteType.split(",");
-    }
-    dialogShow.value = true;
-  } catch (error) {
-    ElMessage.error("加载详情失败,请重试");
-    handleClose();
-  }
-};
-
-// 关闭弹窗
-const handleClose = () => {
-  dialogShow.value = false;
-};
-
-// 暴露给父组件的方法
-defineExpose({
-  open
-});
-</script>
-
-<style lang="scss" scoped>
-/* 样式保持不变 */
-.review-detail {
-  margin-bottom: 20px;
-  .introduction {
-    line-height: 1.5;
-    word-break: break-word;
-    white-space: pre-wrap;
-  }
-}
-.form-tip {
-  margin-top: 5px;
-  font-size: 12px;
-  color: #909399;
-}
-.image-gallery {
-  padding: 10px;
-  margin-bottom: 20px;
-  background-color: #f5f7fa;
-  border-radius: 4px;
-  .image-grid {
-    display: flex;
-    flex-wrap: wrap;
-    gap: 10px;
-    .image-item {
-      width: 100px;
-      height: 100px;
-      cursor: pointer;
-      transition: all 0.3s;
-      &:hover {
-        box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
-        transform: scale(1.05);
-      }
-      .gallery-image {
-        width: 100%;
-        height: 100%;
-        overflow: hidden;
-        border-radius: 4px;
-        :deep(.el-image__inner) {
-          width: 100%;
-          height: 100%;
-          object-fit: cover;
-        }
-        :deep(.el-image__error) {
-          display: flex;
-          align-items: center;
-          justify-content: center;
-          font-size: 12px;
-          color: #909399;
-          background-color: #f0f2f5;
-        }
-      }
-    }
-  }
-}
-</style>

+ 286 - 174
src/views/lawyerManagement/legalScene/index.vue

@@ -1,198 +1,310 @@
 <template>
-  <div class="table-box">
-    <ProTable ref="proTable" :columns="columns" :request-api="getTableList" :data-callback="dataCallback">
-      <template #expertStatus="scope">
-        <el-tag v-if="scope.row.expertStatus == 0" type="success"> 已通过 </el-tag>
-        <el-tag v-if="scope.row.expertStatus == 1" type="primary"> 待审核 </el-tag>
-        <el-tag v-if="scope.row.expertStatus == 2" type="danger"> 已驳回 </el-tag>
-      </template>
-      <template #promoteType="scope">
-        <el-tag v-for="(item, index) in getPromoteTypes(scope.row.promoteType)" :key="index">
-          {{ item }}
-        </el-tag>
-      </template>
-
-      <template #operation="scope">
-        <el-button type="primary" :icon="Search" link @click="handleDetail(scope.row)"> 查看详情 </el-button>
-        <el-button type="primary" :icon="Setting" v-if="scope.row.expertStatus == 1" link @click="handleReview(scope.row)">
-          审核
-        </el-button>
-      </template>
-    </ProTable>
-
-    <ReviewDialog ref="reviewDialog" @approve="handleApprove" @reject="handleReject" />
-    <DetailDialog ref="detailDialog" />
+  <div class="scene-page">
+    <div class="scene-toolbar">
+      <el-button type="primary" :icon="CirclePlus" @click="handleCreate"> 新增场景 </el-button>
+      <div class="scene-search">
+        <el-input v-model="filterText" placeholder="请输入场景分类" clearable />
+        <el-button type="primary" @click="handleSearch"> 搜索 </el-button>
+        <el-button @click="handleReset"> 重置 </el-button>
+      </div>
+    </div>
+    <div class="scene-header">
+      <span>场景分类</span>
+      <span>状态</span>
+      <span>操作</span>
+    </div>
+    <div class="scene-tree">
+      <el-tree
+        ref="treeRef"
+        :data="treeData"
+        node-key="id"
+        :props="treeProps"
+        :filter-node-method="filterNode"
+        default-expand-all
+      >
+        <template #default="{ data }">
+          <span class="scene-node">
+            <span class="scene-name">{{ data.name }}</span>
+            <span class="scene-status">
+              <el-tag size="small" :type="data.status === 1 ? 'success' : 'info'">
+                {{ data.status === 1 ? "启用" : "停用" }}
+              </el-tag>
+            </span>
+            <span class="scene-actions">
+              <el-button type="primary" link size="small" :icon="CirclePlus" @click.stop="handleAddChild(data)">
+                新增子场景
+              </el-button>
+              <el-button type="primary" link size="small" :icon="EditPen" @click.stop="handleEdit(data)"> 编辑 </el-button>
+              <el-button type="danger" link size="small" :icon="Delete" @click.stop="handleDelete(data)"> 删除 </el-button>
+            </span>
+          </span>
+        </template>
+      </el-tree>
+    </div>
+    <SceneDialog ref="sceneDialogRef" @success="loadSceneList" />
   </div>
 </template>
 
 <script setup lang="ts">
-import ReviewDialog from "./reviewDialog.vue";
-import DetailDialog from "./detailDialog.vue";
-import { ref, reactive, onActivated } from "vue";
-import type { Course } from "@/api/interface";
-import { ElMessage } from "element-plus";
-import ProTable from "@/components/ProTable/index.vue";
-import type { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
-import { becomeExpert } from "@/api/modules/user";
-import { getApplicationExpertList } from "@/api/modules/masterManagemen";
-import { Search, Setting } from "@element-plus/icons-vue";
-
-const proTable = ref<ProTableInstance>();
-
-const reviewDialog = ref<any>(null);
-const detailDialog = ref<any>(null);
-// 门店审核状态列表
-const applyStatus = ref<any[]>([
-  { value: 0, label: "审核通过" },
-  { value: 1, label: "待审核" },
-  { value: 2, label: "审核拒绝" }
-]);
-
-const columns = reactive<ColumnProps<Course.ReqCourseParams>[]>([
-  {
-    label: "序号",
-    type: "index",
-    width: 60,
-    align: "center"
-  },
-  {
-    label: "ID",
-    prop: "id",
-    width: 80
-  },
-  {
-    label: "用户昵称",
-    prop: "userName",
-    search: { el: "input", tooltip: "请输入用户昵称" }
-  },
-  {
-    label: "姓名",
-    prop: "realName",
-    search: { el: "input", tooltip: "请输入姓名" }
-  },
-  {
-    label: "联系电话",
-    prop: "userPhone",
-    search: { el: "input", tooltip: "请输入手机号码" }
-  },
-  {
-    label: "推广板块",
-    prop: "promoteType"
-  },
-  {
-    label: "状态",
-    prop: "expertStatus",
-    search: { el: "select", tooltip: "请输入状态" },
-    enum: applyStatus,
-    fieldNames: { label: "label", value: "value" }
-  },
-  {
-    label: "申请时间",
-    prop: "createdTime"
-  },
-  {
-    label: "申请时间",
-    prop: "time",
-    isShow: false, // 关键:不在表格中显示
-    search: {
-      label: "申请时间范围", // 搜索区域显示的标签
-      el: "date-picker",
-      props: {
-        type: "datetimerange",
-        valueFormat: "YYYY-MM-DD HH:mm:ss",
-        rangeSeparator: "至",
-        startPlaceholder: "开始时间",
-        endPlaceholder: "结束时间"
+import { ref, reactive, onMounted, watch, nextTick } from "vue";
+import { ElMessage, ElMessageBox, ElTree } from "element-plus";
+import { CirclePlus, Delete, EditPen } from "@element-plus/icons-vue";
+import { getScenePage, addScene, editScene, deleteScene } from "@/api/modules/lawyer";
+import SceneDialog from "./components/SceneDialog.vue";
+
+const treeProps = { children: "children", label: "name" };
+const treeRef = ref<InstanceType<typeof ElTree>>();
+const sceneDialogRef = ref<InstanceType<typeof SceneDialog>>();
+const treeData = ref<any[]>([]);
+const filterText = ref("");
+
+const getLevelValue = (level: string | number | null | undefined) => {
+  if (typeof level === "number") return level;
+  if (typeof level === "string") {
+    const match = level.match(/(\d+)/);
+    return match ? Number(match[1]) : 1;
+  }
+  return 1;
+};
+
+// 通过parentId 和 level 判断层级
+const buildSceneTree = (list: any[] = []) => {
+  if (!Array.isArray(list)) return [];
+
+  const nodeMap = new Map<string | number, any>();
+  const levelStore = new Map<string | number, number>();
+  const groupLevelMap = new Map<string | number | symbol, Map<number, any[]>>();
+  const ROOT_KEY = Symbol("scene_root_key");
+
+  const getGroupKey = (parentId: string | number | null | undefined): string | number | symbol => {
+    if (parentId === undefined || parentId === null || parentId === "") return ROOT_KEY;
+    return parentId;
+  };
+
+  list.forEach(item => {
+    const levelValue = getLevelValue(item.level);
+    const node = { ...item, children: [] as any[] };
+    nodeMap.set(item.id, node);
+    levelStore.set(item.id, levelValue);
+
+    const key = getGroupKey(item.parentId);
+    if (!groupLevelMap.has(key)) {
+      groupLevelMap.set(key, new Map());
+    }
+    const levelMap = groupLevelMap.get(key)!;
+    if (!levelMap.has(levelValue)) {
+      levelMap.set(levelValue, []);
+    }
+    levelMap.get(levelValue)!.push(node);
+  });
+
+  const findParentByGroup = (node: any) => {
+    const nodeLevel = levelStore.get(node.id) || 1;
+    if (nodeLevel <= 1) return undefined;
+    const key = getGroupKey(node.parentId);
+    const levelMap = groupLevelMap.get(key);
+    if (!levelMap) return undefined;
+    const candidates = levelMap.get(nodeLevel - 1);
+    if (!candidates || !candidates.length) return undefined;
+    // 默认选取该层级中最后一个节点作为父节点
+    return candidates[candidates.length - 1];
+  };
+
+  const roots: any[] = [];
+  list.forEach(item => {
+    const node = nodeMap.get(item.id);
+    if (!node) return;
+    const nodeLevel = levelStore.get(item.id) || 1;
+    let parent: any | undefined;
+
+    if (item.parentId && nodeMap.has(item.parentId)) {
+      const candidate = nodeMap.get(item.parentId);
+      if (candidate) {
+        const candidateLevel = levelStore.get(candidate.id) || 1;
+        if (candidate !== node && candidateLevel === nodeLevel - 1) {
+          parent = candidate;
+        }
       }
     }
-  },
-  {
-    label: "操作",
-    prop: "operation",
-    width: 200
-  }
-]);
-const getTableList = async (params: any) => {
-  let tempParams = JSON.parse(JSON.stringify(params));
-  delete tempParams.time;
-  // 深拷贝原始参数
-  let newParams = JSON.parse(JSON.stringify(tempParams));
-  newParams.page = newParams.pageNum;
-  newParams.size = newParams.pageSize;
-  delete newParams.pageNum;
-  delete newParams.pageSize;
-  if (params.time) {
-    newParams.createdTime = params.time[0];
-    newParams.endTime = params.time[1];
-  }
 
-  const res = await getApplicationExpertList(newParams);
-  return res;
+    if (!parent) {
+      const groupParent = findParentByGroup(node);
+      if (groupParent && groupParent !== node) {
+        parent = groupParent;
+      }
+    }
+
+    if (parent) {
+      parent.children = parent.children || [];
+      parent.children.push(node);
+      node.parentName = parent.name;
+    } else {
+      roots.push(node);
+    }
+  });
+
+  return roots;
 };
-const dataCallback = (data: any) => {
-  return {
-    list: data.records,
-    total: data.total
-  };
+
+const loadSceneList = async () => {
+  const res: any = await getScenePage({ page: 1, size: 999 });
+  const flatList = res?.records || res?.list || res?.data?.records || [];
+  treeData.value = buildSceneTree(flatList);
+  nextTick(() => {
+    if (filterText.value && treeRef.value) {
+      treeRef.value.filter(filterText.value);
+    }
+  });
+};
+
+const handleCreate = () => {
+  sceneDialogRef.value?.open({
+    title: "新增场景",
+    onSubmit: async payload => {
+      let params = {
+        name: payload.name,
+        status: payload.status,
+        level: 1
+      };
+      await addScene(params);
+      ElMessage.success("新增成功");
+      loadSceneList();
+    }
+  });
 };
 
-// 处理推广板块字符串
-const getPromoteTypes = (promoteType: string) => {
-  if (!promoteType) return [];
-  return promoteType.split(",");
+const handleAddChild = (row: any) => {
+  console.log("row", row);
+  sceneDialogRef.value?.open({
+    title: `给【${row.name}】新增子场景`,
+    row,
+    type: "add",
+    onSubmit: async payload => {
+      console.log("payload add", payload);
+      let params = {
+        name: payload.name,
+        parentId: row.id,
+        status: payload.status,
+        level: payload.level ? payload.level + 1 : 1,
+        parentName: payload.parentName
+      };
+      await addScene(params);
+      ElMessage.success("新增子场景成功");
+      loadSceneList();
+    }
+  });
 };
 
-//详情
-const handleDetail = row => {
-  detailDialog.value?.open(row.userId);
+const handleEdit = (row: any) => {
+  sceneDialogRef.value?.open({
+    title: "编辑场景",
+    row,
+    onSubmit: async payload => {
+      console.log("payload edit", payload);
+      let params = {
+        name: payload.name,
+        id: payload.id,
+        status: payload.status,
+        level: payload.level
+      };
+      await editScene(params);
+      ElMessage.success("编辑成功");
+      loadSceneList();
+    }
+  });
 };
 
-// 审核
-const handleReview = (row: any) => {
-  // 打开弹窗并传入当前行数据
-  reviewDialog.value?.open(row);
+const handleDelete = (row: any) => {
+  const hasChildren = Array.isArray(row.children) && row.children.length > 0;
+  const message = hasChildren
+    ? `场景【${row.name}】包含子场景,删除后将一并删除所有子场景,是否继续?`
+    : `确认删除场景【${row.name}】吗?`;
+  ElMessageBox.confirm(message, "提示", {
+    type: "warning"
+  })
+    .then(async () => {
+      await deleteScene({ id: row.id });
+      ElMessage.success("删除成功");
+      loadSceneList();
+    })
+    .catch(() => {});
 };
 
-// 处理同意操作 expertStatus: 0  包含佣金和预付款比例
-const handleApprove = async (payload: { id: number; commissionRate: number; advanceRate: number; userPhone: string }) => {
-  try {
-    await becomeExpert({
-      id: payload.id,
-      expertStatus: 0,
-      commissionRate: payload.commissionRate,
-      advanceRate: payload.advanceRate,
-      userPhone: payload.userPhone
-    });
-
-    ElMessage.success("审核通过");
-    proTable.value?.getTableList();
-  } catch (error) {
-    console.error("审核通过失败:", error);
-    ElMessage.error("审核通过失败");
-  }
+const filterNode = (value: string, data: any) => {
+  if (!value) return true;
+  return data.name?.includes(value);
 };
 
-// 处理驳回操作 expertStatus: 2 包含驳回原因
-const handleReject = async (payload: { id: number; comment: string; userPhone: string }) => {
-  try {
-    await becomeExpert({
-      id: payload.id,
-      expertStatus: 2,
-      comment: payload.comment,
-      userPhone: payload.userPhone
-    });
-
-    ElMessage.success("审核驳回");
-    proTable.value?.getTableList();
-  } catch (error) {
-    console.error("审核驳回失败:", error);
-    ElMessage.error("审核驳回失败");
-  }
+const handleSearch = () => {
+  treeRef.value?.filter(filterText.value);
 };
 
-onActivated(() => {
-  proTable.value?.getTableList();
+const handleReset = () => {
+  filterText.value = "";
+  treeRef.value?.filter("");
+};
+
+watch(filterText, val => {
+  if (!val) treeRef.value?.filter("");
+});
+
+onMounted(() => {
+  loadSceneList();
 });
 </script>
 
-<style scoped lang="scss"></style>
+<style scoped lang="scss">
+.scene-page {
+  padding: 16px;
+}
+.scene-toolbar {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 12px;
+}
+.scene-search {
+  display: flex;
+  gap: 8px;
+  width: 360px;
+}
+.scene-header {
+  display: grid;
+  grid-template-columns: 3fr 1fr 1fr;
+  padding: 12px 16px;
+  font-weight: 600;
+  background-color: var(--el-fill-color-light);
+  border: 1px solid var(--el-border-color);
+  border-bottom: none;
+  border-radius: 4px 4px 0 0;
+}
+.scene-tree {
+  max-height: 680px;
+  overflow: auto;
+  border: 1px solid var(--el-border-color);
+  border-top: none;
+  border-radius: 0 0 4px 4px;
+}
+.scene-node {
+  display: grid;
+  grid-template-columns: 3fr 1fr 1fr;
+  align-items: center;
+  width: 100%;
+  padding: 12px 16px;
+}
+.scene-name {
+  display: flex;
+  align-items: center;
+}
+.scene-status {
+  display: flex;
+  justify-content: flex-start;
+}
+.scene-actions {
+  display: flex;
+  gap: 8px;
+}
+:deep(.el-tree-node__content) {
+  height: auto;
+}
+</style>

+ 0 - 328
src/views/lawyerManagement/legalScene/reviewDialog.vue

@@ -1,328 +0,0 @@
-<template>
-  <el-dialog v-model="dialogShow" title="达人审核" draggable width="700px" @close="handleClose">
-    <div v-if="imageUrls.length > 0" class="image-gallery">
-      <div class="image-grid">
-        <div v-for="(url, index) in imageUrls" :key="index" class="image-item">
-          <el-image
-            :src="url"
-            :preview-src-list="imageUrls"
-            fit="cover"
-            class="gallery-image"
-            :hide-on-click-modal="false"
-            :preview-teleported="true"
-          />
-        </div>
-      </div>
-    </div>
-
-    <!-- 详情展示区域 -->
-    <div class="review-detail">
-      <el-descriptions :column="1" border>
-        <el-descriptions-item label="ID">
-          {{ detailData.id }}
-        </el-descriptions-item>
-        <el-descriptions-item label="用户昵称">
-          {{ detailData.userName }}
-        </el-descriptions-item>
-        <el-descriptions-item label="姓名">
-          {{ detailData.realName }}
-        </el-descriptions-item>
-        <el-descriptions-item label="身份证号码">
-          {{ detailData.idCard }}
-        </el-descriptions-item>
-        <el-descriptions-item label="联系电话">
-          {{ detailData.userPhone }}
-        </el-descriptions-item>
-
-        <el-descriptions-item label="推广模块">
-          <el-tag
-            v-for="(item, index) in getPromoteTypes(detailData.promoteType)"
-            :key="index"
-            style="margin-right: 5px; margin-bottom: 5px"
-          >
-            {{ item }}
-          </el-tag>
-        </el-descriptions-item>
-
-        <el-descriptions-item label="状态">
-          <el-tag v-if="detailData.expertStatus == 0" type="success"> 已通过 </el-tag>
-          <el-tag v-if="detailData.expertStatus == 1" type="primary"> 待审核 </el-tag>
-          <el-tag v-if="detailData.expertStatus == 2" type="danger"> 已驳回 </el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="申请时间">
-          {{ detailData.createdTime }}
-        </el-descriptions-item>
-        <el-descriptions-item label="简介">
-          <div class="introduction">
-            {{ detailData.addExplanation }}
-          </div>
-        </el-descriptions-item>
-      </el-descriptions>
-    </div>
-
-    <!-- 操作按钮区域 -->
-    <template #footer>
-      <span class="dialog-footer">
-        <el-button type="success" @click="openCommissionDialog"> 同意 </el-button>
-        <el-button type="warning" @click="openRejectDialog"> 驳回 </el-button>
-        <el-button @click="handleClose">关闭</el-button>
-      </span>
-    </template>
-
-    <!-- 设置佣金及预付款弹窗 -->
-    <el-dialog title="设置佣金及预付款" v-model="commissionDialogVisible" width="400px" append-to-body>
-      <el-form :model="commissionForm" :rules="commissionRules" ref="commissionFormRef" label-width="120px">
-        <el-form-item label="佣金比例" prop="commissionRate">
-          <el-input-number
-            v-model="commissionForm.commissionRate"
-            :min="0"
-            :max="99"
-            :precision="0"
-            controls-position="right"
-            style="width: 80%"
-          />
-          <div class="form-tip">范围:0-99的整数,表示百分比</div>
-        </el-form-item>
-        <el-form-item label="预付款比例" prop="advanceRate">
-          <el-input-number
-            v-model="commissionForm.advanceRate"
-            :min="1"
-            :max="99"
-            :precision="0"
-            controls-position="right"
-            style="width: 80%"
-          />
-          <div class="form-tip">范围:1-99的整数,表示百分比</div>
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="cancelCommissionDialog">取消</el-button>
-          <el-button type="primary" @click="submitCommission">确认</el-button>
-        </span>
-      </template>
-    </el-dialog>
-
-    <!-- 驳回原因弹窗 -->
-    <el-dialog title="驳回原因" v-model="rejectDialogVisible" width="400px" append-to-body>
-      <el-form :model="rejectForm" :rules="rejectRules" ref="rejectFormRef" label-width="80px">
-        <el-form-item label="原因" prop="comment">
-          <el-input v-model="rejectForm.comment" type="textarea" :rows="3" placeholder="请输入驳回原因" />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="cancelRejectDialog">取消</el-button>
-          <el-button type="primary" @click="submitReject">确认</el-button>
-        </span>
-      </template>
-    </el-dialog>
-  </el-dialog>
-</template>
-
-<script setup lang="ts">
-import { ref, reactive, defineExpose, defineEmits, computed } from "vue";
-import { ElMessageBox } from "element-plus";
-import type { FormInstance, FormRules } from "element-plus";
-
-// 定义弹窗状态
-const dialogShow = ref(false);
-const commissionDialogVisible = ref(false);
-const rejectDialogVisible = ref(false);
-
-// 详情数据
-const detailData: any = ref({});
-
-// 处理图片URL
-const imageUrls = computed(() => {
-  if (!detailData.value.imgUrl) return [];
-  return detailData.value.imgUrl.split(",");
-});
-
-// 处理推广板块字符串
-const getPromoteTypes = (promoteType: string) => {
-  if (!promoteType) return [];
-  return promoteType.split(",");
-};
-
-// 佣金表单
-const commissionForm = reactive({
-  commissionRate: 0,
-  advanceRate: 1
-});
-
-// 驳回表单
-const rejectForm = reactive({
-  comment: ""
-});
-
-// 佣金表单验证规则
-const commissionRules: FormRules = {
-  commissionRate: [
-    { required: true, message: "请输入佣金比例", trigger: "blur" },
-    { type: "number", min: 0, max: 99, message: "佣金比例必须是0-99的整数", trigger: "blur" }
-  ],
-  advanceRate: [
-    { required: true, message: "请输入预付款比例", trigger: "blur" },
-    { type: "number", min: 1, max: 99, message: "预付款比例必须是1-99的整数", trigger: "blur" }
-  ]
-};
-
-// 驳回表单验证规则
-const rejectRules: FormRules = {
-  comment: [
-    { required: true, message: "请输入驳回原因", trigger: "blur" },
-    { min: 2, max: 200, message: "驳回原因长度为2-200个字符", trigger: "blur" }
-  ]
-};
-
-// 表单引用
-const commissionFormRef = ref<FormInstance>();
-const rejectFormRef = ref<FormInstance>();
-
-// 事件触发
-const emit = defineEmits(["approve", "reject", "close"]);
-
-// 显示弹窗方法
-const open = (data: any) => {
-  detailData.value = { ...data };
-  dialogShow.value = true;
-};
-
-// 关闭弹窗
-const handleClose = () => {
-  dialogShow.value = false;
-  emit("close");
-};
-
-// 打开佣金设置弹窗
-const openCommissionDialog = () => {
-  commissionForm.commissionRate = 0;
-  commissionForm.advanceRate = 1;
-  commissionDialogVisible.value = true;
-};
-
-// 取消佣金设置
-const cancelCommissionDialog = () => {
-  commissionDialogVisible.value = false;
-};
-
-// 提交佣金设置
-const submitCommission = async () => {
-  if (!commissionFormRef.value) return;
-
-  const valid = await commissionFormRef.value.validate();
-  if (!valid) return;
-
-  // 显示确认提示
-  try {
-    await ElMessageBox.confirm(
-      `确认同意申请并设置佣金比例为 ${commissionForm.commissionRate}%,预付款比例为 ${commissionForm.advanceRate}%?`,
-      "确认同意",
-      {
-        confirmButtonText: "确认",
-        cancelButtonText: "取消",
-        type: "warning"
-      }
-    );
-
-    // 触发同意事件
-    emit("approve", {
-      id: detailData.value.id,
-      commissionRate: commissionForm.commissionRate,
-      advanceRate: commissionForm.advanceRate,
-      userPhone: detailData.value.userPhone
-    });
-
-    // 关闭所有弹窗
-    commissionDialogVisible.value = false;
-    dialogShow.value = false;
-  } catch (error) {}
-};
-
-// 打开驳回原因弹窗
-const openRejectDialog = () => {
-  rejectForm.comment = "";
-  rejectDialogVisible.value = true;
-};
-
-// 取消驳回
-const cancelRejectDialog = () => {
-  rejectDialogVisible.value = false;
-};
-
-// 提交驳回
-const submitReject = async () => {
-  if (!rejectFormRef.value) return;
-
-  const valid = await rejectFormRef.value.validate();
-  if (!valid) return;
-
-  // 显示确认提示
-  try {
-    await ElMessageBox.confirm("确认驳回申请?", "确认驳回", {
-      confirmButtonText: "确认",
-      cancelButtonText: "取消",
-      type: "warning"
-    });
-
-    // 触发驳回事件
-    emit("reject", {
-      id: detailData.value.id,
-      comment: rejectForm.comment,
-      userPhone: detailData.value.userPhone
-    });
-
-    // 关闭所有弹窗
-    rejectDialogVisible.value = false;
-    dialogShow.value = false;
-  } catch (error) {}
-};
-
-// 暴露给父组件的方法
-defineExpose({
-  open,
-  close: handleClose
-});
-</script>
-
-<style lang="scss" scoped>
-.review-detail {
-  margin-bottom: 20px;
-  .introduction {
-    line-height: 1.5;
-    word-break: break-word;
-    white-space: pre-wrap;
-  }
-}
-.form-tip {
-  margin-top: 5px;
-  font-size: 12px;
-  color: #909399;
-}
-
-/* 隐藏预览时的操作栏 */
-:deep(.el-image-viewer__btn) {
-  display: none !important;
-}
-
-/* 或者更精确地隐藏操作栏区域 */
-:deep(.el-image-viewer__actions) {
-  display: none !important;
-}
-.image-grid {
-  display: grid;
-  grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
-  gap: 8px;
-}
-.image-item {
-  aspect-ratio: 1;
-  overflow: hidden;
-}
-.gallery-image {
-  width: 100%;
-  height: 100%;
-  cursor: pointer;
-  object-fit: cover;
-}
-</style>

+ 80 - 0
src/views/lawyerManagement/professionalField/components/ProfessionalDialog.vue

@@ -0,0 +1,80 @@
+<template>
+  <el-dialog v-model="visible" :title="dialogTitle" width="420px" destroy-on-close>
+    <el-form ref="formRef" :model="form" :rules="rules" label-width="100px" label-suffix=":">
+      <el-form-item label="专业领域" prop="expertiseAreaInfo">
+        <el-input v-model="form.expertiseAreaInfo" placeholder="请输入专业领域" clearable />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="visible = false"> 取 消 </el-button>
+      <el-button type="primary" :loading="loading" @click="handleSubmit"> 保 存 </el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, computed, nextTick } from "vue";
+import type { FormInstance } from "element-plus";
+
+interface DialogOptions {
+  title: string;
+  row?: Record<string, any>;
+  onSubmit: (params: Record<string, any>) => Promise<any>;
+}
+
+const visible = ref(false);
+const loading = ref(false);
+const formRef = ref<FormInstance>();
+const options = ref<DialogOptions>({
+  title: "",
+  onSubmit: async () => Promise.resolve()
+});
+
+const form = reactive({
+  id: "",
+  expertiseAreaInfo: ""
+});
+
+const rules = reactive({
+  expertiseAreaInfo: [{ required: true, message: "请输入专业领域", trigger: "blur" }]
+});
+
+const dialogTitle = computed(() => options.value.title);
+
+const resetForm = () => {
+  form.id = "";
+  form.expertiseAreaInfo = "";
+};
+
+const open = (payload: DialogOptions) => {
+  resetForm();
+  options.value = payload;
+  if (payload.row) {
+    Object.assign(form, payload.row);
+  }
+  visible.value = true;
+  nextTick(() => formRef.value?.clearValidate());
+};
+
+const emits = defineEmits<{
+  success: [];
+}>();
+
+const handleSubmit = () => {
+  formRef.value?.validate(async valid => {
+    if (!valid) return;
+    loading.value = true;
+    try {
+      await options.value.onSubmit({ ...form });
+      emits("success");
+      visible.value = false;
+    } finally {
+      loading.value = false;
+    }
+  });
+};
+
+defineExpose({
+  open
+});
+</script>

+ 0 - 161
src/views/lawyerManagement/professionalField/detailDialog.vue

@@ -1,161 +0,0 @@
-<template>
-  <el-dialog v-model="dialogShow" title="达人详情" draggable width="700px" @close="handleClose">
-    <div v-if="detailData.imgUrl.length > 0" class="image-gallery">
-      <div class="image-grid">
-        <div v-for="(url, index) in detailData.imgUrl" :key="index" class="image-item">
-          <el-image
-            :src="url"
-            :preview-src-list="detailData.imgUrl"
-            fit="cover"
-            class="gallery-image"
-            :hide-on-click-modal="false"
-            :preview-teleported="true"
-          />
-        </div>
-      </div>
-    </div>
-
-    <!-- 详情 -->
-    <div class="review-detail">
-      <el-descriptions :column="1" border>
-        <el-descriptions-item label="用户ID">
-          {{ detailData.id }}
-        </el-descriptions-item>
-        <el-descriptions-item label="用户昵称">
-          {{ detailData.userName }}
-        </el-descriptions-item>
-        <el-descriptions-item label="姓名">
-          {{ detailData.realName }}
-        </el-descriptions-item>
-        <el-descriptions-item label="身份证号码">
-          {{ detailData.idCard }}
-        </el-descriptions-item>
-        <el-descriptions-item label="联系电话">
-          {{ detailData.userPhone }}
-        </el-descriptions-item>
-
-        <el-descriptions-item label="推广模块">
-          <el-tag v-for="(item, index) in detailData.promoteType" :key="index" style="margin-right: 5px; margin-bottom: 5px">
-            {{ item }}
-          </el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="状态">
-          <el-tag v-if="detailData.expertStatus == 0" type="success"> 已通过 </el-tag>
-          <el-tag v-if="detailData.expertStatus == 1" type="primary"> 待审核 </el-tag>
-          <el-tag v-if="detailData.expertStatus == 2" type="danger"> 已驳回 </el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="审核时间" v-if="detailData.reviewTime">
-          {{ detailData.reviewTime || "未审核" }}
-        </el-descriptions-item>
-        <el-descriptions-item label="驳回原因" v-if="detailData.refuseBecause">
-          {{ detailData.refuseBecause }}
-        </el-descriptions-item>
-        <el-descriptions-item label="简介">
-          <div class="introduction">
-            {{ detailData.addExplanation || "--" }}
-          </div>
-        </el-descriptions-item>
-      </el-descriptions>
-    </div>
-  </el-dialog>
-</template>
-
-<script setup lang="ts">
-import { ref, defineExpose, defineEmits } from "vue";
-import { getViewDetails } from "@/api/modules/masterManagemen";
-import { ElMessage } from "element-plus";
-
-// 定义弹窗状态
-const dialogShow = ref(false);
-
-// 详情数据
-const detailData: any = ref({});
-
-const emit = defineEmits(["close"]);
-// 显示弹窗方法
-const open = async (userId: string | number) => {
-  try {
-    const res: any = await getViewDetails(userId);
-    detailData.value = {
-      ...res.data
-    };
-    if (res.data.imgUrl) {
-      detailData.value.imgUrl = res.data.imgUrl.split(",");
-    }
-    if (res.data.promoteType) {
-      detailData.value.promoteType = res.data.promoteType.split(",");
-    }
-    dialogShow.value = true;
-  } catch (error) {
-    ElMessage.error("加载详情失败,请重试");
-    handleClose();
-  }
-};
-
-// 关闭弹窗
-const handleClose = () => {
-  dialogShow.value = false;
-};
-
-// 暴露给父组件的方法
-defineExpose({
-  open
-});
-</script>
-
-<style lang="scss" scoped>
-/* 样式保持不变 */
-.review-detail {
-  margin-bottom: 20px;
-  .introduction {
-    line-height: 1.5;
-    word-break: break-word;
-    white-space: pre-wrap;
-  }
-}
-.form-tip {
-  margin-top: 5px;
-  font-size: 12px;
-  color: #909399;
-}
-.image-gallery {
-  padding: 10px;
-  margin-bottom: 20px;
-  background-color: #f5f7fa;
-  border-radius: 4px;
-  .image-grid {
-    display: flex;
-    flex-wrap: wrap;
-    gap: 10px;
-    .image-item {
-      width: 100px;
-      height: 100px;
-      cursor: pointer;
-      transition: all 0.3s;
-      &:hover {
-        box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
-        transform: scale(1.05);
-      }
-      .gallery-image {
-        width: 100%;
-        height: 100%;
-        overflow: hidden;
-        border-radius: 4px;
-        :deep(.el-image__inner) {
-          width: 100%;
-          height: 100%;
-          object-fit: cover;
-        }
-        :deep(.el-image__error) {
-          display: flex;
-          align-items: center;
-          justify-content: center;
-          font-size: 12px;
-          color: #909399;
-          background-color: #f0f2f5;
-        }
-      }
-    }
-  }
-}
-</style>

+ 51 - 164
src/views/lawyerManagement/professionalField/index.vue

@@ -1,197 +1,84 @@
 <template>
   <div class="table-box">
     <ProTable ref="proTable" :columns="columns" :request-api="getTableList" :data-callback="dataCallback">
-      <template #expertStatus="scope">
-        <el-tag v-if="scope.row.expertStatus == 0" type="success"> 已通过 </el-tag>
-        <el-tag v-if="scope.row.expertStatus == 1" type="primary"> 待审核 </el-tag>
-        <el-tag v-if="scope.row.expertStatus == 2" type="danger"> 已驳回 </el-tag>
+      <template #tableHeader>
+        <el-button type="primary" :icon="CirclePlus" @click="openDialog()"> 新增专业领域 </el-button>
       </template>
-      <template #promoteType="scope">
-        <el-tag v-for="(item, index) in getPromoteTypes(scope.row.promoteType)" :key="index">
-          {{ item }}
-        </el-tag>
-      </template>
-
       <template #operation="scope">
-        <el-button type="primary" :icon="Search" link @click="handleDetail(scope.row)"> 查看详情 </el-button>
-        <el-button type="primary" :icon="Setting" v-if="scope.row.expertStatus == 1" link @click="handleReview(scope.row)">
-          审核
-        </el-button>
+        <el-button type="primary" link :icon="EditPen" @click="openDialog(scope.row)"> 编辑 </el-button>
+        <el-button type="danger" link :icon="Delete" @click="handleDelete(scope.row)"> 删除 </el-button>
       </template>
     </ProTable>
-
-    <ReviewDialog ref="reviewDialog" @approve="handleApprove" @reject="handleReject" />
-    <DetailDialog ref="detailDialog" />
+    <ProfessionalDialog ref="dialogRef" @success="refreshTable" />
   </div>
 </template>
 
 <script setup lang="ts">
-import ReviewDialog from "./reviewDialog.vue";
-import DetailDialog from "./detailDialog.vue";
-import { ref, reactive, onActivated } from "vue";
-import type { Course } from "@/api/interface";
-import { ElMessage } from "element-plus";
+import { ref, reactive, onMounted } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
 import ProTable from "@/components/ProTable/index.vue";
 import type { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
-import { becomeExpert } from "@/api/modules/user";
-import { getApplicationExpertList } from "@/api/modules/masterManagemen";
-import { Search, Setting } from "@element-plus/icons-vue";
+import { CirclePlus, Delete, EditPen } from "@element-plus/icons-vue";
+import { getExpertiseAreaPage, addExpertiseArea, editExpertiseArea, deleteExpertiseArea } from "@/api/modules/lawyer";
+import ProfessionalDialog from "./components/ProfessionalDialog.vue";
 
 const proTable = ref<ProTableInstance>();
+const dialogRef = ref<InstanceType<typeof ProfessionalDialog>>();
 
-const reviewDialog = ref<any>(null);
-const detailDialog = ref<any>(null);
-// 门店审核状态列表
-const applyStatus = ref<any[]>([
-  { value: 0, label: "审核通过" },
-  { value: 1, label: "待审核" },
-  { value: 2, label: "审核拒绝" }
+const columns = reactive<ColumnProps<any>[]>([
+  { label: "序号", type: "index", width: 60, align: "center" },
+  { label: "专业领域", prop: "expertiseAreaInfo", search: { el: "input", props: { placeholder: "请输入专业领域" } } },
+  { label: "创建时间", prop: "createdTime", width: 300 },
+  { label: "操作", prop: "operation", width: 200, fixed: "right" }
 ]);
 
-const columns = reactive<ColumnProps<Course.ReqCourseParams>[]>([
-  {
-    label: "序号",
-    type: "index",
-    width: 60,
-    align: "center"
-  },
-  {
-    label: "ID",
-    prop: "id",
-    width: 80
-  },
-  {
-    label: "用户昵称",
-    prop: "userName",
-    search: { el: "input", tooltip: "请输入用户昵称" }
-  },
-  {
-    label: "姓名",
-    prop: "realName",
-    search: { el: "input", tooltip: "请输入姓名" }
-  },
-  {
-    label: "联系电话",
-    prop: "userPhone",
-    search: { el: "input", tooltip: "请输入手机号码" }
-  },
-  {
-    label: "推广板块",
-    prop: "promoteType"
-  },
-  {
-    label: "状态",
-    prop: "expertStatus",
-    search: { el: "select", tooltip: "请输入状态" },
-    enum: applyStatus,
-    fieldNames: { label: "label", value: "value" }
-  },
-  {
-    label: "申请时间",
-    prop: "createdTime"
-  },
-  {
-    label: "申请时间",
-    prop: "time",
-    isShow: false, // 关键:不在表格中显示
-    search: {
-      label: "申请时间范围", // 搜索区域显示的标签
-      el: "date-picker",
-      props: {
-        type: "datetimerange",
-        valueFormat: "YYYY-MM-DD HH:mm:ss",
-        rangeSeparator: "至",
-        startPlaceholder: "开始时间",
-        endPlaceholder: "结束时间"
-      }
-    }
-  },
-  {
-    label: "操作",
-    prop: "operation",
-    width: 200
-  }
-]);
 const getTableList = async (params: any) => {
-  let tempParams = JSON.parse(JSON.stringify(params));
-  delete tempParams.time;
-  // 深拷贝原始参数
-  let newParams = JSON.parse(JSON.stringify(tempParams));
-  newParams.page = newParams.pageNum;
-  newParams.size = newParams.pageSize;
+  const newParams = { ...params, page: params.pageNum, size: params.pageSize };
   delete newParams.pageNum;
   delete newParams.pageSize;
-  if (params.time) {
-    newParams.createdTime = params.time[0];
-    newParams.endTime = params.time[1];
-  }
-
-  const res = await getApplicationExpertList(newParams);
-  return res;
-};
-const dataCallback = (data: any) => {
-  return {
-    list: data.records,
-    total: data.total
-  };
-};
-
-// 处理推广板块字符串
-const getPromoteTypes = (promoteType: string) => {
-  if (!promoteType) return [];
-  return promoteType.split(",");
+  return getExpertiseAreaPage(newParams);
 };
 
-//详情
-const handleDetail = row => {
-  detailDialog.value?.open(row.userId);
-};
+const dataCallback = (data: any) => ({
+  list: data.records || data.list || [],
+  total: data.total || data.count || 0
+});
 
-// 审核
-const handleReview = (row: any) => {
-  // 打开弹窗并传入当前行数据
-  reviewDialog.value?.open(row);
+const refreshTable = () => {
+  proTable.value?.getTableList();
 };
 
-// 处理同意操作 expertStatus: 0  包含佣金和预付款比例
-const handleApprove = async (payload: { id: number; commissionRate: number; advanceRate: number; userPhone: string }) => {
-  try {
-    await becomeExpert({
-      id: payload.id,
-      expertStatus: 0,
-      commissionRate: payload.commissionRate,
-      advanceRate: payload.advanceRate,
-      userPhone: payload.userPhone
-    });
-
-    ElMessage.success("审核通过");
-    proTable.value?.getTableList();
-  } catch (error) {
-    console.error("审核通过失败:", error);
-    ElMessage.error("审核通过失败");
-  }
+const openDialog = (row?: any) => {
+  dialogRef.value?.open({
+    title: row ? "编辑专业领域" : "新增专业领域",
+    row,
+    onSubmit: async payload => {
+      if (row?.id) {
+        await editExpertiseArea(payload);
+        ElMessage.success("编辑成功");
+      } else {
+        await addExpertiseArea(payload);
+        ElMessage.success("新增成功");
+      }
+      refreshTable();
+    }
+  });
 };
 
-// 处理驳回操作 expertStatus: 2 包含驳回原因
-const handleReject = async (payload: { id: number; comment: string; userPhone: string }) => {
-  try {
-    await becomeExpert({
-      id: payload.id,
-      expertStatus: 2,
-      comment: payload.comment,
-      userPhone: payload.userPhone
-    });
-
-    ElMessage.success("审核驳回");
-    proTable.value?.getTableList();
-  } catch (error) {
-    console.error("审核驳回失败:", error);
-    ElMessage.error("审核驳回失败");
-  }
+const handleDelete = (row: any) => {
+  ElMessageBox.confirm(`确认删除专业领域【${row.expertiseAreaInfo}】吗?`, "提示", {
+    type: "warning"
+  })
+    .then(async () => {
+      await deleteExpertiseArea({ id: row.id });
+      ElMessage.success("删除成功");
+      refreshTable();
+    })
+    .catch(() => {});
 };
 
-onActivated(() => {
-  proTable.value?.getTableList();
+onMounted(() => {
+  refreshTable();
 });
 </script>
 

+ 0 - 328
src/views/lawyerManagement/professionalField/reviewDialog.vue

@@ -1,328 +0,0 @@
-<template>
-  <el-dialog v-model="dialogShow" title="达人审核" draggable width="700px" @close="handleClose">
-    <div v-if="imageUrls.length > 0" class="image-gallery">
-      <div class="image-grid">
-        <div v-for="(url, index) in imageUrls" :key="index" class="image-item">
-          <el-image
-            :src="url"
-            :preview-src-list="imageUrls"
-            fit="cover"
-            class="gallery-image"
-            :hide-on-click-modal="false"
-            :preview-teleported="true"
-          />
-        </div>
-      </div>
-    </div>
-
-    <!-- 详情展示区域 -->
-    <div class="review-detail">
-      <el-descriptions :column="1" border>
-        <el-descriptions-item label="ID">
-          {{ detailData.id }}
-        </el-descriptions-item>
-        <el-descriptions-item label="用户昵称">
-          {{ detailData.userName }}
-        </el-descriptions-item>
-        <el-descriptions-item label="姓名">
-          {{ detailData.realName }}
-        </el-descriptions-item>
-        <el-descriptions-item label="身份证号码">
-          {{ detailData.idCard }}
-        </el-descriptions-item>
-        <el-descriptions-item label="联系电话">
-          {{ detailData.userPhone }}
-        </el-descriptions-item>
-
-        <el-descriptions-item label="推广模块">
-          <el-tag
-            v-for="(item, index) in getPromoteTypes(detailData.promoteType)"
-            :key="index"
-            style="margin-right: 5px; margin-bottom: 5px"
-          >
-            {{ item }}
-          </el-tag>
-        </el-descriptions-item>
-
-        <el-descriptions-item label="状态">
-          <el-tag v-if="detailData.expertStatus == 0" type="success"> 已通过 </el-tag>
-          <el-tag v-if="detailData.expertStatus == 1" type="primary"> 待审核 </el-tag>
-          <el-tag v-if="detailData.expertStatus == 2" type="danger"> 已驳回 </el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="申请时间">
-          {{ detailData.createdTime }}
-        </el-descriptions-item>
-        <el-descriptions-item label="简介">
-          <div class="introduction">
-            {{ detailData.addExplanation }}
-          </div>
-        </el-descriptions-item>
-      </el-descriptions>
-    </div>
-
-    <!-- 操作按钮区域 -->
-    <template #footer>
-      <span class="dialog-footer">
-        <el-button type="success" @click="openCommissionDialog"> 同意 </el-button>
-        <el-button type="warning" @click="openRejectDialog"> 驳回 </el-button>
-        <el-button @click="handleClose">关闭</el-button>
-      </span>
-    </template>
-
-    <!-- 设置佣金及预付款弹窗 -->
-    <el-dialog title="设置佣金及预付款" v-model="commissionDialogVisible" width="400px" append-to-body>
-      <el-form :model="commissionForm" :rules="commissionRules" ref="commissionFormRef" label-width="120px">
-        <el-form-item label="佣金比例" prop="commissionRate">
-          <el-input-number
-            v-model="commissionForm.commissionRate"
-            :min="0"
-            :max="99"
-            :precision="0"
-            controls-position="right"
-            style="width: 80%"
-          />
-          <div class="form-tip">范围:0-99的整数,表示百分比</div>
-        </el-form-item>
-        <el-form-item label="预付款比例" prop="advanceRate">
-          <el-input-number
-            v-model="commissionForm.advanceRate"
-            :min="1"
-            :max="99"
-            :precision="0"
-            controls-position="right"
-            style="width: 80%"
-          />
-          <div class="form-tip">范围:1-99的整数,表示百分比</div>
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="cancelCommissionDialog">取消</el-button>
-          <el-button type="primary" @click="submitCommission">确认</el-button>
-        </span>
-      </template>
-    </el-dialog>
-
-    <!-- 驳回原因弹窗 -->
-    <el-dialog title="驳回原因" v-model="rejectDialogVisible" width="400px" append-to-body>
-      <el-form :model="rejectForm" :rules="rejectRules" ref="rejectFormRef" label-width="80px">
-        <el-form-item label="原因" prop="comment">
-          <el-input v-model="rejectForm.comment" type="textarea" :rows="3" placeholder="请输入驳回原因" />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="cancelRejectDialog">取消</el-button>
-          <el-button type="primary" @click="submitReject">确认</el-button>
-        </span>
-      </template>
-    </el-dialog>
-  </el-dialog>
-</template>
-
-<script setup lang="ts">
-import { ref, reactive, defineExpose, defineEmits, computed } from "vue";
-import { ElMessageBox } from "element-plus";
-import type { FormInstance, FormRules } from "element-plus";
-
-// 定义弹窗状态
-const dialogShow = ref(false);
-const commissionDialogVisible = ref(false);
-const rejectDialogVisible = ref(false);
-
-// 详情数据
-const detailData: any = ref({});
-
-// 处理图片URL
-const imageUrls = computed(() => {
-  if (!detailData.value.imgUrl) return [];
-  return detailData.value.imgUrl.split(",");
-});
-
-// 处理推广板块字符串
-const getPromoteTypes = (promoteType: string) => {
-  if (!promoteType) return [];
-  return promoteType.split(",");
-};
-
-// 佣金表单
-const commissionForm = reactive({
-  commissionRate: 0,
-  advanceRate: 1
-});
-
-// 驳回表单
-const rejectForm = reactive({
-  comment: ""
-});
-
-// 佣金表单验证规则
-const commissionRules: FormRules = {
-  commissionRate: [
-    { required: true, message: "请输入佣金比例", trigger: "blur" },
-    { type: "number", min: 0, max: 99, message: "佣金比例必须是0-99的整数", trigger: "blur" }
-  ],
-  advanceRate: [
-    { required: true, message: "请输入预付款比例", trigger: "blur" },
-    { type: "number", min: 1, max: 99, message: "预付款比例必须是1-99的整数", trigger: "blur" }
-  ]
-};
-
-// 驳回表单验证规则
-const rejectRules: FormRules = {
-  comment: [
-    { required: true, message: "请输入驳回原因", trigger: "blur" },
-    { min: 2, max: 200, message: "驳回原因长度为2-200个字符", trigger: "blur" }
-  ]
-};
-
-// 表单引用
-const commissionFormRef = ref<FormInstance>();
-const rejectFormRef = ref<FormInstance>();
-
-// 事件触发
-const emit = defineEmits(["approve", "reject", "close"]);
-
-// 显示弹窗方法
-const open = (data: any) => {
-  detailData.value = { ...data };
-  dialogShow.value = true;
-};
-
-// 关闭弹窗
-const handleClose = () => {
-  dialogShow.value = false;
-  emit("close");
-};
-
-// 打开佣金设置弹窗
-const openCommissionDialog = () => {
-  commissionForm.commissionRate = 0;
-  commissionForm.advanceRate = 1;
-  commissionDialogVisible.value = true;
-};
-
-// 取消佣金设置
-const cancelCommissionDialog = () => {
-  commissionDialogVisible.value = false;
-};
-
-// 提交佣金设置
-const submitCommission = async () => {
-  if (!commissionFormRef.value) return;
-
-  const valid = await commissionFormRef.value.validate();
-  if (!valid) return;
-
-  // 显示确认提示
-  try {
-    await ElMessageBox.confirm(
-      `确认同意申请并设置佣金比例为 ${commissionForm.commissionRate}%,预付款比例为 ${commissionForm.advanceRate}%?`,
-      "确认同意",
-      {
-        confirmButtonText: "确认",
-        cancelButtonText: "取消",
-        type: "warning"
-      }
-    );
-
-    // 触发同意事件
-    emit("approve", {
-      id: detailData.value.id,
-      commissionRate: commissionForm.commissionRate,
-      advanceRate: commissionForm.advanceRate,
-      userPhone: detailData.value.userPhone
-    });
-
-    // 关闭所有弹窗
-    commissionDialogVisible.value = false;
-    dialogShow.value = false;
-  } catch (error) {}
-};
-
-// 打开驳回原因弹窗
-const openRejectDialog = () => {
-  rejectForm.comment = "";
-  rejectDialogVisible.value = true;
-};
-
-// 取消驳回
-const cancelRejectDialog = () => {
-  rejectDialogVisible.value = false;
-};
-
-// 提交驳回
-const submitReject = async () => {
-  if (!rejectFormRef.value) return;
-
-  const valid = await rejectFormRef.value.validate();
-  if (!valid) return;
-
-  // 显示确认提示
-  try {
-    await ElMessageBox.confirm("确认驳回申请?", "确认驳回", {
-      confirmButtonText: "确认",
-      cancelButtonText: "取消",
-      type: "warning"
-    });
-
-    // 触发驳回事件
-    emit("reject", {
-      id: detailData.value.id,
-      comment: rejectForm.comment,
-      userPhone: detailData.value.userPhone
-    });
-
-    // 关闭所有弹窗
-    rejectDialogVisible.value = false;
-    dialogShow.value = false;
-  } catch (error) {}
-};
-
-// 暴露给父组件的方法
-defineExpose({
-  open,
-  close: handleClose
-});
-</script>
-
-<style lang="scss" scoped>
-.review-detail {
-  margin-bottom: 20px;
-  .introduction {
-    line-height: 1.5;
-    word-break: break-word;
-    white-space: pre-wrap;
-  }
-}
-.form-tip {
-  margin-top: 5px;
-  font-size: 12px;
-  color: #909399;
-}
-
-/* 隐藏预览时的操作栏 */
-:deep(.el-image-viewer__btn) {
-  display: none !important;
-}
-
-/* 或者更精确地隐藏操作栏区域 */
-:deep(.el-image-viewer__actions) {
-  display: none !important;
-}
-.image-grid {
-  display: grid;
-  grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
-  gap: 8px;
-}
-.image-item {
-  aspect-ratio: 1;
-  overflow: hidden;
-}
-.gallery-image {
-  width: 100%;
-  height: 100%;
-  cursor: pointer;
-  object-fit: cover;
-}
-</style>

+ 0 - 1
src/views/lawyerManagement/reconciliation/detailDialog.vue

@@ -62,7 +62,6 @@
 
 <script setup lang="ts">
 import { ref, defineExpose, defineEmits } from "vue";
-import { getViewDetails } from "@/api/modules/masterManagemen";
 import { ElMessage } from "element-plus";
 
 // 定义弹窗状态

+ 215 - 138
src/views/lawyerManagement/reconciliation/index.vue

@@ -1,116 +1,100 @@
 <template>
-  <div class="table-box">
-    <ProTable ref="proTable" :columns="columns" :request-api="getTableList" :data-callback="dataCallback">
-      <template #expertStatus="scope">
-        <el-tag v-if="scope.row.expertStatus == 0" type="success"> 已通过 </el-tag>
-        <el-tag v-if="scope.row.expertStatus == 1" type="primary"> 待审核 </el-tag>
-        <el-tag v-if="scope.row.expertStatus == 2" type="danger"> 已驳回 </el-tag>
+  <div class="reconciliation-page">
+    <div class="summary-wrapper">
+      <div class="summary-card" v-for="item in summaryCards" :key="item.label">
+        <div class="summary-label">
+          {{ item.label }}
+        </div>
+        <div class="summary-value">
+          {{ item.value }}
+        </div>
+      </div>
+    </div>
+    <div class="filter-bar">
+      <div class="filter-left">
+        <div class="filter-title">律师账单数据</div>
+        <div class="filter-desc">查看该律所下所有律师的订单明细数据</div>
+      </div>
+      <div class="filter-right">
+        <el-button type="primary" plain> 导出数据 </el-button>
+        <el-button> 刷新 </el-button>
+      </div>
+    </div>
+    <div class="search-panel">
+      <el-input v-model="filters.name" placeholder="输入律师姓名搜索" clearable />
+      <el-date-picker
+        v-model="filters.dateRange"
+        type="daterange"
+        range-separator="至"
+        start-placeholder="开始日期"
+        end-placeholder="结束日期"
+        value-format="YYYY-MM-DD"
+      />
+      <el-button type="primary" :icon="Search" @click="handleSearch"> 搜索 </el-button>
+      <el-button @click="handleReset"> 重置 </el-button>
+    </div>
+    <div class="custom-table-head">
+      <span> 律师信息 </span>
+      <span> 订单数量 </span>
+      <span> 订单金额 </span>
+      <span> 平台信息服务费 </span>
+      <span> 操作 </span>
+    </div>
+    <ProTable
+      ref="proTable"
+      :columns="columns"
+      :request-api="getTableList"
+      :data-callback="dataCallback"
+      :tool-button="false"
+      :show-table-setting="false"
+    >
+      <template #userName="scope">
+        <div class="lawyer-info">
+          <el-avatar :size="48" :src="scope.row.avatar || defaultAvatar" />
+          <div class="lawyer-meta">
+            <div class="lawyer-name">
+              {{ scope.row.userName || "--" }}
+            </div>
+            <div class="lawyer-extra">执业证号:{{ scope.row.licenseNo || "——" }}</div>
+            <div class="lawyer-extra">手机号:{{ scope.row.userPhone || "——" }}</div>
+          </div>
+        </div>
       </template>
-      <template #promoteType="scope">
-        <el-tag v-for="(item, index) in getPromoteTypes(scope.row.promoteType)" :key="index">
-          {{ item }}
-        </el-tag>
+      <template #orderCount="scope">
+        {{ scope.row.orderCount ?? "--" }}
+      </template>
+      <template #orderAmount="scope">
+        {{ formatCurrency(scope.row.orderAmount) }}
+      </template>
+      <template #platformFee="scope">
+        {{ formatCurrency(scope.row.platformFee) }}
       </template>
-
       <template #operation="scope">
-        <el-button type="primary" :icon="Search" link @click="handleDetail(scope.row)"> 查看详情 </el-button>
-        <el-button type="primary" :icon="Setting" v-if="scope.row.expertStatus == 1" link @click="handleReview(scope.row)">
-          审核
-        </el-button>
+        <el-button type="primary" link @click="handleDetail(scope.row)"> 查看详情 </el-button>
       </template>
     </ProTable>
-
-    <ReviewDialog ref="reviewDialog" @approve="handleApprove" @reject="handleReject" />
     <DetailDialog ref="detailDialog" />
   </div>
 </template>
 
 <script setup lang="ts">
-import ReviewDialog from "./reviewDialog.vue";
-import DetailDialog from "./detailDialog.vue";
-import { ref, reactive, onActivated } from "vue";
+import { ref, reactive, onActivated, computed } from "vue";
 import type { Course } from "@/api/interface";
-import { ElMessage } from "element-plus";
 import ProTable from "@/components/ProTable/index.vue";
 import type { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
-import { becomeExpert } from "@/api/modules/user";
-import { getApplicationExpertList } from "@/api/modules/masterManagemen";
-import { Search, Setting } from "@element-plus/icons-vue";
+import { Search } from "@element-plus/icons-vue";
+import DetailDialog from "./detailDialog.vue";
+import { getApplicationExpertList } from "@/api/modules/lawyer";
 
 const proTable = ref<ProTableInstance>();
-
-const reviewDialog = ref<any>(null);
 const detailDialog = ref<any>(null);
-// 门店审核状态列表
-const applyStatus = ref<any[]>([
-  { value: 0, label: "审核通过" },
-  { value: 1, label: "待审核" },
-  { value: 2, label: "审核拒绝" }
-]);
 
 const columns = reactive<ColumnProps<Course.ReqCourseParams>[]>([
-  {
-    label: "序号",
-    type: "index",
-    width: 60,
-    align: "center"
-  },
-  {
-    label: "ID",
-    prop: "id",
-    width: 80
-  },
-  {
-    label: "用户昵称",
-    prop: "userName",
-    search: { el: "input", tooltip: "请输入用户昵称" }
-  },
-  {
-    label: "姓名",
-    prop: "realName",
-    search: { el: "input", tooltip: "请输入姓名" }
-  },
-  {
-    label: "联系电话",
-    prop: "userPhone",
-    search: { el: "input", tooltip: "请输入手机号码" }
-  },
-  {
-    label: "推广板块",
-    prop: "promoteType"
-  },
-  {
-    label: "状态",
-    prop: "expertStatus",
-    search: { el: "select", tooltip: "请输入状态" },
-    enum: applyStatus,
-    fieldNames: { label: "label", value: "value" }
-  },
-  {
-    label: "申请时间",
-    prop: "createdTime"
-  },
-  {
-    label: "申请时间",
-    prop: "time",
-    isShow: false, // 关键:不在表格中显示
-    search: {
-      label: "申请时间范围", // 搜索区域显示的标签
-      el: "date-picker",
-      props: {
-        type: "datetimerange",
-        valueFormat: "YYYY-MM-DD HH:mm:ss",
-        rangeSeparator: "至",
-        startPlaceholder: "开始时间",
-        endPlaceholder: "结束时间"
-      }
-    }
-  },
-  {
-    label: "操作",
-    prop: "operation",
-    width: 200
-  }
+  { label: "律师信息", prop: "userName", minWidth: 280 },
+  { label: "订单数量", prop: "orderCount", width: 160, align: "center" },
+  { label: "订单金额", prop: "orderAmount", width: 200, align: "center" },
+  { label: "平台信息服务费", prop: "platformFee", width: 220, align: "center" },
+  { label: "操作", prop: "operation", width: 160, align: "center", fixed: "right" }
 ]);
 const getTableList = async (params: any) => {
   let tempParams = JSON.parse(JSON.stringify(params));
@@ -137,57 +121,57 @@ const dataCallback = (data: any) => {
 };
 
 // 处理推广板块字符串
-const getPromoteTypes = (promoteType: string) => {
-  if (!promoteType) return [];
-  return promoteType.split(",");
-};
+const filters = reactive({
+  name: "",
+  dateRange: [] as string[]
+});
 
-//详情
-const handleDetail = row => {
-  detailDialog.value?.open(row.userId);
-};
+const defaultAvatar = "https://img.alicdn.com/tfs/TB1jFtVwYj1gK0jSZFuXXcrHpXa-200-200.png";
 
-// 审核
-const handleReview = (row: any) => {
-  // 打开弹窗并传入当前行数据
-  reviewDialog.value?.open(row);
-};
+const summaryCards = computed(() => {
+  const list = proTable.value?.tableData || [];
+  const totalOrders = list.reduce((sum: number, item: any) => sum + (Number(item.orderCount) || 0), 0);
+  const totalAmount = list.reduce((sum: number, item: any) => sum + (Number(item.orderAmount) || 0), 0);
+  const totalFee = list.reduce((sum: number, item: any) => sum + (Number(item.platformFee) || 0), 0);
+  return [
+    { label: "总订单数量", value: formatNumber(totalOrders) },
+    { label: "总订单金额", value: formatCurrency(totalAmount) },
+    { label: "平台信息服务费", value: formatCurrency(totalFee) }
+  ];
+});
 
-// 处理同意操作 expertStatus: 0  包含佣金和预付款比例
-const handleApprove = async (payload: { id: number; commissionRate: number; advanceRate: number; userPhone: string }) => {
-  try {
-    await becomeExpert({
-      id: payload.id,
-      expertStatus: 0,
-      commissionRate: payload.commissionRate,
-      advanceRate: payload.advanceRate,
-      userPhone: payload.userPhone
-    });
+const handleSearch = () => {
+  if (!proTable.value) return;
+  if (filters.name) proTable.value.searchParam.userName = filters.name;
+  else delete proTable.value.searchParam.userName;
 
-    ElMessage.success("审核通过");
-    proTable.value?.getTableList();
-  } catch (error) {
-    console.error("审核通过失败:", error);
-    ElMessage.error("审核通过失败");
+  if (filters.dateRange?.length === 2) {
+    proTable.value.searchParam.time = filters.dateRange;
+  } else {
+    delete proTable.value.searchParam.time;
   }
+  proTable.value.search();
 };
 
-// 处理驳回操作 expertStatus: 2 包含驳回原因
-const handleReject = async (payload: { id: number; comment: string; userPhone: string }) => {
-  try {
-    await becomeExpert({
-      id: payload.id,
-      expertStatus: 2,
-      comment: payload.comment,
-      userPhone: payload.userPhone
-    });
+const handleReset = () => {
+  filters.name = "";
+  filters.dateRange = [];
+  handleSearch();
+};
 
-    ElMessage.success("审核驳回");
-    proTable.value?.getTableList();
-  } catch (error) {
-    console.error("审核驳回失败:", error);
-    ElMessage.error("审核驳回失败");
-  }
+const formatCurrency = (val?: number | string) => {
+  const num = Number(val) || 0;
+  return `¥${num.toLocaleString("zh-CN", { minimumFractionDigits: 2 })}`;
+};
+
+const formatNumber = (val?: number | string) => {
+  const num = Number(val) || 0;
+  return num.toLocaleString("zh-CN");
+};
+
+// 处理同意操作 expertStatus: 0  包含佣金和预付款比例
+const handleDetail = (row: any) => {
+  detailDialog.value?.open(row.userId);
 };
 
 onActivated(() => {
@@ -195,4 +179,97 @@ onActivated(() => {
 });
 </script>
 
-<style scoped lang="scss"></style>
+<style scoped lang="scss">
+.reconciliation-page {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+  padding: 16px;
+}
+.summary-wrapper {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
+  gap: 16px;
+}
+.summary-card {
+  padding: 16px;
+  background: #f8f9fb;
+  border: 1px solid #e2e6ef;
+  border-radius: 12px;
+}
+.summary-label {
+  margin-bottom: 4px;
+  color: #7a7f87;
+}
+.summary-value {
+  font-size: 24px;
+  font-weight: 600;
+  color: #1f2532;
+}
+.filter-bar {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+.filter-left {
+  display: flex;
+  flex-direction: column;
+}
+.filter-title {
+  font-size: 20px;
+  font-weight: 600;
+  color: #16192c;
+}
+.filter-desc {
+  font-size: 13px;
+  color: #8c8f9c;
+}
+.filter-right {
+  display: flex;
+  gap: 12px;
+}
+.search-panel {
+  display: flex;
+  gap: 12px;
+  align-items: center;
+}
+.custom-table-head {
+  display: grid;
+  grid-template-columns: 3fr 1fr 1fr 1fr 1fr;
+  padding: 12px 16px;
+  font-weight: 600;
+  color: #7a7f87;
+  background: #ffffff;
+  border: 1px solid #e6e9ef;
+  border-bottom: none;
+  border-radius: 12px 12px 0 0;
+}
+:deep(.el-table) {
+  border: 1px solid #e6e9ef;
+  border-top: none;
+  border-radius: 0 0 12px 12px;
+}
+:deep(.el-table__header) {
+  display: none;
+}
+.lawyer-info {
+  display: flex;
+  gap: 12px;
+  align-items: center;
+}
+.lawyer-meta {
+  display: flex;
+  flex-direction: column;
+  font-size: 13px;
+  color: #7e7f8c;
+}
+.lawyer-name {
+  font-size: 15px;
+  font-weight: 600;
+  color: #1c2130;
+}
+.lawyer-extra {
+  font-size: 12px;
+  color: #a5a7b3;
+}
+</style>

+ 0 - 328
src/views/lawyerManagement/reconciliation/reviewDialog.vue

@@ -1,328 +0,0 @@
-<template>
-  <el-dialog v-model="dialogShow" title="达人审核" draggable width="700px" @close="handleClose">
-    <div v-if="imageUrls.length > 0" class="image-gallery">
-      <div class="image-grid">
-        <div v-for="(url, index) in imageUrls" :key="index" class="image-item">
-          <el-image
-            :src="url"
-            :preview-src-list="imageUrls"
-            fit="cover"
-            class="gallery-image"
-            :hide-on-click-modal="false"
-            :preview-teleported="true"
-          />
-        </div>
-      </div>
-    </div>
-
-    <!-- 详情展示区域 -->
-    <div class="review-detail">
-      <el-descriptions :column="1" border>
-        <el-descriptions-item label="ID">
-          {{ detailData.id }}
-        </el-descriptions-item>
-        <el-descriptions-item label="用户昵称">
-          {{ detailData.userName }}
-        </el-descriptions-item>
-        <el-descriptions-item label="姓名">
-          {{ detailData.realName }}
-        </el-descriptions-item>
-        <el-descriptions-item label="身份证号码">
-          {{ detailData.idCard }}
-        </el-descriptions-item>
-        <el-descriptions-item label="联系电话">
-          {{ detailData.userPhone }}
-        </el-descriptions-item>
-
-        <el-descriptions-item label="推广模块">
-          <el-tag
-            v-for="(item, index) in getPromoteTypes(detailData.promoteType)"
-            :key="index"
-            style="margin-right: 5px; margin-bottom: 5px"
-          >
-            {{ item }}
-          </el-tag>
-        </el-descriptions-item>
-
-        <el-descriptions-item label="状态">
-          <el-tag v-if="detailData.expertStatus == 0" type="success"> 已通过 </el-tag>
-          <el-tag v-if="detailData.expertStatus == 1" type="primary"> 待审核 </el-tag>
-          <el-tag v-if="detailData.expertStatus == 2" type="danger"> 已驳回 </el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="申请时间">
-          {{ detailData.createdTime }}
-        </el-descriptions-item>
-        <el-descriptions-item label="简介">
-          <div class="introduction">
-            {{ detailData.addExplanation }}
-          </div>
-        </el-descriptions-item>
-      </el-descriptions>
-    </div>
-
-    <!-- 操作按钮区域 -->
-    <template #footer>
-      <span class="dialog-footer">
-        <el-button type="success" @click="openCommissionDialog"> 同意 </el-button>
-        <el-button type="warning" @click="openRejectDialog"> 驳回 </el-button>
-        <el-button @click="handleClose">关闭</el-button>
-      </span>
-    </template>
-
-    <!-- 设置佣金及预付款弹窗 -->
-    <el-dialog title="设置佣金及预付款" v-model="commissionDialogVisible" width="400px" append-to-body>
-      <el-form :model="commissionForm" :rules="commissionRules" ref="commissionFormRef" label-width="120px">
-        <el-form-item label="佣金比例" prop="commissionRate">
-          <el-input-number
-            v-model="commissionForm.commissionRate"
-            :min="0"
-            :max="99"
-            :precision="0"
-            controls-position="right"
-            style="width: 80%"
-          />
-          <div class="form-tip">范围:0-99的整数,表示百分比</div>
-        </el-form-item>
-        <el-form-item label="预付款比例" prop="advanceRate">
-          <el-input-number
-            v-model="commissionForm.advanceRate"
-            :min="1"
-            :max="99"
-            :precision="0"
-            controls-position="right"
-            style="width: 80%"
-          />
-          <div class="form-tip">范围:1-99的整数,表示百分比</div>
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="cancelCommissionDialog">取消</el-button>
-          <el-button type="primary" @click="submitCommission">确认</el-button>
-        </span>
-      </template>
-    </el-dialog>
-
-    <!-- 驳回原因弹窗 -->
-    <el-dialog title="驳回原因" v-model="rejectDialogVisible" width="400px" append-to-body>
-      <el-form :model="rejectForm" :rules="rejectRules" ref="rejectFormRef" label-width="80px">
-        <el-form-item label="原因" prop="comment">
-          <el-input v-model="rejectForm.comment" type="textarea" :rows="3" placeholder="请输入驳回原因" />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="cancelRejectDialog">取消</el-button>
-          <el-button type="primary" @click="submitReject">确认</el-button>
-        </span>
-      </template>
-    </el-dialog>
-  </el-dialog>
-</template>
-
-<script setup lang="ts">
-import { ref, reactive, defineExpose, defineEmits, computed } from "vue";
-import { ElMessageBox } from "element-plus";
-import type { FormInstance, FormRules } from "element-plus";
-
-// 定义弹窗状态
-const dialogShow = ref(false);
-const commissionDialogVisible = ref(false);
-const rejectDialogVisible = ref(false);
-
-// 详情数据
-const detailData: any = ref({});
-
-// 处理图片URL
-const imageUrls = computed(() => {
-  if (!detailData.value.imgUrl) return [];
-  return detailData.value.imgUrl.split(",");
-});
-
-// 处理推广板块字符串
-const getPromoteTypes = (promoteType: string) => {
-  if (!promoteType) return [];
-  return promoteType.split(",");
-};
-
-// 佣金表单
-const commissionForm = reactive({
-  commissionRate: 0,
-  advanceRate: 1
-});
-
-// 驳回表单
-const rejectForm = reactive({
-  comment: ""
-});
-
-// 佣金表单验证规则
-const commissionRules: FormRules = {
-  commissionRate: [
-    { required: true, message: "请输入佣金比例", trigger: "blur" },
-    { type: "number", min: 0, max: 99, message: "佣金比例必须是0-99的整数", trigger: "blur" }
-  ],
-  advanceRate: [
-    { required: true, message: "请输入预付款比例", trigger: "blur" },
-    { type: "number", min: 1, max: 99, message: "预付款比例必须是1-99的整数", trigger: "blur" }
-  ]
-};
-
-// 驳回表单验证规则
-const rejectRules: FormRules = {
-  comment: [
-    { required: true, message: "请输入驳回原因", trigger: "blur" },
-    { min: 2, max: 200, message: "驳回原因长度为2-200个字符", trigger: "blur" }
-  ]
-};
-
-// 表单引用
-const commissionFormRef = ref<FormInstance>();
-const rejectFormRef = ref<FormInstance>();
-
-// 事件触发
-const emit = defineEmits(["approve", "reject", "close"]);
-
-// 显示弹窗方法
-const open = (data: any) => {
-  detailData.value = { ...data };
-  dialogShow.value = true;
-};
-
-// 关闭弹窗
-const handleClose = () => {
-  dialogShow.value = false;
-  emit("close");
-};
-
-// 打开佣金设置弹窗
-const openCommissionDialog = () => {
-  commissionForm.commissionRate = 0;
-  commissionForm.advanceRate = 1;
-  commissionDialogVisible.value = true;
-};
-
-// 取消佣金设置
-const cancelCommissionDialog = () => {
-  commissionDialogVisible.value = false;
-};
-
-// 提交佣金设置
-const submitCommission = async () => {
-  if (!commissionFormRef.value) return;
-
-  const valid = await commissionFormRef.value.validate();
-  if (!valid) return;
-
-  // 显示确认提示
-  try {
-    await ElMessageBox.confirm(
-      `确认同意申请并设置佣金比例为 ${commissionForm.commissionRate}%,预付款比例为 ${commissionForm.advanceRate}%?`,
-      "确认同意",
-      {
-        confirmButtonText: "确认",
-        cancelButtonText: "取消",
-        type: "warning"
-      }
-    );
-
-    // 触发同意事件
-    emit("approve", {
-      id: detailData.value.id,
-      commissionRate: commissionForm.commissionRate,
-      advanceRate: commissionForm.advanceRate,
-      userPhone: detailData.value.userPhone
-    });
-
-    // 关闭所有弹窗
-    commissionDialogVisible.value = false;
-    dialogShow.value = false;
-  } catch (error) {}
-};
-
-// 打开驳回原因弹窗
-const openRejectDialog = () => {
-  rejectForm.comment = "";
-  rejectDialogVisible.value = true;
-};
-
-// 取消驳回
-const cancelRejectDialog = () => {
-  rejectDialogVisible.value = false;
-};
-
-// 提交驳回
-const submitReject = async () => {
-  if (!rejectFormRef.value) return;
-
-  const valid = await rejectFormRef.value.validate();
-  if (!valid) return;
-
-  // 显示确认提示
-  try {
-    await ElMessageBox.confirm("确认驳回申请?", "确认驳回", {
-      confirmButtonText: "确认",
-      cancelButtonText: "取消",
-      type: "warning"
-    });
-
-    // 触发驳回事件
-    emit("reject", {
-      id: detailData.value.id,
-      comment: rejectForm.comment,
-      userPhone: detailData.value.userPhone
-    });
-
-    // 关闭所有弹窗
-    rejectDialogVisible.value = false;
-    dialogShow.value = false;
-  } catch (error) {}
-};
-
-// 暴露给父组件的方法
-defineExpose({
-  open,
-  close: handleClose
-});
-</script>
-
-<style lang="scss" scoped>
-.review-detail {
-  margin-bottom: 20px;
-  .introduction {
-    line-height: 1.5;
-    word-break: break-word;
-    white-space: pre-wrap;
-  }
-}
-.form-tip {
-  margin-top: 5px;
-  font-size: 12px;
-  color: #909399;
-}
-
-/* 隐藏预览时的操作栏 */
-:deep(.el-image-viewer__btn) {
-  display: none !important;
-}
-
-/* 或者更精确地隐藏操作栏区域 */
-:deep(.el-image-viewer__actions) {
-  display: none !important;
-}
-.image-grid {
-  display: grid;
-  grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
-  gap: 8px;
-}
-.image-item {
-  aspect-ratio: 1;
-  overflow: hidden;
-}
-.gallery-image {
-  width: 100%;
-  height: 100%;
-  cursor: pointer;
-  object-fit: cover;
-}
-</style>

+ 1 - 0
src/views/login/components/LoginForm.vue

@@ -108,6 +108,7 @@ const login = (formEl: FormInstance | undefined) => {
       const loginPayload = {
         username: loginForm.username,
         password: md5(loginForm.password)
+        // password: loginForm.password
       };
       const { data } = (await loginApi(loginPayload)) as { data: Login.ResLogin };
       console.log(data);

+ 160 - 0
src/views/userManagement/components/UserDialog.vue

@@ -0,0 +1,160 @@
+<template>
+  <el-dialog v-model="visible" :title="dialogTitle" width="520px" destroy-on-close>
+    <el-form ref="formRef" :model="form" :rules="rules" label-width="100px" label-suffix=":">
+      <el-form-item label="登录账号" prop="loginAccount">
+        <el-input v-model="form.loginAccount" placeholder="请输入登录账号" clearable />
+      </el-form-item>
+      <el-form-item label="关联律所" prop="roleId">
+        <el-select v-model="form.roleId" placeholder="请选择关联律所" filterable>
+          <el-option v-for="item in lawFirmOptions" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="登录密码" prop="loginPassword">
+        <el-input v-model="form.loginPassword" placeholder="请输入登录密码" type="password" show-password />
+        <span v-if="isEdit" class="form-tip">若无需修改密码,可留空</span>
+      </el-form-item>
+      <el-form-item label="账号状态" prop="status">
+        <el-switch v-model="form.status" :active-value="1" :inactive-value="0" active-text="启用" inactive-text="禁用" />
+      </el-form-item>
+      <el-form-item label="备注" prop="remark">
+        <el-input v-model="form.remark" type="textarea" :autosize="{ minRows: 2, maxRows: 4 }" placeholder="请输入备注信息" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="visible = false"> 取 消 </el-button>
+      <el-button type="primary" :loading="loading" @click="handleSubmit"> 保 存 </el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { reactive, ref, computed, nextTick, watch } from "vue";
+import type { FormInstance } from "element-plus";
+import md5 from "md5";
+import type { User } from "@/api/interface";
+
+interface DialogOptions {
+  title: string;
+  mode: "add" | "edit";
+  row?: Partial<User.ResUserList>;
+  onSubmit: (params: Record<string, any>) => Promise<any>;
+}
+
+const props = defineProps<{
+  lawFirmOptions: { label: string; value: string | number }[];
+}>();
+
+const visible = ref(false);
+const loading = ref(false);
+const formRef = ref<FormInstance>();
+const options = ref<DialogOptions>({
+  title: "",
+  mode: "add",
+  onSubmit: async () => Promise.resolve()
+});
+
+const form = reactive({
+  id: "",
+  loginAccount: "",
+  roleId: "",
+  loginPassword: "",
+  status: 1,
+  remark: "",
+  roleName: ""
+});
+
+const dialogTitle = computed(() => options.value.title);
+const isEdit = computed(() => options.value.mode === "edit");
+
+const rules = reactive({
+  loginAccount: [{ required: true, message: "请输入登录账号", trigger: "blur" }],
+  roleId: [{ required: true, message: "请选择关联律所", trigger: "change" }],
+  loginPassword: [
+    {
+      validator: (_: any, value: string, callback: (error?: Error) => void) => {
+        if (options.value.mode === "add" && !value) return callback(new Error("请输入登录密码"));
+        callback();
+      },
+      trigger: "blur"
+    }
+  ]
+});
+
+const resetForm = () => {
+  form.id = "";
+  form.loginAccount = "";
+  form.roleId = "";
+  form.loginPassword = "";
+  form.status = 1;
+  form.remark = "";
+  form.roleName = "";
+};
+
+const open = (payload: DialogOptions) => {
+  resetForm();
+  options.value = payload;
+  if (payload.row) {
+    Object.assign(form, payload.row);
+    form.roleId = (payload.row?.roleId as string) || "";
+  }
+  visible.value = true;
+  nextTick(() => formRef.value?.clearValidate());
+};
+
+watch(
+  () => props.lawFirmOptions,
+  list => {
+    if (!form.roleId) return;
+    const target = list.find(item => item.value === form.roleId);
+    if (target) form.roleName = target.label;
+  },
+  { immediate: true }
+);
+
+const emits = defineEmits<{
+  success: [];
+}>();
+
+const buildPayload = () => {
+  const payload: Record<string, any> = {
+    id: form.id,
+    loginAccount: form.loginAccount,
+    roleId: form.roleId,
+    status: form.status,
+    remark: form.remark
+  };
+  if (!payload.id) delete payload.id;
+  if (form.loginPassword) {
+    payload.loginPassword = md5(form.loginPassword);
+  }
+  return payload;
+};
+
+const handleSubmit = () => {
+  formRef.value?.validate(async valid => {
+    if (!valid) return;
+    loading.value = true;
+    try {
+      const payload = buildPayload();
+      await options.value.onSubmit(payload);
+      emits("success");
+      visible.value = false;
+    } finally {
+      loading.value = false;
+    }
+  });
+};
+
+defineExpose({
+  open
+});
+</script>
+
+<style scoped lang="scss">
+.form-tip {
+  display: inline-block;
+  margin-left: 12px;
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+}
+</style>

+ 0 - 161
src/views/userManagement/detailDialog.vue

@@ -1,161 +0,0 @@
-<template>
-  <el-dialog v-model="dialogShow" title="达人详情" draggable width="700px" @close="handleClose">
-    <div v-if="detailData.imgUrl.length > 0" class="image-gallery">
-      <div class="image-grid">
-        <div v-for="(url, index) in detailData.imgUrl" :key="index" class="image-item">
-          <el-image
-            :src="url"
-            :preview-src-list="detailData.imgUrl"
-            fit="cover"
-            class="gallery-image"
-            :hide-on-click-modal="false"
-            :preview-teleported="true"
-          />
-        </div>
-      </div>
-    </div>
-
-    <!-- 详情 -->
-    <div class="review-detail">
-      <el-descriptions :column="1" border>
-        <el-descriptions-item label="用户ID">
-          {{ detailData.id }}
-        </el-descriptions-item>
-        <el-descriptions-item label="用户昵称">
-          {{ detailData.userName }}
-        </el-descriptions-item>
-        <el-descriptions-item label="姓名">
-          {{ detailData.realName }}
-        </el-descriptions-item>
-        <el-descriptions-item label="身份证号码">
-          {{ detailData.idCard }}
-        </el-descriptions-item>
-        <el-descriptions-item label="联系电话">
-          {{ detailData.userPhone }}
-        </el-descriptions-item>
-
-        <el-descriptions-item label="推广模块">
-          <el-tag v-for="(item, index) in detailData.promoteType" :key="index" style="margin-right: 5px; margin-bottom: 5px">
-            {{ item }}
-          </el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="状态">
-          <el-tag v-if="detailData.expertStatus == 0" type="success"> 已通过 </el-tag>
-          <el-tag v-if="detailData.expertStatus == 1" type="primary"> 待审核 </el-tag>
-          <el-tag v-if="detailData.expertStatus == 2" type="danger"> 已驳回 </el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="审核时间" v-if="detailData.reviewTime">
-          {{ detailData.reviewTime || "未审核" }}
-        </el-descriptions-item>
-        <el-descriptions-item label="驳回原因" v-if="detailData.refuseBecause">
-          {{ detailData.refuseBecause }}
-        </el-descriptions-item>
-        <el-descriptions-item label="简介">
-          <div class="introduction">
-            {{ detailData.addExplanation || "--" }}
-          </div>
-        </el-descriptions-item>
-      </el-descriptions>
-    </div>
-  </el-dialog>
-</template>
-
-<script setup lang="ts">
-import { ref, defineExpose, defineEmits } from "vue";
-import { getViewDetails } from "@/api/modules/masterManagemen";
-import { ElMessage } from "element-plus";
-
-// 定义弹窗状态
-const dialogShow = ref(false);
-
-// 详情数据
-const detailData: any = ref({});
-
-const emit = defineEmits(["close"]);
-// 显示弹窗方法
-const open = async (userId: string | number) => {
-  try {
-    const res: any = await getViewDetails(userId);
-    detailData.value = {
-      ...res.data
-    };
-    if (res.data.imgUrl) {
-      detailData.value.imgUrl = res.data.imgUrl.split(",");
-    }
-    if (res.data.promoteType) {
-      detailData.value.promoteType = res.data.promoteType.split(",");
-    }
-    dialogShow.value = true;
-  } catch (error) {
-    ElMessage.error("加载详情失败,请重试");
-    handleClose();
-  }
-};
-
-// 关闭弹窗
-const handleClose = () => {
-  dialogShow.value = false;
-};
-
-// 暴露给父组件的方法
-defineExpose({
-  open
-});
-</script>
-
-<style lang="scss" scoped>
-/* 样式保持不变 */
-.review-detail {
-  margin-bottom: 20px;
-  .introduction {
-    line-height: 1.5;
-    word-break: break-word;
-    white-space: pre-wrap;
-  }
-}
-.form-tip {
-  margin-top: 5px;
-  font-size: 12px;
-  color: #909399;
-}
-.image-gallery {
-  padding: 10px;
-  margin-bottom: 20px;
-  background-color: #f5f7fa;
-  border-radius: 4px;
-  .image-grid {
-    display: flex;
-    flex-wrap: wrap;
-    gap: 10px;
-    .image-item {
-      width: 100px;
-      height: 100px;
-      cursor: pointer;
-      transition: all 0.3s;
-      &:hover {
-        box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
-        transform: scale(1.05);
-      }
-      .gallery-image {
-        width: 100%;
-        height: 100%;
-        overflow: hidden;
-        border-radius: 4px;
-        :deep(.el-image__inner) {
-          width: 100%;
-          height: 100%;
-          object-fit: cover;
-        }
-        :deep(.el-image__error) {
-          display: flex;
-          align-items: center;
-          justify-content: center;
-          font-size: 12px;
-          color: #909399;
-          background-color: #f0f2f5;
-        }
-      }
-    }
-  }
-}
-</style>

+ 47 - 146
src/views/userManagement/index.vue

@@ -2,72 +2,42 @@
   <div class="table-box">
     <ProTable ref="proTable" :columns="columns" :request-api="getTableList" :data-callback="dataCallback" row-key="id">
       <template #tableHeader>
-        <el-button type="primary" :icon="CirclePlus" @click="openDialog()"> 新增账号 </el-button>
+        <el-button type="primary" :icon="CirclePlus" @click="handleCreate"> 新增账号 </el-button>
       </template>
       <template #operation="scope">
-        <el-button type="primary" link :icon="EditPen" @click="openDialog(scope.row)"> 编辑 </el-button>
+        <el-button type="primary" link :icon="EditPen" @click="handleEdit(scope.row)"> 编辑 </el-button>
       </template>
     </ProTable>
 
-    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="520px" destroy-on-close>
-      <el-form ref="dialogFormRef" :model="dialogForm" :rules="dialogRules" label-width="100px" label-suffix=":">
-        <el-form-item label="登录账号" prop="loginAccount">
-          <el-input v-model="dialogForm.loginAccount" placeholder="请输入登录账号" />
-        </el-form-item>
-
-        <el-form-item label="关联律所" prop="roleId">
-          <el-select v-model="dialogForm.roleId" placeholder="请选择关联律所">
-            <el-option v-for="item in roleOptions" :key="item.value" :label="item.label" :value="item.value" />
-          </el-select>
-        </el-form-item>
-        <el-form-item label="登录密码" prop="loginPassword">
-          <el-input v-model="dialogForm.loginPassword" placeholder="请输入登录密码" type="password" show-password />
-          <span class="form-tip" v-if="isEdit"> 若无需修改密码,可留空 </span>
-        </el-form-item>
-        <el-form-item label="账号状态" prop="status">
-          <el-switch v-model="dialogForm.status" :active-value="1" :inactive-value="0" active-text="启用" inactive-text="禁用" />
-        </el-form-item>
-        <el-form-item label="备注" prop="remark">
-          <el-input
-            v-model="dialogForm.remark"
-            type="textarea"
-            :autosize="{ minRows: 2, maxRows: 4 }"
-            placeholder="请输入备注信息"
-          />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <el-button @click="dialogVisible = false"> 取 消 </el-button>
-        <el-button type="primary" :loading="dialogLoading" @click="submitDialog"> 保 存 </el-button>
-      </template>
-    </el-dialog>
+    <UserDialog ref="userDialogRef" :law-firm-options="lawFirmOptions" @success="refreshTable" />
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, computed, nextTick } from "vue";
-import { ElMessage, type FormInstance } from "element-plus";
-import md5 from "md5";
+import { ref, reactive, onMounted } from "vue";
+import { ElMessage } from "element-plus";
 import ProTable from "@/components/ProTable/index.vue";
 import type { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
 import type { User } from "@/api/interface";
-import { getUserList, addUser, editUser } from "@/api/modules/user";
+import { getUserList } from "@/api/modules/user";
+import { getLawFirmPage } from "@/api/modules/lawyer";
 import { CirclePlus, EditPen } from "@element-plus/icons-vue";
+import UserDialog from "./components/UserDialog.vue";
 
 const proTable = ref<ProTableInstance>();
+const userDialogRef = ref<InstanceType<typeof UserDialog>>();
+const lawFirmOptions = ref<{ label: string; value: string | number }[]>([]);
 
 const statusOptions = [
   { label: "启用", value: 1 },
   { label: "禁用", value: 0 }
 ];
 
-const roleOptions = ref<{ label: string; value: string }[]>([]);
-
 const columns = reactive<ColumnProps<User.ResUserList>[]>([
   { type: "index", label: "序号", width: 60 },
   { prop: "loginAccount", label: "登录账号", search: { el: "input", props: { placeholder: "请输入登录账号" } } },
   { prop: "loginPassword", label: "登录密码" },
-  { prop: "roleId", label: "关联律所" },
+  { prop: "roleName", label: "关联律所" },
   {
     prop: "status",
     label: "账号状态",
@@ -96,120 +66,51 @@ const dataCallback = (data: any) => ({
   total: data.total
 });
 
-const dialogVisible = ref(false);
-const dialogLoading = ref(false);
-const dialogMode = ref<"add" | "edit">("add");
-const dialogTitle = computed(() => (dialogMode.value === "add" ? "新增账号" : "编辑账号"));
-const dialogFormRef = ref<FormInstance>();
-
-const dialogForm = reactive({
-  id: "",
-  loginAccount: "",
-  roleId: "",
-  loginPassword: "",
-  status: 1,
-  remark: ""
-});
-
-const dialogRules = reactive({
-  loginAccount: [{ required: true, message: "请输入登录账号", trigger: "blur" }],
-  roleId: [{ required: true, message: "请选择账号角色", trigger: "change" }],
-  loginPassword: [
-    {
-      validator: (_: any, value: string, callback: (error?: Error) => void) => {
-        if (dialogMode.value === "add" && !value) return callback(new Error("请输入登录密码"));
-        callback();
-      },
-      trigger: "blur"
-    }
-  ]
-});
-
-const isEdit = computed(() => dialogMode.value === "edit");
-
-const resetDialogForm = () => {
-  dialogForm.id = "";
-  dialogForm.loginAccount = "";
-  dialogForm.roleId = "";
-  dialogForm.loginPassword = "";
-  dialogForm.status = 1;
-  dialogForm.remark = "";
+const refreshTable = () => {
+  proTable.value?.getTableList();
 };
 
-const openDialog = (row?: Partial<User.ResUserList>) => {
-  resetDialogForm();
-  if (row && row.id) {
-    dialogMode.value = "edit";
-    dialogForm.id = row.id as string;
-    dialogForm.loginAccount = (row.loginAccount as string) || "";
-    dialogForm.roleId = (row.roleId as string) || "";
-    dialogForm.status = (row.status as number) ?? 1;
-    dialogForm.remark = (row.remark as string) || "";
-  } else {
-    dialogMode.value = "add";
-  }
-  dialogVisible.value = true;
-  nextTick(() => dialogFormRef.value?.clearValidate());
-};
-
-const buildPayload = () => {
-  const payload: Record<string, any> = {
-    id: dialogForm.id,
-    loginAccount: dialogForm.loginAccount,
-    roleId: dialogForm.roleId,
-    status: dialogForm.status,
-    remark: dialogForm.remark
-  };
-  if (!payload.id) delete payload.id;
-  if (dialogForm.loginPassword) {
-    payload.loginPassword = md5(dialogForm.loginPassword);
-  }
-  return payload;
+const handleCreate = () => {
+  userDialogRef.value?.open({
+    title: "新增账号",
+    mode: "add",
+    onSubmit: async payload => {
+      // await addUser(payload);
+      ElMessage.success("新增账号成功");
+    }
+  });
 };
 
-const submitDialog = () => {
-  dialogFormRef.value?.validate(async valid => {
-    if (!valid) return;
-    dialogLoading.value = true;
-    try {
-      const payload = buildPayload();
-      if (dialogMode.value === "add") {
-        await addUser(payload);
-        ElMessage.success("新增账号成功");
-      } else {
-        await editUser(payload);
-        ElMessage.success("编辑账号成功");
-      }
-      dialogVisible.value = false;
-      proTable.value?.getTableList();
-    } finally {
-      dialogLoading.value = false;
+const handleEdit = (row: Partial<User.ResUserList>) => {
+  userDialogRef.value?.open({
+    title: "编辑账号",
+    mode: "edit",
+    row,
+    onSubmit: async payload => {
+      // await editUser(payload);
+      ElMessage.success("编辑账号成功");
     }
   });
 };
 
-const mockFetchRoleOptions = async () => {
-  // 模拟异步接口
-  const res = await new Promise<{ label: string; value: string }[]>(resolve => {
-    setTimeout(() => {
-      resolve([
-        { label: "运营管理员", value: "ops" },
-        { label: "内容管理员", value: "content" },
-        { label: "系统管理员", value: "admin" }
-      ]);
-    }, 200);
-  });
-  roleOptions.value = res;
+const fetchLawFirmOptions = async () => {
+  try {
+    const res: any = await getLawFirmPage({ page: 1, size: 999 });
+    const list = res?.records || res?.data?.records || res?.data?.list || [];
+    lawFirmOptions.value = list.map((item: any) => ({
+      label: item.firmName,
+      value: item.id
+    }));
+  } catch (error) {
+    console.error("获取律所列表失败", error);
+    lawFirmOptions.value = [];
+  }
 };
 
-mockFetchRoleOptions();
+onMounted(() => {
+  fetchLawFirmOptions();
+  refreshTable();
+});
 </script>
 
-<style scoped lang="scss">
-.form-tip {
-  display: inline-block;
-  margin-left: 12px;
-  font-size: 12px;
-  color: var(--el-text-color-secondary);
-}
-</style>
+<style scoped lang="scss"></style>

+ 0 - 328
src/views/userManagement/reviewDialog.vue

@@ -1,328 +0,0 @@
-<template>
-  <el-dialog v-model="dialogShow" title="达人审核" draggable width="700px" @close="handleClose">
-    <div v-if="imageUrls.length > 0" class="image-gallery">
-      <div class="image-grid">
-        <div v-for="(url, index) in imageUrls" :key="index" class="image-item">
-          <el-image
-            :src="url"
-            :preview-src-list="imageUrls"
-            fit="cover"
-            class="gallery-image"
-            :hide-on-click-modal="false"
-            :preview-teleported="true"
-          />
-        </div>
-      </div>
-    </div>
-
-    <!-- 详情展示区域 -->
-    <div class="review-detail">
-      <el-descriptions :column="1" border>
-        <el-descriptions-item label="ID">
-          {{ detailData.id }}
-        </el-descriptions-item>
-        <el-descriptions-item label="用户昵称">
-          {{ detailData.userName }}
-        </el-descriptions-item>
-        <el-descriptions-item label="姓名">
-          {{ detailData.realName }}
-        </el-descriptions-item>
-        <el-descriptions-item label="身份证号码">
-          {{ detailData.idCard }}
-        </el-descriptions-item>
-        <el-descriptions-item label="联系电话">
-          {{ detailData.userPhone }}
-        </el-descriptions-item>
-
-        <el-descriptions-item label="推广模块">
-          <el-tag
-            v-for="(item, index) in getPromoteTypes(detailData.promoteType)"
-            :key="index"
-            style="margin-right: 5px; margin-bottom: 5px"
-          >
-            {{ item }}
-          </el-tag>
-        </el-descriptions-item>
-
-        <el-descriptions-item label="状态">
-          <el-tag v-if="detailData.expertStatus == 0" type="success"> 已通过 </el-tag>
-          <el-tag v-if="detailData.expertStatus == 1" type="primary"> 待审核 </el-tag>
-          <el-tag v-if="detailData.expertStatus == 2" type="danger"> 已驳回 </el-tag>
-        </el-descriptions-item>
-        <el-descriptions-item label="申请时间">
-          {{ detailData.createdTime }}
-        </el-descriptions-item>
-        <el-descriptions-item label="简介">
-          <div class="introduction">
-            {{ detailData.addExplanation }}
-          </div>
-        </el-descriptions-item>
-      </el-descriptions>
-    </div>
-
-    <!-- 操作按钮区域 -->
-    <template #footer>
-      <span class="dialog-footer">
-        <el-button type="success" @click="openCommissionDialog"> 同意 </el-button>
-        <el-button type="warning" @click="openRejectDialog"> 驳回 </el-button>
-        <el-button @click="handleClose">关闭</el-button>
-      </span>
-    </template>
-
-    <!-- 设置佣金及预付款弹窗 -->
-    <el-dialog title="设置佣金及预付款" v-model="commissionDialogVisible" width="400px" append-to-body>
-      <el-form :model="commissionForm" :rules="commissionRules" ref="commissionFormRef" label-width="120px">
-        <el-form-item label="佣金比例" prop="commissionRate">
-          <el-input-number
-            v-model="commissionForm.commissionRate"
-            :min="0"
-            :max="99"
-            :precision="0"
-            controls-position="right"
-            style="width: 80%"
-          />
-          <div class="form-tip">范围:0-99的整数,表示百分比</div>
-        </el-form-item>
-        <el-form-item label="预付款比例" prop="advanceRate">
-          <el-input-number
-            v-model="commissionForm.advanceRate"
-            :min="1"
-            :max="99"
-            :precision="0"
-            controls-position="right"
-            style="width: 80%"
-          />
-          <div class="form-tip">范围:1-99的整数,表示百分比</div>
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="cancelCommissionDialog">取消</el-button>
-          <el-button type="primary" @click="submitCommission">确认</el-button>
-        </span>
-      </template>
-    </el-dialog>
-
-    <!-- 驳回原因弹窗 -->
-    <el-dialog title="驳回原因" v-model="rejectDialogVisible" width="400px" append-to-body>
-      <el-form :model="rejectForm" :rules="rejectRules" ref="rejectFormRef" label-width="80px">
-        <el-form-item label="原因" prop="comment">
-          <el-input v-model="rejectForm.comment" type="textarea" :rows="3" placeholder="请输入驳回原因" />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="cancelRejectDialog">取消</el-button>
-          <el-button type="primary" @click="submitReject">确认</el-button>
-        </span>
-      </template>
-    </el-dialog>
-  </el-dialog>
-</template>
-
-<script setup lang="ts">
-import { ref, reactive, defineExpose, defineEmits, computed } from "vue";
-import { ElMessageBox } from "element-plus";
-import type { FormInstance, FormRules } from "element-plus";
-
-// 定义弹窗状态
-const dialogShow = ref(false);
-const commissionDialogVisible = ref(false);
-const rejectDialogVisible = ref(false);
-
-// 详情数据
-const detailData: any = ref({});
-
-// 处理图片URL
-const imageUrls = computed(() => {
-  if (!detailData.value.imgUrl) return [];
-  return detailData.value.imgUrl.split(",");
-});
-
-// 处理推广板块字符串
-const getPromoteTypes = (promoteType: string) => {
-  if (!promoteType) return [];
-  return promoteType.split(",");
-};
-
-// 佣金表单
-const commissionForm = reactive({
-  commissionRate: 0,
-  advanceRate: 1
-});
-
-// 驳回表单
-const rejectForm = reactive({
-  comment: ""
-});
-
-// 佣金表单验证规则
-const commissionRules: FormRules = {
-  commissionRate: [
-    { required: true, message: "请输入佣金比例", trigger: "blur" },
-    { type: "number", min: 0, max: 99, message: "佣金比例必须是0-99的整数", trigger: "blur" }
-  ],
-  advanceRate: [
-    { required: true, message: "请输入预付款比例", trigger: "blur" },
-    { type: "number", min: 1, max: 99, message: "预付款比例必须是1-99的整数", trigger: "blur" }
-  ]
-};
-
-// 驳回表单验证规则
-const rejectRules: FormRules = {
-  comment: [
-    { required: true, message: "请输入驳回原因", trigger: "blur" },
-    { min: 2, max: 200, message: "驳回原因长度为2-200个字符", trigger: "blur" }
-  ]
-};
-
-// 表单引用
-const commissionFormRef = ref<FormInstance>();
-const rejectFormRef = ref<FormInstance>();
-
-// 事件触发
-const emit = defineEmits(["approve", "reject", "close"]);
-
-// 显示弹窗方法
-const open = (data: any) => {
-  detailData.value = { ...data };
-  dialogShow.value = true;
-};
-
-// 关闭弹窗
-const handleClose = () => {
-  dialogShow.value = false;
-  emit("close");
-};
-
-// 打开佣金设置弹窗
-const openCommissionDialog = () => {
-  commissionForm.commissionRate = 0;
-  commissionForm.advanceRate = 1;
-  commissionDialogVisible.value = true;
-};
-
-// 取消佣金设置
-const cancelCommissionDialog = () => {
-  commissionDialogVisible.value = false;
-};
-
-// 提交佣金设置
-const submitCommission = async () => {
-  if (!commissionFormRef.value) return;
-
-  const valid = await commissionFormRef.value.validate();
-  if (!valid) return;
-
-  // 显示确认提示
-  try {
-    await ElMessageBox.confirm(
-      `确认同意申请并设置佣金比例为 ${commissionForm.commissionRate}%,预付款比例为 ${commissionForm.advanceRate}%?`,
-      "确认同意",
-      {
-        confirmButtonText: "确认",
-        cancelButtonText: "取消",
-        type: "warning"
-      }
-    );
-
-    // 触发同意事件
-    emit("approve", {
-      id: detailData.value.id,
-      commissionRate: commissionForm.commissionRate,
-      advanceRate: commissionForm.advanceRate,
-      userPhone: detailData.value.userPhone
-    });
-
-    // 关闭所有弹窗
-    commissionDialogVisible.value = false;
-    dialogShow.value = false;
-  } catch (error) {}
-};
-
-// 打开驳回原因弹窗
-const openRejectDialog = () => {
-  rejectForm.comment = "";
-  rejectDialogVisible.value = true;
-};
-
-// 取消驳回
-const cancelRejectDialog = () => {
-  rejectDialogVisible.value = false;
-};
-
-// 提交驳回
-const submitReject = async () => {
-  if (!rejectFormRef.value) return;
-
-  const valid = await rejectFormRef.value.validate();
-  if (!valid) return;
-
-  // 显示确认提示
-  try {
-    await ElMessageBox.confirm("确认驳回申请?", "确认驳回", {
-      confirmButtonText: "确认",
-      cancelButtonText: "取消",
-      type: "warning"
-    });
-
-    // 触发驳回事件
-    emit("reject", {
-      id: detailData.value.id,
-      comment: rejectForm.comment,
-      userPhone: detailData.value.userPhone
-    });
-
-    // 关闭所有弹窗
-    rejectDialogVisible.value = false;
-    dialogShow.value = false;
-  } catch (error) {}
-};
-
-// 暴露给父组件的方法
-defineExpose({
-  open,
-  close: handleClose
-});
-</script>
-
-<style lang="scss" scoped>
-.review-detail {
-  margin-bottom: 20px;
-  .introduction {
-    line-height: 1.5;
-    word-break: break-word;
-    white-space: pre-wrap;
-  }
-}
-.form-tip {
-  margin-top: 5px;
-  font-size: 12px;
-  color: #909399;
-}
-
-/* 隐藏预览时的操作栏 */
-:deep(.el-image-viewer__btn) {
-  display: none !important;
-}
-
-/* 或者更精确地隐藏操作栏区域 */
-:deep(.el-image-viewer__actions) {
-  display: none !important;
-}
-.image-grid {
-  display: grid;
-  grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
-  gap: 8px;
-}
-.image-item {
-  aspect-ratio: 1;
-  overflow: hidden;
-}
-.gallery-image {
-  width: 100%;
-  height: 100%;
-  cursor: pointer;
-  object-fit: cover;
-}
-</style>