From 2581f9ae47ed8342b1494bc84f4236dcedb66fbc Mon Sep 17 00:00:00 2001
From: 陶杰 <1378534974@qq.com>
Date: 星期日, 29 十二月 2024 13:58:29 +0800
Subject: [PATCH] 1.短信模板、短信批量任务

---
 pages/sms/send-batch/_id.vue         |  135 +++++++
 pages/sms/send-batch/index.vue       |  349 +++++++++++++++++++
 components/sms/select-all-user.vue   |  244 +++++++++++++
 components/sms/template-download.vue |   80 ++++
 pages/sms/template/index.vue         |  123 ++++++
 components/sms/copy-textarea.vue     |   92 +++++
 6 files changed, 1,023 insertions(+), 0 deletions(-)

diff --git a/components/sms/copy-textarea.vue b/components/sms/copy-textarea.vue
new file mode 100644
index 0000000..16e3b90
--- /dev/null
+++ b/components/sms/copy-textarea.vue
@@ -0,0 +1,92 @@
+<template>
+  <div class="copy-textarea">
+    <div>
+      <span style="color:red;">手动输入最多支持100个号码,大批量号码建议通过文件导入形式提交</span>
+      <el-button type="text" @click="clearVal">点击清空</el-button></div>
+    <el-input type="textarea" :rows="5" v-model="currentValue"
+      placeholder="提示:一行输入一个号码,多个手机号请换行隔开。"
+      width="80%"
+      @change="handlerInputChange"
+    ></el-input>
+    <div>
+      <span style="color:gray;">提示:一行输入一个号码,多个手机号请换行隔开。</span>
+    </div>
+   
+  </div>
+</template>
+
+<script>
+import cloneDeep from 'lodash.clonedeep'
+export default {
+  props: {
+    value: {
+      type: String,
+      default:'',
+    },
+    disabled: {
+      type: Boolean,
+      default: false,
+    },
+  },
+  data() {
+    return {
+      dialogVisible: false,
+      currentValue: '',
+    }
+  },
+  watch: {
+    value: {
+      immediate: true,
+      handler(value) {
+        this.currentValue = value 
+      },
+    },
+  },
+  methods: {
+    clearVal(){
+      this.$elBusUtil
+        .confirm('确定要清空吗?')
+        .then(() => {
+          this.currentValue = ''
+          this.$emit('input', '')
+          this.$emit('change', '')
+        })
+        .catch(() => {})
+    },
+    handlerInputChange(){
+      this.$emit('input', this.currentValue)
+      this.$emit('change', this.currentValue)
+    }
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+.copy-textarea {
+}
+</style>
+<style lang="scss">
+.shop-user-dialog {
+  .dialog-container {
+    display: flex;
+    align-items: flex-start;
+    &__list {
+      flex: 1;
+      border-right: 1px solid #eee;
+      height: 100%;
+    }
+    &__selected {
+      width: 40%;
+      height: 100%;
+      padding: 24px;
+      .el-bus-title {
+        margin-bottom: 15px;
+      }
+      .el-tag {
+        margin-right: 6px;
+        margin-bottom: 6px;
+      }
+    }
+  }
+}
+</style>
diff --git a/components/sms/select-all-user.vue b/components/sms/select-all-user.vue
new file mode 100644
index 0000000..bc8b325
--- /dev/null
+++ b/components/sms/select-all-user.vue
@@ -0,0 +1,244 @@
+<template>
+  <div class="select-shop-user">
+    <el-button v-if="!disabled" type="primary" @click="chooseUser">选择用户列表</el-button>
+    <el-table v-if="value && value.length > 0" :data="value">
+      <el-table-column label="id" prop="id"></el-table-column>
+      <el-table-column label="名称" prop="loginName"></el-table-column>
+      <el-table-column label="注册手机号方式" prop="tel"></el-table-column>
+      <el-table-column v-if="!disabled" label="操作">
+        <template #default="{ $index }">
+          <text-button type="danger" @click="deleteUser($index)">删除</text-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <el-dialog :visible.sync="dialogVisible" title="选择用户列表" append-to-body :close-on-click-modal="false"
+      custom-class="shop-user-dialog" width="80%">
+      <div class="dialog-container">
+        <div class="dialog-container__list">
+          <el-bus-crud v-bind="tableConfig" />
+        </div>
+        <div class="dialog-container__selected">
+          <el-bus-title title="已添加用户" size="mini" />
+          <el-tag v-for="(tag, i) in currentValue" :key="tag.id" closable @close="deleteCurrentUser(i)">{{ tag.loginName
+            }}</el-tag>
+        </div>
+      </div>
+      <div slot="footer">
+        <el-button @click="cancel">取消</el-button>
+        <el-button type="primary" @click="confirm">确定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import cloneDeep from 'lodash.clonedeep'
+export default {
+  props: {
+    value: {
+      type: Array,
+      default: () => [],
+    },
+    disabled: {
+      type: Boolean,
+      default: false,
+    },
+  },
+  data() {
+    return {
+      dialogVisible: false,
+      currentValue: [],
+      tableConfig: {
+        url: 'flower/api/user/user/list',
+        saveQuery: false,
+        hasNew: false,
+        hasEdit: false,
+        hasDelete: false,
+        hasView: false,
+        columns: [
+          { type: 'selection' },
+          { label: 'id', prop: 'id' },
+          { label: '名称', prop: 'loginName' },
+          { label: '注册手机号', prop: 'tel' },
+        ],
+        extraButtons: [
+          {
+            text: '选择',
+            show: (row) =>
+              !this.currentValue.find((item) => item.id === row.id),
+            atClick: (row) => {
+              this.currentValue.push({
+                id: row.id,
+                loginName: row.loginName,
+                tel: row.tel,
+              })
+              return false
+            },
+          },
+          {
+            text: '取消选择',
+            show: (row) => this.currentValue.find((item) => item.id === row.id),
+            atClick: (row) => {
+              const index = this.currentValue.findIndex(
+                (item) => item.id === row.id
+              )
+              this.currentValue.splice(index, 1)
+              return false
+            },
+          },
+        ],
+        headerButtons: [
+          {
+            text: '批量选择',
+            type: 'primary',
+            disabled: (selected) => selected.length === 0,
+            atClick: (selected) => {
+              console.log(selected)
+              selected.forEach(item => {
+                // 检查 selected 数组的每个元素是否已在 currentValue 中
+                if (!this.currentValue.some(currentItem => currentItem.id === item.id)) {
+                  this.currentValue.push({
+                    id: item.id,
+                    loginName: item.loginName,
+                    tel: item.tel,
+                  })
+                }
+              });
+              return true
+            },
+          },
+          {
+            text: '批量取消',
+            type: 'primary',
+            disabled: (selected) => selected.length === 0,
+            atClick: (selected) => {
+              selected.forEach(item => {
+                // 检查 selected 数组的每个元素是否存在于 currentValue 中
+                const index = this.currentValue.findIndex(currentItem => currentItem.id === item.id);
+                if (index !== -1) {
+                  this.currentValue.splice(index, 1); // 如果存在,则移除
+                }
+              });
+              return true
+            },
+          },
+        ],
+
+        searchFormAttrs: {
+          labelWidth: 'auto',
+        },
+        searchForm: [
+          {
+            type: 'row',
+            span: 12,
+            items: [
+              {
+                label: '列表类型:',
+                id: 'userType',
+                type: 'bus-select-dict',
+                el: {
+                  code: 'USER_TYPE',
+                  multiple: false,
+                  style: 'width:100%',
+                },
+                default: 'customer',
+                searchImmediately: true,
+                on: {
+                change: (e, updateForm, obj) => {
+                  console.log(e[0])
+                  // if (e[0] === 'supplier') {
+                  //   this.tableConfig.url = 'flower/api/supplier/page'
+                  // } else if (e[0] === 'partner') {
+                  //   this.tableConfig.url = 'flower/api/partner/page'
+                  // }else if(e[0]==='customer'){
+                  //   this.tableConfig.url = 'flower/api/customer/page'
+                  // }
+                },
+              },
+
+              },
+
+              { label: 'id:', id: 'id', type: 'input' },
+              { label: '名称:', id: 'loginName', type: 'input' },
+              { label: '注册手机号:', id: 'tel', type: 'input' },
+            ],
+          },
+        ],
+      },
+    }
+  },
+  watch: {
+    value: {
+      immediate: true,
+      handler(value) {
+        this.currentValue = cloneDeep(value || [])
+      },
+    },
+  },
+  methods: {
+    handleSelectionChange(rows) {
+      console.log(rows)
+      alert("全选")
+    },
+    chooseUser() {
+      this.currentValue = cloneDeep(this.value || [])
+      this.dialogVisible = true
+    },
+    deleteCurrentUser(i) {
+      this.currentValue.splice(i, 1)
+    },
+    deleteUser(i) {
+      this.$elBusUtil
+        .confirm('确定要删除吗?')
+        .then(() => {
+          const userList = cloneDeep(this.value || [])
+          userList.splice(i, 1)
+          this.$emit('input', userList)
+          this.$emit('change', userList)
+        })
+        .catch(() => { })
+    },
+    confirm() {
+      this.$emit('input', this.currentValue)
+      this.$emit('change', this.currentValue)
+      this.dialogVisible = false
+    },
+    cancel() {
+      this.dialogVisible = false
+    },
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+.select-shop-user {}
+</style>
+<style lang="scss">
+.shop-user-dialog {
+  .dialog-container {
+    display: flex;
+    align-items: flex-start;
+
+    &__list {
+      flex: 1;
+      border-right: 1px solid #eee;
+      height: 100%;
+    }
+
+    &__selected {
+      width: 40%;
+      height: 100%;
+      padding: 24px;
+
+      .el-bus-title {
+        margin-bottom: 15px;
+      }
+
+      .el-tag {
+        margin-right: 6px;
+        margin-bottom: 6px;
+      }
+    }
+  }
+}
+</style>
diff --git a/components/sms/template-download.vue b/components/sms/template-download.vue
new file mode 100644
index 0000000..1b16e26
--- /dev/null
+++ b/components/sms/template-download.vue
@@ -0,0 +1,80 @@
+<template>
+  <div class="copy-textarea">
+    <div>
+      <el-link href="https://hmy-flower.oss-cn-shanghai.aliyuncs.com/a5/a57ec65b165148e5a669e7766743e489template_phone.xlsx" download="template_phone.xlsx"> 
+        <span style="color:#5FA7EE;">点击下载模板</span>
+      </el-link>
+
+      <!-- <el-link @click="downloadTemplate">点击下载模板a</el-link> -->
+    </div>
+   
+  </div>
+</template>
+
+<script>
+import cloneDeep from 'lodash.clonedeep'
+export default {
+  props: {
+    value: {
+      type: String,
+      default:'',
+    },
+    disabled: {
+      type: Boolean,
+      default: false,
+    },
+  },
+  data() {
+    return {
+      dialogVisible: false,
+      currentValue: '',
+    }
+  },
+  watch: {
+    value: {
+      immediate: true,
+      handler(value) {
+        this.currentValue = value 
+      },
+    },
+  },
+  methods: {
+    downloadTemplate() {
+      const link = document.createElement('a');
+      link.href = 'https://hmy-flower.oss-cn-shanghai.aliyuncs.com/a5/a57ec65b165148e5a669e7766743e489template_phone.xlsx';
+      link.download = 'template_phone.xlsx';
+      link.click();
+    }
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+.copy-textarea {
+}
+</style>
+<style lang="scss">
+.shop-user-dialog {
+  .dialog-container {
+    display: flex;
+    align-items: flex-start;
+    &__list {
+      flex: 1;
+      border-right: 1px solid #eee;
+      height: 100%;
+    }
+    &__selected {
+      width: 40%;
+      height: 100%;
+      padding: 24px;
+      .el-bus-title {
+        margin-bottom: 15px;
+      }
+      .el-tag {
+        margin-right: 6px;
+        margin-bottom: 6px;
+      }
+    }
+  }
+}
+</style>
diff --git a/pages/sms/send-batch/_id.vue b/pages/sms/send-batch/_id.vue
new file mode 100644
index 0000000..c2f9e54
--- /dev/null
+++ b/pages/sms/send-batch/_id.vue
@@ -0,0 +1,135 @@
+<template>
+  <div class="base-page-wrapper coupon-detail">
+    <el-bus-title title="基本信息" size="small" />
+    <!-- <el-bus-form ref="form" label-width="auto" :content="formContent" readonly class="readonly-form" /> -->
+    <el-form ref="form" label-width="100px">
+      <el-form-item label="模板名称:"> {{ statisticsData.smsTaskName || '' }} </el-form-item>
+      <el-form-item label="号码数量:"> {{ statisticsData.totalNum || 0 }} </el-form-item>
+    </el-form>
+
+    <div class="base-page-wrapper__line"></div>
+    <el-bus-title title="发送结果" size="small" />
+    <div><el-row :gutter="20">
+        <el-col :span="3" class="mb-2">
+          <el-card>
+            <div class="statistic-title">发送号码数量</div>
+            <div class="statistic-num">{{ statisticsData.totalNum || 0 }}</div>
+          </el-card>
+        </el-col>
+        <el-col :span="3" class="mb-2">
+          <el-card>
+            <div class="statistic-title">成功</div>
+            <div class="statistic-num">{{ statisticsData.successNum || 0 }}</div>
+          </el-card>
+        </el-col>
+        <el-col :span="3" class="mb-2">
+          <el-card>
+            <div class="statistic-title">失败</div>
+            <div class="statistic-num">{{ statisticsData.failureNum || 0 }}</div>
+          </el-card>
+        </el-col>
+        <el-col :span="3" class="mb-2">
+          <el-card>
+            <div class="statistic-title">发送中</div>
+            <div class="statistic-num"> {{ statisticsData.sendingNum || 0 }} </div>
+          </el-card>
+        </el-col>
+
+      </el-row></div>
+    <div class="base-page-wrapper__line"></div>
+    <el-bus-title title="发放记录" size="small" />
+    <el-bus-crud v-bind="recordTableConfig" />
+    <div class="text-center mt-20">
+      <el-button class="min-w-100" @click="goBack">返回</el-button>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      statisticsData:{},
+      recordTableConfig: {
+        url: `flower/v2/sms-task-detail/list`,
+        hasOperation: false,
+        hasNew: false,
+        extraQuery: {
+          smsTaskId: this.$route.params.id,
+        },
+        columns: [
+          { label: '接收号码', prop: 'phone' },
+          { label: '发送时间', prop: 'createTime' },
+          { label: '发送结果', prop: 'resultStr' },
+          { label: '失败原因', prop: 'failReason' },
+        ],
+        searchForm: [
+          {
+            type: 'row',
+            items: [
+              { label: '接收号码', id: 'phone', type: 'input', searchImmediately: true, },
+              {
+                label: '发送结果',
+                id: 'result',
+                type: 'bus-select-dict',
+                el: {
+                  code: 'SMS_SEND_RESULT',
+                  multiple: false,
+                  style: 'width:100%',
+                  clearable: true,
+                },
+                searchImmediately: true,
+              },
+            ],
+          },
+        ],
+      },
+    }
+  },
+  head() {
+    return {
+      title: '批量发送短信详情',
+    }
+  },
+  mounted() {
+    this.getInitData()
+  },
+  methods: {
+    async getInitData() {
+      const { code, data } = await this.$elBusHttp.request(
+            `flower/v2/sms-task-detail/taskStatistics/${this.$route.params.id}`,
+            {
+              method: 'get',
+              // params: {
+              //   id: this.$route.params.id
+              // }
+            }
+          )
+          if (code === 0) {
+            console.log("data")
+            console.log(data)
+            this.statisticsData = data
+          }
+    },
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+@import '@/assets/coupon/detail.scss';
+@import '@/assets/statistic/index.scss';
+
+.statistic-title {
+  text-align: center;
+  font-size: 16px;
+  color: $main-title-color;
+  font-weight: bold;
+  margin-bottom: 6px;
+}
+
+.statistic-num {
+  text-align: center;
+  font-size: 16px;
+  color: $primary-color;
+}
+</style>
diff --git a/pages/sms/send-batch/index.vue b/pages/sms/send-batch/index.vue
new file mode 100644
index 0000000..692a6af
--- /dev/null
+++ b/pages/sms/send-batch/index.vue
@@ -0,0 +1,349 @@
+<template>
+  <el-bus-crud ref="curd" v-bind="tableConfig" />
+</template>
+
+<script>
+import dayjs from 'dayjs'
+import 'dayjs/locale/zh-cn'
+import SelectAllUser from '@/components/sms/select-all-user'
+import CopyTextarea from '@/components/sms/copy-textarea'
+import TemplateDownload from '@/components/sms/template-download'
+export default {
+  data() {
+    const defaultDate = `${dayjs().format('YYYY-MM-DD')} 00:00:00`
+    return {
+      tableConfig: {
+        url: 'flower/v2/sms-task/list',
+        newUrl: 'flower/v2/sms-task/new',
+        editUrl: 'flower/v2/sms-task/edit',
+        viewUrl: 'flower/v2/sms-task',
+        viewOnPath: true,
+        deleteUrl: 'flower/v2/sms-task/delete',
+        hasNew: true,
+        newText: '添加发送任务',
+        dialogNeedRequest: true,
+        canEdit: (row) => row.status === 'wait_publish',
+        canDelete: (row) => row.status === 'wait_publish',
+        extraButtons: [
+          {
+            text: '发布',
+            show: (row) => {
+              return row.status === 'wait_publish';
+            },
+            atClick: async (row) => {
+              try {
+                await this.$elBusUtil.confirm('确定要发布吗?')
+                const { code } = await this.$elBusHttp.request(
+                  `flower/v2/sms-task/publish`,
+                  {
+                    method: 'post',
+                    data: {
+                      id: row.id,
+                    }
+                  }
+                )
+                if (code === 0) {
+                  this.$message.success('发布成功')
+                }
+              } catch (e) {
+                return false
+              }
+            },
+          },
+        ],
+        onResetView: (row) => {
+          // this.$router.push(`${this.$route.path}/${row.id}`)
+
+          // const searchFormRef = this.$refs.crud.$refs.searchForm
+          // const searchFormValue = searchFormRef.getFormValue()
+          const url = this.$router.resolve(
+            `/sms/send-batch/${row.id}`
+          ).href
+          window.open(url, '_blank')
+
+        },
+        columns: [
+          { label: '任务名称', prop: 'name' },
+          { label: '模板名称', prop: 'smsTemplateName' },
+          { label: '模板描述', prop: 'smsTemplateDesc' },
+          { label: '任务状态', prop: 'statusStr' },
+          { label: '创建时间', prop: 'createTime' },
+        ],
+        searchForm: [
+          {
+            type: 'row',
+            items: [
+              { label: '任务名称', id: 'name', type: 'input' },
+              { label: '模板名称', id: 'smsTemplateName', type: 'input' },
+              {
+                label: '任务状态',
+                id: 'status',
+                type: 'bus-select-dict',
+                el: {
+                  code: 'SMS_TASK_STATUS',
+                  multiple: false,
+                  style: 'width:100%',
+                },
+              },
+              {
+                label: '创建时间',
+                id: 'startDate',
+                component: 'el-bus-date-range',
+                el: {
+                  clearable: true,
+                },
+                // commonFormat: true,
+                // commonFormatProps: ['startDate', 'endDate'],
+                inputFormat: (row) => {
+                  if ('startDate' in row || 'endDate' in row) {
+                    return [
+                      this.$elBusUtil.toDate(row.startDate),
+                      this.$elBusUtil.toDate(row.endDate),
+                    ]
+                  }
+                },
+                outputFormat: (val) => {
+                  return {
+                    startDate: val[0] ? `${this.$elBusUtil.toDate(val[0])} 00:00:00` : undefined,
+                    endDate: val[1] ? `${this.$elBusUtil.toDate(val[1])} 23:59:59` : undefined,
+                  }
+                },
+                customClass: 'in-bus-form',
+                // commonRules: true,
+                // default: [defaultDate, defaultDate],
+              },
+
+            ],
+          },
+        ],
+        form: [
+          {
+            label: '任务名称',
+            id: 'name',
+            type: 'input',
+            rules: {
+              required: true,
+              message: '请输入任务名称',
+              trigger: 'blur',
+            },
+          },
+          {
+            label: '短信模板:',
+            id: 'smsTemplateId',
+            type: 'bus-select',
+            el: {
+              interfaceUri: 'flower/v2/sms-template/templateName/all',
+              extraQuery: {
+                current: 1,
+                size: 2000,
+              },
+              props: {
+                label: 'name',
+                value: 'id',
+                // dataPath: 'data',
+              },
+              filterable: true,
+              multiple: false,
+              style: 'width:100%',
+            },
+            rules: {
+              required: true,
+              message: '请选择短信模板',
+              trigger: 'change',
+            },
+          },
+          {
+            label: '接收号码:',
+            id: 'type',
+            type: 'bus-radio',
+            el: {
+              code: 'SMS_RECEIVE_TYPE',
+              // hasAll: false,
+              // childType: 'el-radio-button',
+            },
+            str: true,
+            on: {
+              change: (e, updateForm, obj) => {
+                // updateForm({
+                //   // fileUrl: undefined,
+                //   // fileUrlDesc: undefined,
+                //   // pointCostomIdList: undefined,
+                //   // input: undefined,
+                // })
+              },
+            },
+            default: this.$route.query.status || '',
+            span: 24,
+            searchImmediately: true,
+            rules: {
+              required: true,
+              message: '请输入接收号码',
+              trigger: 'change',
+            },
+          },
+          {
+            label: '导入接收文件',
+            id: 'fileUrl',
+            component: 'el-bus-upload',
+            el: {
+              // listType: 'text',
+              accept: ".xls,.xlsx",
+              limit: 1
+            },
+            commonFormat: true,
+            forceDisabled: true,
+            hidden: (row) => {
+              return row.type !== 'IMPORT'
+            },
+            readonly: false,
+            rules: [
+              { required: true, message: '请上传文件' },
+              {
+                validator: (rule, value, callback) => {
+                  if (!value || value.length > 1) {
+                    callback(new Error('只能上传一个文件'));
+                  } else {
+                    callback();
+                  }
+                },
+                trigger: 'change',
+              },
+            ],
+          },
+          {
+            label: '',
+            id: 'fileUrlDesc',
+            component: TemplateDownload,
+            hidden: (row) => row.type !== 'IMPORT',
+          },
+          {
+            label: '手动输入:',
+            id: 'phones',
+            component: CopyTextarea,
+            el: {
+              type: 'textarea',
+            },
+            // hidden: (row) => row.discountType !== 'ratio',
+            hidden: (row) => row.type !== 'INPUT',
+            readonly: false,
+            span: 24,
+            rules: [
+              { required: true, message: '请输入号码', trigger: 'blur,change', },
+              // {
+              //   validator: (rule, value, callback) => {
+              //     // 如果值为空,直接返回错误
+              //     if (!value || value.trim() === '') {
+              //       callback(new Error('请输入电话号码,每个号码后跟换行符'));
+              //       return;
+              //     }
+
+              //     // 正则表达式:只匹配中国大陆手机号格式
+              //     const phoneRegex = /^1[3-9]\d{9}$/;
+              //     const lines = value.split('\n'); // 按换行符分割文本
+
+              //     // 遍历每一行并校验
+              //     for (let i = 0; i < lines.length; i++) {
+              //       const line = lines[i]; // 每一行去除空格
+              //       if (line && !phoneRegex.test(line)) {
+              //         // 如果当前行的电话号码格式不正确,提示出错的行号
+              //         callback(new Error(`第 ${i + 1} 行电话号码格式不正确,请检查输入, 如 电话号码后面有空格等`));
+              //         return; // 一旦发现不符合的号码格式,就终止校验
+              //       }
+              //     }
+
+              //     if(lines.length>100){
+              //       callback(new Error(`手动输入最多支持100个号码`));
+              //     }
+
+              //     // 所有行都符合格式,执行 callback()
+              //     callback();
+              //   },
+              //   trigger: 'blur',
+              // }
+              {
+                validator: (rule, value, callback) => {
+                  // 如果值为空,直接返回错误
+                  if (!value || value.trim() === '') {
+                    callback(new Error('请输入电话号码,每个号码后跟换行符'));
+                    return;
+                  }
+
+
+
+                  const phoneRegex = /^1[3-9]\d{9}$/; // 校验手机号格式
+                  const lines = value.split('\n'); // 按换行符分割文本
+                  const phoneCount = {}; // 用来统计电话号码出现次数
+
+                  if (lines.length > 100) {
+                    callback(new Error(`手动输入最多支持100个号码`));
+                  }
+
+                  // 校验每一行的电话号码
+                  for (let i = 0; i < lines.length; i++) {
+                    const line = lines[i].trim();
+                    if (line) {
+                      // 如果当前行不是空的,校验其格式
+                      if (!phoneRegex.test(line)) {
+                        callback(new Error(`第 ${i + 1} 行电话号码格式不正确,请检查输入`));
+                        return;
+                      }
+
+                      // 统计号码出现的次数
+                      phoneCount[line] = (phoneCount[line] || 0) + 1;
+                    }
+                  }
+
+
+                  // 收集所有重复的电话号码
+                  const duplicates = [];
+                  for (const phone in phoneCount) {
+                    if (phoneCount[phone] > 1) {
+                      duplicates.push(`${phone} 重复了 ${phoneCount[phone]} 次`);
+                    }
+                  }
+
+
+                  // 如果有重复号码,返回错误并列出所有重复的号码
+                  if (duplicates.length > 0) {
+                    callback(new Error(`以下电话号码重复:\n${duplicates.join('\n')}`));
+                    return;
+                  }
+
+                  // 校验通过
+                  callback();
+                },
+                trigger: 'blur',
+              }
+
+            ],
+
+          },
+
+          {
+            label: '选择用户列表',
+            id: 'smsUserDTOS',
+            component: SelectAllUser,
+            hidden: (row) => row.type !== 'SELECT',
+            rules: { required: true, message: '请选择领取用户' },
+            inputFormat: (row) => {
+              if ('smsUserDTOS' in row) {
+                return row.smsUserDTOS.filter((i) => i)
+              }
+            },
+            outputFormat: (val) => {
+              return val?.length ? val.map((i) => {return {userId:i.id,userPhone:i.tel} }) : []
+            },
+            forceDisabled: true,
+          },
+        ],
+      },
+    }
+  },
+  head() {
+    return {
+      title: '批量发送短信',
+    }
+  },
+
+}
+</script>
diff --git a/pages/sms/template/index.vue b/pages/sms/template/index.vue
new file mode 100644
index 0000000..2db0779
--- /dev/null
+++ b/pages/sms/template/index.vue
@@ -0,0 +1,123 @@
+<template>
+  <el-bus-crud v-bind="tableConfig" />
+</template>
+
+<script>
+import dayjs from 'dayjs'
+import 'dayjs/locale/zh-cn'
+export default {
+  data() {
+    const defaultDate = `${dayjs().format('YYYY-MM-DD')} 00:00:00`
+    return {
+      tableConfig: {
+        url: 'flower/v2/sms-template/list',
+        newUrl: 'flower/v2/sms-template/new',
+        deleteUrl: 'flower/v2/sms-template/delete',
+        editUrl: 'flower/v2/sms-template/edit',
+        columns: [
+          { label: 'ID', prop: 'id' },
+          { label: 'CODE', prop: 'code' },
+          { label: '模板名称', prop: 'name' },
+          { label: '模板描述', prop: 'description' },
+          { label: '创建时间', prop: 'createTime' },
+        ],
+        searchForm: [
+          {
+            type: 'row',
+            items: [
+              { label: 'ID', id: 'id', type: 'input' },
+              { label: 'CODE', id: 'code', type: 'input' },
+              { label: '模板名称', id: 'name', type: 'input' },
+              {
+                label: '创建时间',
+                id: 'startDate',
+                component: 'el-bus-date-range',
+                el: {
+                  clearable: true,
+                },
+                // commonFormat: true,
+                commonFormatProps: ['startDate', 'endDate'],
+                inputFormat: (row) => {
+                  if ('startDate' in row || 'endDate' in row) {
+                    return [
+                      this.$elBusUtil.toDate(row.startDate),
+                      this.$elBusUtil.toDate(row.endDate),
+                    ]
+                  }
+                },
+                outputFormat: (val) => {
+                  return {
+                    startDate:val[0] ? `${this.$elBusUtil.toDate(val[0])} 00:00:00`: undefined,
+                    endDate: val[1] ?`${this.$elBusUtil.toDate(val[1])} 23:59:59`: undefined,
+                  }
+                },
+                customClass: 'in-bus-form',
+                // commonRules: true,
+                // default: [defaultDate, defaultDate],
+              },
+            ],
+          },
+        ],
+        form: [
+          {
+            label: 'CODE:',
+            id: 'code',
+            type: 'bus-select',
+            el: {
+              interfaceUri: 'flower/v2/sms-template/aliyun/list',
+              extraQuery: {
+                current: 1,
+                size: 2000,
+              },
+              props: {
+                label: 'templateName',
+                value: 'templateCode',
+                // dataPath: 'records',
+              },
+              filterable: true,
+              multiple: false,
+              style: 'width:100%',
+            },
+            rules: {
+              required: true,
+              message: '请选择CODE',
+              trigger: 'blur',
+            },
+          },
+
+          {
+            label: '模板名称:',
+            id: 'name',
+            type: 'input',
+            rules: {
+              required: true,
+              message: '请输入模板名称',
+              trigger: 'blur',
+            },
+          },
+          {
+            label: '模板描述:',
+            id: 'description',
+            type: 'input',
+            el: {
+              type: 'textarea',
+              rows: 6,
+            },
+            rules: {
+              required: true,
+              message: '请输入模板描述',
+              trigger: 'blur',
+            },
+          },
+          
+        ],
+      },
+    }
+  },
+  head() {
+    return {
+      title: '短信模板维护',
+    }
+  },
+}
+</script>

--
Gitblit v1.9.3