fix(加班申请): 去掉撤销相关的状态和动作。
feat(工作报告): 开发工作报告功能
This commit is contained in:
@@ -0,0 +1,534 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user