liusheng
2026-10-30 dfd32b299236e015fe80e10fae01548288d91000
Merge branch 'subtask' of http://116.62.18.175:6699/r/~yxh/smartor-web into subtask
已修改8个文件
已添加4个文件
269 ■■■■■ 文件已修改
.npmrc 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
dist.zip 补丁 | 查看 | 原始文档 | blame | 历史
package.json 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Pagination/index.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/StatisticsCards/index.vue 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/discharge/index.vue 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/mzsatisfaction/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/record/detailpage/index.vue 180 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/zysatisfaction/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tmp-img/crop.py 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tmp-img/crop1.png 补丁 | 查看 | 原始文档 | blame | 历史
vue.config.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.npmrc
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,4 @@
# Node 17+ çš„ OpenSSL 3 ç§»é™¤äº† md4,webpack4 / compression-webpack-plugin@5 å»º hash æ—¶ä¼šæŠ¥
# error:0308010C:digital envelope routines::unsupported
# è¿™é‡Œè®© npm è·‘脚本时自动带上 legacy provider,构建命令不用再手动设环境变量
node-options=--openssl-legacy-provider
dist.zip
Binary files differ
package.json
@@ -7,6 +7,7 @@
  "scripts": {
    "dev": "vue-cli-service serve",
    "build:prod": "vue-cli-service build",
    "build:prod:legacy": "set NODE_OPTIONS=--openssl-legacy-provider&& vue-cli-service build",
    "build:stage": "vue-cli-service build --mode staging",
    "preview": "node build/index.js --preview",
    "lint": "eslint --ext .js,.vue src"
src/components/Pagination/index.vue
@@ -79,7 +79,10 @@
    },
    pageSize: {
      get() {
        return this.limit
        // limit å¯èƒ½è¢«â€œå¯¼å‡ºâ€ç­‰æ“ä½œä¸´æ—¶ç½®ä¸º null,直接透传会让 el-pagination ç®—出
        // pageCount = total / 0 = Infinity,页码里就出现 â€œinfinity”。这里兜底成 10
        const size = Number(this.limit)
        return size > 0 ? size : 10
      },
      set(val) {
        this.$emit('update:limit', val)
src/components/StatisticsCards/index.vue
@@ -114,6 +114,7 @@
        æ— éœ€éšè®¿: "no-follow-card",
        éœ€éšè®¿: "need-follow-card",
        å¾…随访: "pending-card",
        å‘送失败: "failed-card",
        å·²å®Œæˆ: "completed-card",
        ä½é™¢æ€»é‡: "total-card",
        é—¨è¯Šæ€»é‡: "total-card",
@@ -255,6 +256,15 @@
}
/* å¼‚常 - æµ…红色 */
/* å‘送失败 - æµ…红 */
.failed-card .el-card__body {
  background: #ffeaea;
  border-radius: 10px;
}
.failed-card .card-value {
  color: #e04040;
}
.error-card .el-card__body {
  background: #fce4ec;
  border-radius: 10px;
src/views/followvisit/discharge/index.vue
@@ -1249,9 +1249,12 @@
            { value: 1, label: "被领取" },
            { value: 2, label: "待发送" },
            { value: 3, label: "已发送" },
            { value: 5, label: "发送失败" },
            { value: 7, label: "超时" },
          ],
        },
        {
          value: 40, // ä¸€çº§ï¼šå‘送失败(单独列出,方便直接筛)
          label: "发送失败",
        },
        {
          value: 20, // ä¸€çº§ï¼šå·²å®Œæˆ
@@ -1303,6 +1306,10 @@
        },
        {
          name: "待随访",
          value: 0,
        },
        {
          name: "发送失败",
          value: 0,
        },
        {
@@ -1816,9 +1823,9 @@
        this.cardlist[2].value = response.rows[0].ysf;
        this.ycvalue = Number(response.rows[0].yc) || 0;
        this.jgvalue = response.rows[0].jg;
        this.cardlist[3].value = response.rows[0].fssb;
        this.cardlist[4].value = response.rows[0].dsf;
        // this.cardlist[5].value = response.rows[0].yfs2;
        this.cardlist[3].value = response.rows[0].dsf;
        this.cardlist[4].value = response.rows[0].fssb;
        this.cardlist[5].value = response.rows[0].ywc;
        this.yfsvalue = response.rows[0].yfs;
        this.loading = false;
        this.userList.forEach((item) => {
@@ -2060,6 +2067,11 @@
            break;
        }
        this.topqueryParams.sendstate = null;
        // ä¸€çº§ã€Œå‘送失败」:按状态 5 ç›´æŽ¥è¿‡æ»¤ï¼ˆä¸ç­‰åŒäºŽå¾…随访)
        if (value === 40) {
          this.topqueryParams.sendstateView = null;
          this.topqueryParams.sendstate = 5;
        }
      } else {
        // é€‰ä¸­äº†äºŒçº§ï¼ˆå…·ä½“状态)
        this.topqueryParams.sendstateView = null;
@@ -2438,11 +2450,16 @@
      try {
        const res = await getTaskservelist(query);
        const row = (res && res.rows && res.rows[0]) || {};
        // æŽ¥å£æ²¡è¿”回统计行时保持原值,避免一次异常把卡片全刷成 0
        if (!res || !res.rows || !res.rows.length) {
          return;
        }
        this.cardlist[0].value = Number(row.wxsf || 0) + Number(row.xsf || 0);
        this.cardlist[1].value = Number(row.wxsf) || 0;
        this.cardlist[2].value = Number(row.xsf) || 0;
        this.cardlist[3].value = Number(row.dsf) || 0;
        this.cardlist[4].value = Number(row.ywc) || 0;
        this.cardlist[4].value = Number(row.fssb) || 0;
        this.cardlist[5].value = Number(row.ywc) || 0;
        this.ycvalue = Number(row.yc) || 0;
        this.jgvalue = Number(row.jg) || 0;
      } catch (e) {
@@ -2472,6 +2489,8 @@
        query.sendstateView = 3;
      } else if (name === "待随访") {
        query.sendstateView = 1;
      } else if (name === "发送失败") {
        query.sendstates = [5];
      } else if (name === "已完成") {
        query.sendstateView = 2;
      } else if (name === "需随访") {
src/views/followvisit/mzsatisfaction/index.vue
@@ -382,7 +382,8 @@
          align="center"
          key="updateBy"
          prop="updateBy"
          width="120"
          min-width="120"
          :show-overflow-tooltip="true"
        />
        <el-table-column
          label="门诊满意度模板名称"
@@ -1811,6 +1812,9 @@
    },
    /** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
    handleExport() {
      // å¯¼å‡ºéœ€è¦å…¨é‡æ•°æ®ï¼Œä¸´æ—¶åŽ»æŽ‰åˆ†é¡µï¼›å¯¼å‡ºåŽå¿…é¡»è¿˜åŽŸï¼Œå¦åˆ™åˆ†é¡µä¼šç®—æˆ Infinity(页码显示 infinity)
      const originalPageNum = this.topqueryParams.pageNum;
      const originalPageSize = this.topqueryParams.pageSize;
      this.topqueryParams.pageNum = null;
      this.topqueryParams.pageSize = null;
      this.download(
@@ -1819,7 +1823,11 @@
          ...this.topqueryParams,
        },
        `user_${new Date().getTime()}.xlsx`
      );
      ).finally(() => {
        this.topqueryParams.pageNum = originalPageNum;
        this.topqueryParams.pageSize = originalPageSize;
        this.getList();
      });
    },
    // å¼‚常列渲染
    tableRowClassName({ row, rowIndex }) {
src/views/followvisit/record/detailpage/index.vue
@@ -639,10 +639,16 @@
                  <el-button
                    v-if="showQuestionnaireForm"
                    type="primary"
                    :disabled="!canSaveQuestionnaire"
                    :loading="savingQuestion"
                    @click="getdetail"
                    >保存问卷详情</el-button
                  >
                  <el-button v-else type="primary" @click="yuyingetdetail"
                  <el-button
                    v-else
                    type="primary"
                    :loading="savingQuestion"
                    @click="yuyingetdetail"
                    >保存问卷详情</el-button
                  >
                </div>
@@ -700,10 +706,15 @@
                  <el-button
                    v-if="Voicetype"
                    type="primary"
                    :loading="savingVoice"
                    @click="yuyingetdetail"
                    >保存随访详情</el-button
                  >
                  <el-button v-else type="primary" @click="getdetail"
                  <el-button
                    v-else
                    type="primary"
                    :loading="savingQuestion"
                    @click="getdetail"
                    >保存随访详情</el-button
                  >
                </div>
@@ -712,6 +723,38 @@
          </div>
        </div>
      </div>
      <!-- æœªé€‰é€‰é¡¹æç¤ºï¼šå¤§å­—醒目提醒,允许用户坚持保存 -->
      <el-dialog
        :visible.sync="confirmSaveVisible"
        width="640px"
        append-to-body
        :close-on-click-modal="false"
        custom-class="unanswered-dialog"
      >
        <div slot="title" class="unanswered-title">
          <i class="el-icon-warning"></i> æ³¨æ„ï¼šæœ‰é¢˜ç›®æ²¡æœ‰é€‰æ‹©é€‰é¡¹
        </div>
        <div class="unanswered-warn">
          å½“前问卷还有
          <span class="unanswered-count">{{ unansweredQuestions.length }}</span>
          é“题<strong>没有选择任何选项</strong>,继续保存将按“未作答”记录:
        </div>
        <div class="unanswered-list">
          <div v-for="q in unansweredQuestions" :key="q.index">
            ç¬¬ {{ q.index }} é¢˜ï¼š{{ q.title }}
          </div>
        </div>
        <div class="unanswered-tip">确定还要保存吗?</div>
        <span slot="footer">
          <el-button size="medium" @click="confirmSaveVisible = false"
            >返回补填</el-button
          >
          <el-button type="danger" size="medium" @click="confirmForceSave"
            >仍要保存</el-button
          >
        </span>
      </el-dialog>
      <!-- äººå·¥å¤„理 -->
      <div class="manual-action">
        <div class="Followuserinfos">
@@ -1589,6 +1632,12 @@
        "预约下次复诊",
      ],
      tableDatatop: [], //题目表
      // ä¿å­˜é—®å·ï¼šæœªç­”题提示弹窗 / ç”¨æˆ·åšæŒä¿å­˜æ ‡è®°
      confirmSaveVisible: false,
      forceSave: false,
      // é˜²é‡å¤æäº¤ï¼šä¿å­˜ä¸­ç¦ç”¨æŒ‰é’®
      savingQuestion: false,
      savingVoice: false,
      voiceDatatop: [], //题目表
      dynamicTags: [],
      isMergeMode: false,
@@ -1742,6 +1791,26 @@
    }
  },
  computed: {
    // æ˜¯å¦æœ‰ä»»æ„ä¸€é“题「选了选项」或「填了备注」
    hasAnyAnswer() {
      return (this.tableDatatop || []).some((item) => this.isQuestionAnswered(item));
    },
    // å³ä¾§å¤„理意见/备注框是否有内容
    hasRemarkText() {
      const remark = this.form && this.form.remark;
      return remark !== null && remark !== undefined && String(remark).trim() !== "";
    },
    // ä¿å­˜é—®å·è¯¦æƒ…的可点条件:问卷有作答 æˆ– å³è¾¹å¤‡æ³¨æ¡†æœ‰å†…容(有其一即可)
    canSaveQuestionnaire() {
      return this.hasAnyAnswer || this.hasRemarkText;
    },
    // æ²¡æœ‰é€‰æ‹©é€‰é¡¹çš„题目(保存前用大字醒目提示,不区分有没有填备注)
    // index ç”¨é¢˜ç›®åœ¨é—®å·é‡Œçš„原始序号,方便和左边问卷对照
    unansweredQuestions() {
      return (this.tableDatatop || [])
        .map((item, i) => ({ index: i + 1, title: item.scriptContent || "未命名题目", raw: item }))
        .filter((q) => !this.hasOptionValue(q.raw));
    },
    callStatusText() {
      if (this.customCallStatus) {
        return this.customCallStatus;
@@ -2153,8 +2222,52 @@
        }
      });
    },
    // é¢˜ç›®æ˜¯å¦é€‰äº†é€‰é¡¹ï¼ˆå•选字符串 / å¤šé€‰æ•°ç»„)
    hasOptionValue(item) {
      if (!item) {
        return false;
      }
      const val = item.scriptResult;
      if (Array.isArray(val)) {
        return val.some(
          (v) => v !== null && v !== undefined && String(v).trim() !== ""
        );
      }
      return val !== null && val !== undefined && String(val).trim() !== "";
    },
    // é¢˜ç›®æ˜¯å¦å¡«äº†å¤‡æ³¨
    hasRemarkValue(item) {
      if (!item) {
        return false;
      }
      const remark = item.answerps;
      return (
        remark !== null && remark !== undefined && String(remark).trim() !== ""
      );
    },
    // é¢˜ç›®æ˜¯å¦å·²ä½œç­”:选了选项 æˆ– å¡«äº†å¤‡æ³¨ï¼ˆä¸¤è€…有其一即算已答)
    isQuestionAnswered(item) {
      return this.hasOptionValue(item) || this.hasRemarkValue(item);
    },
    // æœªç­”题提示里点「仍要保存」
    confirmForceSave() {
      this.forceSave = true;
      this.confirmSaveVisible = false;
      this.getdetail();
    },
    // åŒ»æŠ¤äººå‘˜å­˜å‚¨æ•°æ®
    getdetail() {
      // é—®å·æ²¡ä½œç­”、右边备注框也空 â†’ ä¸å…è®¸ä¿å­˜
      if (!this.canSaveQuestionnaire) {
        this.$message.warning("请先在问卷中作答,或在右侧填写处理意见/备注");
        return;
      }
      // è¿˜æœ‰é¢˜ç›®æ²¡ä½œç­”:大字提示,用户坚持才继续
      if (this.unansweredQuestions.length > 0 && !this.forceSave) {
        this.confirmSaveVisible = true;
        return;
      }
      this.forceSave = false;
      console.log(1);
      let excep = "";
@@ -2209,6 +2322,11 @@
        param6: this.id,
        type: 2,
      };
      // é˜²é‡å¤æäº¤ï¼šä¿å­˜ä¸­å†æ¬¡ç‚¹å‡»ç›´æŽ¥å¿½ç•¥
      if (this.savingQuestion) {
        return;
      }
      this.savingQuestion = true;
      savequestiondetail(obj).then((res) => {
        if (res.code == 200) {
          this.$modal.msgSuccess("服务保存成功");
@@ -2251,6 +2369,8 @@
              });
          }
        }
      }).finally(() => {
        this.savingQuestion = false;
      });
    },
    Torouter() {
@@ -2554,6 +2674,11 @@
        type: 1,
      };
      console.log("c", 4);
      // é˜²é‡å¤æäº¤ï¼šä¿å­˜ä¸­å†æ¬¡ç‚¹å‡»ç›´æŽ¥å¿½ç•¥
      if (this.savingVoice) {
        return;
      }
      this.savingVoice = true;
      const orgName = localStorage.getItem("orgname");
      console.log(orgName, "orgName");
@@ -2596,6 +2721,8 @@
              this.Torouter();
            });
        }
      }).finally(() => {
        this.savingVoice = false;
      });
    },
    // å†æ¬¡éšè®¿æ•°æ®æ›´æ›¿
@@ -4431,4 +4558,53 @@
    margin-bottom: 12px;
  }
}
/* æœªé€‰é€‰é¡¹æç¤ºï¼šå¤§å­—醒目提醒 */
.unanswered-title {
  font-size: 22px;
  font-weight: 700;
  color: #f56c6c;
  display: flex;
  align-items: center;
}
.unanswered-title i {
  font-size: 26px;
  margin-right: 10px;
}
.unanswered-warn {
  font-size: 20px;
  font-weight: 600;
  color: #303133;
  line-height: 1.8;
}
.unanswered-warn strong {
  color: #f56c6c;
  font-size: 22px;
}
.unanswered-count {
  font-size: 40px;
  font-weight: 700;
  color: #f56c6c;
  margin: 0 6px;
  vertical-align: middle;
}
.unanswered-list {
  margin-top: 14px;
  max-height: 300px;
  overflow-y: auto;
  padding: 12px 16px;
  background: #fef0f0;
  border: 1px solid #fbc4c4;
  border-radius: 6px;
  font-size: 18px;
  line-height: 2;
  color: #303133;
}
.unanswered-tip {
  margin-top: 18px;
  font-size: 22px;
  font-weight: 700;
  color: #f56c6c;
  text-align: center;
}
</style>
src/views/followvisit/zysatisfaction/index.vue
@@ -413,7 +413,8 @@
          align="center"
          key="updateBy"
          prop="updateBy"
          width="120"
          min-width="120"
          :show-overflow-tooltip="true"
        />
        <el-table-column
          label="住院满意度模板名称"
@@ -1899,6 +1900,9 @@
    },
    /** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
    handleExport() {
      // å¯¼å‡ºéœ€è¦å…¨é‡æ•°æ®ï¼Œä¸´æ—¶åŽ»æŽ‰åˆ†é¡µï¼›å¯¼å‡ºåŽå¿…é¡»è¿˜åŽŸï¼Œå¦åˆ™åˆ†é¡µä¼šç®—æˆ Infinity(页码显示 infinity)
      const originalPageNum = this.topqueryParams.pageNum;
      const originalPageSize = this.topqueryParams.pageSize;
      this.topqueryParams.pageNum = null;
      this.topqueryParams.pageSize = null;
      this.download(
@@ -1907,7 +1911,11 @@
          ...this.buildScopePayload(this.topqueryParams),
        },
        `user_${new Date().getTime()}.xlsx`
      );
      ).finally(() => {
        this.topqueryParams.pageNum = originalPageNum;
        this.topqueryParams.pageSize = originalPageSize;
        this.getList();
      });
    },
    // å¼‚常列渲染
    tableRowClassName({ row, rowIndex }) {
tmp-img/crop.py
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
from PIL import Image
src = r"C:\Users\86176\.dsh\attachments\v1\objects\20\20844c3f5c9a090e1010da3d62ae091792c3e25fad6bdd38cc991b054f020d95"
out = r"D:\daima\smartor\smartor-web\tmp-img\crop1.png"
img = Image.open(src)
print("size", img.size)
# ç®­å¤´å¤´éƒ¨é™„近区域
box = (1050, 130, 1450, 420)
crop = img.crop(box)
crop = crop.resize((crop.width * 3, crop.height * 3), Image.LANCZOS)
crop.save(out)
print("saved", out, crop.size)
tmp-img/crop1.png
vue.config.js
@@ -63,6 +63,11 @@
  },
  configureWebpack: {
    name: name,
    // Node 17+ çš„ OpenSSL 3 åŽ»æŽ‰äº† md4,webpack4 é»˜è®¤ç”¨ md4 ç®— hash ä¼šæŠ¥
    // error:0308010C:digital envelope routines::unsupported,这里换成 sha256
    output: {
      hashFunction: 'sha256'
    },
    resolve: {
      alias: {
        '@': resolve('src')