Files
xk-client-wx/utils/content-security.js
李琦 2d2a8abfd4 feat(home/chat): 列表骨架屏、聊天室己方头像与协议/医生详情优化
- 新增分包组件 ListSkeleton(doctor/message/product/article/category/zone)
- 首页/消息/资讯首次加载改用骨架屏,刷新不再清空旧数据避免闪空
  · 去掉 home getList、消息 onShow 中的数组清空
  · 三页 pages.json 增加 easycom 与 componentPlaceholder 主包异步引用
- 聊天室己方头像改为读取本地 userinfo.avatarurl(登录后微信头像),
  mycollection 上传头像成功后同步写回 storage
- 修复协议抽屉点不开:page-container 改为 v-if + :show;
  协议名单独可点节点,匹配放宽 name/desc 双向 includes
- 医生气泡头像可点击进入 doctor-detail?readonly=1 只读医生详情,
  隐藏预约挂号区
2026-07-19 16:08:04 +08:00

389 lines
5.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 微信内容安全检查(患者端小程序)
* 调用 xk-api /api/mobile/content-security/* 接口
*/
import { post } from '@/request/api/http.js';
import { checkDev } from '@/utils/utils';
/** 资料类场景(个人信息编辑) */
export const SCENE_PROFILE = 1;
/** 社交日志场景(聊天) */
export const SCENE_CHAT = 4;
/** 微信 msg_sec_check 单条内容上限 */
const MAX_TEXT_LEN = 2500;
/**
* 获取当前环境 xkApi 基址
*/
function getXkApiBase() {
const env = checkDev();
const map = {
prod: 'https://api.xiaokang88.com/api/mobile',
test: 'https://test.xk.api.nailaoyun.cn/api/mobile',
dev: 'http://127.0.0.1:18001/api/mobile',
};
return map[env] || map.prod;
}
function getAuthHeader() {
let token = uni.getStorageSync('token') || '';
token = String(token).replace(/^Bearer\s+/i, '').trim();
return token ? { authorization: `Bearer ${token}` } : {};
}
/**
* 统一展示内容安全错误
*/
export function handleSecurityError(err) {
const msg = (err && (err.msg || err.message)) || '内容含有违规信息,请修改后重试';
uni.showToast({ title: msg, icon: 'none' });
}
function assertCheckOk(res) {
if (res && res.data.code === 0) {
return true;
}
throw { msg: (res && res.data.message) || '内容含有违规信息,请修改后重试' };
}
/**
* 将字段值规范为可比较的字符串(数组用逗号拼接)
*/
function normalizeFieldValue(val) {
if (val == null) {
return '';
}
if (Array.isArray(val)) {
return val.filter(Boolean).map((item) => String(item).trim()).filter(Boolean).join(',');
}
return String(val).trim();
}
/**
* 与 baseline 对比,返回有变动且非空的文本列表
*/
export function pickChangedTexts(fields, baseline = {}) {
const texts = [];
const base = baseline || {};
Object.keys(fields || {}).forEach((key) => {
const current = normalizeFieldValue(fields[key]);
const original = normalizeFieldValue(base[key]);
if (current && current !== original) {
texts.push(current);
}
});
return texts;
}
/**
* 将多条文本用换行拼接;超过 2500 字则按字段拆成多批请求
*/
export function joinTextsForCheck(texts) {
const list = (Array.isArray(texts) ? texts : [])
.map((item) => String(item || '').trim())
.filter(Boolean);
if (!list.length) {
return [];
}
const batches = [];
let current = '';
list.forEach((text) => {
const merged = current ? `${current}\n${text}` : text;
if (merged.length <= MAX_TEXT_LEN) {
current = merged;
return;
}
if (current) {
batches.push(current);
current = '';
}
if (text.length <= MAX_TEXT_LEN) {
current = text;
} else {
for (let i = 0; i < text.length; i += MAX_TEXT_LEN) {
batches.push(text.slice(i, i + MAX_TEXT_LEN));
}
}
});
if (current) {
batches.push(current);
}
return batches;
}
/**
* 表单提交前:仅检测变动字段,合并为一次或少量 msg_sec_check 调用
*/
export async function checkProfileTexts(fields, baseline, scene = SCENE_PROFILE) {
const changed = pickChangedTexts(fields, baseline);
if (!changed.length) {
return true;
}
const batches = joinTextsForCheck(changed);
for (let i = 0; i < batches.length; i++) {
await checkText(batches[i], scene);
}
return true;
}
/**
* 文本内容安全检查
*/
export async function checkText(content, scene = SCENE_PROFILE) {
const text = String(content || '').trim();
if (!text) {
return true;
}
const res = await post('/content-security/check-text', { content: text, scene }, 3);
return assertCheckOk(res);
}
/**
* 批量文本检查(按顺序,每条独立请求)
*/
export async function checkTexts(texts, scene = SCENE_PROFILE) {
const list = Array.isArray(texts) ? texts : [texts];
for (let i = 0; i < list.length; i++) {
const item = list[i];
if (item) {
await checkText(item, scene);
}
}
return true;
}
/**
* 本地图片安全检查(选图后、上传前)
*/
export function checkImage(filePath, scene = SCENE_PROFILE) {
if (!filePath) {
return Promise.resolve(true);
}
const baseUrl = getXkApiBase();
return new Promise((resolve, reject) => {
uni.uploadFile({
url: `${baseUrl}/content-security/check-image`,
filePath,
name: 'file',
formData: { scene: String(scene) },
header: getAuthHeader(),
success: (uploadRes) => {
try {
const result = JSON.parse(uploadRes.data);
assertCheckOk(result);
resolve(true);
} catch (e) {
reject(e.msg ? e : { msg: '图片安全检查失败' });
}
},
fail: () => reject({ msg: '图片安全检查失败' }),
});
});
}
/**
* 公网图片 URL 检查OSS 直传登记前可选)
*/
export async function checkImageUrl(url, scene = SCENE_PROFILE) {
const imageUrl = String(url || '').trim();
if (!imageUrl) {
return true;
}
const res = await post('/content-security/check-image-url', { url: imageUrl, scene }, 3);
return assertCheckOk(res);
}