修复个人中心问题

This commit is contained in:
hero920103 committed 2026-09-29 18:25:09 +08:00
1 parent 0dc5c2f075
commit ca4220f92c
44 files changed
+1793 -310

No files matched your search

@@ -3,7 +3,7 @@
:model-value="visible"
:title="project?.project_name || '项目详情'"
direction="rtl"
size="900px"
size="1200px"
@update:model-value="handleClose"
@opened="loadAll"
>
@@ -168,11 +168,32 @@ function statusOptionsExcept(cur) {
return PROJECT_STATUS_OPTIONS.filter((i) => String(i.value) !== String(cur));
}
/** 项目状态流转:成功后更新本地展示并通知父组件刷新列表 */
/**
* 项目状态流转:成功后更新本地展示并通知父组件刷新列表。
* 置为「异常」时先询问原因,原因会写入操作日志,便于追溯是什么导致的。
*/
async function handleChangeStatus(status) {
if (!props.project?.id) return;
let reason = "";
if (Number(status) === 5) {
try {
const { value } = await ElMessageBox.prompt(
"请填写项目异常原因,该原因会记入操作日志",
"异常原因",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
inputPlaceholder: "如:客户预算取消 / 供应商延期交付",
inputValidator: (v) => (String(v || "").trim() ? true : "异常原因不能为空"),
}
);
reason = String(value || "").trim();
} catch (e) {
return; // 用户取消:不流转状态
}
}
try {
await changeProjectStatus(props.project.id, Number(status));
await changeProjectStatus(props.project.id, Number(status), reason);
ElMessage.success(`状态已更新为「${projectStatusText(status)}」`);
props.project.status = Number(status);
emit("refresh");
@@ -253,11 +274,13 @@ defineExpose({ loadAll });
width: 100%;
min-width: 0;
// 关键:内容区占满「容器剩余宽度」并允许收缩,内部表格才能随容器自适应
// 关键:内容区占满「容器剩余宽度」并允许收缩,内部表格才能随容器自适应。
// 注意不能用 overflow: hidden——它会把附件/日志等较高的内容直接截断,
// 让内容自然撑高,滚动交给 drawer 自身(el-drawer__body)处理。
:deep(.el-tabs__content) {
flex: 1;
min-width: 0;
overflow: hidden;
overflow: visible;
padding-left: 4px;
}
@@ -44,6 +44,16 @@
</el-select>
</el-form-item>
</el-col>
<!-- 状态选「异常」时必须写明原因,原因会写入操作日志便于追溯 -->
<el-col v-if="Number(form.status) === 5" :span="12">
<el-form-item label="异常原因" prop="reason">
<el-input
v-model="form.reason"
placeholder="如:客户预算取消 / 供应商延期交付"
maxlength="100"
/>
</el-form-item>
</el-col>
<el-col v-if="isBackToBack" :span="12">
<el-form-item label="项目毛利">
<span class="profit-value">¥{{ formatMoney(profitAmount) }}</span>
@@ -299,6 +309,8 @@ const defaultForm = () => ({
downstream_contact: "",
downstream_phone: "",
status: 1,
// 异常原因:状态选「异常」时必填,会写入操作日志
reason: "",
start_date: "",
end_date: "",
address: "",
@@ -402,8 +414,21 @@ async function loadProjectProducts(projectId) {
}
}
/** 异常原因校验:状态改为「异常」时必填(原本已是异常且未改状态的除外,项目表未存历史原因) */
const validateReason = (rule, value, callback) => {
const isAbnormal = Number(form.status) === 5;
const keepOriginal =
isEdit.value && Number(props.editData?.status) === 5 && Number(form.status) === 5;
if (isAbnormal && !keepOriginal && !String(value || "").trim()) {
callback(new Error("请填写项目异常原因"));
} else {
callback();
}
};
const rules = {
project_name: [{ required: true, message: "请输入项目名称", trigger: "blur" }],
reason: [{ validator: validateReason, trigger: "blur" }],
};
watch(
@@ -695,6 +720,8 @@ async function handleSubmit() {
downstream_amount: hasCounterparty.value ? Number(form.downstream_amount) || 0 : 0,
amount: upstream, // 兼容旧字段
status: Number(form.status) || 1,
// 异常原因:后端仅在状态变为「异常」时校验并写入日志,其他状态忽略
reason: form.reason,
start_date: form.start_date,
end_date: form.end_date,
upstream_contact: form.upstream_contact,
+29 -1
View File
@@ -28,6 +28,12 @@
<el-option v-for="i in PROJECT_STATUS_OPTIONS" :key="i.value" :label="i.label" :value="i.value" />
</el-select>
</el-form-item>
<!-- 年度筛选:按项目开始时间(关联合同汇总)所在年份过滤,选项由后端按现有项目汇总 -->
<el-form-item label="年度">
<el-select v-model="filters.year" clearable placeholder="全部" style="width: 120px">
<el-option v-for="y in yearOptions" :key="y" :label="`${y} 年`" :value="String(y)" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="handleSearch">查询</el-button>
<el-button :icon="Refresh" @click="resetFilters">重置</el-button>
@@ -42,6 +48,15 @@
<el-tag :type="projectStatusTag(row.status)" size="small">{{ projectStatusText(row.status) }}</el-tag>
</template>
</el-table-column>
<!-- 年度:按项目开始时间(关联合同汇总的起始日)取年份 -->
<el-table-column label="年度" width="90" align="center">
<template #default="{ row }">
<el-tag v-if="projectYear(row)" type="primary" size="small" effect="plain">
{{ projectYear(row) }} 年
</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="项目名称" min-width="180" show-overflow-tooltip >
<template #default="{ row }">
<span class="name-link" @click="openDetail(row)">{{ row.project_name }}</span>
@@ -158,13 +173,23 @@ const contactBookVisible = ref(false);
const contactBookCompanyId = ref(null);
const contactBookCompanyName = ref("");
const filters = reactive({ keyword: "", status: "" });
const filters = reactive({ keyword: "", status: "", year: "" });
const pagination = reactive({ page: 1, pageSize: 20, total: 0 });
/** 年度筛选项:由后端按现有项目的开始时间汇总返回(倒序),避免前端写死年份范围 */
const yearOptions = ref([]);
onMounted(() => {
fetchList();
});
/** 项目年度:按项目开始时间(关联合同汇总的起始日,回退旧的手工列 start_date)取年份 */
function projectYear(row) {
const d = row?.contract_start_date || row?.start_date;
if (!d) return "";
const y = String(d).slice(0, 4);
return /^\d{4}$/.test(y) ? Number(y) : "";
}
async function fetchList() {
loading.value = true;
try {
@@ -175,6 +200,8 @@ async function fetchList() {
});
tableData.value = res?.data?.list || [];
pagination.total = res?.data?.total || 0;
// 年度选项始终取全量汇总结果(不受当前年度筛选影响,切换年份时下拉不丢项)
if (Array.isArray(res?.data?.years)) yearOptions.value = res.data.years;
} catch (e) {
tableData.value = [];
pagination.total = 0;
@@ -192,6 +219,7 @@ function handleSearch() {
function resetFilters() {
filters.keyword = "";
filters.status = "";
filters.year = "";
handleSearch();
}