项目详细/添加/编辑三合一
This commit is contained in:
parent
1803c997e4
commit
1b651d43ef
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<view class="attachment-block">
|
<view class="attachment-block">
|
||||||
<view class="form-item clickable-item" @click="handleChooseFiles">
|
<view class="form-item clickable-item" :class="{ disabled: isReadonly }" @click="handleChooseFiles">
|
||||||
<view class="form-icon">{{ icon }}</view>
|
<view class="form-icon">{{ icon }}</view>
|
||||||
<text class="form-label">{{ title }}</text>
|
<text class="form-label">{{ title }}</text>
|
||||||
<text class="arrow">›</text>
|
<text class="arrow">›</text>
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
class="preview-image"
|
class="preview-image"
|
||||||
@click="previewImage(index)"
|
@click="previewImage(index)"
|
||||||
/>
|
/>
|
||||||
<view class="remove-btn" @click.stop="removeFile(file)">✕</view>
|
<view class="remove-btn" v-if="!isReadonly" @click.stop="removeFile(file)">✕</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|
@ -37,7 +37,7 @@
|
||||||
<text class="file-size" v-if="file.size > 0">{{ formatFileSize(file.size) }}</text>
|
<text class="file-size" v-if="file.size > 0">{{ formatFileSize(file.size) }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="file-icon">{{ getFileIcon(file.name) }}</view>
|
<view class="file-icon">{{ getFileIcon(file.name) }}</view>
|
||||||
<view class="remove-btn" @click.stop="removeFile(file)">✕</view>
|
<view class="remove-btn" v-if="!isReadonly" @click.stop="removeFile(file)">✕</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
@ -67,6 +67,10 @@ const props = defineProps({
|
||||||
extensions: {
|
extensions: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.txt', '.zip', '.rar', '.jpg', '.png']
|
default: () => ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.txt', '.zip', '.rar', '.jpg', '.png']
|
||||||
|
},
|
||||||
|
readonly: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -90,8 +94,12 @@ const isImageFile = (file) => {
|
||||||
|
|
||||||
const imageFiles = computed(() => files.value.filter((file) => isImageFile(file)));
|
const imageFiles = computed(() => files.value.filter((file) => isImageFile(file)));
|
||||||
const otherFiles = computed(() => files.value.filter((file) => !isImageFile(file)));
|
const otherFiles = computed(() => files.value.filter((file) => !isImageFile(file)));
|
||||||
|
const isReadonly = computed(() => props.readonly);
|
||||||
|
|
||||||
const handleChooseFiles = async () => {
|
const handleChooseFiles = async () => {
|
||||||
|
if (isReadonly.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const remainingCount = props.maxCount - files.value.length;
|
const remainingCount = props.maxCount - files.value.length;
|
||||||
if (remainingCount <= 0) {
|
if (remainingCount <= 0) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
|
|
@ -283,6 +291,7 @@ const uploadFiles = async (fileList) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeFile = (file) => {
|
const removeFile = (file) => {
|
||||||
|
if (isReadonly.value) return;
|
||||||
const next = files.value.filter((item) => item !== file);
|
const next = files.value.filter((item) => item !== file);
|
||||||
files.value = next;
|
files.value = next;
|
||||||
};
|
};
|
||||||
|
|
@ -439,6 +448,14 @@ const previewFile = (file) => {
|
||||||
&:active {
|
&:active {
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
cursor: default;
|
||||||
|
opacity: 0.6;
|
||||||
|
&:active {
|
||||||
|
background-color: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-icon {
|
.form-icon {
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,7 @@
|
||||||
{
|
{
|
||||||
"path": "pages/project/form/index",
|
"path": "pages/project/form/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "项目编辑"
|
"navigationBarTitleText": "项目操作"
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,27 @@
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-else>
|
<view v-else>
|
||||||
|
<view class="detail-header" v-if="isViewMode">
|
||||||
|
<view class="detail-title">{{ detailName }}</view>
|
||||||
|
<view class="detail-meta">
|
||||||
|
<text class="meta-label">状态</text>
|
||||||
|
<text class="meta-value">{{ detailStatusText }}</text>
|
||||||
|
<text class="meta-label">客户</text>
|
||||||
|
<text class="meta-value">{{ detailCustomerText }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="detail-meta">
|
||||||
|
<text class="meta-label">金额</text>
|
||||||
|
<text class="meta-value">{{ detailAmountText }}</text>
|
||||||
|
<text class="meta-label">负责人</text>
|
||||||
|
<text class="meta-value">{{ detailOwnerName }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="detail-meta">
|
||||||
|
<text class="meta-label">截止</text>
|
||||||
|
<text class="meta-value">{{ detailDeadlineText }}</text>
|
||||||
|
<text class="meta-label">创建</text>
|
||||||
|
<text class="meta-value">{{ detailCreatorText }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
<view class="form-container">
|
<view class="form-container">
|
||||||
<view class="form-section">
|
<view class="form-section">
|
||||||
<view class="section-title">基本信息</view>
|
<view class="section-title">基本信息</view>
|
||||||
|
|
@ -28,6 +49,7 @@
|
||||||
<input
|
<input
|
||||||
class="form-input"
|
class="form-input"
|
||||||
v-model.trim="formData.name"
|
v-model.trim="formData.name"
|
||||||
|
:disabled="isViewMode"
|
||||||
placeholder="请输入项目名称"
|
placeholder="请输入项目名称"
|
||||||
placeholder-style="color:#999;"
|
placeholder-style="color:#999;"
|
||||||
/>
|
/>
|
||||||
|
|
@ -38,6 +60,7 @@
|
||||||
<input
|
<input
|
||||||
class="form-input"
|
class="form-input"
|
||||||
v-model.trim="formData.no"
|
v-model.trim="formData.no"
|
||||||
|
:disabled="isViewMode"
|
||||||
placeholder="可填写项目编号"
|
placeholder="可填写项目编号"
|
||||||
placeholder-style="color:#999;"
|
placeholder-style="color:#999;"
|
||||||
/>
|
/>
|
||||||
|
|
@ -50,13 +73,14 @@
|
||||||
class="form-input"
|
class="form-input"
|
||||||
v-model="formData.amount"
|
v-model="formData.amount"
|
||||||
type="digit"
|
type="digit"
|
||||||
|
:disabled="isViewMode"
|
||||||
placeholder="请输入金额"
|
placeholder="请输入金额"
|
||||||
placeholder-style="color:#999;"
|
placeholder-style="color:#999;"
|
||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
<view class="form-item half">
|
<view class="form-item half">
|
||||||
<text class="form-label">到账时间</text>
|
<text class="form-label">到账时间</text>
|
||||||
<view class="picker-input" @click="openDatePicker('expireTime')">
|
<view class="picker-input" :class="{ disabled: isViewMode }" @click="openDatePicker('expireTime')">
|
||||||
<text>{{ formData.expireTime || '请选择日期' }}</text>
|
<text>{{ formData.expireTime || '请选择日期' }}</text>
|
||||||
<text class="picker-arrow">›</text>
|
<text class="picker-arrow">›</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
@ -65,7 +89,7 @@
|
||||||
|
|
||||||
<view class="form-item">
|
<view class="form-item">
|
||||||
<text class="form-label ">客户</text>
|
<text class="form-label ">客户</text>
|
||||||
<view class="picker-input" @click="openCustomerPicker">
|
<view class="picker-input" :class="{ disabled: isViewMode }" @click="openCustomerPicker">
|
||||||
<text>{{ formData.customerName || '请选择客户' }}</text>
|
<text>{{ formData.customerName || '请选择客户' }}</text>
|
||||||
<text class="picker-arrow">›</text>
|
<text class="picker-arrow">›</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
@ -76,6 +100,7 @@
|
||||||
<input
|
<input
|
||||||
class="form-input"
|
class="form-input"
|
||||||
v-model.trim="formData.projectTags"
|
v-model.trim="formData.projectTags"
|
||||||
|
:disabled="isViewMode"
|
||||||
placeholder="请输入标签,多个以逗号分隔"
|
placeholder="请输入标签,多个以逗号分隔"
|
||||||
placeholder-style="color:#999;"
|
placeholder-style="color:#999;"
|
||||||
/>
|
/>
|
||||||
|
|
@ -86,6 +111,7 @@
|
||||||
<textarea
|
<textarea
|
||||||
class="form-textarea"
|
class="form-textarea"
|
||||||
v-model="formData.remark"
|
v-model="formData.remark"
|
||||||
|
:disabled="isViewMode"
|
||||||
placeholder="请输入备注"
|
placeholder="请输入备注"
|
||||||
placeholder-style="color:#999;"
|
placeholder-style="color:#999;"
|
||||||
/>
|
/>
|
||||||
|
|
@ -108,6 +134,7 @@
|
||||||
class="form-input"
|
class="form-input"
|
||||||
type="digit"
|
type="digit"
|
||||||
v-model="formData[item.amountKey]"
|
v-model="formData[item.amountKey]"
|
||||||
|
:disabled="isViewMode"
|
||||||
placeholder="请输入金额"
|
placeholder="请输入金额"
|
||||||
placeholder-style="color:#999;"
|
placeholder-style="color:#999;"
|
||||||
/>
|
/>
|
||||||
|
|
@ -116,6 +143,7 @@
|
||||||
<text class="cost-label">收款时间</text>
|
<text class="cost-label">收款时间</text>
|
||||||
<view
|
<view
|
||||||
class="picker-input"
|
class="picker-input"
|
||||||
|
:class="{ disabled: isViewMode }"
|
||||||
@click="openDatePicker(item.dateKey)"
|
@click="openDatePicker(item.dateKey)"
|
||||||
>
|
>
|
||||||
<text>{{ formData[item.dateKey] || '请选择日期' }}</text>
|
<text>{{ formData[item.dateKey] || '请选择日期' }}</text>
|
||||||
|
|
@ -132,6 +160,7 @@
|
||||||
<AttachmentFileUploader
|
<AttachmentFileUploader
|
||||||
v-model="formData.attaches"
|
v-model="formData.attaches"
|
||||||
:max-count="10"
|
:max-count="10"
|
||||||
|
:readonly="isViewMode"
|
||||||
title="上传附件"
|
title="上传附件"
|
||||||
/>
|
/>
|
||||||
<text class="section-tip">
|
<text class="section-tip">
|
||||||
|
|
@ -142,7 +171,12 @@
|
||||||
<view class="form-section">
|
<view class="form-section">
|
||||||
<view class="section-title member-header">
|
<view class="section-title member-header">
|
||||||
<text>项目成员</text>
|
<text>项目成员</text>
|
||||||
<uv-button size="mini" type="primary" @click="openMemberModal">
|
<uv-button
|
||||||
|
v-if="!isViewMode"
|
||||||
|
size="mini"
|
||||||
|
type="primary"
|
||||||
|
@click="openMemberModal"
|
||||||
|
>
|
||||||
选择成员
|
选择成员
|
||||||
</uv-button>
|
</uv-button>
|
||||||
</view>
|
</view>
|
||||||
|
|
@ -151,7 +185,7 @@
|
||||||
<text class="col index">序号</text>
|
<text class="col index">序号</text>
|
||||||
<text class="col name">成员</text>
|
<text class="col name">成员</text>
|
||||||
<text class="col role">角色</text>
|
<text class="col role">角色</text>
|
||||||
<text class="col action">操作</text>
|
<text class="col action" v-if="!isViewMode">操作</text>
|
||||||
</view>
|
</view>
|
||||||
<scroll-view class="member-scroll" scroll-y>
|
<scroll-view class="member-scroll" scroll-y>
|
||||||
<view
|
<view
|
||||||
|
|
@ -166,7 +200,7 @@
|
||||||
<view class="col role role-options">
|
<view class="col role role-options">
|
||||||
<label
|
<label
|
||||||
class="role-option"
|
class="role-option"
|
||||||
:class="{ active: member.role === 'OWNER' }"
|
:class="{ active: member.role === 'OWNER', disabled: isViewMode }"
|
||||||
@click="updateMemberRole(member.userId, 'OWNER')"
|
@click="updateMemberRole(member.userId, 'OWNER')"
|
||||||
>
|
>
|
||||||
<text class="radio"></text>
|
<text class="radio"></text>
|
||||||
|
|
@ -174,7 +208,7 @@
|
||||||
</label>
|
</label>
|
||||||
<label
|
<label
|
||||||
class="role-option"
|
class="role-option"
|
||||||
:class="{ active: member.role === 'FOLLOWER' }"
|
:class="{ active: member.role === 'FOLLOWER', disabled: isViewMode }"
|
||||||
@click="updateMemberRole(member.userId, 'FOLLOWER')"
|
@click="updateMemberRole(member.userId, 'FOLLOWER')"
|
||||||
>
|
>
|
||||||
<text class="radio"></text>
|
<text class="radio"></text>
|
||||||
|
|
@ -182,14 +216,18 @@
|
||||||
</label>
|
</label>
|
||||||
<label
|
<label
|
||||||
class="role-option"
|
class="role-option"
|
||||||
:class="{ active: member.role === 'NORMAL' }"
|
:class="{ active: member.role === 'NORMAL', disabled: isViewMode }"
|
||||||
@click="updateMemberRole(member.userId, 'NORMAL')"
|
@click="updateMemberRole(member.userId, 'NORMAL')"
|
||||||
>
|
>
|
||||||
<text class="radio"></text>
|
<text class="radio"></text>
|
||||||
<text>普通成员</text>
|
<text>普通成员</text>
|
||||||
</label>
|
</label>
|
||||||
</view>
|
</view>
|
||||||
<text class="col action action-btn" @click="removeMember(member.userId)">
|
<text
|
||||||
|
v-if="!isViewMode"
|
||||||
|
class="col action action-btn"
|
||||||
|
@click="removeMember(member.userId)"
|
||||||
|
>
|
||||||
删除
|
删除
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
@ -202,7 +240,7 @@
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="bottom-bar">
|
<view class="bottom-bar" v-if="!isViewMode">
|
||||||
<uv-button
|
<uv-button
|
||||||
type="primary"
|
type="primary"
|
||||||
:disabled="!canSubmit || submitting"
|
:disabled="!canSubmit || submitting"
|
||||||
|
|
@ -227,7 +265,7 @@
|
||||||
@confirm="onDateConfirm"
|
@confirm="onDateConfirm"
|
||||||
></uv-datetime-picker>
|
></uv-datetime-picker>
|
||||||
|
|
||||||
<view class="member-modal" v-if="showMemberModal">
|
<view class="member-modal" v-if="showMemberModal && !isViewMode">
|
||||||
<view class="modal-mask" @click="closeMemberModal"></view>
|
<view class="modal-mask" @click="closeMemberModal"></view>
|
||||||
<view class="modal-panel">
|
<view class="modal-panel">
|
||||||
<view class="modal-header">
|
<view class="modal-header">
|
||||||
|
|
@ -272,7 +310,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed, watch } from 'vue';
|
||||||
import { onLoad } from '@dcloudio/uni-app';
|
import { onLoad } from '@dcloudio/uni-app';
|
||||||
import AttachmentFileUploader from '@/components/task/AttachmentFileUploader.vue';
|
import AttachmentFileUploader from '@/components/task/AttachmentFileUploader.vue';
|
||||||
import {
|
import {
|
||||||
|
|
@ -290,12 +328,35 @@ const costFields = [
|
||||||
];
|
];
|
||||||
|
|
||||||
const mode = ref('add');
|
const mode = ref('add');
|
||||||
const pageTitle = computed(() => (mode.value === 'edit' ? '编辑项目' : '新建项目'));
|
|
||||||
const pageLoading = ref(true);
|
const pageLoading = ref(true);
|
||||||
const submitting = ref(false);
|
const submitting = ref(false);
|
||||||
const projectId = ref('');
|
const projectId = ref('');
|
||||||
|
|
||||||
const formData = ref(createDefaultForm());
|
const formData = ref(createDefaultForm());
|
||||||
|
const detailInfo = ref(null);
|
||||||
|
|
||||||
|
const isViewMode = computed(() => mode.value === 'view');
|
||||||
|
const pageTitle = computed(() => {
|
||||||
|
if (isViewMode.value) {
|
||||||
|
return '项目详情';
|
||||||
|
}
|
||||||
|
return mode.value === 'edit' ? '编辑项目' : '新建项目';
|
||||||
|
});
|
||||||
|
const syncNavigationTitle = () => {
|
||||||
|
uni.setNavigationBarTitle({
|
||||||
|
title: pageTitle.value
|
||||||
|
});
|
||||||
|
};
|
||||||
|
watch(pageTitle, syncNavigationTitle, { immediate: true });
|
||||||
|
const detailName = computed(() => detailInfo.value?.name || detailInfo.value?.projectName || formData.value.name || '项目详情');
|
||||||
|
const detailStatusText = computed(() => getStatusText(detailInfo.value?.status || formData.value.status));
|
||||||
|
const detailCustomerText = computed(() => detailInfo.value?.customerName || formData.value.customerName || '—');
|
||||||
|
const detailCreatorText = computed(() => detailInfo.value?.createName || '—');
|
||||||
|
const detailDeadlineText = computed(() => formData.value.expireTime || detailInfo.value?.expireTime || '—');
|
||||||
|
const detailAmountText = computed(() => formatAmountDisplay(detailInfo.value?.amount ?? formData.value.amount));
|
||||||
|
const detailOwnerName = computed(() => {
|
||||||
|
const owner = formData.value.memberList.find((member) => member.role === 'OWNER');
|
||||||
|
return owner?.userName || detailInfo.value?.ownerName || '—';
|
||||||
|
});
|
||||||
|
|
||||||
const customerOptions = ref([]);
|
const customerOptions = ref([]);
|
||||||
const customerPickerRef = ref(null);
|
const customerPickerRef = ref(null);
|
||||||
|
|
@ -334,16 +395,24 @@ const filteredMemberOptions = computed(() => {
|
||||||
|
|
||||||
onLoad(async (options) => {
|
onLoad(async (options) => {
|
||||||
if (options?.mode) {
|
if (options?.mode) {
|
||||||
mode.value = options.mode === 'edit' ? 'edit' : 'add';
|
if (options.mode === 'edit') {
|
||||||
|
mode.value = 'edit';
|
||||||
|
} else if (options.mode === 'view') {
|
||||||
|
mode.value = 'view';
|
||||||
|
} else {
|
||||||
|
mode.value = 'add';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (options?.id) {
|
if (options?.id) {
|
||||||
projectId.value = options.id;
|
projectId.value = options.id;
|
||||||
mode.value = 'edit';
|
if (mode.value !== 'view') {
|
||||||
|
mode.value = 'edit';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.all([loadCustomerOptions(), loadMemberOptions()]);
|
await Promise.all([loadCustomerOptions(), loadMemberOptions()]);
|
||||||
if (mode.value === 'edit' && projectId.value) {
|
if ((mode.value === 'edit' || mode.value === 'view') && projectId.value) {
|
||||||
await loadProjectDetail(projectId.value);
|
await loadProjectDetail(projectId.value);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -356,6 +425,9 @@ const handleBack = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const openCustomerPicker = async () => {
|
const openCustomerPicker = async () => {
|
||||||
|
if (isViewMode.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!customerOptions.value.length) {
|
if (!customerOptions.value.length) {
|
||||||
await loadCustomerOptions();
|
await loadCustomerOptions();
|
||||||
}
|
}
|
||||||
|
|
@ -380,6 +452,9 @@ const handleCustomerConfirm = ({ value }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const openDatePicker = (field) => {
|
const openDatePicker = (field) => {
|
||||||
|
if (isViewMode.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
pendingDateField.value = field;
|
pendingDateField.value = field;
|
||||||
datePickerMode.value = 'date';
|
datePickerMode.value = 'date';
|
||||||
datePickerValue.value = formData.value[field]
|
datePickerValue.value = formData.value[field]
|
||||||
|
|
@ -396,6 +471,9 @@ const onDateConfirm = (event) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const openMemberModal = () => {
|
const openMemberModal = () => {
|
||||||
|
if (isViewMode.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
selectedMemberIds.value = formData.value.memberList
|
selectedMemberIds.value = formData.value.memberList
|
||||||
.map((member) => member.userId)
|
.map((member) => member.userId)
|
||||||
.filter((id) => id);
|
.filter((id) => id);
|
||||||
|
|
@ -408,6 +486,9 @@ const closeMemberModal = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleMember = (userId) => {
|
const toggleMember = (userId) => {
|
||||||
|
if (isViewMode.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const id = String(userId);
|
const id = String(userId);
|
||||||
const index = selectedMemberIds.value.indexOf(id);
|
const index = selectedMemberIds.value.indexOf(id);
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
|
|
@ -418,6 +499,9 @@ const toggleMember = (userId) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const confirmMemberSelection = () => {
|
const confirmMemberSelection = () => {
|
||||||
|
if (isViewMode.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const existingMap = new Map(
|
const existingMap = new Map(
|
||||||
formData.value.memberList.map((member) => [String(member.userId), member])
|
formData.value.memberList.map((member) => [String(member.userId), member])
|
||||||
);
|
);
|
||||||
|
|
@ -437,12 +521,18 @@ const confirmMemberSelection = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeMember = (userId) => {
|
const removeMember = (userId) => {
|
||||||
|
if (isViewMode.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
formData.value.memberList = formData.value.memberList.filter(
|
formData.value.memberList = formData.value.memberList.filter(
|
||||||
(member) => member.userId !== userId
|
(member) => member.userId !== userId
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateMemberRole = (userId, role) => {
|
const updateMemberRole = (userId, role) => {
|
||||||
|
if (isViewMode.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
formData.value.memberList = formData.value.memberList.map((member) => {
|
formData.value.memberList = formData.value.memberList.map((member) => {
|
||||||
if (member.userId === userId) {
|
if (member.userId === userId) {
|
||||||
return { ...member, role };
|
return { ...member, role };
|
||||||
|
|
@ -452,6 +542,9 @@ const updateMemberRole = (userId, role) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
|
if (isViewMode.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!canSubmit.value || submitting.value) {
|
if (!canSubmit.value || submitting.value) {
|
||||||
if (!canSubmit.value) {
|
if (!canSubmit.value) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
|
|
@ -467,8 +560,8 @@ const handleSubmit = async () => {
|
||||||
title: mode.value === 'edit' ? '保存中...' : '创建中...'
|
title: mode.value === 'edit' ? '保存中...' : '创建中...'
|
||||||
});
|
});
|
||||||
|
|
||||||
const payload = buildPayload();
|
|
||||||
try {
|
try {
|
||||||
|
const payload = buildPayload();
|
||||||
if (mode.value === 'edit') {
|
if (mode.value === 'edit') {
|
||||||
payload.id = projectId.value || payload.id;
|
payload.id = projectId.value || payload.id;
|
||||||
await updateProject(payload);
|
await updateProject(payload);
|
||||||
|
|
@ -485,6 +578,10 @@ const handleSubmit = async () => {
|
||||||
}, 600);
|
}, 600);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('保存项目失败:', error);
|
console.error('保存项目失败:', error);
|
||||||
|
uni.showToast({
|
||||||
|
title: error?.msg || error?.message || '保存失败,请稍后重试',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
submitting.value = false;
|
submitting.value = false;
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
|
|
@ -534,6 +631,10 @@ const loadProjectDetail = async (id) => {
|
||||||
...createDefaultForm(),
|
...createDefaultForm(),
|
||||||
...detail
|
...detail
|
||||||
};
|
};
|
||||||
|
detailInfo.value = {
|
||||||
|
...detail,
|
||||||
|
memberList: normalizeMembers(detail.memberList)
|
||||||
|
};
|
||||||
formData.value = {
|
formData.value = {
|
||||||
...formData.value,
|
...formData.value,
|
||||||
id: merged.id || id,
|
id: merged.id || id,
|
||||||
|
|
@ -545,6 +646,7 @@ const loadProjectDetail = async (id) => {
|
||||||
expireTime: merged.expireTime,
|
expireTime: merged.expireTime,
|
||||||
projectTags: merged.projectTags || '',
|
projectTags: merged.projectTags || '',
|
||||||
remark: merged.remark || '',
|
remark: merged.remark || '',
|
||||||
|
status: merged.status || '',
|
||||||
developmentCost: safeNumberToString(merged.developmentCost),
|
developmentCost: safeNumberToString(merged.developmentCost),
|
||||||
middleCost: safeNumberToString(merged.middleCost),
|
middleCost: safeNumberToString(merged.middleCost),
|
||||||
afterCost: safeNumberToString(merged.afterCost),
|
afterCost: safeNumberToString(merged.afterCost),
|
||||||
|
|
@ -574,6 +676,7 @@ function createDefaultForm() {
|
||||||
expireTime: '',
|
expireTime: '',
|
||||||
projectTags: '',
|
projectTags: '',
|
||||||
remark: '',
|
remark: '',
|
||||||
|
status: '',
|
||||||
developmentCost: '',
|
developmentCost: '',
|
||||||
middleCost: '',
|
middleCost: '',
|
||||||
afterCost: '',
|
afterCost: '',
|
||||||
|
|
@ -682,6 +785,37 @@ const normalizeMembers = (value) => {
|
||||||
.filter((member) => member.userId);
|
.filter((member) => member.userId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const statusTextMap = {
|
||||||
|
WAIT_START: '待开始',
|
||||||
|
IN_PROGRESS: '开发中',
|
||||||
|
COMPLETED: '开发完成',
|
||||||
|
ACCEPTED: '已验收',
|
||||||
|
MAINTENANCE: '维护中',
|
||||||
|
MAINTENANCE_OVERDUE: '维护到期',
|
||||||
|
DEVELOPMENT_COST: '前期费用',
|
||||||
|
MIDDLE_COST: '中期费用',
|
||||||
|
AFTER_COST: '后期费用',
|
||||||
|
DEVELOPMENT_OVERDUE: '开发超期'
|
||||||
|
};
|
||||||
|
|
||||||
|
function getStatusText(status) {
|
||||||
|
if (!status && status !== 0) {
|
||||||
|
return '—';
|
||||||
|
}
|
||||||
|
return statusTextMap[status] || status;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatAmountDisplay(value) {
|
||||||
|
if (value === null || value === undefined || value === '') {
|
||||||
|
return '—';
|
||||||
|
}
|
||||||
|
const num = Number(value);
|
||||||
|
if (Number.isNaN(num)) {
|
||||||
|
return '—';
|
||||||
|
}
|
||||||
|
return num.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||||
|
}
|
||||||
|
|
||||||
const getFileNameFromUrl = (url = '') => {
|
const getFileNameFromUrl = (url = '') => {
|
||||||
if (!url) return '附件';
|
if (!url) return '附件';
|
||||||
const parts = url.split('/');
|
const parts = url.split('/');
|
||||||
|
|
@ -732,6 +866,16 @@ const formatDate = (timestamp) => {
|
||||||
&.disabled {
|
&.disabled {
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
color: #bbb;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
.radio {
|
||||||
|
border-color: #ddd;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-title {
|
.nav-title {
|
||||||
|
|
@ -762,6 +906,41 @@ const formatDate = (timestamp) => {
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.detail-header {
|
||||||
|
background: #fff;
|
||||||
|
margin: 16px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #111;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-label {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-value {
|
||||||
|
color: #333;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.form-section {
|
.form-section {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
|
|
@ -833,6 +1012,11 @@ const formatDate = (timestamp) => {
|
||||||
|
|
||||||
.picker-input {
|
.picker-input {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
color: #999;
|
||||||
|
background: #f0f0f0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.picker-arrow {
|
.picker-arrow {
|
||||||
|
|
|
||||||
|
|
@ -823,11 +823,16 @@ onPullDownRefresh(async () => {
|
||||||
|
|
||||||
// 跳转到项目详情
|
// 跳转到项目详情
|
||||||
const goToProjectDetail = (project) => {
|
const goToProjectDetail = (project) => {
|
||||||
// TODO: 跳转到项目详情页面
|
if (!project?.id) {
|
||||||
// uni.showToast({
|
uni.showToast({
|
||||||
// title: '项目详情功能开发中',
|
title: '缺少项目ID',
|
||||||
// icon: 'none'
|
icon: 'none'
|
||||||
// });
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/project/form/index?mode=view&id=${project.id}`
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 加载成员列表
|
// 加载成员列表
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export const Request = () => {
|
||||||
uni.$uv.http.setConfig((config) => {
|
uni.$uv.http.setConfig((config) => {
|
||||||
/* config 为默认全局配置*/
|
/* config 为默认全局配置*/
|
||||||
config.baseURL = 'http://192.168.1.2:4001'; /* 根域名 */
|
config.baseURL = 'http://192.168.1.2:4001'; /* 根域名 */
|
||||||
config.baseURL = 'https://pm.ccttiot.com/prod-api'; /* 根域名 */
|
// config.baseURL = 'https://pm.ccttiot.com/prod-api'; /* 根域名 */
|
||||||
return config
|
return config
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user