Files
smartDriveEEFront/src/views/lb-business/assessment-apply/index.vue
2026-05-15 23:36:59 +08:00

784 lines
28 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="app-container">
<el-card class="filter-container" shadow="never">
<el-form :model="queryParams" :inline="true" label-width="100px">
<el-form-item label="申请人">
<el-input v-model="queryParams.applicantName" clearable placeholder="请输入申请人" style="width: 180px" />
</el-form-item>
<el-form-item label="申请人电话">
<el-input v-model="queryParams.applicantPhone" clearable placeholder="请输入申请人电话" style="width: 180px" />
</el-form-item>
<el-form-item label="同事姓名">
<el-input v-model="queryParams.colleagueName" clearable placeholder="请输入同事姓名" style="width: 180px" />
</el-form-item>
<el-form-item label="团队长姓名">
<el-input v-model="queryParams.teamLeaderName" clearable placeholder="请输入团队长姓名" style="width: 180px" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
<el-button icon="el-icon-delete" @click="resetQuery">清空</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="action-container" shadow="never">
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新增</el-button>
<el-button type="success" icon="el-icon-download" :loading="exportLoading" @click="openExportDialog">导出</el-button>
</el-card>
<el-card class="table-container" shadow="never">
<el-table v-loading="loading" :data="list" style="width: 100%">
<el-table-column label="申请人姓名" prop="applicantName" min-width="108" />
<el-table-column label="申请人电话" prop="applicantPhone" min-width="126" />
<el-table-column label="同事姓名" prop="colleagueName" min-width="108" />
<el-table-column label="同事电话" prop="colleaguePhone" min-width="126" />
<el-table-column label="团长姓名" prop="teamLeaderName" min-width="117" />
<el-table-column label="双收益团队长" prop="teamBigLeaderName" min-width="94" />
<el-table-column label="申请日期时间" prop="applicationDatetime" min-width="153">
<template slot-scope="scope">
{{ formatDateTime(scope.row.applicationDatetime, false) }}
</template>
</el-table-column>
<el-table-column label="评估老师" prop="assessmentTeacherName" min-width="108" />
<el-table-column label="主持人" prop="moderatorName" min-width="108" />
<el-table-column label="会议号" prop="meetingNumber" min-width="126" />
<el-table-column label="创建时间" prop="createdAt" min-width="153">
<template slot-scope="scope">
{{ formatDateTime(scope.row.createdAt) }}
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="300">
<template slot-scope="scope">
<el-button
type="text"
:loading="notificationLoadingId === scope.row.id"
@click="handleGenerateNotification(scope.row)"
>生成通知</el-button>
<el-button
type="text"
:loading="dingTalkLoadingId === scope.row.id"
@click="handleSendToDingTalk(scope.row)"
>发到钉钉</el-button>
<el-button type="text" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="text" style="color:#F56C6C;" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
:current-page="queryParams.current"
:page-size="queryParams.size"
:page-sizes="[10, 20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</el-card>
<el-dialog title="导出申请评估" :visible.sync="exportDialogVisible" width="520px" @close="resetExportForm">
<el-form ref="exportFormRef" :model="exportForm" :rules="exportRules" label-width="140px">
<el-form-item label="申请日期" prop="applicationDateRange">
<el-date-picker
v-model="exportForm.applicationDateRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="yyyy-MM-dd"
style="width: 100%"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="exportDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="exportLoading" @click="submitExport">确定</el-button>
</div>
</el-dialog>
<el-dialog title="评估会议通知" :visible.sync="notificationDialogVisible" width="640px" @close="resetNotificationDialog">
<el-input
v-model="notificationText"
type="textarea"
:rows="14"
readonly
placeholder="通知文案将显示在此处"
/>
<div slot="footer" class="dialog-footer">
<el-button @click="notificationDialogVisible = false">关闭</el-button>
<el-button type="primary" icon="el-icon-copy-document" :disabled="!notificationText" @click="copyNotificationText">
复制
</el-button>
</div>
</el-dialog>
<el-dialog v-dialog-drag :title="dialogTitle" :visible.sync="dialogVisible" width="760px" @close="resetForm">
<el-form ref="dataForm" :model="form" :rules="formRules" label-width="120px">
<template v-if="!isEdit">
<el-form-item label="申请评估文本">
<el-input
v-model="assessmentApplyText"
type="textarea"
:rows="7"
placeholder="请输入评估申请的自然语言描述点击下方「AI解析」自动填充下方表单"
/>
</el-form-item>
<el-form-item label=" ">
<el-button type="primary" plain :loading="parseAiLoading" @click="handleParseFromAssessmentText">
AI解析
</el-button>
</el-form-item>
</template>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="申请人" prop="applicantName">
<el-input v-model="form.applicantName" placeholder="请输入申请人" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="申请人电话" prop="applicantPhone">
<el-input v-model="form.applicantPhone" placeholder="请输入申请人电话" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="年龄" prop="applicantAge">
<el-input-number v-model="form.applicantAge" :min="0" :controls="false" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="同事姓名" prop="colleagueName">
<el-input v-model="form.colleagueName" placeholder="请输入同事姓名" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="同事电话" prop="colleaguePhone">
<el-input v-model="form.colleaguePhone" placeholder="请输入同事电话" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="团队长姓名" prop="teamLeaderName">
<el-input v-model="form.teamLeaderName" placeholder="请输入团队长姓名" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="申请时间" prop="applicationDatetime">
<el-date-picker
v-model="form.applicationDatetime"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
clearable
placeholder="请选择申请时间"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="全收益团队长" prop="teamBigLeaderName">
<el-input v-model="form.teamBigLeaderName" placeholder="请输入全收益团队长" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="评估老师" prop="assessmentTeacherName">
<el-select
v-model="form.assessmentTeacherName"
clearable
filterable
placeholder="请选择评估老师"
style="width: 100%"
>
<el-option
v-for="item in assessmentTeacherOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="主持人" prop="moderatorName">
<el-select
v-model="form.moderatorName"
clearable
filterable
placeholder="请选择主持人"
style="width: 100%"
>
<el-option
v-for="item in moderatorOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="24">
<el-form-item label="腾讯会议号" prop="meetingNumber">
<el-input
v-model="form.meetingNumber"
clearable
placeholder="请输入腾讯会议号"
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="从业经历" prop="workExperience">
<el-input
v-model="form.workExperience"
type="textarea"
:rows="3"
placeholder="请输入从业经历"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
addLbAssessmentApply,
deleteLbAssessmentApply,
exportLbAssessmentApply,
getLbAssessmentApplyList,
getLbAssessmentApplyNotificationText,
parseAssessmentApplyFromText,
sendLbAssessmentApplyNotificationToDingTalk,
updateLbAssessmentApply
} from '@/api/lb-assessment-apply'
import { getBusinessHeaders } from '@/utils/business-headers'
export default {
name: 'LbAssessmentApply',
data() {
return {
loading: false,
submitLoading: false,
parseAiLoading: false,
exportLoading: false,
exportDialogVisible: false,
notificationDialogVisible: false,
notificationLoadingId: '',
dingTalkLoadingId: '',
notificationText: '',
assessmentApplyText: '',
list: [],
total: 0,
dialogVisible: false,
dialogTitle: '',
isEdit: false,
moderatorOptions: [
{ label: '胡东梅老师', value: '胡东梅老师' },
{ label: '张增利老师', value: '张增利老师' },
{ label: '陈泽章老师', value: '陈泽章老师' },
{ label: '覃淑恒老师', value: '覃淑恒老师' },
{ label: '刘连超老师', value: '刘连超老师' },
{ label: '刘兰心老师', value: '刘兰心老师' },
{ label: '李海民老师', value: '李海民老师' },
{ label: '闫丽娜老师', value: '闫丽娜老师' },
{ label: '张凌峰老师', value: '张凌峰老师' },
{ label: '张宝凤老师', value: '张宝凤老师' }
],
assessmentTeacherOptions: [
{ label: '张健老师', value: '张健老师' },
{ label: '秦理想老师', value: '秦理想老师' },
{ label: '刘兰兰老师', value: '刘兰兰老师' },
{ label: '张 锋老师', value: '张 锋老师' },
{ label: '汪伟乐老师', value: '汪伟乐老师' },
{ label: '李 佳老师', value: '李 佳老师' }
],
queryParams: {
current: 1,
size: 10,
applicantName: '',
applicantPhone: '',
colleagueName: '',
teamLeaderName: ''
},
form: {
id: '',
applicantName: '',
applicantPhone: '',
applicantAge: undefined,
workExperience: '',
colleagueName: '',
colleaguePhone: '',
teamLeaderName: '',
teamBigLeaderName: '',
applicationDatetime: '',
moderatorName: '',
assessmentTeacherName: '',
meetingNumber: ''
},
formRules: {
applicantName: [{ required: true, message: '请输入申请人', trigger: 'blur' }],
applicantPhone: [{ required: true, message: '请输入申请人电话', trigger: 'blur' }]
},
exportForm: {
applicationDateRange: []
},
exportRules: {
applicationDateRange: [{ required: true, message: '请选择申请日期范围', trigger: 'change' }]
}
}
},
created() {
this.fetchList()
},
methods: {
getTenantIdFromBusinessHeaders() {
const headers = getBusinessHeaders()
const value = headers && headers['X-Tenant-Id']
return value ? String(value).trim() : ''
},
handleParseFromAssessmentText() {
const text = (this.assessmentApplyText || '').trim()
if (!text) {
this.$message.warning('请先填写申请评估文本')
return
}
const tenantId = this.getTenantIdFromBusinessHeaders()
if (!tenantId) {
this.$message.error('未获取到租户ID请先登录或配置业务请求头中的租户')
return
}
this.parseAiLoading = true
parseAssessmentApplyFromText({ assessmentText: text, tenantId })
.then((res) => {
const payload = res && typeof res === 'object' ? res : {}
const entity = payload.data !== undefined && payload.data !== null && typeof payload.data === 'object'
? payload.data
: payload
this.applyParsedEntityToForm(entity)
this.$message.success((payload.message) || '解析成功,已填入表单')
})
.finally(() => {
this.parseAiLoading = false
})
},
normalizeApplicationDatetimeValue(v) {
if (v == null || v === '') return ''
const s = String(v).replace('T', ' ')
return s.length >= 19 ? s.slice(0, 19) : s
},
applyParsedEntityToForm(entity) {
if (!entity || typeof entity !== 'object') return
const keys = [
'applicantName',
'applicantPhone',
'applicantAge',
'workExperience',
'colleagueName',
'colleaguePhone',
'teamLeaderName',
'teamBigLeaderName',
'applicationDatetime',
'moderatorName',
'assessmentTeacherName',
'meetingNumber'
]
keys.forEach((k) => {
const v = entity[k]
if (v === undefined || v === null) return
if (k === 'applicantAge') {
if (v === '') {
this.form.applicantAge = undefined
return
}
const n = Number(v)
this.form.applicantAge = Number.isNaN(n) ? undefined : n
return
}
if (k === 'applicationDatetime') {
this.form.applicationDatetime = this.normalizeApplicationDatetimeValue(v)
return
}
this.form[k] = v
})
},
fetchList() {
this.loading = true
getLbAssessmentApplyList(this.buildListParams())
.then((res) => {
if (res && (res.code === 20000 || res.code === 200 || res.success === true)) {
this.list = Array.isArray(res.data) ? res.data : []
this.total = typeof res.total === 'number'
? res.total
: (res.page && typeof res.page.total === 'number' ? res.page.total : this.list.length)
} else {
this.list = []
this.total = 0
}
})
.catch(() => {
this.list = []
this.total = 0
})
.finally(() => {
this.loading = false
})
},
buildListParams() {
const params = {
current: this.queryParams.current,
size: this.queryParams.size,
applicantName: this.queryParams.applicantName || undefined,
applicantPhone: this.queryParams.applicantPhone || undefined,
colleagueName: this.queryParams.colleagueName || undefined,
teamLeaderName: this.queryParams.teamLeaderName || undefined
}
Object.keys(params).forEach((key) => {
if (params[key] === undefined || params[key] === '') delete params[key]
})
return params
},
handleQuery() {
this.queryParams.current = 1
this.fetchList()
},
resetQuery() {
this.queryParams = {
current: 1,
size: 10,
applicantName: '',
applicantPhone: '',
colleagueName: '',
teamLeaderName: ''
}
this.fetchList()
},
handleSizeChange(size) {
this.queryParams.size = size
this.fetchList()
},
handleCurrentChange(current) {
this.queryParams.current = current
this.fetchList()
},
handleAdd() {
this.dialogTitle = '新增评估申请'
this.isEdit = false
this.resetFormData()
this.dialogVisible = true
this.$nextTick(() => {
this.$refs.dataForm && this.$refs.dataForm.clearValidate()
})
},
openExportDialog() {
this.exportDialogVisible = true
this.$nextTick(() => {
if (this.$refs.exportFormRef) this.$refs.exportFormRef.clearValidate()
})
},
submitExport() {
this.$refs.exportFormRef.validate((valid) => {
if (!valid) return
const tenantId = this.getTenantIdFromBusinessHeaders()
if (!tenantId) {
this.$message.error('未获取到租户ID请先登录或配置业务请求头中的租户')
return
}
const range = this.exportForm.applicationDateRange || []
const startDate = range[0]
const endDate = range[1]
if (!startDate || !endDate) {
this.$message.warning('请选择申请日期范围')
return
}
const applicationDatetimeStart = `${startDate} 00:00:00`
const applicationDatetimeEnd = `${endDate} 23:59:59`
this.exportLoading = true
exportLbAssessmentApply({
tenantId,
applicationDatetimeStart,
applicationDatetimeEnd
})
.then((blob) => {
const safeBlob = blob instanceof Blob ? blob : new Blob([blob], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
const url = window.URL.createObjectURL(safeBlob)
const link = document.createElement('a')
link.href = url
link.download = `lbAssessmentApply_${startDate}_${endDate}_${tenantId}.xlsx`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
this.$message.success('导出成功')
this.exportDialogVisible = false
})
.finally(() => {
this.exportLoading = false
})
})
},
resetExportForm() {
if (this.$refs.exportFormRef) this.$refs.exportFormRef.resetFields()
this.exportForm = {
applicationDateRange: []
}
},
handleGenerateNotification(row) {
const tenantId = this.getTenantIdFromBusinessHeaders()
if (!tenantId) {
this.$message.error('未获取到租户ID请先登录或配置业务请求头中的租户')
return
}
if (!row || !row.id) {
this.$message.warning('记录ID无效无法生成通知')
return
}
this.notificationLoadingId = row.id
getLbAssessmentApplyNotificationText({ tenantId, id: row.id })
.then((res) => {
const payload = res && typeof res === 'object' ? res : {}
const text = this.extractNotificationText(payload)
if (!text) {
this.$message.warning(payload.message || '未获取到通知文案')
return
}
this.notificationText = text
this.notificationDialogVisible = true
})
.finally(() => {
this.notificationLoadingId = ''
})
},
handleSendToDingTalk(row) {
const tenantId = this.getTenantIdFromBusinessHeaders()
if (!tenantId) {
this.$message.error('未获取到租户ID请先登录或配置业务请求头中的租户')
return
}
if (!row || !row.id) {
this.$message.warning('记录ID无效无法发送到钉钉')
return
}
this.$confirm('确定将该条评估会议通知发送到钉钉群吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.dingTalkLoadingId = row.id
return sendLbAssessmentApplyNotificationToDingTalk({ tenantId, id: row.id })
}).then((res) => {
this.$message.success((res && res.message) || '已发送到钉钉')
}).catch(() => {}).finally(() => {
this.dingTalkLoadingId = ''
})
},
extractNotificationText(payload) {
if (!payload || typeof payload !== 'object') return ''
if (typeof payload.data === 'string' && payload.data.trim()) {
return payload.data.trim()
}
const sources = [payload]
if (payload.data && typeof payload.data === 'object') {
sources.push(payload.data)
}
const keys = ['text', 'notificationText', 'content']
for (let i = 0; i < sources.length; i++) {
const source = sources[i]
for (let j = 0; j < keys.length; j++) {
const v = source[keys[j]]
if (typeof v === 'string' && v.trim()) return v.trim()
}
}
return ''
},
resetNotificationDialog() {
this.notificationText = ''
},
copyNotificationText() {
const text = (this.notificationText || '').trim()
if (!text) {
this.$message.warning('没有可复制的通知文案')
return
}
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text).then(() => {
this.$message.success('已复制到剪贴板')
}).catch(() => {
this.fallbackCopyTextToClipboard(text)
})
} else {
this.fallbackCopyTextToClipboard(text)
}
},
fallbackCopyTextToClipboard(text) {
const textarea = document.createElement('textarea')
textarea.value = text
textarea.style.position = 'fixed'
textarea.style.left = '-9999px'
document.body.appendChild(textarea)
textarea.select()
try {
document.execCommand('copy')
this.$message.success('已复制到剪贴板')
} catch (e) {
this.$message.error('复制失败,请手动选择文本复制')
}
document.body.removeChild(textarea)
},
handleEdit(row) {
this.dialogTitle = '编辑评估申请'
this.isEdit = true
this.assessmentApplyText = ''
this.form = {
id: row.id || '',
applicantName: row.applicantName || '',
applicantPhone: row.applicantPhone || '',
applicantAge: row.applicantAge === null || row.applicantAge === undefined ? undefined : Number(row.applicantAge),
workExperience: row.workExperience || '',
colleagueName: row.colleagueName || '',
colleaguePhone: row.colleaguePhone || '',
teamLeaderName: row.teamLeaderName || '',
teamBigLeaderName: row.teamBigLeaderName || '',
applicationDatetime: row.applicationDatetime || '',
moderatorName: row.moderatorName || '',
assessmentTeacherName: row.assessmentTeacherName || '',
meetingNumber: row.meetingNumber || ''
}
this.dialogVisible = true
this.$nextTick(() => {
this.$refs.dataForm && this.$refs.dataForm.clearValidate()
})
},
handleDelete(row) {
this.$confirm('确定删除这条评估申请记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
return deleteLbAssessmentApply(row.id)
}).then((res) => {
this.$message.success((res && res.message) || '删除成功')
this.fetchList()
}).catch(() => {})
},
handleSubmit() {
this.$refs.dataForm.validate((valid) => {
if (!valid) return
const tenantId = this.getTenantIdFromBusinessHeaders()
if (!tenantId) {
this.$message.error('未获取到租户ID请先登录或配置业务请求头中的租户')
return
}
const payload = { ...this.buildPayload(), tenantId }
this.submitLoading = true
const req = this.isEdit ? updateLbAssessmentApply(payload) : addLbAssessmentApply(payload)
req.then((res) => {
this.$message.success((res && res.message) || (this.isEdit ? '更新成功' : '新增成功'))
this.dialogVisible = false
this.fetchList()
}).finally(() => {
this.submitLoading = false
})
})
},
buildPayload() {
const payload = {
applicantName: this.form.applicantName,
applicantPhone: this.form.applicantPhone,
applicantAge: this.form.applicantAge,
workExperience: this.form.workExperience || undefined,
colleagueName: this.form.colleagueName || undefined,
colleaguePhone: this.form.colleaguePhone || undefined,
teamLeaderName: this.form.teamLeaderName || undefined,
teamBigLeaderName: this.form.teamBigLeaderName || undefined,
applicationDatetime: this.form.applicationDatetime || undefined,
moderatorName: this.form.moderatorName || undefined,
assessmentTeacherName: this.form.assessmentTeacherName || undefined,
meetingNumber: this.form.meetingNumber || undefined
}
if (this.isEdit) payload.id = this.form.id
Object.keys(payload).forEach((key) => {
if (payload[key] === undefined || payload[key] === '') delete payload[key]
})
return payload
},
resetForm() {
this.$refs.dataForm && this.$refs.dataForm.resetFields()
this.resetFormData()
},
resetFormData() {
this.assessmentApplyText = ''
this.form = {
id: '',
applicantName: '',
applicantPhone: '',
applicantAge: undefined,
workExperience: '',
colleagueName: '',
colleaguePhone: '',
teamLeaderName: '',
teamBigLeaderName: '',
applicationDatetime: '',
moderatorName: '',
assessmentTeacherName: '',
meetingNumber: ''
}
},
formatDateTime(dateTime, withSeconds = true) {
if (!dateTime) return '-'
const date = new Date(String(dateTime).replace('T', ' '))
if (Number.isNaN(date.getTime())) return String(dateTime)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
if (!withSeconds) {
return `${year}-${month}-${day} ${hours}:${minutes}`
}
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
}
}
</script>
<style lang="scss" scoped>
.app-container {
.filter-container {
margin-bottom: 20px;
}
.action-container {
margin-bottom: 20px;
}
.table-container {
.el-pagination {
margin-top: 20px;
text-align: right;
}
}
}
</style>