123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765 |
- <template>
- <el-dialog
- v-model="dialogVisible"
- :title="getDialogTitle"
- width="60%"
- :close-on-click-modal="false"
- @closed="handleClosed"
- custom-class="interview-notification-dialog"
- >
- <div class="notification">
- <div class="notification-container">
- <!-- 消息头部 -->
- <div class="message-header">
- <div class="avatar">
- <img src="/src/assets/qcLogo.png" alt="公司头像" />
- </div>
- <div class="contact-info">
- <span class="phone">+10688028...</span>
- <div class="contact-actions">
- <el-button link type="primary" size="small">
- <el-icon><Phone /></el-icon>
- </el-button>
- <el-button link type="primary" size="small">
- <el-icon><MoreFilled /></el-icon>
- </el-button>
- </div>
- </div>
- </div>
- <!-- 消息内容 -->
- <div class="message-content">
- <!-- 根据当前模板显示内容 -->
- <div v-if="currentTemplate" v-html="currentTemplate.template"></div>
- <!-- 当没有模板时显示的默认内容 -->
- <div v-else class="no-template">
- 暂无可用的短信模板
- </div>
- </div>
- <div class="notification-right-footer-action">
- <el-icon><Plus /></el-icon>
- <div class="input"></div>
- <el-icon><Plus /></el-icon>
- <el-icon><Upload /></el-icon>
- </div>
- </div>
-
- <!-- 右侧预览区域 -->
- <div class="notification-right">
- <div class="notification-right-header">
- <div v-if="currentTemplate" class="template-info">
- <h3>模板信息</h3>
- <p>模板ID: {{ currentTemplate.id }}</p>
- <p>模板类型: {{ getTemplateTypeName }}</p>
- </div>
- </div>
- <div class="notification-right-content">
- <div v-if="currentTemplate" v-html="currentTemplate.template"></div>
- </div>
- </div>
- </div>
- <template #footer>
- <div class="dialog-footer">
- <el-button @click="dialogVisible = false">关闭</el-button>
- <el-button type="primary" @click="handleSend" :loading="loading">发送通知</el-button>
- </div>
- </template>
- </el-dialog>
- <!-- 编辑表单对话框 -->
- <el-dialog
- v-model="editDialogVisible"
- title="编辑面试邀请"
- width="600px"
- :close-on-click-modal="false"
- >
- <el-form :model="editForm" ref="editFormRef" :rules="editRules" label-width="100px">
- <el-form-item label="手机号" prop="phone">
- <el-input v-model="editForm.phone" placeholder="请输入手机号" />
- </el-form-item>
-
- <el-form-item label="候选人姓名" prop="candidateName">
- <el-input v-model="editForm.candidateName" placeholder="请输入候选人姓名" />
- </el-form-item>
-
- <el-form-item label="公司名称" prop="companyName">
- <el-input v-model="editForm.companyName" placeholder="请输入公司名称" />
- </el-form-item>
-
- <el-form-item label="面试职位" prop="position">
- <el-input v-model="editForm.position" placeholder="请输入面试职位" />
- </el-form-item>
-
- <el-form-item label="面试日期" prop="interviewDate">
- <el-date-picker
- v-model="editForm.interviewDate"
- type="date"
- placeholder="选择面试日期"
- format="YYYY-MM-DD"
- value-format="YYYY-MM-DD"
- />
- </el-form-item>
-
- <el-form-item label="面试时间" prop="interviewTime">
- <el-time-picker
- v-model="editForm.interviewTime"
- format="HH:mm"
- value-format="HH:mm"
- placeholder="选择面试时间"
- />
- </el-form-item>
- <el-form-item label="面试码" prop="interviewCode">
- <el-input v-model="editForm.interviewCode" placeholder="请输入面试码" />
- </el-form-item>
-
- <el-form-item label="面试方式" prop="interviewMethod">
- <el-select v-model="editForm.interviewMethod" placeholder="请选择面试方式">
- <el-option label="线上面试" value="online" />
- <el-option label="线下面试" value="offline" />
- </el-select>
- </el-form-item>
-
- <el-form-item label="面试链接" prop="interviewLink" v-if="editForm.interviewMethod === 'online'">
- <el-input v-model="editForm.interviewLink" placeholder="请输入面试链接" />
- </el-form-item>
-
- <el-form-item label="面试地址" prop="interviewAddress" v-if="editForm.interviewMethod === 'offline'">
- <el-input v-model="editForm.interviewAddress" type="textarea" placeholder="请输入面试地址" />
- </el-form-item>
- </el-form>
-
- <template #footer>
- <el-button @click="editDialogVisible = false">取消</el-button>
- <el-button type="primary" @click="handleSaveEdit">保存</el-button>
- </template>
- </el-dialog>
- </template>
- <script lang="ts" setup>
- import { ref, reactive, onMounted, computed } from 'vue';
- import { ElMessage, FormInstance } from 'element-plus';
- import { Phone, MoreFilled, Edit } from '@element-plus/icons-vue';
- import { updateBatchStatus } from '../api';
- import axios from 'axios';
- import { Session } from '/@/utils/storage';
- const dialogVisible = ref(false);
- const editDialogVisible = ref(false);
- const loading = ref(false);
- const formRef = ref<FormInstance>();
- const editFormRef = ref<FormInstance>();
- // 添加当前状态的响应式变量
- const currentStatus = ref(0);
- // 添加模板相关的响应式变量
- const templates = ref([]);
- const currentTemplate = ref(null);
- // 原有表单数据
- const form = reactive({
- new_status: undefined as number | undefined,
- note: '',
- application_ids: [] as number[]
- });
- // 编辑表单数据
- const editForm = reactive({
- phone: '', // 手机号
- candidateName: '', // 候选人姓名
- companyName: '', // 公司名称
- position: '', // 职位名称
- interviewDate: '', // 面试日期
- interviewTime: '', // 面试时间
- interviewCode: '', // 面试码
- interviewMethod: 'online',
- interviewLink: '',
- interviewAddress: ''
- });
- const rules = {
- new_status: [{ required: true, message: '请选择新状态', trigger: 'change' }]
- };
- const editRules = {
- phone: [
- { required: true, message: '请输入手机号', trigger: 'blur' },
- { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号格式', trigger: 'blur' }
- ],
- candidateName: [
- { required: true, message: '请输入候选人姓名', trigger: 'blur' }
- ],
- companyName: [
- { required: true, message: '请输入公司名称', trigger: 'blur' }
- ],
- position: [
- { required: true, message: '请输入职位名称', trigger: 'blur' }
- ],
- interviewDate: [
- { required: true, message: '请选择面试日期', trigger: 'change' }
- ],
- interviewTime: [
- { required: true, message: '请选择面试时间', trigger: 'change' }
- ],
- interviewCode: [
- { required: true, message: '请输入面试码', trigger: 'blur' }
- ],
- interviewMethod: [
- { required: true, message: '请选择面试方式', trigger: 'change' }
- ]
- };
- const emit = defineEmits(['success']);
- const props = defineProps({
- crudExpose: {
- type: Object,
- required: true
- }
- });
- // 获取短信模板
- const fetchSmsTemplates = async () => {
- try {
- const response = await axios.get(`${import.meta.env.VITE_API_URL}/api/system/sms/templates/`,
- {
- headers: {
- 'Authorization': `JWT ${Session.get('token')}`
- }
- }
- );
- if (response.data.code === 2000) {
- console.log(response.data)
- templates.value = response.data.data.templates;
- updateCurrentTemplate();
- } else {
- ElMessage.error('获取短信模板失败');
- }
- } catch (error) {
- console.error('获取短信模板失败:', error);
- ElMessage.error('获取短信模板失败');
- }
- };
- // 更新当前模板
- const updateCurrentTemplate = () => {
- if (!templates.value.length) return;
-
- let templateIndex;
- switch (currentStatus.value) {
- case 0:
- templateIndex = 1; // 对应序号1的模板
- break;
- case 3:
- templateIndex = 2; // 对应序号2的模板
- break;
- case 4:
- templateIndex = 3; // 对应序号3的模板
- break;
- default:
- templateIndex = 0;
- }
-
- currentTemplate.value = templates.value[templateIndex];
- };
- // 在组件挂载时获取模板
- onMounted(() => {
- fetchSmsTemplates();
- });
- // 修改open函数
- const open = async (selection: any[]) => {
- dialogVisible.value = true;
- form.application_ids = selection.map(item => item.id);
-
- // 设置当前状态(使用第一个选中项的状态)
- currentStatus.value = selection[0]?.status || 0;
-
- // 获取并更新模板
- await fetchSmsTemplates();
-
- // 初始化编辑表单数据,使用选中项的第一条数据
- const firstItem = selection[0];
- if (firstItem) {
- editForm.phone = firstItem.applicant_phone || ''; // 使用申请人电话
- editForm.candidateName = firstItem.applicant_name || ''; // 使用申请人姓名
- editForm.companyName = '上海敏龙智创科技发展有限公司'; // 固定公司名称
- editForm.position = firstItem.position_title || ''; // 使用职位名称
- editForm.interviewDate = ''; // 面试日期需要用户选择
- editForm.interviewTime = ''; // 面试时间需要用户选择
- editForm.interviewCode = generateInterviewCode(); // 生成面试码
- editForm.interviewMethod = 'online';
- editForm.interviewLink = '';
- editForm.interviewAddress = '';
- }
- };
- // 添加生成面试码的函数
- const generateInterviewCode = () => {
- const prefix = 'ML';
- const number = Math.floor(Math.random() * 999).toString().padStart(3, '0');
- return `${prefix}${number}`;
- };
- // 处理编辑
- const handleEdit = () => {
- editDialogVisible.value = true;
- };
- // 保存编辑
- const handleSaveEdit = async () => {
- if (!editFormRef.value) return;
-
- await editFormRef.value.validate((valid) => {
- if (valid) {
- ElMessage.success('编辑保存成功');
- editDialogVisible.value = false;
- }
- });
- };
- // 添加日期格式化函数
- const formatDate = (date: string) => {
- if (!date) return '';
- const d = new Date(date);
- return `${d.getFullYear()}年${d.getMonth() + 1}月${d.getDate()}日`;
- };
- // 添加时间格式化函数
- const formatTime = (time: string) => {
- if (!time) return '';
- const [hours, minutes] = time.split(':');
- const hour = parseInt(hours);
- const period = hour >= 12 ? '下午' : '上午';
- const formattedHour = hour > 12 ? hour - 12 : hour;
- return `${period}${formattedHour}:${minutes}`;
- };
- // 根据状态获取对应的API接口
- const getApiEndpoint = (status: number) => {
- const baseUrl = import.meta.env.VITE_API_URL;
- switch (status) {
- case 0:
- return `${baseUrl}/api/system/sms/interview-notification/`;
- case 3:
- return `${baseUrl}/api/system/sms/interview-passed/`;
- case 4:
- return `${baseUrl}/api/system/sms/interview-failed/`;
- default:
- throw new Error('不支持的状态类型');
- }
- };
- // 根据状态获取对应的请求数据
- const getRequestData = (status: number) => {
- const baseData = {
- application_ids: form.application_ids,
- tenant_id: Session.get('tenant_id'),
- phone: editForm.phone,
- name: editForm.candidateName,
- company: editForm.companyName,
- job: editForm.position,
- date: formatDate(editForm.interviewDate) || '2025年7月30日',
- time: formatTime(editForm.interviewTime) || '10:00',
- code: editForm.interviewCode
- };
- switch (status) {
- case 0:
- return {
- ...baseData,
- interview_method: editForm.interviewMethod,
- interview_link: editForm.interviewLink,
- interview_address: editForm.interviewAddress
- };
- case 3:
- return {
- ...baseData,
- onboard_time: editForm.interviewTime,
- position: editForm.position
- };
- case 4:
- return {
- ...baseData,
- reason: form.note
- };
- default:
- return baseData;
- }
- };
- // 发送通知
- const handleSend = async () => {
- loading.value = true;
- try {
- // 获取对应状态的API接口
- const apiEndpoint = getApiEndpoint(currentStatus.value);
-
- // 获取对应状态的请求数据
- const requestData = getRequestData(currentStatus.value);
-
- // 发送请求
- const response = await axios.post(apiEndpoint, requestData,
- {
- headers: {
- 'Authorization': `JWT ${Session.get('token')}`
- }
- }
- );
-
- if (response.data.code === 2000) {
- ElMessage.success('通知发送成功');
- dialogVisible.value = false;
- emit('success');
- props.crudExpose.doRefresh();
- } else {
- ElMessage.error(response.data.msg || '发送失败');
- }
- } catch (error: any) {
- console.error('发送通知失败:', error);
- ElMessage.error(error.message || '发送失败,请重试');
- } finally {
- loading.value = false;
- }
- };
- // 提交表单(保留原有逻辑)
- const handleSubmit = async () => {
- if (!formRef.value) return;
-
- await formRef.value.validate(async (valid) => {
- if (!valid) return;
-
- loading.value = true;
- try {
- const data = {
- application_ids: form.application_ids,
- new_status: form.new_status as number,
- note: form.note,
- tenant_id:Session.get('tenant_id')
- };
-
- const res = await updateBatchStatus(data);
-
- if (res.code === 2000) {
- ElMessage.success('批量修改状态成功');
- dialogVisible.value = false;
- emit('success');
- props.crudExpose.doRefresh();
- } else {
- ElMessage.error(res.msg || '操作失败');
- }
- } catch (error) {
- console.error('批量修改状态失败:', error);
- ElMessage.error('操作失败,请重试');
- } finally {
- loading.value = false;
- }
- });
- };
- // 关闭对话框时重置表单
- const handleClosed = () => {
- if (formRef.value) {
- formRef.value.resetFields();
- }
- };
- // 暴露方法给父组件
- defineExpose({
- open
- });
- // 添加计算属性
- const getDialogTitle = computed(() => {
- switch (currentStatus.value) {
- case 0:
- return '面试邀请通知';
- case 2:
- return '面试通过通知';
- case 4:
- return '面试未通过通知';
- default:
- return '短信通知';
- }
- });
- const getTemplateTypeName = computed(() => {
- switch (currentStatus.value) {
- case 0:
- return '面试邀请模板';
- case 2:
- return '录用通知模板';
- case 4:
- return '婉拒通知模板';
- default:
- return '通用模板';
- }
- });
- </script>
- <style scoped>
- .notification {
- display: flex;
-
- }
- .notification-container {
- position: relative;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
- background: #f7f7f7;
- border-radius: 8px;
- padding: 20px;
- min-height: 400px;
- width: 45%;
- border: 1px solid #e4e7ed;
- }
- .notification-right{
- width: 100%;
- background: #f7f7f7;
- font-size: 12px;
- color: #595959;
- border-radius: 8px;
- margin-left: 20px;
- padding: 20px;
- min-height: 400px;
- }
- .notification-right-header{
- margin: 30px 0;
-
- }
- .notification-right-content-title{
- color: #595959;
- font-weight: 600;
- }
- .notification-right-content{
- padding:0 10px;
- margin: 50px 0;
- line-height: 1.6;
-
- }
- .notification-right-footer{
-
- }
- .message-header {
- display: flex;
- align-items: center;
- padding: 5px 0;
- border-bottom: 1px solid #e4e7ed;
- margin-bottom: 15px;
- }
- .avatar {
- width: 30px;
- height: 30px;
- border-radius: 4px;
- overflow: hidden;
- margin-right: 10px;
- }
- .avatar img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- .contact-info {
- flex: 1;
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
- .phone {
- font-size: 16px;
- font-weight: 500;
- }
- .contact-actions {
- display: flex;
- gap: 10px;
- }
- .message-content {
- background: #fff;
- border-radius: 8px;
- padding: 15px;
- margin-bottom: 55px;
- position: relative;
- /* margin-left: 50px; */
- box-shadow: 0 2px 6px rgba(0,0,0,0.1);
- }
- .notification-right-footer-action{
- position: absolute;
- bottom: -10px;
- left: 0;
- right: 0;
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-top: 10px;
- margin-bottom: 10px;
- padding: 10px;
- background: #fff;
- border-top-left-radius: 15px;
- border-top-right-radius: 15px;
- }
- .input{
- width: 70%;
- height: 20px;
- border-radius: 10px;
- background: #f2f2f2;
- }
- /* .message-content::before {
- content: '';
- position: absolute;
- left: -8px;
- top: 15px;
- width: 0;
- height: 0;
- border-top: 8px solid transparent;
- border-bottom: 8px solid transparent;
-
- } */
- .invitation-title {
- font-size: 12px;
- font-weight: 600;
- padding-left: 12px;
- margin-bottom: 10px;
- color: #333;
- }
- .invitation-content {
- font-size: 12px;
- line-height: 1.6;
- color: #666;
- }
- .invitation-content p {
- margin-bottom: 10px;
- }
- .highlight {
- color: #595959;
- font-weight: 600;
- }
- .company-name {
- color: #595959;
- font-weight: 500;
- }
- .interview-details {
- margin: 10px 0;
- padding: 10px;
- background: rgba(255,255,255,0.3);
- border-radius: 4px;
- border-left: 3px solid #fff;
- }
- .detail-item {
- margin-bottom: 8px;
- font-size: 12px;
- }
- .detail-item strong {
- color: #333;
- font-weight: 600;
- }
- .editable-field {
- color: #595959;
- background: #ecf5ff;
- padding: 2px 6px;
- border-radius: 3px;
- font-size: 12px;
- }
- .notice-section {
- margin: 10px 0;
- padding: 12px;
- background: rgba(255,255,255,0.2);
- border-radius: 4px;
- border: 1px solid rgba(255,255,255,0.4);
- }
- .notice-title {
- font-weight: 600;
- color: #595959;
- margin-bottom: 8px;
- }
- .notice-content {
- font-size: 13px;
- color: #666;
- }
- .notice-content p {
- margin-bottom: 5px;
- }
- .company-signature {
- margin-top: 15px;
- text-align: left;
- padding-left: 12px;
- font-size: 13px;
- color: #999;
- }
- .date {
- margin-top: 3px;
- }
- .edit-section {
- text-align: left;
- margin-top: 15px;
- padding-top: 10px;
- }
- .dialog-footer {
- text-align: right;
- }
- :deep(.interview-notification-dialog) {
- border-radius: 8px;
- }
- :deep(.interview-notification-dialog .el-dialog__header) {
- background: #f8f9fa;
- border-radius: 8px 8px 0 0;
- }
- :deep(.interview-notification-dialog .el-dialog__body) {
- padding: 20px;
- }
- .template-info {
- margin-bottom: 20px;
- padding: 10px;
- background: #f5f7fa;
- border-radius: 4px;
- }
- .template-info h3 {
- margin-bottom: 10px;
- color: #303133;
- font-size: 16px;
- }
- .template-info p {
- margin: 5px 0;
- color: #606266;
- font-size: 14px;
- }
- .no-template {
- text-align: center;
- padding: 20px;
- color: #909399;
- font-size: 14px;
- }
- </style>
|