|
|
@@ -1,29 +1,9 @@
|
|
|
<template>
|
|
|
<div class="reconciliation-page">
|
|
|
<div class="layout-wrapper">
|
|
|
- <div v-if="isAdmin" class="tree-panel">
|
|
|
- <div class="tree-panel__header">
|
|
|
- <div class="tree-panel__title">律所列表</div>
|
|
|
- <el-input v-model="listFilterText" placeholder="搜索律所" size="small" clearable :prefix-icon="Search" />
|
|
|
- </div>
|
|
|
- <div class="tree-panel__body">
|
|
|
- <el-scrollbar>
|
|
|
- <div
|
|
|
- v-for="item in filteredLawFirmList"
|
|
|
- :key="String(item.id)"
|
|
|
- class="firm-item"
|
|
|
- :class="{ active: selectedFirmId === item.id }"
|
|
|
- @click="handleFirmSelect(item.id)"
|
|
|
- >
|
|
|
- {{ item.label }}
|
|
|
- </div>
|
|
|
- </el-scrollbar>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
<div class="content-panel">
|
|
|
<div class="summary-wrapper">
|
|
|
- <div class="summary-card" v-for="item in summaryCards" :key="item.label">
|
|
|
+ <div v-for="item in summaryCards" :key="item.label" class="summary-card">
|
|
|
<div class="summary-label">
|
|
|
{{ item.label }}
|
|
|
</div>
|
|
|
@@ -32,7 +12,6 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
-
|
|
|
<ProTable
|
|
|
ref="proTable"
|
|
|
:columns="columns"
|
|
|
@@ -40,16 +19,8 @@
|
|
|
:data-callback="dataCallback"
|
|
|
:request-auto="false"
|
|
|
>
|
|
|
- <template #lawyerName="scope">
|
|
|
- <div class="lawyer-info">
|
|
|
- <el-avatar :size="48" :src="scope.row.headImg" />
|
|
|
- <div class="lawyer-meta">
|
|
|
- <div class="lawyer-name">
|
|
|
- {{ scope.row.lawyerName }}
|
|
|
- </div>
|
|
|
- <div class="lawyer-extra">执业证号:{{ scope.row.lawyerCertificateNo }}</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ <template #tableHeader>
|
|
|
+ <el-button type="primary" :icon="Download" :disabled="!canExport" @click="handleExport"> 导出 </el-button>
|
|
|
</template>
|
|
|
<template #operation="scope">
|
|
|
<el-button type="primary" link :icon="EditPen" @click="handleDetail(scope.row)"> 查看详情 </el-button>
|
|
|
@@ -57,47 +28,58 @@
|
|
|
</ProTable>
|
|
|
</div>
|
|
|
</div>
|
|
|
- <DetailDialog ref="detailDialog" />
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { ref, reactive, onActivated, onMounted, computed, nextTick } from "vue";
|
|
|
+import { ref, reactive, onActivated, onMounted, computed } from "vue";
|
|
|
+import { useRouter } from "vue-router";
|
|
|
import type { Course } from "@/api/interface";
|
|
|
import ProTable from "@/components/ProTable/index.vue";
|
|
|
import type { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
|
|
|
-import DetailDialog from "./detailDialog.vue";
|
|
|
-import { getOrderList, getOverview, getLawFirmPage } from "@/api/modules/lawyer";
|
|
|
+import { getAllLawFirmList, getOverview, exportLawFirmReconciliation } from "@/api/modules/lawyer";
|
|
|
import { useUserStore } from "@/stores/modules/user";
|
|
|
-import { EditPen, Search } from "@element-plus/icons-vue";
|
|
|
+import { Download, EditPen } from "@element-plus/icons-vue";
|
|
|
|
|
|
const proTable = ref<ProTableInstance>();
|
|
|
-const detailDialog = ref<any>(null);
|
|
|
-const userStore = useUserStore();
|
|
|
+const router = useRouter();
|
|
|
|
|
|
const summaryData = ref({
|
|
|
totalOrderCount: 0,
|
|
|
totalOrderAmountYuan: 0,
|
|
|
platformServiceFeeYuan: 0
|
|
|
});
|
|
|
-const selectedFirmId = ref<string | number>("");
|
|
|
-const lawFirmList = ref<{ id: string | number; label: string }[]>([]);
|
|
|
-const listFilterText = ref("");
|
|
|
-const isAdmin = computed(() => (userStore.userInfo?.name || "").toLowerCase() === "admin");
|
|
|
-const currentFirmId = computed(() => {
|
|
|
- return isAdmin.value ? selectedFirmId.value : userStore.userInfo?.firmId || "";
|
|
|
+const canExport = computed(() => {
|
|
|
+ const table = proTable.value;
|
|
|
+ if (!table) return false;
|
|
|
+ const data = table.tableData as any;
|
|
|
+ if (Array.isArray(data)) return data.length > 0;
|
|
|
+ if (data && typeof data === "object" && Array.isArray((data as any).list)) return (data as any).list.length > 0;
|
|
|
+ return false;
|
|
|
});
|
|
|
|
|
|
const columns = reactive<ColumnProps<Course.ReqCourseParams>[]>([
|
|
|
- { label: "律师信息", prop: "lawyerName", minWidth: 280, search: { el: "input", props: { placeholder: "请输入律师姓名" } } },
|
|
|
- { label: "订单数量", prop: "orderAmount", width: 160, align: "center" },
|
|
|
- { label: "订单金额", prop: "orderAmount", width: 200, align: "center" },
|
|
|
- { label: "平台信息服务费", prop: "platformFee", width: 220, align: "center" },
|
|
|
+ { label: "律所名称", prop: "firmName", minWidth: 280, search: { el: "input", props: { placeholder: "请输入律所名称" } } },
|
|
|
+ { label: "订单数量", prop: "totalOrderCount", width: 160, align: "center" },
|
|
|
+ { label: "订单金额", prop: "totalOrderAmountYuan", width: 200, align: "center" },
|
|
|
+ { label: "平台信息服务费", prop: "platformServiceFeeYuan", width: 220, align: "center" },
|
|
|
{
|
|
|
label: "订单日期",
|
|
|
prop: "orderTime",
|
|
|
+ isShow: false,
|
|
|
width: 180,
|
|
|
- search: { el: "date-picker", props: { type: "date", valueFormat: "YYYY-MM-DD", placeholder: "请选择从业时间" } }
|
|
|
+ search: {
|
|
|
+ el: "date-picker",
|
|
|
+ props: {
|
|
|
+ type: "monthrange",
|
|
|
+ valueFormat: "YYYY-MM",
|
|
|
+ format: "YYYY-MM",
|
|
|
+ startPlaceholder: "开始月份",
|
|
|
+ endPlaceholder: "结束月份",
|
|
|
+ rangeSeparator: "至",
|
|
|
+ unlinkPanels: true
|
|
|
+ }
|
|
|
+ }
|
|
|
},
|
|
|
{ label: "操作", prop: "operation", width: 160, align: "center", fixed: "right" }
|
|
|
]);
|
|
|
@@ -109,20 +91,18 @@ const buildQueryParams = (params: any) => {
|
|
|
pageNum: tempParams.pageNum,
|
|
|
pageSize: tempParams.pageSize
|
|
|
};
|
|
|
- if (params.time) {
|
|
|
- newParams.createdTime = params.time[0];
|
|
|
- newParams.endTime = params.time[1];
|
|
|
- }
|
|
|
- if (currentFirmId.value) {
|
|
|
- newParams.firmId = currentFirmId.value;
|
|
|
+ if (params.orderTime) {
|
|
|
+ newParams.startTime = params.orderTime[0] + "-00";
|
|
|
+ newParams.endTime = params.orderTime[1] + "-31";
|
|
|
}
|
|
|
+ delete newParams.orderTime;
|
|
|
return newParams;
|
|
|
};
|
|
|
|
|
|
const fetchOverview = async (params: any) => {
|
|
|
try {
|
|
|
const res: any = await getOverview(params);
|
|
|
- const overview = res?.data || res || {};
|
|
|
+ const overview = res?.data;
|
|
|
summaryData.value = {
|
|
|
totalOrderCount: Number(overview.totalOrderCount) || 0,
|
|
|
totalOrderAmountYuan: Number(overview.totalOrderAmountYuan) || 0,
|
|
|
@@ -140,7 +120,7 @@ const fetchOverview = async (params: any) => {
|
|
|
|
|
|
const getTableList = async (params: any) => {
|
|
|
const queryParams = buildQueryParams(params);
|
|
|
- const [listRes] = await Promise.all([getOrderList(queryParams), fetchOverview(queryParams)]);
|
|
|
+ const [listRes] = await Promise.all([getAllLawFirmList(queryParams), fetchOverview(queryParams)]);
|
|
|
return listRes;
|
|
|
};
|
|
|
const dataCallback = (data: any) => {
|
|
|
@@ -152,64 +132,24 @@ const dataCallback = (data: any) => {
|
|
|
|
|
|
// 处理推广板块字符串
|
|
|
const filters = reactive({
|
|
|
- name: "",
|
|
|
+ firmName: "",
|
|
|
dateRange: [] as string[]
|
|
|
});
|
|
|
|
|
|
const summaryCards = computed(() => [
|
|
|
- { label: "总订单数量", value: formatNumber(summaryData.value.totalOrderCount) },
|
|
|
+ { label: "总订单数量", value: summaryData.value.totalOrderCount },
|
|
|
{ label: "总订单金额", value: formatCurrency(summaryData.value.totalOrderAmountYuan) },
|
|
|
{ label: "平台信息服务费", value: formatCurrency(summaryData.value.platformServiceFeeYuan) }
|
|
|
]);
|
|
|
|
|
|
-const filteredLawFirmList = computed(() => {
|
|
|
- if (!listFilterText.value) return lawFirmList.value;
|
|
|
- const keyword = listFilterText.value.toLowerCase();
|
|
|
- return lawFirmList.value.filter(item => String(item.label).toLowerCase().includes(keyword));
|
|
|
-});
|
|
|
-
|
|
|
-const fetchLawFirmList = async () => {
|
|
|
- try {
|
|
|
- const res: any = await getLawFirmPage({ page: 1, size: 999 });
|
|
|
- const list = res?.records || res?.data?.records || res?.data?.list || [];
|
|
|
- const nodes = list.map((item: any) => ({
|
|
|
- id: item.id,
|
|
|
- label: item.firmName
|
|
|
- }));
|
|
|
- lawFirmList.value = nodes;
|
|
|
- if (!selectedFirmId.value && nodes.length) {
|
|
|
- selectedFirmId.value = nodes[0].id;
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error("获取律所列表失败", error);
|
|
|
- lawFirmList.value = [];
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-const handleFirmSelect = (id: string | number) => {
|
|
|
- if (selectedFirmId.value === id) return;
|
|
|
- selectedFirmId.value = id;
|
|
|
- refreshTable();
|
|
|
-};
|
|
|
-
|
|
|
const refreshTable = () => {
|
|
|
proTable.value?.getTableList();
|
|
|
};
|
|
|
|
|
|
-const initializeContext = async () => {
|
|
|
- if (isAdmin.value) {
|
|
|
- await fetchLawFirmList();
|
|
|
- } else {
|
|
|
- selectedFirmId.value = userStore.userInfo?.firmId || "";
|
|
|
- }
|
|
|
- await nextTick();
|
|
|
- refreshTable();
|
|
|
-};
|
|
|
-
|
|
|
const handleSearch = () => {
|
|
|
if (!proTable.value) return;
|
|
|
- if (filters.name) proTable.value.searchParam.userName = filters.name;
|
|
|
- else delete proTable.value.searchParam.userName;
|
|
|
+ if (filters.firmName) proTable.value.searchParam.firmName = filters.firmName;
|
|
|
+ else delete proTable.value.searchParam.firmName;
|
|
|
|
|
|
if (filters.dateRange?.length === 2) {
|
|
|
proTable.value.searchParam.time = filters.dateRange;
|
|
|
@@ -220,28 +160,34 @@ const handleSearch = () => {
|
|
|
};
|
|
|
|
|
|
const handleReset = () => {
|
|
|
- filters.name = "";
|
|
|
+ filters.firmName = "";
|
|
|
filters.dateRange = [];
|
|
|
handleSearch();
|
|
|
};
|
|
|
|
|
|
const formatCurrency = (val?: number | string) => {
|
|
|
- const num = Number(val) || 0;
|
|
|
- return `¥${num.toLocaleString("zh-CN", { minimumFractionDigits: 2 })}`;
|
|
|
+ return `¥${val || 0}`;
|
|
|
};
|
|
|
|
|
|
-const formatNumber = (val?: number | string) => {
|
|
|
- const num = Number(val) || 0;
|
|
|
- return num.toLocaleString("zh-CN");
|
|
|
+const handleDetail = (row: any) => {
|
|
|
+ if (!row?.firmId) return;
|
|
|
+ router.push({
|
|
|
+ path: "/lawyerManagement/reconciliation/lawyerDetail",
|
|
|
+ query: {
|
|
|
+ firmId: row.firmId,
|
|
|
+ firmName: row.firmName
|
|
|
+ }
|
|
|
+ });
|
|
|
};
|
|
|
|
|
|
-// 处理同意操作 expertStatus: 0 包含佣金和预付款比例
|
|
|
-const handleDetail = (row: any) => {
|
|
|
- detailDialog.value?.open(row.userId);
|
|
|
+const handleExport = async () => {
|
|
|
+ if (!canExport.value) return;
|
|
|
+ const params = { ...(proTable.value?.searchParam || {}) };
|
|
|
+ await exportLawFirmReconciliation(params);
|
|
|
};
|
|
|
|
|
|
onMounted(() => {
|
|
|
- initializeContext();
|
|
|
+ refreshTable();
|
|
|
});
|
|
|
|
|
|
onActivated(() => {
|
|
|
@@ -266,73 +212,6 @@ onActivated(() => {
|
|
|
height: 100%;
|
|
|
min-height: 0;
|
|
|
}
|
|
|
-.tree-panel {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- width: 280px;
|
|
|
- min-width: 240px;
|
|
|
- height: calc(100vh - 180px);
|
|
|
- padding: 18px;
|
|
|
- background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
|
|
|
- border: 1px solid #e3e8ef;
|
|
|
- border-radius: 14px;
|
|
|
- box-shadow: 0 12px 24px rgb(31 37 50 / 6%);
|
|
|
-}
|
|
|
-.tree-panel__header {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- gap: 10px;
|
|
|
-}
|
|
|
-.tree-panel__title {
|
|
|
- font-size: 17px;
|
|
|
- font-weight: 600;
|
|
|
- color: #1f2532;
|
|
|
- letter-spacing: 0.5px;
|
|
|
-}
|
|
|
-.tree-panel__body {
|
|
|
- flex: 1;
|
|
|
- min-height: 0;
|
|
|
- margin-top: 14px;
|
|
|
- overflow: hidden;
|
|
|
- background: #ffffff;
|
|
|
- border: 1px solid #edf0f5;
|
|
|
- border-radius: 10px;
|
|
|
-}
|
|
|
-.tree-panel__body :deep(.el-scrollbar__wrap) {
|
|
|
- padding: 8px 0;
|
|
|
-}
|
|
|
-.firm-item {
|
|
|
- position: relative;
|
|
|
- padding: 12px 16px 12px 20px;
|
|
|
- font-size: 14px;
|
|
|
- color: #565d6d;
|
|
|
- cursor: pointer;
|
|
|
- transition: all 0.2s ease;
|
|
|
-}
|
|
|
-.firm-item::before {
|
|
|
- position: absolute;
|
|
|
- top: 50%;
|
|
|
- left: 8px;
|
|
|
- width: 4px;
|
|
|
- height: 18px;
|
|
|
- content: "";
|
|
|
- background: transparent;
|
|
|
- border-radius: 2px;
|
|
|
- transition: background 0.2s;
|
|
|
- transform: translateY(-50%);
|
|
|
-}
|
|
|
-.firm-item:hover {
|
|
|
- color: #1f2532;
|
|
|
- background: #f3f6fb;
|
|
|
-}
|
|
|
-.firm-item.active {
|
|
|
- font-weight: 600;
|
|
|
- color: var(--el-color-primary);
|
|
|
- background: #e9f3ff;
|
|
|
-}
|
|
|
-.firm-item.active::before {
|
|
|
- background: var(--el-color-primary);
|
|
|
-}
|
|
|
.content-panel {
|
|
|
display: flex;
|
|
|
flex: 1;
|