修复个人中心问题
This commit is contained in:
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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user