This commit is contained in:
BianLzhaoMin 2026-02-02 11:10:40 +08:00
parent 521ae6e1ad
commit 2a0b910c7a
6 changed files with 395 additions and 192 deletions

View File

@ -91,3 +91,12 @@ export function resendOneLoopMsgAPI(data) {
})
}
// 立即执行一次
export function runOneLoopMsgAPI(data) {
return request({
url: '/msgJob/executeOnceJob',
method: 'POST',
data,
})
}

View File

@ -101,34 +101,54 @@
</el-table>
</el-col>
<!-- 右侧已选人员 -->
<!-- 右侧已选内容 -->
<el-col :span="8">
<div class="selected-persons">
<div class="selected-header">
<span>已选人员</span>
<span>{{ activeTab === 'person' ? '已选人员' : '已选分组人员' }}</span>
<el-button
type="text"
size="small"
@click="handleClearAll"
:disabled="allSelectedPersons.length === 0"
:disabled="
activeTab === 'person'
? directSelectedPersonIds.length === 0
: selectedGroupIds.length === 0
"
>
清空
</el-button>
</div>
<div class="selected-list">
<div
v-for="person in allSelectedPersons"
:key="person.id"
class="selected-item"
>
<span>{{ person.workerName }}</span>
<el-icon class="remove-icon" @click="handleRemovePerson(person.id)">
<Close />
</el-icon>
</div>
<div v-if="allSelectedPersons.length === 0" class="empty-tip">
暂未选择人员
</div>
<!-- 人员tab显示已选人员 -->
<template v-if="activeTab === 'person'">
<div
v-for="person in directSelectedPersons"
:key="person.id"
class="selected-item"
>
<span>{{ person.workerName }}</span>
<el-icon class="remove-icon" @click="handleRemovePerson(person.id)">
<Close />
</el-icon>
</div>
<div v-if="directSelectedPersons.length === 0" class="empty-tip">
暂未选择人员
</div>
</template>
<!-- 分组tab显示已选分组的所有人员 -->
<template v-else>
<div
v-for="person in groupSelectedPersons"
:key="`${person.groupId}-${person.id}`"
class="selected-item"
>
<span>{{ person.workerName }}</span>
</div>
<div v-if="groupSelectedPersons.length === 0" class="empty-tip">
暂未选择分组
</div>
</template>
</div>
</div>
</el-col>
@ -147,9 +167,13 @@ const props = defineProps({
type: Array,
default: () => [],
},
groupList: {
type: Array,
default: () => [],
},
})
const emit = defineEmits(['update:modelValue', 'confirm'])
const emit = defineEmits(['update:modelValue', 'update:groupList', 'confirm'])
const activeTab = ref('person')
const searchKeyword = ref('')
@ -170,6 +194,9 @@ const selectedGroupIds = ref([])
//
const isSyncing = ref(false)
//
const isInitialized = ref(false)
//
const filteredPersonList = computed(() => {
if (!searchKeyword.value) {
@ -190,7 +217,7 @@ const filteredGroupList = computed(() => {
}
const keyword = groupSearchKeyword.value.toLowerCase()
return groupList.value.filter((item) => {
const groupName = (item.groupName || '').toLowerCase()
const groupName = (item.groupName || item.workerName || '').toLowerCase()
return groupName.includes(keyword)
})
})
@ -198,46 +225,46 @@ const filteredGroupList = computed(() => {
//
const selectedGroupCount = computed(() => selectedGroupIds.value.length)
//
const allSelectedPersons = computed(() => {
const personMap = new Map()
// tab
const directSelectedPersons = computed(() => {
return directSelectedPersonIds.value
.map((personId) => {
const person = personList.value.find((p) => p.id === personId)
if (person) {
return {
id: person.id,
workerName: person.workerName,
orgName: person.orgName,
sex: person.sex,
phone: person.phone,
}
}
return null
})
.filter(Boolean)
})
//
directSelectedPersonIds.value.forEach((personId) => {
const person = personList.value.find((p) => p.id === personId)
if (person) {
personMap.set(person.id, {
id: person.id,
workerName: person.workerName,
orgName: person.orgName,
sex: person.sex,
phone: person.phone,
})
}
})
//
// tab
const groupSelectedPersons = computed(() => {
const persons = []
selectedGroupIds.value.forEach((groupId) => {
const group = groupList.value.find((g) => g.id === groupId)
if (group && group.workerList && Array.isArray(group.workerList)) {
group.workerList.forEach((worker) => {
if (worker && worker.id) {
//
if (!personMap.has(worker.id)) {
personMap.set(worker.id, {
id: worker.id,
workerName: worker.workerName || worker.name || '',
orgName: worker.orgName || '',
sex: worker.sex,
phone: worker.phone || '',
})
}
persons.push({
id: worker.id,
groupId: groupId,
workerName: worker.workerName || worker.name || '',
orgName: worker.orgName || '',
sex: worker.sex,
phone: worker.phone || '',
})
}
})
}
})
return Array.from(personMap.values())
return persons
})
//
@ -249,6 +276,7 @@ const selectedPersonCount = computed(() => {
const handleTabChange = (tabName) => {
searchKeyword.value = ''
groupSearchKeyword.value = ''
// tabtab
nextTick(() => {
if (tabName === 'person') {
syncPersonTableSelection()
@ -293,47 +321,37 @@ const onPersonSelectionChange = (selection) => {
updateSelectedPersons()
}
//
//
const updateSelectedPersons = () => {
const personMap = new Map()
// watch
isInitialized.value = true
//
directSelectedPersonIds.value.forEach((personId) => {
const person = personList.value.find((p) => p.id === personId)
if (person) {
personMap.set(person.id, {
id: person.id,
workerName: person.workerName,
orgName: person.orgName,
sex: person.sex,
phone: person.phone,
})
}
})
//
selectedGroupIds.value.forEach((groupId) => {
const group = groupList.value.find((g) => g.id === groupId)
if (group && group.workerList && Array.isArray(group.workerList)) {
group.workerList.forEach((worker) => {
if (worker && worker.id) {
//
if (!personMap.has(worker.id)) {
personMap.set(worker.id, {
id: worker.id,
workerName: worker.workerName || worker.name || '',
orgName: worker.orgName || '',
sex: worker.sex,
phone: worker.phone || '',
})
}
//
const persons = directSelectedPersonIds.value
.map((personId) => {
const person = personList.value.find((p) => p.id === personId)
if (person) {
return {
id: person.id,
workerName: person.workerName,
orgName: person.orgName,
sex: person.sex,
phone: person.phone,
}
})
}
})
}
return null
})
.filter(Boolean)
emit('update:modelValue', persons)
//
emit('update:modelValue', Array.from(personMap.values()))
//
const groups = selectedGroupIds.value
.map((groupId) => {
const group = groupList.value.find((g) => g.id === groupId)
return group ? { id: group.id, groupName: group.groupName || group.workerName } : null
})
.filter(Boolean)
emit('update:groupList', groups)
}
//
@ -354,7 +372,7 @@ const onGroupSelectionChange = (selection) => {
// IDID
selectedGroupIds.value = [...preservedIds, ...selectedVisibleIds]
//
//
updateSelectedPersons()
}
@ -418,40 +436,10 @@ const syncGroupTableSelection = () => {
})
}
//
// tab
const handleRemovePerson = (personId) => {
//
const isDirectSelected = directSelectedPersonIds.value.includes(personId)
//
const groupsContainingPerson = []
selectedGroupIds.value.forEach((groupId) => {
const group = groupList.value.find((g) => g.id === groupId)
if (group && group.workerList && Array.isArray(group.workerList)) {
const hasPerson = group.workerList.some((w) => w.id === personId)
if (hasPerson) {
groupsContainingPerson.push(groupId)
}
}
})
//
if (isDirectSelected) {
directSelectedPersonIds.value = directSelectedPersonIds.value.filter(
(id) => id !== personId,
)
} else if (groupsContainingPerson.length > 0) {
//
selectedGroupIds.value = selectedGroupIds.value.filter(
(id) => !groupsContainingPerson.includes(id),
)
nextTick(() => {
syncGroupTableSelection()
})
}
directSelectedPersonIds.value = directSelectedPersonIds.value.filter((id) => id !== personId)
updateSelectedPersons()
//
nextTick(() => {
syncPersonTableSelection()
@ -461,15 +449,18 @@ const handleRemovePerson = (personId) => {
//
const handleClearAll = () => {
isSyncing.value = true
directSelectedPersonIds.value = []
selectedGroupIds.value = []
emit('update:modelValue', [])
if (personTableRef.value) {
personTableRef.value.clearSelection()
}
if (groupTableRef.value) {
groupTableRef.value.clearSelection()
if (activeTab.value === 'person') {
directSelectedPersonIds.value = []
if (personTableRef.value) {
personTableRef.value.clearSelection()
}
} else {
selectedGroupIds.value = []
if (groupTableRef.value) {
groupTableRef.value.clearSelection()
}
}
updateSelectedPersons()
nextTick(() => {
isSyncing.value = false
})
@ -495,7 +486,11 @@ const getGroupList = async () => {
try {
const result = await getAllGroupAPI()
if (result.code === 200 && result.rows) {
groupList.value = result.rows || []
// workerName groupName
groupList.value = (result.rows || []).map((group) => ({
...group,
groupName: group.groupName || group.workerName || '',
}))
nextTick(() => {
syncGroupTableSelection()
})
@ -505,37 +500,99 @@ const getGroupList = async () => {
}
}
// modelValue
// modelValue
watch(
() => props.modelValue,
(newVal) => {
//
//
if (directSelectedPersonIds.value.length > 0 || selectedGroupIds.value.length > 0) {
return
}
if (!newVal || newVal.length === 0) {
directSelectedPersonIds.value = []
//
if (directSelectedPersonIds.value.length === 0) {
return
}
//
if (!isInitialized.value) {
directSelectedPersonIds.value = []
}
return
}
//
//
// updateSelectedPersons
const personIds = newVal.map((p) => p.id).filter(Boolean)
directSelectedPersonIds.value = personIds
// ID
const externalPersonIds = newVal.map((p) => p.id).filter(Boolean)
if (activeTab.value === 'person') {
nextTick(() => {
syncPersonTableSelection()
})
//
const currentIds = new Set(directSelectedPersonIds.value.map((id) => Number(id)))
const externalIds = new Set(externalPersonIds.map((id) => Number(id)))
const isEqual =
currentIds.size === externalIds.size &&
[...currentIds].every((id) => externalIds.has(id))
if (isEqual && isInitialized.value) {
return
}
// ID
if (!isInitialized.value || !isEqual) {
directSelectedPersonIds.value = externalPersonIds
isInitialized.value = true
if (activeTab.value === 'person') {
nextTick(() => {
syncPersonTableSelection()
})
}
}
},
{ deep: true, immediate: true },
)
// groupList
watch(
() => props.groupList,
(newVal) => {
if (!newVal || newVal.length === 0) {
//
if (selectedGroupIds.value.length === 0) {
return
}
//
if (!isInitialized.value) {
selectedGroupIds.value = []
}
return
}
// ID
const externalGroupIds = newVal.map((g) => g.id).filter(Boolean)
//
const currentIds = new Set(selectedGroupIds.value.map((id) => Number(id)))
const externalIds = new Set(externalGroupIds.map((id) => Number(id)))
const isEqual =
currentIds.size === externalIds.size &&
[...currentIds].every((id) => externalIds.has(id))
if (isEqual && isInitialized.value) {
return
}
// ID
if (!isInitialized.value || !isEqual) {
selectedGroupIds.value = externalGroupIds
isInitialized.value = true
if (activeTab.value === 'group') {
nextTick(() => {
syncGroupTableSelection()
})
}
}
},
{ deep: true, immediate: true },
)
onMounted(() => {
//
isInitialized.value = false
getPersonList()
getGroupList()
})

View File

@ -33,14 +33,21 @@
</template>
<script setup name="Index">
import { ref } from 'vue'
import { ref, markRaw } from 'vue'
import { useRouter } from 'vue-router'
import { Message, Bell, Document, Setting, DataAnalysis, Connection } from '@element-plus/icons-vue'
const router = useRouter()
//
const icons = ref([Message, Bell, Document, Setting, DataAnalysis, Connection])
// - 使 markRaw
const icons = ref([
markRaw(Message),
markRaw(Bell),
markRaw(Document),
markRaw(Setting),
markRaw(DataAnalysis),
markRaw(Connection),
])
</script>
<style lang="scss" scoped>

View File

@ -60,6 +60,10 @@ export const tableColumns = [
label: '任务状态',
slot: 'taskStatus',
},
{
prop: 'remark',
label: '备注',
},
]
export default {

View File

@ -10,21 +10,21 @@
</template>
<el-form
ref="formRef"
:model="formData"
:rules="!isDetail ? rules : {}"
size="large"
:model="formData"
label-width="auto"
:disabled="isDetail"
:rules="!isDetail ? rules : {}"
>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="任务名称" prop="taskName">
<el-input
v-model.trim="formData.taskName"
placeholder="请输入任务名称"
clearable
maxlength="50"
show-word-limit
clearable
placeholder="请输入任务名称"
v-model.trim="formData.taskName"
/>
</el-form-item>
</el-col>
@ -32,10 +32,10 @@
<el-col :span="12">
<el-form-item label="短信类型" prop="msgType">
<el-select
v-model="formData.msgType"
placeholder="请选择短信类型"
clearable
style="width: 100%"
v-model="formData.msgType"
placeholder="请选择短信类型"
>
<el-option label="通知" value="1" />
<el-option label="计划" value="2" />
@ -77,26 +77,26 @@
</el-form-item>
</el-col>
<el-col :span="12">
<!-- <el-col :span="12">
<el-form-item label="是否并发" prop="concurrent">
<el-radio-group v-model="formData.concurrent">
<el-radio-button value="0">允许</el-radio-button>
<el-radio-button value="1">禁止</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
</el-col> -->
</el-row>
<el-row :gutter="24">
<el-col :span="24">
<el-form-item label="发送内容" prop="sendContent">
<el-input
v-model.trim="formData.sendContent"
type="textarea"
:rows="5"
placeholder="请输入发送内容"
type="textarea"
maxlength="500"
show-word-limit
placeholder="请输入发送内容"
v-model.trim="formData.sendContent"
/>
</el-form-item>
</el-col>
@ -106,12 +106,12 @@
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input
v-model.trim="formData.remark"
type="textarea"
:rows="5"
placeholder="请输入备注"
maxlength="500"
type="textarea"
show-word-limit
placeholder="请输入备注"
v-model.trim="formData.remark"
/>
</el-form-item>
</el-col>
@ -136,30 +136,41 @@
<el-form-item label="接收人员" prop="recipientList">
<div class="recipient-select">
<el-button
type="primary"
link
icon="Plus"
@click="onOpenPersonPicker"
:disabled="isDetail"
type="primary"
v-if="!isDetail"
:disabled="isDetail"
@click="onOpenPersonPicker"
>
选择
</el-button>
<span class="selected-names">{{ selectedRecipientNames }}</span>
</div>
<el-table
v-if="formData.recipientList && formData.recipientList.length > 0"
:data="formData.recipientList"
border
style="margin-top: 10px"
v-if="formData.recipientList && formData.recipientList.length > 0"
>
<el-table-column
width="80"
type="index"
label="序号"
align="center"
:data="formData.recipientList"
/>
<el-table-column align="center" prop="workerName" label="姓名" />
<el-table-column align="center" prop="phone" label="电话" />
<el-table-column v-if="!isDetail" label="操作" align="center">
<el-table-column
width="140"
label="操作"
align="center"
v-if="!isDetail"
>
<template #default="{ $index }">
<el-button
type="danger"
link
type="danger"
icon="Delete"
@click="handleRemoveRecipient($index)"
>
@ -172,10 +183,52 @@
</el-col>
</el-row>
<el-row :gutter="24" v-if="isGenderSendGroup === 0">
<el-col :span="24">
<el-form-item label="已选分组">
<el-table
border
style="margin-top: 10px"
:data="formData.groupList"
v-if="formData.groupList && formData.groupList.length > 0"
>
<el-table-column
width="80"
type="index"
label="序号"
align="center"
/>
<el-table-column align="center" prop="groupName" label="分组名称" />
<el-table-column
label="操作"
width="140"
align="center"
v-if="!isDetail"
>
<template #default="{ $index }">
<el-button
link
type="danger"
icon="Delete"
@click="handleRemoveGroup($index)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<div v-else class="recipient-select">
<span class="selected-names">暂未选择分组</span>
</div>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24" v-if="isGenderSendGroup === 1">
<el-col :span="24">
<el-form-item label="群发短信群体" prop="genderSendGroup">
<el-radio-group v-model="formData.genderSendGroup">
<el-form-item label="群发短信群体" prop="sex">
<el-radio-group v-model="formData.sex">
<el-radio-button :value="1"></el-radio-button>
<el-radio-button :value="0"></el-radio-button>
</el-radio-group>
@ -198,8 +251,8 @@
<template #outerContent>
<crontab
ref="crontabRef"
@hide="handleCronHide"
@fill="crontabFill"
@hide="handleCronHide"
:expression="cronExpression"
/>
</template>
@ -208,7 +261,12 @@
<!-- 人员选择弹窗 -->
<ComDialog :dialog-config="managerDialogConfig" @closeDialogOuter="onCloseDialogOuter">
<template #outerContent>
<PersonPicker v-model="formData.recipientList" />
<PersonPicker
v-model="formData.recipientList"
:group-list="formData.groupList"
@update:groupList="handleGroupListUpdate"
:key="`picker-${managerDialogConfig.outerVisible}`"
/>
<el-row class="common-btn-row fixed-bottom">
<ComButton plain type="info" @click="managerDialogConfig.outerVisible = false">
取消
@ -251,19 +309,19 @@ const pageTitle = computed(() => {
})
const formRef = ref(null)
const crontabRef = ref(null)
const personTableRef = ref(null)
const cronExpression = ref('')
const taskStatus = ref('')
const crontabRef = ref(null)
const cronExpression = ref('')
const personTableRef = ref(null)
const isGenderSendGroup = ref(0)
// Cron
const cronDialogConfig = reactive({
maxHeight: '80vh',
minHeight: '500px',
outerWidth: '70%',
outerVisible: false,
outerTitle: 'Cron表达式生成器',
outerWidth: '70%',
minHeight: '500px',
maxHeight: '80vh',
})
//
@ -275,8 +333,9 @@ const getInitFormData = () => ({
misfirePolicy: '1', // 1-2-3-
concurrent: '1', // 0-1-
sendContent: '', //
genderSendGroup: '', // 0-1-
sex: '', // 0-1-
recipientList: [], //
groupList: [], //
})
const formData = ref(getInitFormData())
@ -290,18 +349,24 @@ const rules = {
recipientList: [
{
required: true,
message: '请至少选择一个接收人员',
message: '请至少选择一个接收人员或分组',
trigger: 'change',
validator: (rule, value, callback) => {
//
if (formData.value.groupList && formData.value.groupList.length > 0) {
callback()
return
}
//
if (!value || value.length === 0) {
callback(new Error('请至少选择一个接收人员'))
callback(new Error('请至少选择一个接收人员或分组'))
} else {
callback()
}
},
},
],
genderSendGroup: [{ required: true, message: '请选择群发短信群体', trigger: 'change' }],
sex: [{ required: true, message: '请选择群发短信群体', trigger: 'change' }],
}
//
@ -321,6 +386,11 @@ const selectedRecipientNames = computed(() => {
return formData.value.recipientList.map((item) => item.workerName).join('、')
})
//
const handleGroupListUpdate = (groupList) => {
formData.value.groupList = groupList || []
}
//
const onOpenPersonPicker = () => {
managerDialogConfig.outerVisible = true
@ -336,6 +406,10 @@ const handleRemoveRecipient = (index) => {
formData.value.recipientList.splice(index, 1)
}
//
const handleRemoveGroup = (index) => {
formData.value.groupList.splice(index, 1)
}
//
const onCloseDialogOuter = (visible) => {
managerDialogConfig.outerVisible = visible
@ -386,11 +460,26 @@ const onSubmit = async () => {
misfirePolicy: formData.value.misfirePolicy,
concurrent: formData.value.concurrent,
sendContent: formData.value.sendContent,
workerList: formData.value.recipientList.map((item) => {
sex: formData.value.sex,
}
// workerListgroupList
if (formData.value.groupList && formData.value.groupList.length > 0) {
// ID
params.groupList = formData.value.groupList.map((item) => {
return {
id: item.id,
}
}),
})
}
if (formData.value.recipientList && formData.value.recipientList.length > 0) {
// ID
params.workerList = formData.value.recipientList.map((item) => {
return {
id: item.id,
}
})
}
// id
@ -419,6 +508,13 @@ const getDetail = async () => {
const result = await getLoopSendDetailAPI(route.query.id)
if (result.code === 200 && result.data) {
const data = result.data
// workerName groupName
const groupList = (data.groupList || []).map((group) => ({
id: group.id,
groupName: group.workerName || group.groupName || '',
}))
formData.value = {
id: data.id,
taskName: data.taskName || '',
@ -428,7 +524,13 @@ const getDetail = async () => {
concurrent: String(data.concurrent || '1'),
sendContent: data.sendContent || '',
recipientList: data.workerList || [],
groupList: groupList,
remark: data.remark || '',
sex: data.sex || '',
}
if (data.sex === 1 || data.sex === 0) {
isGenderSendGroup.value = 1
}
taskStatus.value = data.taskStatus || ''
cronExpression.value = data.cronExpression || ''
@ -450,12 +552,12 @@ const onHandleDetail = async (row) => {
//
const handleGenderSendGroupChange = (value) => {
console.log(value, 'value')
if (value === 0) {
//
formData.value.recipientList = []
formData.value.groupList = []
} else {
formData.value.genderSendGroup = null
formData.value.sex = ''
}
}
@ -539,6 +641,12 @@ onMounted(() => {
}
}
.no-group-tip {
color: #909399;
font-size: 14px;
padding: 10px 0;
}
.person-search-bar {
margin-bottom: 8px;
}

View File

@ -52,6 +52,7 @@ import {
delLoopSendAPI,
listLoopSendAPI,
getSmsBalanceAPI,
runOneLoopMsgAPI,
updateLoopSendStatusAPI,
} from '@/api/sMsSendManage/loopSend.js'
import config from './config'
@ -100,13 +101,20 @@ const actionColumns = [
},
{
label: '发送详情',
label: '立即执行一次',
type: 'primary',
link: true,
handler: (row) => {
sendDetailsDialogConfig.outerTitle = `发送详情 - ${row.taskName || ''}`
sendDetailsId.value = row.id
sendDetailsDialogConfig.outerVisible = true
runOneLoopMsgAPI({ id: row.id, jobId: row.jobId })
.then((res) => {
if (res.code === 200) {
proxy.$modal.msgSuccess('执行成功')
comTableRef.value?.refresh()
}
})
.catch(() => {
proxy.$modal.msgError('执行失败')
})
},
},
{
@ -139,6 +147,16 @@ const actionColumns = [
})
},
},
{
label: '发送详情',
type: 'primary',
link: true,
handler: (row) => {
sendDetailsDialogConfig.outerTitle = `发送详情 - ${row.taskName || ''}`
sendDetailsId.value = row.id
sendDetailsDialogConfig.outerVisible = true
},
},
]
//