123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508 |
- <template>
- <view>
- <uni-popup ref="smsPopup" type="center" @change="popupChange" :is-mask-click="false">
- <view class="sms-manager">
- <view class="sms-manager--header mb">群发短信</view>
- <uni-forms :label-width="100" :modelValue="smsDataModel" ref="smsForm">
- <uni-forms-item v-if="toType === 'user' && !isSelectedReceiver" label="目标对象" name="smsPreset" :rules="[{ required: true, errorMessage: '请选择目标对象' }]" required>
- <uni-data-select class="type m" placeholder="预设条件" size="mini" :clear="false"
- :localdata="smsPresetList" v-model="smsDataModel.smsPreset">
- </uni-data-select>
- <view class="sms-data-tip">如需给指定用户发送,请在列表选择要发送的用户。</view>
- </uni-forms-item>
- <uni-forms-item label="目标对象" v-else-if="toType === 'user' && isSelectedReceiver">
- <view>当前已选择{{ receiver.length }}人</view>
- </uni-forms-item>
- <uni-forms-item label="目标对象" v-else-if="toType === 'userTags' ">
- <view>当前已选择{{ receiver.length }}个标签</view>
- <view class="sms-data-tip">如标签关联的用户没有绑定手机号,将不会发送短信。</view>
- </uni-forms-item>
- <uni-forms-item label="跨分页选择" v-if="isSelectedReceiver && hasCondition">
- <checkbox-group @change="smsFilteredChange">
- <checkbox style="transform: scale(.9)" :checked="smsDataModel.filtered"></checkbox>
- </checkbox-group>
- <view class="sms-data-tip">对用户进行了筛选后,可能存在分页无法全部选中时,请勾选跨分页选中</view>
- </uni-forms-item>
- <uni-forms-item label="任务名称" name="name" required
- :rules="[{ required: true, errorMessage: '请输入任务名称' }]">
- <uni-easyinput v-model="smsDataModel.name" placeholder="请输入任务名称,例如 “7日内未登录用户召回”"/>
- </uni-forms-item>
- <uni-forms-item required label="短信模板" name="templateId"
- :rules="[{ required: true, errorMessage: '请选择短信模板' }]">
- <template v-if="!smsTemplateLoading">
- <view v-if="smsTemplate.length">
- <uni-data-select class="type m" placeholder="请选择短信模板" size="mini" :clear="false"
- :localdata="smsTemplate" v-model="smsDataModel.templateId"
- @change="onSmsTemplateSelected">
- </uni-data-select>
- <view class="sms-data-tip">
- 导入短信模版参考<a class="a-link" href="https://uniapp.dcloud.net.cn/uniCloud/admin.html#群发短信"
- target="_blank">教程</a>;若有新的短信模版,可
- <text @click="chooseFile"
- class="a-link">点此导入
- </text>
- </view>
- </view>
- <view v-else>
- <button @click="chooseFile" type="primary" style="width: 120px;"
- size="mini">上传短信模板
- </button>
- <view class="sms-data-tip">当前未导入短信模板,请从dev.dcloud.net.cn的短信-<a
- href="https://dev.dcloud.net.cn/pages/sms/template" target="_blank">模板配置</a>中导出短信模版,并在此导入。教程<a
- href="https://uniapp.dcloud.net.cn/uniCloud/admin.html#batch-sms" target="_blank">详见</a></view>
- </view>
- </template>
- <template v-else>
- 模板加载中...
- </template>
- </uni-forms-item>
- <uni-forms-item label="短信内容" v-if="smsTemplateContent">
- <view class="form-item-flex-center">{{ smsTemplateContent }}</view>
- </uni-forms-item>
- <uni-forms-item label="模板变量配置" :error-message="smsTemplateDataErrorMessage"
- v-if="smsDataModel.templateData.length">
- <view class="sms-data-item" :key="template.field"
- v-for="(template, index) in smsDataModel.templateData">
- <uni-easyinput class="field m" v-model="template.field" placeholder="字段" :clearable="false"
- :disabled="true" style="width: 120px;flex:none;"/>
- <uni-easyinput class="value m" v-model="template.value"
- placeholder="例 {uni-id-users.username}" :clearable="false"/>
- </view>
- <view class="sms-data-tip">
- 短信变量支持固定值和数据表查询两种方式;固定值如:各位同事,数据表查询如:{uni-id-users.username};请注意,若使用数据表查询方式,目前仅支持查询
- uni-id-users 表;并注意确保数据库中查询字段值不为空,否则短信将发送失败。
- </view>
- </uni-forms-item>
- </uni-forms>
- <view class="uni-group">
- <button @click="sendSms(true)" class="uni-button">预览</button>
- <button @click="sendSms()" class="uni-button" type="primary">提交</button>
- </view>
- </view>
- <uni-icons type="closeempty" size="24" class="close" @click="close"></uni-icons>
- </uni-popup>
- <uni-popup ref="previewPopup" type="center" :is-mask-click="false">
- <view class="sms-manager preview">
- <view class="sms-manager--header mb">
- <view>短信预览</view>
- <view class="sub-title">仅预览第一条短信内容</view>
- <view class="sub-title">预计送达 <text style="color: red">{{smsSendUserCount}}</text> 位用户</view>
- </view>
- <view class="content">
- <view v-for="(content,index) of smsPreviewContent" :key="index">{{ content }}</view>
- <view class="length">短信字数:
- <text class="num">{{ smsPreviewContent.length ? smsPreviewContent[0].length : 0 }}</text>
- 字
- </view>
- </view>
- <view class="tip">
- <view>说明:</view>
- <view>若从数据表中查询,字段内容长度会影响总字数,短信字数=短信签名字数+短信内容字数。</view>
- <view>短信长度不超过70个字,按照一条短信计费;超过70个字,按照67字/条拆分成多条计费。</view>
- </view>
- <view class="uni-group">
- <button @click="$refs.previewPopup.close()" class="uni-button">关闭</button>
- </view>
- </view>
- </uni-popup>
- </view>
- </template>
- <script>
- const uniSmsCo = uniCloud.importObject('uni-sms-co')
- export default {
- name: 'batchSms',
- props: {
- // 发送类型 user|userTags
- toType: String,
- // 接收者 user=user._id, userTags=tag.id
- receiver: {
- type: Array,
- default() {
- return []
- }
- },
- // 条件;跨分页选择时需要
- condition: {
- type: Object,
- default () {
- return {}
- }
- }
- },
- data() {
- return {
- smsTemplateLoading: false,
- smsPresetList: [{
- value: 'all',
- text: '全部用户',
- },{
- value: '7-day-offline-users',
- text: '7天内未登录用户',
- },{
- value: '15-day-offline-users',
- text: '15天内未登录用户',
- },{
- value: '30-day-offline-users',
- text: '30天内未登录用户',
- }],
- smsTemplate: [],
- smsTemplateDataErrorMessage: '',
- smsDataModel: {
- name: '',
- templateId: '',
- templateData: [],
- smsPreset: '',
- filtered: false
- },
- smsTemplateContent: '',
- smsPreviewContent: [],
- smsSendUserCount: 0
- }
- },
- computed: {
- isSelectedReceiver() {
- return !!this.receiver.length
- },
- sendAll() {
- return this.smsDataModel.smsPreset === 'all' || this.toType === 'userTags'
- },
- hasCondition () {
- return !!Object.keys(this.condition).length
- }
- },
- watch: {
- smsDataModel: {
- handler(smsDataModel) {
- if (!smsDataModel.templateId) return ''
- const template = this.smsTemplate.find(template => template.value === smsDataModel.templateId)
- let content = smsDataModel.templateData.reduce((res, param) => {
- const reg = new RegExp(`\\$\\{${param.field}\\}`)
- return res.replace(reg, ($1) => param.value || $1)
- }, template.content)
- this.smsTemplateContent = `【${template.sign}】${content}`
- },
- deep: true
- }
- },
- methods: {
- smsFilteredChange () {
- this.smsDataModel.filtered = !this.smsDataModel.filtered
- },
- popupChange(e) {
- if (!e.show) this.reset()
- },
- open() {
- this.$refs.smsPopup.open()
- this.loadSmsTemplate()
- },
- close() {
- this.reset()
- this.$refs.smsPopup.close()
- },
- async loadSmsTemplate() {
- if (this.smsTemplate.length > 0 || this.smsTemplateLoading) return
- this.smsTemplateLoading = true
- try {
- const uniSmsCo = uniCloud.importObject('uni-sms-co', {customUI: true})
- const res = await uniSmsCo.template()
- this.smsTemplate = res.map(item => ({
- ...item,
- value: item._id,
- text: item.name,
- }))
- } finally {
- this.smsTemplateLoading = false
- }
- },
- onSmsTemplateSelected(templateId) {
- const current = this.smsTemplate.find(template => template.value === templateId)
- if (!current) return
- const reg = new RegExp(/\$\{(.*?)\}/g)
- let templateVars = []
- let _execResult
- while (_execResult = reg.exec(current.content)) {
- const param = _execResult[1]
- if (param) {
- templateVars.push({
- field: param,
- value: ''
- })
- }
- }
- this.smsDataModel.templateData = templateVars
- },
- async sendSms(isPreview = false) {
- const values = await this.$refs.smsForm.validate()
- const receiver = this.receiver
- for (const template of this.smsDataModel.templateData) {
- if (!template.value) {
- this.smsTemplateDataErrorMessage = '字段/值不可为空'
- return
- }
- }
- this.smsTemplateDataErrorMessage = ''
- const to = {
- type: this.toType,
- receiver,
- }
- if (this.smsDataModel.filtered || this.smsDataModel.smsPreset) {
- to.condition = this.smsDataModel.smsPreset || this.condition
- }
- if (isPreview) {
- const res = await uniSmsCo.preview(
- to,
- values.templateId,
- this.smsDataModel.templateData
- )
- if (res.errCode === 0) {
- this.smsPreviewContent = res.list
- this.$refs.previewPopup.open()
- this.smsSendUserCount = res.total
- return
- }
- }
- uni.showModal({
- title: '发送确认',
- content: `短信${this.sendAll ? '将发送给所有用户' : this.smsSendUserCount ? `预计发送${this.smsSendUserCount}人`: `将发送给符合条件的用户`},确定发送?`,
- success: async (e) => {
- this.smsSendUserCount = 0
- if (e.cancel) return
- const res = await uniSmsCo.createSmsTask(
- to,
- values.templateId,
- this.smsDataModel.templateData, {
- taskName: values.name
- }
- )
- if (res.taskId) {
- uni.showModal({
- content: '短信任务已提交,您可在DCloud开发者后台查看短信发送记录',
- confirmText: '立即查看',
- cancelText: '关闭',
- success: (e) => {
- if (e.cancel) {
- this.reset()
- this.$refs.smsPopup.close()
- } else {
- // #ifdef H5
- window.open('https://dev.dcloud.net.cn/#/pages/sms/sendLog', '_blank')
- // #endif
- // ifndef H5
- this.reset()
- this.$refs.smsPopup.close()
- // endif
- }
- }
- })
- }
- }
- })
- },
- chooseFile() {
- if (typeof window.FileReader === 'undefined') {
- uni.showModal({
- content: '当前浏览器不支持文件上传,请升级浏览器重试',
- showCancel: false
- })
- return
- }
- uni.chooseFile({
- count: 1,
- extension: ['.json'],
- success: ({tempFiles}) => {
- if (tempFiles.length <= 0) return
- const [file] = tempFiles
- const reader = new FileReader()
- reader.readAsText(file)
- reader.onload = () => this.parserFileJson(null, reader.result)
- reader.onerror = () => this.parserFileJson(reader.error)
- },
- fail: () => {
- uni.showModal({
- content: '打开选择文件框失败',
- showCancel: false
- })
- }
- })
- },
- async parserFileJson(error, fileContent) {
- if (error) {
- console.error(error)
- uni.showModal({
- content: '文件读取失败,请重新上传文件',
- showCancel: false
- })
- return
- }
- let templates = []
- try {
- templates = JSON.parse(fileContent)
- } catch (e) {
- console.error(e)
- uni.showModal({
- content: '短信模板解析失败,请检查模板格式',
- showCancel: false
- })
- return
- }
- const res = await uniSmsCo.updateTemplates(templates)
- if (res.errCode === 0) {
- uni.showModal({
- content: '短信模板更新成功',
- showCancel: false,
- success: () => {
- this.loadSmsTemplate()
- }
- })
- }
- },
- reset() {
- this.smsDataModel.name = ''
- this.smsDataModel.smsPreset = ''
- this.smsDataModel.templateId = ''
- this.smsDataModel.templateData = []
- this.smsPreviewContent = []
- this.smsTemplateContent = ''
- this.smsSendUserCount = 0
- }
- }
- }
- </script>
- <style lang="scss">
- @import '@/uni_modules/uni-scss/variables.scss';
- .a-link {
- cursor: pointer;
- color: $uni-primary;
- text-decoration: none;
- }
- .close {
- position: absolute;
- right: 20px;
- top: 20px;
- cursor: pointer;
- }
- .sms-manager {
- width: 570px;
- background: #fff;
- padding: 30px;
- border-radius: 5px;
- &.preview {
- width: 550px;
- }
- &--header {
- text-align: center;
- font-size: 22px;
- &.mb {
- margin-bottom: 50px;
- }
- .sub-title {
- margin-top: 5px;
- font-size: 16px;
- color: #999;
- }
- }
- .content {
- margin-top: 20px;
- font-size: 16px;
- line-height: 1.5;
- .length {
- text-align: right;
- font-size: 13px;
- margin-top: 20px;
- .num {
- color: red;
- }
- }
- }
- .tip {
- border-top: #ccc solid 1px;
- padding-top: 20px;
- margin-top: 20px;
- line-height: 1.7;
- font-size: 13px;
- color: #999;
- }
- }
- .sms-data-item {
- display: flex;
- align-items: center;
- margin-top: 10px;
- &:first-child {
- margin-top: 0;
- }
- .m {
- margin: 0 5px;
- &:first-child {
- margin-left: 0;
- }
- &:last-child {
- margin-right: 0;
- }
- }
- .type {
- width: 100px;
- flex: none;
- }
- .add,
- .minus {
- cursor: pointer;
- }
- }
- .sms-data-tip {
- color: $uni-info;
- font-size: 12px;
- margin-top: 5px;
- }
- .form-item-flex-center {
- height: 100%;
- display: flex;
- align-items: center;
- }
- </style>
|