10 Commits
Author SHA1 Message Date
lianlonggangandClaude 8152c4c559 feat(Banner管理): 轮播图新增跳转方式与跳转链接字段
- 列表新增"跳转方式"、"跳转链接"两列
- 表单新增跳转方式单选(不跳转/链接/app内),选"链接"时跳转链接必填

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-01 11:47:01 +08:00
lianlonggang 141cf2b856 feat(personalData): 去掉客户相关字样 2026-08-06 17:54:41 +08:00
lianlonggang 97d51c05ea feat(personalData): 去掉客户相关字样 2026-08-05 19:27:49 +08:00
lianlonggangandClaude 380c495f40 feat(消息管理): 新增员工电话列展示与搜索项
- 列表新增「员工电话」列,从esContent提取3~4~4格式手机号显示纯数字
- 搜索表单新增「员工电话」项,参数传后台由后端还原波浪线后模糊查询

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-16 16:01:46 +08:00
weixin 1da2e17514 fix(消息管理): 消息列表和搜索表单"接收人"字段更名为"发送人"
- 消息管理模块:列表列标题 esReceiver 从"接收人"改为"发送人",搜索表单 esReceiver 字段标签同步修正
2026-07-16 13:39:21 +08:00
lianlonggang 0241e7b083 feat: 新增 apple-app-site-association 配置 iOS Universal Links 2026-07-14 11:38:34 +08:00
wanghao 5d7df50e25 feat(emergency): 急救宣教推送改为后端推 article 卡片
前端不再通过 postMessage 推送自定义消息,改由后端 push/send
接口直接推送 businessID=article 的自定义消息到 IM 会话。iweb
端新增 article 卡片渲染分支(左缩略图 + 右标题摘要横向布局,
max-width 280px),并加消息接收调试日志。

