Files
cn-rdms-web/src/views/personal-center/work-report/shared/components/create-dialog.vue

535 lines
15 KiB
Vue
Raw Normal View History

<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import { Calendar } from '@element-plus/icons-vue';
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
import {
type WorkReportPeriodOption,
buildMonthlyPeriodFromMonth,
buildProjectPeriodFromMonth,
buildWeeklyPeriodFromDate,
formatPeriodDisplayLabel,
getReportTypePeriodOptions
} from '../utils';
import { WORK_REPORT_TYPE_LABEL, type WorkReportType } from '../types';
defineOptions({ name: 'WorkReportCreateDialog' });
interface Props {
defaultReportType?: WorkReportType;
projectVisible?: boolean;
projectOptions?: Api.WorkReport.Project.ProjectReportOwnerProjectOption[];
}
const props = withDefaults(defineProps<Props>(), {
defaultReportType: 'weekly',
projectVisible: false,
projectOptions: () => []
});
const visible = defineModel<boolean>('visible', { default: false });
const emit = defineEmits<{
(
e: 'confirm',
payload:
| { reportType: 'weekly' | 'monthly'; period: WorkReportPeriodOption['period'] }
| {
reportType: 'project';
projectId: string;
flag: number;
period: WorkReportPeriodOption['period'];
}
): void;
}>();
const selectedPeriodKey = ref('');
const selectedProjectId = ref('');
const customWeekDate = ref('');
const customMonth = ref('');
const customProjectMonth = ref('');
const customProjectFlag = ref(1);
const selectedReportType = computed<WorkReportType>(() => {
if (props.defaultReportType === 'project' && !props.projectVisible) return 'weekly';
return props.defaultReportType;
});
const periodOptionMap = computed(() => getReportTypePeriodOptions());
const activePeriodOptions = computed(() => periodOptionMap.value[selectedReportType.value]);
const dialogTitle = computed(() => `新增${WORK_REPORT_TYPE_LABEL[selectedReportType.value]}`);
const projectHalfOptions = [
{ label: '上半月', value: 1 },
{ label: '下半月', value: 2 }
];
const defaultCustomMonth = computed(() => {
const period = activePeriodOptions.value[0]?.period;
return period?.periodStartDate.slice(0, 7) || '';
});
const customPeriod = computed<WorkReportPeriodOption['period'] | null>(() => {
if (selectedPeriodKey.value !== 'custom') return null;
if (selectedReportType.value === 'weekly') {
if (!customWeekDate.value) return null;
return buildWeeklyPeriodFromDate(customWeekDate.value);
}
if (selectedReportType.value === 'monthly') {
if (!customMonth.value) return null;
return buildMonthlyPeriodFromMonth(customMonth.value);
}
if (!customProjectMonth.value) return null;
return buildProjectPeriodFromMonth(customProjectMonth.value, customProjectFlag.value);
});
const selectedPeriod = computed(
() => activePeriodOptions.value.find(item => item.key === selectedPeriodKey.value) ?? activePeriodOptions.value[0]
);
const selectedPeriodValue = computed(() =>
selectedPeriodKey.value === 'custom' ? customPeriod.value : selectedPeriod.value?.period
);
const customPeriodPreviewLabel = computed(() =>
customPeriod.value ? formatPeriodDisplayLabel(customPeriod.value.periodLabel) : ''
);
const confirmDisabled = computed(() => {
if (!selectedPeriodValue.value) return true;
if (selectedReportType.value === 'project' && !selectedProjectId.value) return true;
return false;
});
watch(
selectedReportType,
type => {
selectedPeriodKey.value = periodOptionMap.value[type][0]?.key || '';
if (type === 'project' && !selectedProjectId.value) {
selectedProjectId.value = props.projectOptions[0]?.id || '';
}
},
{ immediate: true }
);
watch(visible, isVisible => {
if (!isVisible) return;
selectedProjectId.value = props.projectOptions[0]?.id || '';
selectedPeriodKey.value = periodOptionMap.value[selectedReportType.value][0]?.key || '';
customWeekDate.value = activePeriodOptions.value[0]?.period.periodStartDate || '';
customMonth.value = defaultCustomMonth.value;
customProjectMonth.value = defaultCustomMonth.value;
customProjectFlag.value = activePeriodOptions.value[0]?.flag || 1;
});
function handleConfirm() {
const period = selectedPeriodValue.value;
if (!period) return;
if (selectedReportType.value === 'project') {
emit('confirm', {
reportType: 'project',
projectId: selectedProjectId.value,
flag: selectedPeriodKey.value === 'custom' ? customProjectFlag.value : selectedPeriod.value.flag || 1,
period
});
} else {
emit('confirm', {
reportType: selectedReportType.value,
period
});
}
visible.value = false;
}
</script>
<template>
<BusinessFormDialog
v-model="visible"
:title="dialogTitle"
class="work-report-create-dialog"
preset="md"
confirm-text="确认新增"
append-to-body
:close-on-click-modal="false"
@confirm="handleConfirm"
>
<div v-if="selectedReportType === 'project'" class="work-report-create-dialog__project-select">
<label class="work-report-create-dialog__label">项目</label>
<ElSelect v-model="selectedProjectId" class="w-full" placeholder="请选择项目" filterable>
<ElOption
v-for="item in props.projectOptions"
:key="item.id"
:label="item.projectCode ? `${item.projectName}${item.projectCode}` : item.projectName"
:value="item.id"
/>
</ElSelect>
</div>
<div class="work-report-create-dialog__section">
<div class="work-report-create-dialog__grid is-period">
<button
v-for="item in activePeriodOptions"
:key="item.key"
type="button"
class="work-report-create-dialog__choice"
:class="{ 'is-active': selectedPeriodKey === item.key }"
@click="selectedPeriodKey = item.key"
>
<div class="work-report-create-dialog__choice-title">{{ item.label }}</div>
<div class="work-report-create-dialog__choice-desc">{{ item.description }}</div>
</button>
<button
type="button"
class="work-report-create-dialog__choice"
:class="{ 'is-active': selectedPeriodKey === 'custom' }"
@click="selectedPeriodKey = 'custom'"
>
<div class="work-report-create-dialog__choice-title">自定义周期</div>
<div class="work-report-create-dialog__choice-desc">
{{
selectedReportType === 'weekly'
? '选择某一周作为周报周期。'
: selectedReportType === 'monthly'
? '选择某一月作为月报周期。'
: '选择某个月的上半月或下半月。'
}}
</div>
</button>
</div>
<div v-if="selectedPeriodKey === 'custom'" class="work-report-create-dialog__custom-period">
<div v-if="selectedReportType === 'weekly'" class="work-report-create-dialog__custom-row">
<div class="work-report-create-dialog__field work-report-create-dialog__field--inline">
<label class="work-report-create-dialog__label">周报周期</label>
<ElDatePicker
v-model="customWeekDate"
type="date"
format="YYYY[年第]ww[周]"
value-format="YYYY-MM-DD"
popper-class="work-report-create-date-popper"
placeholder="请选择周报周期"
/>
<div v-if="customPeriodPreviewLabel" class="work-report-create-dialog__period-preview">
{{ customPeriodPreviewLabel }}
</div>
</div>
</div>
<div v-else-if="selectedReportType === 'monthly'" class="work-report-create-dialog__custom-row">
<div class="work-report-create-dialog__field work-report-create-dialog__field--inline">
<label class="work-report-create-dialog__label">月报周期</label>
<ElDatePicker
v-model="customMonth"
type="month"
value-format="YYYY-MM"
popper-class="work-report-create-date-popper"
placeholder="请选择月份"
/>
<div v-if="customPeriodPreviewLabel" class="work-report-create-dialog__period-preview">
{{ customPeriodPreviewLabel }}
</div>
</div>
</div>
<div v-else class="work-report-create-dialog__custom-project">
<div class="work-report-create-dialog__custom-project-grid">
<div class="work-report-create-dialog__custom-project-item">
<div class="work-report-create-dialog__custom-project-item-label">选择月份</div>
<ElDatePicker
v-model="customProjectMonth"
class="w-full"
type="month"
value-format="YYYY-MM"
popper-class="work-report-create-date-popper"
placeholder="请选择月份"
/>
</div>
<div class="work-report-create-dialog__custom-project-item">
<div class="work-report-create-dialog__custom-project-item-label">选择半月</div>
<ElSegmented
v-model="customProjectFlag"
:options="projectHalfOptions"
class="work-report-create-dialog__half-segmented"
/>
</div>
</div>
<div v-if="customPeriodPreviewLabel" class="work-report-create-dialog__period-preview">
<ElIcon class="work-report-create-dialog__period-preview-icon"><Calendar /></ElIcon>
<span class="work-report-create-dialog__period-preview-text">已选周期</span>
<span class="work-report-create-dialog__period-preview-value">{{ customPeriodPreviewLabel }}</span>
</div>
</div>
</div>
</div>
<template #footer="{ close }">
<div class="work-report-create-dialog__footer">
<ElButton @click="close">取消</ElButton>
<ElButton type="primary" :disabled="confirmDisabled" @click="handleConfirm">确认新增</ElButton>
</div>
</template>
</BusinessFormDialog>
</template>
<style scoped>
.work-report-create-dialog__header {
padding: 0 0 14px;
}
.work-report-create-dialog__title {
margin: 0;
font-size: 18px;
font-weight: 900;
}
.work-report-create-dialog__subtitle {
margin-top: 5px;
color: #667085;
font-size: 12px;
}
.work-report-create-dialog__section + .work-report-create-dialog__section {
margin-top: 18px;
}
.work-report-create-dialog__grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
}
.work-report-create-dialog__grid.is-period {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.work-report-create-dialog__choice {
padding: 16px;
border: 2px solid #e5edf1;
border-radius: 16px;
background: #fbfdfe;
text-align: left;
cursor: pointer;
transition:
border-color 0.16s ease,
background 0.16s ease,
box-shadow 0.16s ease;
}
.work-report-create-dialog__choice:hover {
border-color: rgba(15, 118, 110, 0.28);
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.06);
}
.work-report-create-dialog__choice.is-active {
border-color: #0f766e;
background: #ecfdf5;
}
.work-report-create-dialog__choice-title {
font-weight: 900;
color: #14213d;
}
.work-report-create-dialog__choice-desc {
margin-top: 7px;
color: #667085;
font-size: 12px;
line-height: 1.5;
}
.work-report-create-dialog__project-select {
margin: 4px 0 18px;
display: grid;
gap: 6px;
}
.work-report-create-dialog__field {
display: grid;
gap: 6px;
}
/** 行内字段label 和控件在同一行,绿色 label 紧贴日期选择器右边 */
.work-report-create-dialog__field--inline {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
min-width: 0;
}
.work-report-create-dialog__field--inline .work-report-create-dialog__label {
flex-shrink: 0;
white-space: nowrap;
}
.work-report-create-dialog__field--inline :deep(.el-date-editor) {
width: auto;
min-width: 160px;
max-width: 240px;
}
.work-report-create-dialog__label {
color: #667085;
font-size: 12px;
font-weight: 800;
}
.work-report-create-dialog__custom-period {
margin-top: 14px;
padding: 16px;
border: 1px solid rgba(15, 118, 110, 0.18);
border-radius: 14px;
background: linear-gradient(180deg, #f8fffd 0%, #ffffff 100%);
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.06);
}
.work-report-create-dialog__custom-row {
display: flex;
gap: 12px;
align-items: flex-end;
}
.work-report-create-dialog__custom-row > .work-report-create-dialog__field--inline {
flex: 1;
min-width: 0;
}
.work-report-create-dialog__custom-project {
display: grid;
gap: 14px;
}
.work-report-create-dialog__custom-project-grid {
display: grid;
grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
gap: 14px;
align-items: stretch;
}
.work-report-create-dialog__custom-project-item {
display: flex;
flex-direction: column;
gap: 8px;
padding: 12px 14px;
border: 1px solid #e5edf1;
border-radius: 10px;
background: #fff;
transition: border-color 0.18s ease;
}
.work-report-create-dialog__custom-project-item:hover {
border-color: rgba(15, 118, 110, 0.4);
}
.work-report-create-dialog__custom-project-item-label {
color: #475467;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.2px;
}
.work-report-create-dialog__custom-project-item :deep(.el-date-editor) {
width: 100%;
}
.work-report-create-dialog__half-segmented {
width: 100%;
display: flex;
}
.work-report-create-dialog__half-segmented :deep(.el-segmented__group) {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
width: 100%;
gap: 0;
}
.work-report-create-dialog__half-segmented :deep(.el-segmented__item) {
flex: 1;
min-width: 0;
justify-content: center;
}
.work-report-create-dialog__period-preview {
display: inline-flex;
align-items: center;
gap: 6px;
min-height: 32px;
padding: 0 14px;
border: 1px solid rgba(15, 118, 110, 0.18);
border-radius: 999px;
background: #ecfdf5;
color: #0f766e;
font-size: 13px;
font-weight: 700;
white-space: nowrap;
width: fit-content;
}
.work-report-create-dialog__period-preview-icon {
font-size: 14px;
color: #0f766e;
}
.work-report-create-dialog__period-preview-text {
color: #475467;
font-weight: 600;
}
.work-report-create-dialog__period-preview-value {
color: #0f766e;
font-weight: 800;
}
.work-report-create-dialog__footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
@media (width <= 900px) {
.work-report-create-dialog__grid,
.work-report-create-dialog__grid.is-period {
grid-template-columns: 1fr;
}
.work-report-create-dialog__custom-row,
.work-report-create-dialog__custom-project-grid {
flex-direction: column;
grid-template-columns: 1fr;
}
.work-report-create-dialog__field--inline {
flex-wrap: wrap;
}
.work-report-create-dialog__field--inline :deep(.el-date-editor) {
max-width: 100%;
flex: 1;
}
.work-report-create-dialog__period-preview {
justify-content: center;
width: 100%;
}
}
:global(.work-report-create-date-popper) {
border-radius: 12px;
overflow: hidden;
}
:global(.work-report-create-date-popper .el-picker-panel__body-wrapper) {
background: #fff;
}
:global(.work-report-create-date-popper .el-date-table td.current:not(.disabled) .el-date-table-cell__text),
:global(.work-report-create-date-popper .el-month-table td.current:not(.disabled) .cell) {
background-color: #0f766e;
}
</style>