feat(projects): 1、执行、任务、工作日志开发调试;2、增加富文本、附件等支撑
This commit is contained in:
317
src/views/project/list/modules/project-create-base-form.vue
Normal file
317
src/views/project/list/modules/project-create-base-form.vue
Normal file
@@ -0,0 +1,317 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onMounted, ref } from 'vue';
|
||||
import { ElCol, ElDatePicker, ElFormItem, ElInput, ElOption, ElRow, ElSelect } from 'element-plus';
|
||||
import dayjs from 'dayjs';
|
||||
import { RDMS_OBJECT_DIRECTION_DICT_CODE, RDMS_PROJECT_TYPE_DICT_CODE } from '@/constants/dict';
|
||||
import { fetchGetProductPage } from '@/service/api';
|
||||
import { useDict } from '@/hooks/business/dict';
|
||||
import { useForm, useFormRules } from '@/hooks/common/form';
|
||||
import BusinessUserSelect from '@/components/custom/business-user-select.vue';
|
||||
import DictSelect from '@/components/custom/dict-select.vue';
|
||||
|
||||
defineOptions({ name: 'ProjectCreateBaseForm' });
|
||||
|
||||
export interface ProjectCreateBaseForm {
|
||||
projectCode: string;
|
||||
projectName: string;
|
||||
directionCode: string;
|
||||
projectType: string;
|
||||
productId: string | null;
|
||||
managerUserId: string | null;
|
||||
plannedStartDate: string | null;
|
||||
plannedEndDate: string | null;
|
||||
projectDesc: string;
|
||||
}
|
||||
|
||||
interface ProductOption {
|
||||
id: string;
|
||||
name: string;
|
||||
directionCode: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
managerUserOptions: Api.SystemManage.UserSimple[];
|
||||
}
|
||||
|
||||
defineProps<Props>();
|
||||
|
||||
const model = defineModel<ProjectCreateBaseForm>('modelValue', { required: true });
|
||||
|
||||
const { formRef, validate } = useForm();
|
||||
const { createRequiredRule } = useFormRules();
|
||||
const { getLabel: getDirectionLabel } = useDict(RDMS_OBJECT_DIRECTION_DICT_CODE);
|
||||
|
||||
const productOptions = ref<ProductOption[]>([]);
|
||||
|
||||
const hasAssociatedProduct = computed(() => Boolean(model.value.productId));
|
||||
const directionReadonly = computed(() => hasAssociatedProduct.value);
|
||||
|
||||
const selectedProductDirection = computed(() => {
|
||||
if (!model.value.productId) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return productOptions.value.find(p => p.id === model.value.productId)?.directionCode || '';
|
||||
});
|
||||
|
||||
const effectiveDirectionCode = computed({
|
||||
get: () => {
|
||||
if (hasAssociatedProduct.value) {
|
||||
return selectedProductDirection.value || model.value.directionCode;
|
||||
}
|
||||
|
||||
return model.value.directionCode;
|
||||
},
|
||||
set: (val: string) => {
|
||||
if (!hasAssociatedProduct.value) {
|
||||
model.value.directionCode = val;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const directionDisplayName = computed(() => {
|
||||
const directionCode = effectiveDirectionCode.value;
|
||||
|
||||
if (!directionCode) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return getDirectionLabel(directionCode, directionCode);
|
||||
});
|
||||
|
||||
function parsePlannedDate(value: string | null | undefined) {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const date = new Date(value);
|
||||
return Number.isNaN(date.getTime()) ? null : date;
|
||||
}
|
||||
|
||||
function isPlannedDateRangeValid(startDate: string | null, endDate: string | null) {
|
||||
if (!startDate || !endDate) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !dayjs(endDate).isBefore(dayjs(startDate), 'day');
|
||||
}
|
||||
|
||||
function buildEndDateShortcut(text: string, mutator: (date: Date) => void) {
|
||||
return {
|
||||
text,
|
||||
value: () => {
|
||||
let startDate = parsePlannedDate(model.value.plannedStartDate);
|
||||
|
||||
if (!startDate) {
|
||||
startDate = new Date();
|
||||
model.value.plannedStartDate = dayjs(startDate).format('YYYY-MM-DD');
|
||||
window.$message?.info('未选择计划开始日期,已按今日为基准计算');
|
||||
nextTick(() => formRef.value?.clearValidate('plannedStartDate'));
|
||||
}
|
||||
|
||||
const endDate = new Date(startDate.getTime());
|
||||
mutator(endDate);
|
||||
return endDate;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const plannedEndDateShortcuts = [
|
||||
buildEndDateShortcut('一星期', date => date.setDate(date.getDate() + 7)),
|
||||
buildEndDateShortcut('两星期', date => date.setDate(date.getDate() + 14)),
|
||||
buildEndDateShortcut('一个月', date => date.setMonth(date.getMonth() + 1)),
|
||||
buildEndDateShortcut('三个月', date => date.setMonth(date.getMonth() + 3)),
|
||||
buildEndDateShortcut('半年', date => date.setMonth(date.getMonth() + 6)),
|
||||
buildEndDateShortcut('一年', date => date.setFullYear(date.getFullYear() + 1))
|
||||
];
|
||||
|
||||
const rules = computed(
|
||||
() =>
|
||||
({
|
||||
projectName: [createRequiredRule('请输入项目名称')],
|
||||
directionCode: [createRequiredRule('请选择项目方向')],
|
||||
projectType: [createRequiredRule('请选择项目类型')],
|
||||
managerUserId: [createRequiredRule('请选择项目经理')],
|
||||
plannedStartDate: [createRequiredRule('请选择计划开始日期')],
|
||||
plannedEndDate: [
|
||||
createRequiredRule('请选择计划结束日期'),
|
||||
{
|
||||
validator: (_rule, value: string | null, callback) => {
|
||||
if (!isPlannedDateRangeValid(model.value.plannedStartDate, value)) {
|
||||
callback(new Error('计划结束日期不能早于计划开始日期'));
|
||||
return;
|
||||
}
|
||||
|
||||
callback();
|
||||
},
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
}) satisfies Record<string, App.Global.FormRule[]>
|
||||
);
|
||||
|
||||
async function loadProductOptions() {
|
||||
const { error, data } = await fetchGetProductPage({ pageNo: 1, pageSize: 200 });
|
||||
|
||||
if (error || !data) {
|
||||
productOptions.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
productOptions.value = data.list.map(item => ({
|
||||
id: item.id,
|
||||
name: item.name || item.code || item.id,
|
||||
directionCode: item.directionCode || ''
|
||||
}));
|
||||
}
|
||||
|
||||
function onProductChange(newProductId: string | null) {
|
||||
if (!newProductId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const product = productOptions.value.find(p => p.id === newProductId);
|
||||
|
||||
if (product) {
|
||||
model.value.directionCode = product.directionCode;
|
||||
}
|
||||
}
|
||||
|
||||
async function runValidate(): Promise<boolean> {
|
||||
try {
|
||||
await validate();
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadProductOptions);
|
||||
|
||||
defineExpose({ validate: runValidate });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElForm ref="formRef" :model="model" :rules="rules" label-position="top">
|
||||
<ElRow :gutter="16">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="项目名称" prop="projectName">
|
||||
<ElInput v-model="model.projectName" clearable maxlength="128" placeholder="请输入项目名称" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="项目编码" prop="projectCode">
|
||||
<ElInput v-model="model.projectCode" clearable placeholder="不填则由后端自动生成" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="项目方向" prop="directionCode">
|
||||
<DictSelect
|
||||
v-if="!directionReadonly"
|
||||
v-model="effectiveDirectionCode"
|
||||
:dict-code="RDMS_OBJECT_DIRECTION_DICT_CODE"
|
||||
filterable
|
||||
placeholder="请选择项目方向"
|
||||
/>
|
||||
<ElInput
|
||||
v-else
|
||||
:model-value="directionDisplayName"
|
||||
readonly
|
||||
class="project-create-base-form__readonly-input"
|
||||
placeholder="未获取到项目方向"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="项目类型" prop="projectType">
|
||||
<DictSelect
|
||||
v-model="model.projectType"
|
||||
:dict-code="RDMS_PROJECT_TYPE_DICT_CODE"
|
||||
filterable
|
||||
placeholder="请选择项目类型"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="所属产品" prop="productId">
|
||||
<ElSelect
|
||||
v-model="model.productId"
|
||||
clearable
|
||||
filterable
|
||||
placeholder="选择所属产品(可选),选择后将锁定项目方向"
|
||||
@change="onProductChange"
|
||||
>
|
||||
<ElOption v-for="item in productOptions" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="项目经理" prop="managerUserId">
|
||||
<BusinessUserSelect
|
||||
v-model="model.managerUserId"
|
||||
:options="managerUserOptions"
|
||||
placeholder="请选择项目经理"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="计划开始日期" prop="plannedStartDate">
|
||||
<ElDatePicker
|
||||
v-model="model.plannedStartDate"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="选择开始日期"
|
||||
class="project-create-base-form__date-picker"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="计划结束日期" prop="plannedEndDate">
|
||||
<ElDatePicker
|
||||
v-model="model.plannedEndDate"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="选择结束日期"
|
||||
:shortcuts="plannedEndDateShortcuts"
|
||||
class="project-create-base-form__date-picker"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="24">
|
||||
<ElFormItem label="项目说明" prop="projectDesc">
|
||||
<ElInput
|
||||
v-model="model.projectDesc"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
placeholder="请输入项目说明"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</ElForm>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.project-create-base-form__readonly-input .el-input__wrapper) {
|
||||
background: linear-gradient(180deg, rgb(241 245 249 / 98%), rgb(226 232 240 / 94%)), rgb(241 245 249);
|
||||
box-shadow: 0 0 0 1px rgb(203 213 225 / 96%) inset;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
:deep(.project-create-base-form__readonly-input .el-input__wrapper:hover),
|
||||
:deep(.project-create-base-form__readonly-input.is-focus .el-input__wrapper) {
|
||||
box-shadow: 0 0 0 1px rgb(203 213 225 / 96%) inset;
|
||||
}
|
||||
|
||||
:deep(.project-create-base-form__readonly-input .el-input__inner) {
|
||||
color: rgb(51 65 85 / 96%);
|
||||
cursor: default;
|
||||
-webkit-text-fill-color: rgb(51 65 85 / 96%);
|
||||
}
|
||||
|
||||
:deep(.project-create-base-form__date-picker.el-date-editor.el-input) {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user