同时切换开发环境后台地址到 10.10.10.228:19996。
2026-07-07 17:40:50 +08:00
weixin 48eaec6393 fix(健康检测-用户数据): 睡眠折线图按阶段分段显示,各段间不再连续连线
每段睡眠数据(深睡/浅睡/清醒/零星小睡)之间插入 null 断点,
ECharts 折线图在 null 处自动断开,使不同睡眠阶段呈现为独立线段。
2026-06-30 17:54:49 +08:00
wanghao 375011991e refactor(label): 清理标签分类页面未使用代码与废弃函数
删除弹窗状态变量、reactive 导入、废弃函数,提升可读性。
2026-06-26 14:43:54 +08:00
wanghao 9c6680146f feat(resource): 对接急救宣教资源管理 V3 接口
- 审批与上架分离:已通过状态显示「已通过」+「上架」操作
- 分类扁平化:分类表单去掉父级分类字段
- 标签从分类 tags 字段联动取,不再调独立标签接口
- 标签表单加所属分类必填,支持从分类「+标签」按钮预设锁定
- 审核记录字段对齐后端:resourceTitle/auditRejectReason
- 资源表单 footer 隐藏默认按钮,写操作 API 关全局提示避免重复弹
2026-06-25 10:30:22 +08:00
36 changed files with 26733 additions and 192 deletions
+2 -2
View File
@@ -10,7 +10,7 @@ VITE_GLOB_APP_TITLE_JD = 健康干预后台
VITE_GLOB_APP_SHORT_NAME = CqytHealthAdmin VITE_GLOB_APP_SHORT_NAME = CqytHealthAdmin
# 单点登录服务端地址 # 单点登录服务端地址
VITE_GLOB_APP_CAS_BASE_URL=http://cas.test.com:8443/cas VITE_GLOB_APP_CAS_BASE_URL=
# 是否开启单点登录 # 是否开启单点登录
VITE_GLOB_APP_OPEN_SSO = false VITE_GLOB_APP_OPEN_SSO = false
@@ -19,6 +19,6 @@ VITE_GLOB_APP_OPEN_SSO = false
VITE_GLOB_APP_OPEN_QIANKUN=true VITE_GLOB_APP_OPEN_QIANKUN=true
# 文件预览地址 # 文件预览地址
VITE_GLOB_ONLINE_VIEW_URL=http://fileview.jeecg.com/onlinePreview VITE_GLOB_ONLINE_VIEW_URL=
+2 -2
View File
@@ -16,10 +16,10 @@ VITE_BUILD_COMPRESS = 'gzip'
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
#后台接口父地址(必填) #后台接口父地址(必填)
VITE_GLOB_API_URL=http://cqyt.dev.yg.dt.io VITE_GLOB_API_URL=http://10.8.0.4:29990
#后台接口全路径地址(必填) #后台接口全路径地址(必填)
VITE_GLOB_DOMAIN_URL=http://cqyt.dev.yg.dt.io VITE_GLOB_DOMAIN_URL=http://10.8.0.4:29990
#食堂接口前缀 #食堂接口前缀
VITE_GLOB_ST_DOMAIN_URL=http://113.200.64.105:9002 VITE_GLOB_ST_DOMAIN_URL=http://113.200.64.105:9002
+2 -2
View File
@@ -11,13 +11,13 @@ VITE_PUBLIC_PATH = /
VITE_DROP_CONSOLE = false VITE_DROP_CONSOLE = false
#后台接口父地址(必填) #后台接口父地址(必填)
VITE_GLOB_API_URL=http://10.10.20.18 VITE_GLOB_API_URL=http://10.10.10.228:19996
#VITE_GLOB_API_URL=https://xj-api.mcrm.vip:8888 #VITE_GLOB_API_URL=https://xj-api.mcrm.vip:8888
#VITE_GLOB_API_URL=http://192.168.1.223 #VITE_GLOB_API_URL=http://192.168.1.223
#VITE_GLOB_API_URL=http://cqyt.test.yg.dt.io #VITE_GLOB_API_URL=http://cqyt.test.yg.dt.io
#后台接口全路径地址(必填) #后台接口全路径地址(必填)
VITE_GLOB_DOMAIN_URL=http://10.10.20.18 VITE_GLOB_DOMAIN_URL=http://10.10.10.228:19996
#VITE_GLOB_DOMAIN_URL=https://xj-api.mcrm.vip:8888 #VITE_GLOB_DOMAIN_URL=https://xj-api.mcrm.vip:8888
#VITE_GLOB_DOMAIN_URL=http://192.168.1.223 #VITE_GLOB_DOMAIN_URL=http://192.168.1.223
#VITE_GLOB_DOMAIN_URL=http://cqyt.test.yg.dt.io #VITE_GLOB_DOMAIN_URL=http://cqyt.test.yg.dt.io
+6 -6
View File
@@ -16,14 +16,14 @@ VITE_BUILD_COMPRESS = 'gzip'
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
#后台接口父地址(必填) #后台接口父地址(必填)
VITE_GLOB_API_URL=https://xj-api.mcrm.vip:8888 VITE_GLOB_API_URL=https://test.xjygjk.com
#后台接口全路径地址(必填) #后台接口全路径地址(必填)
VITE_GLOB_DOMAIN_URL=https://xj-api.mcrm.vip:8888 VITE_GLOB_DOMAIN_URL=https://test.xjygjk.com
#食堂接口前缀 #食堂接口前缀
VITE_GLOB_ST_DOMAIN_URL=https://yyjk.cqygjk.com/gateway VITE_GLOB_ST_DOMAIN_URL=
#西安、银川应急大屏 #西安、银川应急大屏
VITE_EMERGENCY_SCREEN=https://screen.cqygjk.com/ VITE_EMERGENCY_SCREEN=
# 接口父路径前缀 # 接口父路径前缀
VITE_GLOB_API_URL_PREFIX= VITE_GLOB_API_URL_PREFIX=
@@ -37,8 +37,8 @@ VITE_USE_PWA = false
# 是否兼容旧浏览器 # 是否兼容旧浏览器
VITE_LEGACY = false VITE_LEGACY = false
# 油田平台CODE # 平台CODE
VITE_PLATFORM = 'YT' VITE_PLATFORM = 'YT'
# 跳转外部链接 # 跳转外部链接
VITE_GLOB_OUT_URL=https://console-jkglpt.iosp.ydpt.tech/hb/healthadmin/index.html?param= VITE_GLOB_OUT_URL=https://test.com
+1
View File
@@ -3,6 +3,7 @@ node_modules
.github .github
.gitee .gitee
dist dist
renkang
.npmrc .npmrc
.cache .cache
+7 -3
View File
@@ -3,9 +3,9 @@ MAINTAINER jeecgos@163.com
VOLUME /tmp VOLUME /tmp
ENV LANG en_US.UTF-8 ENV LANG en_US.UTF-8
ENV VITE_GLOB_API_URL https://xj-api.mcrm.vip:8888 ENV VITE_GLOB_API_URL http://10.10.10.228:19996
ENV VITE_GLOB_DOMAIN_URL https://xj-api.mcrm.vip:8888 ENV VITE_GLOB_DOMAIN_URL http://10.10.10.228:19996
ENV VITE_GLOB_OUT_URL https://gstest.superwx.cn/healthapiadmin/index.html?param= ENV VITE_GLOB_OUT_URL https://gstest.superwx.cn/healthcheckadmin/index.html?param=
RUN echo "server { \ RUN echo "server { \
#解决Router(mode: 'history')模式下,刷新路由地址不能找到页面的问题 \ #解决Router(mode: 'history')模式下,刷新路由地址不能找到页面的问题 \
@@ -17,6 +17,10 @@ RUN echo "server { \
break; \ break; \
} \ } \
} \ } \
location = /.well-known/apple-app-site-association { \
root /var/www/html; \
default_type application/json; \
} \
access_log /var/log/nginx/access.log ; \ access_log /var/log/nginx/access.log ; \
} " > /etc/nginx/conf.d/default.conf \ } " > /etc/nginx/conf.d/default.conf \
&& mkdir -p /var/www \ && mkdir -p /var/www \
@@ -94,6 +94,15 @@
</div> </div>
</div> </div>
</template> </template>
<template v-else-if="isCustom.businessID === constant.typeArticle">
<div class="article-card">
<img v-if="isCustom?.messageImageUrl" :src="isCustom?.messageImageUrl" class="article-cover" />
<div class="article-info">
<div class="article-title">{{ isCustom?.title }}</div>
<div v-if="isCustom?.content" class="article-content">{{ isCustom?.content }}</div>
</div>
</div>
</template>
<template v-else-if="isCustom.businessID === constant.typeBusinessCard"> <template v-else-if="isCustom.businessID === constant.typeBusinessCard">
<div style="cursor: pointer; min-width: 180px"> <div style="cursor: pointer; min-width: 180px">
<div class="business-type"> <div class="business-type">
@@ -372,6 +381,56 @@
} }
} }
.article-card {
display: flex;
align-items: center;
gap: 8px;
max-width: 280px;
padding: 8px;
border-radius: 8px;
background: #f7f7f7;
border: 1px solid #ececec;
.article-cover {
width: 60px;
height: 60px;
border-radius: 6px;
flex-shrink: 0;
object-fit: cover;
}
.article-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 4px;
}
.article-title {
font-size: 14px;
font-weight: 500;
color: #333;
line-height: 1.3;
word-break: break-all;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.article-content {
font-size: 12px;
color: #7d7d7d;
line-height: 1.3;
word-break: break-all;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
}
.img-list { .img-list {
img { img {
width: 100px; width: 100px;
@@ -115,6 +115,14 @@ export default class TUIChatServer extends IComponentServer {
} }
private handleMessageReceived(event: any) { private handleMessageReceived(event: any) {
// 调试日志:打印收到的所有消息,看后端推送的是什么格式
console.log('[IM 收到消息]', event?.data?.map((m: any) => ({
type: m?.type,
text: m?.payload?.text,
data: m?.payload?.data,
description: m?.payload?.description,
extension: m?.payload?.extension,
})));
event?.data?.forEach((message: Message) => { event?.data?.forEach((message: Message) => {
if (message?.conversationID === this?.store?.conversation?.conversationID) { if (message?.conversationID === this?.store?.conversation?.conversationID) {
this.currentStore.messageList = [...this.currentStore.messageList, message]; this.currentStore.messageList = [...this.currentStore.messageList, message];
@@ -31,6 +31,7 @@ const constant: any = {
typeIphone: 'iphone', typeIphone: 'iphone',
typeMini: 'miniprogram', typeMini: 'miniprogram',
typeSystem: 'system', typeSystem: 'system',
typeArticle: 'article',
typeBusinessCard: 'business_card', typeBusinessCard: 'business_card',
typeArchives: 'archives', typeArchives: 'archives',
typeMedicalExaminationReport: 'medical_examination_report', typeMedicalExaminationReport: 'medical_examination_report',
+1 -1
View File
@@ -5,7 +5,7 @@
<van-collapse-item name="1"> <van-collapse-item name="1">
<template #title> <template #title>
<CardTitle title="我的岗位健康"> <CardTitle title="我的岗位健康">
<div class="Depart-info">采气工 财务部主任</div> <div class="Depart-info">财务部主任</div>
</CardTitle> </CardTitle>
</template> </template>
<div class="user-unit-content" :class="[toggleCard ? 'fold' : 'unfold']"> <div class="user-unit-content" :class="[toggleCard ? 'fold' : 'unfold']">
@@ -221,7 +221,7 @@
break; break;
case '否': case '否':
state.questionnaireIndex = state.questionnaireData.qsList.findIndex((item) => { state.questionnaireIndex = state.questionnaireData.qsList.findIndex((item) => {
return item.qsDes === '您是否感觉因油田工作与社会脱节'; return item.qsDes === '您是否感觉因工作与社会脱节';
}); });
break; break;
default: default:
+26425
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,11 @@
{
"applinks": {
"apps": [],
"details": [
{
"appID": "A7CC5LW224.com.questionTools.XinjiangProject",
"paths": ["*"]
}
]
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -1,4 +1,4 @@
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"><link rel="icon" href="faviconnew.png"><title>即时通信</title><link href="css/vendors~app.3f294141.css" rel="stylesheet"><link href="css/app.e6e597ee.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but 腾讯云即时通信 Web-IM doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="./TCaptcha.js"></script><script src="./aegis.min.js"></script><script>/** <!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"><link rel="icon" href="faviconnew.png"><title>即时通信</title><link href="css/vendors~app.3f294141.css" rel="stylesheet"><link href="css/app.f83e8594.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but 腾讯云即时通信 Web-IM doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="./TCaptcha.js"></script><script src="./aegis.min.js"></script><script>/**
* 根据基础字体大小 和 效果图宽度设置 根节点字体大小 * 根据基础字体大小 和 效果图宽度设置 根节点字体大小
* @param baseFontSize 浏览器默认字体大小 [注]浏览器最小字体为12像素,所以该值必需大于等于12 * @param baseFontSize 浏览器默认字体大小 [注]浏览器最小字体为12像素,所以该值必需大于等于12
* @param baseWidth 设计稿的尺寸 * @param baseWidth 设计稿的尺寸
@@ -53,4 +53,4 @@
}, },
false false
); );
};</script><script src="js/vendors~app.be028cd5.js"></script><script src="js/app.d3bfe68a.js"></script></body></html> };</script><script src="js/vendors~app.be028cd5.js"></script><script src="js/app.ece2c62c.js"></script></body></html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+12 -13
View File
@@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>健康油我用户协议</title> <title>用户协议</title>
</head> </head>
<style> <style>
html,body { html,body {
@@ -38,17 +38,16 @@
</style> </style>
<body> <body>
<div id="zwb"> <div id="zwb">
<h3 class="title-one">健康油我用户协议</h3> <h3 class="title-one">软件使用用户协议</h3>
<h3 class="title-one">软件使用许可协议(健康油我)</h3>
<h3 class="title-one">软件使用许可协议</h3> <h3 class="title-one">软件使用许可协议</h3>
<div>欢迎使用我们的服务</div> <div>欢迎使用我们的服务</div>
<div>本软件许可使用协议《健康油我平台服务协议》(以下称"本协议")由您与中国石油天然气股份有限公司新疆油田分公司行政事务管理处(以下称“新疆油田”)共同签署。</div> <div>本软件许可使用协议《平台服务协议》(以下称"本协议")由您与本司共同签署。</div>
<div>在使用“健康油我”软件(以下称许可软件)之前,请仔细阅读本协议。一旦您下载安装使用许可软件,即表示您同意接受本协议所有条款和条件的约束。如您不同意本协议条款和条件,请勿使用许可软件,并请销毁所有许可软件副本。</div> <div>在使用本软件软件(以下称许可软件)之前,请仔细阅读本协议。一旦您下载安装使用许可软件,即表示您同意接受本协议所有条款和条件的约束。如您不同意本协议条款和条件,请勿使用许可软件,并请销毁所有许可软件副本。</div>
<div>健康油我有权根据实际运营的情况对本协议不时进行调整,并以平台公告或电子邮件等书面形式通知您。修改后的协议于公示或邮件送达时生效。协议条款修改后,您继续使用许可软件的,即视为您已阅读并接受修改后的协议。</div> <div>本软件有权根据实际运营的情况对本协议不时进行调整,并以平台公告或电子邮件等书面形式通知您。修改后的协议于公示或邮件送达时生效。协议条款修改后,您继续使用许可软件的,即视为您已阅读并接受修改后的协议。</div>
<div>一、定义</div> <div>一、定义</div>
<div>1. 健康油我APP:是指由新疆油田开发的,供您从应用下载平台(以下称下载平台)下载,并仅限在手机等手持移动终端(以下称移动终端)中安装、使用的软件系统。</div> <div>1. APP:是指由本司开发的,供您从应用下载平台(以下称下载平台)下载,并仅限在手机等手持移动终端(以下称移动终端)中安装、使用的软件系统。</div>
<div>2. 健康油我服务:由新疆油田为您提供的服务。您可以通过许可软件在手持移动终端使用健康油我服务。</div> <div>2. 本软件服务:由本司为您提供的服务。您可以通过许可软件在手持移动终端使用本软件服务。</div>
<div>3. 用户:指阅读并同意本协议内容,经过健康油我在线注册程序成为健康油我在线各项服务使用者。(以下更多称为“您”)</div> <div>3. 用户:指阅读并同意本协议内容,经过本软件在线注册程序成为在线各项服务使用者。(以下更多称为“您”)</div>
<div>二、服务说明</div> <div>二、服务说明</div>
<div>1. 本平台是一个集健康咨询、应急就医、健康档案、体检服务等功能的综合个人健康管理平台,为确保您能正常地使用平台的各项服务,您应当阅读并遵守本协议。请您务必审慎阅读、充分理解各条款内容,以及开通或使用某项服务的单独协议,并选择接受或不接受。</div> <div>1. 本平台是一个集健康咨询、应急就医、健康档案、体检服务等功能的综合个人健康管理平台,为确保您能正常地使用平台的各项服务,您应当阅读并遵守本协议。请您务必审慎阅读、充分理解各条款内容,以及开通或使用某项服务的单独协议,并选择接受或不接受。</div>
<div>2. 除非您已阅读并接受本协议所有条款,否则您无权使用平台的各项服务(以下简称"本服务”)。您在平台的登录、查看、发布信息等行为即视为您已阅读并同意本协议的约束,包括接受更新后的本协议条款。当您与平台发生争议时,应以最新的服务协议为准。</div> <div>2. 除非您已阅读并接受本协议所有条款,否则您无权使用平台的各项服务(以下简称"本服务”)。您在平台的登录、查看、发布信息等行为即视为您已阅读并同意本协议的约束,包括接受更新后的本协议条款。当您与平台发生争议时,应以最新的服务协议为准。</div>
@@ -58,7 +57,7 @@
<div>6. 本协议的条款适用于平台提供的各种服务,但当您使用平台某特定服务时,如该服务另有单独的服务条款、指引或规则,您应遵守本协议及平台随时公布的与该服务相关的服务条款、指引或规则等。前述所有的指引和规则,均构成本协议的一部分。除非本协议另有其他明示规定,新推出的服务、增加或强化目前本服务的新功能,均受到本协议之规范。</div> <div>6. 本协议的条款适用于平台提供的各种服务,但当您使用平台某特定服务时,如该服务另有单独的服务条款、指引或规则,您应遵守本协议及平台随时公布的与该服务相关的服务条款、指引或规则等。前述所有的指引和规则,均构成本协议的一部分。除非本协议另有其他明示规定,新推出的服务、增加或强化目前本服务的新功能,均受到本协议之规范。</div>
<div>7. 如对本协议内容有任何疑问、意见或建议,您可与我们联系。</div> <div>7. 如对本协议内容有任何疑问、意见或建议,您可与我们联系。</div>
<div>三、本协议的确认和接纳</div> <div>三、本协议的确认和接纳</div>
<div>1、平台的各项服务的所有权和运作权归新疆油田所有。您同意所有注册协议条款,才能成为平台平台的正式用户。您确认:本协议条款是处理双方权利义务的契约,始终有效,法律另有强制性规定或双方另有特别约定的,依其规定。</div> <div>1、平台的各项服务的所有权和运作权归本司所有。您同意所有注册协议条款,才能成为平台平台的正式用户。您确认:本协议条款是处理双方权利义务的契约,始终有效,法律另有强制性规定或双方另有特别约定的,依其规定。</div>
<div>2、您点击注册登录,即视为您确认自己具有享受平台服务相应的权利能力和行为能力,能够独立承担法律责任。</div> <div>2、您点击注册登录,即视为您确认自己具有享受平台服务相应的权利能力和行为能力,能够独立承担法律责任。</div>
<div>3、平台通过互联网为您提供各项服务,并承担本协议和其它服务协议中对您的责任和义务。为使用本服务,您必须能够自行通过有法律资格的第三方对您提供互联网接入服务,并自行承担以下内容:</div> <div>3、平台通过互联网为您提供各项服务,并承担本协议和其它服务协议中对您的责任和义务。为使用本服务,您必须能够自行通过有法律资格的第三方对您提供互联网接入服务,并自行承担以下内容:</div>
<div>(1)自行配备上网所需的设备,包括手机、个人电脑、调制解调器及其他必要的设备装置。</div> <div>(1)自行配备上网所需的设备,包括手机、个人电脑、调制解调器及其他必要的设备装置。</div>
@@ -95,8 +94,8 @@
<div>2、 请您在支付费用时,仔细阅读收费服务的相关协议,除协议约定的情况外,否则,平台将不予退费。退费的方式视支付方式的不同而不同。员工应严格依据退费方式及退费周期收回退款。</div> <div>2、 请您在支付费用时,仔细阅读收费服务的相关协议,除协议约定的情况外,否则,平台将不予退费。退费的方式视支付方式的不同而不同。员工应严格依据退费方式及退费周期收回退款。</div>
<div>3、平台可能根据实际需要对收费服务的收费标准、方式进行修改和变更,平台也可能会对部分免费服务变更收费。如果您不同意上述修改、变更或付费内容,则应停止使用该服务。</div> <div>3、平台可能根据实际需要对收费服务的收费标准、方式进行修改和变更,平台也可能会对部分免费服务变更收费。如果您不同意上述修改、变更或付费内容,则应停止使用该服务。</div>
<div>八、服务暂停、变更与中止条款</div> <div>八、服务暂停、变更与中止条款</div>
<div>1、鉴于移动互联网服务的特殊性,健康油我平台有权随时变更、中止或终上部分或全部的服务。如变更、中止或终止的服务属于免费服务,平台无需通知您,也无需对您或任何第三方承担任何责任。</div> <div>1、鉴于移动互联网服务的特殊性,平台有权随时变更、中止或终上部分或全部的服务。如变更、中止或终止的服务属于免费服务,平台无需通知您,也无需对您或任何第三方承担任何责任。</div>
<div>2、您理解,因不可抗力、网络状况、通讯线路、用户自身过错等技术原因,或平台需要进行定期或不定期地对所提供的服务进行维护升级、对相关的设备进行检修或者维护,如因此类情况而造成本服务在合理时间内的中断,健康油我平台无需为此承担任何责任。</div> <div>2、您理解,因不可抗力、网络状况、通讯线路、用户自身过错等技术原因,或平台需要进行定期或不定期地对所提供的服务进行维护升级、对相关的设备进行检修或者维护,如因此类情况而造成本服务在合理时间内的中断,平台无需为此承担任何责任。</div>
<div>3、 如发生下列任何一种情形,平台有权随时中断或终止向您提供本协议项下的服务而无需对您或任何第三方承担任何责任:</div> <div>3、 如发生下列任何一种情形,平台有权随时中断或终止向您提供本协议项下的服务而无需对您或任何第三方承担任何责任:</div>
<div>(1)您违反国家有关法律法规或平台规定,侵害他人合法权益的;</div> <div>(1)您违反国家有关法律法规或平台规定,侵害他人合法权益的;</div>
<div>(2)您提供的个人资料不真实;</div> <div>(2)您提供的个人资料不真实;</div>
@@ -114,7 +113,7 @@
<div>1、 您一旦接受本协议,即表明您在任何时间在平台发表的任何形式的信息内容(包括但不限于员工评价、员工咨询、各类话题文章等信息内容)产生的权利,如著作权财产权(包括并不限于:复制权、发行权、出租权、展览权、表演权、放映权、广播权、信息网络传播权、摄制权、改编权、翻译权、汇编权以及应当由著作权人享有的其他可转让权利), 全部独家且不可撤销地转让给平台所有,您同意平台有权就任何主体侵权而单独提起诉讼。</div> <div>1、 您一旦接受本协议,即表明您在任何时间在平台发表的任何形式的信息内容(包括但不限于员工评价、员工咨询、各类话题文章等信息内容)产生的权利,如著作权财产权(包括并不限于:复制权、发行权、出租权、展览权、表演权、放映权、广播权、信息网络传播权、摄制权、改编权、翻译权、汇编权以及应当由著作权人享有的其他可转让权利), 全部独家且不可撤销地转让给平台所有,您同意平台有权就任何主体侵权而单独提起诉讼。</div>
<div>2、本协议已经构成《中华人民共和国著作权法》第二十五条(条文序号依照2011年版著作权法确定)及相关法律规定的著作财产权等权利转让书面协议,其效力及于您在平台上发布的任何受著作权法保护的作品内容,无论该等内容形成于本协议订立前还是本协议订立后。</div> <div>2、本协议已经构成《中华人民共和国著作权法》第二十五条(条文序号依照2011年版著作权法确定)及相关法律规定的著作财产权等权利转让书面协议,其效力及于您在平台上发布的任何受著作权法保护的作品内容,无论该等内容形成于本协议订立前还是本协议订立后。</div>
<div>3、您同意并已充分了解本协议的条款,承诺不将已发表于平台的信息,以任何形式发布或授权其它主体以任何方式使用(包括但限于在各类网站、媒体上使用)、</div> <div>3、您同意并已充分了解本协议的条款,承诺不将已发表于平台的信息,以任何形式发布或授权其它主体以任何方式使用(包括但限于在各类网站、媒体上使用)、</div>
<div>4、新疆油田是平台的制作者,拥有平台内容及资源的著作权等合法权利,受国家法律保护,有权不时地对本协议及平台的内容进行修改,并在平台张贴,无须另行通知您。在法律允许的最大限度范围内,平台对本协议及的内容拥有解释权。</div> <div>4、本司是平台的制作者,拥有平台内容及资源的著作权等合法权利,受国家法律保护,有权不时地对本协议及平台的内容进行修改,并在平台张贴,无须另行通知您。在法律允许的最大限度范围内,平台对本协议及的内容拥有解释权。</div>
<div>5、除法律另有强制性规定外,未经平台明确的特别书面许可,任何单位或个人不得以任何方式非法地全部或部分复制、转载、引用、链接、抓取或以其他方式使用平台的信息内容,否则,平台有权追究其法律责任。</div> <div>5、除法律另有强制性规定外,未经平台明确的特别书面许可,任何单位或个人不得以任何方式非法地全部或部分复制、转载、引用、链接、抓取或以其他方式使用平台的信息内容,否则,平台有权追究其法律责任。</div>
<div>6、平台所刊登的资料信息(诸如文字、图表、标识、按钮图标、图像、声音文件片段、数字下载、数据编辑和软件),均是平台或其内容提供者的财产,受中国和国际版权法的保护。平台上所有内容的汇编是平台的排他财产,受中国和国际版权法的保护。平台上的所有软件都是平台的财产,受中国和国际版权法的保护。</div> <div>6、平台所刊登的资料信息(诸如文字、图表、标识、按钮图标、图像、声音文件片段、数字下载、数据编辑和软件),均是平台或其内容提供者的财产,受中国和国际版权法的保护。平台上所有内容的汇编是平台的排他财产,受中国和国际版权法的保护。平台上的所有软件都是平台的财产,受中国和国际版权法的保护。</div>
<div>十二、特别授权</div> <div>十二、特别授权</div>
@@ -6,8 +6,6 @@
<div class="anticon" :class="getAppLogoClass" @click="goHome"> <div class="anticon" :class="getAppLogoClass" @click="goHome">
<div class="ml-2 truncate md:opacity-100" v-show="showTitle"> <div class="ml-2 truncate md:opacity-100" v-show="showTitle">
<!-- <div class="logo-left" v-if="getEnvInfo().VITE_PLATFORM === 'YT'">--> <!-- <div class="logo-left" v-if="getEnvInfo().VITE_PLATFORM === 'YT'">-->
<!-- <div>长庆</div>-->
<!-- <div>油田</div>-->
<!-- </div>--> <!-- </div>-->
<div>{{ getTitle(title) }}</div> <div>{{ getTitle(title) }}</div>
</div> </div>
+2 -2
View File
@@ -26,12 +26,12 @@ export function isShowIdCard() {
return (res.VITE_PLATFORM !== 'JD') as boolean; return (res.VITE_PLATFORM !== 'JD') as boolean;
} }
// 判断是否为青海 // 判断是否为
export function isQH() { export function isQH() {
const res = getEnvInfo(); const res = getEnvInfo();
return (res.VITE_PLATFORM === 'QH') as boolean; return (res.VITE_PLATFORM === 'QH') as boolean;
} }
// 判断是否为油田 // 判断是否为
export function isYT() { export function isYT() {
const res = getEnvInfo(); const res = getEnvInfo();
return (res.VITE_PLATFORM === 'YT') as boolean; return (res.VITE_PLATFORM === 'YT') as boolean;
+11 -2
View File
@@ -50,6 +50,7 @@
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'action'"> <template v-if="column.dataIndex === 'action'">
<a-button type="link" size="small" @click="handlePreview(record)">查看</a-button> <a-button type="link" size="small" @click="handlePreview(record)">查看</a-button>
<a-button type="link" size="small" @click="handlePublish(record)">上架</a-button>
</template> </template>
</template> </template>
</a-table> </a-table>
@@ -123,6 +124,7 @@
approveApi, approveApi,
rejectApi, rejectApi,
} from './check.api'; } from './check.api';
import { publishApi } from '/@/views/resource/resource.api';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { CONTENT_TYPE } from '/@/views/resource/resource.data'; import { CONTENT_TYPE } from '/@/views/resource/resource.data';
import PreviewModal from '/@/views/resource/components/PreviewModal.vue'; import PreviewModal from '/@/views/resource/components/PreviewModal.vue';
@@ -174,11 +176,11 @@
]; ];
const recordColumns = [ const recordColumns = [
{ title: '资源名称', dataIndex: 'title' }, { title: '资源名称', dataIndex: 'resourceTitle' },
{ title: '操作', dataIndex: 'action', align: 'center', width: 100 }, { title: '操作', dataIndex: 'action', align: 'center', width: 100 },
{ title: '审核人', dataIndex: 'auditBy', align: 'center', width: 120 }, { title: '审核人', dataIndex: 'auditBy', align: 'center', width: 120 },
{ title: '时间', dataIndex: 'auditTime', align: 'center', width: 170 }, { title: '时间', dataIndex: 'auditTime', align: 'center', width: 170 },
{ title: '意见', dataIndex: 'auditOpinion' }, { title: '意见', dataIndex: 'auditRejectReason' },
]; ];
function getTypeLabel(type: string) { function getTypeLabel(type: string) {
@@ -210,6 +212,13 @@
createMessage.info(`重新编辑:${record.title}(跳转到资源管理编辑)`); createMessage.info(`重新编辑:${record.title}(跳转到资源管理编辑)`);
} }
// V3:已通过的资源需手动上架
async function handlePublish(record: any) {
await publishApi({ id: record.id });
createMessage.success('已上架');
loadPassed();
}
async function loadPending() { async function loadPending() {
pendingLoading.value = true; pendingLoading.value = true;
try { try {
@@ -70,7 +70,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch, onMounted, type PropType, type Ref } from 'vue'; import { ref, watch, onMounted } from 'vue';
import { useMessage } from '/@/hooks/web/useMessage'; import { useMessage } from '/@/hooks/web/useMessage';
import { pushResourceListApi, pushSendApi, pushBatchSendApi } from './firstAidPush.api'; import { pushResourceListApi, pushSendApi, pushBatchSendApi } from './firstAidPush.api';
import { categoryListAllApi } from '/@/views/resource/resource.api'; import { categoryListAllApi } from '/@/views/resource/resource.api';
@@ -78,7 +78,6 @@
const props = defineProps({ const props = defineProps({
orderId: { type: String, default: '' }, orderId: { type: String, default: '' },
iframeRef: { type: Object as PropType<Ref<HTMLIFrameElement>>, default: null },
hasInteractedWithIm: { type: Boolean, default: false }, hasInteractedWithIm: { type: Boolean, default: false },
}); });
@@ -142,29 +141,6 @@
loadList(); loadList();
} }
// 通过 IM iframe 的 postMessage 发送自定义消息到当前会话
// 复用 iweb 端 message-custom.vue 中 businessID='system' 的卡片渲染
function postMessageToIm(item: any) {
// Vue 3 中父组件传 ref 给子组件 prop 会自动解包,props.iframeRef 已是 iframe DOM 元素
const iframe = props.iframeRef as any;
if (!iframe?.contentWindow) {
return false;
}
const payload = {
code: 'custom',
data: {
businessID: 'system',
title: item.title,
messageImageUrl: item.coverImage || '',
content: item.summary || '',
// 唯一时间戳,避免 IM SDK 对相同 payload 去重导致重复推送不显示
_ts: Date.now(),
},
};
iframe.contentWindow.postMessage(JSON.stringify(payload), '*');
return true;
}
async function pushSingle(item: any) { async function pushSingle(item: any) {
if (!props.hasInteractedWithIm) { if (!props.hasInteractedWithIm) {
createMessage.warning('请先在左侧选择会话'); createMessage.warning('请先在左侧选择会话');
@@ -176,10 +152,8 @@
} }
pushingId.value = item.id; pushingId.value = item.id;
try { try {
// 1. 调后端接口写入推送记录(后端会触发推送通知给目标用户 // 调后端接口推送 article 卡片到当前会话(后端调用 IM 推送自定义消息
await pushSendApi({ resourceId: item.id, orderId: props.orderId }); await pushSendApi({ resourceId: item.id, orderId: props.orderId });
// 2. 同时通过 IM iframe 推送卡片到当前会话(即时展示)
postMessageToIm(item);
item.pushed = true; item.pushed = true;
createMessage.success(`已推送:${item.title}`); createMessage.success(`已推送:${item.title}`);
} finally { } finally {
@@ -203,16 +177,11 @@
} }
batchLoading.value = true; batchLoading.value = true;
try { try {
// 1. 调后端接口批量推送(后端按条件查出资源并触发推送通知 // 调后端接口批量推送(后端按条件查出资源并触发 IM 推送 article 卡片
await pushBatchSendApi({ await pushBatchSendApi({
orderId: props.orderId, orderId: props.orderId,
resourceIds: unpushed.map((item) => item.id), resourceIds: unpushed.map((item) => item.id),
}); });
// 2. 同时通过 IM iframe 推送卡片到当前会话(即时展示)
for (const item of unpushed) {
postMessageToIm(item);
await new Promise((resolve) => setTimeout(resolve, 100));
}
list.value.forEach((item) => (item.pushed = true)); list.value.forEach((item) => (item.pushed = true));
createMessage.success(`已推送 ${unpushed.length} 条宣教内容`); createMessage.success(`已推送 ${unpushed.length} 条宣教内容`);
} finally { } finally {
@@ -13,7 +13,7 @@ const api = {
stationSave: '/health-emergency/api/emergency/order/stationSendOrder', // 驻场派单保存 stationSave: '/health-emergency/api/emergency/order/stationSendOrder', // 驻场派单保存
secondDepart: '/sys/sysDepart/allSecondaryDeparts', // 查询所有二级部门 secondDepart: '/sys/sysDepart/allSecondaryDeparts', // 查询所有二级部门
employList: '/sys/healthUserEmployeeEx/list', // 查询二级部门下的员工 employList: '/sys/healthUserEmployeeEx/list', // 查询二级部门下的员工
listByMc: '/sys/healthUserEmployeeEx/listByMc', // 日常诊疗,长庆员工列表最新接口 2024年4月19日09:42:30 listByMc: '/sys/healthUserEmployeeEx/listByMc', // 日常诊疗,员工列表最新接口 2024年4月19日09:42:30
employListNew: '/health-system/sys/healthUserEmployeeEx/listUserBackByOrg', // 查询二级部门下的员工新 employListNew: '/health-system/sys/healthUserEmployeeEx/listUserBackByOrg', // 查询二级部门下的员工新
updateSalvageUser: '/health-emergency/api/emergency/order/updateSalvageUser', // 驻场派单保存 updateSalvageUser: '/health-emergency/api/emergency/order/updateSalvageUser', // 驻场派单保存
stat: '/health-emergency/api/emergency/stat', //数据统计 stat: '/health-emergency/api/emergency/stat', //数据统计
@@ -153,7 +153,7 @@
</a-row> </a-row>
<forHelpAdv :isSpecialized="true" ref="forHelpAdvRef" /> <forHelpAdv :isSpecialized="true" ref="forHelpAdvRef" />
<detail ref="detailRef" /> <detail ref="detailRef" />
<FirstAidPush :orderId="orderId" :iframeRef="iframeRef" :hasInteractedWithIm="hasInteractedWithIm" /> <FirstAidPush :orderId="orderId" :hasInteractedWithIm="hasInteractedWithIm" />
</div> </div>
</div> </div>
</div> </div>
@@ -88,7 +88,13 @@
haveInfo.value = res; haveInfo.value = res;
}); });
if (getDisabled(nowDate.value)) {
await getInfo(); await getInfo();
} else {
// 本地记录无数据:清空展示数据并行内提示具体日期
infoData.value = {};
noDataTip.value = `${dayjs(nowDate.value).format('YYYY-MM-DD')}】暂无监测数据`;
}
setModalProps({ footer: false }); setModalProps({ footer: false });
}); });
@@ -159,7 +165,13 @@
); );
function changeValue() { function changeValue() {
if (getDisabled(nowDate.value)) {
getInfo(); getInfo();
} else {
// 本地记录无数据:清空展示数据并行内提示具体日期
infoData.value = {};
noDataTip.value = `${dayjs(nowDate.value).format('YYYY-MM-DD')}】暂无监测数据`;
}
} }
// 加载指定年份的 bgc 标记数据 // 加载指定年份的 bgc 标记数据
@@ -168,32 +168,30 @@
let xArr: any[] = []; let xArr: any[] = [];
let yArr: any[] = []; let yArr: any[] = [];
res.timePeriods && res.timePeriods &&
res.timePeriods.map((item) => { res.timePeriods.forEach((item) => {
switch (item.flag) { switch (item.flag) {
case 1: case 1:
// 浅睡 // 浅睡
yArr.push(1); yArr.push(1, 1, null);
yArr.push(1);
break; break;
case 3: case 3:
// 深睡 // 深睡
yArr.push(0); yArr.push(0, 0, null);
yArr.push(0);
break; break;
case 4: case 4:
// 清醒 // 清醒
yArr.push(3); yArr.push(3, 3, null);
yArr.push(3);
break; break;
case 5: case 5:
// 零星小睡 // 零星小睡
yArr.push(2); yArr.push(2, 2, null);
yArr.push(2);
break; break;
} }
xArr.push(dayjs(item.start).format('HH:mm')); xArr.push(dayjs(item.start).format('HH:mm'), dayjs(item.end).format('HH:mm'), '');
xArr.push(dayjs(item.end).format('HH:mm'));
}); });
// 去掉最后多余的 null
if (yArr.length > 0) yArr.pop();
if (xArr.length > 0) xArr.pop();
const option = { const option = {
xAxis: { xAxis: {
type: 'category', type: 'category',
@@ -4,17 +4,6 @@
<a-form-item label="分类名称" required> <a-form-item label="分类名称" required>
<a-input v-model:value="formState.name" placeholder="请输入分类名称" :maxlength="50" /> <a-input v-model:value="formState.name" placeholder="请输入分类名称" :maxlength="50" />
</a-form-item> </a-form-item>
<a-form-item label="父级分类">
<a-tree-select
v-model:value="formState.parentId"
:tree-data="categoryTreeData"
:field-names="{ label: 'name', value: 'id', children: 'children' }"
placeholder="顶级分类(不选)"
allow-clear
tree-default-expand-all
:disabled="!!formState.id"
/>
</a-form-item>
<a-form-item label="排序号"> <a-form-item label="排序号">
<a-input-number v-model:value="formState.sortNo" :min="0" style="width: 100%" /> <a-input-number v-model:value="formState.sortNo" :min="0" style="width: 100%" />
</a-form-item> </a-form-item>
@@ -26,20 +15,19 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'; import { ref, reactive } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal'; import { BasicModal, useModalInner } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage'; import { useMessage } from '/@/hooks/web/useMessage';
import { categoryListAllApi, categoryAddApi, categoryEditApi, buildTree } from '../label.api'; import { categoryAddApi, categoryEditApi } from '../label.api';
const emit = defineEmits(['success', 'register']); const emit = defineEmits(['success', 'register']);
const { createMessage } = useMessage(); const { createMessage } = useMessage();
const title = ref('新增分类'); const title = ref('新增分类');
const categoryTreeData = ref<any[]>([]);
// V3:分类扁平化,仅一级,不再有 parentId
const formState = reactive({ const formState = reactive({
id: '', id: '',
name: '', name: '',
parentId: undefined as string | undefined,
sortNo: 1, sortNo: 1,
description: '', description: '',
}); });
@@ -49,21 +37,10 @@
title.value = isUpdate ? '编辑分类' : '新增分类'; title.value = isUpdate ? '编辑分类' : '新增分类';
formState.id = data?.record?.id || ''; formState.id = data?.record?.id || '';
formState.name = data?.record?.name || ''; formState.name = data?.record?.name || '';
formState.parentId = data?.record?.parentId || (data?.parentId || undefined);
formState.sortNo = data?.record?.sortNo ?? 1; formState.sortNo = data?.record?.sortNo ?? 1;
formState.description = data?.record?.description || ''; formState.description = data?.record?.description || '';
// 编辑时父级分类禁用(避免循环引用)
if (isUpdate) {
formState.parentId = data?.record?.parentId || undefined;
}
await loadCategoryTree();
}); });
async function loadCategoryTree() {
const list = (await categoryListAllApi()) || [];
categoryTreeData.value = buildTree(list);
}
function validate(): boolean { function validate(): boolean {
if (!formState.name) { if (!formState.name) {
createMessage.warning('请输入分类名称'); createMessage.warning('请输入分类名称');
@@ -79,7 +56,6 @@
const params = { const params = {
id: formState.id || undefined, id: formState.id || undefined,
name: formState.name, name: formState.name,
parentId: formState.parentId || undefined,
sortNo: formState.sortNo, sortNo: formState.sortNo,
description: formState.description, description: formState.description,
}; };
@@ -96,8 +72,4 @@
setModalProps({ confirmLoading: false }); setModalProps({ confirmLoading: false });
} }
} }
onMounted(() => {
loadCategoryTree();
});
</script> </script>
+33 -1
View File
@@ -1,6 +1,16 @@
<template> <template>
<BasicModal v-bind="$attrs" @register="registerModal" :title="title" :width="480" :minHeight="260" @ok="handleSubmit"> <BasicModal v-bind="$attrs" @register="registerModal" :title="title" :width="480" :minHeight="260" @ok="handleSubmit">
<a-form layout="vertical"> <a-form layout="vertical">
<a-form-item label="所属分类" required>
<a-select
v-model:value="formState.parentId"
:options="categoryOptions"
:field-names="{ label: 'name', value: 'id' }"
placeholder="请选择所属分类"
:disabled="!!lockParentId"
allow-clear
/>
</a-form-item>
<a-form-item label="标签名称" required> <a-form-item label="标签名称" required>
<a-input v-model:value="formState.name" placeholder="请输入标签名称" :maxlength="20" /> <a-input v-model:value="formState.name" placeholder="请输入标签名称" :maxlength="20" />
</a-form-item> </a-form-item>
@@ -15,18 +25,23 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive } from 'vue'; import { ref, reactive, onMounted } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal'; import { BasicModal, useModalInner } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage'; import { useMessage } from '/@/hooks/web/useMessage';
import { tagAddApi, tagEditApi } from '../label.api'; import { tagAddApi, tagEditApi } from '../label.api';
import { categoryListAllApi } from '/@/views/resource/resource.api';
const emit = defineEmits(['success', 'register']); const emit = defineEmits(['success', 'register']);
const { createMessage } = useMessage(); const { createMessage } = useMessage();
const title = ref('新增标签'); const title = ref('新增标签');
const categoryOptions = ref<any[]>([]);
// 从分类的「+标签」按钮进来时锁定所属分类
const lockParentId = ref('');
const formState = reactive({ const formState = reactive({
id: '', id: '',
name: '', name: '',
parentId: '' as string,
sortNo: 1, sortNo: 1,
description: '', description: '',
}); });
@@ -36,11 +51,19 @@
title.value = isUpdate ? '编辑标签' : '新增标签'; title.value = isUpdate ? '编辑标签' : '新增标签';
formState.id = data?.record?.id || ''; formState.id = data?.record?.id || '';
formState.name = data?.record?.name || ''; formState.name = data?.record?.name || '';
// V3:支持从分类「+标签」按钮进来时预设 parentId 并锁定
const presetParentId = data?.parentId || data?.record?.parentId || '';
formState.parentId = presetParentId;
lockParentId.value = data?.parentId ? presetParentId : '';
formState.sortNo = data?.record?.sortNo ?? 1; formState.sortNo = data?.record?.sortNo ?? 1;
formState.description = data?.record?.description || ''; formState.description = data?.record?.description || '';
}); });
function validate(): boolean { function validate(): boolean {
if (!formState.parentId) {
createMessage.warning('请选择所属分类');
return false;
}
if (!formState.name) { if (!formState.name) {
createMessage.warning('请输入标签名称'); createMessage.warning('请输入标签名称');
return false; return false;
@@ -55,6 +78,7 @@
const params = { const params = {
id: formState.id || undefined, id: formState.id || undefined,
name: formState.name, name: formState.name,
parentId: formState.parentId,
sortNo: formState.sortNo, sortNo: formState.sortNo,
description: formState.description, description: formState.description,
}; };
@@ -71,4 +95,12 @@
setModalProps({ confirmLoading: false }); setModalProps({ confirmLoading: false });
} }
} }
onMounted(async () => {
try {
categoryOptions.value = (await categoryListAllApi()) || [];
} catch {
categoryOptions.value = [];
}
});
</script> </script>
+26 -52
View File
@@ -29,7 +29,7 @@
<a-tag v-if="status === 2" color="red" style="margin-left: 4px">冻结</a-tag> <a-tag v-if="status === 2" color="red" style="margin-left: 4px">冻结</a-tag>
<span class="tree-actions"> <span class="tree-actions">
<a-button type="link" size="small" @click.stop="handleEditCategory({ id, name })"></a-button> <a-button type="link" size="small" @click.stop="handleEditCategory({ id, name })"></a-button>
<a-button type="link" size="small" @click.stop="handleAddSubCategory(id)">+</a-button> <a-button type="link" size="small" @click.stop="handleAddTagUnderCategory(id)">+标签</a-button>
<a-button type="link" size="small" danger @click.stop="handleDeleteCategory({ id, name })"></a-button> <a-button type="link" size="small" danger @click.stop="handleDeleteCategory({ id, name })"></a-button>
</span> </span>
</div> </div>
@@ -70,14 +70,8 @@
</a-card> </a-card>
<a-card title="分类权限配置" :bordered="false" class="card-section"> <a-card title="分类权限配置" :bordered="false" class="card-section">
<!-- <a-alert message="权限矩阵接口后端尚未提供,当前为占位展示" type="info" show-icon style="margin-bottom: 12px" />--> <!-- <a-alert message="权限矩阵接口后端尚未提供,当前为占位展示" type="info" show-icon style="margin-bottom: 12px" />-->
<a-table <a-table :columns="permissionColumns" :data-source="permissionList" :pagination="false" size="middle" bordered>
:columns="permissionColumns"
:data-source="permissionList"
:pagination="false"
size="middle"
bordered
>
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.dataIndex !== 'role'"> <template v-if="column.dataIndex !== 'role'">
<a-tag :color="getPermissionColor(record.permissions[column.idx])"> <a-tag :color="getPermissionColor(record.permissions[column.idx])">
@@ -93,22 +87,16 @@
<!-- 分类新增/编辑弹窗 --> <!-- 分类新增/编辑弹窗 -->
<CategoryFormModal @register="registerCategoryModal" @success="loadCategoryTree" /> <CategoryFormModal @register="registerCategoryModal" @success="loadCategoryTree" />
<!-- 标签新增/编辑弹窗 --> <!-- 标签新增/编辑弹窗 -->
<TagFormModal @register="registerTagModal" @success="loadTags" /> <TagFormModal @register="registerTagModal" @success="loadCategoryTree" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'; import { ref, computed, onMounted } from 'vue';
import { PlusOutlined } from '@ant-design/icons-vue'; import { PlusOutlined } from '@ant-design/icons-vue';
import { useMessage } from '/@/hooks/web/useMessage'; import { useMessage } from '/@/hooks/web/useMessage';
import { useModal } from '/@/components/Modal'; import { useModal } from '/@/components/Modal';
import { import { categoryListAllApi, categoryDeleteApi, tagDeleteApi, buildTree } from './label.api';
categoryListAllApi,
categoryDeleteApi,
tagListAllApi,
tagDeleteApi,
buildTree,
} from './label.api';
import CategoryFormModal from './components/CategoryFormModal.vue'; import CategoryFormModal from './components/CategoryFormModal.vue';
import TagFormModal from './components/TagFormModal.vue'; import TagFormModal from './components/TagFormModal.vue';
@@ -142,11 +130,13 @@
return filter(categoryTree.value); return filter(categoryTree.value);
}); });
const categoryTreeSelectData = computed(() => categoryTree.value); function onSelectCategory(keys: string[]) {
// V3:标签数据从分类的 tags 字段取,不再单独调接口
function onSelectCategory(keys: string[], info: any) {
if (keys.length > 0) { if (keys.length > 0) {
createMessage.info(`已选中分类:${info.node.name}`); const category = categoryList.value.find((c) => c.id === keys[0]);
tagList.value = category?.tags || [];
} else {
tagList.value = [];
} }
} }
@@ -154,8 +144,9 @@
openCategoryModal(true, { isUpdate: false, parentId }); openCategoryModal(true, { isUpdate: false, parentId });
} }
function handleAddSubCategory(parentId: string) { // V3:分类项后面的「+标签」按钮,新增该分类下的标签
openCategoryModal(true, { isUpdate: false, parentId }); function handleAddTagUnderCategory(parentId: string) {
openTagModal(true, { isUpdate: false, parentId });
} }
function handleEditCategory(record: any) { function handleEditCategory(record: any) {
@@ -195,7 +186,7 @@
function handleDeleteTag(tag: any) { function handleDeleteTag(tag: any) {
tagDeleteApi({ id: tag.id }, () => { tagDeleteApi({ id: tag.id }, () => {
createMessage.success('已删除标签'); createMessage.success('已删除标签');
loadTags(); loadCategoryTree();
}); });
} }
@@ -229,22 +220,6 @@
return code; return code;
} }
// ============ 弹窗状态 ============
const categoryModalVisible = ref(false);
const categoryModalTitle = ref('新增分类');
const categorySubmitting = ref(false);
const categoryForm = reactive({
id: '',
name: '',
parentId: undefined as string | undefined,
sortNo: 1,
description: '',
});
const tagModalVisible = ref(false);
const tagSubmitting = ref(false);
const tagForm = reactive({ name: '', sortNo: 1, description: '' });
// ============ 加载 ============ // ============ 加载 ============
async function loadCategoryTree() { async function loadCategoryTree() {
categoryLoading.value = true; categoryLoading.value = true;
@@ -255,24 +230,23 @@
if (expandedKeys.value.length === 0 && categoryTree.value.length > 0) { if (expandedKeys.value.length === 0 && categoryTree.value.length > 0) {
expandedKeys.value = [categoryTree.value[0].id]; expandedKeys.value = [categoryTree.value[0].id];
} }
// V3:默认选中第一个分类,标签从分类的 tags 字段取
if (selectedKeys.value.length === 0 && categoryList.value.length > 0) {
const first = categoryList.value[0];
selectedKeys.value = [first.id];
tagList.value = first?.tags || [];
} else if (selectedKeys.value.length > 0) {
// 已选中分类,重新拉数据后刷新该分类的标签(新增/编辑/删除标签后回显最新数据)
const selected = categoryList.value.find((c) => c.id === selectedKeys.value[0]);
tagList.value = selected?.tags || [];
}
} finally { } finally {
categoryLoading.value = false; categoryLoading.value = false;
} }
} }
async function loadTags() {
tagLoading.value = true;
try {
const res = await tagListAllApi();
tagList.value = res || [];
} finally {
tagLoading.value = false;
}
}
onMounted(() => { onMounted(() => {
loadCategoryTree(); loadCategoryTree();
loadTags();
}); });
</script> </script>
+8 -8
View File
@@ -30,8 +30,8 @@ export const categoryListAllApi = (params?: any) => defHttp.get({ url: Api.categ
export const categoryListRootApi = (params?: any) => defHttp.get({ url: Api.categoryListRoot, params }); export const categoryListRootApi = (params?: any) => defHttp.get({ url: Api.categoryListRoot, params });
export const categoryListChildrenApi = (params: any) => defHttp.get({ url: Api.categoryListChildren, params }); export const categoryListChildrenApi = (params: any) => defHttp.get({ url: Api.categoryListChildren, params });
export const categoryQueryByIdApi = (params: any) => defHttp.get({ url: Api.categoryQueryById, params }); export const categoryQueryByIdApi = (params: any) => defHttp.get({ url: Api.categoryQueryById, params });
export const categoryAddApi = (params: any) => defHttp.post({ url: Api.categoryAdd, params }); export const categoryAddApi = (params: any) => defHttp.post({ url: Api.categoryAdd, params }, { successNeedMessage: false });
export const categoryEditApi = (params: any) => defHttp.post({ url: Api.categoryEdit, params }); export const categoryEditApi = (params: any) => defHttp.post({ url: Api.categoryEdit, params }, { successNeedMessage: false });
export const categoryDeleteApi = (params: any, handleSuccess: any) => { export const categoryDeleteApi = (params: any, handleSuccess: any) => {
createConfirm({ createConfirm({
@@ -39,7 +39,7 @@ export const categoryDeleteApi = (params: any, handleSuccess: any) => {
title: '确认删除', title: '确认删除',
content: '是否删除该分类?', content: '是否删除该分类?',
onOk: async () => { onOk: async () => {
await defHttp.post({ url: Api.categoryDelete, params }); await defHttp.post({ url: Api.categoryDelete, params }, { successNeedMessage: false });
handleSuccess(); handleSuccess();
}, },
}); });
@@ -51,7 +51,7 @@ export const categoryDeleteBatchApi = (params: any, handleSuccess: any) => {
title: '批量删除', title: '批量删除',
content: `是否删除选中的 ${params?.length || 0} 个分类?`, content: `是否删除选中的 ${params?.length || 0} 个分类?`,
onOk: async () => { onOk: async () => {
await defHttp.post({ url: Api.categoryDeleteBatch, params }); await defHttp.post({ url: Api.categoryDeleteBatch, params }, { successNeedMessage: false });
handleSuccess(); handleSuccess();
}, },
}); });
@@ -61,8 +61,8 @@ export const categoryDeleteBatchApi = (params: any, handleSuccess: any) => {
export const tagListApi = (params: any) => defHttp.get({ url: Api.tagList, params }); export const tagListApi = (params: any) => defHttp.get({ url: Api.tagList, params });
export const tagListAllApi = (params?: any) => defHttp.get({ url: Api.tagListAll, params }); export const tagListAllApi = (params?: any) => defHttp.get({ url: Api.tagListAll, params });
export const tagQueryByIdApi = (params: any) => defHttp.get({ url: Api.tagQueryById, params }); export const tagQueryByIdApi = (params: any) => defHttp.get({ url: Api.tagQueryById, params });
export const tagAddApi = (params: any) => defHttp.post({ url: Api.tagAdd, params }); export const tagAddApi = (params: any) => defHttp.post({ url: Api.tagAdd, params }, { successNeedMessage: false });
export const tagEditApi = (params: any) => defHttp.post({ url: Api.tagEdit, params }); export const tagEditApi = (params: any) => defHttp.post({ url: Api.tagEdit, params }, { successNeedMessage: false });
export const tagDeleteApi = (params: any, handleSuccess: any) => { export const tagDeleteApi = (params: any, handleSuccess: any) => {
createConfirm({ createConfirm({
@@ -70,7 +70,7 @@ export const tagDeleteApi = (params: any, handleSuccess: any) => {
title: '确认删除', title: '确认删除',
content: '是否删除该标签?', content: '是否删除该标签?',
onOk: async () => { onOk: async () => {
await defHttp.post({ url: Api.tagDelete, params }); await defHttp.post({ url: Api.tagDelete, params }, { successNeedMessage: false });
handleSuccess(); handleSuccess();
}, },
}); });
@@ -82,7 +82,7 @@ export const tagDeleteBatchApi = (params: any, handleSuccess: any) => {
title: '批量删除', title: '批量删除',
content: `是否删除选中的 ${params?.length || 0} 个标签?`, content: `是否删除选中的 ${params?.length || 0} 个标签?`,
onOk: async () => { onOk: async () => {
await defHttp.post({ url: Api.tagDeleteBatch, params }); await defHttp.post({ url: Api.tagDeleteBatch, params }, { successNeedMessage: false });
handleSuccess(); handleSuccess();
}, },
}); });
@@ -1,5 +1,5 @@
<template> <template>
<BasicModal v-bind="$attrs" @register="registerModal" :title="title" :width="900" :minHeight="500" :useDefaultFooter="false" @ok="handleSubmit"> <BasicModal v-bind="$attrs" @register="registerModal" :title="title" :width="900" :minHeight="500" :useDefaultFooter="false" :showCancelBtn="false" :showOkBtn="false">
<div class="resource-form"> <div class="resource-form">
<a-form layout="vertical" :model="formState"> <a-form layout="vertical" :model="formState">
<a-row :gutter="16"> <a-row :gutter="16">
@@ -33,9 +33,10 @@
<a-select <a-select
v-model:value="formState.tagIdList" v-model:value="formState.tagIdList"
mode="multiple" mode="multiple"
:options="tagOptions" :options="currentCategoryTags"
placeholder="请选择标签" placeholder="请选择分类"
:field-names="{ label: 'name', value: 'id' }" :field-names="{ label: 'name', value: 'id' }"
:disabled="!formState.categoryId"
allow-clear allow-clear
/> />
</a-form-item> </a-form-item>
@@ -88,11 +89,11 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'; import { ref, reactive, computed, onMounted } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal'; import { BasicModal, useModalInner } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage'; import { useMessage } from '/@/hooks/web/useMessage';
import { TYPE_OPTIONS, SCENE_OPTIONS } from '../resource.data'; import { TYPE_OPTIONS, SCENE_OPTIONS } from '../resource.data';
import { addApi, editApi, submitForReviewApi, categoryListAllApi, tagListAllApi } from '../resource.api'; import { addApi, editApi, submitForReviewApi, categoryListAllApi } from '../resource.api';
import JImageUpload from '/@/components/Form/src/jeecg/components/JImageUpload.vue'; import JImageUpload from '/@/components/Form/src/jeecg/components/JImageUpload.vue';
import JUpload from '/@/components/Form/src/jeecg/components/JUpload/JUpload.vue'; import JUpload from '/@/components/Form/src/jeecg/components/JUpload/JUpload.vue';
import JEditor from '/@/components/Form/src/jeecg/components/JEditor.vue'; import JEditor from '/@/components/Form/src/jeecg/components/JEditor.vue';
@@ -101,10 +102,16 @@
const { createMessage } = useMessage(); const { createMessage } = useMessage();
const title = ref('新增急救宣教资源'); const title = ref('新增急救宣教资源');
const categoryOptions = ref<any[]>([]); const categoryOptions = ref<any[]>([]);
const tagOptions = ref<any[]>([]);
// 附件列表(JUpload 用 array 绑定,提交时 JSON.stringify 为后端 fileUrl 字段) // 附件列表(JUpload 用 array 绑定,提交时 JSON.stringify 为后端 fileUrl 字段)
const fileList = ref<any[]>([]); const fileList = ref<any[]>([]);
// V3:标签从分类的 tags 字段取,选择分类后联动
const currentCategoryTags = computed(() => {
if (!formState.categoryId) return [];
const category = categoryOptions.value.find((c) => c.id === formState.categoryId);
return category?.tags || [];
});
const formState = reactive({ const formState = reactive({
id: '', id: '',
title: '', title: '',
@@ -233,8 +240,8 @@
} }
onMounted(async () => { onMounted(async () => {
// V3:标签从分类的 tags 字段取,不再单独调接口
categoryOptions.value = (await categoryListAllApi()) || []; categoryOptions.value = (await categoryListAllApi()) || [];
tagOptions.value = (await tagListAllApi()) || [];
}); });
</script> </script>
+3 -3
View File
@@ -200,10 +200,10 @@
}, },
}); });
} else if (record.status === STATUS.DRAFT && record.auditStatus === AUDIT_STATUS.PASSED) { } else if (record.status === STATUS.DRAFT && record.auditStatus === AUDIT_STATUS.PASSED) {
// 已通过的草稿状态(编辑后自动退回草稿),可重新提交 // V3:已通过待上架状态,操作为「上架」
actions.push({ actions.push({
label: '提交审核', label: '上架',
onClick: () => handleSubmitReview(record), onClick: () => handlePublish(record),
}); });
} }
return actions; return actions;
+3 -1
View File
@@ -33,10 +33,12 @@ export const CONTENT_TYPE = {
}; };
// 根据发布状态+审核状态返回展示信息 // 根据发布状态+审核状态返回展示信息
// V3:审核通过但未上架 (auditStatus=11 && status=0) 显示为「已通过」
export function getResourceDisplayStatus(status: number, auditStatus: number | null) { export function getResourceDisplayStatus(status: number, auditStatus: number | null) {
if (status === STATUS.PUBLISHED) return { text: '已发布', color: 'success' }; if (status === STATUS.PUBLISHED) return { text: '已发布', color: 'success' };
if (status === STATUS.OFFLINE) return { text: '已下架', color: 'error' }; if (status === STATUS.OFFLINE) return { text: '已下架', color: 'error' };
if (auditStatus === AUDIT_STATUS.PENDING) return { text: '待审核', color: 'warning' }; if (auditStatus === AUDIT_STATUS.PENDING) return { text: '待审核', color: 'warning' };
if (auditStatus === AUDIT_STATUS.PASSED) return { text: '已通过', color: 'processing' };
if (auditStatus === AUDIT_STATUS.REJECTED) return { text: '已驳回', color: 'error' }; if (auditStatus === AUDIT_STATUS.REJECTED) return { text: '已驳回', color: 'error' };
return { text: '草稿', color: 'default' }; return { text: '草稿', color: 'default' };
} }
@@ -61,7 +63,7 @@ export const STATUS_OPTIONS = [
// 适用场景 // 适用场景
export const SCENE_OPTIONS = [ export const SCENE_OPTIONS = [
{ label: '油田现场', value: '油田现场' }, { label: '工作现场', value: '工作现场' },
{ label: '日常生活', value: '日常生活' }, { label: '日常生活', value: '日常生活' },
{ label: '高原环境', value: '高原环境' }, { label: '高原环境', value: '高原环境' },
{ label: '高温环境', value: '高温环境' }, { label: '高温环境', value: '高温环境' },
@@ -12,6 +12,17 @@ export const columns: BasicColumn[] = [
width: 200, width: 200,
// slots: { customRender: 'esContent' }, // slots: { customRender: 'esContent' },
}, },
{
title: '员工电话',
dataIndex: 'employeePhone',
width: 140,
// 从发送内容中提取 3位~4位~4位 格式的手机号(如 185~9920~5354),取第一个匹配项并去掉分隔符显示纯数字,匹配不到则显示空
// 分隔符兼容半角~、全角~、连字符-
customRender: ({ record }) => {
const match = record?.esContent?.match(/\d{3}[~\-]\d{4}[~\-]\d{4}/);
return match ? match[0].replace(/[~\-]/g, '') : '';
},
},
{ {
title: '接收人', title: '接收人',
dataIndex: 'esReceiver', dataIndex: 'esReceiver',
@@ -50,6 +61,12 @@ export const searchFormSchema: FormSchema[] = [
field: 'esReceiver', field: 'esReceiver',
component: 'Input', component: 'Input',
}, },
{
// 员工电话: 传到后台后由后台拼回波浪线再模糊查询 esContent
label: '员工电话',
field: 'employeePhone',
component: 'Input',
},
{ {
label: '发送状态', label: '发送状态',
field: 'esSendStatus', field: 'esSendStatus',
@@ -48,6 +48,22 @@ export const columns: BasicColumn[] = [
align: 'center', align: 'center',
dataIndex: 'isLongTermEffective_dictText', dataIndex: 'isLongTermEffective_dictText',
}, },
{
title: '跳转方式',
align: 'center',
dataIndex: 'tfJump',
customRender: ({ text }) => {
// 跳转方式文案映射:0-不跳转 1-链接 2-app内
const map = { '0': '不跳转', '1': '链接', '2': 'app内' };
return map[text] ?? '';
},
},
{
title: '跳转链接',
align: 'center',
dataIndex: 'jumpUrl',
ellipsis: true,
},
]; ];
//查询数据 //查询数据
export const searchFormSchema: FormSchema[] = [ export const searchFormSchema: FormSchema[] = [
@@ -108,6 +124,33 @@ export const formSchema: FormSchema[] = [
}, },
rules: [{ required: true }], rules: [{ required: true }],
}, },
{
label: '跳转方式',
field: 'tfJump',
component: 'RadioButtonGroup',
defaultValue: '0',
componentProps: {
options: [
{ label: '不跳转', value: '0' },
{ label: '链接', value: '1' },
{ label: 'app内', value: '2' },
],
},
rules: [{ required: true, message: '请选择跳转方式' }],
},
{
label: '跳转链接',
field: 'jumpUrl',
component: 'Input',
componentProps: {
placeholder: '请输入跳转链接',
},
// 仅跳转方式为"链接"时显示且必填
ifShow: ({ model }) => model.tfJump === '1',
dynamicRules: ({ model }) => {
return [{ required: model.tfJump === '1', message: '请输入跳转链接' }];
},
},
{ {
label: '是否有效', label: '是否有效',
field: 'status', field: 'status',