优化项目管理

This commit is contained in:
2026-09-13 22:31:42 +08:00
parent 2f6cdbb3cd
commit a8c09c1db5
30 changed files with 1654 additions and 346 deletions
+66 -11
View File
@@ -28,7 +28,7 @@
start-placeholder="开始日期"
end-placeholder="结束日期"
:clearable="false"
@change="loadStats"
@change="onRangeChange"
/>
</div>
@@ -184,12 +184,14 @@
</template>
<script setup>
import { ref, reactive, onMounted } from "vue";
import { ref, reactive, onMounted, nextTick, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { ElMessage, ElMessageBox } from "element-plus";
import { Plus, Search, Refresh } from "@element-plus/icons-vue";
import {
getContractList,
getContractStats,
getContractDetail,
} from "@/api/crmContract";
import ContractCreate from "./components/create.vue";
import ContractDetail from "./components/detail.vue";
@@ -207,6 +209,9 @@ import {
formatMoney,
} from "../dict";
const route = useRoute();
const router = useRouter();
const loading = ref(false);
const tableData = ref([]);
const createVisible = ref(false);
@@ -223,28 +228,33 @@ const filters = reactive({
});
const pagination = reactive({ page: 1, pageSize: 20, total: 0 });
/** 统计周期:month/quarter/year/custom(自定义选区间) */
const statsPeriod = ref("month");
/** 统计周期:month/quarter/year/custom(自定义选区间),默认本年 */
const statsPeriod = ref("year");
const statsRange = ref([]);
/** 列表页统计:后端按周期汇总(排除已作废),周期参数透传给 Stats */
const stats = ref({ total: 0, total_amount: 0, total_cost: 0, total_profit: 0 });
/** 周期参数:统计与列表共用,保证两者口径一致 */
function periodParams() {
const params = { period: statsPeriod.value };
if (statsPeriod.value === "custom" && statsRange.value && statsRange.value.length === 2) {
params.start_date = statsRange.value[0];
params.end_date = statsRange.value[1];
}
return params;
}
async function loadStats() {
try {
const params = { period: statsPeriod.value };
if (statsPeriod.value === "custom" && statsRange.value && statsRange.value.length === 2) {
params.start_date = statsRange.value[0];
params.end_date = statsRange.value[1];
}
const res = await getContractStats(params);
const res = await getContractStats(periodParams());
stats.value = res?.data || stats.value;
} catch {
/* 统计失败不影响列表 */
}
}
/** 切到「自定义」首次未选区间时,默认当前月,避免卡片空值 */
/** 周期变化:列表与统计一并刷新(回到第一页) */
function onPeriodChange(val) {
if (val === "custom" && (!statsRange.value || statsRange.value.length !== 2)) {
const now = new Date();
@@ -253,6 +263,15 @@ function onPeriodChange(val) {
const last = new Date(y, now.getMonth() + 1, 0).getDate();
statsRange.value = [`${y}-${m}-01`, `${y}-${m}-${last}`];
}
pagination.page = 1;
fetchList();
loadStats();
}
/** 自定义区间变化:同样刷新列表与统计 */
function onRangeChange() {
pagination.page = 1;
fetchList();
loadStats();
}
@@ -261,6 +280,16 @@ onMounted(() => {
loadStats();
});
// 页面被 keep-alive 缓存后 onMounted 只执行一次,改为监听 ?id 变化,
// 保证每次从项目详情「合同管理」等入口带 id 跳转进来都能打开目标合同。
watch(
() => route.query.id,
(id) => {
if (id) openByQueryId();
},
{ immediate: true }
);
async function fetchList() {
loading.value = true;
try {
@@ -268,6 +297,8 @@ async function fetchList() {
page: pagination.page,
pageSize: pagination.pageSize,
...filters,
// 与顶部统计同口径:按所选周期筛选列表
...periodParams(),
});
tableData.value = res?.data?.list || [];
pagination.total = res?.data?.total || 0;
@@ -281,6 +312,30 @@ async function fetchList() {
}
}
/**
* URL 带 ?id= 时自动打开对应合同详情(供项目详情「合同管理」点击合同名称跳转)。
* 打开后清除 query,避免刷新或返回本页时反复弹出。
*/
async function openByQueryId() {
const id = route.query.id;
if (!id) return;
// 抽屉可能因 keep-alive 缓存仍处于打开状态,先收起再打开,确保按新合同重新加载
detailVisible.value = false;
await nextTick();
try {
const res = await getContractDetail(id);
const row = res?.data;
if (!row?.id) throw new Error("合同不存在或已被删除");
openDetail(row);
} catch (e) {
ElMessage.error(e.message || "打开合同详情失败");
} finally {
const query = { ...route.query };
delete query.id;
router.replace({ path: route.path, query });
}
}
function handleSearch() {
pagination.page = 1;
fetchList();