| | |
| | | d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" |
| | | /> |
| | | </svg> |
| | | <!-- ✅ 新增短信图标 --> |
| | | <svg |
| | | v-else-if="action.icon === 'IconMessageSquare'" |
| | | viewBox="0 0 24 24" |
| | | fill="none" |
| | | stroke="currentColor" |
| | | stroke-width="2" |
| | | > |
| | | <rect x="3" y="3" width="18" height="18" rx="2" ry="2" /> |
| | | <line x1="8" y1="9" x2="16" y2="9" /> |
| | | <line x1="8" y1="13" x2="14" y2="13" /> |
| | | <line x1="8" y1="17" x2="12" y2="17" /> |
| | | </svg> |
| | | </div> |
| | | <div class="action-label">{{ action.label }}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </transition> |
| | | <!-- 短信发送对话框 --> |
| | | <el-dialog |
| | | title="短信发送" |
| | | :visible.sync="smsDialogVisible" |
| | | width="800px" |
| | | :close-on-click-modal="false" |
| | | append-to-body |
| | | > |
| | | <el-form ref="smsForm" :model="smsForm" label-width="100px"> |
| | | <el-form-item label="患者名称"> |
| | | <el-input v-model="smsForm.sendname"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="年龄"> |
| | | <el-input v-model="smsForm.age"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="电话"> |
| | | <el-input v-model="smsForm.telcode"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="科室"> |
| | | <el-input v-model="smsForm.deptname"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="病区"> |
| | | <el-input v-model="smsForm.leavehospitaldistrictname"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="选择模板"> |
| | | <el-row :gutter="10"> |
| | | <el-col :span="7"> |
| | | <el-select |
| | | v-model="templateFilterDept" |
| | | placeholder="按科室" |
| | | filterable |
| | | clearable |
| | | style="width: 100%" |
| | | @change="filterTemplates" |
| | | > |
| | | <el-option |
| | | v-for="dept in departmentOptions" |
| | | :key="dept.value" |
| | | :label="dept.label" |
| | | :value="dept.value" |
| | | /> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="7"> |
| | | <el-select |
| | | v-model="templateFilterWard" |
| | | placeholder="按病区" |
| | | filterable |
| | | clearable |
| | | style="width: 100%" |
| | | @change="filterTemplates" |
| | | > |
| | | <el-option |
| | | v-for="ward in wardOptions" |
| | | :key="ward.value" |
| | | :label="ward.label" |
| | | :value="ward.value" |
| | | /> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="10"> |
| | | <el-select |
| | | v-model="selectedTemplateId" |
| | | placeholder="请选择短信模板" |
| | | filterable |
| | | clearable |
| | | style="width: 100%" |
| | | @change="handleTemplateSelect" |
| | | > |
| | | <el-option |
| | | v-for="tmpl in filteredTemplateOptions" |
| | | :key="tmpl.templetid" |
| | | :label="tmpl.templetname" |
| | | :value="tmpl.templetid" |
| | | > |
| | | <div class="template-option-content"> |
| | | <div class="template-option-text"> |
| | | {{ tmpl.templetcontent || "暂无内容" }} |
| | | </div> |
| | | <div class="template-option-meta"> |
| | | <span>{{ tmpl.deptName || "通用" }}</span> |
| | | <span class="meta-separator">/</span> |
| | | <span>{{ tmpl.wardName || "全部" }}</span> |
| | | </div> |
| | | </div> |
| | | </el-option> |
| | | </el-select> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-button |
| | | type="primary" |
| | | plain |
| | | icon="el-icon-plus" |
| | | @click="openQuickCreateTemplate" |
| | | > |
| | | 新建 |
| | | </el-button> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="短信内容"> |
| | | <el-input |
| | | type="textarea" |
| | | :rows="4" |
| | | v-model="smsContent" |
| | | placeholder="请输入短信内容..." |
| | | maxlength="500" |
| | | show-word-limit |
| | | ></el-input> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div slot="footer" class="dialog-footer"> |
| | | <el-button @click="smsDialogVisible = false">取 消</el-button> |
| | | <el-button type="primary" @click="sendSms" :loading="smsLoading"> |
| | | 确认发送 |
| | | </el-button> |
| | | </div> |
| | | <!-- 内嵌:快速新建模板对话框 --> |
| | | <!-- 快速新建模板对话框(增强版) --> |
| | | <el-dialog |
| | | title="新建短信模板" |
| | | :visible.sync="quickCreateVisible" |
| | | width="500px" |
| | | append-to-body |
| | | :close-on-click-modal="false" |
| | | > |
| | | <el-form |
| | | :model="quickTemplateForm" |
| | | :rules="quickTemplateRules" |
| | | ref="quickTemplateForm" |
| | | label-width="90px" |
| | | > |
| | | <el-form-item label="模板编号" prop="templetno"> |
| | | <el-input |
| | | v-model="quickTemplateForm.templetno" |
| | | placeholder="请输入模板编号" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="模板名称" prop="templetname"> |
| | | <el-input |
| | | v-model="quickTemplateForm.templetname" |
| | | placeholder="请输入模板名称" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="所属科室"> |
| | | <el-select |
| | | v-model="quickTemplateForm.deptCode" |
| | | placeholder="请选择科室" |
| | | filterable |
| | | clearable |
| | | style="width: 100%" |
| | | @change=" |
| | | (val) => { |
| | | const dept = departmentOptions.find((d) => d.value === val); |
| | | quickTemplateForm.deptName = dept ? dept.label : ''; |
| | | } |
| | | " |
| | | > |
| | | <el-option |
| | | v-for="dept in departmentOptions" |
| | | :key="dept.value" |
| | | :label="dept.label" |
| | | :value="dept.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="所属病区"> |
| | | <el-select |
| | | v-model="quickTemplateForm.wardCode" |
| | | placeholder="请选择病区" |
| | | filterable |
| | | clearable |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="ward in wardOptions" |
| | | :key="ward.value" |
| | | :label="ward.label" |
| | | :value="ward.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="模板内容" prop="templetcontent"> |
| | | <el-input |
| | | v-model="quickTemplateForm.templetcontent" |
| | | type="textarea" |
| | | :rows="4" |
| | | placeholder="请输入短信模板内容" |
| | | maxlength="500" |
| | | show-word-limit |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div slot="footer"> |
| | | <el-button @click="quickCreateVisible = false">取 消</el-button> |
| | | <el-button |
| | | type="primary" |
| | | @click="submitQuickTemplate" |
| | | :loading="quickCreateLoading" |
| | | > |
| | | 保存并使用 |
| | | </el-button> |
| | | </div> |
| | | </el-dialog> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import { getCurrentUserServiceSubtaskCount } from "@/api/AiCentre/index"; |
| | | import { |
| | | getCurrentUserServiceSubtaskCount, |
| | | sendMsg, |
| | | } from "@/api/AiCentre/index"; |
| | | import { |
| | | listSmstemplet, |
| | | getSmstemplet, |
| | | addSmstemplet, |
| | | updateSmstemplet, |
| | | delSmstemplet, |
| | | } from "@/api/smartor/smstemplet"; |
| | | export default { |
| | | name: "FloatBall", |
| | | |
| | |
| | | hideTimer: null, |
| | | updateTime: "", |
| | | roles: null, |
| | | templateOptions: [], // 模板下拉列表 |
| | | selectedTemplateId: "", // 选中的模板ID |
| | | templateLoading: false, // 模板列表加载状态 |
| | | // 模板筛选 |
| | | templateFilterDept: "", |
| | | templateFilterWard: "", // 新增:病区筛选 |
| | | filteredTemplateOptions: [], |
| | | |
| | | // 科室选项(从 Vuex 获取) |
| | | departmentOptions: [], |
| | | // 新增:病区选项(从 Vuex 获取) |
| | | wardOptions: [], |
| | | // 快速新建模板 |
| | | quickCreateVisible: false, |
| | | quickCreateLoading: false, |
| | | quickTemplateForm: { |
| | | templetno: "", |
| | | templetname: "", |
| | | templetcontent: "", |
| | | }, |
| | | quickTemplateRules: { |
| | | templetname: [ |
| | | { required: true, message: "请输入模板名称", trigger: "blur" }, |
| | | ], |
| | | templetcontent: [ |
| | | { required: true, message: "请输入模板内容", trigger: "blur" }, |
| | | ], |
| | | }, |
| | | // 短信发送对话框 |
| | | // smsDialogVisible: false, |
| | | smsLoading: false, // ✅ 新增加载状态 |
| | | smsContent: "", |
| | | smsForm: { |
| | | sendname: "", |
| | | age: "", |
| | | telcode: "", |
| | | deptname: "", |
| | | leavehospitaldistrictname: "", |
| | | }, |
| | | // 统计数据 |
| | | statsItems: [ |
| | | { |
| | |
| | | icon: "IconPhone", |
| | | url: "/followvisit/particty?type=1&serviceType=2", |
| | | }, |
| | | // { |
| | | // id: 'chat', |
| | | // label: '在线聊天', |
| | | // icon: 'IconMessageCircle', |
| | | // url: '/chat' |
| | | // } |
| | | { |
| | | id: "sendSms", // ✅ 新增短信发送 |
| | | label: "短信发送", |
| | | icon: "IconMessageSquare", |
| | | action: "openSmsDialog", // 标记为弹框操作 |
| | | }, |
| | | ], |
| | | }; |
| | | }, |
| | |
| | | totalUnread() { |
| | | return this.statsItems.reduce((sum, item) => sum + item.unread, 0); |
| | | }, |
| | | // ✅ Vuex 双向绑定 |
| | | smsDialogVisible: { |
| | | get() { |
| | | return this.$store.state.sms.smsDialogVisible; |
| | | }, |
| | | set(val) { |
| | | if (!val) { |
| | | this.$store.dispatch("sms/closeSmsDialog"); |
| | | } |
| | | }, |
| | | }, |
| | | }, |
| | | watch: { |
| | | // ✅ 监听 Vuex 对话框状态 |
| | | "$store.state.sms.smsDialogVisible": { |
| | | handler(val) { |
| | | if (val) { |
| | | const patientData = this.$store.state.sms.patientData; |
| | | this.smsForm = { ...patientData }; |
| | | this.smsContent = this.$store.state.sms.smsTemplate || ""; |
| | | |
| | | // 展开悬浮球 |
| | | if (!this.isExpanded) { |
| | | this.isExpanded = true; |
| | | this.isHidden = false; |
| | | clearTimeout(this.hideTimer); |
| | | } |
| | | |
| | | // ★★★ 关键修复:在这里初始化模板相关数据 ★★★ |
| | | this.initTemplateData(); |
| | | } |
| | | }, |
| | | immediate: false, |
| | | }, |
| | | }, |
| | | mounted() { |
| | | this.roles = this.$store.state.user.roles; |
| | | this.loadPosition(); |
| | |
| | | }, |
| | | |
| | | methods: { |
| | | initTemplateData() { |
| | | // 初始化科室选项 |
| | | if (this.$store.getters.belongDepts) { |
| | | this.departmentOptions = this.$store.getters.belongDepts.map( |
| | | (dept) => ({ |
| | | label: dept.deptName, |
| | | value: dept.deptCode, |
| | | }) |
| | | ); |
| | | } |
| | | |
| | | // ★★★ 新增:初始化病区选项 ★★★ |
| | | if (this.$store.getters.belongWards) { |
| | | this.wardOptions = this.$store.getters.belongWards.map((ward) => ({ |
| | | label: ward.districtName, |
| | | value: ward.districtCode, |
| | | })); |
| | | } |
| | | |
| | | // 重置筛选 |
| | | this.templateFilterDept = ""; |
| | | this.templateFilterWard = ""; // 新增 |
| | | this.selectedTemplateId = ""; |
| | | |
| | | // 加载模板列表 |
| | | this.loadTemplates(); |
| | | }, |
| | | toggleExpand() { |
| | | this.isExpanded = !this.isExpanded; |
| | | if (this.isExpanded) { |
| | |
| | | this.updateStats(); |
| | | } |
| | | }, |
| | | async loadTemplates() { |
| | | this.templateLoading = true; |
| | | try { |
| | | // const { listSmstemplet } = await import("@/api/smartor/smstemplet"); |
| | | const res = await listSmstemplet({ pageNum: 1, pageSize: 999 }); |
| | | this.templateOptions = res.rows || []; |
| | | this.filterTemplates(); // 应用筛选 |
| | | } catch (error) { |
| | | console.error("加载短信模板失败:", error); |
| | | this.templateOptions = []; |
| | | this.filteredTemplateOptions = []; |
| | | } finally { |
| | | this.templateLoading = false; |
| | | } |
| | | }, |
| | | handleTemplateSelect(templateId) { |
| | | if (!templateId) { |
| | | this.smsContent = ""; |
| | | return; |
| | | } |
| | | const selected = this.templateOptions.find( |
| | | (t) => t.templetid === templateId |
| | | ); |
| | | if (selected) { |
| | | this.smsContent = selected.templetcontent || ""; |
| | | } |
| | | }, |
| | | openQuickCreateTemplate() { |
| | | this.quickTemplateForm = { |
| | | templetno: "", |
| | | templetname: "", |
| | | templetcontent: "", |
| | | }; |
| | | this.quickCreateVisible = true; |
| | | this.$nextTick(() => { |
| | | if (this.$refs.quickTemplateForm) { |
| | | this.$refs.quickTemplateForm.clearValidate(); |
| | | } |
| | | }); |
| | | }, |
| | | /** |
| | | * 提交快速新建模板 |
| | | */ |
| | | async submitQuickTemplate() { |
| | | this.$refs.quickTemplateForm.validate(async (valid) => { |
| | | if (!valid) return; |
| | | |
| | | this.quickCreateLoading = true; |
| | | try { |
| | | // const { addSmstemplet } = await import("@/api/smartor/smstemplet"); |
| | | const res = await addSmstemplet(this.quickTemplateForm); |
| | | |
| | | if (res.code === 200) { |
| | | this.$modal.msgSuccess("模板创建成功"); |
| | | |
| | | // 刷新模板列表 |
| | | await this.loadTemplates(); |
| | | |
| | | // 自动选中刚创建的模板 |
| | | const newTmpl = this.templateOptions.find( |
| | | (t) => t.templetname === this.quickTemplateForm.templetname |
| | | ); |
| | | if (newTmpl) { |
| | | this.selectedTemplateId = newTmpl.templetid; |
| | | this.smsContent = newTmpl.templetcontent; |
| | | } |
| | | |
| | | this.quickCreateVisible = false; |
| | | } else { |
| | | this.$modal.msgError(res.msg || "创建失败"); |
| | | } |
| | | } catch (error) { |
| | | console.error("创建模板失败:", error); |
| | | this.$modal.msgError("创建失败,请稍后重试"); |
| | | } finally { |
| | | this.quickCreateLoading = false; |
| | | } |
| | | }); |
| | | }, |
| | | handleMouseEnter() { |
| | | this.isHovering = true; |
| | | if (this.autoHide) { |
| | |
| | | }, |
| | | |
| | | handleActionClick(action) { |
| | | // 如果是短信发送操作,打开对话框 |
| | | console.log(action); |
| | | |
| | | if (action.action === "openSmsDialog") { |
| | | this.openSmsDialog(); |
| | | return; |
| | | } |
| | | |
| | | // 原有逻辑保持不变 |
| | | console.log(this.roles, "this.roles"); |
| | | if ( |
| | | action.url && |
| | |
| | | this.$modal.msgError("非管理员用户暂无创建任务权限"); |
| | | } |
| | | }, |
| | | // 打开短信发送对话框 |
| | | /** |
| | | * 改造原有的 openSmsDialog 方法 |
| | | */ |
| | | openSmsDialog(patientData = {}) { |
| | | // 重置筛选 |
| | | this.templateFilterDept = ""; |
| | | |
| | | // 重置选择 |
| | | this.selectedTemplateId = ""; |
| | | this.smsContent = patientData.smsTemplate || ""; |
| | | |
| | | // 加载模板列表 |
| | | this.loadTemplates(); |
| | | |
| | | // 打开对话框(通过 Vuex) |
| | | this.$store.dispatch("sms/openSmsDialog", { |
| | | name: patientData.name || "", |
| | | age: patientData.age || "", |
| | | phone: patientData.phone || "", |
| | | deptName: patientData.deptName || "", |
| | | wardName: patientData.wardName || "", |
| | | smsTemplate: patientData.smsTemplate || "", |
| | | }); |
| | | }, |
| | | // 新增:筛选模板 |
| | | filterTemplates() { |
| | | let filtered = [...this.templateOptions]; |
| | | |
| | | // 按科室筛选 |
| | | if (this.templateFilterDept) { |
| | | filtered = filtered.filter( |
| | | (tmpl) => tmpl.deptCode === this.templateFilterDept || !tmpl.deptCode |
| | | ); |
| | | } |
| | | |
| | | // ★★★ 新增:按病区筛选 ★★★ |
| | | if (this.templateFilterWard) { |
| | | filtered = filtered.filter( |
| | | (tmpl) => tmpl.wardCode === this.templateFilterWard || !tmpl.wardCode |
| | | ); |
| | | } |
| | | |
| | | this.filteredTemplateOptions = filtered; |
| | | |
| | | // 清空已选 |
| | | this.selectedTemplateId = ""; |
| | | this.smsContent = ""; |
| | | }, |
| | | // 发送短信 |
| | | async sendSms() { |
| | | if (!this.smsContent.trim()) { |
| | | this.$modal.msgError("请输入短信内容"); |
| | | return; |
| | | } |
| | | |
| | | if (!this.smsForm.telcode) { |
| | | this.$modal.msgError("患者电话不能为空"); |
| | | return; |
| | | } |
| | | |
| | | this.smsLoading = true; |
| | | try { |
| | | const res = await sendMsg({ |
| | | phone: this.smsForm.telcode, |
| | | content: this.smsContent, |
| | | }); |
| | | |
| | | if (res.code === 200) { |
| | | this.$modal.msgSuccess("短信发送成功"); |
| | | // ✅ 通过 Vuex 关闭对话框 |
| | | this.$store.dispatch("sms/closeSmsDialog"); |
| | | this.smsContent = ""; |
| | | } else { |
| | | this.$modal.msgError(res.msg || "发送失败"); |
| | | } |
| | | } catch (error) { |
| | | console.error("发送短信失败:", error); |
| | | this.$modal.msgError("发送失败,请稍后重试"); |
| | | } finally { |
| | | this.smsLoading = false; |
| | | this.selectedTemplateId = ""; |
| | | } |
| | | }, |
| | | async updateStats() { |
| | | try { |
| | | // 这里可以替换为实际的 API 调用 |
| | |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .wide-template-popper { |
| | | width: 420px !important; /* 进一步加宽,适应内容展示 */ |
| | | } |
| | | |
| | | .template-option-content { |
| | | padding: 8px 0; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 6px; |
| | | } |
| | | |
| | | .template-option-text { |
| | | font-size: 13px; |
| | | color: #303133; |
| | | line-height: 1.6; |
| | | display: -webkit-box; |
| | | -webkit-line-clamp: 3; /* 最多显示3行 */ |
| | | -webkit-box-orient: vertical; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | word-break: break-all; |
| | | } |
| | | |
| | | .template-option-meta { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .meta-separator { |
| | | color: #dcdfe6; |
| | | } |
| | | .float-ball { |
| | | position: fixed; |
| | | z-index: 9999; |