|
|
@@ -23,7 +23,6 @@ import InputIcon from 'primevue/inputicon'
|
|
|
import InputText from 'primevue/inputtext'
|
|
|
import Message from 'primevue/message'
|
|
|
import Textarea from 'primevue/textarea'
|
|
|
-import FileUpload from 'primevue/fileupload'
|
|
|
import Tag from 'primevue/tag'
|
|
|
import ProgressBar from 'primevue/progressbar'
|
|
|
import { useToast } from 'primevue/usetoast'
|
|
|
@@ -67,6 +66,15 @@ const fetchData = async () => {
|
|
|
}
|
|
|
|
|
|
const response = await listSmsTasks(params)
|
|
|
+
|
|
|
+ // 对返回的数据进行 URL 解码
|
|
|
+ if (response.content && Array.isArray(response.content)) {
|
|
|
+ response.content = response.content.map(task => ({
|
|
|
+ ...task,
|
|
|
+ message: task.message ? decodeURIComponent(task.message) : task.message
|
|
|
+ }))
|
|
|
+ }
|
|
|
+
|
|
|
tableData.value = response
|
|
|
} catch (error) {
|
|
|
toast.add({
|
|
|
@@ -96,7 +104,7 @@ const getStatusSeverity = (status) => {
|
|
|
const severityMap = {
|
|
|
idle: 'secondary',
|
|
|
pending: 'info',
|
|
|
- running: 'primary',
|
|
|
+ running: 'info',
|
|
|
cutting: 'info',
|
|
|
paused: 'warn',
|
|
|
queued: 'info',
|
|
|
@@ -141,7 +149,7 @@ const taskForm = ref({
|
|
|
file: null
|
|
|
})
|
|
|
const taskFormLoading = ref(false)
|
|
|
-const fileUploadRef = ref()
|
|
|
+const fileInputRef = ref()
|
|
|
|
|
|
const taskFormResolver = computed(() => {
|
|
|
const schema = {
|
|
|
@@ -183,8 +191,8 @@ const openEditTaskDialog = (task) => {
|
|
|
}
|
|
|
|
|
|
// 文件选择处理
|
|
|
-const onFileSelect = (event) => {
|
|
|
- const files = event.files
|
|
|
+const handleFileChange = (event) => {
|
|
|
+ const files = event.target.files
|
|
|
if (files && files.length > 0) {
|
|
|
taskForm.value.file = files[0]
|
|
|
}
|
|
|
@@ -193,8 +201,8 @@ const onFileSelect = (event) => {
|
|
|
// 清除文件
|
|
|
const clearFile = () => {
|
|
|
taskForm.value.file = null
|
|
|
- if (fileUploadRef.value) {
|
|
|
- fileUploadRef.value.clear()
|
|
|
+ if (fileInputRef.value) {
|
|
|
+ fileInputRef.value.value = ''
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -203,12 +211,15 @@ const saveTask = async ({ valid }) => {
|
|
|
|
|
|
taskFormLoading.value = true
|
|
|
try {
|
|
|
+ // 对短信内容进行 URL 编码
|
|
|
+ const encodedMessage = encodeURIComponent(taskForm.value.message)
|
|
|
+
|
|
|
if (isEditMode.value) {
|
|
|
// 更新任务
|
|
|
const updateData = {
|
|
|
id: taskForm.value.id,
|
|
|
name: taskForm.value.name,
|
|
|
- message: taskForm.value.message
|
|
|
+ message: encodedMessage
|
|
|
}
|
|
|
if (taskForm.value.remark) {
|
|
|
updateData.remark = taskForm.value.remark
|
|
|
@@ -225,7 +236,7 @@ const saveTask = async ({ valid }) => {
|
|
|
// 创建任务
|
|
|
const formData = new FormData()
|
|
|
formData.append('name', taskForm.value.name)
|
|
|
- formData.append('message', taskForm.value.message)
|
|
|
+ formData.append('message', encodedMessage)
|
|
|
if (taskForm.value.remark) {
|
|
|
formData.append('remark', taskForm.value.remark)
|
|
|
}
|
|
|
@@ -262,8 +273,19 @@ const closeTaskDialog = () => {
|
|
|
}
|
|
|
|
|
|
// ========== 任务操作 ==========
|
|
|
+// 操作中的任务ID集合
|
|
|
+const operatingTaskIds = ref(new Set())
|
|
|
+
|
|
|
+// 检查任务是否正在操作中
|
|
|
+const isTaskOperating = (taskId) => {
|
|
|
+ return operatingTaskIds.value.has(taskId)
|
|
|
+}
|
|
|
+
|
|
|
// 开始任务
|
|
|
const handleStartTask = async (task) => {
|
|
|
+ if (isTaskOperating(task.id)) return
|
|
|
+
|
|
|
+ operatingTaskIds.value.add(task.id)
|
|
|
try {
|
|
|
await startSmsTask(task.id)
|
|
|
toast.add({
|
|
|
@@ -272,8 +294,12 @@ const handleStartTask = async (task) => {
|
|
|
detail: '任务已开始',
|
|
|
life: 3000
|
|
|
})
|
|
|
- fetchData()
|
|
|
+ setTimeout(() => {
|
|
|
+ fetchData()
|
|
|
+ operatingTaskIds.value.delete(task.id)
|
|
|
+ }, 2000)
|
|
|
} catch (error) {
|
|
|
+ operatingTaskIds.value.delete(task.id)
|
|
|
toast.add({
|
|
|
severity: 'error',
|
|
|
summary: '错误',
|
|
|
@@ -285,6 +311,9 @@ const handleStartTask = async (task) => {
|
|
|
|
|
|
// 暂停任务
|
|
|
const handlePauseTask = async (task) => {
|
|
|
+ if (isTaskOperating(task.id)) return
|
|
|
+
|
|
|
+ operatingTaskIds.value.add(task.id)
|
|
|
try {
|
|
|
await pauseSmsTask(task.id)
|
|
|
toast.add({
|
|
|
@@ -293,8 +322,12 @@ const handlePauseTask = async (task) => {
|
|
|
detail: '任务已暂停',
|
|
|
life: 3000
|
|
|
})
|
|
|
- fetchData()
|
|
|
+ setTimeout(() => {
|
|
|
+ fetchData()
|
|
|
+ operatingTaskIds.value.delete(task.id)
|
|
|
+ }, 2000)
|
|
|
} catch (error) {
|
|
|
+ operatingTaskIds.value.delete(task.id)
|
|
|
toast.add({
|
|
|
severity: 'error',
|
|
|
summary: '错误',
|
|
|
@@ -397,14 +430,42 @@ const handleTaskItemPageChange = (event) => {
|
|
|
fetchTaskItems()
|
|
|
}
|
|
|
|
|
|
+// 节流控制:记录最后一次点击时间
|
|
|
+const lastClickTime = ref({
|
|
|
+ search: 0,
|
|
|
+ reset: 0,
|
|
|
+ refresh: 0
|
|
|
+})
|
|
|
+
|
|
|
+// 节流检查函数
|
|
|
+const checkThrottle = (actionType) => {
|
|
|
+ const now = Date.now()
|
|
|
+ const lastTime = lastClickTime.value[actionType]
|
|
|
+
|
|
|
+ if (now - lastTime < 1000) {
|
|
|
+ toast.add({
|
|
|
+ severity: 'warn',
|
|
|
+ summary: '提示',
|
|
|
+ detail: '操作过快,请稍后再试',
|
|
|
+ life: 2000
|
|
|
+ })
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ lastClickTime.value[actionType] = now
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
// 应用筛选
|
|
|
const applyFilters = () => {
|
|
|
+ if (!checkThrottle('search')) return
|
|
|
tableData.value.metadata.page = 0
|
|
|
fetchData()
|
|
|
}
|
|
|
|
|
|
// 重置筛选
|
|
|
const resetFilters = () => {
|
|
|
+ if (!checkThrottle('reset')) return
|
|
|
filters.value = {
|
|
|
status: null
|
|
|
}
|
|
|
@@ -412,6 +473,12 @@ const resetFilters = () => {
|
|
|
fetchData()
|
|
|
}
|
|
|
|
|
|
+// 手动刷新(带节流)
|
|
|
+const handleRefresh = () => {
|
|
|
+ if (!checkThrottle('refresh')) return
|
|
|
+ fetchData()
|
|
|
+}
|
|
|
+
|
|
|
onMounted(() => {
|
|
|
fetchData()
|
|
|
})
|
|
|
@@ -437,7 +504,7 @@ onMounted(() => {
|
|
|
<Button icon="pi pi-search" label="搜索" @click="applyFilters" size="small" />
|
|
|
|
|
|
<span class="w-px h-6 bg-[var(--p-content-border-color)] mx-1"></span>
|
|
|
- <Button icon="pi pi-refresh" @click="fetchData" label="刷新" size="small" />
|
|
|
+ <Button icon="pi pi-refresh" @click="handleRefresh" label="刷新" size="small" />
|
|
|
</div>
|
|
|
|
|
|
<!-- 右侧按钮:新建任务 -->
|
|
|
@@ -505,9 +572,14 @@ onMounted(() => {
|
|
|
<div class="flex gap-1 justify-center">
|
|
|
<Button v-if="canStartTask(slotProps.data)" icon="pi pi-play" severity="success" size="small"
|
|
|
text rounded aria-label="开始" v-tooltip.top="'开始'"
|
|
|
+ :disabled="isTaskOperating(slotProps.data.id)"
|
|
|
+ :loading="isTaskOperating(slotProps.data.id)"
|
|
|
@click="handleStartTask(slotProps.data)" />
|
|
|
<Button v-if="canPauseTask(slotProps.data)" icon="pi pi-pause" severity="warn" size="small" text
|
|
|
- rounded aria-label="暂停" v-tooltip.top="'暂停'" @click="handlePauseTask(slotProps.data)" />
|
|
|
+ rounded aria-label="暂停" v-tooltip.top="'暂停'"
|
|
|
+ :disabled="isTaskOperating(slotProps.data.id)"
|
|
|
+ :loading="isTaskOperating(slotProps.data.id)"
|
|
|
+ @click="handlePauseTask(slotProps.data)" />
|
|
|
<Button icon="pi pi-eye" severity="info" size="small" text rounded aria-label="详情"
|
|
|
v-tooltip.top="'详情'" @click="openTaskDetailDialog(slotProps.data)" v-if="isAdmin" />
|
|
|
<Button icon="pi pi-pencil" severity="info" size="small" text rounded aria-label="编辑"
|
|
|
@@ -552,14 +624,27 @@ onMounted(() => {
|
|
|
</div>
|
|
|
|
|
|
<div class="field mt-4" v-if="!isEditMode">
|
|
|
- <label class="block mb-2 text-sm">手机号文件 * (.txt格式,每行一个手机号)</label>
|
|
|
- <FileUpload ref="fileUploadRef" name="file" accept=".txt" :maxFileSize="10000000"
|
|
|
- @select="onFileSelect" :auto="false" :showUploadButton="false" :showCancelButton="false"
|
|
|
- chooseLabel="选择文件">
|
|
|
- <template #empty>
|
|
|
- <p>拖拽文件到这里上传</p>
|
|
|
- </template>
|
|
|
- </FileUpload>
|
|
|
+ <div>
|
|
|
+ <input ref="fileInputRef" type="file" accept=".txt" @change="handleFileChange"
|
|
|
+ style="display: none;" />
|
|
|
+ <Button label="选择文件" icon="pi pi-upload" @click="fileInputRef?.click()" size="small"
|
|
|
+ severity="info" />
|
|
|
+ <div v-if="taskForm.file" class="mt-2 text-sm text-[var(--p-text-color)]">
|
|
|
+ <i class="pi pi-file mr-1"></i>{{ taskForm.file.name }}
|
|
|
+ </div>
|
|
|
+ <div class="mt-2 text-xs text-[var(--p-text-muted-color)]">
|
|
|
+ 目标号码文件,需要 .txt 格式
|
|
|
+ </div>
|
|
|
+ <div class="mt-2 text-xs text-[var(--p-text-muted-color)]">
|
|
|
+ 内容格式示例:区号+手机号,每行一个,如下:
|
|
|
+ <br>
|
|
|
+ 138001381001
|
|
|
+ <br>
|
|
|
+ 138001381002
|
|
|
+ <br>
|
|
|
+ 138001381003
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
<Message v-if="$form.file?.invalid" severity="error" size="small" variant="simple" class="mt-2">
|
|
|
{{ $form.file.error?.message }}
|
|
|
</Message>
|