fix: 员工排班接入真实数据并移除兜底
This commit is contained in:
@@ -58,9 +58,11 @@ export interface StaffScheduleDto {
|
|||||||
|
|
||||||
/** 门店排班聚合 */
|
/** 门店排班聚合 */
|
||||||
export interface StoreStaffScheduleDto {
|
export interface StoreStaffScheduleDto {
|
||||||
|
isScheduleConfigured: boolean;
|
||||||
|
isTemplateConfigured: boolean;
|
||||||
schedules: StaffScheduleDto[];
|
schedules: StaffScheduleDto[];
|
||||||
storeId: string;
|
storeId: string;
|
||||||
templates: StoreShiftTemplatesDto;
|
templates: null | StoreShiftTemplatesDto;
|
||||||
weekStartDate: string;
|
weekStartDate: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import dayjs from 'dayjs';
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isSaving: boolean;
|
isSaving: boolean;
|
||||||
|
isTemplateConfigured: boolean;
|
||||||
onSetTemplateTime: (payload: {
|
onSetTemplateTime: (payload: {
|
||||||
field: 'endTime' | 'startTime';
|
field: 'endTime' | 'startTime';
|
||||||
shiftType: Exclude<ShiftType, 'off'>;
|
shiftType: Exclude<ShiftType, 'off'>;
|
||||||
@@ -71,6 +72,10 @@ function handleTemplateTimeChange(payload: {
|
|||||||
<span class="section-title">班次模板</span>
|
<span class="section-title">班次模板</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<div v-if="!props.isTemplateConfigured" class="template-guide">
|
||||||
|
当前门店尚未配置班次模板,请先设置并保存模板,再进行员工排班。
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="template-list">
|
<div class="template-list">
|
||||||
<div
|
<div
|
||||||
v-for="row in templateRows"
|
v-for="row in templateRows"
|
||||||
@@ -113,7 +118,11 @@ function handleTemplateTimeChange(payload: {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="template-tip">
|
<div class="template-tip">
|
||||||
调整模板后,个人排班和周排班中的同类型班次会同步到新的时间段。
|
{{
|
||||||
|
props.isTemplateConfigured
|
||||||
|
? '调整模板后,个人排班和周排班中的同类型班次会同步到新的时间段。'
|
||||||
|
: '模板保存成功后,员工列表中的“排班”和“编辑排班”会自动可用。'
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="staff-card-actions">
|
<div class="staff-card-actions">
|
||||||
|
|||||||
@@ -40,6 +40,12 @@ function getDayShift(dayOfWeek: number) {
|
|||||||
return props.form.shifts.find((item) => item.dayOfWeek === dayOfWeek);
|
return props.form.shifts.find((item) => item.dayOfWeek === dayOfWeek);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 判断某日是否展示时间编辑。 */
|
||||||
|
function isTimeHidden(dayOfWeek: number) {
|
||||||
|
const shift = getDayShift(dayOfWeek);
|
||||||
|
return !shift || shift.shiftType === 'off';
|
||||||
|
}
|
||||||
|
|
||||||
/** 转换时间组件值。 */
|
/** 转换时间组件值。 */
|
||||||
function toPickerValue(time: string) {
|
function toPickerValue(time: string) {
|
||||||
if (!time) return undefined;
|
if (!time) return undefined;
|
||||||
@@ -65,7 +71,7 @@ function toTimeText(value: Dayjs | null | string | undefined) {
|
|||||||
@update:open="(value) => emit('update:open', value)"
|
@update:open="(value) => emit('update:open', value)"
|
||||||
>
|
>
|
||||||
<div class="staff-schedule-hint">
|
<div class="staff-schedule-hint">
|
||||||
点击班次胶囊可自动填充时间,选择“休息”后该日不排班。
|
点击班次胶囊可自动填充时间;显示“未配置”表示该日尚未设置排班。
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="staff-schedule-list">
|
<div class="staff-schedule-list">
|
||||||
@@ -94,14 +100,19 @@ function toTimeText(value: Dayjs | null | string | undefined) {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<span v-if="!getDayShift(day.dayOfWeek)" class="staff-schedule-empty">
|
||||||
|
未配置
|
||||||
|
</span>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="staff-schedule-time"
|
class="staff-schedule-time"
|
||||||
:class="{ hidden: getDayShift(day.dayOfWeek)?.shiftType === 'off' }"
|
:class="{ hidden: isTimeHidden(day.dayOfWeek) }"
|
||||||
>
|
>
|
||||||
<TimePicker
|
<TimePicker
|
||||||
:value="toPickerValue(getDayShift(day.dayOfWeek)?.startTime || '')"
|
:value="toPickerValue(getDayShift(day.dayOfWeek)?.startTime || '')"
|
||||||
format="HH:mm"
|
format="HH:mm"
|
||||||
:allow-clear="false"
|
:allow-clear="false"
|
||||||
|
:disabled="isTimeHidden(day.dayOfWeek)"
|
||||||
@update:value="
|
@update:value="
|
||||||
(value) => props.onSetShiftStart(day.dayOfWeek, toTimeText(value))
|
(value) => props.onSetShiftStart(day.dayOfWeek, toTimeText(value))
|
||||||
"
|
"
|
||||||
@@ -111,6 +122,7 @@ function toTimeText(value: Dayjs | null | string | undefined) {
|
|||||||
:value="toPickerValue(getDayShift(day.dayOfWeek)?.endTime || '')"
|
:value="toPickerValue(getDayShift(day.dayOfWeek)?.endTime || '')"
|
||||||
format="HH:mm"
|
format="HH:mm"
|
||||||
:allow-clear="false"
|
:allow-clear="false"
|
||||||
|
:disabled="isTimeHidden(day.dayOfWeek)"
|
||||||
@update:value="
|
@update:value="
|
||||||
(value) => props.onSetShiftEnd(day.dayOfWeek, toTimeText(value))
|
(value) => props.onSetShiftEnd(day.dayOfWeek, toTimeText(value))
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ interface Props {
|
|||||||
getShiftClass: (shiftType: ShiftType) => string;
|
getShiftClass: (shiftType: ShiftType) => string;
|
||||||
getShiftLabel: (shiftType: ShiftType) => string;
|
getShiftLabel: (shiftType: ShiftType) => string;
|
||||||
getShiftTimeText: (shift: StaffDayShiftDto) => string;
|
getShiftTimeText: (shift: StaffDayShiftDto) => string;
|
||||||
|
isTemplateConfigured: boolean;
|
||||||
isSaving: boolean;
|
isSaving: boolean;
|
||||||
legendItems: ShiftLegendItem[];
|
legendItems: ShiftLegendItem[];
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -33,19 +34,30 @@ const emit = defineEmits<{
|
|||||||
(event: 'update:open', value: boolean): void;
|
(event: 'update:open', value: boolean): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
/** 获取某行某天班次,缺失时兜底为休息。 */
|
/** 获取某行某天班次,缺失时返回未配置。 */
|
||||||
function resolveDayShift(
|
function resolveDayShift(
|
||||||
row: WeekEditorRow,
|
row: WeekEditorRow,
|
||||||
dayOfWeek: number,
|
dayOfWeek: number,
|
||||||
): StaffDayShiftDto {
|
): StaffDayShiftDto | undefined {
|
||||||
return (
|
return row.shifts.find((item) => item.dayOfWeek === dayOfWeek);
|
||||||
row.shifts.find((item) => item.dayOfWeek === dayOfWeek) ?? {
|
|
||||||
dayOfWeek,
|
|
||||||
shiftType: 'off',
|
|
||||||
startTime: '',
|
|
||||||
endTime: '',
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
/** 获取单元格样式。 */
|
||||||
|
function resolveCellClass(shift: StaffDayShiftDto | undefined) {
|
||||||
|
if (!shift) return 'shift-unconfigured';
|
||||||
|
return props.getShiftClass(shift.shiftType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取单元格班次文案。 */
|
||||||
|
function resolveCellLabel(shift: StaffDayShiftDto | undefined) {
|
||||||
|
if (!shift) return '未配置';
|
||||||
|
return props.getShiftLabel(shift.shiftType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取单元格时间文案。 */
|
||||||
|
function resolveCellTimeText(shift: StaffDayShiftDto | undefined) {
|
||||||
|
if (!shift) return '未配置';
|
||||||
|
return props.getShiftTimeText(shift);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -74,7 +86,11 @@ function resolveDayShift(
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="props.rows.length === 0" class="staff-week-empty">
|
<div v-if="!props.isTemplateConfigured" class="staff-week-empty">
|
||||||
|
<Empty description="未配置班次模板,请先配置模板后再编辑周排班" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="props.rows.length === 0" class="staff-week-empty">
|
||||||
<Empty description="暂无可编辑排班" />
|
<Empty description="暂无可编辑排班" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -105,11 +121,7 @@ function resolveDayShift(
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="staff-week-cell"
|
class="staff-week-cell"
|
||||||
:class="
|
:class="resolveCellClass(resolveDayShift(row, day.dayOfWeek))"
|
||||||
props.getShiftClass(
|
|
||||||
resolveDayShift(row, day.dayOfWeek).shiftType,
|
|
||||||
)
|
|
||||||
"
|
|
||||||
@click="
|
@click="
|
||||||
emit('cycleShift', {
|
emit('cycleShift', {
|
||||||
staffId: row.staffId,
|
staffId: row.staffId,
|
||||||
@@ -118,16 +130,10 @@ function resolveDayShift(
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
<span class="staff-week-cell-label">
|
<span class="staff-week-cell-label">
|
||||||
{{
|
{{ resolveCellLabel(resolveDayShift(row, day.dayOfWeek)) }}
|
||||||
props.getShiftLabel(
|
|
||||||
resolveDayShift(row, day.dayOfWeek).shiftType,
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</span>
|
</span>
|
||||||
<span class="staff-week-cell-time">
|
<span class="staff-week-cell-time">
|
||||||
{{
|
{{ resolveCellTimeText(resolveDayShift(row, day.dayOfWeek)) }}
|
||||||
props.getShiftTimeText(resolveDayShift(row, day.dayOfWeek))
|
|
||||||
}}
|
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
@@ -141,6 +147,7 @@ function resolveDayShift(
|
|||||||
<Button @click="emit('update:open', false)">取消</Button>
|
<Button @click="emit('update:open', false)">取消</Button>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
|
:disabled="!props.isTemplateConfigured"
|
||||||
:loading="props.isSaving"
|
:loading="props.isSaving"
|
||||||
@click="emit('submit')"
|
@click="emit('submit')"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ interface Props {
|
|||||||
getShiftClass: (shiftType: ShiftType) => string;
|
getShiftClass: (shiftType: ShiftType) => string;
|
||||||
getShiftLabel: (shiftType: ShiftType) => string;
|
getShiftLabel: (shiftType: ShiftType) => string;
|
||||||
getShiftTimeText: (shift: StaffDayShiftDto) => string;
|
getShiftTimeText: (shift: StaffDayShiftDto) => string;
|
||||||
|
isScheduleConfigured: boolean;
|
||||||
|
isTemplateConfigured: boolean;
|
||||||
roleLabelResolver: (roleType: WeekEditorRow['roleType']) => string;
|
roleLabelResolver: (roleType: WeekEditorRow['roleType']) => string;
|
||||||
rows: WeekEditorRow[];
|
rows: WeekEditorRow[];
|
||||||
}
|
}
|
||||||
@@ -24,19 +26,30 @@ const emit = defineEmits<{
|
|||||||
(event: 'editWeek'): void;
|
(event: 'editWeek'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
/** 读取员工某日排班,缺失时兜底为休息。 */
|
/** 读取员工某日排班,缺失时返回未配置。 */
|
||||||
function resolveDayShift(
|
function resolveDayShift(
|
||||||
row: WeekEditorRow,
|
row: WeekEditorRow,
|
||||||
dayOfWeek: number,
|
dayOfWeek: number,
|
||||||
): StaffDayShiftDto {
|
): StaffDayShiftDto | undefined {
|
||||||
return (
|
return row.shifts.find((item) => item.dayOfWeek === dayOfWeek);
|
||||||
row.shifts.find((item) => item.dayOfWeek === dayOfWeek) ?? {
|
|
||||||
dayOfWeek,
|
|
||||||
shiftType: 'off',
|
|
||||||
startTime: '',
|
|
||||||
endTime: '',
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
/** 读取某单元格样式。 */
|
||||||
|
function resolveCellClass(shift: StaffDayShiftDto | undefined) {
|
||||||
|
if (!shift) return 'shift-unconfigured';
|
||||||
|
return props.getShiftClass(shift.shiftType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 读取某单元格时间文案。 */
|
||||||
|
function resolveCellTimeText(shift: StaffDayShiftDto | undefined) {
|
||||||
|
if (!shift) return '未配置';
|
||||||
|
return props.getShiftTimeText(shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 读取某单元格班次文案。 */
|
||||||
|
function resolveCellLabel(shift: StaffDayShiftDto | undefined) {
|
||||||
|
if (!shift) return '未配置';
|
||||||
|
return props.getShiftLabel(shift.shiftType);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -46,14 +59,28 @@ function resolveDayShift(
|
|||||||
<span class="section-title">本周排班</span>
|
<span class="section-title">本周排班</span>
|
||||||
</template>
|
</template>
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<Button @click="emit('editWeek')">编辑排班</Button>
|
<Button
|
||||||
|
:disabled="!props.isTemplateConfigured"
|
||||||
|
:title="props.isTemplateConfigured ? '' : '请先配置班次模板'"
|
||||||
|
@click="emit('editWeek')"
|
||||||
|
>
|
||||||
|
编辑排班
|
||||||
|
</Button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div v-if="props.rows.length === 0" class="schedule-empty">
|
<div v-if="!props.isTemplateConfigured" class="schedule-empty">
|
||||||
|
<Empty description="未配置班次模板,请先在上方完成模板设置" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="props.rows.length === 0" class="schedule-empty">
|
||||||
<Empty description="暂无可排班员工" />
|
<Empty description="暂无可排班员工" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="schedule-board-wrap">
|
<div v-else class="schedule-board-wrap">
|
||||||
|
<div v-if="!props.isScheduleConfigured" class="schedule-board-tip">
|
||||||
|
当前门店还没有排班数据,点击“编辑排班”开始配置。
|
||||||
|
</div>
|
||||||
|
|
||||||
<table class="schedule-board-table">
|
<table class="schedule-board-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -78,23 +105,13 @@ function resolveDayShift(
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="schedule-cell"
|
class="schedule-cell"
|
||||||
:class="
|
:class="resolveCellClass(resolveDayShift(row, day.dayOfWeek))"
|
||||||
props.getShiftClass(
|
|
||||||
resolveDayShift(row, day.dayOfWeek).shiftType,
|
|
||||||
)
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<span class="schedule-cell-time">
|
<span class="schedule-cell-time">
|
||||||
{{
|
{{ resolveCellTimeText(resolveDayShift(row, day.dayOfWeek)) }}
|
||||||
props.getShiftTimeText(resolveDayShift(row, day.dayOfWeek))
|
|
||||||
}}
|
|
||||||
</span>
|
</span>
|
||||||
<span class="schedule-cell-label">
|
<span class="schedule-cell-label">
|
||||||
{{
|
{{ resolveCellLabel(resolveDayShift(row, day.dayOfWeek)) }}
|
||||||
props.getShiftLabel(
|
|
||||||
resolveDayShift(row, day.dayOfWeek).shiftType,
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -99,18 +99,18 @@ export const STAFF_PERMISSION_OPTIONS: StaffPermissionOption[] = [
|
|||||||
{ value: '数据统计', label: '数据统计' },
|
{ value: '数据统计', label: '数据统计' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const DEFAULT_SHIFT_TEMPLATES: StoreShiftTemplatesDto = {
|
export const EMPTY_SHIFT_TEMPLATES: StoreShiftTemplatesDto = {
|
||||||
morning: {
|
morning: {
|
||||||
startTime: '09:00',
|
startTime: '',
|
||||||
endTime: '14:00',
|
endTime: '',
|
||||||
},
|
},
|
||||||
evening: {
|
evening: {
|
||||||
startTime: '14:00',
|
startTime: '',
|
||||||
endTime: '21:00',
|
endTime: '',
|
||||||
},
|
},
|
||||||
full: {
|
full: {
|
||||||
startTime: '09:00',
|
startTime: '',
|
||||||
endTime: '21:00',
|
endTime: '',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -22,16 +22,18 @@ import {
|
|||||||
getStoreStaffScheduleApi,
|
getStoreStaffScheduleApi,
|
||||||
} from '#/api/store-staff';
|
} from '#/api/store-staff';
|
||||||
|
|
||||||
|
import { EMPTY_SHIFT_TEMPLATES } from './constants';
|
||||||
import {
|
import {
|
||||||
cloneScheduleMap,
|
cloneScheduleMap,
|
||||||
cloneShifts,
|
cloneShifts,
|
||||||
cloneTemplates,
|
cloneTemplates,
|
||||||
createEmptyWeekShifts,
|
normalizeSparseWeekShifts,
|
||||||
normalizeWeekShifts,
|
|
||||||
} from './helpers';
|
} from './helpers';
|
||||||
|
|
||||||
interface CreateDataActionsOptions {
|
interface CreateDataActionsOptions {
|
||||||
filters: StaffFilterState;
|
filters: StaffFilterState;
|
||||||
|
isScheduleConfigured: Ref<boolean>;
|
||||||
|
isTemplateConfigured: Ref<boolean>;
|
||||||
isPageLoading: Ref<boolean>;
|
isPageLoading: Ref<boolean>;
|
||||||
isStoreLoading: Ref<boolean>;
|
isStoreLoading: Ref<boolean>;
|
||||||
scheduleMap: Ref<Record<string, StaffDayShiftDto[]>>;
|
scheduleMap: Ref<Record<string, StaffDayShiftDto[]>>;
|
||||||
@@ -45,11 +47,21 @@ interface CreateDataActionsOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createDataActions(options: CreateDataActionsOptions) {
|
export function createDataActions(options: CreateDataActionsOptions) {
|
||||||
|
/** 判断当前排班映射是否包含任意配置。 */
|
||||||
|
function hasAnyConfiguredShift(
|
||||||
|
scheduleMap: Record<string, StaffDayShiftDto[]>,
|
||||||
|
) {
|
||||||
|
return Object.values(scheduleMap).some((shifts) => shifts.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
/** 清空当前门店相关数据。 */
|
/** 清空当前门店相关数据。 */
|
||||||
function clearStoreData() {
|
function clearStoreData() {
|
||||||
options.staffRows.value = [];
|
options.staffRows.value = [];
|
||||||
options.staffDirectory.value = [];
|
options.staffDirectory.value = [];
|
||||||
options.staffTotal.value = 0;
|
options.staffTotal.value = 0;
|
||||||
|
options.templates.value = cloneTemplates(EMPTY_SHIFT_TEMPLATES);
|
||||||
|
options.isTemplateConfigured.value = false;
|
||||||
|
options.isScheduleConfigured.value = false;
|
||||||
options.scheduleMap.value = {};
|
options.scheduleMap.value = {};
|
||||||
options.scheduleSnapshot.value = null;
|
options.scheduleSnapshot.value = null;
|
||||||
}
|
}
|
||||||
@@ -97,6 +109,9 @@ export function createDataActions(options: CreateDataActionsOptions) {
|
|||||||
/** 加载班次模板与排班配置。 */
|
/** 加载班次模板与排班配置。 */
|
||||||
async function loadScheduleSettings() {
|
async function loadScheduleSettings() {
|
||||||
if (!options.selectedStoreId.value) {
|
if (!options.selectedStoreId.value) {
|
||||||
|
options.templates.value = cloneTemplates(EMPTY_SHIFT_TEMPLATES);
|
||||||
|
options.isTemplateConfigured.value = false;
|
||||||
|
options.isScheduleConfigured.value = false;
|
||||||
options.scheduleMap.value = {};
|
options.scheduleMap.value = {};
|
||||||
options.scheduleSnapshot.value = null;
|
options.scheduleSnapshot.value = null;
|
||||||
return;
|
return;
|
||||||
@@ -105,18 +120,19 @@ export function createDataActions(options: CreateDataActionsOptions) {
|
|||||||
const result = await getStoreStaffScheduleApi(
|
const result = await getStoreStaffScheduleApi(
|
||||||
options.selectedStoreId.value,
|
options.selectedStoreId.value,
|
||||||
);
|
);
|
||||||
const templates = cloneTemplates(result.templates);
|
const templates = cloneTemplates(result.templates ?? EMPTY_SHIFT_TEMPLATES);
|
||||||
|
|
||||||
const scheduleMap: Record<string, StaffDayShiftDto[]> = {};
|
const scheduleMap: Record<string, StaffDayShiftDto[]> = {};
|
||||||
for (const schedule of result.schedules ?? []) {
|
for (const schedule of result.schedules ?? []) {
|
||||||
scheduleMap[schedule.staffId] = normalizeWeekShifts({
|
scheduleMap[schedule.staffId] = normalizeSparseWeekShifts({
|
||||||
shifts: schedule.shifts,
|
shifts: schedule.shifts,
|
||||||
fallback: createEmptyWeekShifts(templates),
|
|
||||||
templates,
|
templates,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
options.templates.value = templates;
|
options.templates.value = templates;
|
||||||
|
options.isTemplateConfigured.value = Boolean(result.isTemplateConfigured);
|
||||||
|
options.isScheduleConfigured.value = Boolean(result.isScheduleConfigured);
|
||||||
options.scheduleMap.value = scheduleMap;
|
options.scheduleMap.value = scheduleMap;
|
||||||
options.scheduleSnapshot.value = {
|
options.scheduleSnapshot.value = {
|
||||||
templates: cloneTemplates(templates),
|
templates: cloneTemplates(templates),
|
||||||
@@ -195,6 +211,7 @@ export function createDataActions(options: CreateDataActionsOptions) {
|
|||||||
[staffId]: cloneShifts(shifts),
|
[staffId]: cloneShifts(shifts),
|
||||||
};
|
};
|
||||||
options.scheduleMap.value = nextMap;
|
options.scheduleMap.value = nextMap;
|
||||||
|
options.isScheduleConfigured.value = hasAnyConfiguredShift(nextMap);
|
||||||
options.scheduleSnapshot.value = {
|
options.scheduleSnapshot.value = {
|
||||||
templates: cloneTemplates(options.templates.value),
|
templates: cloneTemplates(options.templates.value),
|
||||||
scheduleMap: cloneScheduleMap(nextMap),
|
scheduleMap: cloneScheduleMap(nextMap),
|
||||||
@@ -204,6 +221,7 @@ export function createDataActions(options: CreateDataActionsOptions) {
|
|||||||
/** 批量更新排班映射。 */
|
/** 批量更新排班映射。 */
|
||||||
function patchScheduleMap(nextMap: Record<string, StaffDayShiftDto[]>) {
|
function patchScheduleMap(nextMap: Record<string, StaffDayShiftDto[]>) {
|
||||||
options.scheduleMap.value = cloneScheduleMap(nextMap);
|
options.scheduleMap.value = cloneScheduleMap(nextMap);
|
||||||
|
options.isScheduleConfigured.value = hasAnyConfiguredShift(nextMap);
|
||||||
options.scheduleSnapshot.value = {
|
options.scheduleSnapshot.value = {
|
||||||
templates: cloneTemplates(options.templates.value),
|
templates: cloneTemplates(options.templates.value),
|
||||||
scheduleMap: cloneScheduleMap(nextMap),
|
scheduleMap: cloneScheduleMap(nextMap),
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
import type {
|
import type {
|
||||||
ShiftType,
|
ShiftType,
|
||||||
StaffDayShiftDto,
|
StaffDayShiftDto,
|
||||||
StaffScheduleDto,
|
|
||||||
StoreShiftTemplatesDto,
|
StoreShiftTemplatesDto,
|
||||||
} from '#/api/store-staff';
|
} from '#/api/store-staff';
|
||||||
import type {
|
import type {
|
||||||
@@ -15,8 +14,6 @@ import type {
|
|||||||
WeekEditorRow,
|
WeekEditorRow,
|
||||||
} from '#/views/store/staff/types';
|
} from '#/views/store/staff/types';
|
||||||
|
|
||||||
import { DAY_OPTIONS } from './constants';
|
|
||||||
|
|
||||||
/** 深拷贝模板。 */
|
/** 深拷贝模板。 */
|
||||||
export function cloneTemplates(
|
export function cloneTemplates(
|
||||||
source: StoreShiftTemplatesDto,
|
source: StoreShiftTemplatesDto,
|
||||||
@@ -68,18 +65,6 @@ export function cloneStaffForm(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 按模板创建空白一周(默认休息)。 */
|
|
||||||
export function createEmptyWeekShifts(
|
|
||||||
_templates: StoreShiftTemplatesDto,
|
|
||||||
): StaffDayShiftDto[] {
|
|
||||||
return DAY_OPTIONS.map((day) => ({
|
|
||||||
dayOfWeek: day.dayOfWeek,
|
|
||||||
shiftType: 'off' as const,
|
|
||||||
startTime: '',
|
|
||||||
endTime: '',
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 生成指定班次默认时间。 */
|
/** 生成指定班次默认时间。 */
|
||||||
export function resolveShiftTimeByType(
|
export function resolveShiftTimeByType(
|
||||||
shiftType: ShiftType,
|
shiftType: ShiftType,
|
||||||
@@ -124,58 +109,40 @@ export function normalizeDayShift(payload: {
|
|||||||
return {
|
return {
|
||||||
dayOfWeek: payload.dayOfWeek,
|
dayOfWeek: payload.dayOfWeek,
|
||||||
shiftType,
|
shiftType,
|
||||||
startTime: normalizeTime(payload.shift.startTime, templateTime.startTime),
|
startTime: normalizeTime(
|
||||||
endTime: normalizeTime(payload.shift.endTime, templateTime.endTime),
|
payload.shift.startTime,
|
||||||
|
payload.fallback?.startTime || templateTime.startTime,
|
||||||
|
),
|
||||||
|
endTime: normalizeTime(
|
||||||
|
payload.shift.endTime,
|
||||||
|
payload.fallback?.endTime || templateTime.endTime,
|
||||||
|
),
|
||||||
} satisfies StaffDayShiftDto;
|
} satisfies StaffDayShiftDto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 归一化周排班数组,确保包含 0-6 共七天。 */
|
/** 归一化周排班数组(仅保留真实配置的天)。 */
|
||||||
export function normalizeWeekShifts(payload: {
|
export function normalizeSparseWeekShifts(payload: {
|
||||||
fallback: StaffDayShiftDto[];
|
|
||||||
shifts: Partial<StaffDayShiftDto>[];
|
shifts: Partial<StaffDayShiftDto>[];
|
||||||
templates: StoreShiftTemplatesDto;
|
templates: StoreShiftTemplatesDto;
|
||||||
}) {
|
}) {
|
||||||
const shiftMap = new Map<number, Partial<StaffDayShiftDto>>();
|
const shiftMap = new Map<number, StaffDayShiftDto>();
|
||||||
for (const shift of payload.shifts) {
|
for (const shift of payload.shifts) {
|
||||||
const dayOfWeek = Number(shift.dayOfWeek);
|
const dayOfWeek = Number(shift.dayOfWeek);
|
||||||
if (!Number.isInteger(dayOfWeek) || dayOfWeek < 0 || dayOfWeek > 6) {
|
if (!Number.isInteger(dayOfWeek) || dayOfWeek < 0 || dayOfWeek > 6) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
shiftMap.set(dayOfWeek, shift);
|
|
||||||
}
|
|
||||||
|
|
||||||
return DAY_OPTIONS.map((day) => {
|
shiftMap.set(
|
||||||
const fallbackShift = payload.fallback.find(
|
dayOfWeek,
|
||||||
(item) => item.dayOfWeek === day.dayOfWeek,
|
normalizeDayShift({
|
||||||
);
|
dayOfWeek,
|
||||||
return normalizeDayShift({
|
shift,
|
||||||
dayOfWeek: day.dayOfWeek,
|
|
||||||
shift: shiftMap.get(day.dayOfWeek) ?? fallbackShift ?? {},
|
|
||||||
fallback: fallbackShift,
|
|
||||||
templates: payload.templates,
|
templates: payload.templates,
|
||||||
});
|
}),
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 将接口排班数组转换为映射。 */
|
return [...shiftMap.values()].toSorted((a, b) => a.dayOfWeek - b.dayOfWeek);
|
||||||
export function createScheduleMap(schedules: StaffScheduleDto[]) {
|
|
||||||
const scheduleMap: Record<string, StaffDayShiftDto[]> = {};
|
|
||||||
for (const schedule of schedules) {
|
|
||||||
scheduleMap[schedule.staffId] = normalizeWeekShifts({
|
|
||||||
shifts: schedule.shifts,
|
|
||||||
fallback: createEmptyWeekShifts({
|
|
||||||
morning: { startTime: '09:00', endTime: '14:00' },
|
|
||||||
evening: { startTime: '14:00', endTime: '21:00' },
|
|
||||||
full: { startTime: '09:00', endTime: '21:00' },
|
|
||||||
}),
|
|
||||||
templates: {
|
|
||||||
morning: { startTime: '09:00', endTime: '14:00' },
|
|
||||||
evening: { startTime: '14:00', endTime: '21:00' },
|
|
||||||
full: { startTime: '09:00', endTime: '21:00' },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return scheduleMap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 将员工排班矩阵转为可编辑周视图行。 */
|
/** 将员工排班矩阵转为可编辑周视图行。 */
|
||||||
@@ -187,16 +154,13 @@ export function buildWeekEditorRows(payload: {
|
|||||||
roleType: WeekEditorRow['roleType'];
|
roleType: WeekEditorRow['roleType'];
|
||||||
status: WeekEditorRow['status'];
|
status: WeekEditorRow['status'];
|
||||||
}>;
|
}>;
|
||||||
templates: StoreShiftTemplatesDto;
|
|
||||||
}) {
|
}) {
|
||||||
return payload.staffs.map((staff) => ({
|
return payload.staffs.map((staff) => ({
|
||||||
staffId: staff.id,
|
staffId: staff.id,
|
||||||
staffName: staff.name,
|
staffName: staff.name,
|
||||||
roleType: staff.roleType,
|
roleType: staff.roleType,
|
||||||
status: staff.status,
|
status: staff.status,
|
||||||
shifts: cloneShifts(
|
shifts: cloneShifts(payload.scheduleMap[staff.id] ?? []),
|
||||||
payload.scheduleMap[staff.id] ?? createEmptyWeekShifts(payload.templates),
|
|
||||||
),
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,21 +171,27 @@ export function updateWeekRowShift(payload: {
|
|||||||
row: WeekEditorRow;
|
row: WeekEditorRow;
|
||||||
templates: StoreShiftTemplatesDto;
|
templates: StoreShiftTemplatesDto;
|
||||||
}) {
|
}) {
|
||||||
return {
|
const currentShift = payload.row.shifts.find(
|
||||||
...payload.row,
|
(item) => item.dayOfWeek === payload.dayOfWeek,
|
||||||
shifts: payload.row.shifts.map((item) => {
|
);
|
||||||
if (item.dayOfWeek !== payload.dayOfWeek) return { ...item };
|
const nextShift = normalizeDayShift({
|
||||||
const normalized = normalizeDayShift({
|
|
||||||
dayOfWeek: payload.dayOfWeek,
|
dayOfWeek: payload.dayOfWeek,
|
||||||
shift: {
|
shift: {
|
||||||
dayOfWeek: payload.dayOfWeek,
|
dayOfWeek: payload.dayOfWeek,
|
||||||
shiftType: payload.nextShiftType,
|
shiftType: payload.nextShiftType,
|
||||||
},
|
},
|
||||||
fallback: item,
|
fallback: currentShift,
|
||||||
templates: payload.templates,
|
templates: payload.templates,
|
||||||
});
|
});
|
||||||
return normalized;
|
|
||||||
}),
|
return {
|
||||||
|
...payload.row,
|
||||||
|
shifts: [
|
||||||
|
...payload.row.shifts
|
||||||
|
.filter((item) => item.dayOfWeek !== payload.dayOfWeek)
|
||||||
|
.map((item) => ({ ...item })),
|
||||||
|
nextShift,
|
||||||
|
].toSorted((a, b) => a.dayOfWeek - b.dayOfWeek),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ import {
|
|||||||
buildWeekEditorRows,
|
buildWeekEditorRows,
|
||||||
cloneScheduleMap,
|
cloneScheduleMap,
|
||||||
cloneShifts,
|
cloneShifts,
|
||||||
createEmptyWeekShifts,
|
|
||||||
normalizeDayShift,
|
normalizeDayShift,
|
||||||
|
normalizeSparseWeekShifts,
|
||||||
normalizeTime,
|
normalizeTime,
|
||||||
updateWeekRowShift,
|
updateWeekRowShift,
|
||||||
} from './helpers';
|
} from './helpers';
|
||||||
@@ -57,50 +57,74 @@ export function createScheduleActions(options: CreateScheduleActionsOptions) {
|
|||||||
options.personalForm.staffName = staff.name;
|
options.personalForm.staffName = staff.name;
|
||||||
options.personalForm.roleType = staff.roleType;
|
options.personalForm.roleType = staff.roleType;
|
||||||
options.personalForm.shifts = cloneShifts(
|
options.personalForm.shifts = cloneShifts(
|
||||||
options.scheduleMap.value[staff.id] ??
|
options.scheduleMap.value[staff.id] ?? [],
|
||||||
createEmptyWeekShifts(options.templates.value),
|
|
||||||
);
|
);
|
||||||
options.isPersonalDrawerOpen.value = true;
|
options.isPersonalDrawerOpen.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 按天更新个人排班(不存在时自动补入)。 */
|
||||||
|
function upsertPersonalShift(dayOfWeek: number, nextShift: StaffDayShiftDto) {
|
||||||
|
options.personalForm.shifts = [
|
||||||
|
...options.personalForm.shifts
|
||||||
|
.filter((item) => item.dayOfWeek !== dayOfWeek)
|
||||||
|
.map((item) => ({ ...item })),
|
||||||
|
nextShift,
|
||||||
|
].toSorted((a, b) => a.dayOfWeek - b.dayOfWeek);
|
||||||
|
}
|
||||||
|
|
||||||
/** 更新个人排班班次类型。 */
|
/** 更新个人排班班次类型。 */
|
||||||
function setPersonalShiftType(dayOfWeek: number, shiftType: ShiftType) {
|
function setPersonalShiftType(dayOfWeek: number, shiftType: ShiftType) {
|
||||||
options.personalForm.shifts = options.personalForm.shifts.map((item) => {
|
const currentShift = options.personalForm.shifts.find(
|
||||||
if (item.dayOfWeek !== dayOfWeek) return { ...item };
|
(item) => item.dayOfWeek === dayOfWeek,
|
||||||
return normalizeDayShift({
|
);
|
||||||
|
const nextShift = normalizeDayShift({
|
||||||
dayOfWeek,
|
dayOfWeek,
|
||||||
shift: {
|
shift: {
|
||||||
...item,
|
dayOfWeek,
|
||||||
shiftType,
|
shiftType,
|
||||||
},
|
},
|
||||||
fallback: item,
|
fallback: currentShift,
|
||||||
templates: options.templates.value,
|
templates: options.templates.value,
|
||||||
});
|
});
|
||||||
});
|
upsertPersonalShift(dayOfWeek, nextShift);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 更新个人排班开始时间。 */
|
/** 更新个人排班开始时间。 */
|
||||||
function setPersonalShiftStart(dayOfWeek: number, startTime: string) {
|
function setPersonalShiftStart(dayOfWeek: number, startTime: string) {
|
||||||
options.personalForm.shifts = options.personalForm.shifts.map((item) => {
|
const currentShift = options.personalForm.shifts.find(
|
||||||
if (item.dayOfWeek !== dayOfWeek) return { ...item };
|
(item) => item.dayOfWeek === dayOfWeek,
|
||||||
if (item.shiftType === 'off') return { ...item };
|
);
|
||||||
return {
|
if (!currentShift || currentShift.shiftType === 'off') return;
|
||||||
...item,
|
|
||||||
startTime: normalizeTime(startTime, item.startTime),
|
const nextShift = normalizeDayShift({
|
||||||
};
|
dayOfWeek,
|
||||||
|
shift: {
|
||||||
|
...currentShift,
|
||||||
|
startTime: normalizeTime(startTime, currentShift.startTime),
|
||||||
|
},
|
||||||
|
fallback: currentShift,
|
||||||
|
templates: options.templates.value,
|
||||||
});
|
});
|
||||||
|
upsertPersonalShift(dayOfWeek, nextShift);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 更新个人排班结束时间。 */
|
/** 更新个人排班结束时间。 */
|
||||||
function setPersonalShiftEnd(dayOfWeek: number, endTime: string) {
|
function setPersonalShiftEnd(dayOfWeek: number, endTime: string) {
|
||||||
options.personalForm.shifts = options.personalForm.shifts.map((item) => {
|
const currentShift = options.personalForm.shifts.find(
|
||||||
if (item.dayOfWeek !== dayOfWeek) return { ...item };
|
(item) => item.dayOfWeek === dayOfWeek,
|
||||||
if (item.shiftType === 'off') return { ...item };
|
);
|
||||||
return {
|
if (!currentShift || currentShift.shiftType === 'off') return;
|
||||||
...item,
|
|
||||||
endTime: normalizeTime(endTime, item.endTime),
|
const nextShift = normalizeDayShift({
|
||||||
};
|
dayOfWeek,
|
||||||
|
shift: {
|
||||||
|
...currentShift,
|
||||||
|
endTime: normalizeTime(endTime, currentShift.endTime),
|
||||||
|
},
|
||||||
|
fallback: currentShift,
|
||||||
|
templates: options.templates.value,
|
||||||
});
|
});
|
||||||
|
upsertPersonalShift(dayOfWeek, nextShift);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 提交个人排班。 */
|
/** 提交个人排班。 */
|
||||||
@@ -115,7 +139,13 @@ export function createScheduleActions(options: CreateScheduleActionsOptions) {
|
|||||||
shifts: cloneShifts(options.personalForm.shifts),
|
shifts: cloneShifts(options.personalForm.shifts),
|
||||||
});
|
});
|
||||||
|
|
||||||
options.patchStaffSchedule(result.staffId, result.shifts);
|
options.patchStaffSchedule(
|
||||||
|
result.staffId,
|
||||||
|
normalizeSparseWeekShifts({
|
||||||
|
shifts: result.shifts,
|
||||||
|
templates: options.templates.value,
|
||||||
|
}),
|
||||||
|
);
|
||||||
options.isPersonalDrawerOpen.value = false;
|
options.isPersonalDrawerOpen.value = false;
|
||||||
message.success('员工排班已保存');
|
message.success('员工排班已保存');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -133,7 +163,6 @@ export function createScheduleActions(options: CreateScheduleActionsOptions) {
|
|||||||
options.weekRows.value = buildWeekEditorRows({
|
options.weekRows.value = buildWeekEditorRows({
|
||||||
staffs: editableStaffs,
|
staffs: editableStaffs,
|
||||||
scheduleMap: options.scheduleMap.value,
|
scheduleMap: options.scheduleMap.value,
|
||||||
templates: options.templates.value,
|
|
||||||
});
|
});
|
||||||
options.isWeekDrawerOpen.value = true;
|
options.isWeekDrawerOpen.value = true;
|
||||||
}
|
}
|
||||||
@@ -178,7 +207,10 @@ export function createScheduleActions(options: CreateScheduleActionsOptions) {
|
|||||||
|
|
||||||
const nextMap = cloneScheduleMap(options.scheduleMap.value);
|
const nextMap = cloneScheduleMap(options.scheduleMap.value);
|
||||||
for (const schedule of result.schedules ?? []) {
|
for (const schedule of result.schedules ?? []) {
|
||||||
nextMap[schedule.staffId] = cloneShifts(schedule.shifts);
|
nextMap[schedule.staffId] = normalizeSparseWeekShifts({
|
||||||
|
shifts: cloneShifts(schedule.shifts),
|
||||||
|
templates: options.templates.value,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
options.patchScheduleMap(nextMap);
|
options.patchScheduleMap(nextMap);
|
||||||
|
|
||||||
|
|||||||
@@ -24,11 +24,13 @@ import type {
|
|||||||
|
|
||||||
import { computed, onActivated, onMounted, reactive, ref, watch } from 'vue';
|
import { computed, onActivated, onMounted, reactive, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DAY_OPTIONS,
|
DAY_OPTIONS,
|
||||||
DEFAULT_FILTER_STATE,
|
DEFAULT_FILTER_STATE,
|
||||||
DEFAULT_SHIFT_TEMPLATES,
|
|
||||||
DEFAULT_STAFF_FORM,
|
DEFAULT_STAFF_FORM,
|
||||||
|
EMPTY_SHIFT_TEMPLATES,
|
||||||
PAGE_SIZE_OPTIONS,
|
PAGE_SIZE_OPTIONS,
|
||||||
SHIFT_OPTIONS,
|
SHIFT_OPTIONS,
|
||||||
STAFF_PERMISSION_OPTIONS,
|
STAFF_PERMISSION_OPTIONS,
|
||||||
@@ -42,11 +44,9 @@ import {
|
|||||||
cloneShifts,
|
cloneShifts,
|
||||||
cloneStaffForm,
|
cloneStaffForm,
|
||||||
cloneTemplates,
|
cloneTemplates,
|
||||||
createEmptyWeekShifts,
|
|
||||||
formatShiftTimeText,
|
formatShiftTimeText,
|
||||||
getCurrentWeekStartDate,
|
getCurrentWeekStartDate,
|
||||||
maskPhone,
|
maskPhone,
|
||||||
normalizeWeekShifts,
|
|
||||||
resolveAvatarText,
|
resolveAvatarText,
|
||||||
} from './staff-page/helpers';
|
} from './staff-page/helpers';
|
||||||
import { createScheduleActions } from './staff-page/schedule-actions';
|
import { createScheduleActions } from './staff-page/schedule-actions';
|
||||||
@@ -75,9 +75,11 @@ export function useStoreStaffPage() {
|
|||||||
const staffRows = ref<StoreStaffDto[]>([]);
|
const staffRows = ref<StoreStaffDto[]>([]);
|
||||||
const staffTotal = ref(0);
|
const staffTotal = ref(0);
|
||||||
const staffDirectory = ref<StoreStaffDto[]>([]);
|
const staffDirectory = ref<StoreStaffDto[]>([]);
|
||||||
|
const isTemplateConfigured = ref(false);
|
||||||
|
const isScheduleConfigured = ref(false);
|
||||||
|
|
||||||
const templates = ref<StoreShiftTemplatesDto>(
|
const templates = ref<StoreShiftTemplatesDto>(
|
||||||
cloneTemplates(DEFAULT_SHIFT_TEMPLATES),
|
cloneTemplates(EMPTY_SHIFT_TEMPLATES),
|
||||||
);
|
);
|
||||||
const scheduleMap = ref<Record<string, StaffDayShiftDto[]>>({});
|
const scheduleMap = ref<Record<string, StaffDayShiftDto[]>>({});
|
||||||
const scheduleSnapshot = ref<null | {
|
const scheduleSnapshot = ref<null | {
|
||||||
@@ -97,7 +99,7 @@ export function useStoreStaffPage() {
|
|||||||
staffId: '',
|
staffId: '',
|
||||||
staffName: '',
|
staffName: '',
|
||||||
roleType: 'cashier',
|
roleType: 'cashier',
|
||||||
shifts: createEmptyWeekShifts(DEFAULT_SHIFT_TEMPLATES),
|
shifts: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const isWeekDrawerOpen = ref(false);
|
const isWeekDrawerOpen = ref(false);
|
||||||
@@ -182,9 +184,7 @@ export function useStoreStaffPage() {
|
|||||||
staffName: staff.name,
|
staffName: staff.name,
|
||||||
roleType: staff.roleType,
|
roleType: staff.roleType,
|
||||||
status: staff.status,
|
status: staff.status,
|
||||||
shifts: cloneShifts(
|
shifts: cloneShifts(scheduleMap.value[staff.id] ?? []),
|
||||||
scheduleMap.value[staff.id] ?? createEmptyWeekShifts(templates.value),
|
|
||||||
),
|
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -195,7 +195,10 @@ export function useStoreStaffPage() {
|
|||||||
type: item.value as Exclude<ShiftType, 'off'>,
|
type: item.value as Exclude<ShiftType, 'off'>,
|
||||||
label: item.label,
|
label: item.label,
|
||||||
className: item.className,
|
className: item.className,
|
||||||
timeText: `${template.startTime}-${template.endTime}`,
|
timeText:
|
||||||
|
template.startTime && template.endTime
|
||||||
|
? `${template.startTime}-${template.endTime}`
|
||||||
|
: '未配置',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -212,6 +215,8 @@ export function useStoreStaffPage() {
|
|||||||
reloadStoreData,
|
reloadStoreData,
|
||||||
} = createDataActions({
|
} = createDataActions({
|
||||||
filters,
|
filters,
|
||||||
|
isScheduleConfigured,
|
||||||
|
isTemplateConfigured,
|
||||||
isPageLoading,
|
isPageLoading,
|
||||||
isStoreLoading,
|
isStoreLoading,
|
||||||
scheduleMap,
|
scheduleMap,
|
||||||
@@ -349,7 +354,7 @@ export function useStoreStaffPage() {
|
|||||||
|
|
||||||
/** 判断员工是否可进入排班。 */
|
/** 判断员工是否可进入排班。 */
|
||||||
function isScheduleDisabled(staff: StoreStaffDto) {
|
function isScheduleDisabled(staff: StoreStaffDto) {
|
||||||
return staff.status === 'resigned';
|
return staff.status === 'resigned' || !isTemplateConfigured.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 打开个人排班抽屉。 */
|
/** 打开个人排班抽屉。 */
|
||||||
@@ -358,6 +363,15 @@ export function useStoreStaffPage() {
|
|||||||
openPersonalScheduleDrawer(staff);
|
openPersonalScheduleDrawer(staff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 打开周排班抽屉。 */
|
||||||
|
function handleOpenWeekSchedule() {
|
||||||
|
if (!isTemplateConfigured.value) {
|
||||||
|
message.warning('请先配置班次模板,再编辑排班');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
openWeekScheduleDrawer();
|
||||||
|
}
|
||||||
|
|
||||||
/** 获取角色展示文案。 */
|
/** 获取角色展示文案。 */
|
||||||
function getRoleLabel(roleType: StaffRoleType) {
|
function getRoleLabel(roleType: StaffRoleType) {
|
||||||
return roleOptionMap.value.get(roleType)?.label ?? roleType;
|
return roleOptionMap.value.get(roleType)?.label ?? roleType;
|
||||||
@@ -390,23 +404,6 @@ export function useStoreStaffPage() {
|
|||||||
return shiftOptionMap.value.get(shiftType)?.className ?? 'shift-off';
|
return shiftOptionMap.value.get(shiftType)?.className ?? 'shift-off';
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取员工某天排班。 */
|
|
||||||
function getStaffShift(row: WeekEditorRow, dayOfWeek: number) {
|
|
||||||
return (
|
|
||||||
row.shifts.find((item) => item.dayOfWeek === dayOfWeek) ??
|
|
||||||
normalizeWeekShifts({
|
|
||||||
shifts: [],
|
|
||||||
fallback: createEmptyWeekShifts(templates.value),
|
|
||||||
templates: templates.value,
|
|
||||||
}).find((item) => item.dayOfWeek === dayOfWeek) ?? {
|
|
||||||
dayOfWeek,
|
|
||||||
shiftType: 'off',
|
|
||||||
startTime: '',
|
|
||||||
endTime: '',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 7. 监听门店切换。
|
// 7. 监听门店切换。
|
||||||
watch(selectedStoreId, async (storeId) => {
|
watch(selectedStoreId, async (storeId) => {
|
||||||
if (!storeId) {
|
if (!storeId) {
|
||||||
@@ -437,11 +434,12 @@ export function useStoreStaffPage() {
|
|||||||
deletingStaffId,
|
deletingStaffId,
|
||||||
filters,
|
filters,
|
||||||
getCurrentWeekStartDate,
|
getCurrentWeekStartDate,
|
||||||
|
isScheduleConfigured,
|
||||||
|
isTemplateConfigured,
|
||||||
getRoleLabel,
|
getRoleLabel,
|
||||||
getRoleTagClass,
|
getRoleTagClass,
|
||||||
getShiftClass,
|
getShiftClass,
|
||||||
getShiftLabel,
|
getShiftLabel,
|
||||||
getStaffShift,
|
|
||||||
getStatusDotClass,
|
getStatusDotClass,
|
||||||
getStatusLabel,
|
getStatusLabel,
|
||||||
handleCopyCheckAll,
|
handleCopyCheckAll,
|
||||||
@@ -470,7 +468,7 @@ export function useStoreStaffPage() {
|
|||||||
maskPhone,
|
maskPhone,
|
||||||
openCopyModal,
|
openCopyModal,
|
||||||
openStaffDrawer,
|
openStaffDrawer,
|
||||||
openWeekScheduleDrawer,
|
handleOpenWeekSchedule,
|
||||||
personalDrawerTitle,
|
personalDrawerTitle,
|
||||||
personalScheduleForm,
|
personalScheduleForm,
|
||||||
resetFilters,
|
resetFilters,
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ const {
|
|||||||
handleCopyCheckAll,
|
handleCopyCheckAll,
|
||||||
handleCopySubmit,
|
handleCopySubmit,
|
||||||
handleDeleteStaff,
|
handleDeleteStaff,
|
||||||
|
handleOpenWeekSchedule,
|
||||||
handleOpenPersonalSchedule,
|
handleOpenPersonalSchedule,
|
||||||
handlePageChange,
|
handlePageChange,
|
||||||
handleSavePersonalSchedule,
|
handleSavePersonalSchedule,
|
||||||
@@ -56,17 +57,18 @@ const {
|
|||||||
isPageLoading,
|
isPageLoading,
|
||||||
isPersonalDrawerOpen,
|
isPersonalDrawerOpen,
|
||||||
isPersonalSaving,
|
isPersonalSaving,
|
||||||
|
isScheduleConfigured,
|
||||||
isScheduleDisabled,
|
isScheduleDisabled,
|
||||||
isStaffDrawerOpen,
|
isStaffDrawerOpen,
|
||||||
isStaffSaving,
|
isStaffSaving,
|
||||||
isStoreLoading,
|
isStoreLoading,
|
||||||
isTemplateSaving,
|
isTemplateSaving,
|
||||||
|
isTemplateConfigured,
|
||||||
isWeekDrawerOpen,
|
isWeekDrawerOpen,
|
||||||
isWeekSaving,
|
isWeekSaving,
|
||||||
maskPhone,
|
maskPhone,
|
||||||
openCopyModal,
|
openCopyModal,
|
||||||
openStaffDrawer,
|
openStaffDrawer,
|
||||||
openWeekScheduleDrawer,
|
|
||||||
personalDrawerTitle,
|
personalDrawerTitle,
|
||||||
personalScheduleForm,
|
personalScheduleForm,
|
||||||
resetFilters,
|
resetFilters,
|
||||||
@@ -164,6 +166,7 @@ const {
|
|||||||
<ShiftTemplateCard
|
<ShiftTemplateCard
|
||||||
:templates="templates"
|
:templates="templates"
|
||||||
:is-saving="isTemplateSaving"
|
:is-saving="isTemplateSaving"
|
||||||
|
:is-template-configured="isTemplateConfigured"
|
||||||
:on-set-template-time="setTemplateTime"
|
:on-set-template-time="setTemplateTime"
|
||||||
@save="handleSaveTemplates"
|
@save="handleSaveTemplates"
|
||||||
@reset="resetTemplates"
|
@reset="resetTemplates"
|
||||||
@@ -175,8 +178,10 @@ const {
|
|||||||
:get-shift-class="getShiftClass"
|
:get-shift-class="getShiftClass"
|
||||||
:get-shift-label="getShiftLabel"
|
:get-shift-label="getShiftLabel"
|
||||||
:get-shift-time-text="formatShiftTimeText"
|
:get-shift-time-text="formatShiftTimeText"
|
||||||
|
:is-template-configured="isTemplateConfigured"
|
||||||
|
:is-schedule-configured="isScheduleConfigured"
|
||||||
:role-label-resolver="getRoleLabel"
|
:role-label-resolver="getRoleLabel"
|
||||||
@edit-week="openWeekScheduleDrawer"
|
@edit-week="handleOpenWeekSchedule"
|
||||||
/>
|
/>
|
||||||
</Spin>
|
</Spin>
|
||||||
</template>
|
</template>
|
||||||
@@ -223,6 +228,7 @@ const {
|
|||||||
:get-shift-class="getShiftClass"
|
:get-shift-class="getShiftClass"
|
||||||
:get-shift-label="getShiftLabel"
|
:get-shift-label="getShiftLabel"
|
||||||
:get-shift-time-text="formatShiftTimeText"
|
:get-shift-time-text="formatShiftTimeText"
|
||||||
|
:is-template-configured="isTemplateConfigured"
|
||||||
:role-label-resolver="getRoleLabel"
|
:role-label-resolver="getRoleLabel"
|
||||||
@update:open="setWeekDrawerOpen"
|
@update:open="setWeekDrawerOpen"
|
||||||
@cycle-shift="cycleWeekShift"
|
@cycle-shift="cycleWeekShift"
|
||||||
|
|||||||
@@ -241,6 +241,12 @@
|
|||||||
gap: 6px;
|
gap: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.staff-schedule-empty {
|
||||||
|
margin-left: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #8c8c8c;
|
||||||
|
}
|
||||||
|
|
||||||
.staff-shift-pill {
|
.staff-shift-pill {
|
||||||
min-width: 46px;
|
min-width: 46px;
|
||||||
padding: 4px 10px;
|
padding: 4px 10px;
|
||||||
|
|||||||
@@ -12,6 +12,14 @@
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.schedule-board-tip {
|
||||||
|
padding: 10px 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #ad6800;
|
||||||
|
background: #fffbe6;
|
||||||
|
border-bottom: 1px solid #ffe58f;
|
||||||
|
}
|
||||||
|
|
||||||
.schedule-board-table,
|
.schedule-board-table,
|
||||||
.staff-week-table {
|
.staff-week-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -67,6 +75,13 @@
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.schedule-cell.shift-unconfigured,
|
||||||
|
.staff-week-cell.shift-unconfigured {
|
||||||
|
color: #8c8c8c;
|
||||||
|
background: #fafafa;
|
||||||
|
border-color: #d9d9d9;
|
||||||
|
}
|
||||||
|
|
||||||
.schedule-cell-time {
|
.schedule-cell-time {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
/* 文件职责:班次模板区样式。 */
|
/* 文件职责:班次模板区样式。 */
|
||||||
.page-store-staff {
|
.page-store-staff {
|
||||||
|
.template-guide {
|
||||||
|
padding: 10px 12px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #ad6800;
|
||||||
|
background: #fffbe6;
|
||||||
|
border: 1px solid #ffe58f;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.template-list {
|
.template-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
Reference in New Issue
Block a user