Skip to content

Commit f3b2c53

Browse files
Merge pull request #99 from yuki-sp/main
feat:在转移阶段提供直达发送通知的按钮and支持一次性添加多个日程,精确到分钟不再是秒,提供默认面试时长和休息时长选择
2 parents 21fa045 + ee41dd3 commit f3b2c53

3 files changed

Lines changed: 127 additions & 21 deletions

File tree

src/locale/zh-CN/common.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,4 +157,11 @@ export default {
157157
'报名开始时间必须等于招新开始时间',
158158
'common.createRec.signupEndTimeBeforeRecEndTime':
159159
'报名结束时间必须早于招新结束时间',
160+
161+
'common.interview.duration':
162+
'面试时长 (分钟)',
163+
'common.interview.rest':
164+
'休息时长 (分钟)',
165+
'common.interview.error.incompleteInfo':
166+
'日期或时间未填写完整',
160167
};

src/views/interview/management/add-date-modal.vue

Lines changed: 100 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,63 @@
2626
</a-select>
2727
</div>
2828

29-
<div class="flex justify-between mb-2 sm:flex-row flex-col">
30-
<div class="sm:w-1/2 w-4/5 sm:mb-0 mb-4">
29+
<div class="flex gap-4 mb-6">
30+
<div class="flex-1">
31+
<div class="font-semibold mb-2">
32+
{{ $t('common.interview.duration') }}
33+
</div>
34+
<a-input-number v-model="duration" :min="1" />
35+
</div>
36+
<div class="flex-1">
37+
<div class="font-semibold mb-2">
38+
{{ $t('common.interview.rest') }}
39+
</div>
40+
<a-input-number v-model="rest" :min="0" />
41+
</div>
42+
</div>
43+
44+
<div class="flex gap-4">
45+
<div class="w-5/12">
3146
<div class="font-semibold mb-2">
3247
{{ $t('common.date') }}<span class="text-blue-600">*</span>
3348
</div>
34-
<a-date-picker v-model="interviewDate" class="w-5/6" />
49+
<a-date-picker v-model="interviewDate" class="w-full" />
3550
</div>
36-
<div class="sm:w-1/2 w-4/5 pr-2">
51+
52+
<div class="flex-1">
3753
<div class="font-semibold mb-2">
3854
{{ $t('common.time') }}<span class="text-blue-600">*</span>
3955
</div>
40-
<a-time-picker
41-
v-model="interviewTime"
42-
type="time-range"
43-
class="w-full"
44-
/>
56+
<div
57+
v-for="(_, index) in interviewTimes"
58+
:key="index"
59+
class="flex items-center mb-2 gap-2"
60+
>
61+
<a-time-picker
62+
v-model="interviewTimes[index]"
63+
type="time-range"
64+
format="HH:mm"
65+
class="flex-1"
66+
/>
67+
<a-button
68+
type="text"
69+
status="danger"
70+
class="px-1"
71+
@click="removeTimeRange(index)"
72+
>
73+
<template #icon><icon-delete /></template>
74+
</a-button>
75+
<a-button
76+
v-if="index === interviewTimes.length - 1"
77+
type="text"
78+
class="px-1"
79+
@click="addTimeRange"
80+
>
81+
<template #icon><icon-plus /></template>
82+
</a-button>
83+
<!-- 占位,保证对齐 -->
84+
<div v-else class="w-8"></div>
85+
</div>
4586
</div>
4687
</div>
4788
</div>
@@ -54,6 +95,7 @@ import { Group, Period, PeriodDefineHour } from '@/constants/team';
5495
import useRecruitmentStore from '@/store/modules/recruitment';
5596
import { Message } from '@arco-design/web-vue';
5697
import { useI18n } from 'vue-i18n';
98+
import { IconPlus, IconDelete } from '@arco-design/web-vue/es/icon';
5799
58100
const { t } = useI18n();
59101
@@ -72,9 +114,40 @@ const props = defineProps({
72114
73115
const currentGroup = ref<Group>(props.currentGroupStart);
74116
const interviewDate = ref<string>('');
75-
const interviewTime = ref<string[]>([]);
117+
const interviewTimes = ref<string[][]>([[]]);
118+
const duration = ref(30);
119+
const rest = ref(10);
76120
const recStore = useRecruitmentStore();
77121
122+
const addTimeRange = () => {
123+
const lastRange = interviewTimes.value[interviewTimes.value.length - 1];
124+
if (lastRange && lastRange[1]) {
125+
const [h, m, s] = lastRange[1].split(':').map(Number);
126+
const date = new Date();
127+
date.setHours(h, m, s || 0);
128+
129+
const nextStart = new Date(date.getTime() + rest.value * 60000);
130+
const nextEnd = new Date(nextStart.getTime() + duration.value * 60000);
131+
132+
const format = (d: Date) =>
133+
`${String(d.getHours()).padStart(2, '0')}:${String(
134+
d.getMinutes(),
135+
).padStart(2, '0')}`;
136+
137+
interviewTimes.value.push([format(nextStart), format(nextEnd)]);
138+
} else {
139+
interviewTimes.value.push([]);
140+
}
141+
};
142+
143+
const removeTimeRange = (index: number) => {
144+
if (interviewTimes.value.length > 1) {
145+
interviewTimes.value.splice(index, 1);
146+
} else {
147+
interviewTimes.value = [[]];
148+
}
149+
};
150+
78151
const groupOptions = Object.entries(Group).map(([label, value]) => ({
79152
label,
80153
value,
@@ -93,22 +166,28 @@ const calcPeriod = (time: Date): Period => {
93166
};
94167
95168
const handleCreate = async () => {
96-
const [startTime, endTime] = interviewTime.value;
97-
98-
// 创建日期和时间的ISO格式
99-
const startDate = new Date(`${interviewDate.value}T${startTime}`);
100-
const start = startDate.toISOString();
101-
const end = new Date(`${interviewDate.value}T${endTime}`).toISOString();
169+
if (
170+
!interviewDate.value ||
171+
interviewTimes.value.some((time) => time.length < 2)
172+
) {
173+
Message.warning(t('common.interview.error.incompleteInfo'));
174+
return;
175+
}
102176
103-
visible.value = false;
104-
const res = await recStore.createInterview(currentGroup.value, [
105-
{
177+
const interviews = interviewTimes.value.map(([startTime, endTime]) => {
178+
const startDate = new Date(`${interviewDate.value}T${startTime}`);
179+
const start = startDate.toISOString();
180+
const end = new Date(`${interviewDate.value}T${endTime}`).toISOString();
181+
return {
106182
date: new Date(interviewDate.value).toISOString(),
107183
period: calcPeriod(startDate),
108184
start,
109185
end,
110-
},
111-
]);
186+
};
187+
});
188+
189+
visible.value = false;
190+
const res = await recStore.createInterview(currentGroup.value, interviews);
112191
if (res) {
113192
recStore.refresh();
114193
Message.success(t('common.result.addInterviewSuccess'));

src/views/overview/candidate/components/edit-buttons.vue

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,26 @@
8080
</a-form-item>
8181
</a-form>
8282
</div>
83+
<template #footer>
84+
<div class="flex justify-end gap-2">
85+
<a-button @click="showSwitchStage = false">
86+
{{ $t('common.operation.cancel') }}
87+
</a-button>
88+
<a-button type="primary" @click="handleSwitchStage">
89+
{{ $t('common.operation.confirm') }}
90+
</a-button>
91+
<a-button
92+
type="outline"
93+
class="max-sm:hidden"
94+
:size="buttonSize"
95+
:disabled="props.curStep >= recruitSteps.length || !candidates.length"
96+
@click="openNotify"
97+
>
98+
<template #icon> <icon-plus /> </template>
99+
{{ $t('common.operation.sendNotification') }}
100+
</a-button>
101+
</div>
102+
</template>
83103
</a-modal>
84104
<a-modal
85105
v-model:visible="showTerminate"

0 commit comments

Comments
 (0)