<template>
|
<view class="sales-account">
|
<PageHeader title="项目管理"
|
@back="goBack" />
|
|
<view class="search-section">
|
<view class="search-bar">
|
<view class="search-input">
|
<up-input class="search-text"
|
v-model="query.projectNameOrCode"
|
placeholder="请输入项目名称/编号"
|
clearable
|
@change="getList" />
|
</view>
|
<view class="filter-button"
|
@click="showFilter = true">
|
<up-icon name="list"
|
size="24"
|
color="#999"></up-icon>
|
</view>
|
</view>
|
</view>
|
|
<view v-if="projectList.length > 0"
|
class="ledger-list">
|
<view v-for="item in projectList"
|
:key="item.id"
|
class="ledger-item">
|
<view class="item-header">
|
<view class="item-left">
|
<view class="document-icon">
|
<up-icon name="grid"
|
size="16"
|
color="#ffffff"></up-icon>
|
</view>
|
<text class="item-id">{{ item.billNo || "-" }}</text>
|
</view>
|
<up-tag :text="dictLabel(auditOptions, item.auditStatus)"
|
:type="dictTagType(auditOptions, item.auditStatus)"
|
size="mini"
|
shape="circle" />
|
</view>
|
<up-divider></up-divider>
|
<view class="item-details">
|
<view class="detail-row">
|
<text class="detail-label">项目名称</text>
|
<text class="detail-value highlight">{{ item.projectName || "-" }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">客户名称</text>
|
<text class="detail-value">{{ item.customerName || "-" }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">立项日期</text>
|
<text class="detail-value">{{ item.setupDate || "-" }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">项目来源</text>
|
<text class="detail-value">{{ item.projectSource || "-" }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">项目分类</text>
|
<text class="detail-value">{{ item.projectClassification || "-" }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">计划状态</text>
|
<text class="detail-value">{{ dictLabel(stageOptions, item.projectStage) || "-" }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">项目金额</text>
|
<text class="detail-value">{{ formatAmount(item.projectAmount) }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">销售合同总数</text>
|
<text class="detail-value">{{ item.salesContractCount ?? 0 }}</text>
|
</view>
|
</view>
|
<view class="action-buttons">
|
<up-button class="action-btn"
|
size="small"
|
type="primary"
|
@click="goDetail(item)">详情</up-button>
|
<up-button class="action-btn"
|
size="small"
|
@click="goEdit(item)">编辑</up-button>
|
<up-button class="action-btn"
|
size="small"
|
@click="goProgress(item)">进度汇报</up-button>
|
<up-button class="action-btn"
|
size="small"
|
@click="goArchive(item)">文档归档</up-button>
|
<up-button v-if="canSubmit(item)"
|
class="action-btn"
|
size="small"
|
type="success"
|
@click="handleSubmitRow(item)">提交</up-button>
|
<up-button v-if="canAudit(item)"
|
class="action-btn"
|
size="small"
|
type="warning"
|
@click="handleAuditRow(item)">审核</up-button>
|
<up-button class="action-btn"
|
size="small"
|
type="error"
|
plain
|
@click="handleDelete(item)">删除</up-button>
|
</view>
|
</view>
|
</view>
|
<view v-else
|
class="no-data">
|
<text>暂无项目数据</text>
|
</view>
|
|
<view class="fab-button"
|
@click="goAdd">
|
<up-icon name="plus"
|
size="28"
|
color="#ffffff"></up-icon>
|
</view>
|
|
<FilterPanel v-model:show="showFilter"
|
:model-value="query"
|
title="项目筛选"
|
:fields="filterFields"
|
@search="onFilterSearch"
|
@reset="onFilterSearch" />
|
</view>
|
</template>
|
|
<script setup>
|
import { computed, reactive, ref } from "vue";
|
import { onShow } from "@dcloudio/uni-app";
|
import PageHeader from "@/components/PageHeader.vue";
|
import FilterPanel from "@/components/FilterPanel.vue";
|
import { useDict } from "@/utils/dict";
|
import { unwrapList } from "@/utils/list";
|
import {
|
auditProject,
|
delProject,
|
listProject,
|
submitProject,
|
} from "@/api/projectManagement/project";
|
import { toListRow } from "./_utils/mapper";
|
import { dictLabel, dictTagType, toPickerOptions } from "./_utils/dict";
|
|
const { bill_status, project_management, plan_status } = useDict(
|
"bill_status",
|
"project_management",
|
"plan_status"
|
);
|
|
const billOptions = computed(() => toPickerOptions(bill_status.value));
|
const auditOptions = computed(() => toPickerOptions(project_management.value));
|
const stageOptions = computed(() => toPickerOptions(plan_status.value));
|
|
const projectList = ref([]);
|
const showFilter = ref(false);
|
|
// 后端没有单据状态(billStatus)的查询参数,管理端也是拉回列表后本地过滤
|
const query = reactive({
|
projectNameOrCode: "",
|
customerName: "",
|
salesperson: "",
|
billStatus: "",
|
projectStage: "",
|
auditStatus: "",
|
});
|
|
const filterFields = computed(() => [
|
{
|
prop: "projectNameOrCode",
|
label: "项目名称/编号",
|
type: "input",
|
placeholder: "请输入项目名称/编号",
|
},
|
{
|
prop: "customerName",
|
label: "客户名称",
|
type: "input",
|
placeholder: "请输入客户名称",
|
},
|
{
|
prop: "salesperson",
|
label: "业务人员",
|
type: "input",
|
placeholder: "请输入业务人员",
|
},
|
{
|
prop: "billStatus",
|
label: "单据状态",
|
type: "select",
|
options: billOptions.value,
|
},
|
{
|
prop: "projectStage",
|
label: "计划状态",
|
type: "select",
|
options: stageOptions.value,
|
},
|
{
|
prop: "auditStatus",
|
label: "审核状态",
|
type: "select",
|
options: auditOptions.value,
|
},
|
]);
|
|
const goBack = () => uni.navigateBack();
|
|
const goAdd = () => {
|
uni.removeStorageSync("projectDetail");
|
uni.navigateTo({ url: "/pages/projectManagement/project/edit" });
|
};
|
|
const goEdit = item => {
|
uni.setStorageSync("projectDetail", item || {});
|
uni.navigateTo({
|
url: `/pages/projectManagement/project/edit?id=${item.id}`,
|
});
|
};
|
|
const goDetail = item => {
|
uni.setStorageSync("projectDetail", item || {});
|
uni.navigateTo({
|
url: `/pages/projectManagement/project/detail?id=${item.id}`,
|
});
|
};
|
|
const goProgress = item => {
|
uni.setStorageSync("projectDetail", item || {});
|
uni.navigateTo({
|
url: `/pages/projectManagement/project/progress?id=${item.id}`,
|
});
|
};
|
|
const goArchive = item => {
|
uni.navigateTo({
|
url: `/pages/projectManagement/project/archive?id=${item.id}&name=${encodeURIComponent(item.projectName || "")}`,
|
});
|
};
|
|
const formatAmount = amount =>
|
amount === null || amount === undefined || amount === ""
|
? "-"
|
: `¥${Number(amount).toFixed(2)}`;
|
|
// 提交 / 审核是线性流程:新建(无状态) → 提交(reviewStatus 0) → 审核(reviewStatus 1)。
|
// 管理端另有反审核,手机端按约定不做。
|
const canSubmit = item => String(item?.auditStatus ?? "") === "";
|
const canAudit = item => String(item?.auditStatus) === "0";
|
|
const getList = () => {
|
listProject({
|
noOrName: query.projectNameOrCode || undefined,
|
clientName: query.customerName || undefined,
|
salesmanName: query.salesperson || undefined,
|
reviewStatus: query.auditStatus || undefined,
|
stage: query.projectStage || undefined,
|
current: -1,
|
size: -1,
|
})
|
.then(res => {
|
const rows = unwrapList(res).map(toListRow);
|
projectList.value = query.billStatus
|
? rows.filter(
|
item => String(item.billStatus) === String(query.billStatus)
|
)
|
: rows;
|
})
|
.catch(() => {
|
uni.showToast({ title: "查询失败", icon: "none" });
|
});
|
};
|
|
// FilterPanel 只回吐筛选值,写回 query 后统一查询
|
const onFilterSearch = values => {
|
Object.assign(query, values);
|
getList();
|
};
|
|
const confirmStatusAction = (item, label, content, action) => {
|
uni.showModal({
|
title: `${label}确认`,
|
content,
|
success: res => {
|
if (!res.confirm) return;
|
uni.showLoading({ title: "处理中...", mask: true });
|
action({ id: item.id })
|
.then(() => {
|
uni.showToast({ title: `${label}成功`, icon: "success" });
|
getList();
|
})
|
.catch(() => {
|
uni.showToast({ title: `${label}失败`, icon: "none" });
|
})
|
.finally(() => {
|
uni.hideLoading();
|
});
|
},
|
});
|
};
|
|
const handleSubmitRow = item => {
|
if (!item?.id) return;
|
confirmStatusAction(item, "提交", "确认提交该项目吗?", submitProject);
|
};
|
|
const handleAuditRow = item => {
|
if (!item?.id) return;
|
confirmStatusAction(item, "审核", "确认审核通过该项目吗?", auditProject);
|
};
|
|
const handleDelete = item => {
|
if (!item?.id) return;
|
uni.showModal({
|
title: "删除确认",
|
content: `确认删除项目「${item.projectName || ""}」吗?`,
|
success: res => {
|
if (!res.confirm) return;
|
uni.showLoading({ title: "处理中...", mask: true });
|
delProject([item.id])
|
.then(() => {
|
uni.showToast({ title: "删除成功", icon: "success" });
|
getList();
|
})
|
.catch(() => {
|
uni.showToast({ title: "删除失败", icon: "none" });
|
})
|
.finally(() => {
|
uni.hideLoading();
|
});
|
},
|
});
|
};
|
|
onShow(() => {
|
getList();
|
});
|
</script>
|
|
<style scoped lang="scss">
|
@import "@/styles/sales-common.scss";
|
|
.detail-value {
|
max-width: 70%;
|
word-break: break-all;
|
}
|
|
.action-buttons {
|
flex-wrap: wrap;
|
}
|
|
.action-btn {
|
flex: 0 0 calc(33.33% - 8px);
|
}
|
</style>
|