全栈小学生 b2a30f1172 up admin
2026-03-20 15:56:15 +08:00

607 lines
18 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>
<el-dialog v-model="visible" :title="t('选择签名')" width="1200px" destroy-on-close :close-on-click-modal="false">
<el-alert type="warning" :closable="false" class="!mb-[10px]">
<template #default>
<p>签名数据的变更(新增 / 删除)需经过五分钟的生效周期,在此期间系统将完成数据同步与更新</p>
</template>
</el-alert>
<div class="flex justify-between items-center mb-[16px]">
<el-button type="primary" @click="addEvent">{{ t('添加短信签名') }}</el-button>
</div>
<div class="mb-[10px] flex items-center">
<el-checkbox v-model="toggleCheckbox" size="large" class="px-[14px]" @change="toggleChange" :indeterminate="isIndeterminate" />
<el-button @click="batchDeleteEvent" size="small">{{ t('批量删除') }}</el-button>
</div>
<el-table
:data="tableData.data"
size="large"
v-loading="tableData.loading"
ref="smsSignListTableRef"
@selection-change="handleSelectionChange"
>
<template #empty>
<span>{{ !tableData.loading ? t('emptyData') : '' }}</span>
</template>
<el-table-column type="selection" :selectable="checkSelectable" width="55" />
<el-table-column prop="sign" :label="t('签名名称')" min-width="200" />
<el-table-column prop="is_default" :label="t('使用状态')" min-width="120">
<template #default="{ row }">
<div>
{{ row.is_default ? t('使用中') : t('未使用') }}
</div>
</template>
</el-table-column>
<el-table-column prop="auditResultName" :label="t('审核状态')" min-width="200">
<template #default="{ row }">
<div>
<div :class="[row.auditResult == 2 ? 'text-green-600' : '']">
{{ row.auditResultName }}
</div>
<div class="text-red-600" v-if="row.auditResult != 2">
{{ row.auditMsg }}
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="realNameDx" :label="t('实名状态')" min-width="200">
<template #header>
<div style="display: inline-flex; align-items: center">
<span class="mr-[5px]">{{ t('实名状态') }}</span>
<el-tooltip class="box-item" effect="light" placement="top">
<template #content>
状态标识说明:<br />未实名状态显示为灰色;<br />实名通过状态显示为绿色;<br />实名失败状态显示为红色。<br />
短信接收条件:仅当手机号在对应运营商处实名通过后,才可接收短信。
</template>
<el-icon color="#666">
<QuestionFilled />
</el-icon>
</el-tooltip>
</div>
</template>
<template #default="{ row }">
<div class="flex gap-[5px]">
<el-tag :type="row.realNameLt == 0 ? 'info' : row.realNameLt == 1 ? 'success' : 'danger'">{{ t('联通') }}</el-tag>
<el-tag :type="row.realNameYd == 0 ? 'info' : row.realNameYd == 1 ? 'success' : 'danger'">{{ t('移动') }}</el-tag>
<el-tag :type="row.realNameDx == 0 ? 'info' : row.realNameDx == 1 ? 'success' : 'danger'">{{ t('电信') }}</el-tag>
</div>
</template>
</el-table-column>
<el-table-column prop="create_time" :label="t('createTime')" min-width="120">
<template #default="{ row }">
<div>{{ row.createTime }}</div>
</template>
</el-table-column>
<el-table-column :label="t('操作')" fixed="right" align="right" min-width="120">
<template #default="{ row }">
<el-button type="primary" link @click="selectTemplate(row)" v-if="!row.is_default && row.auditResult == 2">
{{ t('使用') }}
</el-button>
<el-button type="primary" link @click="deleteTemplate(row)" v-if="!row.is_default">
{{ t('删除') }}
</el-button>
</template>
</el-table-column>
</el-table>
<div class="mt-[16px] flex justify-end">
<el-pagination
v-model:current-page="tableData.page"
v-model:page-size="tableData.limit"
layout="total, sizes, prev, pager, next, jumper"
:total="tableData.total"
@size-change="loadSignList()"
@current-change="loadSignList"
/>
</div>
<template #footer>
<el-button @click="visible = false">{{ t('cancel') }}</el-button>
</template>
</el-dialog>
<el-dialog v-model="visibleAdd" :title="t('添加签名')" width="800px" destroy-on-close :close-on-click-modal="false">
<div class="max-h-[600px] overflow-auto">
<el-form label-width="150px" :model="formData" ref="formRef" :rules="formRules" class="page-form ml-[20px]">
<el-form-item :label="t('短信签名')" prop="signature">
<el-input
v-model="formData.signature"
placeholder="请输入短信签名"
class="input-width"
maxlength="20"
show-word-limit
clearable
/>
</el-form-item>
<div class="ml-[150px] text-[12px] text-[#999] leading-[20px]">必须由【】包裹例如【test】</div>
<div class="my-[5px] ml-[150px] text-[12px] text-[#999] leading-[20px]">
字数要求在2-20个字符不能使用空格和特殊符号“ - + = * & % # @ ~等;
</div>
<el-form-item :label="t('短信示例内容')" prop="contentExample">
<el-input
v-model="formData.contentExample"
placeholder="请输入短信示例内容"
clearable
maxlength="50"
show-word-limit
class="input-width"
/>
</el-form-item>
<el-form-item :label="t('企业名称')" prop="companyName">
<el-input
v-model="formData.companyName"
placeholder="请输入企业名称"
clearable
maxlength="20"
show-word-limit
class="input-width"
/>
</el-form-item>
<el-form-item :label="t('社会统一信用代码')" prop="creditCode">
<el-input
v-model="formData.creditCode"
placeholder="请输入社会统一信用代码"
clearable
maxlength="20"
show-word-limit
class="input-width"
/>
</el-form-item>
<el-form-item :label="t('法人姓名')" prop="legalPerson">
<el-input
v-model="formData.legalPerson"
placeholder="请输入法人姓名"
clearable
maxlength="20"
show-word-limit
class="input-width"
/>
</el-form-item>
<el-form-item :label="t('责任人姓名')" prop="principalName">
<el-input
v-model="formData.principalName"
placeholder="请输入责任人姓名"
clearable
maxlength="20"
show-word-limit
class="input-width"
/>
</el-form-item>
<el-form-item :label="t('责任人手机号')" prop="principalMobile">
<el-input
v-model="formData.principalMobile"
placeholder="请输入责任人手机号"
clearable
maxlength="20"
show-word-limit
class="input-width"
/>
</el-form-item>
<el-form-item :label="t('责任人身份证')" prop="principalIdCard">
<el-input
v-model="formData.principalIdCard"
placeholder="请输入责任人身份证"
clearable
maxlength="18"
show-word-limit
class="input-width"
/>
</el-form-item>
<el-form-item :label="t('签名来源')">
<el-radio-group v-model="formData.signSource">
<el-radio v-for="item in signConfig.signSourceList" :key="item.type" :label="item.type">{{ item.name }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="t('签名类型')">
<el-radio-group v-model="formData.signType">
<el-radio v-for="item in signConfig.signTypeList" :key="item.type" :label="item.type">{{ item.name }}</el-radio>
</el-radio-group>
</el-form-item>
<!-- <el-form-item :label="t('上传图片')" prop="imgUrl">
<upload-image v-model="formData.imgUrl" :limit="1" />
</el-form-item>
<div class="ml-[150px] text-[12px] text-[#999] leading-[20px]">
当签名来源为商标、APP、小程序、事业单位简称或企业名称简称时需必填此字段
</div>
<div
class="my-[5px] ml-[150px] text-[12px] text-[#999] leading-[20px]"
>
当签名来源为事业单位全称或企业名称全称时,选填此字段。
</div> -->
<el-form-item :label="t('营业执照')" prop="bizLicenseUrl">
<upload-image v-model="formData.bizLicenseUrl" :limit="1" />
</el-form-item>
<el-form-item :label="t('企查查唯一性截图')" prop="qccUrl">
<upload-image v-model="formData.qccUrl" :limit="1" />
</el-form-item>
<el-form-item :label="t('中国商标网截图')" prop="tmnetUrl">
<upload-image v-model="formData.tmnetUrl" :limit="1" />
</el-form-item>
<el-form-item :label="t('移动ICP截图')" prop="mobileIcpUrl">
<upload-image v-model="formData.mobileIcpUrl" :limit="1" />
</el-form-item>
<el-form-item :label="t('应用商店 /小程序页面开发者截图')" prop="telecomAppstoreUrl">
<upload-image v-model="formData.telecomAppstoreUrl" :limit="1" />
</el-form-item>
<el-form-item :label="t('身份证正面(人像面)')" prop="idcardFrontUrl">
<upload-image v-model="formData.idcardFrontUrl" :limit="1" />
</el-form-item>
<el-form-item :label="t('身份证反面URL')" prop="idcardBackUrl">
<upload-image v-model="formData.idcardBackUrl" :limit="1" />
</el-form-item>
<el-form-item :label="t('是否默认')">
<el-radio-group v-model="formData.defaultSign">
<el-radio :label="1">是</el-radio>
<el-radio :label="0"></el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</div>
<template #footer>
<el-button @click="visibleAdd = false">{{ t('cancel') }}</el-button>
<el-button type="primary" @click="onSave()">{{ t('confirm') }}</el-button>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, reactive } from 'vue'
import { getSignList, addSign, getSmsSignConfig, deleteSign } from '@/app/api/notice'
import { t } from '@/lang'
const visible = ref(false)
const visibleAdd = ref(false)
const emit = defineEmits(['select'])
const props = defineProps({
username: {
type: String,
default: '',
},
})
const initialFormData = {
defaultSign: 0,
imgUrl: '',
bizLicenseUrl: '',
qccUrl: '',
tmnetUrl: '',
mobileIcpUrl: '',
telecomAppstoreUrl: '',
idcardFrontUrl: '',
idcardBackUrl: '',
contentExample: '',
signType: '',
signSource: '',
principalIdCard: '',
principalName: '',
principalMobile: '',
legalPerson: '',
creditCode: '',
companyName: '',
signature: '',
}
const formData = reactive({ ...initialFormData })
const signConfig = reactive({
signTypeList: [],
signSourceList: [],
})
const getSmsSignConfigFn = () => {
getSmsSignConfig().then((res) => {
signConfig.signTypeList = res.data.sign_type_list
signConfig.signSourceList = res.data.sign_source_list
formData.signSource = res.data.sign_source_list[0].type
formData.signType = res.data.sign_type_list[0].type
})
}
getSmsSignConfigFn()
const formRef = ref()
const formRules = computed(() => {
return {
signature: [
{ required: true, message: '请输入短信签名', trigger: 'blur' },
{
validator: (rule, value, callback) => {
const singleBracketValid = /^【[^【】]*】$/.test(value)
if (!singleBracketValid) {
return callback(new Error('短信签名必须被【】包裹'))
}
const content = value.slice(1, -1)
const lengthValid = content.length >= 2 && content.length <= 20
if (!lengthValid) {
return callback(new Error('短信签名内容需在 2-20 个字符之间'))
}
const invalidChars = /[\s\-+=*&%#@~;]/
if (invalidChars.test(content)) {
return callback(new Error('短信签名不能包含空格或特殊字符 - + = * & % # @ ~ ;'))
}
callback()
},
trigger: 'blur',
},
],
principalMobile: [
{ required: true, message: '请输入责任人手机号', trigger: 'blur' },
{ validator: phoneVerify, trigger: 'blur' },
],
companyName: [{ required: true, message: '请输入企业名称', trigger: 'blur' }],
contentExample: [{ required: true, message: '请输入短信示例内容', trigger: 'blur' }],
creditCode: [
{
required: true,
message: '请输入社会统一信用代码',
trigger: 'blur',
},
],
legalPerson: [{ required: true, message: '请输入法人姓名', trigger: 'blur' }],
principalName: [{ required: true, message: '请输入责任人姓名', trigger: 'blur' }],
principalIdCard: [
{ required: true, message: '请输入责任人身份证', trigger: 'blur' },
{ validator: idCardVerify, trigger: 'blur' },
],
imgUrl: [
{
validator: (rule, value, callback) => {
const needImage = [3, 4, 5].includes(formData.signSource) || formData.signType === 1
if (needImage) {
if (!value || value.length === 0) {
callback(new Error('请上传图片'))
} else {
callback()
}
} else {
callback() // 不需要校验
}
},
trigger: 'blur',
},
],
bizLicenseUrl: [{ required: true, message: '请输入营业执照', trigger: 'blur' }],
qccUrl: [
{
validator: (rule, value, callback) => {
const needImage = [1, 2].includes(formData.signSource) || formData.signType === 1
if (needImage) {
if (!value || value.length === 0) {
callback(new Error('请上传企查查唯一性截图'))
} else {
callback()
}
} else {
callback() // 不需要校验
}
},
trigger: 'blur',
},
],
tmnetUrl: [
{
validator: (rule, value, callback) => {
const needImage = [3].includes(formData.signSource)
if (needImage) {
if (!value || value.length === 0) {
callback(new Error('请上传中国商标网截图'))
} else {
callback()
}
} else {
callback() // 不需要校验
}
},
trigger: 'blur',
},
],
mobileIcpUrl: [
{
validator: (rule, value, callback) => {
const needImage = [4, 5].includes(formData.signSource)
if (needImage) {
if (!value || value.length === 0) {
callback(new Error('请上传移动ICP截图'))
} else {
callback()
}
} else {
callback() // 不需要校验
}
},
trigger: 'blur',
},
],
telecomAppstoreUrl: [
{
validator: (rule, value, callback) => {
const needImage = [4, 5].includes(formData.signSource)
if (needImage) {
if (!value || value.length === 0) {
callback(new Error('请上传应用商店/小程序页面开发者截图'))
} else {
callback()
}
} else {
callback() // 不需要校验
}
},
trigger: 'blur',
},
],
idcardFrontUrl: [
{
required: true,
message: '请输入身份证正面(人像面)',
trigger: 'blur',
},
],
idcardBackUrl: [{ required: true, message: '请输入身份证反面', trigger: 'blur' }],
}
})
const idCardVerify = (rule: any, value: any, callback: any) => {
if (value && !/^[1-9]\d{5}(19|20)\d{2}((0\d)|(1[0-2]))(([0-2]\d)|3[0-1])\d{3}([0-9Xx])$/.test(value)) {
callback(new Error(t('请输入正确的身份证号码')))
} else {
callback()
}
}
const phoneVerify = (rule: any, value: any, callback: any) => {
if (value && !/^1[3-9]\d{9}$/.test(value)) {
callback(new Error(t('请输入正确的手机号码')))
} else {
callback()
}
}
const onSave = async () => {
await formRef.value?.validate(async (valid) => {
if (valid) {
addSign(props.username, formData).then((res) => {
setTimeout(() => {
visibleAdd.value = false
loadSignList()
}, 500)
})
}
})
}
// 表单内容
const tableData = reactive({
page: 1,
limit: 10,
total: 0,
loading: false,
data: [],
searchParam: {},
})
const open = () => {
visible.value = true
loadSignList()
}
// 获取列表
const loadSignList = () => {
tableData.loading = true
const params = {
page: tableData.page,
limit: tableData.limit,
...tableData.searchParam,
}
getSignList(props.username, params)
.then((res) => {
tableData.loading = false
tableData.data = res.data.data
tableData.total = res.data.total
})
.catch(() => {
tableData.loading = false
})
}
const addEvent = () => {
Object.assign(formData, initialFormData)
formData.signSource = signConfig.signSourceList[0].type
formData.signType = signConfig.signTypeList[0].type
visibleAdd.value = true
}
const deleteTemplate = (row: any) => {
ElMessageBox.confirm(t('确定删除该签名吗?'), t('提示'), {
confirmButtonText: t('确定'),
cancelButtonText: t('取消'),
type: 'warning',
}).then(() => {
deleteSign(props.username, { signatures: [row.sign] }).then((res) => {
// loadSignList()
tableData.loading = true
setTimeout(() => {
loadSignList()
}, 1000)
})
})
}
// 批量复选框
const toggleCheckbox = ref()
// 复选框中间状态
const isIndeterminate = ref(false)
// 监听批量复选框事件
const toggleChange = (value: any) => {
isIndeterminate.value = false
smsSignListTableRef.value.toggleAllSelection()
}
const smsSignListTableRef = ref()
// 选中数据
const multipleSelection: any = ref([])
// 监听表格单行选中
const handleSelectionChange = (val: []) => {
multipleSelection.value = val
toggleCheckbox.value = false
if (multipleSelection.value.length > 0 && multipleSelection.value.length < tableData.data.length) {
isIndeterminate.value = true
} else {
isIndeterminate.value = false
}
if (multipleSelection.value.length == tableData.data.length && tableData.data.length && multipleSelection.value.length) {
toggleCheckbox.value = true
}
}
const checkSelectable = (row: any, index: number) => {
return !row.is_default // 只有不是“使用中”的行可选
}
// 批量删除
const batchDeleteEvent = () => {
if (multipleSelection.value.length == 0) {
ElMessage({
type: 'warning',
message: `${t('请选择要删除的签名')}`,
})
return
}
ElMessageBox.confirm(t('确定删除选中的签名吗?'), t('warning'), {
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning',
}).then(() => {
const signatures: any = []
multipleSelection.value.forEach((item: any) => {
signatures.push(item.sign)
})
deleteSign(props.username, {
signatures,
})
.then(() => {
tableData.loading = true
setTimeout(() => {
loadSignList()
}, 1000)
})
.catch(() => {})
})
}
const selectTemplate = (row: any) => {
visible.value = false
emit('select', row)
}
defineExpose({ open })
</script>
<style lang="scss" scoped></style>