|
|
@@ -1,117 +1,147 @@
|
|
|
<template>
|
|
|
<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 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="summary-value">
|
|
|
- {{ item.value }}
|
|
|
+ <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>
|
|
|
- <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 class="content-panel">
|
|
|
+ <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 class="lawyer-extra">执业证号:{{ scope.row.licenseNo || "——" }}</div>
|
|
|
- <div class="lawyer-extra">手机号:{{ scope.row.userPhone || "——" }}</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </template>
|
|
|
- <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" link @click="handleDetail(scope.row)"> 查看详情 </el-button>
|
|
|
- </template>
|
|
|
- </ProTable>
|
|
|
+
|
|
|
+ <ProTable
|
|
|
+ ref="proTable"
|
|
|
+ :columns="columns"
|
|
|
+ :request-api="getTableList"
|
|
|
+ :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>
|
|
|
+ <template #operation="scope">
|
|
|
+ <el-button type="primary" link :icon="EditPen" @click="handleDetail(scope.row)"> 查看详情 </el-button>
|
|
|
+ </template>
|
|
|
+ </ProTable>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
<DetailDialog ref="detailDialog" />
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { ref, reactive, onActivated, computed } from "vue";
|
|
|
+import { ref, reactive, onActivated, onMounted, computed, nextTick } from "vue";
|
|
|
import type { Course } from "@/api/interface";
|
|
|
import ProTable from "@/components/ProTable/index.vue";
|
|
|
import type { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
|
|
|
-import { Search } from "@element-plus/icons-vue";
|
|
|
import DetailDialog from "./detailDialog.vue";
|
|
|
-import { getApplicationExpertList } from "@/api/modules/lawyer";
|
|
|
+import { getOrderList, getOverview, getLawFirmPage } from "@/api/modules/lawyer";
|
|
|
+import { useUserStore } from "@/stores/modules/user";
|
|
|
+import { EditPen, Search } from "@element-plus/icons-vue";
|
|
|
|
|
|
const proTable = ref<ProTableInstance>();
|
|
|
const detailDialog = ref<any>(null);
|
|
|
+const userStore = useUserStore();
|
|
|
+
|
|
|
+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 columns = reactive<ColumnProps<Course.ReqCourseParams>[]>([
|
|
|
- { label: "律师信息", prop: "userName", minWidth: 280 },
|
|
|
- { label: "订单数量", prop: "orderCount", width: 160, align: "center" },
|
|
|
+ { 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: "orderTime",
|
|
|
+ width: 180,
|
|
|
+ search: { el: "date-picker", props: { type: "date", valueFormat: "YYYY-MM-DD", placeholder: "请选择从业时间" } }
|
|
|
+ },
|
|
|
{ label: "操作", prop: "operation", width: 160, align: "center", fixed: "right" }
|
|
|
]);
|
|
|
-const getTableList = async (params: any) => {
|
|
|
- let tempParams = JSON.parse(JSON.stringify(params));
|
|
|
+const buildQueryParams = (params: any) => {
|
|
|
+ const tempParams = { ...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;
|
|
|
+ const newParams = {
|
|
|
+ ...tempParams,
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ return newParams;
|
|
|
+};
|
|
|
|
|
|
- const res = await getApplicationExpertList(newParams);
|
|
|
- return res;
|
|
|
+const fetchOverview = async (params: any) => {
|
|
|
+ try {
|
|
|
+ const res: any = await getOverview(params);
|
|
|
+ const overview = res?.data || res || {};
|
|
|
+ summaryData.value = {
|
|
|
+ totalOrderCount: Number(overview.totalOrderCount) || 0,
|
|
|
+ totalOrderAmountYuan: Number(overview.totalOrderAmountYuan) || 0,
|
|
|
+ platformServiceFeeYuan: Number(overview.platformServiceFeeYuan) || 0
|
|
|
+ };
|
|
|
+ } catch (error) {
|
|
|
+ summaryData.value = {
|
|
|
+ totalOrderCount: 0,
|
|
|
+ totalOrderAmountYuan: 0,
|
|
|
+ platformServiceFeeYuan: 0
|
|
|
+ };
|
|
|
+ console.error("获取对账概览失败", error);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const getTableList = async (params: any) => {
|
|
|
+ const queryParams = buildQueryParams(params);
|
|
|
+ const [listRes] = await Promise.all([getOrderList(queryParams), fetchOverview(queryParams)]);
|
|
|
+ return listRes;
|
|
|
};
|
|
|
const dataCallback = (data: any) => {
|
|
|
return {
|
|
|
@@ -126,20 +156,56 @@ const filters = reactive({
|
|
|
dateRange: [] as string[]
|
|
|
});
|
|
|
|
|
|
-const defaultAvatar = "https://img.alicdn.com/tfs/TB1jFtVwYj1gK0jSZFuXXcrHpXa-200-200.png";
|
|
|
+const summaryCards = computed(() => [
|
|
|
+ { label: "总订单数量", value: formatNumber(summaryData.value.totalOrderCount) },
|
|
|
+ { label: "总订单金额", value: formatCurrency(summaryData.value.totalOrderAmountYuan) },
|
|
|
+ { label: "平台信息服务费", value: formatCurrency(summaryData.value.platformServiceFeeYuan) }
|
|
|
+]);
|
|
|
|
|
|
-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) }
|
|
|
- ];
|
|
|
+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;
|
|
|
@@ -174,18 +240,106 @@ const handleDetail = (row: any) => {
|
|
|
detailDialog.value?.open(row.userId);
|
|
|
};
|
|
|
|
|
|
+onMounted(() => {
|
|
|
+ initializeContext();
|
|
|
+});
|
|
|
+
|
|
|
onActivated(() => {
|
|
|
- proTable.value?.getTableList();
|
|
|
+ refreshTable();
|
|
|
});
|
|
|
</script>
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
.reconciliation-page {
|
|
|
+ box-sizing: border-box;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
gap: 16px;
|
|
|
+ min-height: calc(100vh - 150px);
|
|
|
padding: 16px;
|
|
|
}
|
|
|
+.layout-wrapper {
|
|
|
+ display: flex;
|
|
|
+ flex: 1;
|
|
|
+ gap: 16px;
|
|
|
+ align-items: stretch;
|
|
|
+ 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;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 16px;
|
|
|
+ min-height: 0;
|
|
|
+}
|
|
|
.summary-wrapper {
|
|
|
display: grid;
|
|
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
|
@@ -193,7 +347,7 @@ onActivated(() => {
|
|
|
}
|
|
|
.summary-card {
|
|
|
padding: 16px;
|
|
|
- background: #f8f9fb;
|
|
|
+ background: #ffffff;
|
|
|
border: 1px solid #e2e6ef;
|
|
|
border-radius: 12px;
|
|
|
}
|
|
|
@@ -228,30 +382,11 @@ onActivated(() => {
|
|
|
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;
|