From b58f53769f004fb0ba6b5aedf55ef602850c7c97 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期日, 20 九月 2026 15:46:05 +0800
Subject: [PATCH] 天津豹鸣app 1.部署修改
---
src/pages/projectManagement/project/edit.vue | 574 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 574 insertions(+), 0 deletions(-)
diff --git a/src/pages/projectManagement/project/edit.vue b/src/pages/projectManagement/project/edit.vue
new file mode 100644
index 0000000..54ce91e
--- /dev/null
+++ b/src/pages/projectManagement/project/edit.vue
@@ -0,0 +1,574 @@
+<!--
+ 椤圭洰鏂板 / 缂栬緫
+
+ 琛ㄥ崟鍒嗗尯瀵归綈绠$悊绔� formDia.vue锛氬熀纭�璧勬枡 / 浜у搧淇℃伅 / 椤圭洰鍥㈤槦 / 鏀惰揣鍦板潃 / 鑱旂郴淇℃伅 / 闄勪欢銆�
+ 绠$悊绔繕鏈変竴涓�岄」鐩樁娈点�嶅瓙琛紝鏁村潡鏄敞閲婃帀鐨勪笖涓嶅湪 /save 鐨勫叆鍙傞噷锛�
+ 鎵�浠ヨ繖閲屼篃涓嶅仛鍙紪杈戦樁娈佃〃锛堝仛浜嗕細闈欓粯涓㈡暟鎹級锛涢樁娈甸�氳繃璇︽儏椤垫煡鐪嬨�侀�氳繃銆岃繘搴︽眹鎶ャ�嶅啓鍏ャ��
+
+ 鍩虹璧勬枡閲屻�岄璁″伐鏈熴�嶅湪绠$悊绔彲濉絾涓嶅湪鎻愪氦缁撴瀯閲岋紙鍚庣鎸夎鍒掕捣姝㈡棩鏈熸淳鐢燂級锛�
+ 鎵�浠ヨ繖閲屽仛鎴愬彧璇绘淳鐢熷�硷紝閬垮厤鐢ㄦ埛濉簡涓嶇敓鏁堛��
+-->
+<template>
+ <view class="project-edit">
+ <PageHeader :title="pageTitle"
+ @back="goBack" />
+
+ <view class="form-container">
+ <up-form ref="formRef"
+ :model="form"
+ :rules="rules"
+ label-width="120"
+ input-align="right"
+ error-message-align="right">
+ <u-cell-group title="鍩虹璧勬枡"
+ class="form-section">
+ <up-form-item v-if="projectId"
+ label="鍗曟嵁缂栧彿">
+ <up-input :model-value="form.billNo || '绯荤粺鐢熸垚'"
+ disabled />
+ </up-form-item>
+ <up-form-item label="椤圭洰鍚嶇О"
+ prop="projectName"
+ required>
+ <up-input v-model="form.projectName"
+ placeholder="璇疯緭鍏ラ」鐩悕绉�"
+ clearable />
+ </up-form-item>
+ <up-form-item label="瀹㈡埛鍚嶇О"
+ prop="customerName">
+ <up-input v-model="form.customerName"
+ placeholder="璇疯緭鍏ュ鎴峰悕绉�"
+ clearable />
+ </up-form-item>
+ <up-form-item label="绔嬮」鏃ユ湡"
+ prop="setupDate">
+ <FormPicker v-model="form.setupDate"
+ type="date"
+ placeholder="璇烽�夋嫨绔嬮」鏃ユ湡" />
+ </up-form-item>
+ <up-form-item label="椤圭洰鏉ユ簮"
+ prop="projectSource">
+ <up-input v-model="form.projectSource"
+ placeholder="璇疯緭鍏ラ」鐩潵婧�"
+ clearable />
+ </up-form-item>
+ <up-form-item label="绔嬮」浜�"
+ prop="creatorName">
+ <up-input v-model="form.creatorName"
+ placeholder="璇疯緭鍏ョ珛椤逛汉"
+ clearable />
+ </up-form-item>
+ <up-form-item label="椤圭洰绫诲瀷"
+ prop="projectManagementPlanId"
+ required>
+ <FormPicker v-model="form.projectManagementPlanId"
+ type="select"
+ title="閫夋嫨椤圭洰绫诲瀷"
+ :options="projectTypeOptions"
+ placeholder="璇烽�夋嫨椤圭洰绫诲瀷" />
+ </up-form-item>
+ <up-form-item label="璁″垝寮�濮嬫棩鏈�"
+ prop="planStartDate">
+ <FormPicker v-model="form.planStartDate"
+ type="date"
+ placeholder="璇烽�夋嫨璁″垝寮�濮嬫棩鏈�" />
+ </up-form-item>
+ <up-form-item label="璁″垝瀹屾垚鏃ユ湡"
+ prop="planEndDate">
+ <FormPicker v-model="form.planEndDate"
+ type="date"
+ placeholder="璇烽�夋嫨璁″垝瀹屾垚鏃ユ湡" />
+ </up-form-item>
+ <up-form-item label="棰勮宸ユ湡(澶�)">
+ <up-input :model-value="estimatedDays === null ? '鎸夎鍒掓棩鏈熻嚜鍔ㄨ绠�' : String(estimatedDays)"
+ disabled />
+ </up-form-item>
+ <up-form-item label="椤圭洰閲戦"
+ prop="projectAmount">
+ <up-input v-model="form.projectAmount"
+ type="number"
+ placeholder="璇疯緭鍏ラ」鐩噾棰�"
+ clearable />
+ </up-form-item>
+ <up-form-item label="瀹℃牳鐘舵��"
+ prop="auditStatus">
+ <FormPicker v-model="form.auditStatus"
+ type="select"
+ title="閫夋嫨瀹℃牳鐘舵��"
+ :options="auditOptions"
+ placeholder="璇烽�夋嫨瀹℃牳鐘舵��" />
+ </up-form-item>
+ <up-form-item label="澶囨敞"
+ prop="remark">
+ <up-textarea v-model="form.remark"
+ placeholder="璇疯緭鍏ュ娉�"
+ :maxlength="100"
+ count
+ auto-height />
+ </up-form-item>
+ </u-cell-group>
+
+ <u-cell-group title="浜у搧淇℃伅"
+ class="form-section">
+ <view class="section-tools">
+ <up-button type="primary"
+ size="small"
+ text="娣诲姞浜у搧"
+ @click="addProduct" />
+ </view>
+ <view v-if="productList.length === 0"
+ class="empty-text">
+ <text>璇疯嚦灏戞坊鍔犱竴鏉′骇鍝佷俊鎭�</text>
+ </view>
+ <view v-else
+ class="row-list">
+ <ProductRow v-for="(row, index) in productList"
+ :key="row.uid"
+ :row="row"
+ :index="index"
+ :tax-rate-options="taxRateOptions"
+ @remove="removeProduct(index)" />
+ </view>
+ </u-cell-group>
+
+ <u-cell-group title="椤圭洰鍥㈤槦"
+ class="form-section">
+ <view class="section-tools">
+ <up-button type="primary"
+ size="small"
+ text="娣诲姞鎴愬憳"
+ @click="addTeamRow" />
+ </view>
+ <view v-if="teamList.length === 0"
+ class="empty-text">
+ <text>鏆傛棤椤圭洰鎴愬憳</text>
+ </view>
+ <view v-else
+ class="row-list">
+ <TeamRow v-for="(row, index) in teamList"
+ :key="index"
+ :row="row"
+ :index="index"
+ :user-options="userOptions"
+ :role-options="roleOptions"
+ @remove="removeTeamRow(index)" />
+ </view>
+ </u-cell-group>
+
+ <u-cell-group title="鏀惰揣鍦板潃"
+ class="form-section">
+ <view v-if="addressList.length === 0"
+ class="section-tools">
+ <up-button type="primary"
+ size="small"
+ text="娣诲姞鍦板潃"
+ @click="addAddressRow" />
+ </view>
+ <view v-else
+ class="row-list">
+ <AddressRow v-for="(row, index) in addressList"
+ :key="index"
+ :row="row" />
+ </view>
+ </u-cell-group>
+
+ <u-cell-group title="鑱旂郴淇℃伅"
+ class="form-section">
+ <up-form-item label="鑱旂郴浜哄鍚�"
+ prop="contactName">
+ <up-input v-model="form.contactName"
+ placeholder="璇疯緭鍏ヨ仈绯讳汉濮撳悕"
+ clearable />
+ </up-form-item>
+ <up-form-item label="鎬у埆"
+ prop="contactGender">
+ <FormPicker v-model="form.contactGender"
+ type="select"
+ title="閫夋嫨鎬у埆"
+ :options="GENDER_OPTIONS"
+ placeholder="璇烽�夋嫨鎬у埆" />
+ </up-form-item>
+ <up-form-item label="鐢熸棩"
+ prop="contactBirthday">
+ <FormPicker v-model="form.contactBirthday"
+ type="date"
+ placeholder="璇烽�夋嫨鐢熸棩" />
+ </up-form-item>
+ <up-form-item label="閮ㄩ棬"
+ prop="contactDept">
+ <up-input v-model="form.contactDept"
+ placeholder="璇疯緭鍏ラ儴闂�"
+ clearable />
+ </up-form-item>
+ <up-form-item label="鑱屽姟"
+ prop="contactJob">
+ <up-input v-model="form.contactJob"
+ placeholder="璇疯緭鍏ヨ亴鍔�"
+ clearable />
+ </up-form-item>
+ <up-form-item label="鎵嬫満鍙风爜"
+ prop="contactMobile">
+ <up-input v-model="form.contactMobile"
+ type="number"
+ placeholder="璇疯緭鍏ユ墜鏈哄彿鐮�"
+ clearable />
+ </up-form-item>
+ <up-form-item label="閭"
+ prop="contactEmail">
+ <up-input v-model="form.contactEmail"
+ placeholder="璇疯緭鍏ラ偖绠�"
+ clearable />
+ </up-form-item>
+ <up-form-item label="QQ"
+ prop="contactQq">
+ <up-input v-model="form.contactQq"
+ placeholder="璇疯緭鍏� QQ"
+ clearable />
+ </up-form-item>
+ <up-form-item label="寰俊鍙风爜"
+ prop="contactWechat">
+ <up-input v-model="form.contactWechat"
+ placeholder="璇疯緭鍏ュ井淇″彿鐮�"
+ clearable />
+ </up-form-item>
+ <up-form-item label="浼佷笟寰俊"
+ prop="contactWorkWechat">
+ <up-input v-model="form.contactWorkWechat"
+ placeholder="璇疯緭鍏ヤ紒涓氬井淇�"
+ clearable />
+ </up-form-item>
+ <up-form-item label="鍦板潃"
+ prop="contactAddress">
+ <up-input v-model="form.contactAddress"
+ placeholder="璇疯緭鍏ュ湴鍧�"
+ clearable />
+ </up-form-item>
+ <up-form-item label="鑱旂郴淇℃伅澶囨敞"
+ prop="contactRemark">
+ <up-textarea v-model="form.contactRemark"
+ placeholder="璇疯緭鍏ュ娉�"
+ :maxlength="200"
+ count
+ auto-height />
+ </up-form-item>
+ </u-cell-group>
+
+ <u-cell-group title="闄勪欢"
+ class="form-section">
+ <view class="upload-block">
+ <CommonUpload v-model="attachmentFiles"
+ accept="all" />
+ </view>
+ </u-cell-group>
+ </up-form>
+ </view>
+
+ <FooterButtons :loading="loading"
+ confirmText="淇濆瓨"
+ @cancel="goBack"
+ @confirm="handleSubmit" />
+ </view>
+</template>
+
+<script setup>
+ import { computed, ref } from "vue";
+ import { onLoad } from "@dcloudio/uni-app";
+ import PageHeader from "@/components/PageHeader.vue";
+ import FooterButtons from "@/components/FooterButtons.vue";
+ import FormPicker from "@/components/FormPicker.vue";
+ import CommonUpload from "@/components/CommonUpload.vue";
+ import ProductRow from "./_components/ProductRow.vue";
+ import TeamRow from "./_components/TeamRow.vue";
+ import AddressRow from "./_components/AddressRow.vue";
+ import { useDict } from "@/utils/dict";
+ import { parseDate } from "@/utils/warning";
+ import { unwrapList } from "@/utils/list";
+ import { userListNoPageByTenantId } from "@/api/system/user";
+ import { findRoleListPage } from "@/api/projectManagement/role";
+ import { listPlan } from "@/api/projectManagement/projectType";
+ import { getProject, saveProject } from "@/api/projectManagement/project";
+ import {
+ GENDER_OPTIONS,
+ createEmptyProduct,
+ toForm,
+ toPayload,
+ } from "./_utils/mapper";
+ import { toPickerOptions } from "./_utils/dict";
+
+ const MS_PER_DAY = 24 * 60 * 60 * 1000;
+
+ const { project_management, tax_rate } = useDict(
+ "project_management",
+ "tax_rate"
+ );
+ const auditOptions = computed(() => toPickerOptions(project_management.value));
+ const taxRateOptions = computed(() =>
+ (tax_rate.value || []).map(item => ({ label: item.label, value: item.value }))
+ );
+
+ const formRef = ref();
+ const loading = ref(false);
+ const projectId = ref("");
+ const projectTypeOptions = ref([]);
+ const userOptions = ref([]);
+ const roleOptions = ref([]);
+
+ const form = ref({
+ id: undefined,
+ billNo: "",
+ projectName: "",
+ clientId: "",
+ customerName: "",
+ parentProjectId: "",
+ parentProjectName: "",
+ projectManagementPlanId: "",
+ projectSource: "",
+ managerId: "",
+ creatorName: "",
+ salesmanId: "",
+ salesmanName: "",
+ departmentId: "",
+ departmentName: "",
+ setupDate: "",
+ planStartDate: "",
+ planEndDate: "",
+ actualStartDate: "",
+ actualEndDate: "",
+ orderDate: "",
+ projectAmount: "",
+ billStatus: "",
+ auditStatus: "",
+ projectStage: "",
+ remark: "",
+ contactName: "",
+ contactGender: "",
+ contactBirthday: "",
+ contactDept: "",
+ contactJob: "",
+ contactMobile: "",
+ contactEmail: "",
+ contactQq: "",
+ contactWechat: "",
+ contactWorkWechat: "",
+ contactAddress: "",
+ contactRemark: "",
+ });
+
+ const teamList = ref([]);
+ const addressList = ref([]);
+ const productList = ref([]);
+ const attachmentFiles = ref([]);
+
+ const rules = {
+ projectName: [{ required: true, message: "璇疯緭鍏ラ」鐩悕绉�", trigger: "blur" }],
+ projectManagementPlanId: [
+ { required: true, message: "璇烽�夋嫨椤圭洰绫诲瀷", trigger: "change" },
+ ],
+ };
+
+ const pageTitle = computed(() => (projectId.value ? "缂栬緫椤圭洰" : "鏂板椤圭洰"));
+
+ // 鍚庣鎸夎鍒掕捣姝㈡棩鏈熸淳鐢� estimatedDays锛岃繖閲屽彧鍋氬睍绀猴紝涓嶅弬涓庢彁浜�
+ const estimatedDays = computed(() => {
+ const start = parseDate(form.value.planStartDate);
+ const end = parseDate(form.value.planEndDate);
+ if (!start || !end || end < start) return null;
+ return Math.floor((end.getTime() - start.getTime()) / MS_PER_DAY) + 1;
+ });
+
+ const goBack = () => uni.navigateBack();
+
+ const addProduct = () => productList.value.push(createEmptyProduct());
+
+ const removeProduct = index => {
+ productList.value.splice(index, 1);
+ };
+
+ const addTeamRow = () =>
+ teamList.value.push({
+ memberId: "",
+ memberName: "",
+ roleId: "",
+ roleName: "",
+ enterDate: "",
+ leaveDate: "",
+ phone: "",
+ remark: "",
+ });
+
+ const removeTeamRow = index => {
+ teamList.value.splice(index, 1);
+ };
+
+ const addAddressRow = () =>
+ addressList.value.push({ receiver: "", phone: "", address: "" });
+
+ const loadProjectTypes = () => {
+ listPlan({ current: 1, size: 999 })
+ .then(res => {
+ projectTypeOptions.value = unwrapList(res).map(item => ({
+ name: item.name || item.planName || "",
+ value: item.id,
+ }));
+ })
+ .catch(() => {
+ projectTypeOptions.value = [];
+ });
+ };
+
+ const loadUsers = () => {
+ userListNoPageByTenantId()
+ .then(res => {
+ const rows = unwrapList(res);
+ userOptions.value = (Array.isArray(rows) ? rows : []).map(item => ({
+ name: item.nickName || item.userName || "",
+ value: item.userId ?? item.id,
+ }));
+ })
+ .catch(() => {
+ userOptions.value = [];
+ });
+ };
+
+ const loadRoles = () => {
+ findRoleListPage({ pageNum: 1, pageSize: 999 })
+ .then(res => {
+ roleOptions.value = unwrapList(res).map(item => ({
+ name: item.roleName || item.name || "",
+ value: item.id,
+ }));
+ })
+ .catch(() => {
+ roleOptions.value = [];
+ });
+ };
+
+ const loadDetail = async () => {
+ try {
+ const res = await getProject(projectId.value);
+ const detail = res?.data?.data ?? res?.data ?? res;
+ const mapped = toForm(detail || {});
+ form.value = { ...form.value, ...mapped.form };
+ teamList.value = mapped.teamList;
+ addressList.value = mapped.addressList;
+ productList.value = mapped.productList;
+ attachmentFiles.value = mapped.attachments;
+ } catch (error) {
+ uni.showToast({ title: "鑾峰彇椤圭洰璇︽儏澶辫触", icon: "none" });
+ }
+ };
+
+ const validateProducts = () => {
+ if (productList.value.length === 0) {
+ uni.showToast({ title: "璇锋坊鍔犱骇鍝佷俊鎭�", icon: "none" });
+ return false;
+ }
+ const invalid = productList.value.some(row => !row.productCategoryId);
+ if (invalid) {
+ uni.showToast({ title: "璇蜂负姣忔潯浜у搧閫夋嫨浜у搧澶х被", icon: "none" });
+ return false;
+ }
+ return true;
+ };
+
+ const handleSubmit = async () => {
+ const valid = await formRef.value.validate().catch(() => false);
+ if (!valid) return;
+ if (!validateProducts()) return;
+
+ // CommonUpload 瀛樼殑鏄笂浼犳帴鍙h繑鍥炵殑瀵硅薄锛屾彁浜ゅ彧瑕� id
+ const attachmentIds = attachmentFiles.value
+ .map(item => item?.id ?? item?.attachmentId)
+ .filter(Boolean);
+
+ loading.value = true;
+ saveProject(
+ toPayload({
+ form: form.value,
+ teamList: teamList.value,
+ addressList: addressList.value,
+ productList: productList.value,
+ attachmentIds,
+ })
+ )
+ .then(() => {
+ uni.showToast({ title: "淇濆瓨鎴愬姛", icon: "success" });
+ setTimeout(() => uni.navigateBack(), 300);
+ })
+ .catch(() => {
+ uni.showToast({ title: "淇濆瓨澶辫触", icon: "none" });
+ })
+ .finally(() => {
+ loading.value = false;
+ });
+ };
+
+ onLoad(async options => {
+ projectId.value = options?.id || "";
+ if (projectId.value) form.value.id = projectId.value;
+
+ loadProjectTypes();
+ loadUsers();
+ loadRoles();
+
+ if (projectId.value) {
+ await loadDetail();
+ } else {
+ // 鏂板鏃跺厛閾轰竴鏉$┖琛岋紝鍑忓皯涓�娆$偣鍑伙紱浜у搧蹇呴』鑷冲皯涓�鏉★紝鍚庣浼氭牎楠�
+ addProduct();
+ addAddressRow();
+ }
+ });
+</script>
+
+<style scoped lang="scss">
+ @import "@/static/scss/form-common.scss";
+
+ .project-edit {
+ min-height: 100vh;
+ background: #f8f9fa;
+ padding-bottom: 100px;
+ }
+
+ .form-container {
+ padding: 12px 12px 0;
+ }
+
+ .form-section {
+ margin-bottom: 12px;
+ border-radius: 12px;
+ overflow: hidden;
+ box-shadow: 0 2px 10px rgba(15, 35, 95, 0.05);
+ }
+
+ .section-tools {
+ display: flex;
+ justify-content: flex-end;
+ padding: 12px 12px 0;
+ }
+
+ .row-list {
+ padding: 12px;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ }
+
+ .empty-text {
+ padding: 16px 12px;
+ color: #999;
+ font-size: 14px;
+ }
+
+ .upload-block {
+ padding: 12px;
+ }
+
+ :deep(.u-cell-group__title) {
+ padding: 14px 18px 10px !important;
+ font-size: 15px !important;
+ font-weight: 600 !important;
+ color: #22324d !important;
+ background: #f8fbff !important;
+ }
+</style>
--
Gitblit v1.9.3