sgc пре 2 недеља
родитељ
комит
aa3580c233

+ 2 - 2
.env.development

@@ -20,7 +20,7 @@ VITE_API_URL_STORE = /api  # 开发环境使用
 
 # 开发环境跨域代理,支持配置多个
 # VITE_PROXY = [["/api","https://api.ailien.shop"]] #生产环境
-# VITE_PROXY = [["/api","http://192.168.2.181:7000"]] # 本地联调
-VITE_PROXY = [["/api","http://192.168.2.251:7000"]] # 本地联调
+VITE_PROXY = [["/api","http://192.168.2.75:8000"]] # 本地联调
+# VITE_PROXY = [["/api","http://192.168.2.251:7000"]] # 本地联调
 
 # VITE_PROXY = [["/api-easymock","https://mock.mengxuegu.com"],["/api-fastmock","https://www.fastmock.site"]]


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

@@ -38,6 +38,14 @@ export const deleteLawyerUser = (params: any) => {
 export const editLawyerUser = (params: any) => {
   return http.post(url + `/lawyer/user/editLawyerUser`, params);
 };
+// 推荐律师
+export const recommendLawyer = (params: any) => {
+  return http.post(url + `/lawyer/user/isRecommended`, params);
+};
+// 导出律师列表
+export const exportLawyerList = (params: any) => {
+  return http.get(url + `/lawyer/user/exportLawyerList`, params, { responseType: "blob" });
+};
 //////////////////////////// 法律场景管理 ////////////////////////////
 // 法律问题场景 列表查询
 export const getScenePage = (params: any) => {
@@ -55,6 +63,10 @@ export const deleteScene = (params: any) => {
 export const editScene = (params: any) => {
   return http.post(url + `/lawyer/legalProblemScenar/editLawyerLegalProblemScenar`, params);
 };
+// 设置法律问题场景状态
+export const setSceneStatus = (params: any) => {
+  return http.post(url + `/lawyer/legalProblemScenar/setStatus`, params);
+};
 //////////////////////////// 专业领域管理 ////////////////////////////
 // 专业领域 列表查询
 export const getExpertiseAreaPage = (params: any) => {

+ 12 - 0
src/views/home/index.scss

@@ -0,0 +1,12 @@
+.home {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  height: 100%;
+  .home-bg {
+    width: 70%;
+    max-width: 1200px;
+    margin-bottom: 20px;
+  }
+}

+ 11 - 0
src/views/home/index.vue

@@ -0,0 +1,11 @@
+<template>
+  <div class="home card">
+    <img class="home-bg" src="@/assets/images/welcome.png" alt="welcome" />
+  </div>
+</template>
+
+<script setup lang="ts" name="home"></script>
+
+<style scoped lang="scss">
+@import "./index";
+</style>

+ 148 - 158
src/views/lawyerManagement/lawyer/index.vue

@@ -2,62 +2,126 @@
   <div class="table-box">
     <ProTable ref="proTable" :columns="columns" :request-api="getTableList" :data-callback="dataCallback">
       <template #tableHeader>
-        <el-button type="primary" :icon="CirclePlus" :disabled="!isAdmin" @click="handleCreate"> 新增律师 </el-button>
+        <el-button
+          type="primary"
+          :icon="Download"
+          :loading="exportLoading"
+          :disabled="!isAdmin || !hasExportableData"
+          @click="handleExport"
+        >
+          导出
+        </el-button>
       </template>
       <template #operation="scope">
-        <el-button type="primary" link :icon="EditPen" :disabled="!isAdmin" @click="handleEdit(scope.row)"> 编辑 </el-button>
-        <el-button type="danger" link :icon="Delete" :disabled="!isAdmin" @click="handleDelete(scope.row)"> 删除 </el-button>
+        <el-button type="primary" link :icon="View" @click="handleDetail(scope.row)"> 查看详情 </el-button>
+        <el-button
+          type="warning"
+          link
+          :icon="Number(scope.row.isRecommended) === 1 ? StarFilled : Star"
+          :disabled="!isAdmin"
+          @click="handleRecommend(scope.row)"
+        >
+          {{ Number(scope.row.isRecommended) === 1 ? "取消推荐" : "设为推荐" }}
+        </el-button>
       </template>
     </ProTable>
-    <LawyerDialog
-      ref="lawyerDialogRef"
-      :law-firm-options="lawFirmOptions"
-      :law-firm-loading="lawFirmLoading"
-      :fetch-law-firms="fetchLawFirmOptions"
-      :scene-options="sceneOptions"
-      :professional-options="professionalOptions"
-      @success="refreshTable"
-    />
+    <el-drawer v-model="detailVisible" title="律师详情" :size="480" append-to-body>
+      <el-descriptions v-if="detailData" :column="1" border>
+        <el-descriptions-item v-for="field in detailFields" :key="field.prop" :label="field.label">
+          {{ getDetailValue(field) }}
+        </el-descriptions-item>
+      </el-descriptions>
+      <div v-else class="detail-empty">暂无数据</div>
+    </el-drawer>
   </div>
 </template>
 
 <script setup lang="ts">
 import { ref, reactive, onMounted, computed } from "vue";
-import { ElMessage, ElMessageBox } from "element-plus";
+import { ElMessage } from "element-plus";
 import ProTable from "@/components/ProTable/index.vue";
 import type { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
-import {
-  getLawyerPage,
-  addLawyerUser,
-  deleteLawyerUser,
-  editLawyerUser,
-  getSelectList,
-  getScenePage,
-  getExpertiseAreaPage
-} from "@/api/modules/lawyer";
-import { CirclePlus, Delete, EditPen } from "@element-plus/icons-vue";
-import LawyerDialog from "./components/LawyerDialog.vue";
+import { getLawyerPage, recommendLawyer, exportLawyerList } from "@/api/modules/lawyer";
+import { useDownload } from "@/hooks/useDownload";
+import { Download, View, Star, StarFilled } from "@element-plus/icons-vue";
 import { useUserStore } from "@/stores/modules/user";
 
 const proTable = ref<ProTableInstance>();
-const lawyerDialogRef = ref<InstanceType<typeof LawyerDialog>>();
-const lawFirmOptions = ref<
-  { label: string; value: string | number; paymentList?: { id?: string | number; paymentAccount: string }[] }[]
->([]);
-const lawFirmLoading = ref(false);
-const sceneOptions = ref<any[]>([]);
-const professionalOptions = ref<{ label: string; value: string | number }[]>([]);
-
+const exportLoading = ref(false);
+const tableTotal = ref(0);
+const hasExportableData = computed(() => tableTotal.value > 0);
+const detailVisible = ref(false);
+const detailData = ref<Record<string, any> | null>(null);
 const userStore = useUserStore();
 const isAdmin = computed(() => userStore.userInfo?.name === "admin");
 const loginFirmId = computed(() => userStore.userInfo?.roleId || "");
 
+interface DetailField {
+  label: string;
+  prop: string;
+  formatter?: (row: Record<string, any>) => string;
+}
+
+const statusText = (status?: number | string) => {
+  return Number(status) === 1 ? "接单中" : "暂停接单";
+};
+
+const recommendText = (value?: number | string) => {
+  return Number(value) === 1 ? "已推荐" : "未推荐";
+};
+
+const detailFields: DetailField[] = [
+  { label: "律师姓名", prop: "name" },
+  { label: "联系电话", prop: "phone" },
+  { label: "法律场景", prop: "firstLevelScenario" },
+  {
+    label: "接单状态",
+    prop: "status",
+    formatter: row => statusText(row.status)
+  },
+  { label: "收款账号", prop: "paymentNum" },
+  { label: "所属律所", prop: "firmName" },
+  {
+    label: "推荐状态",
+    prop: "isRecommended",
+    formatter: row => recommendText(row.isRecommended)
+  }
+];
+
+const getDetailValue = (field: DetailField) => {
+  const data = detailData.value;
+  if (!data) return "-";
+  if (field.formatter) return field.formatter(data);
+  const value = data[field.prop];
+  return value === undefined || value === null || value === "" ? "-" : value;
+};
+
 const ensureAdmin = () => {
   if (isAdmin.value) return true;
   ElMessage.warning("仅管理员可使用该功能");
   return false;
 };
 
+const normalizeRequestParams = (params: Record<string, any> = {}) => {
+  const newParams: Record<string, any> = { ...params };
+  if (Reflect.has(newParams, "pageNum")) {
+    newParams.page = newParams.pageNum;
+    delete newParams.pageNum;
+  }
+  if (Reflect.has(newParams, "pageSize")) {
+    newParams.size = newParams.pageSize;
+    delete newParams.pageSize;
+  }
+  if (Array.isArray(newParams.time) && newParams.time.length === 2) {
+    [newParams.startTime, newParams.endTime] = newParams.time;
+  }
+  delete newParams.time;
+  if (!isAdmin.value && loginFirmId.value) {
+    newParams.firmId = loginFirmId.value;
+  }
+  return newParams;
+};
+
 const columns = reactive<ColumnProps<any>[]>([
   { label: "序号", type: "index", width: 60, align: "center" },
   { label: "律师姓名", prop: "name", width: 140, search: { el: "input", props: { placeholder: "请输入律师姓名" } } },
@@ -80,7 +144,7 @@ const columns = reactive<ColumnProps<any>[]>([
     }
   },
   // { label: "专业领域", prop: "expertiseAreaInfo" },
-  { label: "法律场景", prop: "firstLevelScenario" },
+  { label: "法律场景", prop: "scenarioNames" },
   {
     label: "接单状态",
     prop: "status",
@@ -92,159 +156,85 @@ const columns = reactive<ColumnProps<any>[]>([
     ],
     fieldNames: { label: "label", value: "value" }
   },
+  {
+    label: "推荐状态",
+    prop: "isRecommended",
+    width: 120,
+    tag: true,
+    enum: [
+      { label: "已推荐", value: 1, tagType: "success" },
+      { label: "未推荐", value: 0, tagType: "info" }
+    ],
+    fieldNames: { label: "label", value: "value" }
+  },
   { label: "收款账号", prop: "paymentNum", width: 220 },
   { label: "所属律所", prop: "firmName", width: 220 },
-  { label: "操作", prop: "operation", width: 200, fixed: "right" }
+  { label: "操作", prop: "operation", width: 240, fixed: "right" }
 ]);
 
 const getTableList = async (params: any) => {
-  const newParams: Record<string, any> = { ...params, page: params.pageNum, size: params.pageSize };
-  delete newParams.pageNum;
-  delete newParams.pageSize;
-  if (!isAdmin.value && loginFirmId.value) {
-    newParams.firmId = loginFirmId.value;
-  }
-  if (params.time) {
-    newParams.startTime = params.time[0];
-    newParams.endTime = params.time[1];
-  }
-  delete newParams.time;
-  return getLawyerPage(newParams);
+  return getLawyerPage(normalizeRequestParams(params));
 };
 
-const dataCallback = (data: any) => ({
-  list: data.records,
-  total: data.total
-});
+const dataCallback = (data: any) => {
+  const total = Number(data.total) || 0;
+  tableTotal.value = total;
+  return {
+    list: data.records,
+    total
+  };
+};
 
 const refreshTable = () => {
   proTable.value?.getTableList();
 };
 
-const handleCreate = () => {
-  if (!ensureAdmin()) return;
-  lawyerDialogRef.value?.open({
-    title: "新增律师",
-    onSubmit: async payload => {
-      let params = {
-        name: payload.name,
-        phone: payload.phone,
-        practiceStartDate: payload.practiceStartDate,
-        firstLevelScenario: payload.firstLevelScenario,
-        status: payload.status,
-        paymentNum: payload.paymentNum,
-        firmId: payload.lawFirmId
-      };
-      await addLawyerUser(params);
-      ElMessage.success("新增成功");
-      refreshTable();
-    }
-  });
-};
-
-const handleEdit = (row: any) => {
-  if (!ensureAdmin()) return;
-  lawyerDialogRef.value?.open({
-    title: "编辑律师",
-    row,
-    onSubmit: async payload => {
-      let params = {
-        id: payload.id,
-        name: payload.name,
-        phone: payload.phone,
-        practiceStartDate: payload.practiceStartDate,
-        firstLevelScenario: payload.firstLevelScenario,
-        status: payload.status,
-        paymentNum: payload.paymentNum,
-        firmId: payload.lawFirmId
-      };
-      await editLawyerUser(params);
-      ElMessage.success("编辑成功");
-      refreshTable();
-    }
-  });
-};
-
-const handleDelete = (row: any) => {
-  if (!ensureAdmin()) return;
-  ElMessageBox.confirm(`确定删除律师【${row.userName}】吗?`, "提示", {
-    type: "warning"
-  })
-    .then(async () => {
-      await deleteLawyerUser({ id: row.id });
-      ElMessage.success("删除成功");
-      refreshTable();
-    })
-    .catch(() => {});
+const collectTableParams = () => {
+  const table = proTable.value;
+  if (!table) return {};
+  const params: Record<string, any> = { ...(table.searchParam || {}) };
+  if (table.pageable) {
+    params.pageNum = table.pageable.pageNum;
+    params.pageSize = table.pageable.pageSize;
+  }
+  return params;
 };
 
-const fetchLawFirmOptions = async (keyword = "") => {
-  lawFirmLoading.value = true;
+const handleExport = async () => {
+  if (!ensureAdmin() || !hasExportableData.value) return;
+  exportLoading.value = true;
   try {
-    const res: any = await getSelectList({ firmName: keyword });
-    const listSource = res?.data || [];
-    lawFirmOptions.value = listSource.map((item: any) => ({
-      label: item.firmName,
-      value: item.id,
-      paymentList: (item.paymentList || []).map((payment: any) => ({
-        ...payment,
-        paymentAccount: payment.paymentAccount || ""
-      }))
-    }));
-  } catch (error) {
-    console.error("获取律所列表失败", error);
-    lawFirmOptions.value = [];
+    const params = normalizeRequestParams(collectTableParams());
+    await useDownload(exportLawyerList, "律师列表", params);
   } finally {
-    lawFirmLoading.value = false;
+    exportLoading.value = false;
   }
 };
 
-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 handleDetail = (row: any) => {
+  detailData.value = { ...row };
+  detailVisible.value = true;
 };
 
-const fetchSceneOptions = async () => {
-  try {
-    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 = [];
+const handleRecommend = async (row: any) => {
+  if (!ensureAdmin()) return;
+  const targetId = row?.id;
+  if (!targetId) {
+    ElMessage.error("未获取到律师ID");
+    return;
   }
-};
-
-const fetchProfessionalOptions = async () => {
+  const current = Number(row?.isRecommended) === 1 ? 1 : 0;
+  const nextStatus = current === 1 ? 0 : 1;
   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
-    }));
+    await recommendLawyer({ lawyerId: targetId, isRecommended: nextStatus });
+    ElMessage.success(nextStatus === 1 ? "设置推荐成功" : "取消推荐成功");
+    refreshTable();
   } catch (error) {
-    console.error("获取专业领域失败", error);
-    professionalOptions.value = [];
+    console.error("更新推荐状态失败", error);
   }
 };
 
 onMounted(() => {
-  fetchLawFirmOptions();
-  fetchSceneOptions();
-  fetchProfessionalOptions();
   refreshTable();
 });
 </script>

+ 20 - 1
src/views/lawyerManagement/legalScene/index.vue

@@ -44,6 +44,9 @@
               <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="handleToggleStatus(data)">
+                {{ data.status === 1 ? "停用" : "启用" }}
+              </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>
@@ -59,7 +62,7 @@
 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 { getScenePage, addScene, editScene, deleteScene, setSceneStatus } from "@/api/modules/lawyer";
 import SceneDialog from "./components/SceneDialog.vue";
 
 const treeProps = { children: "children", label: "name" };
@@ -170,6 +173,7 @@ const loadSceneList = async () => {
 const handleCreate = () => {
   sceneDialogRef.value?.open({
     title: "新增场景",
+    type: "create",
     onSubmit: async payload => {
       let params = {
         name: payload.name,
@@ -209,6 +213,7 @@ const handleAddChild = (row: any) => {
 const handleEdit = (row: any) => {
   sceneDialogRef.value?.open({
     title: "编辑场景",
+    type: "edit",
     row,
     onSubmit: async payload => {
       let params = {
@@ -241,6 +246,20 @@ const handleDelete = (row: any) => {
     .catch(() => {});
 };
 
+const handleToggleStatus = (row: any) => {
+  const nextStatus = row.status === 1 ? 0 : 1;
+  const actionText = nextStatus === 1 ? "启用" : "停用";
+  ElMessageBox.confirm(`确认${actionText}场景【${row.name}】吗?`, "提示", {
+    type: "warning"
+  })
+    .then(async () => {
+      await setSceneStatus({ id: row.id, status: nextStatus });
+      ElMessage.success(`${actionText}成功`);
+      loadSceneList();
+    })
+    .catch(() => {});
+};
+
 const filterNode = (value: string, data: any) => {
   if (!value) return true;
   return data.name?.includes(value);

+ 1 - 1
src/views/login/index.vue

@@ -7,7 +7,7 @@
       </div>
       <div class="login-form">
         <div class="login-logo">
-          <h2 class="logo-text">Alien-Store</h2>
+          <h2 class="logo-text">Alien-Lawyer</h2>
         </div>
         <LoginForm />
       </div>