Compare commits

..
93 Commits
Author SHA1 Message Date
zoujiandong bafde6e744 Merge branch 'dev' 4.8 2026-04-08 09:04:32 +08:00
zoujiandong c7dbbfda16 4.7提交 2026-04-07 14:32:00 +08:00
zoujiandong fd78b504a6 4.3提交 2026-04-03 17:32:25 +08:00
zoujiandong a26fc1a4a7 4.2 2026-04-02 14:49:21 +08:00
zoujiandong ab812a2bc6 解决滋滋声 2026-04-02 11:37:44 +08:00
zoujiandong 5d31c3658b 4.2提交 2026-04-02 11:30:39 +08:00
zoujiandong 3140db227c 3.31更新兑换积分 2026-03-31 15:05:38 +08:00
zoujiandong 5f9a346960 3.31画像提交 2026-03-31 13:07:16 +08:00
zoujiandong 7180e176eb 3.30解决bug 2026-03-30 16:12:38 +08:00
zoujiandong e3a852cb43 3.30提交 2026-03-30 09:45:55 +08:00
zoujiandong 0a51901807 3.26 2026-03-26 17:34:15 +08:00
zoujiandong 8a879df4b4 3.25提交 2026-03-25 17:33:54 +08:00
zoujiandong 18c7f7386d 3.18提交 2026-03-18 17:19:05 +08:00
zoujiandong b5cc11ddd3 3.17 2026-03-17 18:59:46 +08:00
haomingming 4a22470f38 Merge branch 'master' of https://gitea.igandanyiyuan.com/gdxz/uniapp-app
# Conflicts:
#	pages_app/patientMsg/patientMsg.vue
2026-03-17 14:53:44 +08:00
haomingming 6cdd8b90db patientList 2026-03-17 14:49:50 +08:00
zoujiandong cbc427e41c 3.17上午提交 2026-03-17 14:02:39 +08:00
zoujiandong 45a9478790 3.16 2026-03-16 14:35:45 +08:00
haomingming c6ce198c31 Merge branch 'master' of https://gitea.igandanyiyuan.com/gdxz/uniapp-app 2026-03-16 14:34:53 +08:00
haomingming f4fe516224 webview 2026-03-16 14:34:46 +08:00
zoujiandong 80772749b2 3.14提交 2026-03-14 17:32:11 +08:00
zoujiandong 0fa310e422 地址高度修改 2026-03-13 17:50:47 +08:00
zoujiandong 761a8fdaa4 3.13下班提交 2026-03-13 17:42:34 +08:00
zoujiandong ca69dfbe4b 3.13 五点更新 2026-03-13 17:13:09 +08:00
zoujiandong 4042df6762 3.13提交 2026-03-13 16:18:33 +08:00
zoujiandong 4e3a30b863 3.12晚上 2026-03-12 18:57:28 +08:00
zoujiandong 2831de027c 3.12提交 2026-03-12 14:38:11 +08:00
zoujiandong 08e3ba22c5 3.11提交 2026-03-11 13:13:10 +08:00
zoujiandong 40f5ddf4a5 3.9提交 2026-03-09 18:59:27 +08:00
zoujiandong 1fa822ed72 3.6修改 2026-03-06 19:01:31 +08:00
haomingming 89848ba321 我的账户 2026-03-06 14:40:09 +08:00
haomingming 7fb1200528 我的鲜花 2026-03-06 10:45:44 +08:00
haomingming b833a00d65 我的鲜花 2026-03-06 10:44:49 +08:00
zoujiandong e8e9c84cdf 分组数据较多时 2026-03-05 19:00:51 +08:00
haomingming 1e99b3a082 Merge branch 'master' of https://gitea.igandanyiyuan.com/gdxz/uniapp-app 2026-03-05 17:08:47 +08:00
haomingming efcc7d9603 常用银行卡 2026-03-05 17:08:18 +08:00
zoujiandong 54f8132cee ppt页面修改 2026-03-05 14:50:50 +08:00
zoujiandong ddbfac11c3 请求注释 2026-03-05 13:14:03 +08:00
zoujiandong 1de18aa228 群发消息输入 2026-03-05 10:10:58 +08:00
zoujiandong 799471090f 3.5提交代码 2026-03-05 09:57:10 +08:00
zoujiandong ae9a961a42 2.6提交 2026-02-06 17:30:16 +08:00
zoujiandong db9ca707ae 保利视频 2026-02-04 10:12:59 +08:00
zoujiandong a7b2a673a9 2.2提交 2026-02-02 17:44:10 +08:00
zoujiandong d06ee397cf 1.16提交 2026-01-16 17:36:04 +08:00
zoujiandong 034744959f 1.16更新 2026-01-16 10:58:35 +08:00
zoujiandong cf6df28e8c 1.15提交 2026-01-15 18:38:21 +08:00
zoujiandong 00d0ec8a9b 1.14提交 2026-01-14 17:41:42 +08:00
zoujiandong 56c645a75c 1.6提交 2026-01-06 08:36:02 +08:00
zoujiandong 8025b2abab 12.16提交 2025-12-16 17:42:25 +08:00
zoujiandong c86aa03f01 12.10提交 2025-12-10 17:33:39 +08:00
zoujiandong 22104881f1 12.3 2025-12-03 17:31:08 +08:00
zoujiandong f4c8df1950 111 2025-12-03 10:20:33 +08:00
zoujiandong 1785e9309e 1111 2025-12-03 10:06:59 +08:00
zoujiandong da3659412d 11111 2025-12-03 09:58:56 +08:00
zoujiandong 51367c48c9 pdf方法 2025-12-03 09:13:24 +08:00
zoujiandong 41079458dc zhinanlist报错 2025-12-03 08:40:39 +08:00
zoujiandong 60531b339e 12.2提交 2025-12-02 15:24:51 +08:00
zoujiandong 9788e723a2 11.28提交 2025-11-28 10:43:13 +08:00
zoujiandong 91726b87c5 11.21提交 2025-11-21 17:49:39 +08:00
zoujiandong 6b37f0899d 去掉toast的引入 2025-11-20 11:33:55 +08:00
zoujiandong f4b1ef932d 11.20 2025-11-20 10:29:35 +08:00
zoujiandong 76e619b85d video宽度 2025-11-17 16:08:45 +08:00
zoujiandong 2cfc225b55 Merge branch 'master' of https://gitea.igandanyiyuan.com/gdxz/uniapp-app 2025-11-17 16:05:19 +08:00
zoujiandong 2cfa4bdc53 viddeo页修改 2025-11-17 16:05:14 +08:00
haomingming 9e0a5d2512 Merge branch 'master' of https://gitea.igandanyiyuan.com/gdxz/uniapp-app 2025-11-17 15:25:52 +08:00
haomingming 589092ec62 333 2025-11-17 15:25:42 +08:00
zoujiandong 1255ce8aa5 Merge branch 'master' of https://gitea.igandanyiyuan.com/gdxz/uniapp-app 2025-11-17 11:44:19 +08:00
zoujiandong 4dca51b0a3 更改间距以及应用名称 2025-11-17 11:44:17 +08:00
haomingming 8d9ad1ca51 222 2025-11-17 11:27:15 +08:00
zoujiandong e7cebcb00c 修改遮挡 2025-11-17 11:02:15 +08:00
zoujiandong dcade02e47 17:48修改 2025-11-14 17:48:07 +08:00
zoujiandong f7d44b9c27 111 2025-11-14 16:51:24 +08:00
zoujiandong d1a4f610d3 更新其他会议 2025-11-14 16:39:19 +08:00
zoujiandong 6b238c2d33 该颜色问题 2025-11-14 15:14:53 +08:00
zoujiandong 7c46a3487a 去掉退出登录的取消按钮 2025-11-14 14:03:09 +08:00
haomingming 38d13d33ce 修改强制退出登录 2025-11-14 13:57:14 +08:00
zoujiandong 5d2d8cc9ff 改颜色 2025-11-14 13:56:17 +08:00
zoujiandong 5af6f8c906 11.14提交 2025-11-14 13:23:42 +08:00
zoujiandong 1e134f236c 11.13早上提交 2025-11-13 08:41:08 +08:00
zoujiandong 4816054b96 10.17下班提交 2025-10-17 17:54:03 +08:00
haomingming ea320f4e54 冲突 2025-10-15 13:48:52 +08:00
haomingming d44cd1516a Merge branch 'master' of https://gitea.igandanyiyuan.com/gdxz/uniapp-app
# Conflicts:
#	App.vue
#	manifest.json
2025-10-15 11:49:15 +08:00
haomingming 2a1f4a2405 测试 2025-10-15 11:44:47 +08:00
zoujiandong 2532aa055c 10.15上午更新 2025-10-15 11:40:13 +08:00
zoujiandong b16601baa6 10.14号更新 2025-10-14 17:46:23 +08:00
zoujiandong 8b1e9bcb41 10.13下午跟新 2025-10-13 17:41:23 +08:00
zoujiandong dd9b2984b2 10.9提交 2025-10-09 16:48:46 +08:00
haomingming 65b2d269c5 推送 云函数 一键登录 2025-09-30 16:42:25 +08:00
zoujiandong 570e47698c 9.26晚提交 2025-09-26 19:06:31 +08:00
haomingming 71136a03a6 Merge branch 'master' of https://gitea.igandanyiyuan.com/gdxz/uniapp-app
# Conflicts:
#	pages/my/my.vue
2025-09-26 17:16:18 +08:00
haomingming 053063dc88 启动页 推送 2025-09-26 17:13:02 +08:00
zoujiandong 4703b22d25 9.26早上提交 2025-09-26 08:52:37 +08:00
haomingming df4d95993a 版本更新 2025-09-26 08:41:01 +08:00
343 changed files with 59516 additions and 14674 deletions
+12
View File
@@ -0,0 +1,12 @@
changed 1 package, and audited 42 packages in 5s
7 packages are looking for funding
run `npm fund` for details
3 high severity vulnerabilities
Some issues need review, and may require choosing
a different dependency.
Run `npm audit` for details.
+239 -434
View File
@@ -1,459 +1,264 @@
<script > <script>
import RootStore from "@xkit-yx/im-store-v2"; import version from '@/utils/version.js'
/** esm 版本 */
//@ts-ignore
// import { V2NIMConst, NIM } from './esmNim.js'
/** 常规版本*/
import NIM from "nim-web-sdk-ng/dist/v2/NIM_UNIAPP_SDK";
import { V2NIMConst } from "nim-web-sdk-ng/dist/esm/nim";
import {
customRedirectTo,
customReLaunch,
customSwitchTab,
} from "@/utils/im/customNavigate";
import { getMsgContentTipByType } from "@/utils/im/msg";
import { STORAGE_KEY } from "@/utils/im/constants";
import { isWxApp } from "@/utils/im/index";
/** 国际化*/
import { setLanguage } from "@/utils/im/i18n";
import BASE_URL from "@/utils/config.js"
// #ifdef APP-PLUS
/** 推送插件 */
const nimPushPlugin = uni.requireNativePlugin("NIMUniPlugin-PluginModule");
/** 音视频通话插件 */
const nimCallKit = (uni.$UIKitCallKit =
uni.requireNativePlugin("netease-CallKit"));
// #endif
let startByNotificationId = "";
export default { export default {
onLaunch() { globalData: {
// #ifdef APP-PLUS plAd: true,
/** 关闭启动画面,锁定竖屏 */ dotNumber: {},
plus.navigator.closeSplashscreen(); apiHost: "https://dev-app.igandan.com/app",
plus.screen.lockOrientation("portrait-primary"); //BASE_URL='https://dev-app.igandan.com/app'
// #endif },
onLaunch(options) {
/** 设置语言 (此处为了方便demo切换语言,将其存到本地,实际需根据业务情况设置)*/ //uni.setStorageSync('apiHost', 'value')
setLanguage( console.log("打印options");
uni.getStorageSync("switchToEnglishFlag") == "en" ? "en" : "zh" console.log(options);
);
/** 已经登录了 不用走初始化逻辑*/
if ( if (
uni?.$UIKitStore?.connectStore?.connectStatus === options &&
V2NIMConst.V2NIMConnectStatus.V2NIM_CONNECT_STATUS_CONNECTED options.referrerInfo.extraData &&
options.referrerInfo.extraData.token
) { ) {
return; uni.setStorageSync(
"AUTH_TOKEN_App",
options.referrerInfo.extraData.token
);
uni.setStorageSync("userInfo", options.referrerInfo.extraData.userInfo);
if(process.env.UNI_PLATFORM =="mp-weixin"){
uni.setStorageSync("app_version",version);
}else{
let app_version=options.referrerInfo.extraData.app_version?options.referrerInfo.extraData.app_version:version;
uni.setStorageSync("app_version",app_version);
};
//getApp({allowDefault: true}).globalData.apiHost = options.referrerInfo.extraData.apiHost;
}
if (
options &&
options.referrerInfo.extraData &&
options.referrerInfo.extraData.targetPath
) {
let src=options.referrerInfo.extraData.targetPath
if(src.indexOf('downLoadVideo') > -1 ){
uni.navigateTo({
url: src+'?from=mine',
});
}else{
uni.navigateTo({
url: src,
});
}
} }
let storage_token=''; console.log("app传递登录信息onLaunch-------------------");
let storage_accid=''; try {
if (process.env.UNI_PLATFORM == "h5") { // #ifdef APP
if (window.location.href.indexOf('dev') > -1) { uni.onNativeEventReceive((event, data) => {
storage_accid=uni.getStorageSync('AUTH_YX_ACCID_App'); console.log("event", event);
storage_token=uni.setStorageSync('AUTH_YX_TOKEN_App'); console.log("data", data);
} else { if (event.indexOf("clearToken") > -1) {
storage_accid=uni.getStorageSync('DEV_AUTH_YX_ACCID_App'); uni.setStorageSync("AUTH_TOKEN_App", "");
storage_token=uni.getStorageSync('DEV_AUTH_YX_TOKEN_App'); plus.runtime.quit();
} }
} else if(process.env.UNI_PLATFORM == "mp-weixin") { if(event.indexOf("goPage") > -1){
const { if(data.indexOf('downLoadVideo') > -1){
envVersion uni.navigateTo({
} = uni.getAccountInfoSync().miniProgram; url: data+'?from=mine'
if (envVersion == "release") { });
storage_accid=uni.getStorageSync('AUTH_YX_ACCID_App'); }else{
storage_token=uni.setStorageSync('AUTH_YX_TOKEN_App'); uni.navigateTo({
} else { url: data,
storage_accid=uni.getStorageSync('DEV_AUTH_YX_ACCID_App'); });
storage_token=uni.getStorageSync('DEV_AUTH_YX_TOKEN_App'); }
} }
}else{ if(event.indexOf('paySuccess') > -1){
if (BASE_URL.indexOf('dev') == -1) { console.log('app.vue监听到成功支付');
storage_accid=uni.getStorageSync('DEV_AUTH_YX_ACCID_App'); if(data.type=='GandanFile'){
storage_token=uni.getStorageSync('DEV_AUTH_YX_TOKEN_App'); uni.$emit('paySuccess',{
type:'GandanFile',
} else { });
storage_accid=uni.getStorageSync('AUTH_YX_ACCID_App'); }else if(data.type=='BuyPoint'){
storage_token=uni.setStorageSync('AUTH_YX_TOKEN_App'); uni.navigateBack();
} }
} }
const account =storage_accid; });
const token = storage_token; // #endif
const imOptions = { } catch (error) {
appkey: "885dea390870814acf3ba8558c717572", // 请填写你的appkey console.log("app传递登录信息error-------------------");
account: account,//"9ufkll2xo57km6224xe", // 请填写你的account console.log(error);
token: token//"4918605da57e573cff93209df56f351d", // 请填写你的token
};
if (imOptions) {
this.initNim(imOptions);
} else {
/** 未登录 跳转登录页 */
// customRedirectTo({
// url: isWxApp ? "/pages/index/index" : "/pages/Login/index",
// });
} }
}, },
onShow() { onShow() {
// #ifdef APP-PLUS try {
uni?.$UIKitNIM?.V2NIMSettingService?.setAppBackground(false); // #ifdef APP
let checkTokenCallBackFlag = false;
console.log("执行onshow");
// 点击通知栏推送监听 uni.sendNativeEvent(
nimPushPlugin.addOpenNotificationListener((res) => { "checkToken",
if (typeof res == "object" && res?.sessionId && res?.sessionType) {
// 当前登录账号id 具体获取根据您的业务逻辑调整
const imOptions = uni.getStorageSync(STORAGE_KEY);
// 会话类型
const type = res?.sessionType;
// 拼装会话ID
startByNotificationId = `${imOptions.account}|${type}|${res?.sessionId}`;
}
});
// #endif
},
onHide() {
// #ifdef APP-PLUS
uni?.$UIKitNIM?.V2NIMSettingService?.setAppBackground(true);
// #endif
// 重置推送 startByNotificationId
startByNotificationId = "";
},
methods: {
initNim(opts) {
/** 保存登录信息 demo 层逻辑 具体根据您的业务调整*/
uni.setStorage({
key: STORAGE_KEY,
data: opts,
});
/** 是否开启云端会话(此处为了方便demo切换云端/本地会话,将其存到本地,实际需根据业务情况设置)*/
const enableV2CloudConversation =
uni.getStorageSync("enableV2CloudConversation") === "on";
/** 初始化 nim sdk */
//@ts-ignore
const nim = (uni.$UIKitNIM = NIM.getInstance(
{ {
appkey: opts.appkey, msg: "checkToken",
needReconnect: true,
debugLevel: "debug",
apiVersion: "v2",
enableV2CloudConversation: enableV2CloudConversation,
}, },
{ (ret) => {
V2NIMLoginServiceConfig: { checkTokenCallBackFlag = true;
/** console.log("check回调");
* 微信小程序需要使用单独的lbsUrls和linkUrl console.log(ret);
*/ if (ret.code == 0) {
lbsUrls: isWxApp uni.showModal({
? ["https://lbs.netease.im/lbs/wxwebconf.jsp"] title: "提示",
: ["https://lbs.netease.im/lbs/webconf.jsp"], showCancel: false,
linkUrl: isWxApp ? "wlnimsc0.netease.im" : "weblink.netease.im", content: "您已退出登录!",
/** success: function (res) {
* 使用固定设备ID, if (res.confirm) {
*/ uni.sendNativeEvent(
isFixedDeviceId: true, "goTabbarPage",
}, {
} msg: "home",
));
/** 初始化 im store */
// @ts-ignore
const store = (uni.$UIKitStore = new RootStore(
// @ts-ignore
nim,
{
// 添加好友是否需要验证
addFriendNeedVerify: true,
// 是否需要显示 p2p 消息、p2p会话列表消息已读未读,默认 false
p2pMsgReceiptVisible: true,
// 是否需要显示群组消息已读未读,默认 false
teamMsgReceiptVisible: true,
// 是否显示在线离线
loginStateVisible: true,
// 群组被邀请模式,默认需要验证
teamAgreeMode:
V2NIMConst.V2NIMTeamAgreeMode.V2NIM_TEAM_AGREE_MODE_NO_AUTH,
// 发送消息前回调, 可对消息体进行修改,添加自定义参数
// @ts-ignore
sendMsgBefore: async (options) => {
const pushContent = getMsgContentTipByType({
text: options.msg.text,
messageType: options.msg.messageType,
});
const yxAitMsg = options.serverExtension
? options.serverExtension.yxAitMsg
: { forcePushIDsList: "[]", needForcePush: false };
// 如果是 at 消息,需要走离线强推
// @ts-ignore
const { forcePushIDsList, needForcePush } = yxAitMsg
? // @ts-ignore
store.msgStore._formatExtAitToPushInfo(
yxAitMsg,
options.msg.text
)
: { forcePushIDsList: "[]", needForcePush: false };
const { conversationId } = options;
const conversationType =
nim.V2NIMConversationIdUtil.parseConversationType(conversationId);
const targetId =
nim.V2NIMConversationIdUtil.parseConversationTargetId(
conversationId
);
// 设置离线强推,厂商相关推送在此处配置
// 具体参考文档 https://doc.yunxin.163.com/messaging2/guide/zc4MTg5MDY?platform=client#%E7%AC%AC%E4%B8%80%E6%AD%A5%E4%B8%8A%E4%BC%A0%E6%8E%A8%E9%80%81%E8%AF%81%E4%B9%A6
const pushPayload = JSON.stringify({
pushTitle: "", // 必填,推送消息标题
notify_effect: "2", //可选项,预定义通知栏消息的点击行为。1:通知栏点击后打开app的Launcher Activity,2:通知栏点击后打开app的任一Activity(开发者还需要传入intent_uri),3:通知栏点击后打开网页(开发者还需要传入web_uri)
intent_uri:
"intent:#Intent;action=com.netease.nimlib.uniapp.push.NotificationClickActivity;component=com.netease.nim.demo/com.netease.nimlib.uniapp.push.NotificationClickActivity;launchFlags=0x04000000;i.sessionType=0;S.sessionId=cs1;end", //可选项,打开当前app的任一组件。
hwField: {
click_action: {
//必填,消息点击行为
type: 1, //必填,消息点击行为类型,取值如下:1:打开应用自定义页面 2:点击后打开特定URL 3:点击后打开应用
// 自定义页面中intent的实现,请参见指定intent参数​。当type为1时,字段intent和action至少二选一。scheme方式和指定activity方式都可以
intent:
"intent:#Intent;action=com.netease.nimlib.uniapp.push.NotificationClickActivity;component=com.netease.nim.demo/com.netease.nimlib.uniapp.push.NotificationClickActivity;launchFlags=0x04000000;i.sessionType=0;S.sessionId=cs1;end",
},
androidConfig: {
category: "IM", //可选项,标识消息类型,用于标识高优先级透传场景,详见官方文档 AndroidConfig.category
},
},
honorField: {
notification: {
// AndroidNotification
clickAction: {
//必填,消息点击行为
type: 1, //必填,消息点击行为类型,取值如下:1:打开应用自定义页面 2:点击后打开特定URL 3:点击后打开应用
//自定义页面中intent的实现,请参见指定intent参数。当type为1时,字段intent和action至少二选一。
intent: "",
},
importance: "NORMAL", //可选项,Android通知消息分类,决定用户设备消息通知行为,取值如下:LOW:资讯营销类消息 NORMAL:服务与通讯类消息
},
},
vivoField: {
skipType: "4", //必填,点击跳转类型 1:打开APP首页 2:打开链接 3:自定义 4:打开app内指定页面,默认为1
skipContent: "",
classification: "1", //可选项,消息类型 0:运营类消息,1:系统类消息。默认为0
category: "IM", // 可选项,二级分类
},
oppoField: {
channel_id: "", //可选项,指定下发的通道ID
category: "IM", //可选项,通道类别名
notify_level: 2, //通知栏消息提醒等级,1-通知栏;2-通知栏+锁屏;16-通知栏+锁屏+横幅+震动+铃声
click_action_type: "1", //点击通知栏后触发的动作类型。0(默认0.启动应用;1.跳转指定应用内页(action标签名);2.跳转网页;4.跳转指定应用内页(全路径类名);5.跳转Intent scheme URL: "",
click_action_activity: "",
action_parameters: "",
},
fcmFieldV1: {
message: {
android: {
priority: "",
data: {
sessionType: "0",
sessionId: "cs1",
}, },
notification: { (ret) => {
click_action: "", console.log(ret);
}, }
}, );
}, plus.runtime.quit();
}, } else if (res.cancel) {
console.log("用户点击取消");
// IOS apns
sessionId:
conversationType == 1
? uni.$UIKitStore.userStore.myUserInfo.accountId
: targetId,
sessionType: conversationType,
});
// @ts-ignore
const pushConfig = {
pushEnabled: true,
pushNickEnabled: true,
forcePush: needForcePush,
forcePushContent: pushContent,
forcePushAccountIds: forcePushIDsList,
pushPayload,
pushContent,
};
return { ...options, pushConfig };
},
},
"UniApp"
));
// #ifdef APP-PLUS
/** 注册推送 实际根据您在推送厂商申请的证书进行配置,具体参考文档 https://doc.yunxin.163.com/messaging2/guide/zc4MTg5MDY?platform=client#%E7%AC%AC%E4%B8%80%E6%AD%A5%E4%B8%8A%E4%BC%A0%E6%8E%A8%E9%80%81%E8%AF%81%E4%B9%A6
*/
nim.V2NIMSettingService.setOfflinePushConfig(nimPushPlugin, {
// miPush: {
// appId: "",
// appKey: "",
// certificateName: "",
// },
// hwPush: {
// appId: "",
// certificateName: "",
// },
// oppoPush: {
// appId: "",
// appKey: "",
// certificateName: "",
// secret: "",
// },
// vivoPush: {
// appId: "",
// appKey: "",
// certificateName: "",
// },
// fcmPush: {
// certificateName: "",
// },
// mzPush: {
// appId: "",
// appKey: "",
// certificateName: "",
// },
// apns: {
// certificateName: "",
// },
});
// #endif
/** nim sdk 登录 */
nim.V2NIMLoginService.login(opts.account, opts.token).then(async () => {
console.log("登录成功");
nim.V2NIMLoginService.on('onKickedOffline', (res) => {
console.log("被踢下线", res);
});
// #ifdef APP-PLUS
/** 初始化音视频通话插件*/
nimCallKit.initConfig(
{
appKey: opts.appkey, // 请填写你的appkey
account: opts.account, // 请填写你的account
token: opts.token, // 请填写你的token
apnsCername: "",
pkCername: "",
},
(ret) => {
if (ret.code != 200) {
// callkit init失败
} else {
nimCallKit.login(
{
account: opts.account,
token: opts.token,
},
function (ret) {
if (ret.code != 200) {
// 登录失败
} else {
// 登录成功
}
} }
); },
}
}
);
// #endif
// 判断时手动点击唤起 还是 点击推送通知栏唤起,点击通知栏唤起直接跳转到聊天页面
if (!startByNotificationId) {
// customSwitchTab({
// url: "/pages/Conversation/index",
// });
} else {
if (startByNotificationId) {
await uni.$UIKitStore.uiStore.selectConversation(
startByNotificationId
);
uni.navigateTo({
url: `/pages/Chat/index?conversationId=${startByNotificationId}`,
}); });
startByNotificationId = "";
} }
} }
}); );
},
logout() { // setTimeout(function(){
uni.removeStorageSync(STORAGE_KEY); // console.log("执行 setTimeout")
try { // if(!checkTokenCallBackFlag){
nimCallKit.logout({}, (ret) => { // uni.showModal({
if (ret.code != 200) { // title: '提示',
console.log("音视频通话插件退出失败"); // showCancel: false,
} else { // content: '您已退出登录',
console.log("音视频通话插件退出成功"); // success: function (res) {
} // if (res.confirm) {
}); // uni.sendNativeEvent('goTabbarPage', {
} catch (error) { // msg: 'home'
console.log("音视频通话插件退出失败", error); // }, ret => {
} // console.log(ret);
// 退出登录 // })
uni.$UIKitNIM.V2NIMLoginService.logout().then((res) => { // } else if (res.cancel) {
console.log("退出登录", res); // console.log('用户点击取消');
uni.$UIKitStore.destroy(); // }
customReLaunch({ // }
url: "/pages/Login/index", // });
}); // }
}); // }, 1000)
},
let main = plus.android.runtimeMainActivity();
//为了防止快速点按返回键导致程序退出重写quit方法改为隐藏至后台
plus.runtime.quit = function () {
main.moveTaskToBack(false);
};
//重写toast方法如果内容为 ‘再按一次退出应用’ 就隐藏应用,其他正常toast
plus.nativeUI.toast = function (str) {
if (str == "再按一次退出应用") {
main.moveTaskToBack(false);
} else {
uni.showToast({
title: str,
icon: "none",
});
}
};
// uni.onNativeEventReceive((event, data) => {
// console.log("app传递登录信息onshow-------------------");
// console.log("event", event);
// console.log("data", data);
// if (event == "loginInfo") {
// uni.setStorageSync("DEV_AUTH_YX_TOKEN_App", data.token);
// uni.setStorageSync("userInfo", data.userInfo);
// }else if(event == "showRedDot"){
// console.log("showRedDot");
// console.log(data);
// getApp().globalData.dotNumber=data;
// }
// });
// #endif
} catch (error) {
console.log("error");
console.log(error);
}
}, },
onHide() {},
methods: {},
}; };
</script> </script>
<template>
<GlobalDialog />
</template>
<style lang="scss"> <style lang="scss">
/*每个页面公共css */
@import "@/uni_modules/uni-scss/index.scss";
/* #ifndef APP-NVUE */
@import "@/static/customicons.css";
/*每个页面公共css */ // 设置整个项目的背景色
@import '@/uni_modules/uni-scss/index.scss'; page {
/* #ifndef APP-NVUE */ background-color: #f5f5f5;
@import '@/static/customicons.css'; }
// 设置整个项目的背景色
page {
background-color: #f5f5f5;
}
/* #endif */ .uni-navbar__header-btns-right {
.uni-nav-bar-text{ min-width: 120rpx;
font-weight: bold; width: auto !important;
font-size: 34rpx!important; }
}
.example-info {
font-size: 14px;
color: #333;
padding: 10px;
}
.twoline{
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.oneline{ /* #endif */
overflow: hidden; .uni-nav-bar-text {
white-space: nowrap;
text-overflow: ellipsis; font-size: 40rpx !important;
} }
.uni-navbar .uniui-left{
font-weight: bold; .example-info {
font-size: 50rpx!important; font-size: 14px;
} color: #333;
::-webkit-scrollbar { display: none; } padding: 10px;
.nav-right{ }
margin-top: -20px;
} .twoline {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.oneline {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.uni-navbar .uniui-left {
font-weight: bold;
font-size: 50rpx !important;
}
::-webkit-scrollbar {
display: none;
}
.status_bar {
height: var(--status-bar-height);
background-color: #eeeeee;
}
.navbox {
position: fixed;
top: 0;
left: 0;
right: 0;
height: calc(var(--status-bar-height) + 44px);
z-index: 9999;
}
.share-content .share-item:last-child{
display: none;
}
</style> </style>
+3
View File
@@ -0,0 +1,3 @@
{
"prompt" : "none"
}
+182 -6
View File
@@ -1,4 +1,5 @@
import {request} from '@/utils/request.js' import {request} from '@/utils/request.js'
const api = { const api = {
expertWxLogin(jscode){ expertWxLogin(jscode){
@@ -119,11 +120,11 @@ const api = {
}, },
// 积分支付列表 // 积分支付列表
bonusPointsPayList(data) { bonusPointsPayList(data) {
return request('/expertAPI/bonusPointsPayList', data, 'post', false); return request('/expertAPI/bonusPointsPayList', data, 'post', true);
}, },
// 积分列表 // 积分列表
bonusPointsList(data) { bonusPointsList(data) {
return request('/expertAPI/bonusPointsList', data, 'post', false); return request('/expertAPI/bonusPointsList', data, 'post', true);
}, },
getWelfarePage(data) { getWelfarePage(data) {
@@ -335,6 +336,9 @@ const api = {
applyList(data){ applyList(data){
return request('/expertAPI/applyList', data, 'post', false); return request('/expertAPI/applyList', data, 'post', false);
}, },
patientListByGBKPage(data){
return request('/expertAPI/patientListByGBKPage', data, 'post', false);
},
patientListByGBK(data){ patientListByGBK(data){
return request('/expertAPI/patientListByGBK', data, 'post', false); return request('/expertAPI/patientListByGBK', data, 'post', false);
}, },
@@ -411,6 +415,10 @@ const api = {
bankCardList(data){ bankCardList(data){
return request('/expertPay/bankCardList', data, 'post', false); return request('/expertPay/bankCardList', data, 'post', false);
}, },
deleteBankCard(data){
return request('/expertPay/deleteBankCard', data, 'post', false);
},
getIncomeTax(data){ getIncomeTax(data){
return request('/expertPay/getIncomeTax', data, 'post', false); return request('/expertPay/getIncomeTax', data, 'post', false);
}, },
@@ -446,10 +454,10 @@ const api = {
return request('/expertAPI/consultListHis', data, 'post', false); return request('/expertAPI/consultListHis', data, 'post', false);
}, },
listNewInterrogation(data){ listNewInterrogation(data){
return request('/expertAPI/listNewInterrogation', data, 'post', false); return request('/expertAPI/listNewInterrogation', data, 'post', true);
}, },
listMyAnsweredInterrogation(data){ listMyAnsweredInterrogation(data){
return request('/expertAPI/listMyAnsweredInterrogation', data, 'post', false); return request('/expertAPI/listMyAnsweredInterrogation', data, 'post', true);
}, },
getInterrogation(data){ getInterrogation(data){
return request('/expertAPI/getInterrogation', data, 'post', false); return request('/expertAPI/getInterrogation', data, 'post', false);
@@ -458,10 +466,10 @@ const api = {
return request('/expertAPI/InterrogationPatientInfo', data, 'post', false); return request('/expertAPI/InterrogationPatientInfo', data, 'post', false);
}, },
updateInterrogationAnswer(data){ updateInterrogationAnswer(data){
return request('/expertAPI/updateInterrogationAnswer', data, 'post', false); return request('/expertAPI/updateInterrogationAnswerU', data, 'post', true);
}, },
addInterrogationAnswer(data){ addInterrogationAnswer(data){
return request('/expertAPI/addInterrogationAnswer', data, 'post', false); return request('/expertAPI/addInterrogationAnswerU', data, 'post', true);
}, },
videoDetail(data){ videoDetail(data){
return request('/expertAPI/videoDetail', data, 'post', false); return request('/expertAPI/videoDetail', data, 'post', false);
@@ -556,6 +564,174 @@ const api = {
toAddNickname(data){ toAddNickname(data){
return request('/expertAPI/toAddNickname', data, 'post', false); return request('/expertAPI/toAddNickname', data, 'post', false);
}, },
caseDetail(data){
return request('/expertAPI/caseDetail', data, 'post', false);
},
conditionRecordList(data){
return request('/expertAPI/conditionRecordList', data, 'post', false);
},
addConditionRecord(data){
return request('/expertAPI/addConditionRecord', data, 'post', false);
},
updateConditionRecord(data){
return request('/expertAPI/upConditionRecord', data, 'post', false);
},
deleteConditionRecord(data){
return request('/expertAPI/delConditionRecord', data, 'post', false);
},
consultDetail(data){
return request('/expertAPI/consultDetail', data, 'post', false);
},
isConsultIng(data){
return request('/expertAPI/isConsultIng', data, 'post', false);
},
resConsult(data){
return request('/expertAPI/resConsult', data, 'post', false);
},
countConsult(data){
return request('/expertAPI/countConsult', data, 'post', false);
},
closeConsult(data){
return request('/expertAPI/closeConsult', data, 'post', false);
},
checkUser(data){
return request('/expertAPI/checkUser', data, 'post', false);
},
ganDanFileDetials(data){
return request('/expertAPI/ganDanFileDetials', data, 'post', false);
},
createGanDanFileOrder(data){
return request('/expertPay/createGanDanFileOrder', data, 'post', false);
},
getOrderStatus(data){
return request('/expertPay/getOrderStatus', data, 'post', false);
},
getOrderStatus(data){
return request('/expertPay/getOrderStatus', data, 'post', false);
},
getBalance(data){
return request('/expertPay/getBalance', data, 'post', false);
},
payGanDanFile(data){
return request('/expertPay/payGanDanFileOrder', data, 'post', false);
},
// discollection(data){
// return request('/expert/discollection', data, 'post', false);
// },
// collection(data){
// return request('/expert/collection', data, 'post', false);
// },
getTypeUuidByName(data){
return request('/expertAPI/getTypeUuidByName', data, 'post', false);
},
videoByTypeNew(data){
return request('/expertAPI/videoByTypeNew', data, 'post', false);
},
meetingHistoryList(data){
return request('/expertAPI/meetingHistoryList', data, 'post', false);
},
// 扫码登录相关API
// 确认扫码登录
confirmScanLogin(data){
return request('/expertAPI/confirmScanLogin', data, 'post', false);
},
// 取消扫码登录
cancelScanLogin(data){
return request('/expertAPI/cancelScanLogin', data, 'post', false);
},
// 获取扫码登录状态
getScanLoginStatus(data){
return request('/expertAPI/getScanLoginStatus', data, 'post', false);
},
gethonorDetail(data){
return request('/expertAPI/gethonorDetail', data, 'post', false);
},
checkVersion(data){
return request('/expertAPI/checkVersion', data, 'post', false);
},
getIsAccount(data){
return request('/expertAPI/getIsAccount', data, 'post', false);
},
patientVideoByJingHuaNew(data){
return request('/expertAPI/patientVideoByJingHuaNew', data, 'post', false);
},
useWelfareNum(data){
return request('/expertAPI/useWelfareNum', data, 'post', false);
},
deleteComment(data){
return request('/expertAPI/deleteComment', data, 'post', false);
},
downloadGanDanFile(data){
return request('/expertAPI/downloadGanDanFile', data, 'post', false,'application/json',{},'arraybuffer');
},
downloadGanDanFileV2(data){
return request('/expertAPI/downloadGanDanFileV2', data, 'post', false);
},
feedBack(data){
return request('/expertAPI/addFeedBack', data, 'post', false);
},
getPointUnitPrice(data){
return request('/expertAPI/getPointUnitPrice', data, 'post', false);
},
welfareList(data){
return request('/expertAPI/welfareList', data, 'post', false);
},
getUGoods(data){
return request('/expertAPI/getGoodsDetailU', data, 'post', false);
},
createGoodsOrder(data){
return request('/expertAPI/createGoodsOrder', data, 'post', false);
},
useWelfareNum(data){
return request('/expertAPI/useWelfareNum', data, 'post', false);
},
createBointsOrder(data){
return request('/expertPay/createBointsOrder', data, 'post', false);
},
receiveWelfare(data){
return request('/expertAPI/receiveWelfare', data, 'post', false);
},
getKePuCollection(data){
return request('/expertAPI/getKePuCollection', data, 'post', false);
},
getRead(data){
return request('/expertAPI/read', data, 'post', false);
},
getOutRead(data){
return request('/expertAPI/outread', data, 'post', false);
},
getAgree(data){
return request('/expertAPI/agree', data, 'post', false);
},
getDisagree(data){
return request('/expertAPI/disagree', data, 'post', false);
},
getCollect(data){
return request('/expertAPI/collection', data, 'post', false);
},
getDiscollect(data){
return request('/expertAPI/discollection', data, 'post', false);
},
getGanDanFileOrder(data){
return request('/expertPay/getGanDanFileOrder', data, 'post', false);
},
publishOutPatient(data){
return request('/expertAPI/addGroupSendMsg4YunXin', data, 'post', false);
},
polularScienceArticleListIndexNew(data){
return request('/expertAPI/polularScienceArticleListIndexNew', data, 'post', false);
},
getNewsDetail(data){
return request('/expertAPI/newsDetial', data, 'post', false);
},
//https://dev-app.igandan.com/app/expertPay/getGanDanFileOrder
} }
export default api export default api
+15 -1
View File
@@ -23,9 +23,23 @@ const goods_api = {
createGoodsOrder(data) { createGoodsOrder(data) {
return request('/expertAPI/createGoodsOrder', data, 'post', true); return request('/expertAPI/createGoodsOrder', data, 'post', true);
}, },
payGoodsOrder(data) {
return request('/expertAPI/payGoodsOrder', data, 'post', true);
},
getGoodsOrderList(data) { getGoodsOrderList(data) {
return request('/expertAPI/goodsOrderList', data, 'post', true); return request('/expertAPI/goodsOrderList', data, 'post', true);
},
getUpanWelfareCount(data){
return request('/expertAPI/getUpanWelfareCount', data, 'post', false);
},
getGoodsOrderDetail(data) {
return request('/expertAPI/getGoodsOrder', data, 'post', true);
},
getOrderTrack(data) {
return request('/expertAPI/getOrderTrack', data, 'post', true);
},
cancelGoodsOrder(data) {
return request('/expertAPI/cancleGoodsOrder', data, 'post', true);
} }
} }
+11
View File
@@ -14,6 +14,17 @@ const my_api = {
expertWxLogin(data,header) { expertWxLogin(data,header) {
return request('/expertAPI/wechatLogin', data, 'post', true,'application/json',header); return request('/expertAPI/wechatLogin', data, 'post', true,'application/json',header);
},
checkVersion(data) {
return request('/expertAPI/checkVersion', data, 'post', true);
},
startpage() {
return request('/expertAPI/startpage', {}, 'post', true);
},
addUniappCid(data) {
return request('/expertAPI/addUniappCid', data, 'post', true);
} }
} }
+1 -1
View File
@@ -13,7 +13,7 @@
:lazy-load="true" :lazy-load="true"
class="avatar-img" class="avatar-img"
v-if="avatarUrl" v-if="avatarUrl"
:src="docUrl+avatarUrl.replaceAll('null','')" :src="docUrl+avatarUrl.replace('null','')"
mode="aspectFill" mode="aspectFill"
/> />
<image <image
+169
View File
@@ -0,0 +1,169 @@
<template>
<view v-show="visible" class="gd-mask" :class="alignClass" :style="maskStyle">
<view
class="gd-dialog"
@touchstart="onTouchStart"
@touchmove="onTouchMove"
@touchend="onTouchEnd"
>
<view class="gd-title">{{ title }}</view>
<scroll-view class="gd-content" scroll-y>
<text class="gd-text">{{ content }}</text>
</scroll-view>
<!-- <view class="gd-footer">
<view class="gd-btn gd-confirm" @click="handleConfirm">{{ confirmText }}</view>
</view> -->
</view>
</view>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, computed } from 'vue'
const visible = ref(false)
const title = ref('消息通知')
const content = ref('')
const confirmText = ref('我知道了')
const position = ref('center') // 'center' | 'top'
const offsetTop = ref('20rpx')
const autoHideTimer = ref(null)
const startY = ref(0)
const hasSwipedUp = ref(false)
const emit = defineEmits(['confirm'])
function handleConfirm(){
visible.value = false
emit('confirm')
}
function show(payload = {}){
title.value = payload.title || '消息通知'
content.value = payload.content || ''
confirmText.value = payload.confirmText || '我知道了'
position.value = payload.position === 'top' ? 'top' : 'center'
offsetTop.value = payload.offsetTop || '20rpx'
visible.value = true
// 自动 5 秒后消失
if (autoHideTimer.value) clearTimeout(autoHideTimer.value)
autoHideTimer.value = setTimeout(() => {
hide()
}, 5000)
}
function hide(){
visible.value = false
if (autoHideTimer.value) {
clearTimeout(autoHideTimer.value)
autoHideTimer.value = null
}
}
function onTouchStart(e){
const touch = e.changedTouches ? e.changedTouches[0] : (e.touches ? e.touches[0] : null)
if (!touch) return
startY.value = touch.clientY || touch.pageY || 0
hasSwipedUp.value = false
}
function onTouchMove(e){
const touch = e.changedTouches ? e.changedTouches[0] : (e.touches ? e.touches[0] : null)
if (!touch) return
const currentY = touch.clientY || touch.pageY || 0
const deltaY = currentY - startY.value
if (deltaY < -60) {
hasSwipedUp.value = true
}
}
function onTouchEnd(){
if (hasSwipedUp.value) {
hide()
}
}
onMounted(() => {
console.log('[GlobalDialog] mounted')
uni.$on('global-dialog:show', show)
uni.$on('global-dialog:hide', hide)
// 便于全局手动触发测试
// @ts-ignore
uni.$showGlobalDialog = (payload = {}) => {
console.log('[GlobalDialog] uni.$showGlobalDialog called', payload)
uni.$emit('global-dialog:show', payload)
}
})
onBeforeUnmount(() => {
uni.$off('global-dialog:show', show)
uni.$off('global-dialog:hide', hide)
})
const alignClass = computed(() => position.value === 'top' ? 'is-top' : 'is-center')
const maskStyle = computed(() => position.value === 'top' ? { paddingTop: offsetTop.value } : {})
</script>
<style scoped lang="scss">
.gd-mask{
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
display: none;
align-items: center;
justify-content: center;
// 非模态:容器不截获事件
pointer-events: none;
}
.gd-mask.is-top{
align-items: flex-start;
justify-content: center;
}
.gd-dialog{
width: 600rpx;
max-width: 90%;
background: #fff;
border-radius: 16rpx;
overflow: hidden;
// 允许对话框自己接收事件(容器为非模态)
pointer-events: auto;
// 轻微上滑消失的过渡体验
transition: transform 0.2s ease, opacity 0.2s ease;
}
.gd-title{
padding: 28rpx 32rpx 12rpx;
font-size: 32rpx;
font-weight: 600;
color: #111;
text-align: left;
}
.gd-content{
max-height: 420rpx;
padding: 0 32rpx 24rpx;
}
.gd-text{
font-size: 28rpx;
line-height: 1.7;
color: #333;
}
.gd-footer{
padding: 16rpx 24rpx 24rpx;
display: flex;
justify-content: center;
}
.gd-btn{
min-width: 200rpx;
padding: 20rpx 24rpx;
text-align: center;
border-radius: 12rpx;
font-size: 28rpx;
}
.gd-confirm{
background: #2979ff;
color: #fff;
}
</style>
+94
View File
@@ -0,0 +1,94 @@
<template>
<!-- domId 是要截图的容器 id -->
<view class="my-html2canvas" :prop="domId" :change:prop="h2cRender.watchDomId">
<!-- 用于触发 renderjs 截图 -->
<text :prop="expOver" :change:prop="h2cRender.watchExpOver"></text>
</view>
</template>
<script>
export default {
props: {
domId: {
type: String,
required: true
}
},
data() {
return {
expOver: 0
}
},
methods: {
// 业务层调用这个方法开始截图
h2cRenderDom() {
// #ifdef H5
// H5 直接在当前环境(有 DOM)里跑 renderDom
this.$emit('renderDomInH5')
// #endif
// #ifndef H5
// 非 H5(APP 等)通过 expOver 触发 renderjs 中的截图逻辑
this.expOver++
// #endif
},
// renderjs 截图完成后会 callMethod 回调到这里
renderOver(base64) {
this.$emit('renderOver', base64)
}
}
}
</script>
<!-- renderjs 只能内联 -->
<script module="h2cRender" lang="renderjs">
import html2canvas from 'html2canvas'
export default {
data() {
return {
domIdValue: ''
}
},
methods: {
async renderDom() {
try {
const el = document.getElementById(this.domIdValue)
if (!el) {
console.error('[MyHtml2canvas] dom 未找到,请检查 domId:', this.domIdValue)
return
}
const canvas = await html2canvas(el, {
width: el.offsetWidth,
height: el.offsetHeight,
x: 0,
y: 0,
useCORS: true,
scale: 2
})
const base64 = canvas.toDataURL('image/png', 1)
this.$ownerInstance.callMethod('renderOver', base64)
} catch (err) {
console.error('[MyHtml2canvas] html2canvas 失败:', err && err.message)
}
},
// script 部分通过 :prop 传入 domId
watchDomId(newVal) {
this.domIdValue = newVal
},
// expOver 变化时触发渲染
watchExpOver(newVal) {
if (newVal !== 0) {
this.renderDom()
}
}
}
}
</script>
<style>
.my-html2canvas {
position: relative;
}
</style>
+91
View File
@@ -0,0 +1,91 @@
<template>
<view>
<!-- 当父组件传递的prop属性数据发生变化的时候 ,生成海报图片-->
<view :prop="domId" :change:prop="html2canvas.emitData" class="html2canvas">
<slot></slot>
</view>
</view>
</template>
<script>
import { base64ToPath } from '@/utils/image-tools.js';
export default {
props: {
domId: {
type: String,
required: true
}
},
mounted:function(){
console.log("组件加载完成");
},
methods: {
// 接收renderjs发回的数据
async receiveRenderData(imgData) {
let posterHeight=imgData.height;
let posterWidth=imgData.width;
let imgPath =await base64ToPath(imgData.imgVal, '.jpeg');
this.$emit('renderFinish', {"posterPath":imgPath,"posterHeight":posterHeight,"posterWidth":posterWidth});
uni.hideLoading();
},
showLoading() {
uni.showLoading({
title: "正在生成导出",
icon: "none",
mask: true,
duration: 3000
})
},
hideLoading() {
uni.hideLoading();
}
}
}
</script>
<script module="html2canvas" lang="renderjs">
import html2canvas from 'html2canvas';
export default {
data() {
return {
}
},
mounted:function(){
console.log("视图层渲染完成");
},
methods: {
// 发送数据到逻辑层,下面这里最好是加一个settimeout延迟,因为主界面数据还没加载渲染完成
emitData(domId) {
this.$ownerInstance.callMethod('showLoading', true);
const dom = document.getElementById('poster');
console.log("dom",dom);
let width= dom.offsetWidth;
let height=dom.offsetHeight;
let windowHeight= uni.getSystemInfoSync().windowHeight;
let windowWidth=uni.getSystemInfoSync().windowWidth;
html2canvas(dom, {
width:width,
height:height,
//scale:2.5, // 缩放倍数
scrollY: 0, // html2canvas默认绘制视图内的页面,需要把scrollY,scrollX设置为0
scrollX: 0,
x:0,
y:0,
windowWidth:windowWidth,
windowHeight:windowHeight,
useCORS: true, //支持跨域,但好像没什么用
}).then((canvas) => {
let imgVal= canvas.toDataURL('image/png');
this.$ownerInstance.callMethod('receiveRenderData',{imgVal:imgVal,height:height,width:width});
});
}
}
};
</script>
<style lang="scss">
</style>
@@ -0,0 +1,91 @@
<template>
<view>
<!-- 当父组件传递的prop属性数据发生变化的时候 ,生成海报图片-->
<view :prop="domId" :change:prop="html2canvas.emitData" class="html2canvas">
<slot></slot>
</view>
</view>
</template>
<script>
import { base64ToPath } from '@/utils/image-tools.js';
export default {
props: {
domId: {
type: String,
required: true
}
},
mounted:function(){
console.log("组件加载完成");
},
methods: {
// 接收renderjs发回的数据
async receiveRenderData(imgData) {
let posterHeight=imgData.height;
let posterWidth=imgData.width;
let imgPath =await base64ToPath(imgData.imgVal, '.jpeg');
this.$emit('renderFinish', {"posterPath":imgPath,"posterHeight":posterHeight,"posterWidth":posterWidth});
uni.hideLoading();
},
showLoading() {
uni.showLoading({
title: "正在生成导出",
icon: "none",
mask: true,
duration: 3000
})
},
hideLoading() {
uni.hideLoading();
}
}
}
</script>
<script module="html2canvas" lang="renderjs">
import html2canvas from 'html2canvas';
export default {
data() {
return {
}
},
mounted:function(){
console.log("视图层渲染完成");
},
methods: {
// 发送数据到逻辑层,下面这里最好是加一个settimeout延迟,因为主界面数据还没加载渲染完成
emitData(domId) {
this.$ownerInstance.callMethod('showLoading', true);
const dom = document.getElementById('poster');
console.log("dom",dom);
let width= dom.offsetWidth;
let height=dom.offsetHeight;
let windowHeight= uni.getSystemInfoSync().windowHeight;
let windowWidth=uni.getSystemInfoSync().windowWidth;
html2canvas(dom, {
width:width,
height:height,
//scale:2.5, // 缩放倍数
scrollY: 0, // html2canvas默认绘制视图内的页面,需要把scrollY,scrollX设置为0
scrollX: 0,
x:0,
y:0,
windowWidth:windowWidth,
windowHeight:windowHeight,
useCORS: true, //支持跨域,但好像没什么用
}).then((canvas) => {
let imgVal= canvas.toDataURL('image/png');
this.$ownerInstance.callMethod('receiveRenderData',{imgVal:imgVal,height:height,width:width});
});
}
}
};
</script>
<style lang="scss">
</style>
+9 -3
View File
@@ -3,7 +3,7 @@
<view class="dialog-container" @click.stop> <view class="dialog-container" @click.stop>
<!-- 弹窗标题 --> <!-- 弹窗标题 -->
<view class="dialog-header"> <view class="dialog-header">
<text class="dialog-title">{{ title }}</text> <text class="dialog-title" :style="{ color: titleColor }">{{ title }}</text>
</view> </view>
<!-- 弹窗内容 --> <!-- 弹窗内容 -->
@@ -42,13 +42,13 @@
@click="oncancel" @click="oncancel"
> >
<text class="btn-text">取消</text> <text class="btn-text">{{ cancelText }}</text>
</view> </view>
<view <view
class="dialog-btn confirm" class="dialog-btn confirm"
@click="onconfirm" @click="onconfirm"
> >
<text class="btn-text">确定</text> <text class="btn-text">{{ confirmText }}</text>
</view> </view>
</view> </view>
</view> </view>
@@ -63,6 +63,10 @@
type: Boolean, type: Boolean,
default: false default: false
}, },
titleColor: {
type: String,
default: '#8B2316'
},
title: { title: {
type: String, type: String,
default: '温馨提示' default: '温馨提示'
@@ -130,6 +134,8 @@
} }
.dialog-container { .dialog-container {
position: relative;
z-index:100;
background-color: #ffffff; background-color: #ffffff;
border-radius: 20rpx; border-radius: 20rpx;
width: 92%; width: 92%;
+63 -4
View File
@@ -1,14 +1,68 @@
<template> <template>
<view class="emptybox"> <view class="emptybox">
<up-image :src="emptyImg" width="176rpx" height="204rpx" ></up-image> <view class="empty-image-wrap" :style="imageWrapStyle">
<text class="empty-text">暂无数据</text> <up-image
:src="emptyImage || emptyImg"
:width="renderWidth"
:height="renderHeight"
:mode="imgMode"
></up-image>
</view>
<text class="empty-text">{{ emptyDesc }}</text>
</view> </view>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue'; import { computed } from 'vue';
import { onShow } from "@dcloudio/uni-app";
import emptyImg from "@/static/icon_empty.png" import emptyImg from "@/static/icon_empty.png"
const props = defineProps({
emptyImage: {
type: String,
default: ''
},
imgWidth: {
type: [String, Number],
default: ''
},
imgHeight: {
type: [String, Number],
default: ''
},
emptyDesc: {
type: String,
default: '暂无数据'
}
})
const normalizeSize = (size) => {
if (size === '' || size === null || size === undefined) return '';
// 直传字符串(如 50%、206rpx、120px),数字交给 up-image 按组件规则处理
return typeof size === 'string' ? size.trim() : size;
};
const isPercentSize = (size) => typeof size === 'string' && size.endsWith('%');
const imgWidth = computed(() => {
const width = normalizeSize(props.imgWidth);
const height = normalizeSize(props.imgHeight);
if (width !== '') return width;
if (height === '') return '206rpx';
return '';
});
const imgHeight = computed(() => normalizeSize(props.imgHeight));
const renderWidth = computed(() => (isPercentSize(imgWidth.value) ? '100%' : imgWidth.value));
const renderHeight = computed(() => (isPercentSize(imgHeight.value) ? '100%' : imgHeight.value));
const imageWrapStyle = computed(() => {
const style = {};
if (isPercentSize(imgWidth.value)) style.width = imgWidth.value;
if (isPercentSize(imgHeight.value)) style.height = imgHeight.value;
return style;
});
const imgMode = computed(() => {
// 只传宽/高时按比例缩放,避免图片被拉伸
if (imgWidth.value && !imgHeight.value) return 'widthFix';
if (!imgWidth.value && imgHeight.value) return 'heightFix';
return 'aspectFit';
});
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@@ -21,6 +75,11 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
flex-direction: column; flex-direction: column;
.empty-image-wrap{
width: 100%;
display: flex;
justify-content: center;
}
.empty-text{ .empty-text{
font-size: 30rpx; font-size: 30rpx;
color:#999; color:#999;
+26 -6
View File
@@ -1,21 +1,30 @@
<template> <template>
<!-- 顶部导航栏 --> <!-- 顶部导航栏 -->
<uni-nav-bar <view class="navbox">
left-icon="left" <view class="status_bar"></view>
<uni-nav-bar
:left-icon="showLeft?'left':''"
:title="title" :title="title"
@clickLeft="goBack" @clickLeft="goBack"
fixed
color="#8B2316" color="#8B2316"
height="140rpx"
:border="false" :border="false"
backgroundColor="#eeeeee" backgroundColor="#eeeeee"
/> />
</view>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { onShow } from "@dcloudio/uni-app"; import { onShow } from "@dcloudio/uni-app";
const props=defineProps({ const props=defineProps({
showLeft:{
type:Boolean,
default:true
},
noticeBack:{
type:Boolean,
default:false
},
title:{ title:{
type:String, type:String,
default:'' default:''
@@ -29,10 +38,21 @@
url: '/pages/index/index' url: '/pages/index/index'
}); });
} }
}); });
}; };
</script> </script>
<style scoped> <style scoped>
.navbox{
position: fixed;
top: 0;
left: 0;
right: 0;
height:(var(--status-bar-height)+44px);
z-index: 9999;
}
.status_bar{
height: var(--status-bar-height);
background-color: #eeeeee;
}
</style> </style>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,97 @@
/**
* @file 拖动进度提示组件
*/
<template>
<view class="plv-player-drag-tips">
<view class="plv-player-drag-tips__box">
<image class="plv-player-drag-tips__direction-img" :src="imgSrc"
@error="imageError">
</image>
<text class="plv-player-drag-tips__time">
{{changeTimeFormat}} / {{durationFormat}}
</text>
</view>
</view>
</template>
<script>
import utils from '@/common/util.js';
import backImg from '@/static/skin/back.png';
import forwardImg from '@/static/skin/forward.png'
export default {
props: {
currentTime: {
type: Number
},
duration: {
type: Number
},
changeTime: {
type: Number
}
},
data() {
return {
isForward: false
}
},
watch: {
changeTime(newValue, oldValue){
this.isForward = newValue > oldValue;
}
},
computed: {
timeFormatType() {
this.duration >= 3600 ? 2 : 1;
},
changeTimeFormat() {
return utils.formatTimeByDuration(this.changeTime, this.timeFormatType);
},
durationFormat() {
return utils.formatTimeByDuration(this.duration, this.timeFormatType)
},
imgSrc() {
return this.isForward ? forwardImg : backImg;
}
}
}
</script>
<style>
.plv-player-drag-tips {
width: 250rpx;
height: 160rpx;
background: rgba(0,0,0,.7);
border-radius: 10rpx;
}
.plv-player-drag-tips__box {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
justify-content: center;
align-items: center;
}
.plv-player-drag-tips__direction-img {
width: 100rpx;
height: 100rpx;
}
.plv-player-drag-tips__time {
font-size: 24rpx;
color: #FFFFFF;
}
</style>
+271
View File
@@ -0,0 +1,271 @@
<template>
<view class="plv-player-gesture"
@touchstart="touchstart"
@touchmove="touchmove"
@longpress="handleLongPress"
@touchcancel="handleTouchCancel"
@touchend="touchend">
</view>
</template>
<script>
export default {
props: {
// 屏幕宽度(物理像素)
holeWidth: Number,
// 视频总时长
duration: Number,
// 视频当前播放时间
currentTime: Number
},
computed: {
halfWidth() {
return parseInt(this.holeWidth / 2);
}
},
data() {
return {
// 是否在左区域按下手势
downLeft: false,
// 滑动方向
swipeDir: null
}
},
mounted() {
this.addHandler();
},
methods:{
addHandler() {
// 增加父容器左右滑动监听
let finalTime = -1;
// 计算左右滑动seek进度
const countTime = (position) => {
const { holeWidth, duration, currentTime } = this;
const precent = Math.abs(position) / holeWidth;
const nowTime = currentTime || 0;
const constSecond = duration < 60 ? duration : 60;
let plusTime = parseInt(Math.abs(precent * constSecond));
if (position < 0) plusTime = plusTime * -1;
finalTime = nowTime + plusTime;
finalTime = this.limit(finalTime, 0, duration);
return finalTime;
};
this.addGesture((type, position) => {
switch (type) {
// 下滑
case -1:
// console.log('//下滑');
this.$emit('onGestureEvent', {
type: this.downLeft ? 'LEFT_DOWN' : 'RIGHT_DOWN',
position
});
break;
// 上滑
case -2:
// console.log('//上滑');
this.$emit('onGestureEvent', {
type: this.downLeft ? 'LEFT_UP' : 'RIGHT_UP',
position
});
break;
// 左滑
case 0:
// console.log('//左滑');
this.swipeDir = type;
countTime(position);
this.$emit('onGestureEvent', {
type: 'SEEK_TIME_UPDATE',
finalTime
});
break;
// 右滑
case 1:
// console.log('//右滑');
this.swipeDir = type;
countTime(position);
this.$emit('onGestureEvent', {
type: 'SEEK_TIME_UPDATE',
finalTime
});
break;
// touchend for swipe
case 3:
if (this.swipeDir > -1) {
this.$emit('onGestureEvent', {
type: this.swipeDir ? 'SWIPE_RIGHT' : 'SWIPE_LEFT'
});
this.swipeDir = -1;
}
if (finalTime > -1) {
this.$emit('onGestureSeekTo', finalTime);
finalTime = -1;
}
break;
// doubleTap
case 4:
// console.log('>>>> 双击');
this.$emit('onGestureEvent', {
type: 'DOUBLE_CLICK'
});
break;
// tap
case 5:
// console.log('>>>> 单击');
this.handleClick();
break;
// longTap
case 6:
break;
}
});
},
addGesture(callback) {
const { options } = this;
let startX, startY, moveEndX, moveEndY, X, Y, position, hasType;
let T, lastTap, isJustTouch = true;
const excuteCb = (type) => {
// type 手势类型 -2 上滑 -1 下滑 0 左滑 1 右滑 2 just touch 3 touchend 4 doubleTap 5 tap 6 longTap
if (callback && typeof callback === 'function')
callback(type, position);
};
const YPlus = 20;
const YReduce = YPlus * -1;
const XPlus = 50;
const XReduce = XPlus * -1;
const isXchange = () => {
if (X > XPlus || X < XReduce) {
position = X;
hasType = 'X';
isJustTouch = false;
}
if (X > XPlus) excuteCb(1);
// 左滑
else if (X < XReduce) excuteCb(0);
};
const isYchange = () => {
if (Y > YPlus || Y < YReduce) {
position = Y;
hasType = 'Y';
isJustTouch = false;
}
if (Y > YPlus) excuteCb(-1);
// 上滑
else if (Y < YReduce) excuteCb(-2);
};
const countDirect = () => {
if (hasType) {
hasType === 'X' ? isXchange() : isYchange();
return;
}
isXchange();
isYchange();
};
this.handleTouch = (type, e) => {
switch(type) {
case 'start':
startX = e.changedTouches[0].pageX;
startY = e.changedTouches[0].pageY;
T = Date.now();
hasType = '';
isJustTouch = true;
this.downLeft = startX < this.halfWidth ? true : false;
break;
case 'move':
moveEndX = e.changedTouches[0].pageX;
moveEndY = e.changedTouches[0].pageY;
X = moveEndX - startX;
Y = moveEndY - startY;
countDirect();
break;
case 'end':
if (isJustTouch)
if (lastTap && Date.now() - lastTap <= 300)
excuteCb(4);
else if (Date.now() - T < 1000)
excuteCb(5);
else
excuteCb(6);
else
excuteCb(3);
lastTap = Date.now();
isJustTouch = true;
break;
}
};
},
handleClick() {
this.$emit('onGestureClick');
},
touchstart(e) {
this.handleTouch('start', e);
},
touchmove(e) {
this.handleTouch('move', e);
},
touchend(e) {
this.handleTouch('end', e);
this.$emit('onTouchEnd');
},
handleTouchCancel() {
this.$emit('onTouchCancel');
},
handleLongPress() {
this.$emit('onLongPress');
},
/**
* 控制最小值,最大值
* @param num
* @param min
* @param max
* @returns {*}
*/
limit(num, min, max) {
if (num < min) return min;
if (num > max) return max;
return num;
}
}
}
</script>
<style>
.plv-player-gesture {
flex: 1;
flex-direction: row;
}
.plv-player-gesture__left {
flex: 5;
}
.plv-player-gesture__right {
flex: 5;
}
</style>
+68
View File
@@ -0,0 +1,68 @@
<template>
<view v-if="isShow" class="plv-player-icon">
<image class="plv-player-icon__img" :src="imgUrl"></image>
<text class="plv-player-icon__text">{{ precent + '%' }}</text>
</view>
</template>
<script>
export default {
props: {
imgUrl: {
type: 'string',
required: true
},
precent: {
type: 'Number',
default: 100,
required: true
}
},
watch: {
precent(num) {
if (!this.isShow) this.isShow = true;
this.startClock();
}
},
data() {
return {
isShow: false
}
},
methods: {
startClock() {
this.clearClock();
this.clock = setTimeout(() => {
this.isShow = false;
}, 1500);
},
clearClock() {
if (this.clock) clearTimeout(this.clock);
this.clock = null;
}
}
}
</script>
<style>
.plv-player-icon {
align-items: center;
justify-content: center;
}
.plv-player-icon__img {
width: 80rpx;
height: 80rpx;
}
.plv-player-icon__text {
color: #FFFFFF;
text-align: center;
}
</style>
File diff suppressed because it is too large Load Diff
+92
View File
@@ -0,0 +1,92 @@
<template>
<view v-if="isShow" class="plv-player-volume-wrap">
<image class="plv-player-volume__img" :src="value === 0 ? muteImgUrl : imgUrl "></image>
<view class="plv-player-volume__progress">
<view class="plv-player-volume__progress__played" :style="'flex:' + value"></view>
<view class="plv-player-volume__progress_surplus" :style="'flex:' + valueLeft"></view>
</view>
</view>
</template>
<script>
export default {
props: {
imgUrl: {
type: 'string',
required: true
},
muteImgUrl: {
type: 'string',
required: true
},
value: {
type: 'Number',
default: 0.5,
required: true
}
},
watch: {
value(num) {
if (!this.isShow) this.isShow = true;
this.startClock();
}
},
computed: {
valueLeft() {
return 1 - this.value;
}
},
data() {
return {
isShow: false
}
},
methods: {
startClock() {
this.clearClock();
this.clock = setTimeout(() => {
this.isShow = false;
}, 1500);
},
clearClock() {
if (this.clock) clearTimeout(this.clock);
this.clock = null;
}
}
}
</script>
<style>
.plv-player-volume-wrap {
flex-direction: row;
align-items: center;
justify-content: center;
}
.plv-player-volume__img {
width: 40rpx;
height: 40rpx;
margin-right: 10rpx;
}
.plv-player-volume__progress {
width: 260rpx;
height: 8rpx;
flex-direction: row;
background-color: #09BB07;
}
.plv-player-volume__progress__played {
background-color: #007AFF;
}
.plv-player-volume__progress_surplus {
background-color: #FFFFFF;
}
</style>
+49 -23
View File
@@ -5,7 +5,7 @@
v-for="(item, index) in tabList" v-for="(item, index) in tabList"
:key="index" :key="index"
@click="switchTab(index, item)" @click="switchTab(index, item)"
:class="{ active: currentTab === index }" :class="{ active: 2 === index }"
> >
<!-- 图标容器 --> <!-- 图标容器 -->
<view class="icon-container"> <view class="icon-container">
@@ -33,11 +33,13 @@
<!-- <view class="active-indicator" v-if="currentTab === index"></view> --> <!-- <view class="active-indicator" v-if="currentTab === index"></view> -->
</view> </view>
</view> </view>
<GlobalDialog />
</template> </template>
<script setup> <script setup>
import { ref, reactive, onMounted, nextTick } from 'vue'; import { ref, reactive, onMounted, nextTick } from 'vue';
import { onShow, onLoad } from "@dcloudio/uni-app"; import { onShow, onLoad } from "@dcloudio/uni-app";
import GlobalDialog from "@/components/GlobalDialog.vue"
import home from '@/static/home_nor.png' import home from '@/static/home_nor.png'
import homeOn from '@/static/home_sel.png' import homeOn from '@/static/home_sel.png'
import classroom from "@/static/classroom.png" import classroom from "@/static/classroom.png"
@@ -70,7 +72,7 @@
activeColor: '#007aff', activeColor: '#007aff',
badge: 0, badge: 0,
showRedDot: false, showRedDot: false,
pagePath: '/pages/index/index' pagePath: '/pages/index/index?from=push'
}, },
{ {
icon: classroom, icon: classroom,
@@ -119,32 +121,56 @@
if (currentTab.value === index) return; if (currentTab.value === index) return;
// 更新当前tab // 更新当前tab
currentTab.value = index; //currentTab.value = index;
let msg="home"
// 清除红点 if(index == 0){
if (item.showRedDot) { msg="home"
item.showRedDot = false; }else if(index == 1){
msg="patientClass"
}else if(index == 2){
msg="live"
}else if(index == 3){
msg="education"
}else if(index == 4){
msg="mine"
} }
// 页面跳转 try{
uni.redirectTo({ uni.sendNativeEvent('goTabbarPage', {
url: item.pagePath, msg: msg
fail: () => { },ret => {
// 如果页面不存在,使用navigateTo
uni.navigateTo({
url: item.pagePath,
fail: () => {
} console.log(ret);
}); })
} }catch(e){
}); console.log(e);
}
plus.runtime.quit()
// 清除红点
// if (item.showRedDot) {
// item.showRedDot = false;
// }
// 页面跳转
// uni.redirectTo({
// url: item.pagePath,
// fail: () => {
// // 如果页面不存在,使用navigateTo
// uni.navigateTo({
// url: item.pagePath,
// fail: () => {
// }
// });
// }
// });
// 触发父组件事件 // 触发父组件事件
emit('tabChange', { // emit('tabChange', {
index, // index,
item // item
}); // });
}; };
// 设置徽章数量 // 设置徽章数量
+64
View File
@@ -0,0 +1,64 @@
<template>
<view class="top-tip" v-if="visible">
<view class="title">{{ title }}</view>
<view class="content">
{{ content }}
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
const visible = ref(false)
const props = defineProps({
title: {
type: String,
default: ''
},
content: {
type: String,
default: ''
}
})
const open = () => {
visible.value = true
}
const close = () => {
visible.value = false
}
defineExpose({
open,
close
})
</script>
<style scoped lang="scss">
.top-tip{
position: fixed;
top: 180rpx;
left: 40rpx;
right:40rpx;
padding: 20rpx;
border-radius: 16rpx;
background-color: rgba(0, 0, 0, 0.8);
z-index: 99999999999;
.title{
font-size: 32rpx;
color: #fff;
text-align: left;
}
.content{
font-size: 28rpx;
color: #fff;
text-align: left;
margin-top: 25rpx;
line-height: 1.5;
}
}
</style>
+273
View File
@@ -0,0 +1,273 @@
/**
* 本模块封装了Android、iOS的应用权限判断、打开应用权限设置界面、以及位置系统服务是否开启
*/
var isIos
// #ifdef APP-PLUS
isIos = (plus.os.name == "iOS")
// #endif
// 判断推送权限是否开启
function judgeIosPermissionPush() {
var result = false;
var UIApplication = plus.ios.import("UIApplication");
var app = UIApplication.sharedApplication();
var enabledTypes = 0;
if (app.currentUserNotificationSettings) {
var settings = app.currentUserNotificationSettings();
enabledTypes = settings.plusGetAttribute("types");
console.log("enabledTypes1:" + enabledTypes);
if (enabledTypes == 0) {
console.log("推送权限没有开启");
} else {
result = true;
console.log("已经开启推送功能!")
}
plus.ios.deleteObject(settings);
} else {
enabledTypes = app.enabledRemoteNotificationTypes();
if (enabledTypes == 0) {
console.log("推送权限没有开启!");
} else {
result = true;
console.log("已经开启推送功能!")
}
console.log("enabledTypes2:" + enabledTypes);
}
plus.ios.deleteObject(app);
plus.ios.deleteObject(UIApplication);
return result;
}
// 判断定位权限是否开启
function judgeIosPermissionLocation() {
var result = false;
var cllocationManger = plus.ios.import("CLLocationManager");
var status = cllocationManger.authorizationStatus();
result = (status != 2)
console.log("定位权限开启:" + result);
// 以下代码判断了手机设备的定位是否关闭,推荐另行使用方法 checkSystemEnableLocation
/* var enable = cllocationManger.locationServicesEnabled();
var status = cllocationManger.authorizationStatus();
console.log("enable:" + enable);
console.log("status:" + status);
if (enable && status != 2) {
result = true;
console.log("手机定位服务已开启且已授予定位权限");
} else {
console.log("手机系统的定位没有打开或未给予定位权限");
} */
plus.ios.deleteObject(cllocationManger);
return result;
}
// 判断麦克风权限是否开启
function judgeIosPermissionRecord() {
var result = false;
var avaudiosession = plus.ios.import("AVAudioSession");
var avaudio = avaudiosession.sharedInstance();
var permissionStatus = avaudio.recordPermission();
console.log("permissionStatus:" + permissionStatus);
if (permissionStatus == 1684369017 || permissionStatus == 1970168948) {
console.log("麦克风权限没有开启");
} else {
result = true;
console.log("麦克风权限已经开启");
}
plus.ios.deleteObject(avaudiosession);
return result;
}
// 判断相机权限是否开启
function judgeIosPermissionCamera() {
var result = false;
var AVCaptureDevice = plus.ios.import("AVCaptureDevice");
var authStatus = AVCaptureDevice.authorizationStatusForMediaType('vide');
console.log("authStatus:" + authStatus);
if (authStatus == 3) {
result = true;
console.log("相机权限已经开启");
} else {
console.log("相机权限没有开启");
}
plus.ios.deleteObject(AVCaptureDevice);
return result;
}
// 判断相册权限是否开启
function judgeIosPermissionPhotoLibrary() {
var result = false;
var PHPhotoLibrary = plus.ios.import("PHPhotoLibrary");
var authStatus = PHPhotoLibrary.authorizationStatus();
console.log("authStatus:" + authStatus);
if (authStatus == 3) {
result = true;
console.log("相册权限已经开启");
} else {
console.log("相册权限没有开启");
}
plus.ios.deleteObject(PHPhotoLibrary);
return result;
}
// 判断通讯录权限是否开启
function judgeIosPermissionContact() {
var result = false;
var CNContactStore = plus.ios.import("CNContactStore");
var cnAuthStatus = CNContactStore.authorizationStatusForEntityType(0);
if (cnAuthStatus == 3) {
result = true;
console.log("通讯录权限已经开启");
} else {
console.log("通讯录权限没有开启");
}
plus.ios.deleteObject(CNContactStore);
return result;
}
// 判断日历权限是否开启
function judgeIosPermissionCalendar() {
var result = false;
var EKEventStore = plus.ios.import("EKEventStore");
var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(0);
if (ekAuthStatus == 3) {
result = true;
console.log("日历权限已经开启");
} else {
console.log("日历权限没有开启");
}
plus.ios.deleteObject(EKEventStore);
return result;
}
// 判断备忘录权限是否开启
function judgeIosPermissionMemo() {
var result = false;
var EKEventStore = plus.ios.import("EKEventStore");
var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(1);
if (ekAuthStatus == 3) {
result = true;
console.log("备忘录权限已经开启");
} else {
console.log("备忘录权限没有开启");
}
plus.ios.deleteObject(EKEventStore);
return result;
}
// Android权限查询
function requestAndroidPermission(permissionID) {
return new Promise((resolve, reject) => {
plus.android.requestPermissions(
[permissionID], // 理论上支持多个权限同时查询,但实际上本函数封装只处理了一个权限的情况。有需要的可自行扩展封装
function(resultObj) {
var result = 0;
for (var i = 0; i < resultObj.granted.length; i++) {
var grantedPermission = resultObj.granted[i];
console.log('已获取的权限:' + grantedPermission);
result = 1
}
for (var i = 0; i < resultObj.deniedPresent.length; i++) {
var deniedPresentPermission = resultObj.deniedPresent[i];
console.log('拒绝本次申请的权限:' + deniedPresentPermission);
result = 0
}
for (var i = 0; i < resultObj.deniedAlways.length; i++) {
var deniedAlwaysPermission = resultObj.deniedAlways[i];
console.log('永久拒绝申请的权限:' + deniedAlwaysPermission);
result = -1
}
resolve(result);
// 若所需权限被拒绝,则打开APP设置界面,可以在APP设置界面打开相应权限
// if (result != 1) {
// gotoAppPermissionSetting()
// }
},
function(error) {
console.log('申请权限错误:' + error.code + " = " + error.message);
resolve({
code: error.code,
message: error.message
});
}
);
});
}
// 使用一个方法,根据参数判断权限
function judgeIosPermission(permissionID) {
if (permissionID == "location") {
return judgeIosPermissionLocation()
} else if (permissionID == "camera") {
return judgeIosPermissionCamera()
} else if (permissionID == "photoLibrary") {
return judgeIosPermissionPhotoLibrary()
} else if (permissionID == "record") {
return judgeIosPermissionRecord()
} else if (permissionID == "push") {
return judgeIosPermissionPush()
} else if (permissionID == "contact") {
return judgeIosPermissionContact()
} else if (permissionID == "calendar") {
return judgeIosPermissionCalendar()
} else if (permissionID == "memo") {
return judgeIosPermissionMemo()
}
return false;
}
// 跳转到**应用**的权限页面
function gotoAppPermissionSetting() {
if (isIos) {
var UIApplication = plus.ios.import("UIApplication");
var application2 = UIApplication.sharedApplication();
var NSURL2 = plus.ios.import("NSURL");
// var setting2 = NSURL2.URLWithString("prefs:root=LOCATION_SERVICES");
var setting2 = NSURL2.URLWithString("app-settings:");
application2.openURL(setting2);
plus.ios.deleteObject(setting2);
plus.ios.deleteObject(NSURL2);
plus.ios.deleteObject(application2);
} else {
// console.log(plus.device.vendor);
var Intent = plus.android.importClass("android.content.Intent");
var Settings = plus.android.importClass("android.provider.Settings");
var Uri = plus.android.importClass("android.net.Uri");
var mainActivity = plus.android.runtimeMainActivity();
var intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
var uri = Uri.fromParts("package", mainActivity.getPackageName(), null);
intent.setData(uri);
mainActivity.startActivity(intent);
}
}
// 检查系统的设备服务是否开启
// var checkSystemEnableLocation = async function () {
function checkSystemEnableLocation() {
if (isIos) {
var result = false;
var cllocationManger = plus.ios.import("CLLocationManager");
var result = cllocationManger.locationServicesEnabled();
console.log("系统定位开启:" + result);
plus.ios.deleteObject(cllocationManger);
return result;
} else {
var context = plus.android.importClass("android.content.Context");
var locationManager = plus.android.importClass("android.location.LocationManager");
var main = plus.android.runtimeMainActivity();
var mainSvr = main.getSystemService(context.LOCATION_SERVICE);
var result = mainSvr.isProviderEnabled(locationManager.GPS_PROVIDER);
console.log("系统定位开启:" + result);
return result
}
}
// module.exports = {
// judgeIosPermission: judgeIosPermission,
// requestAndroidPermission: requestAndroidPermission,
// checkSystemEnableLocation: checkSystemEnableLocation,
// gotoAppPermissionSetting: gotoAppPermissionSetting
// }
export default {judgeIosPermission,requestAndroidPermission,checkSystemEnableLocation,gotoAppPermissionSetting}
+4
View File
@@ -1,5 +1,9 @@
// ES6模块化引入
//import VConsole from 'vconsole'
import App from './App' import App from './App'
import uviewPlus, { setConfig } from 'uview-plus' import uviewPlus, { setConfig } from 'uview-plus'
//const vConsole = new VConsole();
// #ifndef VUE3 // #ifndef VUE3
import Vue from 'vue' import Vue from 'vue'
import './uni.promisify.adaptor' import './uni.promisify.adaptor'
+107 -26
View File
@@ -1,12 +1,13 @@
{ {
"name" : "肝胆相照专家端", "name" : "肝胆相照专家版",
"appid" : "__UNI__89F511F", "appid" : "__UNI__89F511F",
"description" : "", "description" : "",
"versionName" : "1.0.0", "versionName" : "4.2.7",
"versionCode" : "100", "versionCode" : 427,
"transformPx" : false, "transformPx" : false,
"app-plus" : { "app-plus" : {
/* 5+App特有相关 */ /* 5+App特有相关 */
"runmode" : "liberate",
"usingComponents" : true, "usingComponents" : true,
"nvueCompiler" : "uni-app", "nvueCompiler" : "uni-app",
"nvueStyleCompiler" : "uni-app", "nvueStyleCompiler" : "uni-app",
@@ -16,17 +17,28 @@
"autoclose" : true, "autoclose" : true,
"delay" : 0 "delay" : 0
}, },
"compatible" : {
"ignoreVersion" : true
},
"useragent" : {
"value" : "gdxz",
"concatenate" : true
},
"modules" : { "modules" : {
"OAuth" : {},
"Payment" : {},
"Share" : {}, "Share" : {},
"Webview-x5" : {}, "Webview-x5" : {},
"UIWebview" : {} "UIWebview" : {},
"VideoPlayer" : {},
"OAuth" : {},
"Barcode" : {},
"Camera" : {},
"Payment" : {}
}, },
/* 模块配置 */ /* 模块配置 */
"distribute" : { "distribute" : {
/* 应用发布信息 */ /* 应用发布信息 */
"android" : { "android" : {
"hasTaskAffinity" : true,
/* android打包配置 */ /* android打包配置 */
"permissions" : [ "permissions" : [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>", "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
@@ -45,19 +57,31 @@
"<uses-feature android:name=\"android.hardware.camera\"/>", "<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>" "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
], ],
"abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ] "abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ],
"minSdkVersion" : 21,
"targetSdkVersion" : 30
}, },
"ios" : { "ios" : {
"dSYMs" : false, "dSYMs" : false,
"idfa" : false "idfa" : false,
"urlschemewhitelist" : [ "weixin", "wechat" ],
"permission" : {
"NSPhotoLibraryUsageDescription" : "需要访问相册以选择图片",
"NSCameraUsageDescription" : "需要访问相机以拍照"
}
}, },
/* ios打包配置 */ /* ios打包配置 */
"sdkConfigs" : { "sdkConfigs" : {
"oauth" : { "oauth" : {
"weixin" : { "weixin" : {
"appid" : "wxbf3658f5e674667c", "appid" : "wxbf3658f5e674667c",
"appsecret" : "c4505a04a9910c65efea8e11ffc93f92",
"UniversalLinks" : "https://doc.igandan.com/gdxzExpert/" "UniversalLinks" : "https://doc.igandan.com/gdxzExpert/"
},
"univerify" : {},
"sina" : {
"appkey" : "2071931330",
"redirect_uri" : "http://sns.whalecloud.com/sina2/callback",
"UniversalLinks" : ""
} }
}, },
"payment" : { "payment" : {
@@ -72,49 +96,104 @@
"weixin" : { "weixin" : {
"appid" : "wxbf3658f5e674667c", "appid" : "wxbf3658f5e674667c",
"UniversalLinks" : "https://doc.igandan.com/gdxzExpert/" "UniversalLinks" : "https://doc.igandan.com/gdxzExpert/"
},
"sina" : {
"appkey" : "2071931330",
"redirect_uri" : "http://sns.whalecloud.com/sina2/callback",
"UniversalLinks" : "https://doc.igandan.com/gdxzExpert/"
} }
}, },
"speech" : {} "speech" : {},
"push" : {}
}, },
"splashscreen" : { "splashscreen" : {
"androidStyle" : "common" "androidStyle" : "common",
"useOriginalMsgbox" : false,
"iosStyle" : "common"
}, },
"icons" : { "icons" : {
"android" : { "android" : {
"hdpi" : "D:/haomi/Documents/WeChat Files/wxid_x5u4rycntnkq21/FileStorage/File/2025-09/专家版图标/专家版图标/72.png", "hdpi" : "D:/uniapp/专家版图标/72.png",
"xhdpi" : "D:/haomi/Documents/WeChat Files/wxid_x5u4rycntnkq21/FileStorage/File/2025-09/专家版图标/专家版图标/96.png", "xhdpi" : "D:/uniapp/专家版图标/96.png",
"xxhdpi" : "D:/haomi/Documents/WeChat Files/wxid_x5u4rycntnkq21/FileStorage/File/2025-09/专家版图标/专家版图标/144.png", "xxhdpi" : "D:/uniapp/专家版图标/144.png",
"xxxhdpi" : "D:/haomi/Documents/WeChat Files/wxid_x5u4rycntnkq21/FileStorage/File/2025-09/专家版图标/专家版图标/192.png" "xxxhdpi" : "D:/uniapp/专家版图标/192.png"
}, },
"ios" : { "ios" : {
"appstore" : "D:/haomi/Documents/WeChat Files/wxid_x5u4rycntnkq21/FileStorage/File/2025-09/专家版图标/专家版图标/1024.png", "appstore" : "D:/uniapp/专家版图标/1024.png",
"iphone" : { "iphone" : {
"app@2x" : "D:/haomi/Documents/WeChat Files/wxid_x5u4rycntnkq21/FileStorage/File/2025-09/专家版图标/专家版图标/120.png", "app@2x" : "D:/uniapp/专家版图标/120.png",
"app@3x" : "D:/haomi/Documents/WeChat Files/wxid_x5u4rycntnkq21/FileStorage/File/2025-09/专家版图标/专家版图标/180.png", "app@3x" : "D:/uniapp/专家版图标/180.png",
"spotlight@2x" : "D:/haomi/Documents/WeChat Files/wxid_x5u4rycntnkq21/FileStorage/File/2025-09/专家版图标/专家版图标/80.png", "spotlight@2x" : "D:/uniapp/专家版图标/80.png",
"spotlight@3x" : "D:/haomi/Documents/WeChat Files/wxid_x5u4rycntnkq21/FileStorage/File/2025-09/专家版图标/专家版图标/120.png", "spotlight@3x" : "D:/uniapp/专家版图标/120.png",
"settings@2x" : "D:/haomi/Documents/WeChat Files/wxid_x5u4rycntnkq21/FileStorage/File/2025-09/专家版图标/专家版图标/58.png", "settings@2x" : "D:/uniapp/专家版图标/58.png",
"settings@3x" : "D:/haomi/Documents/WeChat Files/wxid_x5u4rycntnkq21/FileStorage/File/2025-09/专家版图标/专家版图标/87.png", "settings@3x" : "D:/uniapp/专家版图标/87.png",
"notification@2x" : "D:/haomi/Documents/WeChat Files/wxid_x5u4rycntnkq21/FileStorage/File/2025-09/专家版图标/专家版图标/40.png", "notification@2x" : "D:/uniapp/专家版图标/40.png",
"notification@3x" : "D:/haomi/Documents/WeChat Files/wxid_x5u4rycntnkq21/FileStorage/File/2025-09/专家版图标/专家版图标/60.png" "notification@3x" : "D:/uniapp/专家版图标/60.png"
} }
} }
} }
}, },
"nativePlugins" : { "nativePlugins" : {
"NIMUniPlugin" : {
"PUSH_VIVO_APPID" : "100034326",
"PUSH_VIVO_APPKEY" : "e789634c1f76c8f184b1def1101ce824",
"PUSH_HONOR_APPID" : "900782424",
"__plugin_info__" : {
"name" : "NIMUniPlugin",
"description" : "uni消息推送插件",
"platforms" : "Android,iOS",
"url" : "",
"android_package_name" : "",
"ios_bundle_id" : "",
"isCloud" : false,
"bought" : -1,
"pid" : "",
"parameters" : {
"PUSH_VIVO_APPID" : {
"des" : "厂商VIVO-app_id",
"key" : "com.vivo.push.app_id",
"value" : ""
},
"PUSH_VIVO_APPKEY" : {
"des" : "厂商VIVO-app_key",
"key" : "com.vivo.push.api_key",
"value" : ""
},
"PUSH_HONOR_APPID" : {
"des" : "厂商HONOR-app_id",
"key" : "com.hihonor.push.app_id",
"value" : ""
}
}
}
},
"PLV-MediaPlayer" : { "PLV-MediaPlayer" : {
"__plugin_info__" : { "__plugin_info__" : {
"name" : "Polyv播放器插件", "name" : "Polyv播放器插件",
"description" : "该插件封装了保利威底层播放器,是云直播和云点播插件必须的附属插件", "description" : "该插件封装了保利威底层播放器,是云直播和云点播插件必须的附属插件",
"platforms" : "Android,iOS", "platforms" : "Android,iOS",
"url" : "https://ext.dcloud.net.cn/plugin?id=4798", "url" : "https://ext.dcloud.net.cn/plugin?id=4798",
"android_package_name" : "", "android_package_name" : "cn.shangyu.gdxzExpert",
"ios_bundle_id" : "cn.shangyu.gdxzExport", "ios_bundle_id" : "cn.shangyu.gdxzExport",
"isCloud" : true, "isCloud" : true,
"bought" : 1, "bought" : 1,
"pid" : "4798", "pid" : "4798",
"parameters" : {} "parameters" : {}
} }
},
"PLV-VodUniPlugin" : {
"__plugin_info__" : {
"name" : "polyv保利威视频云点播插件",
"description" : "polyv保利威视频云点播软件组件,支持聊天弹幕、视频加密、多码率、去广告等,还支持视频多维数据统计,适用于教学录播、产品介绍片、视频门户等",
"platforms" : "Android,iOS",
"url" : "https://ext.dcloud.net.cn/plugin?id=2802",
"android_package_name" : "cn.shangyu.gdxzExpert",
"ios_bundle_id" : "cn.shangyu.gdxzExport",
"isCloud" : true,
"bought" : 1,
"pid" : "2802",
"parameters" : {}
}
} }
} }
}, },
@@ -132,5 +211,7 @@
"vueVersion" : "3", "vueVersion" : "3",
"uniStatistics" : { "uniStatistics" : {
"version" : "2" "version" : "2"
} },
"fallbackLocale" : "zh-Hans",
"locale" : "zh-Hans"
} }
@@ -0,0 +1 @@
{"agcgw":{"url":"connect-drcn.dbankcloud.cn","backurl":"connect-drcn.hispace.hicloud.com","websocketurl":"connect-ws-drcn.hispace.dbankcloud.cn","websocketbackurl":"connect-ws-drcn.hispace.dbankcloud.com"},"agcgw_all":{"SG":"connect-dra.dbankcloud.cn","SG_back":"connect-dra.hispace.hicloud.com","CN":"connect-drcn.dbankcloud.cn","CN_back":"connect-drcn.hispace.hicloud.com","RU":"connect-drru.hispace.dbankcloud.ru","RU_back":"connect-drru.hispace.dbankcloud.cn","DE":"connect-dre.dbankcloud.cn","DE_back":"connect-dre.hispace.hicloud.com"},"websocketgw_all":{"SG":"connect-ws-dra.hispace.dbankcloud.cn","SG_back":"connect-ws-dra.hispace.dbankcloud.com","CN":"connect-ws-drcn.hispace.dbankcloud.cn","CN_back":"connect-ws-drcn.hispace.dbankcloud.com","RU":"connect-ws-drru.hispace.dbankcloud.ru","RU_back":"connect-ws-drru.hispace.dbankcloud.cn","DE":"connect-ws-dre.hispace.dbankcloud.cn","DE_back":"connect-ws-dre.hispace.dbankcloud.com"},"client":{"cp_id":"890086000102014306","product_id":"9105163812218585677","client_id":"178572405214545024","client_secret":"B90774CD63B8A6AB5C2C587DC9D7C4C666EF53A19DBA70C4D549C6302ED6CFBE","project_id":"9105163812218585677","app_id":"10526652","api_key":"CV58Raxnpwbfazn6iLPkESUy7KoiUzZ5iTv8lFg/WuexwYZceAK6F9BjRf0HVmT+039dGKt5yecdkRdQ4FCHsmLZ1Zvh","package_name":"cn.shangyu.gdxzExpert"},"oauth_client":{"client_id":"10526652","client_type":1},"app_info":{"app_id":"10526652","package_name":"cn.shangyu.gdxzExpert"},"service":{"analytics":{"collector_url":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn","collector_url_cn":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn","collector_url_de":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn","collector_url_ru":"datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com","collector_url_sg":"datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn","resource_id":"p1","channel_id":""},"ml":{"mlservice_url":"ml-api-drcn.ai.dbankcloud.com,ml-api-drcn.ai.dbankcloud.cn"},"cloudstorage":{"storage_url":"https://agc-storage-drcn.platform.dbankcloud.cn","storage_url_ru":"https://agc-storage-drru.cloud.huawei.ru","storage_url_sg":"https://ops-dra.agcstorage.link","storage_url_de":"https://ops-dre.agcstorage.link","storage_url_cn":"https://agc-storage-drcn.platform.dbankcloud.cn","storage_url_ru_back":"https://agc-storage-drru.cloud.huawei.ru","storage_url_sg_back":"https://agc-storage-dra.cloud.huawei.asia","storage_url_de_back":"https://agc-storage-dre.cloud.huawei.eu","storage_url_cn_back":"https://agc-storage-drcn.cloud.huawei.com.cn"},"search":{"url":"https://search-drcn.cloud.huawei.com"},"edukit":{"edu_url":"edukit.cloud.huawei.com.cn","dh_url":"edukit.cloud.huawei.com.cn"}},"region":"CN","configuration_version":"3.0","appInfos":[{"package_name":"cn.shangyu.gdxzExpert","client":{"app_id":"10526652"},"app_info":{"package_name":"cn.shangyu.gdxzExpert","app_id":"10526652"},"oauth_client":{"client_type":1,"client_id":"10526652"}}]}
@@ -0,0 +1,6 @@
{
"developer_id":"109999967097",
"app_id":"900782424",
"package_name":"cn.shangyu.gdxzExpert",
"version":"1.0"
}
@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>files</key>
<dict>
<key>Info.plist</key>
<data>
MxazSmkOgOArGHG40O8L+mZ4ULU=
</data>
</dict>
<key>files2</key>
<dict/>
<key>rules</key>
<dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^version.plist$</key>
<true/>
</dict>
<key>rules2</key>
<dict>
<key>.*\.dSYM($|/)</key>
<dict>
<key>weight</key>
<real>11</real>
</dict>
<key>^(.*/)?\.DS_Store$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>2000</real>
</dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^Info\.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^PkgInfo$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^embedded\.provisionprofile$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^version\.plist$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
</dict>
</dict>
</plist>
+70
View File
@@ -0,0 +1,70 @@
{
"name": "NIMUniPlugin",
"id": "NIMUniPlugin",
"version": "1.1.3",
"git": "3ff8678",
"buildAt": "7/21/2025, 10:17:00 AM",
"description": "uni消息推送插件",
"_dp_type": "nativeplugin",
"_dp_nativeplugin": {
"android": {
"plugins": [
{
"type": "module",
"name": "NIMUniPlugin-PluginModule",
"class": "com.netease.nimlib.uniapp.push.PushModule"
}
],
"hooksClass": "com.netease.nimlib.uniapp.push.PushModule",
"integrateType": "aar",
"dependencies": [
"com.huawei.hms:push:6.9.0.300",
"com.meizu.flyme.internet:push-internal:4.2.3",
"com.google.android.gms:play-services-base:18.2.0",
"com.google.firebase:firebase-messaging:23.1.2",
"com.google.firebase:firebase-core:21.1.1",
"com.google.code.gson:gson:2.6.2",
"commons-codec:commons-codec:1.6",
"com.netease.nimlib:basesdk:9.16.1",
"com.netease.nimlib:push:9.16.1"
],
"compileOptions": {
"sourceCompatibility": "1.8",
"targetCompatibility": "1.8"
},
"abis": [
"armeabi-v7a",
"arm64-v8a",
"x86"
],
"minSdkVersion": "21",
"useAndroidX": true,
"parameters": {
"PUSH_VIVO_APPID": {
"des": "厂商VIVO-app_id",
"key": "com.vivo.push.app_id"
},
"PUSH_VIVO_APPKEY": {
"des": "厂商VIVO-app_key",
"key": "com.vivo.push.api_key"
},
"PUSH_HONOR_APPID": {
"des": "厂商HONOR-app_id",
"key": "com.hihonor.push.app_id"
}
}
},
"ios": {
"plugins": [
{
"type": "module",
"name": "NIMUniPlugin-PluginModule",
"class": "NIMPluginModule"
}
],
"hooksClass": "NIMPluginProxy",
"integrateType": "framework",
"deploymentTarget": "9.0"
}
}
}
+10 -5
View File
@@ -8,15 +8,20 @@
"license": "ISC", "license": "ISC",
"description": "", "description": "",
"dependencies": { "dependencies": {
"@xkit-yx/im-store-v2": "^0.8.3",
"@xkit-yx/utils": "^0.7.2",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"@xkit-yx/im-store-v2": "^0.8.3",
"@xkit-yx/utils": "^0.7.2",
"mobx": "^6.6.1",
"nim-web-sdk-ng": "^10.9.30",
"dayjs": "^1.11.18", "dayjs": "^1.11.18",
"html2canvas": "^1.4.1",
"image-tools": "^1.4.0",
"js-base64": "^3.7.8", "js-base64": "^3.7.8",
"js-md5": "^0.8.3", "js-md5": "^0.8.3",
"mobx": "^6.6.1",
"nim-web-sdk-ng": "^10.9.50",
"pinyin": "^4.0.0", "pinyin": "^4.0.0",
"uview-plus": "^3.4.73" "uni-packing-wgt": "^1.2.3",
"uniapp-video-player": "^1.3.0",
"uview-plus": "^3.4.73",
"vconsole": "^3.15.1"
} }
} }
+1694 -1170
View File
File diff suppressed because it is too large Load Diff
+197
View File
@@ -0,0 +1,197 @@
<template>
<view class="ad-container" v-if="adData.path">
<!-- 广告图片 -->
<image
class="ad-image"
:src="adData.path"
mode="aspectFill"
@click="handleAdClick"
></image>
<!-- 右上角倒计时跳过按钮 -->
<view class="skip-button" @click="skipAd">
<text class="skip-text">跳过 {{countdown}}s</text>
</view>
<!-- 底部点击跳转按钮 -->
<view class="bottom-button" v-if="adData.url" @click="handleAdClick">
<text class="button-text">点击查看详情</text>
</view>
</view>
<!-- 加载状态 -->
<view class="loading-container" v-else>
<text class="loading-text">加载中...</text>
</view>
</template>
<script setup>
import { ref, reactive, onMounted, nextTick, computed, onUnmounted } from 'vue';
import { onShow, onLoad, onPageScroll } from "@dcloudio/uni-app";
import my_api from '@/api/my_api.js'
import docUrl from "@/utils/docUrl.js";
const app = getApp()
// 广告数据
const adData = ref({
path: '',
url: ''
});
// 倒计时
const countdown = ref(5);
let timer = null;
// 页面加载
onLoad(() => {
console.log('开屏广告页面加载');
getStartpage();
});
// 获取开屏广告数据
const getStartpage = () => {
my_api.startpage().then(res => {
console.log('开屏广告数据:', res);
if (res && res.data) {
adData.value = {
path: docUrl + res.data.path || '',
url: res.data.url || ''
};
// 如果有广告图片,开始倒计时
if (res.data.path) {
startCountdown();
} else {
// 没有广告,直接跳转到首页
navigateToHome();
}
} else {
// 接口返回异常,直接跳转到首页
navigateToHome();
}
}).catch(err => {
console.error('获取开屏广告失败:', err);
// 接口调用失败,直接跳转到首页
navigateToHome();
});
}
// 开始倒计时
const startCountdown = () => {
timer = setInterval(() => {
countdown.value--;
if (countdown.value <= 0) {
clearInterval(timer);
if(app.globalData.plAd){
navigateToHome();
}
}
}, 1000);
}
// 跳过广告
const skipAd = () => {
clearInterval(timer);
navigateToHome();
}
// 点击广告
const handleAdClick = () => {
if (adData.value.url) {
// 有跳转链接,跳转到指定页面
uni.navigateTo({
url: '/pages/webview/webview?url=' + adData.value.url
});
} else {
// 没有跳转链接,直接跳转到首页
navigateToHome();
}
}
// 跳转到首页
const navigateToHome = () => {
uni.redirectTo({
url: '/pages/index/index'
});
}
// 页面卸载时清理定时器
onUnmounted(() => {
if (timer) {
clearInterval(timer);
}
});
</script>
<style scoped>
.ad-container {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: #000;
z-index: 9999;
}
.ad-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.skip-button {
position: absolute;
top: 60rpx;
right: 30rpx;
background-color: rgba(0, 0, 0, 0.6);
border-radius: 40rpx;
padding: 16rpx 24rpx;
z-index: 10000;
}
.skip-text {
color: #fff;
font-size: 28rpx;
font-weight: 500;
}
.bottom-button {
position: absolute;
bottom: 100rpx;
left: 50%;
transform: translateX(-50%);
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 50rpx;
padding: 24rpx 60rpx;
box-shadow: 0 8rpx 32rpx rgba(102, 126, 234, 0.3);
z-index: 10000;
}
.button-text {
color: #fff;
font-size: 32rpx;
font-weight: 600;
}
.loading-container {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.loading-text {
color: #666;
font-size: 32rpx;
}
</style>
+8 -8
View File
@@ -1,5 +1,5 @@
<template> <template>
<uni-nav-bar title="继续教育" fixed color="#8B2316" height="140rpx" :border="false" backgroundColor="#eeeeee"></uni-nav-bar> <navBar :title="'继续教育'"></navBar>
<view class="page"> <view class="page">
<!-- 课程列表 --> <!-- 课程列表 -->
<view class="course-list"> <view class="course-list">
@@ -103,21 +103,22 @@
import xueyuanImg from "@/static/ganbingxueyuan.png" import xueyuanImg from "@/static/ganbingxueyuan.png"
import newerImg from "@/static/xinshoujiaocheng.png" import newerImg from "@/static/xinshoujiaocheng.png"
import navTo from '@/utils/navTo.js'; import navTo from '@/utils/navTo.js';
import navBar from "@/components/navBar/navBar.vue"
// 方法 // 方法
// 方法 // 方法
const goToCourse = (courseType) => { const goToCourse = (courseType) => {
console.log('进入课程:', courseType);
// 根据课程类型进行不同的处理 // 根据课程类型进行不同的处理
switch(courseType) { switch(courseType) {
case 'course': case 'course':
navTo({ uni.sendNativeEvent('goCourse',{
url: '/pages_course/course/course' msg: 'course'
}) })
break; break;
case 'video': case 'video':
navTo({ navTo({
url: '/pages_app/video/video' url: '/pages_app/video/video?from=education'
}) })
break; break;
case 'ppt': case 'ppt':
@@ -166,9 +167,8 @@ $padding-small: 10px;
.page { .page {
background-color: $gray-bg; background-color: $gray-bg;
min-height: calc(100vh - 140rpx); min-height: calc(100vh - var(--status-bar-height) - 44px);
margin-top:calc(var(--status-bar-height) + 44px);
padding-bottom: 120rpx;
} }
// 课程列表 // 课程列表
+975 -494
View File
File diff suppressed because it is too large Load Diff
+1393 -1055
View File
File diff suppressed because it is too large Load Diff
+52
View File
@@ -0,0 +1,52 @@
<template>
<navBar :showLeft="false" :title="'加载中...'"></navBar>
<view class="loading-page"></view>
</template>
<script setup>
import { onMounted,ref} from 'vue';
import { onLoad,onUnload,onHide,onShow } from '@dcloudio/uni-app';
import navBar from '@/components/navBar/navBar.vue';
const timer=ref(null);
const count=ref(0);
const countDown=()=>{
if(timer.value){
return
}
timer.value=setInterval(()=>{
count.value++;
if(count.value>=8){
clearInterval(timer.value);
plus.runtime.quit();
}
},1000);
}
onShow(() => {
uni.showLoading({
title: '加载中...',
mask: true
});
countDown()
});
onUnload(() => {
uni.hideLoading();
if(timer.value){
clearInterval(timer.value);
}
});
onHide(() => {
uni.hideLoading();
if(timer.value){
clearInterval(timer.value);
}
});
</script>
<style scoped lang="scss">
.loading-page{
width: 100%;
height: 100%;
background: #fff;
}
</style>
+2 -1
View File
@@ -1,5 +1,5 @@
<template> <template>
<uni-nav-bar title="肝胆会议" fixed color="#8B2316" height="140rpx" :border="false" backgroundColor="#eeeeee"></uni-nav-bar> <navBar :title="'肝胆会议'"></navBar>
<view class="page"> <view class="page">
<!-- 筛选标签栏 --> <!-- 筛选标签栏 -->
<view class="filter-bar"> <view class="filter-bar">
@@ -146,6 +146,7 @@
import timeImg from "@/static/play_long.png" import timeImg from "@/static/play_long.png"
import api from '@/api/api'; import api from '@/api/api';
import docUrl from '@/utils/docUrl'; import docUrl from '@/utils/docUrl';
import navBar from "@/components/navBar/navBar.vue"
// 弹窗状态 // 弹窗状态
const isTimePopupShow = ref(false); const isTimePopupShow = ref(false);
const isLocationPopupShow = ref(false); const isLocationPopupShow = ref(false);
+424 -29
View File
@@ -1,20 +1,23 @@
<template> <template>
<uni-nav-bar title="我的" fixed color="#8B2316" height="140rpx" :border="false" <navBar :title="'我的'" :showLeft="false"></navBar>
backgroundColor="#eeeeee"></uni-nav-bar>
<view class="page"> <view class="page">
<!-- 用户信息卡片 --> <!-- 用户信息卡片 -->
<view class="user-card" <view class="user-card"
:style="{'background-image': `url(${myInfoBackGround || '/static/big_background_my.png'})`, 'background-size':'cover','background-repeat':'no-repeat'}"> :style="{'background-image': `url(${myInfoBackGround || '/static/big_background_my.png'})`, 'background-size':'cover','background-repeat':'no-repeat'}">
<view class="user-info" @click="goToPage('myInfo')"> <view class="user-info">
<image class="avatar" :src="avatar" mode="aspectFill"></image> <image class="avatar" :src="avatar" mode="aspectFill" @click="goToPage('myInfo')"></image>
<view class="user-info-content"> <view class="user-info-content">
<text class="username">{{username}}</text> <text class="username">{{username}}</text>
<scroll-view class="honor_box" scroll-x="true" show-scrollbar="false"> <scroll-view class="honor_box" scroll-x="true" show-scrollbar="false">
<view class="honor_wrap"> <view class="honor_wrap">
<view class="honor_item" v-for="item in honor_list" :key="item.id"> <view class="honor_item" v-if="ranking > 0">
<image class="honor_image" src="/static/wdsc.png" mode="aspectFill"></image> <image class="honor_image" :src="suifangImg" mode="aspectFill"></image>
<text class="honor_text">随访达人|排名{{ranking}}</text>
</view>
<view class="honor_item" v-for="item in honor_list" :key="item.id" @click="getHonorDetail(item.id)">
<image class="honor_image" :src="rongyu" mode="aspectFill"></image>
<text class="honor_text">{{ item.nick_name }}</text> <text class="honor_text">{{ item.nick_name }}</text>
</view> </view>
</view> </view>
@@ -101,9 +104,9 @@
<!-- 账户明细 --> <!-- 账户明细 -->
<view class="section"> <view class="section">
<view class="section-title">账户明细</view> <view class="section-title">账户明细</view>
<up-scroll-list indicatorActiveColor="#8B2316" indicatorWidth="40rpx" indicatorBarWidth="20rpx" <up-scroll-list :indicator="isAccount" indicatorActiveColor="#8B2316" indicatorWidth="40rpx" indicatorBarWidth="20rpx"
class="account-grid"> class="account-grid">
<view class="account-item" @click="goToPage('myAccount')"> <view class="account-item" @click="goToPage('myAccount')" v-if="isAccount">
<up-image :src="wdzzImg" width="48rpx" height="48rpx"></up-image> <up-image :src="wdzzImg" width="48rpx" height="48rpx"></up-image>
<text>我的账户</text> <text>我的账户</text>
</view> </view>
@@ -115,11 +118,11 @@
<up-image :src="wdflImg" width="48rpx" height="48rpx"></up-image> <up-image :src="wdflImg" width="48rpx" height="48rpx"></up-image>
<text>我的福利</text> <text>我的福利</text>
</view> </view>
<view class="account-item" @click="goToPage('myFlower')"> <view class="account-item" @click="goToPage('myFlower')" v-if="isAccount">
<up-image :src="wdxhImg" width="48rpx" height="48rpx"></up-image> <up-image :src="wdxhImg" width="48rpx" height="48rpx"></up-image>
<text>我的鲜花</text> <text>我的鲜花</text>
</view> </view>
<view class="account-item" @click="goToPage('pptDetail')"> <view class="account-item" @click="goToPage('pptDetail')" v-if="isAccount">
<up-image :src="kjmxImg" width="48rpx" height="48rpx"></up-image> <up-image :src="kjmxImg" width="48rpx" height="48rpx"></up-image>
<text>课件明细</text> <text>课件明细</text>
</view> </view>
@@ -144,13 +147,13 @@
<up-image :src="ghsjhImg" width="48rpx" height="48rpx"></up-image> <up-image :src="ghsjhImg" width="48rpx" height="48rpx"></up-image>
<text>更换手机号</text> <text>更换手机号</text>
</view> </view>
<view class="operation-item" @click="goToPage('notifications')"> <view class="operation-item" @click="switchPushPermissions">
<up-image :src="tzykImg" width="48rpx" height="48rpx"></up-image> <up-image :src="tzykImg" width="48rpx" height="48rpx"></up-image>
<text>通知已开</text> <text>通知已{{notice_on?'开':'关'}}</text>
</view> </view>
<view class="operation-item" @click="showVersion"> <view class="operation-item" @click="showVersion">
<up-image :src="versionImg" width="48rpx" height="48rpx"></up-image> <up-image :src="hasNewVersion?versionImgOn:versionImg" width="48rpx" height="48rpx"></up-image>
<text>V{{version}}</text> <text>{{hasNewVersion?'有新版本':'V'+version}}</text>
</view> </view>
</view> </view>
</view> </view>
@@ -175,7 +178,7 @@
<!-- 常用银行卡 --> <!-- 常用银行卡 -->
<view class="sectioncell bank-section"> <view class="sectioncell bank-section">
<view class="bank-item" @click="goToPage('bankCard')"> <view class="bank-item" @click="goToPage('bankCard')" v-if="isAccount">
<view class="bank-icon"> <view class="bank-icon">
<up-image :src="cyyhk" width="48rpx" height="48rpx"></up-image> <up-image :src="cyyhk" width="48rpx" height="48rpx"></up-image>
</view> </view>
@@ -194,10 +197,12 @@
<view class="settings-icon"> <view class="settings-icon">
<up-image :src="settingImg" width="48rpx" height="48rpx"></up-image> <up-image :src="settingImg" width="48rpx" height="48rpx"></up-image>
</view> </view>
<view class="settings-content"> <view class="settings-content flex-row" >
<text class="settings-title">设置与帮助</text> <text class="settings-title">设置与帮助</text>
<text v-if="hasNewVersion && isIos" class="new-version">有新版本</text>
</view> </view>
<view class="settings-arrow"> <view class="settings-arrow">
<uni-icons type="forward" size="16" color="#ccc"></uni-icons> <uni-icons type="forward" size="16" color="#ccc"></uni-icons>
</view> </view>
</view> </view>
@@ -215,16 +220,68 @@
<!-- 底部导航栏 --> <!-- 底部导航栏 -->
<CustomTabbar></CustomTabbar> <CustomTabbar></CustomTabbar>
</view> </view>
<unidialog :visible="hasSign" content="今日已签到,每天只能签到一次。<br>请明日继续哦~" @close="hasSign=false" :showCancel="true" cancelText="关闭"></unidialog>
<up-overlay :show="showSign" >
<view class="signwrap">
<view class="signbox">
<view class="close" @click="showSign=false"></view>
<view class="signbg">
<up-image :src="signImg" width="604rpx" height="964rpx" ></up-image>
</view>
<view class="signcontent">
<view class="day">今天是我们相识的第{{signInfo.gdxzday}}天</view>
<view class="signtotal">本周共签到{{signInfo.totalDay}}次</view>
<view class="signcontinue">已经连续签到{{signInfo.continuous_day}}天</view>
<view class="tip">连续签到获取更多积分</view>
<view class="news" @click.stop="goNews">
{{signInfo.news.summary}}
</view>
</view>
</view>
</view>
</up-overlay>
<up-overlay :show="showHonor" >
<view class="honorwrap">
<view class="honorbox">
<image :src="docUrl+honnor_detail.img" class="honor_image"></image>
<uni-icons type="close" class="close_honor" size="24" color="#fff" @click="showHonor=false"></uni-icons>
<view class="content">
<view class="content_title">
{{honnor_detail.title}}
</view>
<view class="row">
<view class="row_item" v-for="item in honnor_detail.honorlist" :key="item.id">
{{ item.nick_name }}
</view>
</view>
<view class="content_content">
{{honnor_detail.note}}
</view>
</view>
</view>
</view>
</up-overlay>
</template> </template>
<script setup> <script setup>
import unidialog from '@/components/dialog/dialog.vue'
import signImg from "@/static/sign_in_bng_big.png"
import CustomTabbar from '@/components/tabBar/tabBar.vue'; import CustomTabbar from '@/components/tabBar/tabBar.vue';
import navBar from "@/components/navBar/navBar.vue"
import { import {
ref ref,reactive
} from 'vue'; } from 'vue';
import { import {
onShow onShow
} from "@dcloudio/uni-app"; } from "@dcloudio/uni-app";
const showHonor=ref(false);
import docUrl from "@/utils/docUrl.js"
import honnorExpert from "@/static/zixunexpert.png"
import honnorYingxiong from "@/static/yingxiongbanglog.png"
import rongyu from "@/static/yxb.png"
import version from '@/utils/version.js' import version from '@/utils/version.js'
import hzshImg from "@/static/hzsh.png" import hzshImg from "@/static/hzsh.png"
import hzfzImg from "@/static/hzfz.png" import hzfzImg from "@/static/hzfz.png"
@@ -246,8 +303,9 @@
import ghsjhImg from "@/static/ghsjh.png" import ghsjhImg from "@/static/ghsjh.png"
import tzykImg from "@/static/xxtx.png" import tzykImg from "@/static/xxtx.png"
import versionImg from "@/static/fxxbb.png" import versionImg from "@/static/fxxbb.png"
import versionImgOn from "@/static/fxbb.png"
import jifenImg from "@/static/point_buy.png"
import suifangImg from "@/static/suifang.png"
import fulicard from "@/static/fulicard.png" import fulicard from "@/static/fulicard.png"
import fpgl from "@/static/fpgl.png" import fpgl from "@/static/fpgl.png"
import cyyhk from "@/static/cyhhk.png" import cyyhk from "@/static/cyhhk.png"
@@ -256,9 +314,16 @@
import my_api from '@/api/my_api.js' import my_api from '@/api/my_api.js'
import navTo from "@/utils/navTo" import navTo from "@/utils/navTo"
import linkUrl from "@/utils/docUrl" import linkUrl from "@/utils/docUrl"
// 响应式数据 // 响应式数据
const isLargeFont = ref(false); const isLargeFont = ref(false);
const hasSign = ref(false)
const showSign = ref(false)
const signInfo=reactive({
news:{
summary:''
}
})
// 新增:我的信息数据 // 新增:我的信息数据
const avatar = ref('') const avatar = ref('')
const username = ref('') const username = ref('')
@@ -269,6 +334,43 @@
const totalPoints = ref(0) const totalPoints = ref(0)
const sign_in = ref(0) const sign_in = ref(0)
const honor_list = ref([]) const honor_list = ref([])
const ranking = ref(0)
const notice_on = ref(false)
const hasNewVersion = ref(false)
const checkVersion=()=>{
api.checkVersion().then(res=>{
if(res.code==80002){
hasNewVersion.value=true
}
})
}
const isAccount = ref(false);
const getIsAccount=()=>{
api.getIsAccount().then(res=>{
if(res.code==1){
isAccount.value=res.is_account==0?false:true;
}
})
}
const goNews=()=>{
let url=docUrl+signInfo.news.path;
// #ifdef H5
window.open(url, '_blank');
// #endif
// #ifdef APP-PLUS
plus.runtime.openURL(url);
// #endif
// #ifdef MP
const encoded = encodeURIComponent(url);
navTo({
url: `/pages_app/webview/webview?url=${encoded}`
});
// #endif
}
// 从storage获取用户信息 // 从storage获取用户信息
const getUserInfoFromStorage = () => { const getUserInfoFromStorage = () => {
try { try {
@@ -299,6 +401,7 @@
title: '签到成功,获得'+res.bonuspoints+'积分', title: '签到成功,获得'+res.bonuspoints+'积分',
icon: 'none' icon: 'none'
}); });
Object.assign(signInfo,res);
} else { } else {
uni.showToast({ uni.showToast({
title: res?.msg || '签到失败', title: res?.msg || '签到失败',
@@ -326,6 +429,7 @@
totalPoints.value = data.total_points || 0 totalPoints.value = data.total_points || 0
sign_in.value = data.sign_in || 0 sign_in.value = data.sign_in || 0
honor_list.value = data.honor_list || [] honor_list.value = data.honor_list || []
ranking.value = data.ranking || 0
} }
} catch (e) { } catch (e) {
console.error('获取我的信息失败', e) console.error('获取我的信息失败', e)
@@ -419,7 +523,7 @@
url="/pages_app/changeMobile/changeMobile" url="/pages_app/changeMobile/changeMobile"
break; break;
case 'benefitExchange': case 'benefitExchange':
url="/pages_app/myWelfareCard/myWelfareCard" url="/pages_app/myWelfareCard/myWelfareCard?from=my"
break; break;
case 'bankCard': case 'bankCard':
url="/pages_app/idcardAuth/bankCardList" url="/pages_app/idcardAuth/bankCardList"
@@ -428,8 +532,9 @@
url="/pages_app/setting/setting" url="/pages_app/setting/setting"
break; break;
default: default:
const push_clientid = uni.getStorageSync("push_clientid")
uni.showToast({ uni.showToast({
title: '功能开发中', title: 'push_clientid: '+push_clientid,
icon: 'none' icon: 'none'
}); });
} }
@@ -438,6 +543,131 @@
}) })
}; };
// 判断通知权限是否开启
const isNoticeOn = () => {
// #ifdef APP-PLUS
if (plus.os.name == "Android") {
var main = plus.android.runtimeMainActivity();
var NotificationManagerCompat = plus.android.importClass("android.support.v4.app.NotificationManagerCompat");
if (NotificationManagerCompat == null) {
NotificationManagerCompat = plus.android.importClass("androidx.core.app.NotificationManagerCompat");
}
var areNotificationsEnabled = NotificationManagerCompat.from(main).areNotificationsEnabled();
notice_on.value = areNotificationsEnabled
return areNotificationsEnabled;
} else if (plus.os.name == "iOS") {
var isIosOn = undefined;
var types = 0;
var app = plus.ios.invoke("UIApplication", "sharedApplication");
var settings = plus.ios.invoke(app, "currentUserNotificationSettings");
if (settings) {
types = settings.plusGetAttribute("types");
plus.ios.deleteObject(settings);
} else {
types = plus.ios.invoke(app, "enabledRemoteNotificationTypes");
}
plus.ios.deleteObject(app);
isIosOn = 0 != types;
notice_on.value = isIosOn
return isIosOn;
}
// #endif
}
// 前往系统设置页面开启或关闭通知权限
const switchPushPermissions = () => {
const push_clientid = uni.getStorageSync("push_clientid")
// #ifdef APP-PLUS
let title = isNoticeOn() ? "通知权限关闭提醒" : "通知权限开启提醒";
let content = isNoticeOn()
? "通知权限已开启,是否前往设置关闭?"+push_clientid
: "您还没有开启通知权限,无法接受到消息通知,是否前往设置?"+push_clientid;
// 打开安卓系统设置页面
let openAndroidSetting = () => {
var main = plus.android.runtimeMainActivity();
var pkName = main.getPackageName();
var uid = main.getApplicationInfo().plusGetAttribute("uid");
var Intent = plus.android.importClass("android.content.Intent");
var Build = plus.android.importClass("android.os.Build");
//android 8.0引导
if (Build.VERSION.SDK_INT >= 26) {
var intent = new Intent("android.settings.APP_NOTIFICATION_SETTINGS");
intent.putExtra("android.provider.extra.APP_PACKAGE", pkName);
} else if (Build.VERSION.SDK_INT >= 21) {
//android 5.0-7.0
var intent = new Intent("android.settings.APP_NOTIFICATION_SETTINGS");
intent.putExtra("app_package", pkName);
intent.putExtra("app_uid", uid);
} else {
//(<21)其他--跳转到该应用管理的详情页
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
var uri = Uri.fromParts("package", mainActivity.getPackageName(), null);
intent.setData(uri);
}
// 跳转到该应用的系统通知设置页
main.startActivity(intent);
};
// 打开苹果系统设置页面
let openIOSSetting = () => {
var app = plus.ios.invoke("UIApplication", "sharedApplication");
var setting = plus.ios.invoke("NSURL", "URLWithString:", "app-settings:");
plus.ios.invoke(app, "openURL:", setting);
plus.ios.deleteObject(setting);
plus.ios.deleteObject(app);
// ios18跳转不过去的解决方案
// 方案一:使用uni.openAppAuthorizeSetting(),文档地址:https://uniapp.dcloud.net.cn/api/system/openappauthorizesetting.html
// 方案二:待验证,AI生成;来源评论区:iOS 18以后,openURL:完全被弃用,需要用openURL:options:completionHandler:替代。
// var app = plus.ios.invoke("UIApplication", "sharedApplication");
// var url;
// // 获取安全 URL(iOS 8+)
// if (plus.ios.invoke("UIApplication", "respondsToSelector:", "openSettingsURLString")) {
// var urlString = plus.ios.invoke("UIApplication", "openSettingsURLString");
// url = plus.ios.invoke("NSURL", "URLWithString:", urlString);
// } else {
// url = plus.ios.invoke("NSURL", "URLWithString:", "app-settings:");
// }
// // 判断新 API 可用性(iOS 10+)
// var supportsNewAPI = plus.ios.invoke(app, "respondsToSelector:", "openURL:options:completionHandler:");
// if (supportsNewAPI) {
// var options = plus.ios.newObject("NSDictionary");
// var completion = plus.ios.newBlock(function(success) {
// console.log("跳转结果:" + success);
// if (!success) {
// plus.nativeUI.toast("无法打开设置,请手动前往");
// }
// }, "v@:B");
// plus.ios.invoke(app, "openURL:options:completionHandler:", url, options, completion);
// plus.ios.deleteObject(completion);
// } else {
// if (plus.ios.invoke(app, "canOpenURL:", url)) {
// plus.ios.invoke(app, "openURL:", url);
// } else {
// throw new Error("当前系统不支持跳转设置");
// }
// }
// plus.ios.deleteObject(url);
// plus.ios.deleteObject(app);
};
// 弹窗提醒开通或关闭权限,点击确认后,跳转到系统设置页面进行设置
uni.showModal({
title: title,
content: content,
showCancel: true,
confirmColor: "#ff903f",
success: (res) => {
if (res.confirm) {
if (plus.os.name == "Android") {
openAndroidSetting();
} else if (plus.os.name == "iOS") {
openIOSSetting();
}
}
},
});
// #endif
}
const showYearSummary = () => { const showYearSummary = () => {
console.log('显示年度总结'); console.log('显示年度总结');
uni.showToast({ uni.showToast({
@@ -453,7 +683,16 @@
showCancel: false showCancel: false
}); });
}; };
const honnor_detail=ref({});
const getHonorDetail=async(id)=>{
const res = await api.gethonorDetail({
id:id
})
if (res.code == 1 ) {
honnor_detail.value = res.data;
showHonor.value = true;
}
}
const onStatsClick = (key) => { const onStatsClick = (key) => {
let url = ''; let url = '';
switch (key) { switch (key) {
@@ -485,12 +724,23 @@
isBoundWechat.value = res.data isBoundWechat.value = res.data
} }
} }
const isBoundWechat = ref(false) const isBoundWechat = ref(false);
const isIos = ref(false);
onShow(() => { onShow(() => {
console.log('我的页面显示'); console.log('我的页面显示');
getUserInfoFromStorage() // 先从storage获取用户信息 getUserInfoFromStorage() // 先从storage获取用户信息
fetchMyInfo() // 然后从API获取最新数据 fetchMyInfo() // 然后从API获取最新数据
getIsBoundWechat() getIsBoundWechat()
isNoticeOn();
checkVersion();
getIsAccount();
let systemInfo = uni.getSystemInfoSync();
console.log(systemInfo)
if(systemInfo.osName == 'ios'){
isIos.value = true;
}else{
isIos.value = false;
}
}); });
</script> </script>
@@ -514,12 +764,151 @@
.page { .page {
background-color: $gray-bg; background-color: $gray-bg;
height: calc(100vh - 140rpx); margin-top:calc(var(--status-bar-height) + 44px);
height: calc(100vh - var(--status-bar-height) - 44px);
overflow-y: scroll; overflow-y: scroll;
padding-bottom: 100rpx; }
.flex-row{
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.new-version{
background:#8B2316;
color:#fff;
font-size: 24rpx;
margin-right: 10rpx;
padding: 8rpx 20rpx;
border-radius: 30rpx;
}
.honorwrap{
width:100%;
height:100vh;
display: flex;
align-items: center;
justify-content: center;
.honorbox{
width:80%;
display: flex;
flex-direction: column;
justify-content: center;
position: relative;
min-height:200rpx;
background:linear-gradient(to bottom, #54049b, #da4ee2);
border-radius: 20rpx;
padding: 20rpx;
.row{
display: grid;
grid-template-columns: repeat(2, 1fr); /* 创建两列,每列等宽 */
gap: 20px;
.row_item{
font-size: 26rpx;
color: #da4ee2;
margin-right: 20rpx;
text-align: center;
padding: 10rpx 20rpx;
background:#fff;
white-space: nowrap;
border-radius: 10rpx;
border:1rpx solid #da4ee2;
}
}
.close_honor{
position: absolute;
top:20rpx;
right:20rpx;
}
.honor_image{
width:269rpx;
height:213rpx;
position: absolute;
top:-76rpx;
left:50%;
transform: translateX(-50%);
}
.content{
margin-top:73rpx;
background:#fff;
border-radius: 5rpx;
padding: 20rpx;
.content_title{
font-weight: bold;
padding: 35rpx 0 20rpx;
text-align: center;
font-size: 36rpx;
color: #da4ee2;
}
.content_content{
font-size: 26rpx;
color: #333;
margin-top: 20rpx;
line-height: 1.5;
}
}
}
}
.signwrap{
display: flex;
align-items: center;
justify-content: center;
height:100%;
.signbox{
display: flex;
flex-direction: column;
align-items: center;
position: relative;
z-index:0;
.close{
position:absolute;
right:0;
height:60rpx;
width:60rpx;
opacity: 0;
background:#fff;
z-index:2;
border-radius: 50%;
}
}
} }
.signwrap .signcontent{
width:100%;
top:0;
position: absolute;
z-index:1;
}
.signwrap .signcontent .day{
margin-top: 384rpx;
text-align: center;
}
.signwrap .signtotal{
margin-top: 30rpx;
text-align: center;
font-size: 30rpx;
}
.signwrap .signcontinue{
font-size: 30rpx;
text-align: center;
}
.signwrap .signcontent .tip{
margin-top: 40rpx;
color:red;
font-size: 28rpx;
text-align: center;
}
.signwrap .signcontent .news{
margin: 196rpx 60rpx 0;
height: 116rpx;
font-size: 30rpx;
}
.signwrap .signbg{
width:604rpx;
height:964rpx;
}
// 用户信息卡片 // 用户信息卡片
.user-card { .user-card {
@@ -541,7 +930,6 @@
width: 120rpx; width: 120rpx;
height: 120rpx; height: 120rpx;
border-radius: 10rpx; border-radius: 10rpx;
} }
.user-info-content { .user-info-content {
@@ -549,6 +937,7 @@
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
gap: 10rpx; gap: 10rpx;
margin-left: 10rpx;
.username { .username {
color: $white; color: $white;
@@ -589,6 +978,11 @@
.honor_text { .honor_text {
font-size: 24rpx; font-size: 24rpx;
color: $white; color: $white;
display: flex;
align-items: center;
.bar_rank{
padding:0 6rpx;
}
} }
} }
} }
@@ -619,7 +1013,7 @@
position: absolute; position: absolute;
right: 0; right: 0;
top: 54rpx; top: 54rpx;
width: 160rpx; width: 150rpx;
height: 56rpx; height: 56rpx;
display: flex; display: flex;
align-items: center; align-items: center;
@@ -641,10 +1035,11 @@
right: 0; right: 0;
top: 54rpx; top: 54rpx;
width: 160rpx; width: 160rpx;
height: 56rpx; height: 54rpx;
display: flex; display: flex;
align-items: center; align-items: center;
border: 1rpx solid #fc564a; border: 1rpx solid #fc564a;
border-right: none;
border-radius: 30rpx 0 0 30rpx; border-radius: 30rpx 0 0 30rpx;
background: #fff url("@/static/qd_bg.9.png")no-repeat 0 0; background: #fff url("@/static/qd_bg.9.png")no-repeat 0 0;
background-size: 57rpx 56rpx; background-size: 57rpx 56rpx;
+131 -71
View File
@@ -1,5 +1,5 @@
<template> <template>
<uni-nav-bar title="患教学堂" fixed color="#8B2316" height="140rpx" :border="false" backgroundColor="#eeeeee"></uni-nav-bar> <navBar :title="'患教学堂'" :showLeft="false"></navBar>
<view class="page"> <view class="page">
<!-- 顶部标题栏 --> <!-- 顶部标题栏 -->
@@ -10,12 +10,13 @@
<view class="tab-item" :class="{ active: activeTab === 0 }" @click="switchTab(0)"> <view class="tab-item" :class="{ active: activeTab === 0 }" @click="switchTab(0)">
患教文库 患教文库
</view> </view>
<view class="bar"></view>
<view class="tab-item" :class="{ active: activeTab === 1 }" @click="switchTab(1)"> <view class="tab-item" :class="{ active: activeTab === 1 }" @click="switchTab(1)">
患教视频 患教视频
</view> </view>
<view class="tab-item" :class="{ active: activeTab === 2 }" @click="switchTab(2)"> <!-- <view class="tab-item" :class="{ active: activeTab === 2 }" @click="switchTab(2)">
常见问题 常见问题
</view> </view> -->
</view> </view>
<!-- 筛选栏 --> <!-- 筛选栏 -->
@@ -51,7 +52,7 @@
:class="{ active: tag.selected }" :class="{ active: tag.selected }"
@click="toggleTag(index)" @click="toggleTag(index)"
> >
{{ tag.DES }} {{ tag.NAME }}
</view> </view>
</view> </view>
@@ -74,20 +75,20 @@
:lower-threshold="100" :lower-threshold="100"
> >
<view class="article-item" v-for="(article, index) in articleList" :key="article.uuid || index" @click="goToDetail(article)"> <view class="article-item" v-for="(article, index) in articleList" :key="article.uuid || index" @click="goToDetail(article)">
<image class="article-image" :src="article.image" mode="aspectFill"></image> <image class="article-image" :src="article.image"></image>
<view class="article-content"> <view class="article-content">
<view class="article-title">{{ article.title }}</view> <view class="article-title">{{ article.title }}</view>
<view class="article-meta"> <view class="article-meta">
<view class="date-tag" v-if="article.isToday">今日</view> <view class="date-tag" v-if="article.isToday">今日</view>
<view class="date" v-else>{{ article.date }}</view> <view class="date" v-else>{{ article.date }}</view>
<view class="stats"> <view class="stats">
<view class="stat-item"> <view class="stat-item readnum">
<uni-icons type="eye" size="12" color="#999"></uni-icons> <uni-icons type="eye" size="12" color="#999"></uni-icons>
<text>{{ article.views }}</text> <text>{{ formatNumber(article.views) }}</text>
</view> </view>
<view class="stat-item"> <view class="stat-item">
<uni-icons type="hand-up" size="13" color="#999"></uni-icons> <uni-icons type="hand-up" size="13" color="#999"></uni-icons>
<text>{{ article.likes }}</text> <text>{{ formatNumber(article.likes) }}</text>
</view> </view>
</view> </view>
</view> </view>
@@ -107,8 +108,9 @@
<!-- 空状态 --> <!-- 空状态 -->
<view class="empty-state" v-if="articleList.length === 0 && !loading"> <view class="empty-state" v-if="articleList.length === 0 && !loading">
<uni-icons type="article" size="80" color="#cccccc"></uni-icons> <empty :emptyDesc="'暂无患教信息'"></empty>
<text class="empty-text">暂无文章</text> <!-- <uni-icons type="article" size="80" color="#cccccc"></uni-icons>
<text class="empty-text">暂无文章</text> -->
</view> </view>
</scroll-view> </scroll-view>
@@ -120,7 +122,7 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { onShow } from "@dcloudio/uni-app"; import {onShow,onLoad,onBackPress } from "@dcloudio/uni-app";
import CustomTabbar from '@/components/tabBar/tabBar.vue'; import CustomTabbar from '@/components/tabBar/tabBar.vue';
import upImg from "@/static/cb_up.png" import upImg from "@/static/cb_up.png"
import downImg from "@/static/cb_up.png" import downImg from "@/static/cb_up.png"
@@ -129,9 +131,24 @@
import api from "@/api/api.js" import api from "@/api/api.js"
import docUrl from '@/utils/docUrl.js'; import docUrl from '@/utils/docUrl.js';
import navTo from '@/utils/navTo.js'; import navTo from '@/utils/navTo.js';
import navBar from "@/components/navBar/navBar.vue"
import formatNumber from '@/utils/formatNumber.js';
const type=ref(1) const type=ref(1)
onBackPress(() => {
plus.runtime.quit();
return true;
});
onLoad(() => {
uni.sendNativeEvent("portraitReport",{
msg:{
event_nickname: "list_page",
page_type: "患教文库",
},
},
(ret) => {
console.log(ret);
});
})
// 分页相关状态 // 分页相关状态
const currentPage = ref(1); const currentPage = ref(1);
const pageSize = ref(10); const pageSize = ref(10);
@@ -147,7 +164,8 @@
// 重置分页状态 // 重置分页状态
currentPage.value = 1; currentPage.value = 1;
hasMore.value = true; hasMore.value = true;
polularScienceArticleListByKeywordsNew(true); articleList.value = [];
polularScienceArticleListByKeywordsNew(false);
}; };
const loadGuideTags = async () => { const loadGuideTags = async () => {
try { try {
@@ -195,9 +213,9 @@
// 判断是否还有更多数据 // 判断是否还有更多数据
hasMore.value = newData.length >= pageSize.value; hasMore.value = newData.length >= pageSize.value;
if (!isRefresh) { // if (!isRefresh) {
currentPage.value++; // currentPage.value++;
} // }
console.log('文章数据加载成功:', articleList.value); console.log('文章数据加载成功:', articleList.value);
} }
@@ -240,9 +258,18 @@
// 格式化日期 // 格式化日期
const formatDate = (date) => { const formatDate = (date) => {
const today = new Date();
const year = date.getFullYear();
const currentYear = today.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0'); const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0');
return `${month}-${day}`;
// 如果年份是今年,只显示月日,否则显示年月日
if (year === currentYear) {
return `${month}-${day}`;
} else {
return `${year}-${month}-${day}`;
}
}; };
// 筛选标签数据 // 筛选标签数据
const filterTags = ref([]); const filterTags = ref([]);
@@ -255,12 +282,12 @@
if(index==1){ if(index==1){
navTo({ navTo({
url: '/pages_app/patientVideo/patientVideo' url: '/pages_app/patientVideo/patientVideo?from=patientClass'
}) })
}else if(index==2){ }else if(index==2){
let url=encodeURIComponent('https://wx.igandan.com/wxPatient/index.htm#/problem?link=share&fromtype=doctor') let url=encodeURIComponent('https://wx.igandan.com/wxPatient/index.htm#/problem?link=share&fromtype=doctor')
navTo({ navTo({
url: '/pages_app/webview/webview?url='+url url: '/pages_app/webview/webview?url='+url+'&title='+'常见问题'
}) })
} }
}; };
@@ -270,15 +297,15 @@
// 跳转到详情页 // 跳转到详情页
if (article.path) { if (article.path) {
uni.navigateTo({ uni.navigateTo({
url: `/pages_app/webview/webview?url=${encodeURIComponent(docUrl+article.path)}&title=${encodeURIComponent(article.title)}` url: `/pages_app/webview/webviewClass?url=${encodeURIComponent(docUrl+article.path+'?fromtype=doctor')}&title=患教详情&uuid=${article.uuid}&sharetitle=${article.title}&summary=${article.summary}&type=huanjiao&imgPath=${encodeURIComponent(article.image)}&tags=${article.tags}`
}); });
} }
}; };
// 跳转到搜索页面 // 跳转到搜索页面
const goToSearch = () => { const goToSearch = () => {
uni.navigateTo({ uni.sendNativeEvent('goHomeSearch',{
url: '/pages_app/search/search' msg: 'patientClass'
}); });
}; };
@@ -296,6 +323,7 @@
// 上拉加载更多 // 上拉加载更多
const onLoadMore = async () => { const onLoadMore = async () => {
if (!hasMore.value || loading.value) return; if (!hasMore.value || loading.value) return;
currentPage.value++
await polularScienceArticleListByKeywordsNew(false); await polularScienceArticleListByKeywordsNew(false);
}; };
@@ -319,7 +347,19 @@
}; };
const toggleTag = (index) => { const toggleTag = (index) => {
filterTags.value[index].selected = !filterTags.value[index].selected; const tag = filterTags.value[index];
// 若当前为未选中状态,且已选满3个,则禁止再选
if (!tag.selected) {
const selectedCount = filterTags.value.filter(t => t.selected).length;
if (selectedCount >= 3) {
uni.showToast({
title: '最多选择3个标签',
icon: 'none'
});
return;
}
}
tag.selected = !tag.selected;
}; };
const resetFilter = () => { const resetFilter = () => {
@@ -333,25 +373,26 @@
// 检查是否有选中的标签 // 检查是否有选中的标签
const hasSelected = filterTags.value.some(tag => tag.selected); const hasSelected = filterTags.value.some(tag => tag.selected);
isFilterActive.value = hasSelected; isFilterActive.value = hasSelected;
let words='';
// 执行筛选逻辑 // 执行筛选逻辑
if (hasSelected) { if (hasSelected) {
const selectedTags = filterTags.value.filter(tag => tag.selected).map(tag => tag.DES); const selectedTags = filterTags.value.filter(tag => tag.selected).map(tag => tag.NAME);
console.log('选中的标签:', selectedTags); console.log('选中的标签:', selectedTags);
keywords.value = selectedTags.join(','); words = selectedTags.join(',');
} else { } else {
keywords.value = ''; words = '';
} }
keywords.value = words;
// 重置分页状态 // 重置分页状态
currentPage.value = 1; currentPage.value = 1;
hasMore.value = true; hasMore.value = true;
articleList.value = [];
showFilter.value = false; showFilter.value = false;
polularScienceArticleListByKeywordsNew(true); polularScienceArticleListByKeywordsNew(false);
}; };
onShow(() => { onShow(() => {
polularScienceArticleListByKeywordsNew(true); polularScienceArticleListByKeywordsNew(true);
loadGuideTags(); loadGuideTags();
}); });
@@ -369,14 +410,14 @@ $gray-dark: #666;
$text-color: #333; $text-color: #333;
// 尺寸变量 // 尺寸变量
$border-radius: 8px; $border-radius: 16rpx;
$border-radius-small: 6px; $border-radius-small: 12rpx;
$padding: 15px; $padding: 30rpx;
$padding-small: 10px; $padding-small: 20rpx;
.page { .page {
background-color: $gray-bg; background-color: $gray-bg;
height: calc(100vh - 140rpx); height: calc(100vh - 180rpx);
display: flex; display: flex;
overflow-y: hidden; overflow-y: hidden;
flex-direction: column; flex-direction: column;
@@ -385,19 +426,25 @@ $padding-small: 10px;
.top{ .top{
width:100%; width:100%;
position: fixed; position: fixed;
top:140rpx; top:calc(var(--status-bar-height) + 44px);
} }
// 选项卡 // 选项卡
.tabs { .tabs {
background-color: $white; background-color: $white;
display: flex; display: flex;
border-bottom: 1px solid $gray-light; align-items: center;
border-bottom: 2rpx solid $gray-light;
.bar{
width: 2rpx;
height: 98rpx;
background-color: $gray-light;
}
.tab-item { .tab-item {
flex: 1; flex: 1;
text-align: center; text-align: center;
padding: $padding 0; padding: $padding 0;
font-size: 16px; font-size: 32rpx;
color: $gray-dark; color: $gray-dark;
position: relative; position: relative;
@@ -411,8 +458,8 @@ $padding-small: 10px;
bottom: 0; bottom: 0;
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
width: 30px; width: 60rpx;
height: 2px; height: 4rpx;
} }
} }
@@ -425,25 +472,25 @@ $padding-small: 10px;
padding: $padding-small $padding; padding: $padding-small $padding;
display: flex; display: flex;
align-items: center; align-items: center;
border-bottom: 1px solid $gray-light; border-bottom: 2rpx solid $gray-light;
.search-box { .search-box {
flex: 1; flex: 1;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
gap: 5px; gap: 10rpx;
.search-text { .search-text {
font-size: 14px; font-size: 28rpx;
color: $gray-medium; color: $gray-medium;
} }
} }
.divider { .divider {
width: 1px; width: 2rpx;
height: 16px; height: 32rpx;
background-color: $gray-medium; background-color: $gray-light;
margin: 0 $padding; margin: 0 $padding;
} }
@@ -452,8 +499,8 @@ $padding-small: 10px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 3px; gap: 6rpx;
font-size: 14px; font-size: 28rpx;
color: #999; color: #999;
transition: all 0.3s ease; transition: all 0.3s ease;
} }
@@ -466,23 +513,24 @@ $padding-small: 10px;
// 文章列表 // 文章列表
.article-list { .article-list {
position: fixed; position: fixed;
height:calc(100vh - 450rpx); // height:calc(100vh - var(--status-bar-height) - 44px - 207rpx);
top: 342rpx; top: calc(var(--status-bar-height) + 44px + 188rpx);
padding-bottom: 130rpx; bottom: 128rpx;
.article-item { .article-item {
background-color: $white; background-color: $white;
margin-bottom: $padding-small; margin-bottom: $padding-small;
border-radius: $border-radius; border-radius: $border-radius;
padding: $padding; padding: $padding;
display: flex; display: flex;
gap: 12px;
.article-image { .article-image {
width: 80px; width: 260rpx;
height: 80px; height: 160rpx;
border-radius: $border-radius-small; border-radius: $border-radius-small;
flex-shrink: 0; flex-shrink: 0;
margin-right: 24rpx;
} }
.article-content { .article-content {
@@ -492,7 +540,7 @@ $padding-small: 10px;
justify-content: space-between; justify-content: space-between;
.article-title { .article-title {
font-size: 16px; font-size: 32rpx;
color: $text-color; color: $text-color;
line-height: 1.4; line-height: 1.4;
margin-bottom: $padding-small; margin-bottom: $padding-small;
@@ -506,32 +554,40 @@ $padding-small: 10px;
.article-meta { .article-meta {
display: flex; display: flex;
justify-content: space-between;
align-items: center; align-items: center;
.date-tag { .date-tag {
background-color: $primary-color; background-color: $primary-color;
color: $white; color: $white;
font-size: 12px; font-size: 24rpx;
padding: 2px 6px; padding: 4rpx 12rpx;
border-radius: 3px; border-radius: 6rpx;
} }
.date { .date {
font-size: 12px; width: 125rpx;
font-size: 24rpx;
color: $gray-medium; color: $gray-medium;
white-space: nowrap;
} }
.stats { .stats {
display: flex; display: flex;
gap: $padding; gap: $padding;
margin-left: 50rpx;
.readnum{
width:100rpx;
}
.stat-item { .stat-item {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 3px; gap: 10rpx;
font-size: 12px;
font-size: 24rpx;
color: $gray-medium; color: $gray-medium;
text{
white-space: nowrap;
}
} }
} }
} }
@@ -591,8 +647,8 @@ $padding-small: 10px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
gap: 2px; gap: 4rpx;
font-size: 12px; font-size: 24rpx;
color: $gray-medium; color: $gray-medium;
&.active { &.active {
@@ -600,7 +656,7 @@ $padding-small: 10px;
} }
text { text {
font-size: 10px; font-size: 20rpx;
} }
} }
} }
@@ -619,7 +675,7 @@ $padding-small: 10px;
z-index: 9999; z-index: 9999;
.filter-content { .filter-content {
margin-top:329rpx; margin-top:calc(var(--status-bar-height) + 44px + 188rpx);;
background-color: $white; background-color: $white;
// border-radius: 20rpx 20rpx 0 0; // border-radius: 20rpx 20rpx 0 0;
@@ -630,17 +686,21 @@ $padding-small: 10px;
.filter-tags { .filter-tags {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 20rpx;
margin-bottom: 60rpx; margin-bottom: 60rpx;
max-height: 65vh; max-height: 65vh;
overflow-y: auto; overflow-y: auto;
.tag-item { .tag-item {
background-color: #f8f8f8; background-color: #f8f8f8;
color: $gray-dark; color: $gray-dark;
padding: 8rpx 24rpx; padding: 8rpx 0rpx;
width:152rpx;
margin:8rpx;
text-align:center;
border-radius: 30rpx; border-radius: 30rpx;
font-size: 26rpx; font-size: 26rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
border: 2rpx solid #efefef; border: 2rpx solid #efefef;
transition: all 0.3s ease; transition: all 0.3s ease;
+96
View File
@@ -0,0 +1,96 @@
<template>
<view>
</view>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { onShow, onLoad } from "@dcloudio/uni-app";
// 页面加载
onLoad(() => {
console.log('登录页面加载完成');
// #ifdef APP
console.log('一键登录');
uni.login({
provider: 'univerify',
univerifyStyle: {
"fullScreen": true, // 是否全屏显示,默认值: false
"backgroundColor": "#ffffff", // 授权页面背景颜色,默认值:#ffffff
"backgroundImage": "", // 全屏显示的背景图片,默认值:"" (仅支持本地图片,只有全屏显示时支持)
"icon": {
"path": "static/xxx.png", // 自定义显示在授权框中的logo,仅支持本地图片 默认显示App logo
"width": "60px", //图标宽度 默认值:60px
"height": "60px" //图标高度 默认值:60px
},
"closeIcon": {
"path": "static/xxx.png", // 自定义显示在授权框中的logo,仅支持本地图片
"width": "60px", //图标宽度 默认值:60px (HBuilderX 4.0+ 仅iOS支持)
"height": "60px" //图标高度 默认值:60px (HBuilderX 4.0+ 仅iOS支持)
},
"phoneNum": {
"color": "#202020" // 手机号文字颜色 默认值:#202020
},
"slogan": {
"color": "#BBBBBB" // slogan 字体颜色 默认值:#BBBBBB
},
"authButton": {
"normalColor": "#3479f5", // 授权按钮正常状态背景颜色 默认值:#3479f5
"highlightColor": "#2861c5", // 授权按钮按下状态背景颜色 默认值:#2861c5(仅ios支持)
"disabledColor": "#73aaf5", // 授权按钮不可点击时背景颜色 默认值:#73aaf5(仅ios支持)
"textColor": "#ffffff", // 授权按钮文字颜色 默认值:#ffffff
"title": "本机号码一键登录", // 授权按钮文案 默认值:“本机号码一键登录”
"borderRadius": "24px" // 授权按钮圆角 默认值:"24px" (按钮高度的一半)
},
"otherLoginButton": {
"visible": true, // 是否显示其他登录按钮,默认值:true
"normalColor": "", // 其他登录按钮正常状态背景颜色 默认值:透明
"highlightColor": "", // 其他登录按钮按下状态背景颜色 默认值:透明
"textColor": "#656565", // 其他登录按钮文字颜色 默认值:#656565
"title": "其他登录方式", // 其他登录方式按钮文字 默认值:“其他登录方式”
"borderColor": "", //边框颜色 默认值:透明(仅iOS支持)
"borderRadius": "0px" // 其他登录按钮圆角 默认值:"24px" (按钮高度的一半)
},
"privacyTerms": {
"defaultCheckBoxState": true, // 条款勾选框初始状态 默认值: true
"isCenterHint": false, //未勾选服务条款时点击登录按钮的提示是否居中显示 默认值: false (3.7.13+ 版本支持)
"uncheckedImage": "", // 可选 条款勾选框未选中状态图片(仅支持本地图片 建议尺寸 24x24px)(3.2.0+ 版本支持)
"checkedImage": "", // 可选 条款勾选框选中状态图片(仅支持本地图片 建议尺寸24x24px)(3.2.0+ 版本支持)
"checkBoxSize": 12, // 可选 条款勾选框大小
"textColor": "#BBBBBB", // 文字颜色 默认值:#BBBBBB
"termsColor": "#5496E3", // 协议文字颜色 默认值: #5496E3
"prefix": "我已阅读并同意", // 条款前的文案 默认值:“我已阅读并同意”
"suffix": "并使用本机号码登录", // 条款后的文案 默认值:“并使用本机号码登录”
"privacyItems": [ // 自定义协议条款,最大支持2个,需要同时设置url和title. 否则不生效
{
"url": "https://", // 点击跳转的协议详情页面
"title": "用户服务协议" // 协议名称
}
]
}
},
success(res) { // 登录成功
console.log(res.authResult); // {openid:'登录授权唯一标识',access_token:'接口返回的 token'}
},
fail(res) { // 登录失败
console.log(res.errCode)
console.log(res.errMsg)
uni.redirectTo({
url: '/pages_app/login/login?univerify=0'
});
}
})
// #endif
});
// 页面显示
onShow(() => {
console.log('登录页面显示');
});
</script>
<style>
</style>
+299
View File
@@ -0,0 +1,299 @@
<template>
<view class="wrap">
<view :class="isFull ? 'player-full' : 'player'">
<plv-player
ref="vodPlayer"
class="vod-player"
seekType=1
autoPlay=true
disableScreenCAP=false
rememberLastPosition=false
@onPlayStatus="onPlayStatus"
@onPlayError="onPlayError"
@positionChange="positionChange"
@onEnableSubtitle="onEnableSubtitle"
@onSRTTextConfig="onSRTTextConfig"
@onSRTTitle="onSRTTitle">
</plv-player>
<skin ref="skinRef"
class="skin-control"
:defaultVolume="defaultVolume"
@onPlayBtnClick="onPlayBtnClick"
@onToSeek="onToSeek"
@onFullBtnClick="onFullBtnClick"
@onHdBtnClick="onHdBtnClick"
@onRateBtnClick="onRateBtnClick"
@onVolumeChanged="onVolumeChanged"
@onScalingBtnClick="onScalingBtnClick"
@onScreenShot="onScreenShot"
@onChangeSRTBtnClick="onChangeSRTBtnClick"
@onChangeSRTSingleMode="onChangeSRTSingleMode"
@onOpenSRT="onOpenSRT">
</skin>
</view>
<view v-if="!isFull" style="padding: 20rpx;">
<input class="uni-input" style="min-height: 35px; height: 35px; border: 1px solid #cccccc;font-size: 14px;" :value="videoVid"
@input="onVidInput" placeholder="请输入视频vid" />
<button type="primary" @click="setVid()">播放</button>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import { onShow } from '@dcloudio/uni-app';
//import Head from '../../components/page-head.nvue';
//import skin from '@/components/plv-player-skin/skin.nvue';
//import DragTips from '@/components/plv-player-skin/drag-tips.nvue';
const dom = weex.requireModule('dom');
var configModule = uni.requireNativePlugin("PLV-VodUniPlugin-ConfigModule")
const vod=ref(null);
const title='播放器Demo';
// 播放器组件
const vodPlayer=ref(null);
const skinRef=ref(null);
const isFull=ref(false);
const defaultVolume=ref(0.5);
const videoVid=ref('4ae3fe3ab5a2009a8d6605d81b2e3c9b_4');
onShow(() => {
configModule.setConfig({
'config': "dDzXiPJ8psA/7LvtlPgpNfdOjzKVVEYvca1QBOp2MqCVKC93YiligqqfzOzETkDmolLNBUF4vAykBuUnn+U4V9NoK9vaofwESluIMO9WSB0eWhrw+fBkn+h+OZAmwmYgHQ6fCtsggZKN6cW/FhjWAQ==", //加密串配置
},
(ret) => {
if (ret.isSuccess == true) {
uni.showToast({
title: '设置加密串成功',
icon: "none"
})
}
})
})
const onVidInput=(event)=> {
videoVid.value = event.detail.value;
};
const setVid=()=> {
if(!videoVid.value) {
uni.showToast({
title: "请输入视频Vid",
icon: "none"
})
return;
}
vodPlayer.value.setVid({
vid:videoVid.value,
level:0
}, (ret) => {
console.log(12)
console.log(ret)
text.value = JSON.stringify(ret);
if (ret.errMsg != null) {
uni.showToast({
title: ret.errMsg,
icon: "none"
})
}
});
};
const onPlayError=(e)=>{
console.log(e);
if (e.detail.errEvent != null) {
uni.showToast({
title:'playErrorEvent - '+e.detail.errEvent,
icon: "none"
})
}
};
const positionChange=(e)=>{
skinRef.value.timeUpdate(e.detail.currentPosition);
};
const onEnableSubtitle=(e)=>{
console.log("===1" + e.detail);
skinRef.value.setEnableSubtitle(e.detail.isEnableSubtitle, e.detail.isEnableDoubleSubtitle);
};
const onSRTTextConfig=(e)=>{
console.log("===1 fontColor: " + e.detail.fontColor);
skinRef.value.setSRTTextConfig(e.detail);
};
const onSRTTitle=(e)=>{
console.log("===1 onSRTTitle" + e.detail);
};
const onPlayStatus=(e)=>{
//const { skin, vodPlayer } = this;
const state = e.detail.playbackState;
const preparedToPlay = e.detail.preparedToPlay;
if (state != null) {
skinRef.value.changePlayStatus(state === 'start');
} else if (preparedToPlay != null) {
updateDuration();
updateLevels();
updateScaling();
}
};
const updateDuration=()=> {
if (vodPlayer.value) return;
vodPlayer.value.getDuration(null, ret => {
skinRef.value.updateDuration(ret.duration);
});
};
const updateLevels=()=> {
if (vodPlayer.value) return;
vodPlayer.value.getLevelNum(null, ret => {
skinRef.value.updateLevels(ret.levelNum);
});
vodPlayer.value.getCurrentLevel(null, ret => {
skinRef.value.updateCurrentLevel(ret.currentLevel);
});
};
const updateScaling=()=> {
if (vodPlayer.value) return;
vodPlayer.value.getScalingMode({}, ret => {
skinRef.value.updateScaling(ret.scalingMode);
});
};
const onPlayBtnClick=(isPlaying)=> {
if (vodPlayer.value) return;
isPlaying ? vodPlayer.value.start() : vodPlayer.value.pause();
};
const onHdBtnClick=(level)=> {
if (vodPlayer.value) return;
vodPlayer.value.changeLevel({level});
};
const onRateBtnClick=(speed)=> {
if (vodPlayer.value) return;
vodPlayer.value.setSpeed({speed});
};
const onScalingBtnClick=(scalingMode)=> {
if (vodPlayer.value) return;
vodPlayer.value.setScalingMode({scalingMode});
};
const onChangeSRTBtnClick=(srt)=> {
console.log("===1 " + srt);
if (vodPlayer.value) return;
vodPlayer.value.changeSRT(srt);
};
const onChangeSRTSingleMode=(isSingle)=> {
if (vodPlayer.value) return;
vodPlayer.value.changeSRTSingleMode(isSingle);
};
const onOpenSRT=(isOpen)=> {
if (vodPlayer.value) return;
vodPlayer.value.setOpenSRT({
openSrt: isOpen
});
};
const onFullBtnClick=(isFull)=> {
// plus.screen.unlockOrientation();
plus.navigator.setFullscreen(isFull);
if (vodPlayer.value) return;
isFull ? vodPlayer.value.changeToLandscape() : vodPlayer.value.changeToPortrait();
isFull.value = isFull;
};
const onToSeek=(time)=> {
if (vodPlayer.value) return;
vodPlayer.value.seekTo({seconds: time});
};
const onVolumeChanged=(value)=> {
if (vodPlayer.value) return;
vodPlayer.value.getVolume(null, ret => {
const changedValue = ret.volume + value;
const realValue =limit(changedValue, 0, 1);
vodPlayer.value.setVolume({volume: realValue});
skinRef.value.updateVolumeValue(realValue);
});
};
const onScreenShot=()=> {
if (vodPlayer.value) return;
vodPlayer.value.snapshot(null, result =>{
if(result.errMsg != null){
console.log(result.errMsg)
}
});
};
/**
* 控制最小值,最大值
* @param num
* @param min
* @param max
* @returns {*}
*/
const limit=(num, min, max)=> {
if (num < min) return min;
if (num > max) return max;
return num;
}
</script>
<style>
.wrap {
flex: 1;
}
.title {
height: 140rpx;
}
.player {
height: 400rpx;
}
.player-full {
flex: 1;
}
.vod-player {
flex: 1;
}
.skin-control {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1;
}
.hide {
height: 0;
}
</style>
+337
View File
@@ -0,0 +1,337 @@
<template>
<view class="wrap">
<view :class="isFull ? 'player-full' : 'player'">
<plv-player
ref="vod"
class="vod-player"
seekType=1
autoPlay=true
disableScreenCAP=false
rememberLastPosition=false
@onPlayStatus="onPlayStatus"
@onPlayError="onPlayError"
@positionChange="positionChange"
@onEnableSubtitle="onEnableSubtitle"
@onSRTTextConfig="onSRTTextConfig"
@onSRTTitle="onSRTTitle">
</plv-player>
<Skin ref="skin"
class="skin-control"
:defaultVolume="defaultVolume"
@onPlayBtnClick="onPlayBtnClick"
@onToSeek="onToSeek"
@onFullBtnClick="onFullBtnClick"
@onHdBtnClick="onHdBtnClick"
@onRateBtnClick="onRateBtnClick"
@onVolumeChanged="onVolumeChanged"
@onScalingBtnClick="onScalingBtnClick"
@onScreenShot="onScreenShot"
@onChangeSRTBtnClick="onChangeSRTBtnClick"
@onChangeSRTSingleMode="onChangeSRTSingleMode"
@onOpenSRT="onOpenSRT">
</Skin>
</view>
<view v-if="!isFull" style="padding: 20rpx;">
<input class="uni-input" style="min-height: 35px; height: 35px; border: 1px solid #cccccc;font-size: 14px;" :value="videoVid"
@input="onVidInput" placeholder="请输入视频vid" />
<button type="primary" @click="setVid()">播放</button>
</view>
</view>
</template>
<script>
//import Head from '../../components/page-head.nvue';
import Skin from '@/components/plv-player-skin2/skin.nvue';
//import DragTips from '@/components/plv-player-skin/drag-tips.nvue';
const dom = weex.requireModule('dom');
var configModule = uni.requireNativePlugin("PLV-VodUniPlugin-ConfigModule")
export default {
components: {
Skin,
//Head
},
onReady() {
configModule.setConfig({
'config': "dDzXiPJ8psA/7LvtlPgpNfdOjzKVVEYvca1QBOp2MqCVKC93YiligqqfzOzETkDmolLNBUF4vAykBuUnn+U4V9NoK9vaofwESluIMO9WSB0eWhrw+fBkn+h+OZAmwmYgHQ6fCtsggZKN6cW/FhjWAQ==", //加密串配置
},
(ret) => {
if (ret.isSuccess == true) {
uni.showToast({
title: '设置加密串成功',
icon: "none"
})
} else {
let errMsg = ret.errMsg;
uni.showToast({
title: '设置加密串失败:' + errMsg,
icon: "none"
})
}
})
this.vodPlayer = this.$refs.vod;
this.skin = this.$refs.skin;
// plus.screen.lockOrientation('portrait-primary');
},
data() {
return {
title: '播放器Demo',
// 播放器组件
vodPlayer: null,
// 控制栏组件
skin: null,
//是否全屏
isFull: false,
// 默认音量
defaultVolume: 0.5,
videoVid: '4ae3fe3ab5a2009a8d6605d81b2e3c9b_4'
};
},
methods: {
//输入vid
onVidInput: function(event) {
this.videoVid = event.detail.value;
},
setVid() {
if(!this.videoVid) {
uni.showToast({
title: "请输入视频Vid",
icon: "none"
})
return;
}
const { vodPlayer } = this;
vodPlayer.setVid({
vid:this.videoVid,
level:0
}, (ret) => {
console.log(12)
console.log(ret)
this.text = JSON.stringify(ret);
if (ret.errMsg != null) {
uni.showToast({
title: ret.errMsg,
icon: "none"
})
}
});
},
onPlayError(e){
console.log(e);
if (e.detail.errEvent != null) {
uni.showToast({
title:'playErrorEvent - '+e.detail.errEvent,
icon: "none"
})
}
},
positionChange(e){
this.skin.timeUpdate(e.detail.currentPosition);
},
onEnableSubtitle(e){
console.log("===1" + e.detail);
this.skin.setEnableSubtitle(e.detail.isEnableSubtitle, e.detail.isEnableDoubleSubtitle);
},
onSRTTextConfig(e) {
console.log("===1 fontColor: " + e.detail.fontColor);
this.skit.setSRTTextConfig(e.detail);
},
onSRTTitle(e) {
console.log("===1 onSRTTitle" + e.detail);
},
onPlayStatus(e){
const { skin, vodPlayer } = this;
const state = e.detail.playbackState;
const preparedToPlay = e.detail.preparedToPlay;
if (state != null) {
this.skin.changePlayStatus(state === 'start');
} else if (preparedToPlay != null) {
this.updateDuration();
this.updateLevels();
this.updateScaling();
}
},
updateDuration() {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.getDuration(null, ret => {
this.skin.updateDuration(ret.duration);
});
},
updateLevels() {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.getLevelNum(null, ret => {
this.skin.updateLevels(ret.levelNum);
});
vodPlayer.getCurrentLevel(null, ret => {
this.skin.updateCurrentLevel(ret.currentLevel);
});
},
updateScaling() {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.getScalingMode({}, ret => {
this.skin.updateScaling(ret.scalingMode);
});
},
onPlayBtnClick(isPlaying) {
const { vodPlayer } = this;
if (!vodPlayer) return;
isPlaying ? vodPlayer.start() : vodPlayer.pause();
},
onHdBtnClick(level) {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.changeLevel({level});
},
onRateBtnClick(speed) {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.setSpeed({speed});
},
onScalingBtnClick(scalingMode) {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.setScalingMode({scalingMode});
},
onChangeSRTBtnClick(srt) {
console.log("===1 " + srt);
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.changeSRT(srt);
},
onChangeSRTSingleMode(isSingle) {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.changeSRTSingleMode(isSingle);
},
onOpenSRT(isOpen) {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.setOpenSRT({
openSrt: isOpen
});
},
onFullBtnClick(isFull) {
// plus.screen.unlockOrientation();
plus.navigator.setFullscreen(isFull);
//通过h5+实现横竖屏
// isFull ? plus.screen.lockOrientation('landscape-primary') : plus.screen.lockOrientation('portrait-primary');
//通过原生实现横竖屏切换
const { vodPlayer } = this;
if (!vodPlayer) return;
isFull ? vodPlayer.changeToLandscape() : vodPlayer.changeToPortrait();
this.isFull = isFull;
},
onToSeek(time) {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.seekTo({seconds: time});
},
onVolumeChanged(value) {
const { vodPlayer, skin } = this;
if (!vodPlayer) return;
vodPlayer.getVolume(null, ret => {
const changedValue = ret.volume + value;
const realValue = this.limit(changedValue, 0, 1);
vodPlayer.setVolume({volume: realValue});
skin.updateVolumeValue(realValue);
});
},
onScreenShot() {
const { vodPlayer } = this;
if (!vodPlayer) return;
vodPlayer.snapshot(null, result =>{
if(result.errMsg != null){
console.log(result.errMsg)
}
});
},
/**
* 控制最小值,最大值
* @param num
* @param min
* @param max
* @returns {*}
*/
limit(num, min, max) {
if (num < min) return min;
if (num > max) return max;
return num;
}
}
}
</script>
<style>
.wrap {
flex: 1;
}
.title {
height: 140rpx;
}
.player {
height: 400rpx;
}
.player-full {
flex: 1;
}
.vod-player {
flex: 1;
}
.skin-control {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1;
}
.hide {
height: 0;
}
</style>
+10 -2
View File
@@ -1,17 +1,25 @@
<template> <template>
<web-view :src="url"></web-view> <navBar :title="title" ></navBar>
<web-view :src="url" :webview-styles="webviewStyles"></web-view>
</template> </template>
<script> <script>
import navBar from "@/components/navBar/navBar.vue"
export default { export default {
data() { data() {
return { return {
url: '' url: '',
title: '新闻详情',
webviewStyles: {
width: '100%',
height: 'calc(100vh - 160px)'
}
} }
}, },
onLoad(item) { onLoad(item) {
// 传入需要跳转的链接 使用web-view标签进行跳转 // 传入需要跳转的链接 使用web-view标签进行跳转
this.url = decodeURIComponent(item.url) this.url = decodeURIComponent(item.url)
this.title = item.title;
// console.log(this.url) // console.log(this.url)
} }
} }
@@ -0,0 +1,843 @@
<template>
<view class="zhinan-list-page">
<!-- 头部导航栏 -->
<view class="navbox">
<view class="status_bar"></view>
<uni-nav-bar
left-icon="left"
title="文献期刊"
@clickLeft="goBack"
color="#8B2316"
:border="false"
backgroundColor="#eeeeee"
>
</uni-nav-bar>
</view>
<!-- 使用scroll-view实现列表 -->
<scroll-view
class="guidelines-scroll-view"
scroll-y="true"
>
<!-- 指南列表 -->
<view class="guidelines-list">
<view
class="guideline-item"
v-for="(item, index) in downLoadtaskList"
:key="item.id || index"
>
<!-- 指南信息 -->
<view class="item-content">
<view class="item-title">{{ item.title }}</view>
<view class="item-bottom">
<view class="item-date">{{ formatDate(item.releaseTime) }}</view>
<!-- 操作按钮 -->
<view class="item-action">
<view
class="view-btn"
@click.stop="viewGuideline(item)"
>查看</view>
</view>
</view>
</view>
</view>
</view>
<!-- 无数据提示 -->
<view class="no-data" v-if="downLoadtaskList.length === 0 ">
<uni-icons type="info" size="60" color="#999"></uni-icons>
<text>暂无数据</text>
</view>
</scroll-view>
</view>
<!-- 组内排序弹窗 -->
<view v-if="showInnerSort" class="popup-panel">
<view
class="popup-item"
:class="{ active: sort == 1 }"
@click.stop="chooseInnerSort(1)"
>
<text class="item-text">下载量</text>
<uni-icons
v-if="sort == 1"
type="checkmarkempty"
color="#8B2316"
size="22"
></uni-icons>
</view>
<view class="popup-divider"></view>
<view
class="popup-item"
:class="{ active: sort == 2 }"
@click.stop="chooseInnerSort(2)"
>
<text class="item-text">上传时间</text>
<uni-icons
v-if="sort == 2"
type="checkmarkempty"
color="#8B2316"
size="22"
></uni-icons>
</view>
<view class="popup-divider"></view>
<view
class="popup-item"
:class="{ active: sort == 3 }"
@click.stop="chooseInnerSort(3)"
>
<text class="item-text">标题</text>
<uni-icons
v-if="sort == 3"
type="checkmarkempty"
color="#8B2316"
size="22"
></uni-icons>
</view>
</view>
</template>
<script setup>
import { ref, onMounted,computed,nextTick} from "vue";
import api from "@/api/api.js";
import { onShow, onLoad } from "@dcloudio/uni-app";
import docUrl from "@/utils/docUrl.js";
import navTo from "@/utils/navTo.js";
import hotImg from "@/static/hot_booklist.png";
const guidelinesList = ref([]);
const isLoading = ref(false);
const hasMoreData = ref(true);
const currentPage = ref(1);
const pageSize = ref(10);
const isRefreshing = ref(false);
const keywords = ref("");
import upImg from "@/static/triangle_green_theme.png";
import downImg from "@/static/triangle_normal.png";
const loadMoreStatus = ref("more"); // 'loading', 'more', 'noMore'
const typeUuid = ref("");
const title = ref("");
const sort = ref(2);
const showInnerSort = ref(false);
const innerSortTitle = ref("上传时间");
import downloadStore from "@/store/downloadStoreFile.js";
const downloadTasks=ref([]);
const downLoadtaskList=computed(()=>{
return downloadTasks.value.filter((item)=>item.status == "completed" && item.type == "article");
});
const chooseInnerSort = (index) => {
sort.value = index;
if (index == 1) {
innerSortTitle.value = "下载量";
} else if (index == 2) {
innerSortTitle.value = "上传时间";
} else if (index == 3) {
innerSortTitle.value = "标题";
}
showInnerSort.value = false;
loadGuidelinesList(true);
};
onLoad((options) => {
});
const goGot = () => {
navTo({
url: "/pages_app/hot/hot?from=hot&typeUuid=" + typeUuid.value,
});
};
const toggleInnerSort = () => {
showInnerSort.value = !showInnerSort.value;
};
onShow(() => {
syncTasksFromStore();
// 监听store变化
downloadStore.addListener((tasks) => {
downloadTasks.value = tasks;
});
// 恢复正在下载的任务
resumeDownloadingTasks();
// 页面显示时,如果列表为空则加载数据
});
// 跳转到搜索页
const goToSearch = () => {
const q = keywords.value ? encodeURIComponent(keywords.value) : "";
const t = title.value ? encodeURIComponent(title.value) : "";
const type = typeUuid.value
? `&typeUuid=${encodeURIComponent(typeUuid.value)}`
: "";
uni.navigateTo({
url: `/pages_app/search/search?keywords=${q}&title=${t}${type}`,
});
// const goSearch=()=>{
// uni.sendNativeEvent('goHomeSearch', {
// msg: 'goHomeSearch'
// },ret => {
// console.log(ret);
// })
// }
};
// scroll-view 下拉刷新
const onRefresh = async () => {
console.log("scroll-view 下拉刷新触发");
isRefreshing.value = true;
await refreshData();
isRefreshing.value = false;
};
// scroll-view 滚动到底部
const onScrollToLower = () => {
console.log("=== onScrollToLower 触发 ===");
console.log("当前状态:", {
loadMoreStatus: loadMoreStatus.value,
isRefreshing: isRefreshing.value,
currentPage: currentPage.value,
listLength: guidelinesList.value.length,
hasMoreData: hasMoreData.value,
isLoading: isLoading.value,
});
if (
loadMoreStatus.value === "more" &&
!isRefreshing.value &&
!isLoading.value
) {
console.log("条件满足,开始加载更多数据");
loadMoreData();
} else {
console.log("条件不满足,跳过加载:", {
loadMoreStatus: loadMoreStatus.value,
isRefreshing: isRefreshing.value,
isLoading: isLoading.value,
});
}
};
// 刷新数据
const refreshData = async () => {
try {
currentPage.value = 1;
hasMoreData.value = true;
loadMoreStatus.value = "more";
await loadGuidelinesList(true);
uni.showToast({
title: "刷新成功",
icon: "none",
});
} catch (error) {
console.error("刷新失败:", error);
uni.showToast({
title: "刷新失败",
icon: "none",
});
}
};
// 加载指南列表
const loadGuidelinesList = async (isRefresh = false) => {
console.log("=== loadGuidelinesList 开始 ===");
console.log("当前参数:", {
isRefresh,
page: currentPage.value,
keywords: keywords.value,
});
if (isLoading.value) return;
try {
isLoading.value = true;
loadMoreStatus.value = "loading";
const params = {
page: currentPage.value,
pageSize: pageSize.value,
type: 1, // 诊疗指南类型
sort: 2,
typeUuid: typeUuid.value,
keywords: keywords.value,
};
const res = await api.searchLibraryU(params);
console.log("指南列表响应:", res);
if (res.code === 200 && res.data) {
const newData = res.data.list || res.data;
if (isRefresh) {
guidelinesList.value = newData;
console.log("刷新模式:替换列表数据");
} else {
guidelinesList.value = [...guidelinesList.value, ...newData];
console.log("加载更多:追加数据到列表");
}
// 判断是否还有更多数据
if (newData.length < pageSize.value) {
loadMoreStatus.value = "noMore";
hasMoreData.value = false;
console.log("没有更多数据了");
} else {
loadMoreStatus.value = "more";
hasMoreData.value = true;
// 只有在加载更多模式下才递增页码
if (!isRefresh) {
currentPage.value++;
console.log(`还有更多数据,下一页: ${currentPage.value}`);
}
}
} else {
console.error("加载指南列表失败:", res.message);
loadMoreStatus.value = "noMore";
uni.showToast({
title: res.message || "加载失败",
icon: "none",
});
}
} catch (error) {
console.error("加载指南列表异常:", error);
loadMoreStatus.value = "noMore";
uni.showToast({
title: "网络异常,请重试",
icon: "none",
});
} finally {
isLoading.value = false;
console.log("=== loadGuidelinesList 结束 ===");
console.log("最终状态:", {
loadMoreStatus: loadMoreStatus.value,
page: currentPage.value,
listLength: guidelinesList.value.length,
});
}
};
// 加载更多数据
const loadMoreData = () => {
console.log("=== loadMoreData 被调用 ===");
console.log("检查条件:", {
hasMoreData: hasMoreData.value,
isLoading: isLoading.value,
currentPage: currentPage.value,
});
if (hasMoreData.value && !isLoading.value) {
console.log("条件满足,开始加载更多数据");
loadGuidelinesList(false);
} else {
console.log("条件不满足,跳过加载更多");
}
};
// 下载指南
const downloadGuideline = (item) => {
console.log("下载指南:", item);
uni.showToast({
title: "开始下载...",
icon: "none",
});
// 这里可以调用下载API
// 示例:下载PDF文件
if (item.file_path) {
uni.downloadFile({
url: item.file_path,
success: (res) => {
if (res.statusCode === 200) {
uni.showToast({
title: "下载成功",
icon: "none",
});
}
},
fail: (err) => {
console.error("下载失败:", err);
uni.showToast({
title: "下载失败1",
icon: "none",
});
},
});
}
};
const downLoad = (item) => {
if(!item.path){
uni.showToast({
title: "暂无下载链接",
icon: "none",
});
return;
}
addDownloadTask({
url: docUrl + item.path,
title: item.title,
id: item.uuid,
});
};
// 查看指南(支持常用文档直链:pdf/doc/docx/xls/xlsx/ppt/pptx/txt)
const viewGuideline = (cell) => {
console.log(downloadTasks.value);
downloadTasks.value.forEach(item => {
if(item.id == cell.id) {
console.log(2222222222);
console.log(item.localPath);
plus.runtime.openFile(item.localPath, function(e) {
console.log('打开成功');
}, function(e) {
uni.showToast({
title: '打开失败:' + e.message,
icon: "none",
duration: 2000,
});
console.log('打开失败:' + e.message);
});
// uni.openDocument({
// filePath: item.localPath,
// fileType: 'pdf',
// success: () => console.log('打开成功'),
// fail: (err) => {
// uni.showModal({
// title: '提示',
// content: JSON.stringify(err),
// showCancel: false,
// confirmText: '确定',
// success: (res) => {
// if(res.confirm){
// }
// }
// });
// }
// });
// let url = "https://view.xdocin.com/view?src=" + encodeURIComponent(pdfUrl);
// uni.downloadFile({
// url: pdfUrl,
// success: (res) => {
// if (res.statusCode === 200) {
// uni.openDocument({
// filePath: res.tempFilePath, // 临时文件路径
// fileType: 'pdf',
// success: () => console.log('打开成功'),
// fail: (err) => console.error('打开失败', err)
// });
// }
// },
// });
}
});
}
// 返回上一页
const goBack = () => {
uni.navigateBack();
};
// 添加下载任务
const addDownloadTask = (item) => {
// 使用store添加任务
const taskIndex = downloadStore.addTask({
...item,
type: 'zhinan',
});
// 同步任务列表
syncTasksFromStore();
// 开始下载
startDownload(taskIndex);
};
// 开始下载
const startDownload = (index) => {
const taskItem = downloadStore.getTask(index);
if (!taskItem) return;
if(taskItem.status == "loading"){
return false;
};
uni.showToast({
title: "正在下载中",
icon: "none",
});
downloadStore.updateTask(index, {
status: "loading"
});
const task = uni.downloadFile({
url: taskItem.url,
success: (res1) => {
console.log("res1:"+JSON.stringify(res1));
if (res1.statusCode === 200) {
downloadStore.updateTask(index, {
status: "completed",
filePath: res1.tempFilePath,
});
uni.showToast({
title: "下载成功",
icon: "none",
});
uni.saveFile({
tempFilePath: res1.tempFilePath,
success: function (res2) {
console.log("res2:"+JSON.stringify(res2));
uni.getSavedFileInfo({
filePath: res2.savedFilePath,
success: function (res) {
console.log("res:"+JSON.stringify(res));
console.log("size:"+res.size);
downloadStore.updateTask(index, {
status: "completed",
localPath: res2.savedFilePath,
});
},
});
},
});
} else {
downloadStore.updateTask(index, {
status: "failed",
});
removeTask(index);
uni.showToast({
title: "下载失败2",
icon: "none",
});
}
},
fail: (err) => {
downloadStore.updateTask(index, {
status: "failed",
});
removeTask(index);
uni.showToast({
title: "下载失败3: " + (err.errMsg || "未知错误"),
icon: "none",
duration: 2000,
});
},
});
// 监听下载进度
task.onProgressUpdate((update) => {
const currentTask = downloadStore.getTask(index);
if (currentTask && currentTask.status === "downloading") {
downloadStore.updateTask(index, {
progress: update.progress
});
}
});
// 保存task对象到store(用于暂停/继续)
downloadStore.updateTask(index, {
task: task,
});
};
const syncTasksFromStore = () => {
downloadTasks.value = downloadStore.getTasks();
};
const removeTask = (index) => {
downloadStore.removeTask(index);
syncTasksFromStore();
};
// 恢复正在下载的任务
const resumeDownloadingTasks = () => {
// 使用 nextTick 确保在页面渲染后再恢复下载任务
nextTick(() => {
downloadStore.resumeDownloadingTasks((index) => {
// 重新开始下载(uni.downloadFile不支持断点续传,所以从0开始)
console.log("恢复下载任务:", downloadStore.getTask(index)?.url);
startDownload(index);
});
});
};
// 测试加载更多功能
const testLoadMore = () => {
console.log("=== 手动测试加载更多 ===");
console.log("当前状态:", {
loadMoreStatus: loadMoreStatus.value,
hasMoreData: hasMoreData.value,
isLoading: isLoading.value,
currentPage: currentPage.value,
listLength: guidelinesList.value.length,
});
if (loadMoreStatus.value === "more" && !isLoading.value) {
loadMoreData();
} else {
uni.showToast({
title: `状态: ${loadMoreStatus.value}, 加载中: ${isLoading.value}`,
icon: "none",
duration: 2000,
});
}
};
// 格式化日期
const formatDate = (dateString) => {
if (!dateString) return "";
try {
const date = new Date(dateString);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
} catch (error) {
return dateString;
}
};
</script>
<style lang="scss" scoped>
// 颜色变量
$primary-color: #ff4757;
$text-primary: #333;
$text-secondary: #666;
$text-light: #999;
$border-color: #e0e0e0;
$bg-color: #f6f6f6;
$white: #ffffff;
.imgbox {
width: 32rpx;
margin-top: -10rpx;
}
.popup-panel {
position: fixed;
top:285rpx;
left: 0;
right: 0;
background: #fff;
z-index: 100;
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.08);
}
.popup-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 28rpx 30rpx;
font-size: 30rpx;
color: #333;
}
.popup-divider {
height: 2rpx;
background: #eaeaea;
}
.popup-item.active .item-text {
color: #8b2316;
}
.current-sort {
display: flex;
align-items: center;
gap: 10rpx;
.sort-text {
font-size: 28rpx;
color: #333;
}
.sort-icon.up {
width: 0;
height: 0;
border-left: 8rpx solid transparent;
border-right: 8rpx solid transparent;
border-bottom: 12rpx solid #999;
&.active {
border-bottom-color: #ff0000;
}
}
}
.zhinan-list-page {
background-color: $bg-color;
height: 100vh;
}
.guidelines-scroll-view {
position: fixed;
top: calc(var(--status-bar-height) + 44px);
left: 0;
right: 0;
bottom: 0;
z-index: 99;
height: calc(100vh - var(--status-bar-height) - 44px);
background-color: $bg-color;
}
// 固定搜索容器
.search-container-fixed {
position: fixed;
top: calc(var(--status-bar-height) + 44px); // 导航栏高度
left: 0;
right: 0;
z-index: 90;
display: flex;
align-items: center;
justify-content: center;
padding: 30rpx 30rpx;
background-color: $white;
gap: 20rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
// 筛选栏
.filter-bar {
background-color: $white;
display: flex;
align-items: center;
border-bottom: 2px solid #eee;
.search-box {
display: flex;
justify-content: center;
align-items: center;
gap: 5px;
.search-text {
font-size: 14px;
color: #999;
}
}
.divider {
width: 1px;
height: 16px;
background-color: #999;
margin: 0 60rpx;
}
.filter-item {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 3px;
font-size: 14px;
color: #999;
}
}
// 指南列表
.guidelines-list {
padding: 20rpx 30rpx;
.guideline-item {
background-color: $white;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
.item-content {
flex: 1;
margin-right: 30rpx;
.item-title {
font-size: 32rpx;
color: $text-primary;
line-height: 1.5;
margin-bottom: 16rpx;
font-weight: 500;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
}
.item-bottom {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 10rpx;
.item-date {
font-size: 26rpx;
color: $text-secondary;
}
.item-action {
display: flex;
align-items: center;
.download-btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background-color: rgba(255, 71, 87, 0.1);
// transition: all 0.3s ease;
// &:active {
// transform: scale(0.95);
// background-color: rgba(255, 71, 87, 0.2);
// }
}
.view-btn {
color: #8b2316;
font-size: 28rpx;
font-weight: 500;
transition: all 0.3s ease;
&:active {
transform: scale(0.95);
background-color: darken($primary-color, 10%);
}
}
}
}
}
}
}
// 加载更多状态
.load-more-status {
padding: 20rpx 0;
text-align: center;
.loading,
.more,
.no-more {
display: flex;
align-items: center;
justify-content: center;
gap: 10rpx;
padding: 20rpx 0;
font-size: 26rpx;
color: $text-light;
}
}
// 无数据提示
.no-data {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 0;
text {
margin-top: 30rpx;
font-size: 28rpx;
color: $text-light;
}
}
</style>
+73 -22
View File
@@ -1,14 +1,15 @@
<template> <template>
<uni-nav-bar <!-- <uni-nav-bar
left-icon="left" left-icon="left"
title="购买积分" title="购买积分"
@clickLeft="goBack" @clickLeft="goBack"
fixed fixed
color="#8B2316" color="#8B2316"
height="140rpx" height="180rpx"
:border="false" :border="false"
backgroundColor="#eeeeee" backgroundColor="#eeeeee"
></uni-nav-bar> ></uni-nav-bar> -->
<navBar :title="'购买积分'" ></navBar>
<view class="buy-point-page"> <view class="buy-point-page">
@@ -46,20 +47,80 @@
</view> </view>
</view> </view>
</view> </view>
<unidialog
:visible="freeVisible"
:content="freeContent"
@close="freeClose"
@confirm="freeConfirm"
></unidialog>
</template> </template>
<script setup> <script setup>
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import api from '@/api/api';
import { onLoad } from '@dcloudio/uni-app';
import unidialog from '@/components/dialog/dialog.vue';
import navBar from '@/components/navBar/navBar.vue';
const freeVisible = ref(false);
const freeContent = ref('您确定是否购买?');
const product_id = ref('');
const type = ref('');
const freeClose = () => {
freeVisible.value = false;
};
const freeConfirm = () => {
freeVisible.value = false;
api.createBointsOrder({
point: selectedPackageData.value.points
}).then(res => {
if(res.code==200){
console.log('price:'+res.data.amount);
console.log('order_id:'+res.data.order_id);
console.log('trade_no:'+res.data.trade_no);
console.log('jifen:'+res.data.point);
uni.sendNativeEvent('payPonitPage', {
msg: {
"price":res.data.amount,
"order_id":res.data.order_id,
"trade_no":res.data.trade_no,
'jifen':res.data.point,
'product_id':product_id.value,
'type':type.value,
},
});
}
});
};
onLoad((options) => {
if(options.product_id){
product_id.value = options.product_id;
}
if(options.type){
type.value = options.type;
}
getPointUnitPrice();
});
const pointUnitPrice = ref(1);
// 积分套餐数据 - 根据图片显示5个选项 // 积分套餐数据 - 根据图片显示5个选项
const packages = ref([ const packages = ref([
{ id: 1, points: 500, price: '45.00' }, { id: 1, points: 500, price: 500*pointUnitPrice.value/100 },
{ id: 2, points: 1000, price: '88.00' }, { id: 2, points: 1000, price: 1000*pointUnitPrice.value/100 },
{ id: 3, points: 2500, price: '198.00' }, { id: 3, points: 2500, price: 2500*pointUnitPrice.value/100 },
{ id: 4, points: 5000, price: '398.00' }, { id: 4, points: 5000, price: 5000*pointUnitPrice.value/100 },
{ id: 5, points: 10000, price: '758.00' } { id: 5, points: 10000, price: 10000*pointUnitPrice.value/100 }
]); ]);
const getPointUnitPrice = () => {
api.getPointUnitPrice().then(res => {
if(res.code==200){
pointUnitPrice.value=Number(res.data);
console.log(pointUnitPrice.value);
packages.value.forEach(pkg => {
pkg.price = pkg.points * pointUnitPrice.value / 100;
});
}
});
};
// 选择状态 // 选择状态
const selectedPackage = ref(0); // 默认未选择 const selectedPackage = ref(0); // 默认未选择
@@ -96,18 +157,7 @@
} }
const packageData = selectedPackageData.value; const packageData = selectedPackageData.value;
uni.showModal({ freeVisible.value=true;
title: '确认购买',
content: `确认购买${packageData.points}积分,支付金额¥${packageData.price}?`,
success: (res) => {
if (res.confirm) {
uni.showToast({
title: '购买成功',
icon: 'none'
});
}
}
});
}; };
</script> </script>
@@ -138,7 +188,7 @@
} }
.buy-point-page { .buy-point-page {
height: calc(100vh - 140rpx); height: 100vh;
background-color: $bg-color; background-color: $bg-color;
overflow: hidden; // 隐藏滚动条 overflow: hidden; // 隐藏滚动条
@@ -224,6 +274,7 @@
} }
.main-content { .main-content {
margin-top: calc(var(--status-bar-height) + 44px);
padding: 40rpx 0rpx; padding: 40rpx 0rpx;
.page-title { .page-title {
+323
View File
@@ -0,0 +1,323 @@
<template>
<view class="address-page">
<navBar :title="'收货地址'" />
<!-- <uni-nav-bar left-icon="left" title="收货地址" fixed color="#8B2316" height="180rpx" :border="false" backgroundColor="#ffffff" @clickLeft="goBack" /> -->
<scroll-view scroll-y class="content">
<view class="form-item">
<view class="label">收件人</view>
<input class="input" v-model="receiver" placeholder="请输入收货人名字" />
</view>
<view class="divider-line"></view>
<view class="form-item">
<view class="label">手机号</view>
<input class="input" v-model="mobile" type="number" placeholder="收货人的电话,方便联系" />
</view>
<view class="divider-line"></view>
<!-- <view class="form-item">
<view class="label">邮箱</view>
<input class="input" v-model="email" type="text" placeholder="用于接收电子卡等信息(可选)" />
</view> -->
<view class="form-item select-item" @click="openAreaPicker">
<view class="label">地址</view>
<view class="input placeholder" v-if="!regionText">请选择地址</view>
<view class="input" v-else>{{ regionText }}</view>
<text class="arrow">›</text>
</view>
<view class="divider-line"></view>
<view class="form-item">
<view class="label">详细地址</view>
<input class="input" v-model="detail" placeholder="请输入街道、门牌等详细地址信息" />
</view>
<view class="divider-line"></view>
</scroll-view>
<view class="footer-bar" @click="submit">
<view class="confirm-btn" >确定兑换</view>
</view>
<!-- 省市区选择器 -->
<view v-if="showAreaPicker" class="picker-mask" @click="closeAreaPicker"></view>
<view v-if="showAreaPicker" class="picker-panel">
<view class="picker-header">
<text class="picker-btn" @click="closeAreaPicker">取消</text>
<text class="picker-title">选择地区</text>
<text class="picker-btn ok" @click="confirmArea">确定</text>
</view>
<picker-view v-if="provinces.length && cities.length && areas.length" class="picker-view" :indicator-style="indicatorStyle" :value="pickerIndex" @change="onAreaChange">
<picker-view-column>
<view v-for="(p,pi) in provinces" :key="pi" class="picker-item">{{ p.name }}</view>
</picker-view-column>
<picker-view-column>
<view v-for="(c,ci) in cities" :key="ci" class="picker-item">{{ c.name }}</view>
</picker-view-column>
<picker-view-column>
<view v-for="(a,ai) in areas" :key="ai" class="picker-item">{{ a.name }}</view>
</picker-view-column>
</picker-view>
<view v-else class="picker-empty">地区数据加载中...</view>
</view>
</view>
<unidialog
:title="'确认兑换信息'"
:visible="freeVisible"
:content="freeContent"
:titleColor="'#000'"
@close="freeClose"
@confirm="freeConfirm"
>
<template #content>
<view>
<view class="row">
<view class="left">收件人:</view>
<view class="right">{{ receiver }}</view>
</view>
<view class="row">
<view class="left">手机号:</view>
<view class="right">{{ mobile }}</view>
</view>
<view class="row">
<view class="left">地址:</view>
<view class="right">{{ regionText+detail }}</view>
</view>
<view class="row">
<view class="left">兑换积分:</view>
<view class="right"><text class="price">0积分</text>(包邮)</view>
</view>
<view class="row">请确认此次兑换,物品不退不换</view>
</view>
</template>
</unidialog>
</template>
<script setup>
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import areaList from '@/utils/areaList.js'
import api from '@/api/api.js'
import unidialog from '@/components/dialog/dialog.vue';
const freeVisible = ref(false)
const freeContent = ref('')
const goods_order_id = ref('')
const goodsName = ref('');
import navTo from '@/utils/navTo';
const freeClose = () => {
freeVisible.value = false
}
const freeConfirm = () => {
freeVisible.value = false;
api.useWelfareNum({
other_uuid: goods_order_id.value,
type: 5
}).then(res => {
if(res.code == 1 || res.code == 200){
uni.showToast({
title: '兑换成功',
icon: 'none'
});
setTimeout(() => {
navTo({
url: '/pages_goods/myRedemption/myRedemption?from=upan'
});
}, 1000);
// uni.sendNativeEvent('goTabbarPage', {
// msg: 'mine'
// },ret => {
// console.log(ret);
// })
}
})
}
const receiver = ref('')
const mobile = ref('')
const regionText = ref('')
const detail = ref('')
const email = ref('')
const editingId = ref(null)
const goodsUuid = ref('')
const goodsNum = ref(0)
const goBack = () => uni.navigateBack()
// 省市区数据(适配树形数组:[{code,label,value,children:[...] }...])
const provinces = ref([])
const cities = ref([])
const areas = ref([])
const pickerIndex = ref([0,0,0])
const showAreaPicker = ref(false)
const indicatorStyle = `height: 80rpx;`
const normalizeNode = (node) => ({
code: node?.code || '',
name: node?.label || node?.value || node?.name || '',
children: Array.isArray(node?.children) ? node.children : []
})
const getAreaTree = () => {
const raw = areaList && (areaList.default || areaList)
return Array.isArray(raw) ? raw.map(normalizeNode) : []
}
const buildData = () => {
const tree = getAreaTree()
provinces.value = tree.length ? tree : [{ code: '', name: '', children: [] }]
const pIdx = Math.min(pickerIndex.value[0], Math.max(provinces.value.length - 1, 0))
const pNode = provinces.value[pIdx]
cities.value = (pNode?.children || []).map(normalizeNode)
if (!cities.value.length) cities.value = [{ code: '', name: '', children: [] }]
const cIdx = Math.min(pickerIndex.value[1], Math.max(cities.value.length - 1, 0))
const cNode = cities.value[cIdx]
areas.value = (cNode?.children || []).map(normalizeNode)
if (!areas.value.length) areas.value = [{ code: '', name: '' }]
}
const openAreaPicker = () => {
showAreaPicker.value = true
pickerIndex.value = [0,0,0]
buildData()
}
const closeAreaPicker = () => { showAreaPicker.value = false }
const onAreaChange = (e) => {
const val = (e && e.detail && e.detail.value) ? e.detail.value : [0,0,0]
const [pi, ci, ai] = val
if (pi !== pickerIndex.value[0]) {
pickerIndex.value = [pi, 0, 0]
buildData()
return
}
if (ci !== pickerIndex.value[1]) {
pickerIndex.value = [pi, ci, 0]
buildData()
return
}
pickerIndex.value = [pi, ci, ai]
}
const confirmArea = () => {
const p = provinces.value[pickerIndex.value[0]]
const c = cities.value[pickerIndex.value[1]]
const a = areas.value[pickerIndex.value[2]]
regionText.value = [p?.name, c?.name, a?.name].filter(Boolean).join(' ')
closeAreaPicker()
}
onLoad((opts) => {
console.log(22);
console.log(opts);
goodsUuid.value = opts.goodsUuid
goodsNum.value = opts.goodsNum;
goodsName.value = opts.goodsName;
getAddress()
})
const saveAddress = () => {
uni.setStorageSync('goods_address', {
receiver: receiver.value,
mobile: mobile.value,
regionText: regionText.value,
detail: detail.value,
email: email.value
})
}
const getAddress = () => {
const address = uni.getStorageSync('goods_address')
if(address){
receiver.value = address.receiver
mobile.value = address.mobile
regionText.value = address.regionText
detail.value = address.detail
email.value = address.email
}
}
const submit = () => {
if (!receiver.value) return uni.showToast({ title: '请输入收件人', icon: 'none' })
if (!/^1\d{10}$/.test(mobile.value)) return uni.showToast({ title: '请输入正确手机号', icon: 'none' })
if (!regionText.value) return uni.showToast({ title: '请选择地址', icon: 'none' })
if (!detail.value) return uni.showToast({ title: '请输入详细地址', icon: 'none' })
uni.sendNativeEvent("portraitReport",{
msg:{
event_nickname: "boint_jifengood",
page_type: "积分商城",
resource_name:goodsName.value,
event_val:0,
event_unit:'积分',
number:goodsNum.value,
belong:'U盘',
},
},
(ret) => {
console.log(ret);
});
saveAddress();
api.createGoodsOrder({
goodsUuid: goodsUuid.value,
goodsNum: goodsNum.value,
user_name: receiver.value,
mobile: mobile.value,
email:'',
address: regionText.value+detail.value
}).then(res => {
if(res.code == 1 || res.code == 200){
freeVisible.value = true
goods_order_id.value = res.data.goods_order_id
}
})
}
</script>
<style scoped lang="scss">
.address-page { min-height: 100vh; background: #fff; }
.content { position: absolute; top: calc(var(--status-bar-height) + 44px); bottom: 120rpx; left: 0; right: 0; background: #fff; }
.form-item { display: flex; align-items: center; padding: 24rpx; }
.label { width: 160rpx; color: #333; font-size: 30rpx; }
.input { flex: 1; color: #333; font-size: 30rpx; }
.placeholder { color: #808080;}
.divider-line { height: 2rpx; background: #eee; margin: 0 24rpx; }
.select-item { position: relative; }
.arrow { position: absolute; right: 24rpx; color: #999; font-size: 48rpx; }
.footer-bar { position: fixed; left: 0; right: 0; bottom: 0; height: 120rpx; background: #27c5b8; display: flex; align-items: center; justify-content: center; }
.confirm-btn { color: #fff; font-size: 34rpx; font-weight: 700; }
/* 地区选择器 */
.picker-mask { position: fixed; left: 0; right: 0; top: 0; bottom: 0; background: rgba(0,0,0,0.4); }
.picker-panel { position: fixed; left: 0; right: 0; bottom: 0; background: #fff; }
.picker-header { display: flex; align-items: center; justify-content: space-between; padding: 20rpx 24rpx; border-bottom: 1rpx solid #eee; }
.picker-title { font-size: 30rpx; color: #333; }
.picker-btn { color: #666; font-size: 28rpx; }
.picker-btn.ok { color: #38c1b1; }
.picker-view { height: 480rpx; }
.picker-item { height: 80rpx; line-height: 80rpx; text-align: center; color: #333; }
.picker-empty { padding: 40rpx; text-align: center; color: #999; }
.row{
display: flex;
align-items: flex-start;
font-size: 28rpx;
padding: 12rpx 0;
max-width: 800rpx;
.left{
text-align: left;
max-width: 142rpx;
white-space: nowrap;
width:auto!important;
}
.price{
color: #FF4D4F;
}
}
.left{
width: 160rpx;
}
</style>
+297
View File
@@ -0,0 +1,297 @@
<template>
<navBar :title="'U盘在线兑换'" />
<view class="buy-upan-page">
<!-- 主要内容区域 -->
<view class="main-content">
<!-- 商品信息卡片 -->
<view class="product-card">
<!-- 商品描述 -->
<view class="product-title">{{ goodsInfo.name }}</view>
<!-- 兑换信息 -->
<view class="exchange-info">
<text class="info-left">免费兑换剩余{{ remainingCount }}个</text>
<text class="info-right">已兑换{{ exchangedCount }}件</text>
</view>
</view>
<!-- 兑换数量选择器 -->
<view class="quantity-section">
<text class="quantity-label">兑换数量</text>
<view class="quantity-selector">
<view
class="quantity-btn minus-btn"
:class="{ disabled: quantity <= 1 }"
@click="decreaseQuantity"
>
<text class="btn-text">—</text>
</view>
<view class="quantity-display">
<text class="quantity-value">{{ quantity }}</text>
</view>
<view
class="quantity-btn plus-btn"
:class="{ disabled: quantity >= remainingCount }"
@click="increaseQuantity"
>
<text class="btn-text">+</text>
</view>
</view>
</view>
</view>
<!-- 底部兑换按钮 -->
<view class="bottom-actions">
<view class="exchange-btn" @click="handleExchange">
<text class="btn-text">我要兑换</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { onShow } from '@dcloudio/uni-app';
import navBar from '@/components/navBar/navBar.vue';
import api from '@/api/api';
import goods_api from '@/api/goods_api';
import navTo from '@/utils/navTo';
const goodsUuid = ref('');
// 商品信息
const remainingCount = ref(0); // 剩余数量
const exchangedCount = ref(1129); // 已兑换数量
const quantity = ref(1); // 当前选择的数量
const goodsInfo = ref({});
// 增加数量
const increaseQuantity = () => {
if (quantity.value < remainingCount.value) {
quantity.value += 1;
}
};
const getUpanCount = async () => {
const res = await goods_api.getUpanWelfareCount()
if (res.code == 200) {
remainingCount.value=res.data;
};
}
// 减少数量
const decreaseQuantity = () => {
if (quantity.value > 1) {
quantity.value -= 1;
}
};
// 处理兑换
const handleExchange = () => {
if (quantity.value <= 0) {
uni.showToast({
title: '请选择兑换数量',
icon: 'none'
});
return;
}
// if (quantity.value > remainingCount.value) {
// uni.showToast({
// title: '兑换数量不能超过剩余数量',
// icon: 'none'
// });
// return;
// }
// TODO: 调用兑换接口
navTo({
url:'/pages_app/buyUpan/address?goodsUuid='+goodsUuid.value+'&goodsNum='+quantity.value+'&goodsName='+goodsInfo.value.name
})
};
// 获取U盘福利剩余数量
const getUpanWelfareCount = () => {
api.getUGoods().then(res => {
console.log(res);
if (res.code == 1 || res.code == 200) {
// 根据实际接口返回的数据结构更新
console.log(33);
console.log(res.data);
goodsInfo.value = res.data;
if (res.data) {
//remainingCount.value = res.data.remainingCount;
exchangedCount.value = res.data.times;
goodsUuid.value = res.data.uuid;
}
}
}).catch(err => {
console.error('获取U盘福利数量失败:', err);
});
};
onShow((options) => {
//remainingCount.value = options.restNum;
// 可选:获取实时剩余数量
//getUGoods();
getUpanWelfareCount();
getUpanCount();
});
</script>
<style scoped lang="scss">
// 变量定义
$bg-color: #ffffff;
$text-primary: #333333;
$text-secondary: #666666;
$text-light: #999999;
$border-color: #e5e5e5;
$white: #ffffff;
$teal-color: #00cbc0;
$light-teal: #e6f7f6;
$page-bg: #f5f6f7;
// 混合器
@mixin flex-center {
display: flex;
align-items: center;
justify-content: center;
}
@mixin flex-between {
display: flex;
align-items: center;
justify-content: space-between;
}
.buy-upan-page {
overflow: hidden;
background-color: $page-bg;
margin-top: calc(var(--status-bar-height) + 44px);
// 为底部按钮留出空间
}
// 商品信息卡片
.product-card {
background-color: $white;
padding: 40rpx 30rpx;
margin-bottom: 40rpx;
.product-title {
font-size: 32rpx;
color: $text-primary;
font-weight: 500;
margin-bottom: 30rpx;
line-height: 1.5;
}
.exchange-info {
@include flex-between;
font-size: 28rpx;
.info-left {
color: red;
}
.info-right {
color: $text-secondary;
}
}
}
// 数量选择器区域
.quantity-section {
background-color: $white;
border-radius: 16rpx;
padding: 40rpx 30rpx;
.quantity-label {
font-size: 32rpx;
color: $text-primary;
margin-bottom: 30rpx;
display: block;
}
.quantity-selector {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 20rpx;
.quantity-btn {
width: 60rpx;
height: 60rpx;
border: 2rpx solid $teal-color;
border-radius: 8rpx;
@include flex-center;
background-color: $white;
cursor: pointer;
transition: all 0.3s ease;
.btn-text {
font-size: 36rpx;
color: $teal-color;
font-weight: 500;
line-height: 1;
}
&.disabled {
opacity: 0.5;
border-color: $border-color;
cursor: not-allowed;
.btn-text {
color: $text-light;
}
}
&:not(.disabled):active {
background-color: $light-teal;
}
}
.quantity-display {
min-width: 120rpx;
height: 80rpx;
background-color: $teal-color;
border-radius: 8rpx;
@include flex-center;
margin: 0 10rpx;
.quantity-value {
font-size: 40rpx;
color: $white;
font-weight: 500;
}
}
}
}
// 底部操作按钮
.bottom-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
.exchange-btn {
background-color: $teal-color;
height: 88rpx;
@include flex-center;
cursor: pointer;
transition: all 0.3s ease;
&:active {
opacity: 0.8;
}
.btn-text {
color: $white;
font-size: 32rpx;
font-weight: 500;
}
}
}
</style>
+458
View File
@@ -0,0 +1,458 @@
<template>
<view class="container">
<!-- 头部导航 -->
<view class="navbox">
<view class="status_bar"></view>
<uni-nav-bar
left-icon="left"
title="病情记录"
@clickLeft="goBack"
color="#8B2316"
:border="false"
backgroundColor="#eee"
>
<template #right>
<view class="nav-right">
<uni-icons type="plus" size="24" color="#8B2316" @click="goAddRecord" style="margin-left: 30rpx;"></uni-icons>
</view>
</template>
</uni-nav-bar>
</view>
<!-- 主要内容区域 -->
<scroll-view
class="main-content"
scroll-y
refresher-enabled
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
@scrolltolower="onLoadMore"
:lower-threshold="100"
>
<!-- 空状态 -->
<empty v-if="recordList.length === 0 && !loading" :emptyDesc="'您暂未添加病情记录'"></empty>
<!-- <view class="empty-state" v-if="recordList.length === 0 && !loading">
<text class="empty-text">暂无病情记录</text>
<button class="add-first-btn" @click="addRecord">添加第一条记录</button>
</view> -->
<!-- 时间线容器 -->
<view class="timeline-container" v-else>
<!-- 时间线 -->
<view class="timeline-line"></view>
<!-- 记录条目 -->
<view
class="record-item"
v-for="(record, index) in recordList"
:key="record.uuid"
@click="goToDetail(record)"
>
<!-- 时间线节点 -->
<view class="timeline-node">
<view class="node-circle"></view>
</view>
<!-- 记录内容 -->
<view class="record-content">
<!-- 日期气泡 -->
<view class="date-bubble">
<text class="date-text">{{ record.create_date }}</text>
</view>
<!-- 记录描述 -->
<view class="record-description" v-if="record.des">
<text class="description-text">{{ record.des }}</text>
</view>
<!-- 记录图片 -->
<view class="record-images" v-if="record.photo && record.photo.length > 0">
<view class="image-grid">
<view
class="image-item"
v-for="(imagePath, imgIndex) in record.photo"
:key="imgIndex"
@click.stop="previewImages(record.photo, imgIndex)"
>
<image
:src="docUrl + imagePath"
class="content-image"
mode="aspectFill"
/>
</view>
</view>
</view>
</view>
</view>
<!-- 加载更多状态 -->
<view class="load-more" v-if="recordList.length > 0">
<view class="loading-text" v-if="loadingMore">
<uni-load-more status="loading" content-text="{ contentText: { contentdown: '上拉显示更多', contentrefresh: '正在加载...', contentnomore: '没有更多数据了' } }"></uni-load-more>
</view>
<!-- <view class="no-more-text" v-else-if="currentPage >= totalPage">
<text>没有更多数据了</text>
</view> -->
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
// Vue3 Composition API
import { ref, reactive, onMounted, computed } from 'vue'
import { onShow, onLoad } from '@dcloudio/uni-app'
import navBar from '@/components/navBar/navBar.vue'
import api from '@/api/api.js'
import docUrl from '@/utils/docUrl.js'
import empty from '@/components/empty/empty.vue'
const patientUuid = ref('')
const getRecordList = (isRefresh = false) => {
let usrInfo = uni.getStorageSync('userInfo')
// 如果是刷新,重置页码
if (isRefresh) {
currentPage.value = 1
}
// 设置加载状态
if (isRefresh) {
refreshing.value = true
} else if (currentPage.value === 1) {
loading.value = true
} else {
loadingMore.value = true
}
api.conditionRecordList({
patient_uuid: patientUuid.value,
expert_uuid: usrInfo.uuid,
page: currentPage.value
}).then(res => {
console.log(res)
if(res.code == 200){
const newList = res.data.list || []
if (isRefresh || currentPage.value === 1) {
// 刷新或首次加载,替换数据
recordList.value = newList
} else {
// 加载更多,追加数据
recordList.value = [...recordList.value, ...newList]
}
totalPage.value = res.data.totalPage || 1
}
}).catch(err => {
console.error('获取病情记录失败:', err)
uni.showToast({
title: '获取数据失败',
icon: 'none'
})
}).finally(() => {
// 重置加载状态
loading.value = false
refreshing.value = false
loadingMore.value = false
})
}
const goToDetail = (record) => {
uni.setStorageSync('caseRecord', record)
uni.navigateTo({
url: '/pages_app/caseRecord/caseRecord?uuid=' + record.uuid + '&patientUuid=' + patientUuid.value
})
}
const goAddRecord = () => {
uni.navigateTo({
url: '/pages_app/caseRecord/caseRecord?patientUuid=' + patientUuid.value
})
}
onLoad((options) => {
patientUuid.value = options.uuid
})
onShow(() => {
getRecordList()
})
// 响应式数据
const recordList = ref([])
const totalPage = ref(1)
const currentPage = ref(1)
const loading = ref(false)
const refreshing = ref(false)
const loadingMore = ref(false)
// 方法
const goBack = () => {
uni.navigateBack({
delta: 1,
fail() {
uni.redirectTo({
url: '/pages/index/index'
})
}
})
}
const addRecord = () => {
// 跳转到添加记录页面
uni.navigateTo({
url: '/pages_app/caseRecord/caseRecord?patientUuid=' + patientUuid.value
})
}
const managePatients = () => {
// 管理患者功能
addRecord()
}
const previewImages = (imageList, currentIndex) => {
const urls = imageList.map(img => docUrl + img)
uni.previewImage({
current: currentIndex,
urls: urls
})
}
// 下拉刷新
const onRefresh = () => {
console.log('下拉刷新')
getRecordList(true)
}
// 上拉加载更多
const onLoadMore = () => {
console.log('上拉加载更多')
// 如果正在加载或没有更多数据,则不执行
if (loadingMore.value || loading.value || refreshing.value) {
return
}
// 如果已经是最后一页,不执行
if (currentPage.value >= totalPage.value) {
return
}
// 页码加1
currentPage.value++
// 加载更多数据
getRecordList(false)
}
// 生命周期
onMounted(() => {
console.log('病情记录页面已加载')
})
</script>
<style scoped>
.container {
background-color: #ffffff;
min-height: 100vh;
}
/* 头部导航样式 */
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 30rpx;
background-color: #ffffff;
border-bottom: 1rpx solid #f0f0f0;
}
.back-btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
font-size: 40rpx;
color: #8B2316;
font-weight: bold;
}
.header-title {
font-size: 36rpx;
color: #8B2316;
font-weight: bold;
flex: 1;
text-align: center;
}
.add-btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.add-icon {
font-size: 40rpx;
color: #8B2316;
font-weight: bold;
}
/* 主要内容区域 */
.main-content {
top: calc(var(--status-bar-height) + 44px);
width: 100%;
position: fixed;
bottom:0;
padding: 40rpx 30rpx;
box-sizing: border-box;
}
.timeline-container {
position: relative;
}
.timeline-line {
position: absolute;
left: 30rpx;
top: 0;
bottom: 0;
width: 4rpx;
background-color: #8B2316;
}
.record-item {
position: relative;
margin-bottom: 60rpx;
display: flex;
align-items: flex-start;
}
.timeline-node {
position: relative;
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
z-index: 2;
}
.node-circle {
width: 20rpx;
height: 20rpx;
background-color: #8B2316;
border-radius: 50%;
border: 4rpx solid #ffffff;
box-shadow: 0 0 0 2rpx #8B2316;
}
.record-content {
flex: 1;
margin-left: 30rpx;
}
.date-bubble {
background-color: #ffffff;
border: 2rpx solid #8B2316;
border-radius: 20rpx;
padding: 20rpx 30rpx;
margin-bottom: 20rpx;
display: inline-block;
}
.date-text {
font-size: 28rpx;
color: #333333;
font-weight: 500;
}
.record-description {
margin-bottom: 20rpx;
}
.description-text {
font-size: 30rpx;
color: #333333;
line-height: 1.6;
}
.record-images {
margin-top: 20rpx;
}
.image-grid {
display: flex;
flex-wrap: wrap;
gap: 15rpx;
}
.image-item {
width: 200rpx;
height: 200rpx;
border-radius: 12rpx;
overflow: hidden;
}
.content-image {
width: 100%;
height: 100%;
}
/* 空状态样式 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 40rpx;
text-align: center;
}
.empty-text {
font-size: 32rpx;
color: #999999;
margin-bottom: 40rpx;
}
.add-first-btn {
background-color: #8B2316;
color: white;
border: none;
border-radius: 25rpx;
padding: 20rpx 40rpx;
font-size: 28rpx;
}
/* 加载更多样式 */
.load-more {
padding: 40rpx 0;
text-align: center;
}
.loading-text {
display: flex;
justify-content: center;
align-items: center;
}
.no-more-text {
font-size: 28rpx;
color: #999999;
}
/* 响应式调整 */
@media (max-width: 750rpx) {
.content-image {
width: 100%;
max-width: 400rpx;
}
}
</style>
+466
View File
@@ -0,0 +1,466 @@
<template>
<view class="container">
<!-- 头部导航 -->
<view class="navbox">
<view class="status_bar"></view>
<uni-nav-bar
left-icon="left"
title="病情记录"
@clickLeft="goBack"
color="#8B2316"
:border="false"
backgroundColor="#eee"
>
<template #right>
<view class="nav-right">
<text class="modify-btn" @click="saveRecord" v-if="recordUuid"
>修改</text
>
<text class="save-btn" @click="saveRecord" v-else>提交</text>
</view>
</template>
</uni-nav-bar>
</view>
<!-- 主要内容区域 -->
<view class="main-content">
<!-- 日期选择区域 -->
<view class="form-item row">
<view class="form-label">日期</view>
<view class="form-value" @click="openDateTime">
<text class="date-text">{{ dateTime }}</text>
<uni-icons type="right" size="16" color="#999"></uni-icons>
</view>
</view>
<view class="divider"></view>
<view class="form-item">
<view class="form-label">描述</view>
<view class="form-value">
<textarea
class="description-input"
v-model="des"
placeholder="请输入患者病情"
:adjust-position="false"
></textarea>
</view>
</view>
<view class="divider"></view>
<!-- 图片上传区域 -->
<view class="form-item">
<view class="form-label">图片</view>
<view class="image-upload-area">
<!-- 已上传的图片 -->
<view
class="image-item"
v-for="(image, index) in tempImageList"
:key="index"
>
<image
:src="image"
class="uploaded-image"
mode="aspectFill"
@click="previewImage(index)"
/>
<view class="delete-btn" @click="deleteImage(index)">
<uni-icons type="closeempty" size="15" color="#fff"></uni-icons>
</view>
</view>
<!-- 添加图片按钮 -->
<view
class="add-image-btn"
v-if="imageList.length < 9"
@click="chooseImage"
>
<uni-icons type="plus" size="24" color="#999"></uni-icons>
</view>
</view>
</view>
</view>
<!-- 底部删除按钮 -->
<view class="bottom-actions" v-if="recordUuid">
<button class="delete-btn" @click="deleteRecord">删除该条记录</button>
</view>
<up-datetime-picker
:show="showDate"
v-model="selectedDate"
@cancel="showDate = false"
@confirm="confirmDate"
mode="date"
></up-datetime-picker>
</view>
</template>
<script setup>
// Vue3 Composition API
import { ref, reactive, onMounted, computed } from "vue";
import { onShow, onLoad } from "@dcloudio/uni-app";
import api from "@/api/api.js";
import docUrl from "@/utils/docUrl.js";
import dayjs from "dayjs";
import { pathToBase64} from "image-tools";
// 响应式数据
const des = ref("");
const showDate = ref(false);
const recordUuid = ref("");
const patientUuid = ref("");
const dateTime = ref(dayjs().format("YYYY年MM月DD日"));
const selectedDate = ref(dayjs());
const description = ref("");
const imageList = ref([]);
const tempImageList = ref([]);
const originalData = ref({});
// 方法
const goBack = () => {
uni.navigateBack({
delta: 1,
fail() {
uni.redirectTo({
url: "/pages/index/index",
});
},
});
};
const confirmDate = ({ value, mode }) => {
console.log(value);
dateTime.value = dayjs(value).format("YYYY年MM月DD日");
selectedDate.value = dayjs(value).format("YYYY-MM-DD");
showDate.value = false;
};
const openDateTime = () => {
console.log("openDateTime");
showDate.value = true;
};
const chooseImage = () => {
uni.chooseImage({
count: 8 - imageList.value.length,
sizeType: ["original", "compressed"],
sourceType: ["album", "camera"],
success: (res) => {
// 这里应该上传图片到服务器
// imageList.value = [...imageList.value, ...res.tempFilePaths]
tempImageList.value = [...tempImageList.value, ...res.tempFilePaths];
for (let i = 0; i < res.tempFilePaths.length; i++) {
pathToBase64(res.tempFilePaths[i])
.then((base64) => {
imageList.value.push(base64.split(',')[1]);
})
.catch((error) => {
console.error(error);
});
}
},
});
};
const deleteImage = (index) => {
// 显示模态框
uni.showModal({
title: "确认删除",
content: "确定要删除这张图片吗?",
// 设置模态框点击确定按钮后的回调函数
success: (res) => {
// 如果用户点击了确认按钮
if (res.confirm) {
// 从imageList数组中删除指定索引的图片
imageList.value.splice(index, 1);
tempImageList.value.splice(index, 1);
}
},
});
};
const previewImage = (index) => {
uni.previewImage({
current: index,
urls: imageList.value,
});
};
const addRecord = async () => {
if (!des.value) {
uni.showToast({
title: "请输入患者病情",
icon: "none",
});
return;
}
let usrInfo = uni.getStorageSync("userInfo");
const res = await api.addConditionRecord({
patient_uuid: patientUuid.value,
expert_uuid: usrInfo.uuid,
des: des.value,
create_date: dayjs(selectedDate.value).format("YYYY-MM-DD"),
img1: imageList.value[0] || "",
img2: imageList.value[1] || "",
img3: imageList.value[2] || "",
img4: imageList.value[3] || "",
img5: imageList.value[4] || "",
img6: imageList.value[5] || "",
img7: imageList.value[6] || "",
img8: imageList.value[7] || "",
});
if (res.code == 200) {
uni.showToast({
title: "添加成功",
icon: "none",
});
goBack();
}
};
const updateRecord = async () => {
if (!des.value) {
uni.showToast({
title: "请输入患者病情",
icon: "none",
});
return;
}
let usrInfo = uni.getStorageSync("userInfo");
const res = await api.updateConditionRecord({
uuid: recordUuid.value,
patient_uuid: patientUuid.value,
expert_uuid: usrInfo.uuid,
des: des.value,
create_date: dayjs(selectedDate.value).format("YYYY-MM-DD"),
img1: imageList.value[0] || "",
img2: imageList.value[1] || "",
img3: imageList.value[2] || "",
img4: imageList.value[3] || "",
img5: imageList.value[4] || "",
img6: imageList.value[5] || "",
img7: imageList.value[6] || "",
img8: imageList.value[7] || "",
});
if (res.code == 200) {
uni.showToast({
title: "修改成功",
icon: "none",
});
uni.setStorageSync("caseRecord", null);
goBack();
}
};
const deleteConditionRecord = async () => {
const res = await api.deleteConditionRecord({
uuid: recordUuid.value,
});
if (res.code == 200) {
uni.showToast({
title: "删除成功",
icon: "none",
});
goBack();
}
};
const deleteRecord = () => {
uni.showModal({
title: "确认删除",
content: "确定要删除这条记录吗?",
success: (res) => {
if (res.confirm) {
deleteConditionRecord();
}
},
});
};
// 生命周期
onLoad((options) => {
console.log(options);
recordUuid.value = options.uuid || "";
patientUuid.value = options.patientUuid || "";
if (recordUuid.value) {
imageList.value = [];
let record = uni.getStorageSync("caseRecord");
des.value = record.des;
tempImageList.value = record.photo.map((item) => docUrl + item);
// imageList.value = record.photo.map(item=>docUrl+item);
dateTime.value = dayjs(record.create_date).format("YYYY年MM月DD日");
selectedDate.value = dayjs(record.create_date);
for (let i = 0; i < record.photo.length; i++) {
console.log("url", docUrl + record.photo[i]);
uni.request({
url: docUrl + record.photo[i],
method: "GET",
responseType: "arraybuffer",
success: (res) => {
const base64 = `${uni.arrayBufferToBase64(res.data)}`;
console.log(base64);
imageList.value.push(base64);
},
fail: (err) => {
reject(err);
},
});
}
}
});
const saveRecord = () => {
if (recordUuid.value) {
updateRecord();
} else {
addRecord();
}
};
</script>
<style scoped lang="scss">
.row {
display: flex;
align-items: center;
flex-direction: row;
}
.container {
background-color: #ffffff;
min-height: 100vh;
}
/* 头部导航样式 */
.nav-right {
padding-right: 30rpx;
}
.modify-btn,
.save-btn {
font-size: 32rpx;
color: #8b2316;
font-weight: 500;
}
/* 主要内容区域 */
.main-content {
position: fixed;
top: calc(var(--status-bar-height) + 44px);
left: 0;
right: 0;
width: 100%;
box-sizing: border-box;
bottom:150rpx;
z-index:1;
overflow-y: scroll;
padding:0 30rpx;
}
.form-item {
padding: 30rpx 0;
}
.form-label {
font-size: 32rpx;
color: #333333;
margin-bottom: 20rpx;
font-weight: 500;
}
.form-value {
display: flex;
flex: 1;
justify-content: flex-end;
align-items: center;
padding: 20rpx 0;
}
.date-text {
font-size: 30rpx;
color: #333333;
}
.description-input {
width: 100%;
min-height: 200rpx;
font-size: 30rpx;
color: #333333;
line-height: 1.6;
background-color: 2rpx solid #f5f5f5;
border: none;
padding: 20rpx 0;
}
.description-input:disabled {
color: #999999;
background-color: #f5f5f5;
}
/* 图片上传区域 */
.image-upload-area {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
margin-top: 20rpx;
}
.image-item {
position: relative;
width: 160rpx;
height: 160rpx;
border-radius: 12rpx;
overflow: hidden;
}
.uploaded-image {
width: 100%;
height: 100%;
}
.image-item .delete-btn {
position: absolute;
top: 10rpx;
right: 10rpx;
width: 40rpx;
height: 40rpx;
background-color: rgba(0, 0, 0, 0.6);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.add-image-btn {
width: 160rpx;
height: 160rpx;
border: 2rpx dashed #cccccc;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: #f9f9f9;
}
.divider {
height: 1rpx;
background-color: #f0f0f0;
margin: 0 -30rpx;
}
/* 底部操作区域 */
.bottom-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 30rpx;
background-color: #ffffff;
border-top: 1rpx solid #f0f0f0;
}
.bottom-actions .delete-btn {
width: 100%;
height: 88rpx;
background-color: #8b2316;
color: white;
border: none;
border-radius: 12rpx;
font-size: 32rpx;
font-weight: 500;
}
</style>
+1 -1
View File
@@ -7,7 +7,7 @@
@clickLeft="goBack" @clickLeft="goBack"
fixed fixed
color="#8B2316" color="#8B2316"
height="140rpx" height="180rpx"
:border="false" :border="false"
backgroundColor="#eeeeee" backgroundColor="#eeeeee"
></uni-nav-bar> ></uni-nav-bar>
+1 -1
View File
@@ -7,7 +7,7 @@
@clickLeft="goBack" @clickLeft="goBack"
fixed fixed
color="#8B2316" color="#8B2316"
height="140rpx" height="180rpx"
:border="false" :border="false"
backgroundColor="#eeeeee" backgroundColor="#eeeeee"
></uni-nav-bar> ></uni-nav-bar>
+354
View File
@@ -0,0 +1,354 @@
<template>
<view class="container">
<!-- 头部导航 -->
<navBar :title="headerTitle" />
<!-- 主要内容区域 -->
<view class="main-content">
<!-- 疾病诊断部分 -->
<view class="header">
<view class="section">
<view class="section-content">
<text class="diagnosis-text">疾病诊断: {{ pageData.diseaseName }}</text>
</view>
</view>
</view>
<!-- 化验报告部分 -->
<view class="section">
<view class="section-header">
<text class="section-title">化验报告</text>
</view>
<view class="section-content">
<text class="no-report-text" v-if="!pageData.hasLabReport">无检查化验报告</text>
<view v-else class="lab-report-container">
<view class="image-grid">
<view
class="image-item"
v-for="(image, index) in caseImages"
:key="image.uuid"
@click="previewImage(index)"
>
<image
:src="docUrl+image.path"
class="report-image"
mode="aspectFill"
/>
<!-- <view class="image-date">{{ formatDate(image.createDate) }}</view> -->
</view>
</view>
</view>
</view>
</view>
<!-- 疾病描述部分 -->
<view class="section">
<view class="section-header">
<text class="section-title">疾病描述</text>
</view>
<view class="section-content">
<text class="description-text">{{ pageData.des || '暂无描述' }}</text>
</view>
</view>
</view>
<!-- 底部线条 -->
<view class="bottom-line"></view>
</view>
</template>
<script setup>
// Vue3 Composition API
import { ref, reactive, onMounted, computed } from 'vue'
import { onShow,onLoad } from '@dcloudio/uni-app'
import navBar from '@/components/navBar/navBar.vue'
import api from '@/api/api.js'
import docUrl from '@/utils/docUrl.js'
const uuid = ref('')
onLoad((options) => {
uuid.value = options.uuid
})
onShow(() => {
getDetail()
})
// 响应式数据
const pageData = ref({
date: '',
diseaseName: '',
diagnosis: '',
hasLabReport: false,
labReport: '',
description: '',
photo: '',
age: 0,
title: '',
patientUuid: '',
diseaseUuid: '',
state: 0,
createDate: '',
modifyDate: null
})
// 案例图片数据
const caseImages = ref([])
const getDetail = () => {
api.caseDetail({
caseUuid: uuid.value
}).then(res => {
console.log(res)
if(res.code == 200){
// 更新案例数据
pageData.value = res.data.case
// 更新案例图片
caseImages.value = res.data.caseImg || []
// 设置是否有化验报告(根据图片数量判断)
pageData.value.hasLabReport = caseImages.value.length > 0
}
}).catch(err => {
console.error('获取案例详情失败:', err)
uni.showToast({
title: '获取数据失败',
icon: 'none'
})
})
}
// 计算属性
const headerTitle = computed(() => {
return pageData.value.title || `${pageData.value.createDate}${pageData.value.diseaseName}`
})
// 格式化日期
const formatDate = (dateStr) => {
if (!dateStr) return ''
const date = new Date(dateStr)
return date.toLocaleDateString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
}).replace(/\//g, '-')
}
// 方法
const goBack = () => {
uni.navigateBack()
}
// 预览图片
const previewImage = (index) => {
const urls = caseImages.value.map(img => docUrl+img.path)
uni.previewImage({
current: index,
urls: urls
})
}
// 生命周期
onMounted(() => {
// 页面加载时的逻辑
console.log('检查记录页面已加载')
})
</script>
<style scoped lang="scss">
.container {
background-color: #ffffff;
min-height: 100vh;
}
/* 状态栏样式 */
.status-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10rpx 30rpx;
background-color: #ffffff;
font-size: 28rpx;
color: #333333;
}
.status-left {
font-weight: 500;
}
.status-center {
flex: 1;
display: flex;
justify-content: center;
}
.network-icons {
display: flex;
align-items: center;
gap: 10rpx;
}
.bluetooth-icon, .wifi-icon {
font-size: 24rpx;
}
.speed {
font-size: 24rpx;
color: #666666;
}
.signal {
font-size: 24rpx;
color: #333333;
}
.status-right {
font-weight: 500;
}
/* 头部导航样式 */
.back-btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
}
.back-icon {
font-size: 40rpx;
color: #ff4444;
font-weight: bold;
}
.header-title {
font-size: 36rpx;
color: #ff4444;
font-weight: bold;
flex: 1;
}
/* 主要内容区域 */
.main-content {
position: fixed;
top: calc(var(--status-bar-height) + 44px);
left: 0;
right:0;
bottom: 0rpx;
overflow-y: scroll;
}
.section {
margin-bottom: 40rpx;
}
.section-header {
margin-top: 20rpx;
position: relative;
margin-bottom: 20rpx;
}
.red-bar {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 8rpx;
height: 40rpx;
background-color: #ff4444;
border-radius: 4rpx;
}
.section-title {
font-size: 32rpx;
color: #333333;
font-weight: bold;
margin-left: 20rpx;
}
.section-content {
padding-left: 28rpx;
}
.diagnosis-text {
font-size: 30rpx;
color: #333333;
line-height: 1.5;
}
.header{
background: #8B2316;
color: #fff;
margin-bottom: 20rpx;
.section{
padding:20rpx;
margin-bottom: 10rpx;
}
.section-content{
padding-left:0rpx;
}
.diagnosis-text{
color: #fff;
}
}
.no-report-text {
font-size: 28rpx;
color: #999999;
line-height: 1.5;
}
.lab-report-text {
font-size: 28rpx;
color: #333333;
line-height: 1.5;
}
.lab-report-container {
margin-top: 20rpx;
}
.image-grid {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
}
.image-item {
position: relative;
width: 200rpx;
height: 200rpx;
border-radius: 12rpx;
overflow: hidden;
}
.report-image {
width: 100%;
height: 100%;
}
.image-date {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
color: white;
font-size: 20rpx;
padding: 20rpx 10rpx 10rpx;
text-align: center;
}
.description-text {
font-size: 28rpx;
color: #999999;
line-height: 1.5;
}
/* 底部线条 */
.bottom-line {
height: 2rpx;
background-color: #f0f0f0;
margin: 40rpx 30rpx 0;
}
</style>
+146 -54
View File
@@ -4,6 +4,7 @@
<navBar title="公益咨询" /> <navBar title="公益咨询" />
<view class="tabs"> <view class="tabs">
<view :class="['tab', activeTab==='new' ? 'active' : '']" @tap="switchTab('new')">新的咨询</view> <view :class="['tab', activeTab==='new' ? 'active' : '']" @tap="switchTab('new')">新的咨询</view>
<view class="bar"></view>
<view :class="['tab', activeTab==='mine' ? 'active' : '']" @tap="switchTab('mine')">我已回答</view> <view :class="['tab', activeTab==='mine' ? 'active' : '']" @tap="switchTab('mine')">我已回答</view>
</view> </view>
<view class="tabs-spacer"></view> <view class="tabs-spacer"></view>
@@ -18,16 +19,20 @@
@scrolltolower="onReachBottom" @scrolltolower="onReachBottom"
lower-threshold="80" lower-threshold="80"
> >
<view v-for="(item, idx) in displayList" :key="idx" class="consult-card" @click="goDetail(item.id)"> <view v-for="(item, idx) in displayList" :key="idx" class="consult-card" @click="goDetail(item.id,item.patientUuid)">
<view class="card-head"> <view class="card-head">
<text class="user-name">{{ item.maskName }}</text> <view class="namebox">
<text class="user-name">{{ item.maskName }}</text>
<text class="user-avatar" v-if="bottomActive!=='quick'">({{item.sex==1?'女':'男' }}<text decode>&nbsp;</text>{{ calcAge(item.birthDate) }}岁)</text>
</view>
<text class="date">{{ item.date }}</text> <text class="date">{{ item.date }}</text>
</view> </view>
<view class="card-body"> <view class="card-body">
<text class="content">{{ item.content }}</text> <text class="content twoline">{{ item.disease_describe }}</text>
</view> </view>
<view class="card-foot" v-if="bottomActive==='multi'"> <view class="card-foot" v-if="bottomActive==='multi'">
<text class="reply-count">{{ item.answer_num }}位医生已回答</text> <text class="reply-count" v-if="item.answer_num>0">{{ item.answer_num }}位医生已回答</text>
<text class="reply-count-none" v-else>暂未有医生回答</text>
<view v-if="item.tag" class="tag">{{ item.tag }}</view> <view v-if="item.tag" class="tag">{{ item.tag }}</view>
</view> </view>
<view class="card-foot" v-else> <view class="card-foot" v-else>
@@ -52,7 +57,7 @@
<script setup> <script setup>
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { onShow } from '@dcloudio/uni-app' import { onShow,onBackPress,onLoad } from '@dcloudio/uni-app'
import navBar from '@/components/navBar/navBar.vue' import navBar from '@/components/navBar/navBar.vue'
import empty from '@/components/empty/empty.vue' import empty from '@/components/empty/empty.vue'
import api from '@/api/api.js' import api from '@/api/api.js'
@@ -65,14 +70,30 @@ const pageSize=ref(10)
const hasMore = ref(true) const hasMore = ref(true)
const isLoading = ref(false) const isLoading = ref(false)
const isRefreshing = ref(false) const isRefreshing = ref(false)
const goDetail=async(uuid)=>{ onBackPress(()=>{
plus.runtime.quit();
return true;
})
const goDetail=async(uuid,patientUuid)=>{
if(bottomActive.value==='quick'){ if(bottomActive.value==='quick'){
let userId=uni.getStorageSync('userInfo').uuid.toLowerCase(); if(activeTab.value==='new'){
let conversationId=userId+'|1|'+uuid.toLowerCase(); navTo({
await uni.$UIKitStore.uiStore.selectConversation(conversationId) url:'/pages_app/freeDetail/freeDetail?uuid='+uuid
navTo({ })
url:'/pages_chat/chat/index?from=consult' }else{
let userId=uni.getStorageSync('userInfo').uuid.toLowerCase();
uni.sendNativeEvent('goConsultPage', {
msg: 'chat'
},ret => {
console.log(ret);
}) })
//let conversationId=userId+'|1|'+patientUuid.toLowerCase();
//await uni.$UIKitStore.uiStore.selectConversation(conversationId)
// navTo({
// url:'/pages_chat/chat/index?from=consult&&patientUuid='+patientUuid+'&&uuid='+uuid
// })
}
}else{ }else{
let status=0; let status=0;
if(activeTab.value==='new'){ if(activeTab.value==='new'){
@@ -90,6 +111,22 @@ function maskName(name){
const first = name.slice(0,1) const first = name.slice(0,1)
return `${first}**` return `${first}**`
} }
// 根据出生日期计算年龄(birth 可以是 'YYYY-MM-DD' 或可被 Date 解析的字符串 / 时间戳)
function calcAge(birth) {
if (!birth) return ''
const birthDate = new Date(birth)
if (isNaN(birthDate.getTime())) {
return ''
}
const today = new Date()
let age = today.getFullYear() - birthDate.getFullYear()
const m = today.getMonth() - birthDate.getMonth()
if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
age--
}
// 年龄小于 0 视为无效
return age >= 0 ? age : ''
}
const newConsultList=async(isRefresh=false)=>{ const newConsultList=async(isRefresh=false)=>{
if(isLoading.value) return if(isLoading.value) return
isLoading.value = true isLoading.value = true
@@ -106,12 +143,15 @@ const newConsultList=async(isRefresh=false)=>{
if(res && res.code===200 && res.data && res.data.consult_list){ if(res && res.code===200 && res.data && res.data.consult_list){
const list = Array.isArray(res.data.consult_list.list) ? res.data.consult_list.list : [] const list = Array.isArray(res.data.consult_list.list) ? res.data.consult_list.list : []
const mapped = list.map(item=>({ const mapped = list.map(item=>({
maskName: maskName(item.realName || ''), maskName: item.realName,
date: (item.createDate || '').slice(0,10), date: (item.createDate || '').slice(0,16),
content: item.content || '', content: item.content || '',
replyCount: 0, replyCount: 0,
sex: item.sex || 0,
birthDate: item.birthDate || item.birthday,
tag: item.diseaseName || '', tag: item.diseaseName || '',
id:item.patientUuid || '' id:item.uuid || '',
patientUuid:item.patientUuid || ''
})) }))
if(isRefresh){ if(isRefresh){
listNew.value = mapped listNew.value = mapped
@@ -142,12 +182,15 @@ const consultListHis=async(isRefresh=false)=>{
if(res.code==200){ if(res.code==200){
const list = Array.isArray(res.data.list) ? res.data.list : [] const list = Array.isArray(res.data.list) ? res.data.list : []
const mapped = list.map(item=>({ const mapped = list.map(item=>({
maskName: maskName(item.realName || ''), maskName: item.realName,
date: (item.createDate || '').slice(0,10), date: (item.createDate || '').slice(0,16),
content: item.content || '', content: item.content || '',
replyCount: 0, replyCount: 0,
sex: item.sex || 0,
birthDate: item.birthDate || item.birthday,
tag: item.diseaseName || '', tag: item.diseaseName || '',
id:item.patientUuid || '' id:item.uuid || '',
patientUuid:item.patientUuid || ''
})) }))
if(isRefresh){ if(isRefresh){
listMine.value = mapped listMine.value = mapped
@@ -182,7 +225,9 @@ const listNewInterrogation=async(isRefresh=false)=>{
maskName: maskName(item.name || ''), maskName: maskName(item.name || ''),
date: (item.create_date || '').slice(0,10), date: (item.create_date || '').slice(0,10),
content: item.your_question || '', content: item.your_question || '',
disease_describe:item.your_question || '', disease_describe:item.disease_describe || '',
sex: item.sex || 0,
birthDate: item.birthday || '',
answer_num: item.answer_num || 0, answer_num: item.answer_num || 0,
tag: item.disease_name || '', tag: item.disease_name || '',
id:item.step1_uuid || '' id:item.step1_uuid || ''
@@ -220,6 +265,8 @@ const listMyAnsweredInterrogation=async(isRefresh=false)=>{
date: (item.create_date || '').slice(0,10), date: (item.create_date || '').slice(0,10),
content: item.your_question || '', content: item.your_question || '',
disease_describe:item.disease_describe || '', disease_describe:item.disease_describe || '',
sex: item.sex || 0,
birthDate: item.birthday || '',
answer_num: item.answer_num || 0, answer_num: item.answer_num || 0,
tag: item.disease_name || '', tag: item.disease_name || '',
id:item.step1_uuid || '' id:item.step1_uuid || ''
@@ -237,20 +284,22 @@ const listMyAnsweredInterrogation=async(isRefresh=false)=>{
isRefreshing.value = false isRefreshing.value = false
} }
}; };
onShow(()=>{ onLoad(()=>{
page.value = 1 page.value = 1
hasMore.value = true; hasMore.value = true;
listNew.value = [];
listMine.value=[];
if(bottomActive.value==='quick'){ if(bottomActive.value==='quick'){
if(activeTab.value==='new'){ if(activeTab.value==='new'){
newConsultList(true) newConsultList(false)
}else{ }else{
consultListHis(true) consultListHis(false)
} }
}else{ }else{
if(activeTab.value==='new'){ if(activeTab.value==='new'){
listNewInterrogation(true) listNewInterrogation(false)
}else{ }else{
listMyAnsweredInterrogation(true) listMyAnsweredInterrogation(false)
} }
} }
@@ -267,18 +316,20 @@ function switchTab(key) {
isLoading.value = false; isLoading.value = false;
listNew.value = []; listNew.value = [];
listMine.value=[]; listMine.value=[];
page.value = 1
hasMore.value = true;
if(bottomActive.value==='quick'){ if(bottomActive.value==='quick'){
if(activeTab.value==='new'){ if(activeTab.value==='new'){
newConsultList(true) newConsultList(false)
}else{ }else{
consultListHis(true) consultListHis(false)
} }
}else{ }else{
if(activeTab.value==='new'){ if(activeTab.value==='new'){
listNewInterrogation(true) listNewInterrogation(false)
}else{ }else{
listMyAnsweredInterrogation(true) listMyAnsweredInterrogation(false)
} }
} }
@@ -294,25 +345,32 @@ function goBack() {
} }
// 底部tab交互 // 底部tab交互
const bottomActive = ref('quick') const bottomActive = ref('multi')
const switchBottomTab=(key)=>{ const switchBottomTab=(key)=>{
isLoading.value = false; isLoading.value = false;
bottomActive.value = key; bottomActive.value = key;
page.value = 1
hasMore.value = true;
listNew.value = []; listNew.value = [];
listMine.value=[]; listMine.value=[];
if(key=='quick'){ if(key=='quick'){
try {
plus.runtime.quit();
} catch (error) {
}
if(activeTab.value==='new'){ if(activeTab.value==='new'){
newConsultList(true) newConsultList(false)
}else{ }else{
consultListHis(true) consultListHis(false)
} }
}else{ }else{
if(activeTab.value==='new'){ if(activeTab.value==='new'){
console.log('listNewInterrogation') console.log('listNewInterrogation')
listNewInterrogation(true); listNewInterrogation(false);
}else{ }else{
listMyAnsweredInterrogation(true) listMyAnsweredInterrogation(false)
} }
} }
} }
@@ -322,6 +380,8 @@ function onRefresh(){
isRefreshing.value = true isRefreshing.value = true
page.value = 1 page.value = 1
hasMore.value = true hasMore.value = true
listNew.value = [];
listMine.value=[];
if(bottomActive.value==='quick'){ if(bottomActive.value==='quick'){
if(activeTab.value==='new'){ if(activeTab.value==='new'){
newConsultList(true) newConsultList(true)
@@ -372,35 +432,49 @@ function onReachBottom(){
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
align-items: center; align-items: center;
height: 44px; height: 48px;
padding: 0 16px; padding: 0 16px;
position: fixed; position: fixed;
top: 140rpx; top: calc(var(--status-bar-height) + 44px);
left: 0; left: 0;
right: 0; right: 0;
z-index: 10; z-index: 10;
background-color: #ffffff; background-color: #ffffff;
box-shadow: 0 1px 0 rgba(0,0,0,0.06); border-bottom: 1px solid #e5e5e5;
.bar{
width:1px;
height: 100%;
background-color: #e5e5e5;
}
.tab { .tab {
flex: 1; flex: 1;
text-align: center; text-align: center;
font-size: 16px; font-size: 36rpx;
color: #7a7a7a; color: #7a7a7a;
padding: 10px 0; padding: 10px 0;
&.active { color: #8B2316; position: relative; } &.active { color: #8B2316; position: relative; }
&.active::after { content: ''; position: absolute; left: 25%; right: 25%; bottom: 2px; height: 3px; background-color: #8B2316; border-radius: 2px; }
} }
} }
.tabs-spacer { height: 44px; } .tabs-spacer { height: 44px; }
.namebox{
display: flex;
align-items: center;
justify-content: center;
.user-avatar{
margin-left: 10rpx;
font-size:32rpx;
color: #666;
}
}
.list-scroll { .list-scroll {
flex: 1; flex: 1;
position: fixed; position: fixed;
top: 228rpx; top: calc(var(--status-bar-height) + 44px + 48px);
bottom: 136rpx; bottom:56px;
padding: 8px 0px 0 0px; margin: 20rpx rpx 0;
margin: 20rpx 30rpx 0;
box-sizing: border-box; box-sizing: border-box;
width:auto; width:100%;
} }
.consult-card { .consult-card {
@@ -412,8 +486,8 @@ function onReachBottom(){
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
.user-name { font-size: 16px; color: #a31712; } .user-name { font-size: 36rpx; color: #a31712; }
.date { font-size: 14px; color: #9aa0a6; } .date { font-size: 36rpx; color: #9aa0a6; }
} }
.card-body { .card-body {
background: #efefef; background: #efefef;
@@ -443,31 +517,49 @@ function onReachBottom(){
} }
display: flex; align-items: center; justify-content: space-between; margin-top: 8px; display: flex; align-items: center; justify-content: space-between; margin-top: 8px;
.reply-count { font-size:28rpx; color: #8B2316; } .reply-count { font-size:28rpx; color: #8B2316; }
.reply-count-none{
font-size:28rpx;
color: #999;
position: relative;
padding-left: 25rpx;
}
.reply-count-none::after{
content: '';
position: absolute;
top: 50%;
transform: translateY(-50%);
left: 0;
width: 20rpx;
height: 20rpx;
background: red;
border-radius: 50%;
}
.tag { .tag {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 8rpx 16rpx; background: #fff5f5; color: #a31712; border-radius: 28rpx; font-size: 24rpx; border:2rpx solid #a31712;} padding: 0rpx 16rpx; background: #fff; color: #a31712; border-radius: 28rpx; font-size: 24rpx; border:2rpx solid #a31712;}
} }
} }
.bottom-bar { .bottom-bar {
border-top: 1px solid #e5e5e5;
position: fixed; position: fixed;
left: 0; right: 0; bottom: 0; left: 0; right: 0; bottom: 0;
background-color: #ffffff; background-color: #ffffff;
box-shadow: 0 -2px 8px rgba(0,0,0,0.06); box-shadow: 0 -2px 8px rgba(0,0,0,0.06);
display: flex; align-items: center; justify-content: center; display: flex;
padding: 8px 12px; align-items: center;
gap: 12px; justify-content: center;
.bottom-tab { .bottom-tab {
flex: 1; flex: 1;
height: 40px; height: 56px;
line-height: 40px; line-height: 56px;
text-align: center; text-align: center;
border-radius: 6px;
font-size: 16px; font-size: 36rpx;
color: #a31712; color: #999;
background-color: #fff5f5; background-color: #fff;
} }
.bottom-tab.active { .bottom-tab.active {
background-color: #a31712; background-color: #a31712;
File diff suppressed because it is too large Load Diff
+935
View File
@@ -0,0 +1,935 @@
<template>
<view class="navbox">
<view class="status_bar"></view>
<uni-nav-bar
left-icon="left"
title="视频缓存"
@clickLeft="goBack"
color="#8B2316"
:border="false"
backgroundColor="#eeeeee"
>
<template #right>
<view class="nav-actions">
<view class="nav-edit" @click="toggleEdit">
<text>{{ isEditMode ? "取消" : "编辑" }}</text>
</view>
</view>
</template>
</uni-nav-bar>
</view>
<view class="download-video-page">
<!-- 标签页 -->
<view class="tabs-container">
<view
class="tab-item"
:class="{ active: activeTab === 'completed' }"
@click="switchTab('completed')"
>
已完成
</view>
<view class="tab-divider"></view>
<view
class="tab-item"
:class="{ active: activeTab === 'downloading' }"
@click="switchTab('downloading')"
>
缓存中
</view>
</view>
<!-- 内容列表 -->
<scroll-view class="content-scroll" scroll-y :scroll-top="scrollTop">
<view v-if="currentList.length === 0" class="empty-state">
<text>暂无数据</text>
</view>
<view
v-for="(item, index) in currentList"
:key="item.id || index"
class="video-item"
@click="handleItemClick(item)"
>
<!-- 编辑模式下的多选框 -->
<view
v-if="isEditMode"
class="checkbox-wrapper"
@click.stop="toggleSelectItem(item)"
>
<view
class="checkbox"
:class="{ checked: isItemSelected(item) }"
>
<uni-icons
v-if="isItemSelected(item)"
type="checkmarkempty"
size="16"
color="#ffffff"
></uni-icons>
</view>
</view>
<!-- 缩略图 -->
<image
class="video-thumbnail"
:src="docUrl + item.imgpath"
mode="aspectFill"
/>
<!-- 内容信息 -->
<view class="video-info">
<view class="video-title">{{ item.name || "视频标题" }}</view>
<view class="video-author">{{ item.author || "作者" }}</view>
<!-- 时长和大小 -->
<view class="video-meta" v-if="item.status === 'completed'">
<uni-icons
type="clock"
size="12"
color="#999999"
class="meta-icon"
></uni-icons>
<text class="meta-text"><text v-if="item.duration">{{ formatDuration(item.duration) }}</text></text>
<text class="meta-size">{{ formatSize(item.size) }}</text>
</view>
<view class="video-meta video-status" v-if="item.status == 'downloading' || item.status == 'paused'">
<view class="status-icon">
<image :src="item.status == 'paused' ? playImg : pauseImg" mode="aspectFill"></image>
<text>{{ item.status == 'paused' ? '继续' : '暂停' }}</text>
</view>
<view class="box">
<!-- <view class="video-progress-bar">
<view class="video-progress-bar-inner" :style="{ width: item.progress + '%' }"></view>
</view> -->
<!-- <text>{{ item.progress + '%' }}</text> -->
<view class="size-box">{{ formatSize(item.downloadSize) }}/{{ formatSize(item.totalSize) }}</view>
</view>
</view>
</view>
<!-- 编辑模式下的删除按钮 -->
<!-- <view
v-if="isEditMode"
class="delete-btn"
@click.stop="deleteItem(item, index)"
>
<text>删除</text>
</view> -->
</view>
</scroll-view>
<!-- 底部存储信息 -->
<view class="bottom-box">
<view class="storage-info">
<text
>手机存储:总空间{{ storageInfo.total }}/剩余{{
storageInfo.available
}}可用</text
>
</view>
<view class="footer-btn" v-if="isEditMode">
<view class="footer-btn-item select" @click="clearAll">
<text v-if="!isAllSelect">全选</text>
<text v-else>取消全选</text>
</view>
<view class="bar"></view>
<view class="footer-btn-item del" @click="delTask">
删除<text v-if="selectedItems.length > 0">({{ selectedItems.length }})</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted, nextTick } from "vue";
import downloadStore from "@/store/downloadStoreVideo.js";
import { onLoad, onShow,onUnload,onBackPress} from "@dcloudio/uni-app";
import playImg from "@/static/down_true.png";
import pauseImg from "@/static/down_false.png";
import navTo from "@/utils/navTo";
import docUrl from "@/utils/docUrl";
const downloadTasks = ref([]);
const unsubscribe = ref(null);
const isAllSelect = ref(false);
const selectedItems = ref([]);
// 响应式数据
const activeTab = ref("completed"); // completed: 已完成, downloading: 缓存中
const isEditMode = ref(false);
const scrollTop = ref(0);
const storageInfo = ref({
total: "461.31G",
available: "312.92G",
});
const delTask=()=>{
if(selectedItems.value.length === 0){
uni.showToast({
title: "请选择要删除的缓存",
icon: "none",
});
return;
}
uni.showModal({
title: "提示",
content: "确定要删除选中的缓存吗?",
success: (res) => {
if (res.confirm) {
// 从后往前删除,避免索引变化问题
const idsToDelete = [...selectedItems.value];
idsToDelete.forEach(id => {
const taskIndex = downloadTasks.value.findIndex(t => t.id === id);
if (taskIndex !== -1) {
removeTask(taskIndex);
}
});
// 清空选择
selectedItems.value = [];
isAllSelect.value = false;
}
}
});
}
const clearAll=()=>{
if(!isAllSelect.value){
// 全选当前列表的所有项
selectedItems.value = currentList.value.map(item => item.id);
isAllSelect.value = true;
}else{
// 取消全选
selectedItems.value = [];
isAllSelect.value = false;
}
}
// 判断项是否被选中
const isItemSelected = (item) => {
return selectedItems.value.includes(item.id);
}
onBackPress(() => {
if(from.value){
plus.runtime.quit();
return true;
}
});
// 切换项的选中状态
const toggleSelectItem = (item) => {
const index = selectedItems.value.indexOf(item.id);
if (index > -1) {
selectedItems.value.splice(index, 1);
} else {
selectedItems.value.push(item.id);
}
// 更新全选状态
isAllSelect.value = selectedItems.value.length === currentList.value.length && currentList.value.length > 0;
}
// 计算当前显示的列表
const currentList = computed(() => {
let list = [];
if (activeTab.value === "completed") {
list = downloadTasks.value.filter((task) => task.status === "completed");
} else {
list = downloadTasks.value.filter(
(task) => task.status === "downloading" || task.status === "paused"
);
}
// 当列表变化时,更新全选状态
nextTick(() => {
if (isAllSelect.value && selectedItems.value.length !== list.length) {
isAllSelect.value = false;
}
});
return list;
});
// 方法
const goBack = () => {
if(from.value){
plus.runtime.quit();
}else{
uni.navigateBack()
};
};
const switchTab = (tab) => {
activeTab.value = tab;
isEditMode.value = false;
// 切换标签时清空选择
selectedItems.value = [];
isAllSelect.value = false;
// 切换到缓存中时,滚动到顶部
if (tab === 'downloading') {
// 先设置为一个非0值,然后在下一个tick设置为0,确保滚动触发
scrollTop.value = 0.01;
nextTick(() => {
scrollTop.value = 0;
});
}
};
const toggleEdit = () => {
isEditMode.value = !isEditMode.value;
// 退出编辑模式时清空选择
if (!isEditMode.value) {
selectedItems.value = [];
isAllSelect.value = false;
}
};
const handleItemClick = (item) => {
if (isEditMode.value) {
// 编辑模式下,点击项切换选中状态
toggleSelectItem(item);
return;
}
// 跳转到视频详情或播放页面
if (item.status === "completed") {
uni.navigateTo({
url: `/pages_app/videoDetail/videoDetail?id=${item.id}&from=download`,
});
}else if(item.status === "downloading"){
let index=downloadTasks.value.findIndex(t => t.id === item.id);
if(index !== -1){
pauseTask(index);
}
}else if(item.status === "paused"){
let index=downloadTasks.value.findIndex(t => t.id === item.id);
if(index !== -1){
resumeTask(index);
}
}
};
const deleteItem = (item, index) => {
uni.showModal({
title: "提示",
content: "确定要删除这个缓存吗?",
success: (res) => {
if (res.confirm) {
// 从store中删除
const taskIndex = downloadTasks.value.findIndex((t) => t.id === item.id);
if (taskIndex !== -1) {
removeTask(taskIndex);
}
}
},
});
};
const formatDuration = (duration) => {
if (!duration) return "00:00:00";
// 如果duration是秒数
if (typeof duration === "number") {
const hours = Math.floor(duration / 3600);
const minutes = Math.floor((duration % 3600) / 60);
const seconds = duration % 60;
return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(
2,
"0"
)}:${String(seconds).padStart(2, "0")}`;
}
// 如果已经是格式化字符串
return duration;
};
const formatSize = (size) => {
if (!size) return "0M";
// 如果size是字节数
const mb = (Number(size) / 1024 / 1024).toFixed(2);
return `${mb}M`;
// 如果已经是格式化字符串
return size;
};
const from = ref('');
onLoad((options) => {
if(options.from){
from.value = options.from;
}
if(options.type){
switchTab('downloading')
}
// 从store同步任务列表
syncTasksFromStore();
// 监听store变化
unsubscribe.value = downloadStore.addListener((tasks) => {
downloadTasks.value = tasks;
});
console.log(11111);
console.log(downloadTasks.value);
// 恢复正在下载的任务
resumeDownloadingTasks();
}),
onShow(() => {
// 页面显示时同步最新任务列表
syncTasksFromStore();
// 检查并恢复下载任务
resumeDownloadingTasks();
}),
onUnload(() => {
// 页面 卸载时取消监听
if (unsubscribe.value) {
unsubscribe.value();
}
});
const getStorageInfo = () => {
// 获取手机存储信息
// #ifdef APP-PLUS
if (uni.getSystemInfoSync().platform !== 'android') {
uni.sendNativeEvent('getSysSpace',{
msg: 'getSysSpace'
},(res) => {
storageInfo.value = {
total: res.total,
available: res.available,
};
});
}else{
// 1. 导入所需的Android类
const Environment = plus.android.importClass("android.os.Environment");
const StatFs = plus.android.importClass("android.os.StatFs");
try {
// 2. 获取内部存储的数据目录(File对象)
const dataDirectory = Environment.getDataDirectory(); // 这是一个Java File对象
// 3. 关键修正:使用 plus.android.invoke 调用 File 对象的 getPath 方法
const dataDirectoryPath = plus.android.invoke(dataDirectory, "getPath");
// 现在 dataDirectoryPath 是一个字符串,表示路径
// 4. 创建 StatFs 对象,传入路径字符串
const statFs = new StatFs(dataDirectoryPath);
// 5. 使用 invoke 调用 StatFs 的方法获取存储块信息
const blockSize = plus.android.invoke(statFs, "getBlockSize");
const availableBlocks = plus.android.invoke(statFs, "getAvailableBlocks");
const totalBlocks = plus.android.invoke(statFs, "getBlockCount");
// 6. 计算可用空间和总空间(单位:字节)
const availableSizeInBytes = blockSize * availableBlocks;
const totalSizeInBytes = blockSize * totalBlocks;
// 7. 转换为常用单位
const availableSizeInGB = (availableSizeInBytes / (1024 * 1024 * 1024)).toFixed(2);
const availableSizeInMB = (availableSizeInBytes / (1024 * 1024)).toFixed(2);
const totalSizeInGB = (totalSizeInBytes / (1024 * 1024 * 1024)).toFixed(2);
// console.log(`可用存储空间:${availableSizeInBytes} 字节`);
// console.log(`可用存储空间:${availableSizeInMB} MB`);
// console.log(`可用存储空间:${availableSizeInGB} GB`);
// 可以根据需要返回不同单位的值
storageInfo.value = {
total: totalSizeInGB+"G",
available: availableSizeInGB+"G",
};
} catch (error) {
console.error('获取存储信息时发生错误:', error);
return null;
}
}
// #endif
};
const syncTasksFromStore=()=> {
downloadTasks.value = downloadStore.getTasks();
};
// 添加下载任务
const addDownloadTask = (item) => {
// 使用store添加任务
const taskIndex = downloadStore.addTask(item);
// 同步任务列表
syncTasksFromStore();
// 开始下载
startDownload(taskIndex);
};
// 开始下载
const startDownload = (index) => {
const taskItem = downloadStore.getTask(index);
if (!taskItem) return;
const task = uni.downloadFile({
url: taskItem.url,
success: (res) => {
if (res.statusCode === 200) {
downloadStore.updateTask(index, {
status: "completed",
filePath: res.tempFilePath,
});
uni.showToast({
title: "下载成功",
icon: "success",
});
console.log(res);
uni.saveFile({
tempFilePath: res.tempFilePath,
success: function (res) {
uni.getSavedFileInfo({
filePath: res.savedFilePath,
success: function (res) {
downloadStore.updateTask(index, {
status: "completed",
realyPath: res.savedFilePath,
size: res.size,
});
},
});
},
});
} else {
downloadStore.updateTask(index, {
status: "failed",
});
uni.showToast({
title: "下载失败",
icon: "none",
});
}
},
fail: (err) => {
downloadStore.updateTask(index, {
status: "failed",
});
console.error("下载失败:", err);
uni.showToast({
title: "下载失败: " + (err.errMsg || "未知错误"),
icon: "none",
duration: 2000,
});
},
});
// 监听下载进度
task.onProgressUpdate((update) => {
const currentTask = downloadStore.getTask(index);
if (currentTask && currentTask.status === "downloading") {
console.log(update.progress);
downloadStore.updateTask(index, {
progress: update.progress,
downloadSize: update.totalBytesWritten,
totalSize: update.totalBytesExpectedToWrite,
});
}
});
// 保存task对象到store(用于暂停/继续)
downloadStore.updateTask(index, {
task: task,
});
};
// 暂停任务
const pauseTask = (index) => {
const taskItem = downloadStore.getTask(index);
if (taskItem && taskItem.task) {
taskItem.task.abort();
downloadStore.updateTask(index, {
status: "paused",
task: null,
});
uni.showToast({
title: "已暂停",
icon: "none",
});
}
};
// 继续任务
const resumeTask = (index) => {
downloadStore.updateTask(index, {
status: "downloading",
progress: 0, // 重新开始(uni.downloadFile不支持断点续传)
});
startDownload(index);
};
// 删除任务
const removeTask = (index) => {
downloadStore.removeTask(index);
syncTasksFromStore();
};
// 打开文件
const openFile = (filePath) => {
// #ifdef APP-PLUS
plus.runtime.openFile(filePath);
// #endif
// #ifndef APP-PLUS
uni.showToast({
title: "请在APP中打开",
icon: "none",
});
// #endif
};
// 获取文件名
const getFileName = (url) => {
if (!url) return "未知文件";
try {
const urlObj = new URL(url);
const pathname = urlObj.pathname;
const fileName = pathname.split("/").pop();
return fileName || "下载文件";
} catch (e) {
// 如果不是完整URL,尝试从路径中提取
const parts = url.split("/");
return parts[parts.length - 1] || "下载文件";
}
};
// 获取状态文本
const getStatusText = (status) => {
const statusMap = {
downloading: "下载中",
paused: "已暂停",
completed: "已完成",
failed: "下载失败",
};
return statusMap[status] || "未知";
};
// 恢复正在下载的任务
const resumeDownloadingTasks = () => {
// 使用 nextTick 确保在页面渲染后再恢复下载任务
nextTick(() => {
downloadStore.resumeDownloadingTasks((index) => {
// 重新开始下载(uni.downloadFile不支持断点续传,所以从0开始)
console.log("恢复下载任务:", downloadStore.getTask(index)?.url);
startDownload(index);
});
});
};
onMounted(() => {
getStorageInfo();
});
</script>
<style lang="scss" scoped>
page{
background-color: #ffffff;
}
.size-box{
display: flex;
white-space: nowrap;
justify-content: flex-end;
white-space: nowrap;
font-size: 24rpx;
color:#8b2316
}
.box{
display: flex;
white-space: nowrap;
justify-content: flex-end;
margin-left: 30rpx;
text{
width: 40rpx;
display: flex;
white-space: nowrap;
font-size: 24rpx;
color:#8b2316
}
}
.download-video-page {
width: 100%;
margin-top: calc(var(--status-bar-height) + 44px);
height: calc(100vh - var(--status-bar-height) - 44px);
background-color: #ffffff;
display: flex;
flex-direction: column;
}
.footer-btn{
display: flex;
align-items: center;
justify-content: space-between;
height: 100rpx;
background-color: #ffffff;
border-top: 1px solid #f0f0f0;
display: flex;
justify-content: center;
.select{
flex:1;
height: 100rpx;
display: flex;
align-items: center;
justify-content: center;
color: #333;
}
.del{
height: 100rpx;
flex:1;
color:red;
display: flex;
align-items: center;
justify-content: center;
}
.bar{
width: 1px;
height: 40rpx;
background-color: #999;
}
}
.video-progress-bar {
flex:1;
height: 10rpx;
background-color: #f0f0f0;
border-radius: 5px;
}
.video-progress-bar-inner {
height: 10rpx;
background-color: #8b2316;
border-radius: 5px;
}
.nav-header {
display: flex;
align-items: center;
justify-content: space-between;
height: 44px;
padding: 0 15px;
background-color: #ffffff;
position: relative;
}
.nav-back {
width: 40px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
font-size: 28px;
color: #8b2316;
font-weight: bold;
line-height: 1;
}
.nav-title {
position: absolute;
left: 50%;
transform: translateX(-50%);
font-size: 18px;
font-weight: 500;
color: #8b2316;
}
.nav-edit {
width: 40px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
color: #333333;
}
.tabs-container {
display: flex;
align-items: center;
height: 50px;
background-color: #ffffff;
border-bottom: 1px solid #f0f0f0;
position: sticky;
top: calc(var(--status-bar-height) + 44px);
z-index: 100;
}
.tab-item {
flex: 1;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
color: #999999;
position: relative;
&.active {
color: #8b2316;
font-weight: 500;
}
}
.tab-divider {
width: 1px;
height: 20px;
background-color: #e0e0e0;
}
.content-scroll {
flex: 1;
overflow-y: auto;
padding-bottom: 200rpx;
background-color: #ffffff;
}
.empty-state {
display: flex;
align-items: center;
justify-content: center;
padding: 100px 0;
color: #999999;
font-size: 14px;
}
.video-item {
display: flex;
align-items: center;
padding: 15px;
border-bottom: 1px solid #f0f0f0;
position: relative;
}
.video-thumbnail {
width: 120px;
height: 80px;
border-radius: 4px;
background-color: #f5f5f5;
margin-right: 12px;
flex-shrink: 0;
}
.video-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
min-height: 80px;
}
.video-title {
font-size: 15px;
color: #333333;
line-height: 1.4;
margin-bottom: 4px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
}
.video-author {
font-size: 13px;
color: #666666;
margin-bottom: 8px;
}
.video-meta {
display: flex;
align-items: center;
font-size: 12px;
color: #999999;
}
.video-status{
display: flex;
justify-content: space-between;
.status-icon{
display: flex;
align-items: center;
white-space: nowrap;
justify-content: center;
image{
width:30rpx;
height: 29rpx;
margin-right: 10rpx;
}
text{
white-space: nowrap;
font-size: 24rpx;
color: #999999;
}
}
}
.meta-icon {
margin-right: 4px;
display: inline-flex;
align-items: center;
}
.meta-text {
margin-right: 12px;
}
.meta-size {
margin-left: auto;
}
.checkbox-wrapper {
margin-right: 12px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.checkbox {
width: 20px;
height: 20px;
border: 1px solid #d0d0d0;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background-color: #ffffff;
transition: all 0.2s;
&.checked {
background-color: #8b2316;
border-color: #8b2316;
}
}
.delete-btn {
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
padding: 8px 16px;
background-color: #ff4444;
color: #ffffff;
border-radius: 4px;
font-size: 14px;
}
.bottom-box {
position: fixed;
bottom: 0;
left: 0;
right: 0;
width: 100%;
background-color: #ffffff;
z-index: 100;
}
.storage-info {
padding: 12px 15px;
background-color: #f8f8f8;
border-top: 1px solid #f0f0f0;
font-size: 12px;
color: #666666;
text-align: center;
}
</style>
+16 -8
View File
@@ -3,17 +3,18 @@
<!-- 顶部导航栏 --> <!-- 顶部导航栏 -->
<navBar title="意见反馈"></navBar> <navBar :title="'投诉反馈'"></navBar>
<!-- 反馈输入区域 --> <!-- 反馈输入区域 -->
<view class="feedback-input-container"> <view class="feedback-input-container">
<view class="input-area"> <view class="input-area">
<text class="input-placeholder" v-show="!feedbackText">欢迎对我们的软件提供建议,帮助我们更好更快的改进"肝胆相照"APP (200字以内)</text> <!-- <text class="input-placeholder" v-show="!feedbackText">欢迎对我们的软件提供建议,帮助我们更好更快的改进"肝胆相照"APP (200字以内)</text> -->
<textarea <textarea
class="feedback-textarea" class="feedback-textarea"
v-model="feedbackText" v-model="feedbackText"
:maxlength="200" :maxlength="200"
placeholder="" placeholder="尊敬的医生您好,若在交流过程中发现患者发布不适当内容,请您及时填写相关信息,我们将第一时间处理,谢谢您的支持。"
:adjust-position="false"
@input="onInput" @input="onInput"
></textarea> ></textarea>
<!-- <view class="voice-button" @click="toggleVoiceInput"> <!-- <view class="voice-button" @click="toggleVoiceInput">
@@ -24,7 +25,7 @@
<!-- 底部提交按钮 --> <!-- 底部提交按钮 -->
<view class="submit-container"> <view class="submit-container">
<button class="submit-btn" @click="submitFeedback" :disabled="!feedbackText.trim()"> <button class="submit-btn" @click="submitFeedback" >
提交 提交
</button> </button>
</view> </view>
@@ -126,8 +127,15 @@
/* 反馈输入区域 */ /* 反馈输入区域 */
.feedback-input-container { .feedback-input-container {
top: calc(var(--status-bar-height) + 44px);
position: fixed;
left: 0;
right: 0;
bottom: 160rpx;
overflow-y: scroll;
z-index: 1;
padding: 30rpx; padding: 30rpx;
flex: 1; box-sizing: border-box;
} }
.input-area { .input-area {
@@ -199,10 +207,10 @@
.submit-btn { .submit-btn {
width: 100%; width: 100%;
height: 88rpx; height: 88rpx;
background-color: #ffffff; background-color: #fff;
border: 2rpx solid #e0e0e0; border: 2rpx solid #8B2316;
border-radius: 8rpx; border-radius: 8rpx;
color: #ff0000; color: #8B2316;
font-size: 32rpx; font-size: 32rpx;
font-weight: 500; font-weight: 500;
display: flex; display: flex;
+22 -8
View File
@@ -1,20 +1,22 @@
<template> <template>
<view class="visit-note-page"> <view class="visit-note-page">
<uni-nav-bar <navBar :title="'随访详情'" />
<!-- <uni-nav-bar
left-icon="left" left-icon="left"
title="随访详情" title="随访详情"
@clickLeft="goBack" @clickLeft="goBack"
fixed fixed
color="#8B2316" color="#8B2316"
height="140rpx" height="180rpx"
:border="false" :border="false"
backgroundColor="#eee" backgroundColor="#eee"
> >
</uni-nav-bar> </uni-nav-bar> -->
<!-- 患者行 --> <!-- 患者行 -->
<view class="row" @click="noop"> <view class="contentbox">
<view class="row" @click="noop">
<text class="row-label">患者</text> <text class="row-label">患者</text>
<text class="row-value">{{ patientName }}</text> <text class="row-value">{{ patientName }}</text>
</view> </view>
@@ -27,7 +29,8 @@
<!-- 输入框区域 --> <!-- 输入框区域 -->
<view class="note-box"> <view class="note-box">
<textarea class="note-textarea" v-model.trim="note" placeholder="请填写随访内容" auto-height :maxlength="200" /> <textarea class="note-textarea" v-model.trim="note" placeholder="请填写随访内容" auto-height :maxlength="200" :adjust-position="false"/>
</view>
</view> </view>
<!-- 底部删除按钮 --> <!-- 底部删除按钮 -->
<view class="fixed-footer"> <view class="fixed-footer">
@@ -41,6 +44,7 @@
import { onLoad,onShow } from '@dcloudio/uni-app' import { onLoad,onShow } from '@dcloudio/uni-app'
import api from '@/api/api.js' import api from '@/api/api.js'
const patientName = ref('') const patientName = ref('')
import navBar from '@/components/navBar/navBar.vue'
const note = ref('请近日来医院复诊、复查') const note = ref('请近日来医院复诊、复查')
const datetime = ref('') const datetime = ref('')
const followUpUuid = ref('') const followUpUuid = ref('')
@@ -91,19 +95,29 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.contentbox{
position: fixed;
top: calc(var(--status-bar-height) + 44px);
left: 0;
right: 0;
overflow-y: scroll;
bottom:128rpx;
z-index:1;
}
.visit-note-page{ .visit-note-page{
min-height: 100vh; background:#f5f5f5; padding-bottom: 120rpx; background:#f5f5f5; padding-bottom: 120rpx;
} }
.nav-right{ display:flex; align-items:center; } .nav-right{ display:flex; align-items:center; }
.submit-text{ color:#8B2316; font-size: 30rpx; } .submit-text{ color:#8B2316; font-size: 30rpx; }
.row{ .row{
margin-top: 20rpx; background:#fff; padding: 26rpx 30rpx; display:flex; align-items:center; justify-content:space-between; border-bottom: 1rpx solid #eee; background:#fff; padding: 26rpx 30rpx; display:flex; align-items:center; justify-content:space-between; border-bottom: 1rpx solid #eee;
.row-label{ font-size: 32rpx; color:#8B2316; font-weight: 500; } .row-label{ font-size: 32rpx; color:#8B2316; font-weight: 500; }
.row-value{ font-size: 32rpx; color:#333; } .row-value{ font-size: 32rpx; color:#333; }
} }
.section-title{ background:#fff; padding: 22rpx 30rpx; color:#8B2316; font-size: 30rpx; border-top: 1rpx solid #eee; border-bottom: 1rpx solid #eee; } .section-title{ background:#fff; padding: 22rpx 30rpx; color:#8B2316; font-size: 30rpx;border-bottom: 1rpx solid #eee; }
.note-box{ background:#fff; padding: 20rpx 24rpx; } .note-box{ background:#fff; padding: 20rpx 24rpx; }
.note-textarea{ .note-textarea{
+513
View File
@@ -0,0 +1,513 @@
<template>
<view class="consult-detail-page">
<!-- 导航栏 -->
<navBar title="咨询详情" />
<!-- 内容区域 -->
<scroll-view scroll-y class="content-scroll">
<!-- 用户信息区域 -->
<view class="user-section">
<view class="user-info">
<image v-if="userInfo.avatar" :src="userInfo.avatar" class="user-avatar" mode="aspectFill" />
<view class="user-details">
<view class="user-name">
<text class="name">{{ userInfo.name }}</text>
<text class="gender-age" v-if="userInfo.gender !== '未知'">({{ userInfo.gender }} {{ userInfo.age }}岁)</text>
</view>
</view>
</view>
</view>
<!-- 疾病标签和日期 -->
<view class="tag-date-row">
<view class="disease-tag">
<text class="tag-text">{{ questionInfo.diseaseTag }}</text>
</view>
<view class="date">{{ questionInfo.date }}</view>
</view>
<!-- 问题内容 -->
<view class="question-content">
<text class="content-text">{{ questionInfo.diseaseDescribe }}</text>
</view>
<!-- 疾病描述 -->
<!-- <view v-if="questionInfo.diseaseDescribe" class="disease-describe">
<view class="describe-title">疾病描述:</view>
<text class="describe-text">{{ questionInfo.diseaseDescribe }}</text>
</view> -->
<!-- 图片网格 -->
<view class="image-grid" v-if="questionInfo.images && questionInfo.images.length > 0">
<image
v-for="(img, index) in questionInfo.images"
:key="index"
:src="docUrl+img.path"
class="grid-image"
mode="aspectFill"
@click="previewImage(docUrl+img.path, index)"
/>
</view>
<view class="bar"></view>
</scroll-view>
<!-- 底部固定区域 -->
<view class="bottom-fixed">
<!-- 特别声明 -->
<!-- 编辑按钮 -->
<view class="edit-btn" @click="answerConsult">
我要回答
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import navBar from '@/components/navBar/navBar.vue'
import detailImg from "@/static/iv_jiwangshi.png"
import navTo from '@/utils/navTo.js'
import { onLoad,onShow } from '@dcloudio/uni-app'
import docUrl from '@/utils/docUrl'
import api from "@/api/api.js"
const uuid = ref('');
const step1_uuid = ref('');
let patientUuid = ref('');
const status = ref(0);
const hasAnswer = ref(false);
onLoad((options) => {
uuid.value = options.uuid
status.value = options.status || 0
})
const consultDetail=()=>{
api.consultDetail({
uuid:uuid.value
}).then(res=>{
console.log('咨询详情数据:', res)
if(res.code === 200 && res.data && res.data.detail) {
const detail = res.data.detail
patientUuid.value = detail.patientUuid;
// 更新用户信息
userInfo.value = {
name: detail.patientName ,
gender: '未知', // 新数据结构中没有性别信息
age: '未知' // 新数据结构中没有年龄信息
}
// 更新问题信息
questionInfo.value = {
date: detail.createDate ? formatDate(detail.createDate) : '未知',
diseaseTag: detail.diseaseName || '未知疾病',
content: detail.content || '暂无问题描述',
images: res.data.imgList?res.data.imgList:[],
your_question: detail.content || '',
diseaseDescribe: detail.content || '' // 将咨询内容作为疾病描述显示
}
// 更新患者头像
if(detail.patientPhoto) {
userInfo.value.avatar = docUrl + detail.patientPhoto
}
// 更新状态信息
status.value = detail.state || 0
console.log('更新后的用户信息:', userInfo.value)
console.log('更新后的问题信息:', questionInfo.value)
} else {
console.error('获取咨询详情失败:', res)
uni.showToast({
title: res.msg || '获取咨询详情失败',
icon: 'none'
})
}
}).catch(err => {
console.error('请求咨询详情失败:', err)
uni.showToast({
title: '网络请求失败',
icon: 'none'
})
})
}
const countConsult=()=>{
api.countConsult({
consultUuid:uuid.value,
expertUuid:uni.getStorageSync('userInfo').uuid
}).then(res=>{
console.log('咨询数量:', res)
if(res.code ==200 && res.data){
if(res.data> 0){
uni.showModal({
title: '提示',
content: `您已回答过该患者${res.data}次公益咨询,是否确定回答`,
success: (res)=>{
if(res.confirm){
resConsult()
}
}
})
}else{
resConsult();
}
}
})
}
const answerConsult=async()=>{
if(hasAnswer.value){
uni.sendNativeEvent('goConsultPage', {
msg: 'chat'
},ret => {
console.log(ret);
})
// let userId=uni.getStorageSync('userInfo').uuid.toLowerCase();
// let conversationId=userId+'|1|'+patientUuid.value.toLowerCase();
// await uni.$UIKitStore.uiStore.selectConversation(conversationId)
// navTo({
// url:'/pages_chat/chat/index?from=consult&patientUuid='+patientUuid.value
// })
}else{
countConsult()
}
}
const resConsult=()=>{
api.resConsult({
consultUuid:uuid.value,
expertUuid:uni.getStorageSync('userInfo').uuid
}).then(async(res)=>{
if(res.code ==200){
hasAnswer.value = true;
patientUuid.value = res.data.patient_uuid;
let userId=uni.getStorageSync('userInfo').uuid.toLowerCase();
let conversationId=userId+'|1|'+patientUuid.value.toLowerCase();
await uni.$UIKitStore.uiStore.selectConversation(conversationId)
navTo({
url:'/pages_chat/chat/index?from=consult&patientUuid='+patientUuid.value+'&uuid='+uuid.value
})
}
})
}
onShow(()=>{
consultDetail()
})
// 计算年龄
function calculateAge(birthday) {
const birthDate = new Date(birthday)
const today = new Date()
let age = today.getFullYear() - birthDate.getFullYear()
const monthDiff = today.getMonth() - birthDate.getMonth()
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
age--
}
return age.toString()
}
// 格式化日期
function formatDate(dateString) {
const date = new Date(dateString)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}
// 用户信息
const userInfo = ref({
name: '提**',
gender: '未知',
age: '未知',
avatar: ''
})
// 问题信息
const questionInfo = ref({
date: '2022-11-09',
diseaseTag: '甲型肝炎',
content: '为什么程序员总是分不清万圣节和圣诞节?因为Oct31==Dec25。\n任何我写的代码,超过6个月不去看它,当我再看时,都像是别人写的。',
diseaseDescribe: '', // 疾病描述
images: [
'/static/images/placeholder1.jpg',
'/static/images/placeholder2.jpg',
'/static/images/placeholder3.jpg',
'/static/images/placeholder4.jpg',
'/static/images/placeholder5.jpg',
'/static/images/placeholder6.jpg',
'/static/images/placeholder7.jpg',
'/static/images/placeholder8.jpg'
]
})
// 返回上一页
function goBack() {
uni.navigateBack()
}
// 预览图片
function previewImage(current, index) {
uni.previewImage({
urls: questionInfo.value.images?questionInfo.value.images.map(item=> docUrl + item.path):[],
current: index
})
}
// 编辑问题
function editQuestion() {
navTo({
url:'/pages_app/myAnswer/myAnswer?uuid='+uuid.value
})
}
</script>
<style lang="scss" scoped>
.consult-detail-page {
background-color: #fff;
height: 100vh;
display: flex;
flex-direction: column;
}
.nav-bar {
display: flex;
align-items: center;
justify-content: space-between;
height: 88rpx;
padding: 0 32rpx;
background-color: #ffffff;
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
.nav-left {
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
.back-icon {
font-size: 48rpx;
color: #8B2316;
font-weight: bold;
}
}
.nav-title {
font-size: 36rpx;
font-weight: 500;
color: #8B2316;
}
.nav-right {
width: 80rpx;
}
}
.bar{
width:100%;
height:20rpx;
background-color: #efefef;
}
.content-scroll {
flex: 1;
position: fixed;
top: 135rpx;
box-sizing: border-box;
padding: 30rpx 0;
bottom: 100rpx;
}
.user-section {
background-color: #ffffff;
border-radius: 16rpx;
margin-bottom: 24rpx;
padding: 0 30rpx;
.user-info {
display: flex;
align-items: center;
margin-bottom: 16rpx;
.user-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 40rpx;
margin-right: 24rpx;
background-color: #f0f0f0;
}
.user-details {
flex: 1;
.user-name {
display: flex;
align-items: center;
margin-bottom: 8rpx;
.name {
font-size: 32rpx;
color: #8B2316;
font-weight: 500;
}
.gender-age {
font-size: 28rpx;
color: #666;
margin-left: 8rpx;
}
}
.user-status {
.status-text {
font-size: 24rpx;
color: #999;
padding: 4rpx 12rpx;
background-color: #f0f0f0;
border-radius: 12rpx;
}
}
}
.detail-btn {
display: flex;
margin-left: 10rpx;
.detail-text {
color: #ffffff;
font-size: 24rpx;
margin-right: 8rpx;
}
.detail-icon {
color: #ffffff;
font-size: 24rpx;
}
}
}
}
.tag-date-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24rpx;
margin: 0 30rpx 30rpx;
.disease-tag {
margin-bottom: 0;
.tag-text {
display: inline-block;
background-color: #ffffff;
color: #8B2316;
border: 2rpx solid #8B2316;
border-radius: 40rpx;
padding: 7rpx 22rpx;
font-size: 24rpx;
}
}
.date {
font-size: 28rpx;
color: #999;
}
}
.question-content {
background-color: #f0f0f0;
border-radius: 16rpx;
padding: 32rpx;
margin: 0 30rpx;
margin-bottom: 24rpx;
.content-text {
font-size: 28rpx;
color: #333;
line-height: 1.6;
}
}
.disease-describe {
background-color: #f8f9fa;
border-radius: 16rpx;
padding: 32rpx;
margin: 0 30rpx 24rpx;
border-left: 6rpx solid #8B2316;
.describe-title {
font-size: 30rpx;
color: #8B2316;
font-weight: 500;
margin-bottom: 16rpx;
}
.describe-text {
font-size: 28rpx;
color: #333;
line-height: 1.6;
}
}
.image-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8rpx;
margin: 0 30rpx 40rpx;
.grid-image {
width: 100%;
height: 160rpx;
border-radius: 8rpx;
}
}
.bottom-fixed {
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: 24rpx 32rpx;
}
.disclaimer {
background-color: #fff5f5;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 24rpx;
.disclaimer-title {
font-size: 28rpx;
color: #8B2316;
font-weight: 500;
}
.disclaimer-text {
font-size: 28rpx;
color: #666;
line-height: 1.5;
}
}
.edit-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
text-align: center;
border: 1rpx solid #8B2316;
background-color: #fff;
color: #8B2316;
font-size: 32rpx;
border-radius: 16rpx;
}
</style>
+41 -11
View File
@@ -1,12 +1,14 @@
<template> <template>
<view class="group-edit-page"> <view class="group-edit-page">
<uni-nav-bar <view class="navbox">
<view class="status_bar"></view>
<uni-nav-bar
left-icon="left" left-icon="left"
:title="groupUuid ? '编辑分组' : '新建分组'" :title="groupUuid ? '编辑分组' : '新建分组'"
@clickLeft="goBack" @clickLeft="goBack"
fixed
color="#8B2316" color="#8B2316"
height="140rpx"
:border="false" :border="false"
backgroundColor="#eee" backgroundColor="#eee"
> >
@@ -15,7 +17,9 @@
</template> </template>
</uni-nav-bar> </uni-nav-bar>
<!-- 分组名称 --> </view>
<view class="contentbox">
<!-- 分组名称 -->
<view class="section-header">分组名称</view> <view class="section-header">分组名称</view>
<view class="name-row"> <view class="name-row">
<input class="name-input" v-model.trim="groupName" placeholder="请输入分组名称" maxlength="20" /> <input class="name-input" v-model.trim="groupName" placeholder="请输入分组名称" maxlength="20" />
@@ -28,16 +32,14 @@
<view class="section-header">分组成员</view> <view class="section-header">分组成员</view>
<view class="add-member" @click="addMember"> <view class="add-member" @click="addMember">
<view class="add-circle"> <up-image :src="addImg" width="120rpx" height="120rpx" />
<up-icon name="plus" size="34" color="#bfbfbf" />
</view>
<text class="add-text">添加组患者</text> <text class="add-text">添加组患者</text>
</view> </view>
<!-- 已选中的成员 --> <!-- 已选中的成员 -->
<view class="selected-members" v-if="members.length > 0"> <view class="selected-members" v-if="members.length > 0">
<view class="selected-item" v-for="(m, idx) in members" :key="m.uuid || idx"> <view class="selected-item" v-for="(m, idx) in members" :key="m.uuid || idx">
<image class="selected-avatar" :src="docUrl + (m.photo || '')" mode="aspectFill" /> <image class="selected-avatar" :src="getMemberAvatar(m)" mode="aspectFill" @error="handleMemberAvatarError(m)" />
<text class="selected-name">{{ m.realName || '未知' }}</text> <text class="selected-name">{{ m.nickname || m.realName }}</text>
<view class="remove-selected" @click="removeMember(idx)"> <view class="remove-selected" @click="removeMember(idx)">
<text class="remove-text">——</text> <text class="remove-text">——</text>
<!-- <up-icon name="minus" size="43rpx" color="#fff" bold /> --> <!-- <up-icon name="minus" size="43rpx" color="#fff" bold /> -->
@@ -46,6 +48,7 @@
</view> </view>
</view>
<!-- 底部删除按钮 --> <!-- 底部删除按钮 -->
@@ -63,12 +66,35 @@
import navTo from '@/utils/navTo.js' import navTo from '@/utils/navTo.js'
import docUrl from '@/utils/docUrl.js' import docUrl from '@/utils/docUrl.js'
import delImg from "@/static/iv_delete.png" import delImg from "@/static/iv_delete.png"
import addImg from "@/static/addpatient.png"
import api from '@/api/api.js' import api from '@/api/api.js'
import defaultImg from "@/static/default.png"
import unidialog from '@/components/dialog/dialog.vue' import unidialog from '@/components/dialog/dialog.vue'
const groupUuid = ref('') const groupUuid = ref('')
const groupName = ref('') const groupName = ref('')
const members = ref([]) const members = ref([])
const visible = ref(false) const visible = ref(false)
const avatarErrorMap = ref({})
const getAvatarKey = (member = {}) => String(member.uuid || member.id || member.realName || '')
const normalizeAvatarUrl = (photo) => {
const raw = String(photo || '').trim()
if (!raw) return defaultImg
if (/^https?:\/\//i.test(raw)) return raw
return `${docUrl}${raw}`
}
const getMemberAvatar = (member = {}) => {
const key = getAvatarKey(member)
if (key && avatarErrorMap.value[key]) return defaultImg
return normalizeAvatarUrl(member.photo)
}
const handleMemberAvatarError = (member) => {
const key = getAvatarKey(member)
if (!key) return
avatarErrorMap.value = {
...avatarErrorMap.value,
[key]: true
}
}
const confirmDelete = () => { const confirmDelete = () => {
deleteGroup(); deleteGroup();
} }
@@ -182,6 +208,7 @@
}) })
} }
const patientListByGroup = async () => { const patientListByGroup = async () => {
avatarErrorMap.value = {}
const res = await api.patientListByGroup({ const res = await api.patientListByGroup({
group_uuid: groupUuid.value, group_uuid: groupUuid.value,
list_sort:0 list_sort:0
@@ -194,19 +221,22 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.contentbox{
margin-top: calc(var(--status-bar-height) + 44px);
}
.group-edit-page{ .group-edit-page{
min-height: 100vh; min-height: 100vh;
background: #f5f5f5; background: #f5f5f5;
padding-bottom: 160rpx; padding-bottom: 160rpx;
} }
.save-text{ .save-text{
background:#8B2316; color:#8B2316;
font-size: 30rpx; font-size: 30rpx;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 15rpx 25rpx; padding: 15rpx 25rpx;
color:#fff;
border-radius: 12rpx; border-radius: 12rpx;
} }
+28 -10
View File
@@ -1,12 +1,12 @@
<template> <template>
<view class="group-manage-page"> <view class="group-manage-page">
<uni-nav-bar <view class="navbox">
<view class="status_bar"></view>
<uni-nav-bar
left-icon="left" left-icon="left"
title="分组管理" title="分组管理"
@clickLeft="goBack" @clickLeft="goBack"
fixed
color="#8B2316" color="#8B2316"
height="140rpx"
:border="false" :border="false"
backgroundColor="#eee" backgroundColor="#eee"
> >
@@ -14,8 +14,10 @@
<view class="save-text" @click="saveGroups">保存</view> <view class="save-text" @click="saveGroups">保存</view>
</template> </template>
</uni-nav-bar> </uni-nav-bar>
</view>
<!-- 已选中的分组 --> <view class="content-box">
<!-- 已选中的分组 -->
<view class="selected-section"> <view class="selected-section">
<view class="tag-list"> <view class="tag-list">
<view <view
@@ -30,7 +32,7 @@
<!-- 所有分组 --> <!-- 所有分组 -->
<view class="all-groups-section"> <view class="all-groups-section">
<view class="section-title">所有分组(选择分组)</view> <view class="section-title"><text calss="red">所有分组</text>(选择分组)</view>
<view class="tag-list"> <view class="tag-list">
<view <view
class="tag-item" class="tag-item"
@@ -43,6 +45,7 @@
</view> </view>
</view> </view>
</view> </view>
</view>
<!-- 底部添加按钮 --> <!-- 底部添加按钮 -->
<view class="bottom-bar"> <view class="bottom-bar">
@@ -80,7 +83,9 @@ const loadAllGroups = async () => {
try { try {
const res = await api.groupList(); const res = await api.groupList();
if (res.code === 200) { if (res.code === 200) {
allGroups.value = res.data || []; const hiddenGroupNames = new Set(['待分组患者', '肝系一生非患教组', '肝系一生患教组']);
const arr = (res.data || []).filter((item) => !hiddenGroupNames.has(String(item?.name || '').trim()));
allGroups.value = arr;
} }
} catch (e) { } catch (e) {
console.error("加载分组列表失败:", e); console.error("加载分组列表失败:", e);
@@ -165,10 +170,20 @@ const saveGroups = () => {
font-size: 30rpx; font-size: 30rpx;
color: #333; color: #333;
} }
.content-box{
position: fixed;
left: 0;
right: 0;
top: calc(var(--status-bar-height) + 44px);
bottom: 140rpx;
overflow-y: scroll;
z-index: 1;
}
.selected-section { .selected-section {
min-height: 300rpx; min-height: 300rpx;
padding: 30rpx; padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0; border-bottom: 20rpx solid #f0f0f0;
} }
.all-groups-section { .all-groups-section {
@@ -176,9 +191,12 @@ const saveGroups = () => {
} }
.section-title { .section-title {
font-size: 28rpx; font-size: 30rpx;
color: #666; color: #999;
margin-bottom: 20rpx; margin-bottom: 20rpx;
.red{
color: #8b2316;
}
} }
.tag-list { .tag-list {
@@ -233,7 +251,7 @@ const saveGroups = () => {
bottom: 0; bottom: 0;
background: #fff; background: #fff;
border-top: 1rpx solid #f0f0f0; border-top: 1rpx solid #f0f0f0;
padding: 20rpx 30rpx env(safe-area-inset-bottom); padding: 20rpx 30rpx 20rpx;
} }
.add-btn { .add-btn {
+1 -1
View File
@@ -7,7 +7,7 @@
@clickLeft="goBack" @clickLeft="goBack"
fixed fixed
color="#8B2316" color="#8B2316"
height="140rpx" height="180rpx"
:border="false" :border="false"
backgroundColor="#eeeeee" backgroundColor="#eeeeee"
/> />
+238 -83
View File
@@ -1,18 +1,19 @@
<template> <template>
<view class="zhinan-list-page"> <view class="zhinan-list-page">
<!-- 头部导航栏 --> <!-- 头部导航栏 -->
<uni-nav-bar <!-- <uni-nav-bar
left-icon="left" left-icon="left"
:title="title" :title="title"
@clickLeft="goBack" @clickLeft="goBack"
fixed fixed
color="#8B2316" color="#8B2316"
height="140rpx" height="180rpx"
:border="false" :border="false"
backgroundColor="#eeeeee" backgroundColor="#eeeeee"
> >
</uni-nav-bar> </uni-nav-bar> -->
<navBar :title="title" />
<!-- 使用scroll-view实现列表 --> <!-- 使用scroll-view实现列表 -->
<scroll-view <scroll-view
@@ -39,20 +40,17 @@
<view class="item-date">{{ formatDate(item.releaseTime) }}</view> <view class="item-date">{{ formatDate(item.releaseTime) }}</view>
<!-- 操作按钮 --> <!-- 操作按钮 -->
<view class="item-action"> <view class="item-action">
<view <view class="download-btn" @click.stop="downLoad(item)" v-if="!downLoadtaskList.includes(item.uuid)">
class="download-btn" <!-- <up-icon name="download" color="#8D2316" size="28" ></up-icon> -->
@click.stop="viewGuideline(item)" <up-image :src="downIng" width="35rpx" height="35rpx"></up-image>
> <!-- <uni-icons type="arrow-down" size="26" color="#8D2316"></uni-icons> -->
<up-icon name="download" color="#8D2316" size="28"></up-icon> </view>
</view> <view
<!-- <view v-else
v-else class="view-btn"
class="view-btn" @click.stop="viewGuideline(item)"
@click.stop="viewGuideline(item)" >查看</view>
> </view>
查看
</view> -->
</view>
</view> </view>
</view> </view>
</view> </view>
@@ -73,22 +71,25 @@
</view> </view>
<!-- 无数据提示 --> <!-- 无数据提示 -->
<view class="no-data" v-if="guidelinesList.length === 0 && !isLoading"> <empty v-if="guidelinesList.length === 0 && !isLoading"></empty>
<!-- <view class="no-data" v-if="guidelinesList.length === 0 && !isLoading">
<uni-icons type="info" size="60" color="#999"></uni-icons> <uni-icons type="info" size="60" color="#999"></uni-icons>
<text>暂无诊疗指南数据</text> <text>暂无诊疗指南数据</text>
</view> </view> -->
</scroll-view> </scroll-view>
</view> </view>
</template> </template>
<script setup> <script setup>
import { ref, onMounted} from 'vue' import { ref, onMounted,computed,nextTick} from 'vue'
import api from '@/api/api.js' import api from '@/api/api.js'
import { onShow,onLoad } from "@dcloudio/uni-app"; import { onShow,onLoad } from "@dcloudio/uni-app";
import docUrl from "@/utils/docUrl.js" import docUrl from "@/utils/docUrl.js"
import navTo from "@/utils/navTo.js" import navTo from "@/utils/navTo.js"
import hotImg from "@/static/hot_booklist.png" import hotImg from "@/static/hot_booklist.png"
import navBar from "@/components/navBar/navBar.vue"
import empty from "@/components/empty/empty.vue"
// 响应式数据 // 响应式数据
const guidelinesList = ref([]) const guidelinesList = ref([])
const isLoading = ref(false) const isLoading = ref(false)
@@ -99,6 +100,7 @@ const isRefreshing = ref(false)
const keywords=ref(''); const keywords=ref('');
import upImg from "@/static/triangle_green_theme.png" import upImg from "@/static/triangle_green_theme.png"
import downImg from "@/static/triangle_normal.png" import downImg from "@/static/triangle_normal.png"
import downIng from "@/static/icon_library_down.png";
const loadMoreStatus = ref('more') // 'loading', 'more', 'noMore' const loadMoreStatus = ref('more') // 'loading', 'more', 'noMore'
const typeUuid=ref(''); const typeUuid=ref('');
const title=ref(''); const title=ref('');
@@ -107,6 +109,12 @@ const from=ref('');
const name=ref(''); const name=ref('');
const showInnerSort=ref(false); const showInnerSort=ref(false);
const innerSortTitle=ref('上传时间'); const innerSortTitle=ref('上传时间');
import downloadStore from "@/store/downloadStoreFile.js";
import isAndroid from '@/utils/platform';
const downloadTasks=ref([]);
const downLoadtaskList=computed(()=>{
return downloadTasks.value.filter((item)=>item.status == "completed" && item.type == "zhinan").map((item)=>item.id);
});
const chooseInnerSort=(index)=>{ const chooseInnerSort=(index)=>{
sort.value=index; sort.value=index;
if(index==1){ if(index==1){
@@ -144,12 +152,20 @@ const toggleInnerSort=()=>{
showInnerSort.value=!showInnerSort.value; showInnerSort.value=!showInnerSort.value;
} }
onShow(() => { onShow(() => {
syncTasksFromStore();
// 监听store变化
downloadStore.addListener((tasks) => {
downloadTasks.value = tasks;
});
// 恢复正在下载的任务
resumeDownloadingTasks();
// 页面显示时,如果列表为空则加载数据 // 页面显示时,如果列表为空则加载数据
if (guidelinesList.value.length === 0) { if (guidelinesList.value.length === 0) {
if(from.value){ if(from.value){
top10ByType(); top10ByType();
}else{ }else{
loadGuidelinesList(true) loadGuidelinesList(false)
} }
} }
}) })
@@ -246,15 +262,16 @@ const loadGuidelinesList = async (isRefresh = false) => {
try { try {
isLoading.value = true isLoading.value = true
loadMoreStatus.value = 'loading' loadMoreStatus.value = 'loading'
let expertUuid=uni.getStorageSync('userInfo').uuid;
const params = { const params = {
page: currentPage.value, page: currentPage.value,
pageSize: pageSize.value, pageSize: pageSize.value,
expertUUid:expertUuid,
type: 1, // 诊疗指南类型 type: 1, // 诊疗指南类型
sort:2, sort:2,
name:name.value, name:name.value,
typeUuid:typeUuid.value, typeUuid:typeUuid.value,
keywords:keywords.value keyWords:keywords.value
} }
const res = await api.searchLibraryU(params) const res = await api.searchLibraryU(params)
@@ -272,7 +289,7 @@ const loadGuidelinesList = async (isRefresh = false) => {
} }
// 判断是否还有更多数据 // 判断是否还有更多数据
if (newData.length < pageSize.value) { if (newData.length < res.data.pageSize) {
loadMoreStatus.value = 'noMore' loadMoreStatus.value = 'noMore'
hasMoreData.value = false hasMoreData.value = false
console.log('没有更多数据了'); console.log('没有更多数据了');
@@ -359,66 +376,94 @@ const downloadGuideline = (item) => {
}) })
} }
} }
const downLoad = (item) => {
if(!item.path){
uni.showToast({
title: "暂无下载链接",
icon: "none",
});
return;
}
uni.sendNativeEvent("portraitReport",{
msg:{
event_nickname: "magazine_donwload",
page_type: "诊疗指南",
resource_name:item.title,
belong:title.value,
number:1,
resource_option:item.tags
},
},
(ret) => {
console.log(ret);
});
addDownloadTask({
url: docUrl + item.path,
title: item.title,
id: item.uuid,
type: 'zhinan',
releaseTime: item.releaseTime,
});
};
// 查看指南(支持常用文档直链:pdf/doc/docx/xls/xlsx/ppt/pptx/txt) // 查看指南(支持常用文档直链:pdf/doc/docx/xls/xlsx/ppt/pptx/txt)
const viewGuideline = (item) => { const viewGuideline = (cell) => {
const url = docUrl+item.path console.log(cell);
if (url) { if (!cell.path) {
const isPDF = /\.(pdf)(\?|$)/i.test(url) uni.showToast({
const isOffice = /\.(docx?|xlsx?|pptx?)(\?|$)/i.test(url) title: "暂无下载链接",
const isText = /\.(txt)(\?|$)/i.test(url) icon: "none",
});
return;
}
console.log('下载列表');
console.log(downloadTasks.value);
downloadTasks.value.forEach(item => {
if(item.id == cell.uuid) {
// H5 端 uni.sendNativeEvent("portraitReport",{
// #ifdef H5 msg:{
if (isPDF) { event_nickname: "detail_page",
try { window.open(url, '_blank') } catch (e) { page_type: "诊疗指南",
uni.navigateTo({ url: `/pages_app/webview/webview?url=${encodeURIComponent(url)}` }) resource_name:cell.title,
} resource_option:cell.tags
return },
} },
if (isOffice) { (ret) => {
const officeUrl = `https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(url)}` console.log(ret);
try { window.open(officeUrl, '_blank') } catch (e) { });
uni.navigateTo({ url: `/pages_app/webview/webview?url=${encodeURIComponent(officeUrl)}` }) // if(isAndroid){
} plus.runtime.openFile(item.localPath, function(e) {
return console.log('打开成功');
} }, function(e) {
if (isText) { uni.showToast({
uni.navigateTo({ url: `/pages_app/webview/webview?url=${encodeURIComponent(url)}` }) title: '打开失败:' + e.message,
return icon: "none",
} duration: 2000,
// 其他格式统一用 webview 打开 });
uni.navigateTo({ url: `/pages_app/webview/webview?url=${encodeURIComponent(url)}` }) console.log('打开失败:' + e.message);
return });
// #endif // }else{
// uni.sendNativeEvent('openDocument', {
// msg: item.localPath.replaceAll('_doc/uniapp_save/',''),
// }, ret => {
// console.log(ret);
// });
// }
// uni.openDocument({
// filePath: item.localPath,
// fileType: 'pdf',
// success: () => console.log('打开成功'),
// fail: (err) => {
// uni.showToast({
// title: err,
// icon: "none",
// });
// }
// });
}
});
// 非 H5 端:下载后尝试用 openDocument,失败回退 webview
// #ifndef H5
uni.showToast({ title: '正在打开...', icon: 'none' })
uni.downloadFile({
url,
success: (res) => {
if (res.statusCode === 200 && res.tempFilePath) {
uni.openDocument({
filePath: res.tempFilePath,
showMenu: true,
success: () => {},
fail: () => {
uni.navigateTo({ url: `/pages_app/webview/webview?url=${encodeURIComponent(url)}` })
}
})
} else {
uni.navigateTo({ url: `/pages_app/webview/webview?url=${encodeURIComponent(url)}` })
}
},
fail: () => {
uni.navigateTo({ url: `/pages_app/webview/webview?url=${encodeURIComponent(url)}` })
}
})
return
// #endif
}
// 无直链,回退到详情页
} }
@@ -463,6 +508,116 @@ const formatDate = (dateString) => {
return dateString return dateString
} }
} }
// 添加下载任务
const addDownloadTask = (item) => {
// 使用store添加任务
const taskIndex = downloadStore.addTask(item);
// 同步任务列表
syncTasksFromStore();
// 开始下载
startDownload(taskIndex);
};
// 开始下载
const startDownload = (index) => {
const taskItem = downloadStore.getTask(index);
if (!taskItem) return;
if(taskItem.status == "loading"){
return false;
};
uni.showToast({
title: "正在下载中",
icon: "none",
});
downloadStore.updateTask(index, {
status: "loading"
});
const task = uni.downloadFile({
url: taskItem.url,
success: (res1) => {
//console.log("res1:"+JSON.stringify(res1));
if (res1.statusCode === 200) {
downloadStore.updateTask(index, {
status: "completed",
filePath: res1.tempFilePath,
});
uni.showToast({
title: "下载成功",
icon: "none",
});
uni.saveFile({
tempFilePath: res1.tempFilePath,
success: function (res2) {
console.log("res2:"+JSON.stringify(res2));
uni.getSavedFileInfo({
filePath: res2.savedFilePath,
success: function (res) {
console.log("res:"+JSON.stringify(res));
console.log("size:"+res.size);
downloadStore.updateTask(index, {
status: "completed",
localPath: res2.savedFilePath,
});
},
});
},
});
} else {
downloadStore.updateTask(index, {
status: "failed",
});
uni.showToast({
title: "下载失败",
icon: "none",
});
}
},
fail: (err) => {
downloadStore.updateTask(index, {
status: "failed",
});
uni.showToast({
title: "下载失败: " + (err.errMsg || "未知错误"),
icon: "none",
duration: 2000,
});
},
});
// 监听下载进度
task.onProgressUpdate((update) => {
const currentTask = downloadStore.getTask(index);
if (currentTask && currentTask.status === "downloading") {
downloadStore.updateTask(index, {
progress: update.progress
});
}
});
// 保存task对象到store(用于暂停/继续)
downloadStore.updateTask(index, {
task: task,
});
};
const syncTasksFromStore = () => {
downloadTasks.value = downloadStore.getTasks();
};
// 恢复正在下载的任务
const resumeDownloadingTasks = () => {
// 使用 nextTick 确保在页面渲染后再恢复下载任务
nextTick(() => {
downloadStore.resumeDownloadingTasks((index) => {
// 重新开始下载(uni.downloadFile不支持断点续传,所以从0开始)
console.log("恢复下载任务:", downloadStore.getTask(index)?.url);
startDownload(index);
});
});
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -534,18 +689,18 @@ $white: #ffffff;
.guidelines-scroll-view { .guidelines-scroll-view {
position: fixed; position: fixed;
top: 140rpx; top: 180rpx;
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
z-index: 99; z-index: 99;
height: calc(100vh - 140rpx); height: calc(100vh - 180rpx);
background-color: $bg-color; background-color: $bg-color;
} }
// 固定搜索容器 // 固定搜索容器
.search-container-fixed { .search-container-fixed {
position: fixed; position: fixed;
top: 140rpx; // 导航栏高度 top: 180rpx; // 导航栏高度
left: 0; left: 0;
right: 0; right: 0;
z-index: 90; z-index: 90;
+216 -25
View File
@@ -1,35 +1,63 @@
<template> <template>
<view class="bank-card-page"> <view class="bank-card-page">
<!-- 顶部导航栏 --> <!-- 顶部导航栏 -->
<uni-nav-bar <view class="navbox">
<view class="status_bar"></view>
<uni-nav-bar
left-icon="left" left-icon="left"
title="常用银行卡" title="常用银行卡"
@clickLeft="goBack()" @clickLeft="goBack"
right-text="添加" right-icon="plusempty"
right-icon-color="#8B2316"
@clickRight="addBankCard" @clickRight="addBankCard"
fixed
color="#8B2316" color="#8B2316"
height="140rpx"
:border="false" :border="false"
backgroundColor="#eeeeee" backgroundColor="#eeeeee"
> >
</uni-nav-bar> </uni-nav-bar>
</view>
<!-- 银行卡列表 --> <!-- 银行卡列表 -->
<view class="card-list"> <view class="card-list">
<view class="card-item" v-for="(card, index) in bankCards" :key="card.uuid"> <uni-swipe-action>
<view class="card-logo"> <uni-swipe-action-item
<view class="logo-bg"> v-for="(card, index) in bankCards"
<text class="logo-text">{{ getBankLogo(card.open_bank) }}</text> :key="card.uuid"
@change="onSwipeChange($event, card)"
>
<template v-slot:right>
<view class="swipe-right">
<view class="card-delete-btn" @click.stop="handleDeleteBankCard(card)">删除</view>
</view>
</template>
<view class="card-item" :class="{ 'is-swiping': openedSwipeUuid === card.uuid }">
<view class="card-logo">
<image
v-if="getBankLogoImg(card.open_bank)"
class="bank-logo-img"
:src="getBankLogoImg(card.open_bank)"
mode="aspectFit"
></image>
<view v-else class="logo-bg">
<text class="logo-text">{{ getBankLogo(card.open_bank) }}</text>
</view>
</view>
<view class="card-info">
<view class="card-number">尾号{{ card.card_number }}储蓄卡</view>
<view class="bank-name">{{ card.open_bank }}</view>
</view>
</view> </view>
</view> </uni-swipe-action-item>
<view class="card-info"> </uni-swipe-action>
<view class="card-number">尾号{{ card.card_number }}储蓄卡</view>
<view class="bank-name">{{ card.open_bank }}</view>
</view>
</view>
</view> </view>
<empty v-if="bankCards.length === 0"></empty> <view class="empty-container" v-if="bankCards.length === 0">
<image :src="emptyBankImg" class="empty-image"></image>
<text class="empty-text">使用常用卡,支付更快捷</text>
</view>
<!-- <empty v-if="bankCards.length === 0" :imgWidth="'218rpx'" :imgHeight="'164rpx'" :emptyImage="emptyBankImg" emptyDesc="使用常用卡,支付更快捷"></empty> -->
<!-- 底部导航指示器 --> <!-- 底部导航指示器 -->
<view class="bottom-indicator"></view> <view class="bottom-indicator"></view>
@@ -41,27 +69,127 @@ import { ref, onMounted } from 'vue';
import navTo from '@/utils/navTo'; import navTo from '@/utils/navTo';
import api from '@/api/api'; import api from '@/api/api';
import empty from "@/components/empty/empty.vue" import empty from "@/components/empty/empty.vue"
import emptyBankImg from "@/static/empty_bank.png"
const bankCards = ref([]); const bankCards = ref([]);
const openedSwipeUuid = ref('');
import { onLoad,onBackPress,onShow } from '@dcloudio/uni-app';
const from = ref('');
const returnTo = ref('');
onLoad((options) => {
if(options.from){
from.value = options.from;
}
if (options.returnTo) {
returnTo.value = decodeURIComponent(options.returnTo);
}
uni.sendNativeEvent("portraitReport",{
msg:{
event_nickname: "list_page",
page_type: "常用银行卡",
},
});
});
onShow(() => {
getBankCardList();
});
onBackPress(() => {
if(!from.value){
plus.runtime.quit();
return true;
}
});
// 银行卡数据 // 银行卡数据
const getBankCardList = () => { const getBankCardList = () => {
api.bankCardList({}).then(res => { api.bankCardList({}).then(res => {
if (res.code === 200 && res.data.bankList) {
bankCards.value = res.data.bankList; if (res.code === 200 && res.data) {
bankCards.value = res.data;
} }
}); });
}; };
const goBack = () => { const goBack = () => {
uni.navigateBack(); if(!from.value){
plus.runtime.quit();
}else{
uni.navigateBack();
}
}; };
const addBankCard = () => { const addBankCard = () => {
const target = returnTo.value
? `/pages_app/idcardAuth/idcardAuth?returnTo=${encodeURIComponent(returnTo.value)}`
: '/pages_app/idcardAuth/idcardAuth';
navTo({ navTo({
url: '/pages_app/idcardAuth/idcardAuth' url: target
}); });
}; };
const bankLogoMap = {
'工商银行': '/static/icon_bankcard_gongshang.png',
'中国银行': '/static/icon_bankcard_zhongguo.png',
'建设银行': '/static/icon_bankcard_jianseyinhang.png',
'农业银行': '/static/icon_bankcard_nongyeyinhang.png',
'交通银行': '/static/icon_bankcard_jiatong.png',
'招商银行': '/static/icon_bankcard_zhaoshangyinhang.png',
'民生银行': '/static/icon_bankcard_minshneg.png',
'兴业银行': '/static/icon_bankcard_xingye.png',
'浦发银行': '/static/icon_bankcard_shanghaipudongfazhanyinhang.png',
'光大银行': '/static/icon_bankcard_guangda.png',
'华夏银行': '/static/icon_bankcard_huaxiayinhnag.png',
'中信银行': '/static/icon_bankcard_zhongxinshiye.png',
'平安银行': '/static/icon_bankcard_pingan.png',
'广发银行': '/static/icon_bankcard_guangfa.png',
'邮储银行': '/static/icon_bankcard_youzheng.png',
'上海银行': '/static/icon_bankcard_shanghai.png',
'北京银行': '/static/icon_bankcard_beijingyinhang.png'
};
const getBankLogoImg = (bankName) => {
let name1=bankName.split(',')[0];
let name2=bankName.split(',')[1];
return bankLogoMap[name1] || bankLogoMap[name2];
};
const handleDeleteBankCard = (card) => {
uni.showModal({
title: '确认删除',
content: `确定删除尾号${card.card_number}的银行卡吗?`,
success: async (modalRes) => {
if (!modalRes.confirm) return;
try {
const res = await api.deleteBankCard({
uuid: card.uuid
});
if (res.code === 200) {
uni.showToast({
title: '删除成功',
icon: 'none'
});
getBankCardList();
return;
}
uni.showToast({
title: res.msg || '删除失败',
icon: 'none'
});
} catch (error) {
uni.showToast({
title: '网络错误,请重试',
icon: 'none'
});
}
}
});
};
const onSwipeChange = (e, card) => {
const state = typeof e === 'string'
? e
: e?.position || e?.show || e?.detail?.position || e?.detail?.show || 'none';
openedSwipeUuid.value = state === 'right' ? card.uuid : '';
};
// 获取银行logo文字 // 获取银行logo文字
const getBankLogo = (bankName) => { const getBankLogo = (bankName) => {
const bankLogos = { const bankLogos = {
@@ -84,34 +212,96 @@ const getBankLogo = (bankName) => {
return bankLogos[bankName] || bankName.charAt(0); return bankLogos[bankName] || bankName.charAt(0);
}; };
onMounted(() => {
getBankCardList();
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.empty-container {
height:800rpx;
min-width:600rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.empty-image {
width: 218rpx;
height: 164rpx;
}
.empty-text {
margin-top: 20rpx;
font-size: 30rpx;
color: #999;
}
}
.bank-card-page { .bank-card-page {
min-height: 100vh; min-height: 100vh;
background: #F5F5F5; background: #F5F5F5;
position: relative; position: relative;
:deep(.uni-nav-bar-right-text) {
font-size: 32rpx;
}
:deep(.uni-swipe){
margin-bottom: 20rpx!important;
}
} }
/* 银行卡列表样式 */ /* 银行卡列表样式 */
.card-list { .card-list {
top: calc(var(--status-bar-height) + 44px);
position: fixed;
left: 0;
right: 0;
bottom: 0rpx;
overflow-y: scroll;
z-index: 1;
padding: 30rpx; padding: 30rpx;
box-sizing: border-box;
.swipe-right {
height: 100%;
display: flex;
align-items: stretch;
padding-right: 0;
box-sizing: border-box;
}
.card-delete-btn {
min-width: 112rpx;
height: 100%;
padding: 0 24rpx;
border-radius: 0 16rpx 16rpx 0;
background: #E60012;
color: #ffffff;
font-size: 24rpx;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
}
.card-item { .card-item {
background: #ffffff; background: #ffffff;
border-radius: 16rpx; border-radius: 16rpx;
padding: 30rpx; padding: 30rpx;
margin-bottom: 20rpx;
display: flex; display: flex;
align-items: center; align-items: center;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1); box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
&.is-swiping {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.card-logo { .card-logo {
margin-right: 30rpx; margin-right: 30rpx;
.bank-logo-img {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
}
.logo-bg { .logo-bg {
width: 80rpx; width: 80rpx;
height: 80rpx; height: 80rpx;
@@ -145,6 +335,7 @@ onMounted(() => {
color: #666666; color: #666666;
} }
} }
} }
} }
+140 -47
View File
@@ -1,17 +1,20 @@
<template> <template>
<view class="idcard-auth-page"> <view class="idcard-auth-page">
<!-- 顶部导航栏 --> <!-- 顶部导航栏 -->
<uni-nav-bar <view class="navbox">
<view class="status_bar"></view>
<uni-nav-bar
left-icon="left" left-icon="left"
:title="currentStep === 1 ? '身份验证' : '添加银行卡'" :title="currentStep === 1 ? '身份验证' : '添加银行卡'"
@clickLeft="goBack()" @clickLeft="goBack"
fixed
color="#8B2316" color="#8B2316"
height="140rpx"
:border="false" :border="false"
backgroundColor="#eeeeee" backgroundColor="#eeeeee"
> >
</uni-nav-bar> </uni-nav-bar>
</view>
<!-- 内容区域 --> <!-- 内容区域 -->
<view class="content-area"> <view class="content-area">
<!-- 进度指示器 --> <!-- 进度指示器 -->
@@ -65,6 +68,7 @@
class="form-input" class="form-input"
placeholder="请输入您的姓名" placeholder="请输入您的姓名"
v-model="formData.name" v-model="formData.name"
:adjust-position="false"
placeholder-style="color: #cccccc" placeholder-style="color: #cccccc"
/> />
</view> </view>
@@ -73,6 +77,7 @@
<input <input
class="form-input" class="form-input"
placeholder="请输入您的身份证号" placeholder="请输入您的身份证号"
:adjust-position="false"
v-model="formData.idNumber" v-model="formData.idNumber"
placeholder-style="color: #cccccc" placeholder-style="color: #cccccc"
/> />
@@ -85,28 +90,34 @@
<text class="form-label">银行卡号</text> <text class="form-label">银行卡号</text>
<view class="input-container"> <view class="input-container">
<input <input
class="form-input" class="form-input card-number-input"
:class="{ 'is-filled': !!formData.cardNumber }"
placeholder="仅限借记卡" placeholder="仅限借记卡"
v-model="formData.cardNumber" :value="cardNumberDisplay"
:adjust-position="false"
@input="onCardNumberInput"
placeholder-style="color: #cccccc" placeholder-style="color: #cccccc"
/> />
<view class="info-icon">
</view>
<view class="info-icon" @click="showInfo('card')">
<text class="icon-text">!</text> <text class="icon-text">!</text>
</view> </view>
</view>
</view> </view>
<view class="form-item"> <view class="form-item">
<text class="form-label">手机号</text> <text class="form-label">手机号</text>
<view class="input-container"> <view class="input-container">
<input <input
class="form-input" class="form-input card-number-input"
placeholder="银行预留手机号" placeholder="银行预留手机号"
v-model="formData.mobile" v-model="formData.mobile"
:adjust-position="false"
placeholder-style="color: #cccccc" placeholder-style="color: #cccccc"
/> />
<view class="info-icon">
</view>
<view class="info-icon" @click="showInfo('phone')">
<text class="icon-text">!</text> <text class="icon-text">!</text>
</view>
</view> </view>
</view> </view>
</view> </view>
@@ -145,6 +156,18 @@
</view> </view>
</view> </view>
</view> </view>
<unidialog
:visible="cardVisible"
:showCancel="false"
:title="cardTitle"
:content="cardContent"
:showConfirm="true"
:confirmText="'知道了'"
@close="cardVisible=false"
@confirm="cardVisible=false"
>
</unidialog>
</template> </template>
<script setup> <script setup>
@@ -153,7 +176,11 @@ import stepImg from "@/static/add_card_no.png"
import stepActiveImg from "@/static/add_card_yes.png" import stepActiveImg from "@/static/add_card_yes.png"
import navTo from '@/utils/navTo'; import navTo from '@/utils/navTo';
import api from '@/api/api'; import api from '@/api/api';
import unidialog from '@/components/dialog/dialog.vue';
import { onLoad } from '@dcloudio/uni-app';
const cardContent = ref('暂支持以下银行:农业银行、建设银行、光大银行、平安银行、兴业银行、中信银行、邮政储蓄银行、民生银行、中国银行、工商银行、交通银行、浦发银行、广发银行、华夏银行、招商银行、北京银行、上海银行。');
const cardTitle = ref('银行卡说明');
const cardVisible = ref(false);
const formData = ref({ const formData = ref({
name: '', name: '',
idNumber: '', idNumber: '',
@@ -163,13 +190,29 @@ const formData = ref({
const isLoading = ref(false); const isLoading = ref(false);
const currentStep = ref(1); // 当前步骤,1表示身份信息,2表示添加银行卡 const currentStep = ref(1); // 当前步骤,1表示身份信息,2表示添加银行卡
const showInfo = (type) => {
if (type === 'card') {
cardTitle.value = '银行卡说明';
cardContent.value = '暂支持以下银行:农业银行、建设银行、光大银行、平安银行、兴业银行、中信银行、邮政储蓄银行、民生银行、中国银行、工商银行、交通银行、浦发银行、广发银行、华夏银行、招商银行、北京银行、上海银行。';
} else if (type === 'phone') {
cardTitle.value = '手机号说明';
cardContent.value = '银行预留的手机号码是办理银行卡时所填写的手机号码。没有预留,手机号码已经忘记或者已经停用,请联系银行客服更新处理';
}
cardVisible.value = true;
};
// 短信弹框相关 // 短信弹框相关
const showSmsDialog = ref(false); const showSmsDialog = ref(false);
const smsCode = ref(''); const smsCode = ref('');
const sendingCode = ref(false); const sendingCode = ref(false);
const countdown = ref(0); const countdown = ref(0);
let countdownTimer = null; let countdownTimer = null;
const returnTo = ref('');
onLoad((options) => {
if (options.returnTo) {
returnTo.value = decodeURIComponent(options.returnTo);
}
});
const maskedMobile = computed(() => { const maskedMobile = computed(() => {
const m = formData.value.mobile || ''; const m = formData.value.mobile || '';
@@ -178,6 +221,18 @@ const maskedMobile = computed(() => {
} }
return m || '***********'; return m || '***********';
}); });
const formatCardNumber = (value) => {
const digits = String(value || '').replace(/\D/g, '').slice(0, 19);
return digits.replace(/(\d{4})(?=\d)/g, '$1 ');
};
const cardNumberDisplay = computed(() => formatCardNumber(formData.value.cardNumber));
const onCardNumberInput = (e) => {
const value = e?.detail?.value || '';
formData.value.cardNumber = String(value).replace(/\D/g, '').slice(0, 19);
};
const goBack = () => { const goBack = () => {
uni.navigateBack(); uni.navigateBack();
}; };
@@ -246,7 +301,7 @@ const onNextStep = async () => {
} }
} catch (error) { } catch (error) {
console.error('身份验证失败:', error); console.error('身份验证失败:', error);
uni.showToast({ title: '网络错误,请重试', icon: 'none' }); uni.showToast({ title: error.data.message || error.message, icon: 'none' });
isLoading.value = false; isLoading.value = false;
} }
} else if (currentStep.value === 2) { } else if (currentStep.value === 2) {
@@ -296,7 +351,9 @@ const onGetSmsCode = async () => {
} }
}, 1000); }, 1000);
} else { } else {
uni.showToast({ title: res.msg || '短信验证码发送失败', icon: 'none' }); uni.showToast({
title: res.msg || res.data.message, icon: 'none'
});
} }
}; };
@@ -315,9 +372,16 @@ const onConfirmSms = async () => {
}); });
if (res.code === 200) { if (res.code === 200) {
uni.showToast({ title: '银行卡添加成功', icon: 'none' }); uni.showToast({ title: '银行卡添加成功', icon: 'none' });
navTo({ uni.navigateBack();
url: '/pages_app/idcardAuth/bankCardList' // if (returnTo.value) {
}); // uni.redirectTo({
// url: returnTo.value
// });
// } else {
// navTo({
// url: '/pages_app/idcardAuth/bankCardList'
// });
// }
} else { } else {
uni.showToast({ title: res.msg || '银行卡添加失败', icon: 'none' }); uni.showToast({ title: res.msg || '银行卡添加失败', icon: 'none' });
} }
@@ -332,12 +396,17 @@ const onCancelSms = () => {
<style lang="scss" scoped> <style lang="scss" scoped>
.idcard-auth-page { .idcard-auth-page {
min-height: 100vh;
background: #f5f5f5; background: #f5f5f5;
} }
.content-area { .content-area {
padding-top: 160rpx; top: calc(var(--status-bar-height) + 44px);
position: fixed;
left: 0;
right: 0;
overflow-y: scroll;
z-index: 1;
padding: 0rpx 0rpx 0; padding: 0rpx 0rpx 0;
} }
@@ -455,78 +524,98 @@ const onCancelSms = () => {
.form-item { .form-item {
margin-bottom: 30rpx;
display: flex; display: flex;
align-items: center; align-items: center;
position: relative;
border-bottom: 2rpx solid #eee; border-bottom: 2rpx solid #eee;
padding: 0 30rpx 20rpx; height: 92rpx;
padding: 0 30rpx;
box-sizing: border-box;
&:last-child { &:last-child {
margin-bottom: 0; margin-bottom: 0;
padding-bottom: 0;
} }
.form-label { .form-label {
display: block; display: flex;
align-items: center;
justify-content: flex-end;
height: 80rpx;
font-size: 28rpx; font-size: 28rpx;
color: #000000; color: #000000;
width: 120rpx; width: 120rpx;
font-weight: 500; font-weight: 500;
text-align: right;
text-align-last: auto;
letter-spacing: 0;
white-space: nowrap;
margin-right: 10rpx;
} }
.form-input { .form-input {
flex: 1; flex: 1;
height: 80rpx; height: 80rpx;
min-height: 80rpx;
line-height: 80rpx;
padding: 0 20rpx; padding: 0 20rpx;
font-size: 28rpx; font-size: 28rpx;
background: #ffffff; background: #ffffff;
border: none; border: none;
outline: none; outline: none;
box-sizing: border-box;
&:focus { &:focus {
border-color: #8B2316; border-color: #8B2316;
} }
} }
.info-icon {
position: absolute;
right: 30rpx;
top: 50%;
transform: translateY(-50%);
width: 40rpx;
height: 40rpx;
border-radius: 50%;
background: #fff;
display: flex;
align-items: center;
justify-content: center;
border: 2rpx solid #8B2316;
.icon-text {
font-size: 20rpx;
color: #8B2316;
font-weight: bold;
}
}
.input-container { .input-container {
display: flex; display: flex;
align-items: center; align-items: center;
position: relative; position: relative;
width: 80%; height: 80rpx;
flex: 1;
padding-right: 56rpx;
box-sizing: border-box;
.form-input { .form-input {
flex: 9; flex: 1;
height: 80rpx; height: 100%;
padding: 0 20rpx; min-height: 100%;
line-height: 80rpx;
padding: 0;
font-size: 28rpx; font-size: 28rpx;
background: #ffffff; background: #ffffff;
border: none; border: none;
border-radius: 0; border-radius: 0;
outline: none; outline: none;
box-sizing: border-box;
&:focus { &.card-number-input.is-filled {
/* 去除边框后无需变更边框颜色 */ font-weight: normal;
} }
} }
.info-icon {
flex:1;
position: absolute;
right: 20rpx;
width: 40rpx;
height: 40rpx;
border-radius: 50%;
background: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
.icon-text {
font-size: 20rpx;
color: #cccccc;
font-weight: bold;
}
}
} }
} }
} }
@@ -644,6 +733,10 @@ const onCancelSms = () => {
background: #eeeeee; background: #eeeeee;
color: #8B2316; color: #8B2316;
font-size: 26rpx; font-size: 26rpx;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
} }
.sms-actions { .sms-actions {
File diff suppressed because it is too large Load Diff
+65 -32
View File
@@ -11,14 +11,14 @@
<up-image :src="logo" width="164rpx" height="167rpx" ></up-image> <up-image :src="logo" width="164rpx" height="167rpx" ></up-image>
</view> </view>
<text class="app-name">专家端</text> <text class="app-name">专家版</text>
</view> </view>
<!-- 手机号显示 --> <!-- 手机号显示 -->
<!-- #ifdef APP --> <!-- #ifdef APP -->
<view class="phone-display"> <!-- <view class="phone-display">
<text class="phone-number">176****8628</text> <text class="phone-number">176****8628</text>
</view> </view> -->
<!-- 登录按钮 --> <!-- 登录按钮 -->
<view class="login-button" @click="onOneClickLogin"> <view class="login-button" @click="onOneClickLogin">
@@ -129,34 +129,16 @@
}; };
// 一键登录 // 一键登录
const onOneClickLogin = () => { const onOneClickLogin = () => {
if (!isAgreed.value) { const res = checkUniverifyLogin()
uni.showToast({ if(res){
title: '请先同意相关协议', uni.redirectTo({
icon: 'none' url: '/pages/univerify/univerify'
});
}else{
uni.redirectTo({
url: '/pages_app/smsLogin/smsLogin'
}); });
return;
} }
console.log('一键登录');
uni.showLoading({
title: '登录中2...'
});
// 模拟登录过程
setTimeout(() => {
uni.hideLoading();
uni.showToast({
title: '登录成功',
icon: 'none'
});
// 跳转到首页
setTimeout(() => {
uni.switchTab({
url: '/pages/index/index'
});
}, 1500);
}, 2000);
}; };
// 短信验证码登录 // 短信验证码登录
@@ -179,7 +161,7 @@
// #ifdef APP // #ifdef APP
uni.showToast({ uni.showToast({
title: '微信登录 11', title: '微信登录',
icon: 'none' icon: 'none'
}); });
// #endif // #endif
@@ -235,9 +217,10 @@
uni.setStorageSync('DEV_AUTH_YX_TOKEN_App', res.data.YX_token); uni.setStorageSync('DEV_AUTH_YX_TOKEN_App', res.data.YX_token);
uni.setStorageSync('userInfo', res.data); uni.setStorageSync('userInfo', res.data);
} }
const app = getApp()
//app.initNim({appkey: "885dea390870814acf3ba8558c717572", account:result.data.YX_accid, token: result.data.YX_token })
// 跳转到首页 // 跳转到首页
navTo({ uni.redirectTo({
url: '/pages/index/index' url: '/pages/index/index'
}) })
}).catch(err=>{ }).catch(err=>{
@@ -339,11 +322,61 @@
url:'/pages/index/index' url:'/pages/index/index'
}) })
} }
const univerify = ref(1)
// 页面加载 // 页面加载
onLoad(() => { onLoad(() => {
console.log('登录页面加载完成'); console.log('登录页面加载完成');
onLoad((options) => {
if(options.univerify){
univerify.value = options.univerify // 已支持 App-Plus
}
})
const res = checkUniverifyLogin()
if(res){
uni.redirectTo({
url: '/pages/univerify/univerify'
});
}else{
uni.redirectTo({
url: '/pages_app/smsLogin/smsLogin'
});
}
}); });
const checkUniverifyLogin=()=>{
let res = false
// #ifdef APP
uni.getProvider({
service: 'oauth',
success: function (res) {
console.log(res.provider)// ['qq', 'univerify']
if(res.provider.indexOf('univerify') !== -1){
uni.preLogin({
provider: 'univerify',
success(){ //预登录成功
// 显示一键登录选项
if(univerify.value == 1){
return true
}
},
fail(res){ // 预登录失败
// 不显示一键登录选项(或置灰)
// 根据错误信息判断失败原因,如有需要可将错误提交给统计服务器
console.log(res.errCode)
console.log(res.errMsg)
return false
}
})
}else{
return false
}
}
});
// #endif
return res
}
// 页面显示 // 页面显示
onShow(() => { onShow(() => {
// #ifdef APP // #ifdef APP
+582
View File
@@ -0,0 +1,582 @@
<template>
<view class="navbox">
<view class="status_bar"></view>
<uni-nav-bar
left-icon="left"
title="过往会议"
@clickLeft="goBack"
color="#8B2316"
:border="false"
backgroundColor="#eee"
>
<template #right>
<view class="nav-right">
<uni-icons type="search" size="24" color="#8B2316" @click="searchMeetHistroy"></uni-icons>
</view>
</template>
</uni-nav-bar>
</view>
<view class="page">
<!-- 筛选标签栏 -->
<view class="filter-bar">
<view class="filter-item" :class="{active:monthName}" @click="showTimePopup">
<text>{{ monthName?monthName:'会议年份' }}</text>
<up-image :src="monthName?selectOn:select" width="26rpx" height="26rpx"></up-image>
</view>
<view class="filter-divider"></view>
<view class="filter-item" :class="{active:typeName}" @click="showLocationPopup">
<text>{{ typeName?typeName:'会议月份' }}</text>
<up-image :src="typeName?selectOn:select" width="26rpx" height="26rpx"></up-image>
</view>
</view>
<!-- 可滚动内容区域 -->
<scroll-view
class="scroll-content"
scroll-y="true"
refresher-enabled="true"
:refresher-triggered="isRefreshing"
:refresher-threshold="100"
@refresherrefresh="onRefresh"
@scrolltolower="onLoadMore"
:lower-threshold="100"
>
<!-- 会议列表 -->
<view class="meeting-list" v-if="meetingList.length > 0">
<view
class="meeting-item" @click="goDetail(meeting)"
v-for="(meeting, index) in meetingList"
:key="meeting.id || index"
>
<view class="meeting-content">
<text class="meeting-title">{{
meeting.title || meeting.name
}}</text>
<view class="meeting-info">
<view class="meeting-time">
<view class="timebox">
<up-image
:src="timeImg"
width="24rpx"
height="24rpx"
></up-image>
</view>
<view class="text"> {{ formatDate(meeting.begin_date) }}</view>
</view>
<view class="meeting-location">
<uni-icons type="location" size="14" color="#999"></uni-icons>
{{ meeting.location }}
</view>
</view>
</view>
</view>
</view>
<!-- 空状态 -->
<view class="empty-state" v-else-if="!isRefreshing">
<empty></empty>
</view>
<!-- 加载更多提示 -->
<view class="load-more" v-if="showLoadMore">
<view class="load-more-content" v-if="isLoadingMore">
<uni-icons type="spinner-cycle" size="20" color="#999"></uni-icons>
<text class="load-more-text">加载中...</text>
</view>
<view
class="load-more-content"
v-else-if="hasMoreData && meetingList.length > 0"
>
<text class="load-more-text">上拉加载更多</text>
</view>
<view
class="load-more-content"
v-else-if="meetingList.length > 0 && !isLoadingMore"
>
<text class="load-more-text">没有更多数据了</text>
</view>
</view>
</scroll-view>
<!-- 时间选择弹窗 -->
<view class="time-popup" v-if="isTimePopupShow" @click="hideTimePopup">
<view class="popup-mask"></view>
<view class="time-popup-content" @click.stop>
<view class="time-list">
<view
class="time-item"
v-for="(month, index) in monthList"
:key="index"
:class="{ active: selectedMonth === month.value }"
@click="selectMonth(month)"
>
<text>{{ month.label }}</text>
</view>
</view>
</view>
</view>
<!-- 地区选择弹窗 -->
<view
class="time-popup"
v-if="isLocationPopupShow"
@click="hideLocationPopup"
>
<view class="popup-mask"></view>
<view class="time-popup-content" @click.stop>
<view class="time-list">
<view
class="time-item"
v-for="(location, index) in locationList"
:key="index"
:class="{ active: selectedType === location.code }"
@click="selectLocation(location)"
>
<text>{{ location.name }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { onShow,onLoad,onHide} from "@dcloudio/uni-app";
import api from "@/api/api.js";
import navBar from "@/components/navBar/navBar.vue";
import select from "@/static/triangle_normal.png";
import selectOn from "@/static/triangle_green_theme.png"
import dayjs from "dayjs";
import timeImg from "@/static/play_long.png"
import navTo from "@/utils/navTo";
import empty from '@/components/empty/empty.vue';
// 弹窗状态
const isTimePopupShow = ref(false);
const isLocationPopupShow = ref(false);
const selectedMonth = ref("");
const monthName = ref("");
const selectedType = ref("");
const typeName = ref("");
// 下拉刷新和上拉加载状态
const isRefreshing = ref(false);
const isLoadingMore = ref(false);
const hasMoreData = ref(true);
const showLoadMore = ref(true);
const currentPage = ref(1);
const pageSize = ref(10);
// 会议列表数据
const meetingList = ref([]);
const goBack=()=>{
uni.navigateBack({
fail:()=>{
uni.redirectTo({
url:'/pages/index/index'
})
}
});
}
onShow(() => {
});
onHide(() => {
});
// 年份数据
const monthList = ref([]);
const generateYearList = () => {
const years = [];
const currentDate = new Date();
const currentYear = currentDate.getFullYear();
// 从今年到过去10年
for (let i = 0; i < 10; i++) {
const year = currentYear - i;
years.push({
value: year.toString(),
label: `${year}年`,
year: year,
});
}
monthList.value = years;
console.log("生成年份列表:", monthList.value);
};
// 月份数据
const locationList = ref([]);
// 生成月份列表
const generateMonthList = () => {
const months = [];
const currentDate = new Date();
const currentYear = currentDate.getFullYear();
const currentMonth = currentDate.getMonth() + 1; // 当前月份(1-12)
// 如果选择的是今年,只显示到当前月份
if (selectedMonth.value && selectedMonth.value === currentYear.toString()) {
for (let i = 1; i <= currentMonth; i++) {
months.push({
code: i,
name: `${i}月`,
});
}
} else {
// 其他年份显示完整的12个月
for (let i = 1; i <= 12; i++) {
months.push({
code: i,
name: `${i}月`,
});
}
}
locationList.value = months;
console.log("生成月份列表:", locationList.value);
};
// 页面加载
onMounted(() => {
generateYearList();
generateMonthList();
});
const searchMeetHistroy=()=>{
navTo({
url:'/pages_app/meetHistroySearch/meetHistroySearch'
})
}
onLoad(() => {
currentPage.value = 1;
hasMoreData.value = true;
loadMeetingData();
});
// 加载会议数据
const loadMeetingData = async (isRefresh = false) => {
if (isLoadingMore.value && !isRefresh) return;
if (isRefresh) {
isRefreshing.value = true;
} else {
isLoadingMore.value = true;
}
try {
const params = {
page: currentPage.value,
pageSize: pageSize.value,
year: selectedMonth.value,
title: "",
month: selectedType.value,
};
console.log("获取会议列表参数:", params);
const response = await api.meetingHistoryList(params);
if (response && response.code === 200) {
const { list, totalPage, pageNumber } = response.data || {};
if (isRefresh) {
meetingList.value = list || [];
currentPage.value = 1;
} else {
meetingList.value = [...meetingList.value, ...(list || [])];
}
hasMoreData.value = totalPage > pageNumber;
showLoadMore.value = meetingList.value.length > 0;
}
} catch (error) {
console.error("加载会议数据失败:", error);
} finally {
isRefreshing.value = false;
isLoadingMore.value = false;
}
};
// 下拉刷新
const onRefresh = () => {
currentPage.value = 1;
hasMoreData.value = true;
loadMeetingData(true);
};
// 上拉加载更多
const onLoadMore = () => {
if (!hasMoreData.value || isLoadingMore.value) return;
currentPage.value++;
loadMeetingData();
};
// 显示时间选择弹窗
const showTimePopup = () => {
isTimePopupShow.value = !isTimePopupShow.value;
};
// 隐藏时间选择弹窗
const hideTimePopup = () => {
isTimePopupShow.value = false;
};
// 选择年份
const selectMonth = (month) => {
selectedMonth.value = month.value;
monthName.value = month.label;
console.log("选择年份:", month.label);
// 选择年份后重新生成月份列表
generateMonthList();
// 重置月份选择
selectedType.value = "";
// 选择年份后重新加载数据
loadMeetingData(true);
hideTimePopup();
};
// 显示地区选择弹窗
const showLocationPopup = () => {
if (selectedMonth.value === "") {
uni.showToast({
title: "请先选择年份",
icon: "none",
});
return;
}
isLocationPopupShow.value = !isLocationPopupShow.value;
};
// 隐藏地区选择弹窗
const hideLocationPopup = () => {
isLocationPopupShow.value = false;
};
// 选择月份
const selectLocation = (location) => {
selectedType.value = location.code;
typeName.value = location.name;
console.log("选择月份:", location.name);
// 选择月份后重新加载数据
loadMeetingData(true);
hideLocationPopup();
};
// 格式化日期
const formatDate = (dateStr) => {
if (!dateStr) return "";
return dayjs(dateStr).format("YYYY.MM.DD");
};
const goDetail=(item)=>{
const encoded = encodeURIComponent(item.path);
let imgPath=encodeURIComponent('https://doc.igandan.com/app/html/img/2016/20160714132557.png');
uni.navigateTo({
url: `/pages_app/webview/webview?url=${encoded}&sharetitle=${item.title}&bg=1&type=live_old&imgPath=${imgPath}&share=1&title=会议详情`
});
}
</script>
<style scoped lang="scss">
$white: #fff;
$gray: #999;
$gray-dark: #666;
$gray-light: #eee;
$border-color: #e0e0e0;
$theme-color: #8b2316;
// .nav-right{
// margin-top: 50rpx;
// }
.page {
background-color: #f5f5f5;
height: 100vh;
display: flex;
flex-direction: column;
}
// 筛选标签栏
.filter-bar {
position: fixed;
top: calc(var(--status-bar-height) + 44px);
width: 100%;
z-index: 2;
background-color: $white;
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
padding: 0 30rpx;
border-bottom: 2rpx solid $gray-light;
.filter-item {
display: flex;
align-items: center;
justify-content: center;
padding: 0 20rpx;
font-size: 28rpx;
color:$gray-dark;
text {
margin-right: 10rpx;
}
&.active {
color: #8B2316;
}
}
.filter-divider {
width: 2rpx;
height: 32rpx;
background-color: $border-color;
margin: 0 60rpx;
}
}
// 滚动内容区域
.scroll-content {
position: fixed;
top: calc(var(--status-bar-height) + 44px + 88rpx);
left: 0;
right: 0;
bottom: 0;
background-color: $white;
}
// 会议列表
.meeting-list {
padding: 0 30rpx;
}
.meeting-item {
padding: 30rpx 0;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
}
.meeting-content {
.meeting-title {
font-size: 32rpx;
color: #333;
line-height: 1.4;
margin-bottom: 16rpx;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
}
.meeting-info {
display: flex;
justify-content: space-between;
gap: 8rpx;
.meeting-time,
.meeting-location {
.timebox{
margin-top: -4rpx;
margin-right: 8rpx;
}
display: flex;
align-items: center;
font-size: 24rpx;
color: #999;
}
}
}
// 空状态
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
.empty-icon {
margin-bottom: 20rpx;
}
.empty-text {
color: #999;
font-size: 28rpx;
}
}
// 加载更多
.load-more {
padding: 30rpx 0;
display: flex;
justify-content: center;
.load-more-content {
display: flex;
align-items: center;
gap: 10rpx;
.load-more-text {
font-size: 24rpx;
color: #999;
}
}
}
// 时间选择弹窗
.time-popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
display: flex;
.popup-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.time-popup-content {
margin-top: calc(var(--status-bar-height) + 44px + 90rpx);;
position: relative;
background-color: $white;
width: 100%;
height: calc(100vh - calc(var(--status-bar-height) - 44px - 88rpx));
.time-list {
padding: 0;
height: 100%;
overflow-y: auto;
.time-item {
padding: 25rpx 60rpx;
font-size: 32rpx;
color: #666;
border-bottom: 2rpx solid #f0f0f0;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
&:hover {
background-color: #f8f8f8;
}
&.active {
background-color: #f8f8f8;
color: $theme-color;
}
}
}
}
}
</style>
@@ -0,0 +1,493 @@
<template>
<view class="page">
<!-- 头部搜索栏 -->
<view class="navbox">
<view class="status_bar"></view>
<view class="search-header ">
<view class="back-icon" @click="goBack">
<uni-icons type="left" size="30" color="#8B2316"></uni-icons>
</view>
<view class="search-input-wrapper">
<view class="search-icon">
<uni-icons type="search" size="20" color="#999"></uni-icons>
</view>
<input
class="search-input"
placeholder="搜索会议关键字"
v-model="searchKeyword"
@input="onSearchInput"
@confirm="onSearchConfirm"
confirm-type="search"
/>
<view class="clear-icon" v-if="searchKeyword" @click="clearSearch">
<uni-icons type="clear" size="20" color="#999"></uni-icons>
</view>
</view>
<view class="cancel-btn" @click="goSearch">
<text class="cancel-text">搜索</text>
</view>
</view>
</view>
<!-- 可滚动内容区域 -->
<scroll-view
class="scroll-content"
scroll-y="true"
refresher-enabled="true"
:refresher-triggered="isRefreshing"
:refresher-threshold="100"
@refresherrefresh="onRefresh"
@scrolltolower="onLoadMore"
:lower-threshold="100"
>
<!-- 会议列表 -->
<view class="meeting-list" v-if="meetingList.length > 0">
<view
class="meeting-item" @click="goDetail(meeting)"
v-for="(meeting, index) in meetingList"
:key="meeting.id || index"
>
<view class="meeting-content">
<text class="meeting-title">{{
meeting.title || meeting.name
}}</text>
<view class="meeting-info">
<view class="meeting-time">
<view class="timebox">
<up-image
:src="timeImg"
width="24rpx"
height="24rpx"
></up-image>
</view>
<view class="text" >{{ formatDate(meeting.begin_date,'YYYY.MM.DD')}}</view>
<view class="text" v-if="formatDate(meeting.end_date,'YYYY.MM.DD')!=formatDate(meeting.begin_date,'YYYY.MM.DD')">{{ '-'+formatDate(meeting.end_date,'YYYY.MM.DD')}}</view>
</view>
<view class="meeting-location">
<uni-icons type="location" size="14" color="#999"></uni-icons>
{{ meeting.location }}
</view>
</view>
</view>
</view>
</view>
<!-- 空状态 -->
<view class="empty-state" v-else-if="!isRefreshing">
<empty></empty>
</view>
<!-- 加载更多提示 -->
<view class="load-more" v-if="showLoadMore">
<view class="load-more-content" v-if="isLoadingMore">
<uni-icons type="spinner-cycle" size="20" color="#999"></uni-icons>
<text class="load-more-text">加载中...</text>
</view>
<view
class="load-more-content"
v-else-if="hasMoreData && meetingList.length > 0"
>
<text class="load-more-text">上拉加载更多</text>
</view>
<view
class="load-more-content"
v-else-if="meetingList.length > 0 && !isLoadingMore"
>
<text class="load-more-text">没有更多数据了</text>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { onShow } from "@dcloudio/uni-app";
import api from "@/api/api.js";
import navBar from "@/components/navBar/navBar.vue";
import select from "@/static/triangle_normal.png";
import dayjs from "dayjs";
import timeImg from "@/static/play_long.png"
import empty from "@/components/empty/empty.vue";
const searchKeyword = ref('');
// 下拉刷新和上拉加载状态
const isRefreshing = ref(false);
const isLoadingMore = ref(false);
const hasMoreData = ref(true);
const showLoadMore = ref(true);
const currentPage = ref(1);
const pageSize = ref(10);
// 会议列表数据
const meetingList = ref([]);
const goSearch=()=>{
if(!searchKeyword.value){
uni.showToast({
title: '请输入搜索内容',
icon: 'none'
});
return;
}
currentPage.value = 1;
hasMoreData.value = true;
meetingList.value = [];
loadMeetingData();
}
const goBack=()=>{
uni.navigateBack({
fail:()=>{
uni.redirectTo({
url:'/pages/index/index'
})
}
});
}
// 加载会议数据
const loadMeetingData = async (isRefresh = false) => {
if (isLoadingMore.value && !isRefresh) return;
if (isRefresh) {
isRefreshing.value = true;
} else {
isLoadingMore.value = true;
}
try {
const params = {
page: currentPage.value,
pageSize: pageSize.value,
year:'',
title: searchKeyword.value,
month: '',
};
console.log("获取会议列表参数:", params);
const response = await api.meetingHistoryList(params);
if (response && response.code === 200) {
const { list, totalPage, pageNumber } = response.data || {};
if (isRefresh) {
meetingList.value = list || [];
currentPage.value = 1;
} else {
meetingList.value = [...meetingList.value, ...(list || [])];
}
console.log("会议列表数据:", meetingList.value);
hasMoreData.value = totalPage > pageNumber;
showLoadMore.value = meetingList.value.length > 0;
}
} catch (error) {
console.error("加载会议数据失败:", error);
} finally {
isRefreshing.value = false;
isLoadingMore.value = false;
}
};
// 下拉刷新
const onRefresh = () => {
currentPage.value = 1;
hasMoreData.value = true;
loadMeetingData(true);
};
// 上拉加载更多
const onLoadMore = () => {
if (!hasMoreData.value || isLoadingMore.value) return;
currentPage.value++;
loadMeetingData();
};
// 显示时间选择弹窗
const showTimePopup = () => {
isTimePopupShow.value = !isTimePopupShow.value;
};
// 隐藏时间选择弹窗
const hideTimePopup = () => {
isTimePopupShow.value = false;
};
// 选择年份
const selectMonth = (month) => {
selectedMonth.value = month.value;
console.log("选择年份:", month.label);
// 选择年份后重新生成月份列表
generateMonthList();
// 重置月份选择
selectedType.value = "";
// 选择年份后重新加载数据
loadMeetingData(true);
hideTimePopup();
};
// 显示地区选择弹窗
const showLocationPopup = () => {
if (selectedMonth.value === "") {
uni.showToast({
title: "请先选择年份",
icon: "none",
});
return;
}
isLocationPopupShow.value = !isLocationPopupShow.value;
};
// 隐藏地区选择弹窗
const hideLocationPopup = () => {
isLocationPopupShow.value = false;
};
// 选择月份
const selectLocation = (location) => {
selectedType.value = location.code;
console.log("选择月份:", location.name);
// 选择月份后重新加载数据
loadMeetingData(true);
hideLocationPopup();
};
// 格式化日期
const formatDate = (dateStr) => {
if (!dateStr) return "";
return dayjs(dateStr).format("YYYY.MM.DD");
};
const goDetail=(item)=>{
const encoded = encodeURIComponent(item.path);
let imgPath=encodeURIComponent('https://doc.igandan.com/app/html/img/2016/20160714132557.png');
uni.navigateTo({
url: `/pages_app/webview/webview?url=${encoded}&sharetitle=${item.title}&bg=1&type=live_old&imgPath=${imgPath}&share=1&title=会议详情`
});
}
</script>
<style scoped lang="scss">
$white: #fff;
$gray: #999;
$gray-dark: #666;
$gray-light: #eee;
$border-color: #e0e0e0;
$theme-color: #8b2316;
.page {
background-color: #f5f5f5;
height: 100vh;
display: flex;
flex-direction: column;
}
// 头部搜索栏
.search-header {
background-color: #eeeeee;
display: flex;
height:44px;
align-items: center;
padding:0 30rpx;
gap: 20rpx;
.search-input-wrapper {
flex: 1;
display: flex;
align-items: center;
background-color: #fff;
border-radius: 20rpx;
padding: 0 20rpx;
height:70rpx;
.search-icon {
margin-right: 15rpx;
}
.search-input {
flex: 1;
height: 80rpx;
font-size: 28rpx;
color: #333;
}
.clear-icon {
margin-left: 15rpx;
padding: 10rpx;
}
}
.cancel-btn {
padding: 10rpx 20rpx;
background:#8B2316;
border-radius: 10rpx;
.cancel-text {
font-size: 28rpx;
color: #fff;
}
}
}
// 滚动内容区域
.scroll-content {
position: fixed;
top: calc(var(--status-bar-height) + 44px);
left: 0;
right: 0;
bottom: 0;
background-color: $white;
}
// 会议列表
.meeting-list {
padding: 0 30rpx;
}
.meeting-item {
padding: 30rpx 0;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
}
.meeting-content {
.meeting-title {
font-size: 32rpx;
color: #333;
line-height: 1.4;
margin-bottom: 16rpx;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
}
.meeting-info {
display: flex;
justify-content: space-between;
gap: 8rpx;
.meeting-time,
.meeting-location {
.timebox{
margin-top: -4rpx;
margin-right: 8rpx;
}
display: flex;
align-items: center;
font-size: 24rpx;
color: #999;
}
}
}
// 空状态
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
.empty-icon {
margin-bottom: 20rpx;
}
.empty-text {
color: #999;
font-size: 28rpx;
}
}
// 加载更多
.load-more {
padding: 30rpx 0;
display: flex;
justify-content: center;
.load-more-content {
display: flex;
align-items: center;
gap: 10rpx;
.load-more-text {
font-size: 24rpx;
color: #999;
}
}
}
// 时间选择弹窗
.time-popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
display: flex;
.popup-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.time-popup-content {
margin-top: 271rpx;
position: relative;
background-color: $white;
width: 100%;
height: calc(100vh - 275rpx);
.time-list {
padding: 0;
height: 100%;
overflow-y: auto;
.time-item {
padding: 25rpx 60rpx;
font-size: 32rpx;
color: #666;
border-bottom: 2rpx solid #f0f0f0;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
&:hover {
background-color: #f8f8f8;
}
&.active {
background-color: #f8f8f8;
color: $theme-color;
}
}
}
}
}
</style>
+291 -83
View File
@@ -1,47 +1,53 @@
<template> <template>
<!-- 顶部导航栏 --> <!-- 顶部导航栏 -->
<uni-nav-bar <view class="navbox">
left-icon="left" <view class="status_bar"></view>
title="消息" <uni-nav-bar
right-text="清除" left-icon="left"
@clickRight="clearMsg" :title="'消息'"
@clickLeft="goBack" @clickLeft="goBack"
fixed color="#8B2316"
color="#8B2316" :border="false"
height="140rpx" backgroundColor="#eeeeee"
:border="false" >
backgroundColor="#eeeeee" <template v-slot:right>
></uni-nav-bar> <view class="nav-right" >
<view class="collect-img" @click="clearMsg" >
<image class="img-icon" :src="clearImg" />
</view>
</view>
</template>
</uni-nav-bar>
</view>
<view class="msg-page"> <view class="msg-page">
<!-- 四宫格入口 --> <!-- 四宫格入口 -->
<view class="shortcut-grid"> <view class="shortcut-grid">
<view class="grid-item" @click="goBenefit"> <view class="grid-item" @click="goBenefit">
<view class="icon-wrap gift"> <view class="icon-wrap">
<uni-icons type="gift" size="34" color="#fff"></uni-icons> <image :src="fuliImg" class="imgcell"/>
<view class="badge" v-if="badgeData.Module_Welfare > 0">{{ badgeData.Module_Welfare }}</view> <view class="badge" v-if="badgeData.Module_Welfare > 0">{{ badgeData.Module_Welfare }}</view>
</view> </view>
<text class="label">福利</text> <text class="label">福利</text>
</view> </view>
<view class="grid-item" @click="goOrder"> <view class="grid-item" @click="goOrder">
<view class="icon-wrap order"> <view class="icon-wrap ">
<uni-icons type="list" size="34" color="#fff"></uni-icons> <image :src="orderImg" class="imgcell"/>
<view class="badge" v-if="badgeData.Module_Order > 0">{{ badgeData.Module_Order }}</view> <view class="badge" v-if="badgeData.Module_Order > 0">{{ badgeData.Module_Order }}</view>
</view> </view>
<text class="label">订单</text> <text class="label">订单</text>
</view> </view>
<view class="grid-item" @click="goFollow"> <view class="grid-item" @click="goFollow">
<view class="icon-wrap visit"> <view class="icon-wrap">
<uni-icons type="heart" size="34" color="#fff"></uni-icons> <image :src="followImg" class="imgcell"/>
<view class="badge" v-if="badgeData.Module_Relation > 0">{{ badgeData.Module_Relation }}</view> <view class="badge" v-if="badgeData.Module_Relation > 0">{{ badgeData.Module_Relation }}</view>
</view> </view>
<text class="label">随访</text> <text class="label">随访</text>
</view> </view>
<view class="grid-item" @click="goReply"> <view class="grid-item" @click="goReply">
<view class="icon-wrap reply"> <view class="icon-wrap">
<uni-icons type="chatbubble" size="34" color="#fff"></uni-icons> <image :src="replyImg" class="imgcell"/>
<view class="badge" v-if="badgeData.Module_Comment > 0">{{ badgeData.Module_Comment }}</view> <view class="badge" v-if="badgeData.Module_Comment > 0">{{ badgeData.Module_Comment }}</view>
</view> </view>
<text class="label">回复我的</text> <text class="label">回复我的</text>
@@ -53,15 +59,15 @@
class="msg-list" class="msg-list"
scroll-y="true" scroll-y="true"
:show-scrollbar="false" :show-scrollbar="false"
refresher-enabled="true" :refresher-enabled="true"
:refresher-triggered="refreshing" :refresher-triggered="refreshing"
@refresherrefresh="onRefresh" @refresherrefresh="onRefresh"
@scrolltolower="onLoadMore" @scrolltolower="onLoadMore"
lower-threshold="80" :lower-threshold="100"
> >
<!-- 回复我的模块 - 聊天列表样式 --> <!-- 回复我的模块 - 聊天列表样式 -->
<view v-if="currentModule === 4" class="chat-list"> <view v-if="currentModule === 4" class="chat-list">
<view class="chat-item" v-for="(msg, idx) in msgList" :key="msg.id"> <view class="chat-item" v-for="(msg, idx) in msgList" :key="msg.id" @click="goMsgDetail(msg)">
<view class="avatar"> <view class="avatar">
<image :src="msg.avatar || '/static/default-avatar.png'" mode="aspectFill" @error="handleImageError"></image> <image :src="msg.avatar || '/static/default-avatar.png'" mode="aspectFill" @error="handleImageError"></image>
</view> </view>
@@ -77,24 +83,28 @@
<!-- 其他模块 - 卡片样式 --> <!-- 其他模块 - 卡片样式 -->
<view v-else> <view v-else>
<view class="card" v-for="(msg, idx) in msgList" :key="msg.id"> <view class="card-cell" v-for="(msg, idx) in msgList" :key="msg.id" @click="goMsgDetail(msg)">
<view class="card-title">{{ msg.title }}</view>
<view class="card-content">{{ msg.content }}</view>
<view class="card-time">{{ msg.create_date }}</view> <view class="card-time">{{ msg.create_date }}</view>
<view class="card" >
<view class="card-title" :class="{'active':msg.is_read==0}">{{ msg.title }}</view>
<view class="card-content">{{ msg.content }}</view>
</view>
</view> </view>
</view> </view>
<!-- 空状态 --> <!-- 空状态 -->
<view v-if="msgList.length === 0" class="empty-state"> <view v-if="msgList.length === 0 && !loading && !refreshing" class="empty-state">
<text class="empty-text">暂无消息</text> <empty></empty>
</view> </view>
<!-- 加载更多提示 --> <!-- 加载更多提示 -->
<view v-if="loading" class="loading-more"> <view v-if="loading && msgList.length > 0" class="loading-more">
<view class="loading-spinner"></view>
<text class="loading-text">加载中...</text> <text class="loading-text">加载中...</text>
</view> </view>
<!-- 没有更多数据提示 --> <!-- 没有更多数据提示 -->
<view v-if="noMore" class="no-more"> <view v-if="noMore && msgList.length > 0 && !loading" class="no-more">
<text class="no-more-text">没有更多数据了</text> <text class="no-more-text">— 没有更多数据了 —</text>
</view> </view>
</scroll-view> </scroll-view>
</view> </view>
@@ -104,6 +114,18 @@
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import api from '@/api/api.js'; import api from '@/api/api.js';
import docUrl from '@/utils/docUrl'; import docUrl from '@/utils/docUrl';
import navTo from '@/utils/navTo';
import clearImg from '@/static/clear_message.png';
import empty from '@/components/empty/empty.vue';
import fuliImg from '@/static/fu_message_big.png';
import orderImg from '@/static/ding_message_big.png';
import followImg from '@/static/sui_message_big.png';
import replyImg from '@/static/hui_message_big.png';
import {onBackPress} from '@dcloudio/uni-app';
onBackPress(() => {
plus.runtime.quit();
return true;
});
// 角标数据 // 角标数据
const badgeData = ref({ const badgeData = ref({
@@ -122,81 +144,183 @@
const loading = ref(false); const loading = ref(false);
const noMore = ref(false); const noMore = ref(false);
const page = ref(1); const page = ref(1);
const pageSize = ref(10); const pageSize = 10;
// 重置分页状态
const resetPagination = () => {
page.value = 1;
noMore.value = false;
msgList.value = [];
};
const goBack = () => { const goBack = () => {
uni.navigateBack({ plus.runtime.quit();
fail() { };
uni.redirectTo({
url: '/pages/index/index' // 切换模块
const switchModule = (module) => {
if (currentModule.value === module && msgList.value.length > 0) return; // 避免重复加载
currentModule.value = module;
resetPagination();
getAppMesageList(true);
};
const goBenefit = () => switchModule(1);
const goOrder = () => switchModule(2);
const goFollow = () => switchModule(3);
const goReply = () => switchModule(4);
const readMsg = (id) => {
api.appMesageRead({
id: id
}).then(res => {
if(res.code==200){
msgList.value.forEach(item => {
if(item.id==id){
uni.sendNativeEvent("portraitReport",{
msg:{
event_nickname: "read_message",
},
},
(ret) => {
console.log(ret);
});
item.is_read=1;
}
}); });
} }
}); });
}; }
const goBenefit = () => {
currentModule.value = 1;
getAppMesageList(1);
};
const goOrder = () => {
currentModule.value = 2;
getAppMesageList(2);
};
const goFollow = () => {
currentModule.value = 3;
getAppMesageList(3);
};
const goReply = () => {
currentModule.value = 4;
getAppMesageList(4);
};
const clearMsg = () => { const clearMsg = () => {
uni.showModal({ uni.showModal({
title: '提醒', title: '提醒',
content: '是否要清除所有未读消息?', content: '是否要清除所有未读消息?',
success: (res) => { success: (res) => {
if (res.confirm) { if (res.confirm) {
api.appMesageRead({}).then(res => { api.appMesageRead().then(res => {
console.log(res); console.log(res);
getUnReadList();
uni.sendNativeEvent("portraitReport",{
msg:{
event_nickname: "clear_message",
},
},
(ret) => {
console.log(ret);
});
}); });
getUnReadList();
} }
} }
}); });
} }
const goMsgDetail = (msg) => {
console.log(msg);
if(msg.is_read==0){
readMsg(msg.id);
};
//readMsg(msg.id);
if(msg.extra.module==4){
if(msg.extra.type==17 || msg.extra.type==40){
uni.showToast({
title: '暂停中',
icon: 'none'
});
}else{
uni.sendNativeEvent('goCourseDetail', {
msg: {
id:msg.extra.id
}
});
}
}else if(msg.extra.module==2){
if(msg.extra.type==19){
let url=`/pages_app/myCourseware/myCourseware?from=msg`
navTo({
url: url
})
}else if(msg.extra.type==18){
let url=`/pages_app/myFlower/myFlower?from=msg`
navTo({
url: url
})
}
}else{
let url="";
if(msg.extra.module==1 && msg.extra.type==36){
url=`/pages_app/myPoint/myPoint?from=msg`
navTo({
url: url
})
}else if(msg.extra.module==1 && msg.extra.type==31){
url=`/pages_app/myPoint/myPoint?from=msg`
navTo({
url: url
})
}else if(msg.extra.module==1 && msg.extra.type==35){
//uni.setStorageSync('lookWelfare', 'useWelfare');
url=`/pages_app/myWelfareCard/myWelfareCard?from=msg`
navTo({
url: url
})
}else if(msg.extra.module==3 && msg.extra.type==12){
url=`/pages_app/myPatient/myPatient?from=msg`
navTo({
url: url
})
}
}
}
// 下拉刷新 // 下拉刷新
const onRefresh = async () => { const onRefresh = async () => {
if (refreshing.value) return; if (refreshing.value) return;
refreshing.value = true; refreshing.value = true;
try { try {
// 重新获取当前模块的消息列表 // 重置分页并重新获取数据
getAppMesageList(currentModule.value); resetPagination();
// 重新获取未读消息数量 await Promise.all([
getUnReadList(); getAppMesageList(true),
getUnReadList()
]);
} catch (err) {
console.error('刷新失败:', err);
uni.showToast({
title: '刷新失败',
icon: 'none'
});
} finally { } finally {
refreshing.value = false; refreshing.value = false;
} }
}; };
// 上拉加载 // 上拉加载更多
const onLoadMore = async () => { const onLoadMore = async () => {
if (loading.value || noMore.value) return; if (loading.value || noMore.value || refreshing.value) return;
loading.value = true; loading.value = true;
try { try {
// 这里可以根据需要实现分页加载 page.value++;
// 目前接口返回的数据已经包含了分页信息 await getAppMesageList(false);
// 如果需要加载更多,可以调用 getAppMesageList 并传入页码参数 } catch (err) {
noMore.value = true; // 暂时设置为没有更多数据 // 加载失败时回退页码
page.value = Math.max(1, page.value - 1);
console.error('加载更多失败:', err);
uni.showToast({
title: '加载失败',
icon: 'none'
});
} finally { } finally {
loading.value = false; loading.value = false;
} }
}; };
const getUnReadList = () => { const getUnReadList = () => {
api.unReadList({}).then(res => { return api.unReadList({}).then(res => {
console.log(res); console.log(res);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
// 更新角标数据 // 更新角标数据
@@ -207,17 +331,24 @@
Module_Relation: parseInt(res.data.Module_Relation) || 0 Module_Relation: parseInt(res.data.Module_Relation) || 0
}; };
} }
return res;
}).catch(err => { }).catch(err => {
console.error('获取未读消息列表失败:', err); console.error('获取未读消息列表失败:', err);
throw err;
}); });
}; };
const getAppMesageList = (module) => { // 获取消息列表
api.appMesageList({module: module}).then(res => { // isRefresh: true 表示刷新,false 表示加载更多
const getAppMesageList = (isRefresh = false) => {
return api.appMesageList({
module: currentModule.value,
page: page.value,
pageSize: pageSize
}).then(res => {
console.log(res); console.log(res);
if (res.code === 200 && res.data && res.data.list) { if (res.code === 200 && res.data && res.data.list) {
// 更新消息列表数据 const newList = res.data.list.map(item => ({
msgList.value = res.data.list.map(item => ({
id: item.id, id: item.id,
title: item.title, title: item.title,
content: item.content, content: item.content,
@@ -232,9 +363,31 @@
`${getBaseUrl()}${item.extra.user_photo}`) : `${getBaseUrl()}${item.extra.user_photo}`) :
'/static/default-avatar.png' '/static/default-avatar.png'
})); }));
// 刷新时替换数据,加载更多时追加数据
if (isRefresh || page.value === 1) {
msgList.value = newList;
} else {
msgList.value = [...msgList.value, ...newList];
}
// 判断是否还有更多数据
if (res.isLastPage || newList.length < pageSize) {
noMore.value = true;
} else {
noMore.value = false;
}
} else if (res.code === 200 && (!res.data || !res.data.list || res.data.list.length === 0)) {
// 没有数据的情况
if (isRefresh || page.value === 1) {
msgList.value = [];
}
noMore.value = true;
} }
return res;
}).catch(err => { }).catch(err => {
console.error('获取消息列表失败:', err); console.error('获取消息列表失败:', err);
throw err;
}); });
}; };
@@ -251,7 +404,7 @@
onMounted(() => { onMounted(() => {
getUnReadList(); getUnReadList();
getAppMesageList(1); getAppMesageList(true); // 初始加载视为刷新
}); });
</script> </script>
@@ -270,16 +423,31 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.imgcell{
width:74rpx;
height: 74rpx;
}
.nav-right{
display: flex;
align-items: center;
.collect-img {
width: 50rpx;
height: 50rpx;
.img-icon{
width: 50rpx;
height: 50rpx;
}
}
}
.msg-page { .msg-page {
min-height: 100vh; min-height: 100vh;
background: $bg; background: $bg;
padding-top: $nav-h; // 与导航栏高度一致
} }
.shortcut-grid { .shortcut-grid {
position: fixed; position: fixed;
top: $nav-h; top: calc(var(--status-bar-height) + 44px);
left: 0; left: 0;
right: 0; right: 0;
z-index: 9; z-index: 9;
@@ -333,7 +501,7 @@
.msg-list { .msg-list {
position: fixed; position: fixed;
// 减去导航与宫格的高度 // 减去导航与宫格的高度
top: 360rpx; // 让列表起始位置在宫格下方 top: calc(var(--status-bar-height) + 44px + 230rpx);
padding: 20rpx 24rpx 40rpx; padding: 20rpx 24rpx 40rpx;
box-sizing: border-box; box-sizing: border-box;
bottom: 0rpx; bottom: 0rpx;
@@ -347,6 +515,47 @@
} }
} }
// 空状态样式
.empty-state {
flex-direction: column;
padding: 100rpx 0;
gap: 20rpx;
}
// 加载动画
.loading-more {
gap: 16rpx;
.loading-spinner {
width: 32rpx;
height: 32rpx;
border: 3rpx solid #eee;
border-top-color: $theme;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.card-cell{
display: flex;
flex-direction: column;
margin-bottom: 30rpx;
box-sizing: border-box;
}
.card-time {
width:100%;
margin:10rpx 0;
font-size: 30rpx;
color: #666;
text-align: center;
}
.card { .card {
background: #fff; background: #fff;
border-radius: 16rpx; border-radius: 16rpx;
@@ -359,6 +568,9 @@
color: $text-primary; color: $text-primary;
font-weight: 600; font-weight: 600;
margin-bottom: 14rpx; margin-bottom: 14rpx;
&.active{
color: red;
}
} }
.card-content { .card-content {
@@ -368,11 +580,7 @@
margin-bottom: 14rpx; margin-bottom: 14rpx;
} }
.card-time {
font-size: 24rpx;
color: $muted;
text-align: right;
}
} }
// 聊天列表样式 // 聊天列表样式
+141
View File
@@ -0,0 +1,141 @@
<template>
<view class="bill-detail-page">
<view class="navbox">
<view class="status_bar"></view>
<uni-nav-bar
left-icon="left"
title="详情"
@clickLeft="goBack"
color="#8B2316"
:border="false"
backgroundColor="#eeeeee"
></uni-nav-bar>
</view>
<view class="detail-content">
<view class="detail-row">
<text class="label">交易号:</text>
<text class="value">{{ detail.tradeNo || '-' }}</text>
</view>
<view class="detail-row">
<text class="label">类型:</text>
<text class="value">{{ detail.typeName || '-' }}</text>
</view>
<view class="detail-row">
<text class="label">金额:</text>
<text class="value amount" :class="amountClass">{{ detail.accountStr || '-' }}元</text>
</view>
<view class="detail-row">
<text class="label">用户:</text>
<text class="value">{{ detail.userName || '-' }}</text>
</view>
<view class="detail-row">
<text class="label">时间:</text>
<text class="value">{{ formatDateTime(detail.createTime) }}</text>
</view>
<view class="detail-row">
<text class="label">备注:</text>
<text class="value">{{ detail.remark || '' }}</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
const detail = ref({})
const from = ref('')
onLoad((options) => {
if (options.from) {
from.value = decodeURIComponent(options.from)
}
})
const amountClass = computed(() => {
const amount = Number(detail.value?.accountStr || 0)
return amount < 0 ? 'negative' : 'positive'
})
const formatDateTime = (value) => {
if (!value) return '-'
const text = String(value).trim()
const date = new Date(text)
if (Number.isNaN(date.getTime())) return text
const y = date.getFullYear()
const m = String(date.getMonth() + 1).padStart(2, '0')
const d = String(date.getDate()).padStart(2, '0')
const hh = String(date.getHours()).padStart(2, '0')
const mm = String(date.getMinutes()).padStart(2, '0')
const ss = String(date.getSeconds()).padStart(2, '0')
return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
}
const goBack = () => {
if (from.value === 'billDetails') {
uni.navigateBack()
return
}
uni.redirectTo({
url: '/pages_app/myAccount/billDetails?from=myAccount'
})
}
onMounted(() => {
const cache = uni.getStorageSync('billDetailItem')
detail.value = cache || {}
})
</script>
<style lang="scss" scoped>
.bill-detail-page {
min-height: 100vh;
background: #f3f3f3;
}
.detail-content {
position: fixed;
left: 0;
right: 0;
top: calc(var(--status-bar-height) + 44px);
bottom: 0;
overflow-y: auto;
box-sizing: border-box;
background: #ffffff;
padding: 0 24rpx;
}
.detail-row {
min-height: 88rpx;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1rpx solid #efefef;
}
.label {
font-size: 30rpx;
color: #2c2c2c;
}
.value {
flex: 1;
text-align: right;
font-size: 30rpx;
color: #666666;
margin-left: 20rpx;
word-break: break-all;
}
.amount {
&.positive {
color: #8B2316;
}
&.negative {
color: #3f3f3f;
}
}
</style>
+65 -18
View File
@@ -1,16 +1,19 @@
<template> <template>
<view class="bill-details-page"> <view class="bill-details-page">
<!-- 顶部导航栏 --> <!-- 顶部导航栏 -->
<uni-nav-bar <view class="navbox">
<view class="status_bar"></view>
<uni-nav-bar
left-icon="left" left-icon="left"
title="账单明细" title="账单明细"
@clickLeft="goBack" @clickLeft="goBack"
fixed
color="#8B2316" color="#8B2316"
height="140rpx"
:border="false" :border="false"
backgroundColor="#eeeeee" backgroundColor="#eeeeee"
></uni-nav-bar> ></uni-nav-bar>
</view>
<!-- 表格头部 --> <!-- 表格头部 -->
<view class="table-header"> <view class="table-header">
@@ -32,7 +35,7 @@
@scrolltolower="onLoadMore" @scrolltolower="onLoadMore"
:lower-threshold="100" :lower-threshold="100"
> >
<view class="transaction-item" v-for="(item, index) in orderList" :key="item.id"> <view class="transaction-item" v-for="(item, index) in orderList" :key="item.id" @click="goToDetail(item)">
<view class="cell business-type">{{ item.typeName }}</view> <view class="cell business-type">{{ item.typeName }}</view>
<view class="cell user">{{ item.userName }}</view> <view class="cell user">{{ item.userName }}</view>
<view class="cell date-amount"> <view class="cell date-amount">
@@ -62,6 +65,7 @@
<script setup> <script setup>
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import api from '@/api/api.js' import api from '@/api/api.js'
import { onLoad } from '@dcloudio/uni-app'
// 响应式数据 // 响应式数据
const orderList = ref([]) const orderList = ref([])
@@ -71,10 +75,24 @@ const page = ref(1)
const total = ref(0) const total = ref(0)
const isFirstPage = ref(true) const isFirstPage = ref(true)
const isLastPage = ref(false) const isLastPage = ref(false)
const from = ref('')
onLoad((options) => {
if (options.from) {
from.value = decodeURIComponent(options.from)
}
})
const stopRefresh = () => {
refreshing.value = false
}
// 获取订单列表 // 获取订单列表
const getOrderList = async (isRefresh = false) => { const getOrderList = async (isRefresh = false) => {
if (loading.value) return if (loading.value) {
if (isRefresh) stopRefresh()
return
}
loading.value = true loading.value = true
@@ -93,15 +111,18 @@ const getOrderList = async (isRefresh = false) => {
} }
// 处理订单数据 // 处理订单数据
const processedList = list.map(item => ({ const safeList = Array.isArray(list) ? list : []
const processedList = safeList.map(item => ({
id: item.trade_no, id: item.trade_no,
tradeNo: item.trade_no,
typeName: item.type_name, typeName: item.type_name,
userName: item.user_name, userName: item.user_name,
createTime: item.create_time, createTime: item.create_time,
accountStr: item.accountstr, accountStr: item.accountstr,
payChannel: item.pay_channel, payChannel: item.pay_channel,
orderType: item.order_type, orderType: item.order_type,
status: item.status status: item.status,
remark: item.remark || item.message || ''
})) }))
orderList.value.push(...processedList) orderList.value.push(...processedList)
@@ -117,12 +138,16 @@ const getOrderList = async (isRefresh = false) => {
}) })
} finally { } finally {
loading.value = false loading.value = false
refreshing.value = false stopRefresh()
} }
} }
// 刷新数据 // 刷新数据
const onRefresh = () => { const onRefresh = () => {
if (loading.value) {
stopRefresh()
return
}
refreshing.value = true refreshing.value = true
page.value = 1 page.value = 1
isLastPage.value = false isLastPage.value = false
@@ -149,9 +174,23 @@ const formatDate = (dateStr) => {
// 返回上一页 // 返回上一页
const goBack = () => { const goBack = () => {
if (from.value === 'myAccount') {
uni.redirectTo({
url: '/pages_app/myAccount/myAccount?from=billDetails'
})
return
}
uni.navigateBack() uni.navigateBack()
} }
const goToDetail = (item) => {
uni.setStorageSync('billDetailItem', item || {})
const fromPage = encodeURIComponent('billDetails')
uni.navigateTo({
url: `/pages_app/myAccount/billDetail?from=${fromPage}`
})
}
onMounted(() => { onMounted(() => {
getOrderList() getOrderList()
}) })
@@ -182,14 +221,17 @@ onMounted(() => {
// 表格头部样式 // 表格头部样式
.table-header { .table-header {
background-color: #e0e0e0; background-color: #fff;
position: fixed;
display: flex; display: flex;
width: 100%;
padding: 20rpx 30rpx; padding: 20rpx 30rpx;
margin-top: 170rpx; /* 为固定导航栏留出空间 */ box-sizing: border-box;
top:calc(var(--status-bar-height) + 44px); /* 为固定导航栏留出空间 */
border-bottom: 1rpx solid #ccc; border-bottom: 1rpx solid #ccc;
.header-cell { .header-cell {
font-size: 28rpx; font-size: 30rpx;
color: #333; color: #333;
font-weight: 500; font-weight: 500;
@@ -224,17 +266,22 @@ onMounted(() => {
// 交易记录列表样式 // 交易记录列表样式
.transaction-list { .transaction-list {
background-color: #f5f5f5; top:calc(var(--status-bar-height) + 44px + 130rpx);
position: fixed;
left: 0;
right: 0;
bottom: 0rpx;
background-color: #fff;
} }
.transaction-item { .transaction-item {
background-color: #f8f8f8;
display: flex; display: flex;
padding: 20rpx 30rpx; padding: 20rpx 30rpx;
border-bottom: 1rpx solid #e5e5e5; border-bottom: 1rpx solid #e5e5e5;
.cell { .cell {
font-size: 26rpx; font-size: 30rpx;
&.business-type { &.business-type {
flex: 1; flex: 1;
@@ -245,7 +292,7 @@ onMounted(() => {
&.user { &.user {
flex: 1; flex: 1;
text-align: center; text-align: center;
color: #ff0000; color: #8B2316;
} }
&.date-amount { &.date-amount {
@@ -257,7 +304,7 @@ onMounted(() => {
.date { .date {
font-size: 24rpx; font-size: 24rpx;
color: #666; color: #999;
margin-bottom: 4rpx; margin-bottom: 4rpx;
} }
@@ -265,11 +312,11 @@ onMounted(() => {
font-size: 24rpx; font-size: 24rpx;
&.positive { &.positive {
color: #ff0000; color: #000;
} }
&.negative { &.negative {
color: #666; color: #000;
} }
} }
} }
+71 -29
View File
@@ -1,18 +1,20 @@
<template> <template>
<view class="my-account-page"> <view class="my-account-page">
<!-- 顶部导航栏 --> <!-- 顶部导航栏 -->
<uni-nav-bar <view class="navbox">
<view class="status_bar"></view>
<uni-nav-bar
left-icon="left" left-icon="left"
title="我的账户" title="我的账户"
@clickLeft="goBack" @clickLeft="goBack"
fixed
color="#8B2316" color="#8B2316"
height="140rpx"
:border="false" :border="false"
backgroundColor="#eeeeee" backgroundColor="#eeeeee"
></uni-nav-bar> ></uni-nav-bar>
</view>
<!-- 账户余额区域 --> <view class="contentbox">
<!-- 账户余额区域 -->
<view class="account-summary"> <view class="account-summary">
<view class="profile-section"> <view class="profile-section">
<image class="profile-avatar" :src="avatar" mode="aspectFill"></image> <image class="profile-avatar" :src="avatar" mode="aspectFill"></image>
@@ -21,7 +23,7 @@
<text class="balance-amount">{{ (accountBalance/100).toFixed(2) }}</text> <text class="balance-amount">{{ (accountBalance/100).toFixed(2) }}</text>
<!-- 账户信息详情 --> <!-- 账户信息详情 -->
<view class="account-details"> <!-- <view class="account-details">
<view class="detail-item"> <view class="detail-item">
<text class="detail-label">单次提现限额</text> <text class="detail-label">单次提现限额</text>
<text class="detail-value">{{ (withdrawalBalanceMaxOnce/100).toFixed(2) }}元</text> <text class="detail-value">{{ (withdrawalBalanceMaxOnce/100).toFixed(2) }}元</text>
@@ -34,35 +36,36 @@
<text class="detail-label">最低提现金额</text> <text class="detail-label">最低提现金额</text>
<text class="detail-value">{{ (lessWithdrawalBalance/100).toFixed(2) }}元</text> <text class="detail-value">{{ (lessWithdrawalBalance/100).toFixed(2) }}元</text>
</view> </view>
</view> </view> -->
</view> </view>
<!-- 操作选项 --> <!-- 操作选项 -->
<view class="action-items"> <view class="action-items">
<view class="action-item" @click="handleWithdrawal"> <view class="action-item" @click="handleWithdrawal">
<view class="action-icon withdrawal-icon"> <view class="action-icon withdrawal-icon">
<text class="icon-text">💰</text> <image class="icon-img" :src="withdrawalsIcon" mode="aspectFit"></image>
</view> </view>
<view class="action-content"> <view class="action-content">
<text class="action-title">提现</text> <text class="action-title">提现</text>
<text class="action-subtitle">提现金额不得少于{{ (lessWithdrawalBalance/100).toFixed(2) }}元</text> <text class="action-subtitle">提现金额不得少于{{ (lessWithdrawalBalance/100).toFixed(2) }}元</text>
</view> </view>
<text class="action-arrow">></text> <uni-icons type="right" size="25" color="#999"></uni-icons>
</view> </view>
<view class="action-item" @click="handleBillDetails"> <view class="action-item" @click="handleBillDetails">
<view class="action-icon bill-icon"> <view class="action-icon bill-icon">
<text class="icon-text">📋</text> <image class="icon-img" :src="accountIcon" mode="aspectFit"></image>
</view> </view>
<view class="action-content"> <view class="action-content">
<text class="action-title">账单明细</text> <text class="action-title">账单明细</text>
</view> </view>
<text class="action-arrow">></text> <uni-icons type="right" size="25" color="#999"></uni-icons>
</view> </view>
</view> </view>
</view>
<!-- 底部指示器 --> <!-- 底部指示器 -->
<view class="bottom-indicator"></view> <!-- <view class="bottom-indicator"></view> -->
</view> </view>
</template> </template>
@@ -70,6 +73,27 @@
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import api from '@/api/api.js' import api from '@/api/api.js'
import docUrl from '@/utils/docUrl' import docUrl from '@/utils/docUrl'
import withdrawalsIcon from '@/static/withdrawals.png'
import accountIcon from '@/static/account_icon.png'
import { onLoad,onBackPress,onShow } from '@dcloudio/uni-app';
const from = ref('');
onLoad((options) => {
if(options.from){
from.value = options.from;
}
uni.sendNativeEvent("portraitReport",{
msg:{
event_nickname: "list_page",
page_type: "我的账户",
},
});
});
onBackPress(() => {
if(!from.value){
plus.runtime.quit();
return true;
}
});
// 响应式数据 // 响应式数据
const accountBalance = ref(0) const accountBalance = ref(0)
const withdrawalBalanceMaxOnce = ref(0) const withdrawalBalanceMaxOnce = ref(0)
@@ -77,7 +101,7 @@ const withdrawalBalanceFee = ref(0)
const lessWithdrawalBalance = ref(0) const lessWithdrawalBalance = ref(0)
const avatar = ref('') const avatar = ref('')
onMounted(() => { onShow(() => {
getMyAccount() getMyAccount()
let userInfo= uni.getStorageSync('userInfo') let userInfo= uni.getStorageSync('userInfo')
@@ -86,13 +110,17 @@ onMounted(() => {
// 方法 // 方法
const goBack = () => { const goBack = () => {
uni.navigateBack() if(!from.value){
plus.runtime.quit();
}else{
uni.navigateBack();
}
} }
const handleWithdrawal = () => { const handleWithdrawal = () => {
if(accountBalance.value < lessWithdrawalBalance.value){ if(accountBalance.value < lessWithdrawalBalance.value){
uni.showToast({ uni.showToast({
title: `提现金额不得少于${(lessWithdrawalBalance/100).toFixed(2)}元`, title: `提现金额不得少于${(lessWithdrawalBalance.value/100).toFixed(2)}元`,
icon: 'none' icon: 'none'
}) })
return return
@@ -111,8 +139,9 @@ const handleWithdrawal = () => {
cancelText: '取消', cancelText: '取消',
success: (res) => { success: (res) => {
if(res.confirm){ if(res.confirm){
const returnTo = encodeURIComponent('/pages_app/myAccount/myAccount')
uni.navigateTo({ uni.navigateTo({
url: '/pages_app/idcardAuth/bankCardList' url: `/pages_app/idcardAuth/idcardAuth?returnTo=${returnTo}`
}) })
} }
} }
@@ -123,8 +152,9 @@ const handleWithdrawal = () => {
} }
const handleBillDetails = () => { const handleBillDetails = () => {
const from = encodeURIComponent('myAccount')
uni.navigateTo({ uni.navigateTo({
url: '/pages_app/myAccount/billDetails' url: `/pages_app/myAccount/billDetails?from=${from}`
}) })
} }
@@ -142,6 +172,14 @@ const getMyAccount = async () => {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.contentbox{
position: fixed;
top: calc(var(--status-bar-height) + 44px);
left: 0;
right: 0;
bottom: 0;
}
.my-account-page { .my-account-page {
background-color: #f5f5f5; background-color: #f5f5f5;
min-height: 100vh; min-height: 100vh;
@@ -151,20 +189,19 @@ const getMyAccount = async () => {
// 账户余额区域样式 // 账户余额区域样式
.account-summary { .account-summary {
background: linear-gradient(135deg, #8B2316, #A0522D); background: linear-gradient(135deg, #8B2316, #A0522D);
margin: 30rpx; // margin: 30rpx;
border-radius: 20rpx; // border-radius: 20rpx;
padding: 60rpx 40rpx; padding: 30rpx 40rpx 10rpx;
text-align: center; text-align: center;
box-shadow: 0 8rpx 24rpx rgba(139, 35, 22, 0.3);
position: relative; position: relative;
.profile-section { .profile-section {
margin-bottom: 40rpx; margin-bottom: 10rpx;
.profile-avatar { .profile-avatar {
width: 120rpx; width: 120rpx;
height: 120rpx; height: 120rpx;
border-radius: 60rpx; border-radius: 20rpx;
border: 4rpx solid rgba(255, 255, 255, 0.3); border: 4rpx solid rgba(255, 255, 255, 0.3);
object-fit: cover; object-fit: cover;
} }
@@ -174,18 +211,18 @@ const getMyAccount = async () => {
display: block; display: block;
color: rgba(255, 255, 255, 0.9); color: rgba(255, 255, 255, 0.9);
font-size: 28rpx; font-size: 28rpx;
margin-bottom: 20rpx; margin-bottom: 10rpx;
font-weight: 500; font-weight: 500;
} }
.balance-amount { .balance-amount {
display: block; display: block;
color: #ffffff; color: #ffffff;
font-size: 72rpx; font-size: 50rpx;
font-weight: bold; font-weight: bold;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2); text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
letter-spacing: 2rpx; letter-spacing: 2rpx;
margin-bottom: 40rpx; margin-bottom: 0rpx;
} }
// 账户信息详情样式 // 账户信息详情样式
@@ -223,15 +260,15 @@ const getMyAccount = async () => {
// 操作选项样式 // 操作选项样式
.action-items { .action-items {
background-color: #ffffff; background-color: #ffffff;
margin: 0 30rpx; margin-top: 0rpx;
border-radius: 20rpx; // border-radius: 20rpx;
overflow: hidden; overflow: hidden;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08); // box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
.action-item { .action-item {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 40rpx 30rpx; padding: 20rpx 30rpx;
border-bottom: 1rpx solid #f0f0f0; border-bottom: 1rpx solid #f0f0f0;
transition: background-color 0.2s ease; transition: background-color 0.2s ease;
@@ -256,6 +293,11 @@ const getMyAccount = async () => {
.icon-text { .icon-text {
font-size: 32rpx; font-size: 32rpx;
} }
.icon-img {
width: 34rpx;
height: 34rpx;
}
} }
.withdrawal-icon { .withdrawal-icon {
+135 -53
View File
@@ -1,16 +1,17 @@
<template> <template>
<view class="withdrawal-page"> <view class="withdrawal-page">
<!-- 顶部导航栏 --> <!-- 顶部导航栏 -->
<uni-nav-bar <view class="navbox">
<view class="status_bar"></view>
<uni-nav-bar
left-icon="left" left-icon="left"
title="提现" title="提现"
@clickLeft="goBack" @clickLeft="goBack"
fixed
color="#8B2316" color="#8B2316"
height="140rpx"
:border="false" :border="false"
backgroundColor="#eeeeee" backgroundColor="#eeeeee"
></uni-nav-bar> ></uni-nav-bar>
</view>
<!-- 主内容区域 --> <!-- 主内容区域 -->
<view class="main-content"> <view class="main-content">
@@ -25,20 +26,27 @@
<text class="section-title">结算银行卡</text> <text class="section-title">结算银行卡</text>
<text class="withdrawal-rules" @click="showWithdrawalRules">提现规则</text> <text class="withdrawal-rules" @click="showWithdrawalRules">提现规则</text>
</view> </view>
<view class="divider"></view>
<view class="bank-card-info" @click="selectBankCard" v-if="selectedBankCard"> <view class="bank-card-info" @click="selectBankCard" v-if="selectedBankCard">
<view class="bank-logo"> <view class="bank-logo">
<text class="bank-icon">{{ selectedBankCard.bankName ? selectedBankCard.bankName.charAt(0) : '工' }}</text> <image
v-if="getBankLogoImg(getBankName(selectedBankCard))"
class="bank-logo-img"
:src="getBankLogoImg(getBankName(selectedBankCard))"
mode="aspectFit"
></image>
<view v-else class="logo-bg">
<text class="bank-icon">{{ getBankLogo(getBankName(selectedBankCard)) }}</text>
</view>
</view> </view>
<view class="bank-details"> <view class="bank-details">
<text class="bank-name">{{ selectedBankCard.bankName || '工商银行' }}</text> <text class="bank-name">{{ getBankName(selectedBankCard) || '工商银行' }}</text>
<text class="bank-card-number">尾号为{{ selectedBankCard.cardNumber ? selectedBankCard.cardNumber.slice(-4) : '8937' }}储蓄卡</text> <text class="bank-card-number">尾号为{{ getCardTail(selectedBankCard) }}储蓄卡</text>
</view> </view>
<text class="arrow-icon">›</text> <uni-icons type="right" size="25" color="#999"></uni-icons>
</view> </view>
<view class="no-bank-card" v-else> <view class="no-bank-card" v-else>
<text class="no-card-text">请先添加银行卡</text> <text class="no-card-text">请先添加银行卡</text>
<text class="arrow-icon">›</text> <uni-icons type="right" size="25" color="#999"></uni-icons>
</view> </view>
<view class="divider"></view> <view class="divider"></view>
</view> </view>
@@ -48,12 +56,12 @@
<text class="amount-title">提现金额</text> <text class="amount-title">提现金额</text>
<view class="amount-display"> <view class="amount-display">
<text class="currency-symbol">¥</text> <text class="currency-symbol">¥</text>
<text class="amount-value">{{ withdrawalAmount }}</text> <text class="amount-value">{{ withdrawalAmount}}</text>
</view> </view>
<view class="amount-details"> <view class="amount-details">
<text class="tax-info">扣除个人所得税<text class="highlight-red">{{ (incomeTax / 100).toFixed(2) }}元</text></text> <text class="tax-info">扣除个人所得税<text class="highlight-red">{{ incomeTax}}元</text></text>
<text class="fee-info">扣除提现手续费<text class="highlight-red">{{ (withdrawalBalanceFee / 100).toFixed(2) }}元</text></text> <!-- <text class="fee-info">扣除提现手续费<text class="highlight-red">{{ (withdrawalBalanceFee / 100).toFixed(2) }}元</text></text> -->
<text class="actual-amount">此次提现实际到账<text class="highlight-red">{{ actualAmount }}元</text></text> <text class="actual-amount">此次提现实际到账<text class="highlight-red">{{ actualAmount}}元</text></text>
</view> </view>
</view> </view>
</view> </view>
@@ -132,10 +140,11 @@ onMounted(() => {
// 计算提现金额 // 计算提现金额
const calculateAmounts = () => { const calculateAmounts = () => {
// 提现金额 = 账户余额 - 个人所得税 - 提现手续费 // 提现金额 = 账户余额 - 个人所得税 - 提现手续费
const totalDeduction = incomeTax.value + withdrawalBalanceFee.value // const totalDeduction = incomeTax.value + withdrawalBalanceFee.value
const amount = accountBalance.value - totalDeduction // const amount = accountBalance.value - totalDeduction
withdrawalAmount.value = accountBalance.value > 0 ? (accountBalance.value/100).toFixed(2) : '0.00' // withdrawalAmount.value = accountBalance.value > 0 ? (accountBalance.value/100).toFixed(2) : '0.00'
actualAmount.value = amount > 0 ? (amount/100).toFixed(2) : '0.00' // actualAmount.value = amount > 0 ? (amount/100).toFixed(2) : '0.00'
} }
// 初始化数据 // 初始化数据
@@ -162,6 +171,63 @@ const goBack = () => {
uni.navigateBack() uni.navigateBack()
} }
const bankLogoMap = {
'工商银行': '/static/icon_bankcard_gongshang.png',
'中国银行': '/static/icon_bankcard_zhongguo.png',
'建设银行': '/static/icon_bankcard_jianseyinhang.png',
'农业银行': '/static/icon_bankcard_nongyeyinhang.png',
'交通银行': '/static/icon_bankcard_jiatong.png',
'招商银行': '/static/icon_bankcard_zhaoshangyinhang.png',
'民生银行': '/static/icon_bankcard_minshneg.png',
'兴业银行': '/static/icon_bankcard_xingye.png',
'浦发银行': '/static/icon_bankcard_shanghaipudongfazhanyinhang.png',
'光大银行': '/static/icon_bankcard_guangda.png',
'华夏银行': '/static/icon_bankcard_huaxiayinhnag.png',
'中信银行': '/static/icon_bankcard_zhongxinshiye.png',
'平安银行': '/static/icon_bankcard_pingan.png',
'广发银行': '/static/icon_bankcard_guangfa.png',
'邮储银行': '/static/icon_bankcard_youzheng.png',
'上海银行': '/static/icon_bankcard_shanghai.png',
'北京银行': '/static/icon_bankcard_beijingyinhang.png'
}
const getBankName = (card) => {
if (!card) return ''
return card.bankName || card.open_bank || ''
}
const getCardTail = (card) => {
if (!card) return '8937'
const raw = card.cardNumber || card.card_number || ''
return raw ? String(raw).slice(-4) : '8937'
}
const getBankLogoImg = (bankName) => {
return bankLogoMap[bankName] || ''
}
const getBankLogo = (bankName) => {
const bankLogos = {
'工商银行': '工',
'中国银行': '中',
'建设银行': '建',
'农业银行': '农',
'交通银行': '交',
'招商银行': '招',
'民生银行': '民',
'兴业银行': '兴',
'浦发银行': '浦',
'光大银行': '光',
'华夏银行': '华',
'中信银行': '信',
'平安银行': '平',
'广发银行': '广',
'邮储银行': '邮'
}
if (!bankName) return '工'
return bankLogos[bankName] || bankName.charAt(0)
}
// 获取我的账户信息 // 获取我的账户信息
const getMyAccount = async () => { const getMyAccount = async () => {
try { try {
@@ -173,7 +239,7 @@ const getMyAccount = async () => {
withdrawalBalanceFee.value = res.data.withdrawalBalanceFee withdrawalBalanceFee.value = res.data.withdrawalBalanceFee
lessWithdrawalBalance.value = res.data.lessWithdrawalBalance lessWithdrawalBalance.value = res.data.lessWithdrawalBalance
// 获取账户信息后计算金额 // 获取账户信息后计算金额
calculateAmounts() //calculateAmounts()
// 然后获取个人所得税 // 然后获取个人所得税
getIncomeTax(accountBalance.value) getIncomeTax(accountBalance.value)
} }
@@ -187,10 +253,13 @@ const getIncomeTax = async (amount) => {
try { try {
const res = await api.getIncomeTax({amount: amount}) const res = await api.getIncomeTax({amount: amount})
console.log('个人所得税:', res) console.log('个人所得税:', res)
if (res && res.code === 200 && res.data) { if (res.code ==200) {
incomeTax.value = res.data.incomeTax || 0 incomeTax.value = (res.data.tax/100).toFixed(2) || 0
withdrawalAmount.value = (amount/100).toFixed(2) || 0
actualAmount.value = (res.data.actual_amount/100).toFixed(2) || 0
// 获取税费后重新计算金额 // 获取税费后重新计算金额
calculateAmounts() //calculateAmounts()
} }
} catch (error) { } catch (error) {
console.error('获取个人所得税失败:', error) console.error('获取个人所得税失败:', error)
@@ -214,8 +283,9 @@ const getBankCardList = async () => {
confirmText: '去添加', confirmText: '去添加',
success: (modalRes) => { success: (modalRes) => {
if (modalRes.confirm) { if (modalRes.confirm) {
const returnTo = encodeURIComponent('/pages_app/myAccount/withdrawal?from=bankCardList')
uni.navigateTo({ uni.navigateTo({
url: '/pages_app/idcardAuth/bankCardList' url: `/pages_app/idcardAuth/bankCardList?from=withdrawal&returnTo=${returnTo}`
}) })
} }
} }
@@ -238,7 +308,7 @@ const selectBankCard = () => {
// 这里可以实现银行卡选择逻辑 // 这里可以实现银行卡选择逻辑
uni.showActionSheet({ uni.showActionSheet({
itemList: bankCardList.value.map(card => `${card.bankName} 尾号${card.cardNumber.slice(-4)}`), itemList: bankCardList.value.map(card => `${getBankName(card)} 尾号${getCardTail(card)}`),
success: (res) => { success: (res) => {
selectedBankCard.value = bankCardList.value[res.tapIndex] selectedBankCard.value = bankCardList.value[res.tapIndex]
} }
@@ -252,8 +322,8 @@ const showWithdrawalRules = () => {
const feeAmount = (withdrawalBalanceFee.value / 100).toFixed(2) const feeAmount = (withdrawalBalanceFee.value / 100).toFixed(2)
uni.showModal({ uni.showModal({
title: '提现规则', title: '温馨提示',
content: `1. 提现金额不得少于${minAmount}元\n2. 单次提现限额为${maxAmount}元\n3. 提现手续费为${feeAmount}元\n4. 工作日24小时内到账`, content: `提交成功后会有工作人员与您联系`,
showCancel: false, showCancel: false,
confirmText: '我知道了' confirmText: '我知道了'
}) })
@@ -278,15 +348,7 @@ const confirmWithdrawal = () => {
return return
} }
// 检查单次提现限额
const maxAmount = withdrawalBalanceMaxOnce.value / 100
if (parseFloat(withdrawalAmount.value) > maxAmount) {
uni.showToast({
title: `单次提现限额为${maxAmount.toFixed(2)}元`,
icon: 'none'
})
return
}
// 弹出短信验证码弹框 // 弹出短信验证码弹框
showSmsDialog.value = true showSmsDialog.value = true
@@ -337,7 +399,8 @@ const onGetSmsCode = async () => {
} catch (error) { } catch (error) {
console.error('发送短信验证码失败:', error) console.error('发送短信验证码失败:', error)
uni.showToast({ uni.showToast({
title: '网络错误,请重试', title:error.data.message || error.message || error.data.msg || error.msg || '频繁发送短信,请稍后再试'
,
icon: 'none' icon: 'none'
}) })
} finally { } finally {
@@ -405,7 +468,9 @@ const onCancelSms = () => {
// 主内容区域 // 主内容区域
.main-content { .main-content {
padding: 0 30rpx; padding-top: calc(var(--status-bar-height) + 44px);
padding-bottom: 180rpx;
box-sizing: border-box;
} }
// 加载状态 // 加载状态
@@ -433,7 +498,7 @@ const onCancelSms = () => {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 30rpx 0; padding: 30rpx;
.section-title { .section-title {
font-size: 32rpx; font-size: 32rpx;
@@ -448,30 +513,43 @@ const onCancelSms = () => {
} }
.divider { .divider {
height: 1rpx; height: 2rpx;
background-color: #f0f0f0; background-color: #bab7b7;
margin: 0; margin: 0;
} }
.bank-card-info { .bank-card-info {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 30rpx 0; padding: 30rpx;
background-color: #ffffff;
.bank-logo { .bank-logo {
width: 60rpx; width: 60rpx;
height: 60rpx; height: 60rpx;
background-color: #8B2316;
border-radius: 8rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 24rpx; margin-right: 24rpx;
.bank-icon { .bank-logo-img {
font-size: 32rpx; width: 60rpx;
color: #ffffff; height: 60rpx;
font-weight: bold; border-radius: 50%;
}
.logo-bg {
width: 60rpx;
height: 60rpx;
background: #E60012;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 2rpx solid #E60012;
.bank-icon {
font-size: 28rpx;
color: #ffffff;
font-weight: bold;
}
} }
} }
@@ -493,7 +571,7 @@ const onCancelSms = () => {
} }
.arrow-icon { .arrow-icon {
font-size: 32rpx; font-size: 62rpx;
color: #cccccc; color: #cccccc;
font-weight: bold; font-weight: bold;
} }
@@ -520,8 +598,8 @@ const onCancelSms = () => {
// 金额区域 // 金额区域
.amount-section { .amount-section {
padding: 40rpx 0; padding: 40rpx 30rpx;
background-color: #ffffff;
.amount-title { .amount-title {
display: block; display: block;
font-size: 32rpx; font-size: 32rpx;
@@ -556,7 +634,7 @@ const onCancelSms = () => {
margin-bottom: 12rpx; margin-bottom: 12rpx;
.highlight-red { .highlight-red {
color: #8B2316; color: red;
} }
} }
} }
@@ -668,6 +746,10 @@ const onCancelSms = () => {
color: #8B2316; color: #8B2316;
font-size: 26rpx; font-size: 26rpx;
border: none; border: none;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
&:disabled { &:disabled {
background: #f5f5f5; background: #f5f5f5;
+436 -238
View File
@@ -1,272 +1,470 @@
<template> <template>
<view class="my-answer-page"> <view class="my-answer-page">
<navBar title="我的意见" /> <navBar title="我的意见" />
<scroll-view scroll-y class="content-scroll"> <scroll-view scroll-y class="content-scroll">
<!-- 文本输入 --> <!-- 文本输入 -->
<view class="card"> <view class="card">
<view class="card-title">我的意见 <text class="required">*</text></view> <view class="card-title">我的意见 <text class="required">*</text></view>
<view class="textarea-wrap"> <view class="textarea-wrap">
<textarea <textarea
v-model="form.note" v-model="note"
class="textarea" class="textarea"
:maxlength="300" :maxlength="300"
placeholder="请依据患者的个人信息、疾病资料及患者所咨询的问题详细解答患者的问题(信息仅提问患者及医生可见,最多输入300个字)" placeholder="请依据患者的个人信息、疾病资料及患者所咨询的问题详细解答患者的问题(信息仅提问患者及医生可见,最多输入300个字)"
placeholder-class="ph" placeholder-class="ph"
auto-height auto-height
/> :adjust-position="false"
/>
</view>
</view>
</view> <!-- 图片上传 -->
</view> <view class="card">
<view class="card-title">相关图片</view>
<!-- 图片上传 --> <view class="sub-tip">可以用部分科普或文献来协助回答问题,最多6张</view>
<view class="card"> <!-- <view class="imgbox">
<view class="card-title">相关图片</view> <uni-file-picker limit="6" v-model="tempImageList" file-mediatype="image" :auto-upload="false" @select="selectImages" @delete="deleteImages" ></uni-file-picker>
<view class="sub-tip">可以用部分科普或文献来协助回答问题,最多6张</view> </view> -->
<view class="img-grid"> <view class="img-grid">
<view <view
v-if="imgList.length>0" v-if="tempImageList.length > 0"
v-for="(img, index) in imgList" v-for="(img, index) in tempImageList"
:key="index" :key="index"
class="img-item" class="img-item"
@click="preview(index)" @click="preview(index)"
> >
<image :src="docUrl+img" mode="aspectFill" class="img" /> <image :src="img" mode="aspectFill" class="img" />
<view class="del" @click.stop="remove(index)">×</view> <view class="del" @click.stop="remove(index)">×</view>
</view> </view>
<view v-if="imgList.length < maxImages" class="img-item add" @click="addImages"> <view
<view class="plus">+</view> v-if="tempImageList.length < maxImages"
</view> class="img-item add"
</view> @click="beforeAddImages"
</view> >
</scroll-view> <view class="plus">+</view>
</view>
<!-- 底部提交 --> </view>
<view class="bottom-fixed"> </view>
<view class="submit-btn" @click="submit">提交</view> </scroll-view>
</view> <!-- <button style="position: fixed;bottom: 200rpx;left: 50%;transform: translateX(-50%);z-index: 99999999999;" @click="test" >测试</button> -->
</view> <!-- 底部提交 -->
<view class="bottom-fixed">
<view class="submit-btn" @click="submit">提交</view>
</view>
</view>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import { ref } from "vue";
import navBar from '@/components/navBar/navBar.vue' import navBar from "@/components/navBar/navBar.vue";
import api from '@/api/api' import api from "@/api/api";
import docUrl from '@/utils/docUrl' import docUrl from "@/utils/docUrl";
import { onLoad,onShow } from '@dcloudio/uni-app' import { onLoad, onShow } from "@dcloudio/uni-app";
const uuid=ref(''); import { pathToBase64 } from "image-tools";
import { getCameraPermission } from "@/utils/permission.js";
import isAndroid from "@/utils/platform.js";
const uuid = ref("");
const answer_uuid = ref("");
const maxImages = 6; const maxImages = 6;
const imgList = ref([]); const imgList = ref([]);
const form = ref({ const isLock = ref(false);
note: '', const tempImageList = ref([]);
images: [] const note = ref("");
})
onLoad((options) => { onLoad((options) => {
uuid.value = options.uuid || '' uuid.value = options.uuid || "";
}) answer_uuid.value = options.answer_uuid || "";
const getInterrogation=()=>{ getInterrogation();
api.getInterrogation({ });
uuid:uuid.value const beforeAddImages = () => {
}).then(res=>{ if (isAndroid) {
console.log(res) getCameraPermission(addImages);
if(res.code === '200' && res.data) { } else {
let user=uni.getStorageSync('userInfo'); addImages();
let arr=res.data.AnswerList.filter(item=>{ }
return user.uuid == item.expert_uuid };
}) const getInterrogation = () => {
api
form.value= arr[0]; .getInterrogation({
imgList.value= form.value.imgs?form.value.imgs.split(','):[]; uuid: uuid.value,
}
})
}
const updateInterrogationAnswer=()=>{
let imgobj={};
if(imgList.value.length>0){
let count=0;
imgList.value.forEach(item=>{
imgobj['img'+count]=docUrl+item;
})
}
api.updateInterrogationAnswer({
answer_uuid: answer_uuid.value,
note: form.value.note,
imgsBean: imgobj
}).then(res=>{
if(res.code == 200){
uni.showToast({title: '提交成功', icon: 'none'})
uni.navigateBack()
}
})
}
const addInterrogationAnswer=()=>{
api.addInterrogationAnswer({
note: form.value.note,
imgsBean: imgobj
}).then(res=>{
if(res.code == 200){
uni.showToast({title: '提交成功', icon: 'none'})
uni.navigateBack()
}
}) })
} .then((res) => {
onShow(()=>{ console.log(res);
getInterrogation() if (res.code === "200" && res.data) {
}) let user = uni.getStorageSync("userInfo");
function addImages(){ let arr = res.data.AnswerList.filter((item) => {
const remain = maxImages - form.value.images.length return user.uuid == item.expert_uuid;
if(remain <= 0){ });
uni.showToast({title: '最多6张', icon: 'none'}) imgList.value = [];
return if (arr && arr.length > 0) {
} note.value = arr[0].note || "";
uni.chooseImage({ let imgs = arr[0].imgs ? arr[0].imgs.split(",") : [];
count: remain, tempImageList.value = imgs.map((item, index) => {
sizeType: ['compressed'], return {
sourceType: ['album','camera'], name: "图片" + index + 1,
success: (res)=>{ url: docUrl + item,
const paths = (res.tempFilePaths || res.tempFiles?.map(f=>f.path) || []) };
imgList.value = imgList.value.concat(paths).slice(0, maxImages) });
} tempImageList.value = imgs.map((item) => docUrl + item);
}) for (let i = 0; i < imgs.length; i++) {
uni.request({
url: docUrl + imgs[i],
method: "GET",
responseType: "arraybuffer",
success: (res) => {
const base64 = `${uni.arrayBufferToBase64(res.data)}`;
imgList.value.push(base64);
},
fail: (err) => {
reject(err);
},
});
}
}
}
});
};
const updateInterrogationAnswer = () => {
if (note.value.trim().length < 5) {
uni.showToast({ title: "输入意见至少5个字", icon: "none" });
return;
}
if (isLock.value) {
return;
}
isLock.value = true;
let imgobj = {};
if (imgList.value.length > 0) {
let count = 0;
imgList.value.forEach((item, index) => {
imgobj["img" + (index + 1)] = item;
});
}
api
.updateInterrogationAnswer({
uuid: answer_uuid.value,
note: note.value,
imgsBean: imgobj,
})
.then((res) => {
isLock.value = false;
if (res.code == 200) {
uni.showToast({ title: "提交成功", icon: "none" });
uni.$emit("updateStatus", 1);
uni.navigateBack();
}
});
};
const test = () => {
uni.navigateTo({
url: "/pages/upload/upload",
});
};
const addInterrogationAnswer = () => {
if (note.value.trim().length < 5) {
uni.showToast({ title: "输入意见至少5个字", icon: "none" });
return;
}
if (isLock.value) {
return;
}
isLock.value = true;
let imgobj = {};
if (imgList.value.length > 0) {
imgList.value.forEach((item, index) => {
imgobj["img" + (index + 1)] = item;
});
}
api
.addInterrogationAnswer({
step1_uuid: uuid.value,
note: note.value,
imgsBean: imgobj,
})
.then((res) => {
isLock.value = false;
if (res.code == 200) {
uni.$emit("updateStatus", 1);
uni.showToast({ title: "提交成功", icon: "none" });
uni.navigateBack();
}
});
};
const selectImages = (e) => {
let tempFilePaths = e.tempFiles;
console.log(e);
tempImageList.value = [...tempImageList.value, ...tempFilePaths];
for (let i = 0; i < tempFilePaths.length; i++) {
pathToBase64(tempFilePaths[i].url)
.then((base64) => {
console.log("转base64成功");
imgList.value.push(base64.split(",")[1]);
})
.catch((error) => {
console.log("转base64成功");
console.error(error);
});
}
console.log(tempImageList.value);
console.log(imgList.value);
};
const addImages = () => {
const remain = maxImages - imgList.value.length;
if (remain <= 0) {
uni.showToast({ title: "最多6张", icon: "none" });
return;
}
console.log("执行选择图片");
console.log('isAndroid:'+isAndroid)
if (isAndroid) {
uni.chooseImage({
count: remain,
sizeType: ["original", "compressed"],
sourceType: ["album", "camera"],
success: (res) => {
console.log("获取到照片");
console.log(res.tempFilePaths);
tempImageList.value = [...tempImageList.value, ...res.tempFilePaths];
for (let i = 0; i < res.tempFilePaths.length; i++) {
uni.saveFile({
tempFilePath: res.tempFilePaths[i],
success: (res1) => {
pathToBase64(res1.savedFilePath)
.then((base64) => {
console.log("转base64成功");
imgList.value.push(base64.split(",")[1]);
})
.catch((error) => {
console.log("转base64失败");
console.error(error);
});
}
})
}
},
});
} else {
uni.chooseImage({
count: remain,
sourceType: ["album", "camera"],
crop: {
width: 375,
height: 500,
resize: true,
quality:100,
},
success: (res) => {
console.log("获取到照片");
console.log(res.tempFilePaths);
tempImageList.value = [...tempImageList.value, ...res.tempFilePaths];
for (let i = 0; i < res.tempFilePaths.length; i++) {
uni.getImageInfo({
src: res.tempFilePaths[i],
success: (result) => {
console.log(result);
if (result.width > 1000) {
uni.compressImage({
quality:100,
src: res.tempFilePaths[i],
success: (result2) => {
console.log("压缩成功");
console.log(result2.tempFilePath);
pathToBase64(result2.tempFilePath)
.then((base64) => {
console.log("转base64成功");
imgList.value.push(base64.split(",")[1]);
})
.catch((error) => {
console.log("转base64失败");
console.error(error);
});
},
fail: (err) => {
console.log("压缩失败:" + err.errMsg);
},
});
} else {
pathToBase64(res.tempFilePaths[i])
.then((base64) => {
console.log("转base64成功");
imgList.value.push(base64.split(",")[1]);
})
.catch((error) => {
console.log("转base64失败");
console.error(error);
});
}
},
});
}
},
fail: (err) => {
console.log("获取图片信息失败:");
console.error(err.errMsg);
},
});
}
};
function preview(index) {
uni.previewImage({
urls: tempImageList.value,
current: index,
});
} }
function preview(index){ function remove(index) {
uni.previewImage({ imgList.value.splice(index, 1);
urls:imgList.map(path=> docUrl + path), tempImageList.value.splice(index, 1);
current: index
})
} }
const deleteImages = (e) => {
let index = e.index;
imgList.value.splice(index, 1);
tempImageList.value.splice(index, 1);
};
function remove(index){ function submit() {
imgList.valuesplice(index, 1) if (answer_uuid.value) {
} updateInterrogationAnswer();
} else {
function submit(){ addInterrogationAnswer();
if(!form.value.note.trim()){ }
uni.showToast({title:'请输入意见', icon:'none'})
return
}
if(answer_uuid.value){
updateInterrogationAnswer()
}else{
addInterrogationAnswer()
}
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.my-answer-page{ .my-answer-page {
background-color: #fff; background-color: #fff;
height: 100vh; height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.content-scroll{ .content-scroll {
flex: 1; flex: 1;
position: fixed; position: fixed;
top: 135rpx; top: calc(var(--status-bar-height) + 44px);
left: 0; left: 0;
right: 0; right: 0;
bottom: 160rpx; bottom: 160rpx;
padding: 24rpx 24rpx 0; padding: 24rpx 24rpx 0;
box-sizing: border-box; box-sizing: border-box;
} }
.card{ .card {
background: #fff; background: #fff;
border-radius: 16rpx; border-radius: 16rpx;
padding: 24rpx; padding: 24rpx;
margin-bottom: 24rpx; margin-bottom: 24rpx;
} }
.card-title{ .card-title {
font-size: 32rpx; font-size: 32rpx;
color: #8B2316; color: #8b2316;
font-weight: 500; font-weight: 500;
margin-bottom: 16rpx; margin-bottom: 16rpx;
} }
.required{ color: #ff4d4f; } .required {
color: #ff4d4f;
.textarea-wrap{
position: relative;
background: #f7f7f7;
border-radius: 12rpx;
padding: 16rpx 88rpx 16rpx 16rpx;
}
.textarea{
min-height: 180rpx;
width: 100%;
font-size: 28rpx;
color: #333;
}
.ph{ color:#999; }
.voice-btn{
position: absolute;
right: 16rpx;
bottom: 16rpx;
width: 64rpx;
height: 64rpx;
border-radius: 32rpx;
background: #b90f0f;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
} }
.sub-tip{ .textarea-wrap {
font-size: 26rpx; position: relative;
color: #999; background: #f7f7f7;
margin-bottom: 16rpx; border-radius: 12rpx;
padding: 16rpx 16rpx 16rpx 16rpx;
}
.textarea {
min-height: 180rpx;
width: 100%;
font-size: 28rpx;
color: #333;
}
.ph {
color: #999;
}
.voice-btn {
position: absolute;
right: 16rpx;
bottom: 16rpx;
width: 64rpx;
height: 64rpx;
border-radius: 32rpx;
background: #b90f0f;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
} }
.img-grid{ .sub-tip {
display: grid; font-size: 26rpx;
grid-template-columns: repeat(4, 1fr); color: #999;
gap: 16rpx; margin-bottom: 16rpx;
}
.img-item{
position: relative;
width:150rpx;
height: 150rpx;
border-radius: 12rpx;
overflow: hidden;
background: #f0f0f0;
}
.img{ width: 100%; height: 100%; }
.add{ display:flex; align-items:center; justify-content:center; }
.plus{ font-size: 80rpx; color:#c0c0c0; line-height: 1; }
.del{
position: absolute;
top: 8rpx;
right: 8rpx;
width: 44rpx;
height: 44rpx;
border-radius: 22rpx;
background: rgba(0,0,0,.5);
color: #fff;
text-align: center;
line-height: 44rpx;
font-size: 28rpx;
} }
.bottom-fixed{ .img-grid {
position: fixed; display: grid;
left: 0; right: 0; bottom: 0; grid-template-columns: repeat(4, 1fr);
background: #fff; gap: 16rpx;
padding: 24rpx;
box-shadow: 0 -2rpx 10rpx rgba(0,0,0,.06);
} }
.submit-btn{ .img-item {
width: 100%; position: relative;
height: 88rpx; width: 150rpx;
line-height: 88rpx; height: 150rpx;
text-align: center; border-radius: 12rpx;
background: #00bcd4; overflow: hidden;
color: #fff; background: #f0f0f0;
font-size: 32rpx; }
border-radius: 16rpx; .img {
width: 100%;
height: 100%;
}
.add {
display: flex;
align-items: center;
justify-content: center;
}
.plus {
font-size: 80rpx;
color: #c0c0c0;
line-height: 1;
}
.del {
position: absolute;
top: 8rpx;
right: 8rpx;
width: 44rpx;
height: 44rpx;
border-radius: 22rpx;
background: rgba(0, 0, 0, 0.5);
color: #fff;
text-align: center;
line-height: 44rpx;
font-size: 28rpx;
}
.bottom-fixed {
position: fixed;
left: 0;
right: 0;
bottom: 0;
background: #fff;
padding: 24rpx;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.06);
}
.submit-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
text-align: center;
background: #3cc7c0;
color: #fff;
font-size: 32rpx;
border-radius: 16rpx;
} }
</style> </style>
+15 -4
View File
@@ -1,13 +1,15 @@
<template> <template>
<view class="my-application-page"> <view class="my-application-page">
<!-- 顶部导航栏 --> <!-- 顶部导航栏 -->
<uni-nav-bar <view class="navBox">
<view class="status_bar"></view>
<uni-nav-bar
left-icon="left" left-icon="left"
title="我的应用" title="我的应用"
@clickLeft="goBack" @clickLeft="goBack"
fixed
color="#8B2316" color="#8B2316"
height="140rpx"
:border="false" :border="false"
backgroundColor="#eeeeee" backgroundColor="#eeeeee"
> >
@@ -17,6 +19,7 @@
}}</text> }}</text>
</template> </template>
</uni-nav-bar> </uni-nav-bar>
</view>
<scroll-view class="content" scroll-y> <scroll-view class="content" scroll-y>
<!-- 我的应用部分 --> <!-- 我的应用部分 -->
@@ -102,6 +105,14 @@
// 获取用户图标数据 // 获取用户图标数据
const addUserIcon = () => { const addUserIcon = () => {
if(myApps.value.length <3){
uni.showToast({
title: '至少添加3个应用',
icon: 'none',
duration: 1500
});
return false;
}
api.addUserIcon({ api.addUserIcon({
idStr:myApps.value.map(item => item.id).join(',') idStr:myApps.value.map(item => item.id).join(',')
}).then(res => { }).then(res => {
@@ -190,7 +201,7 @@
navTo({ navTo({
url:url url:url
}) })
}else if(app.name=="肝病新闻"){ }else if(app.name=="肝胆新闻"){
url='/pages_app/news/news' url='/pages_app/news/news'
navTo({ navTo({
url:url url:url
+670 -86
View File
@@ -1,13 +1,45 @@
<template> <template>
<view class="my-code-page"> <view class="navbox" v-if="!jieping">
<view class="status_bar"></view>
<uni-nav-bar
left-icon="left"
title="我的二维码"
@clickLeft="goBack"
color="#8B2316"
:border="false"
backgroundColor="#eeeeee"
>
<template #right>
<view class="nav-actions">
<view class="collect-img" @click="shareToggle" >
<image
class="share-img-icon"
:src="shareIcon"
mode="aspectFill"
/>
</view>
</view>
</template>
</uni-nav-bar>
</view>
<!-- <navBar title="我的二维码" v-if="!jieping"/> -->
<!-- 自己实现的 html2canvas 截图组件 -->
<MyHtml2canvas
ref="shotRef"
domId="render-dom"
@renderOver="onRenderOver"
/>
<view class="my-code-page renderDom" id="render-dom" :class="{'active':jieping}">
<!-- <webview src="https://dev-wx.igandan.com/expert/expertcodeimg?expert_uuid=9UFkll2Xo57km6224XE&fromtype=doctor"></webview> -->
<!-- 顶部导航栏 --> <!-- 顶部导航栏 -->
<navBar title="我的二维码" />
<!-- 内容 --> <!-- 内容 -->
<scroll-view scroll-y class="page-scroll"> <scroll-view scroll-y class="page-scroll" id="pageContent">
<!-- 顶部蓝色横幅 --> <!-- 顶部蓝色横幅 -->
<view class="blue-banner"> <view class="blue-banner">
<up-image :src="bgImg" width="100%" mode="widthFix" ></up-image> <up-image :src="bgBase64 || bgImg" width="100%" mode="widthFix" ></up-image>
</view> </view>
<!-- 白色信息卡 --> <!-- 白色信息卡 -->
@@ -16,10 +48,17 @@
<view class="leftCircle"></view> <view class="leftCircle"></view>
<view class="rightCircle"></view> <view class="rightCircle"></view>
<view class="halfCircle"></view> <view class="halfCircle"></view>
<view class="avatar-wrapper"> <view class="star" v-if="userInfo.isStar==1">
<image class="avatar" :src="docUrl+userInfo.photo" mode="aspectFill" /> 咨询专家
</view> </view>
<view class="name-viewne">{{ userInfo.realName }} {{ userInfo.positionName }}</view> <view class="avatar-wrapper">
<image
class="avatar"
:src="userInfo.avatarBase64 || userInfo.localPhoto || (docUrl+userInfo.photo)"
mode="aspectFill"
/>
</view>
<view class="name-viewne">{{ userInfo.realName }} <text class="position-name">{{ userInfo.positionName }}</text></view>
<view class="org-viewne">{{ userInfo.hospitalName }}</view> <view class="org-viewne">{{ userInfo.hospitalName }}</view>
<view class="dash-viewne"></view> <view class="dash-viewne"></view>
<view class="slogan"> <view class="slogan">
@@ -28,10 +67,10 @@
</view> </view>
<view class="contact-qr"> <view class="contact-qr">
<view class="contact-btn"> <view class="contact-btn">
<up-image :src="viewnkImg" width="430rpx" height="131rpx" ></up-image> <up-image :src="viewnkBase64 || viewnkImg" width="430rpx" height="131rpx" ></up-image>
</view> </view>
<image class="qr-img" :src="docUrl+userInfo.qrcode" mode="aspectFit" /> <image class="qr-img" :src="userInfo.localQrcode || (docUrl+userInfo.qrcode)" mode="aspectFit" />
</view> </view>
</view> </view>
</view> </view>
@@ -44,8 +83,8 @@
<view class="s-bottom"> <view class="s-bottom">
<view> <view>
<view class="descrow"><text>1</text>微信扫一扫上方二维码,关注“肝胆相照一家人”公众号</view> <view class="descrow"><text>1</text>微信扫一扫上方二维码,关注“肝胆相照一家人”公众号</view>
<view class="descrow"><text>2</text>点击“邹建东专家工作室”,选择微信登录,注册后直接发送随访申请</view> <view class="descrow"><text>2</text>点击“{{ userInfo.realName }}专家工作室”,选择微信登录,注册后直接发送随访申请</view>
<view class="descrow"><text>3</text>若未弹出“随访申请发送成功”提示,请再次点击“邹建东专家工作室”发送随访申请</view> <view class="descrow"><text>3</text>若未弹出“随访申请发送成功”提示,请再次点击“{{ userInfo.realName }}专家工作室”发送随访申请</view>
<view class="descrow"><text>4</text>审核通过后,点击“就医服务-随访交流”,与专家进行图文交流</view> <view class="descrow"><text>4</text>审核通过后,点击“就医服务-随访交流”,与专家进行图文交流</view>
</view> </view>
</view> </view>
@@ -58,100 +97,470 @@
</scroll-view> </scroll-view>
<!-- 底部保存按钮 --> <!-- 底部保存按钮 -->
<view class="save-bar"> <view class="save-bar" v-if="!jieping">
<button class="save-btn" @click="onSave">保存二维码到手机</button> <button class="save-btn" @click="onSave">保存到手机相册</button>
</view> </view>
</view> </view>
<!-- 分享弹窗 -->
<uni-popup ref="shareRef" type="bottom" safeArea backgroundColor="#fff">
<view class="share-popup">
<view class="share-title">分享到</view>
<view class="share-content">
<view class="share-item" @click="shareToWechat">
<view class="share-icon wechat-icon">
<image class="share-img" :src="wxImg" mode="aspectFill" />
</view>
<text class="share-text">微信</text>
</view>
<view class="share-item" @click="shareToMoments">
<view class="share-icon moments-icon">
<image class="share-img" :src="friendImg" mode="aspectFill" />
</view>
<text class="share-text">朋友圈</text>
</view>
<view class="share-item" @click="shareToWeibo">
<view class="share-icon weibo-icon">
<image class="share-img" :src="sinaImg" mode="aspectFill" />
</view>
<text class="share-text">新浪微博</text>
</view>
</view>
<view class="share-cancel" @click="closeShare">
<text>取消</text>
</view>
</view>
</uni-popup>
</template> </template>
<script setup> <script setup >
import { base64ToPath } from 'image-tools'
import { urlToBase64 } from '@/uni_modules/sp-html2canvas-render/utils/index.js'
import navBar from '@/components/navBar/navBar.vue' import navBar from '@/components/navBar/navBar.vue'
import { onShow } from "@dcloudio/uni-app"; import { onShow,onBackPress,onLoad} from "@dcloudio/uni-app";
import { ref } from 'vue'; import { ref } from 'vue';
import MyHtml2canvas from '@/components/MyHtml2canvas.vue'
import docUrl from '@/utils/docUrl' import docUrl from '@/utils/docUrl'
import bgImg from "@/static/background.jpg" import otherHost from '@/utils/otherHost'
import shareIcon from "@/static/icon_share.png";
import sinaImg from "@/static/share_sina.png";
import wxImg from "@/static/share_weixin.png";
import friendImg from "@/static/share_wxc.png";
import logoImg from "@/static/weiboShare.png";
import isAndroid from "@/utils/platform.js";
// 背景图使用线上地址
const bgImg = 'https://oss.igandan.com/images/expertcode_background.jpg?v=1'
import viewnkImg from "@/static/arr.png" import viewnkImg from "@/static/arr.png"
const userInfo = ref({}) const userInfo = ref({})
const jieping = ref(false)
const shotRef = ref(null)
const bgBase64 = ref('')
const viewnkBase64 = ref('')
const from=ref('');
const title=ref('');
const summary=ref('参与爱肝行动,共创美好前程');
const link=ref('');
onBackPress(() => {
if(!from.value){
plus.runtime.quit();
return true;
}
});
const goBack = () => { const goBack = () => {
uni.navigateBack(); if(!from.value){
plus.runtime.quit();
return true;
}else{
uni.navigateBack();
}
}; };
onShow(()=>{ onLoad((options) => {
userInfo.value = uni.getStorageSync('userInfo') from.value = options.from;
uni.sendNativeEvent("portraitReport",{
msg:{
event_nickname: "list_page",
page_type: "我的二维码",
},
});
}) })
const onSave = () => {
// 检查是否有二维码图片 // 页面显示时拉取用户信息,并尽量把图片转成 base64,避免 html2canvas 跨域污染
if (!userInfo.value.qrcode) { onShow(async () => {
uni.showToast({ title: '二维码不存在', icon: 'none' }); const info = uni.getStorageSync('userInfo') || {}
return; console.log('[myCode] onShow userInfo:', info)
title.value = '不方便到医院就诊,'+info.realName+'医生在肝胆相照平台帮助您!';
link.value = otherHost+'/expert/expertcodeimg?expert_uuid='+info.uuid+'&fromtype=doctor'
// 背景图转 base64
if (!bgBase64.value) {
try {
console.log('[myCode] 准备将背景图转为 base64:', bgImg)
const bg = await urlToBase64(bgImg, 'jpg')
bgBase64.value = bg
console.log('[myCode] 背景图转 base64 成功,长度:', bg && bg.length)
} catch (e) {
console.error('[myCode] 背景图转 base64 失败:', e)
}
} }
// 显示加载提示 // 按钮图转 base64
uni.showLoading({ title: '保存中...' }); if (!viewnkBase64.value) {
try {
// 下载二维码图片 console.log('[myCode] 准备将按钮图转为 base64:', viewnkImg)
uni.downloadFile({ const btn = await urlToBase64(viewnkImg, 'png')
url: docUrl + userInfo.value.qrcode, viewnkBase64.value = btn
success: (res) => { console.log('[myCode] 按钮图转 base64 成功,长度:', btn && btn.length)
if (res.statusCode === 200) { } catch (e) {
// 保存到相册 console.error('[myCode] 按钮图转 base64 失败:', e)
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: () => {
uni.hideLoading();
uni.showToast({
title: '保存成功',
icon: 'none',
duration: 2000
});
},
fail: (err) => {
uni.hideLoading();
console.error('保存失败:', err);
// 根据错误类型给出不同提示
if (err.errMsg.includes('auth deny') || err.errMsg.includes('authorize')) {
uni.showModal({
title: '权限提示',
content: '需要相册权限才能保存图片,请在设置中开启权限',
showCancel: false,
confirmText: '知道了'
});
} else {
uni.showToast({
title: '保存失败,请重试',
icon: 'none',
duration: 2000
});
}
}
});
} else {
uni.hideLoading();
uni.showToast({
title: '下载失败,请检查网络',
icon: 'none',
duration: 2000
});
}
},
fail: (err) => {
uni.hideLoading();
console.error('下载失败:', err);
uni.showToast({
title: '下载失败,请检查网络',
icon: 'none',
duration: 2000
});
} }
}); }
// 头像:远程地址转 base64,写入 avatarBase64,优先被使用
if (info.photo && !info.avatarBase64) {
const avatarUrl = docUrl + info.photo
try {
console.log('[myCode] 准备将头像转为 base64:', avatarUrl)
const avatarBase64 = await urlToBase64(avatarUrl, 'jpg')
info.avatarBase64 = avatarBase64
console.log('[myCode] 头像转 base64 成功,长度:', avatarBase64 && avatarBase64.length)
} catch (e) {
console.error('[myCode] 头像转 base64 失败:', e)
}
}
// 二维码:远程地址转 base64,写入 localQrcode,模板已优先使用该字段
if (info.qrcode && !info.localQrcode) {
const qrUrl = docUrl + info.qrcode
try {
console.log('[myCode] 准备将二维码转为 base64:', qrUrl)
const qrBase64 = await urlToBase64(qrUrl, 'png')
info.localQrcode = qrBase64
console.log('[myCode] 二维码转 base64 成功,长度:', qrBase64 && qrBase64.length)
} catch (e) {
console.error('[myCode] 二维码转 base64 失败:', e)
}
}
userInfo.value = info
})
// 保存失败时统一处理
const handleSaveError = (err) => {
if (err && err.errMsg && (err.errMsg.includes('auth deny') || err.errMsg.includes('authorize'))) {
uni.showModal({
title: '权限提示',
content: '需要相册权限才能保存图片,请在系统设置中开启相册权限',
showCancel: false,
confirmText: '知道了'
});
} else {
uni.showToast({
title: '保存失败,请重试',
icon: 'none',
duration: 2000
});
}
};
// 点击“保存到手机相册”
const onSave = () => {
console.log('[myCode] onSave 点击,准备开始截图')
// 使用自定义组件触发 html2canvas(内部通过 renderjs 在视图层执行)
jieping.value = true
// 调用自定义组件方法开始渲染
if (shotRef.value && shotRef.value.h2cRenderDom) {
shotRef.value.h2cRenderDom()
} else {
console.error('[myCode] shotRef 或 h2cRenderDom 不存在')
jieping.value = false
uni.showToast({
title: '截图组件未准备好',
icon: 'none'
})
}
};
// 自定义组件截图完成回调(拿到 base64)
const onRenderOver = (base64) => {
console.log('[myCode] onRenderOver base64 长度:', base64 && base64.length)
if (!base64) {
jieping.value = false
uni.showToast({
title: '生成图片失败',
icon: 'none'
})
return
}
base64ToPath(base64)
.then((res) => {
const path = res.path || res
console.log('[myCode] base64ToPath 转换成功,path:', path)
uni.saveImageToPhotosAlbum({
filePath: path,
success() {
uni.showToast({
title: '保存图片成功',
mask: false,
duration: 1500
})
},
fail(err) {
console.error('[myCode] 保存到相册失败:', err)
handleSaveError(err)
},
complete() {
jieping.value = false
}
})
})
.catch((err) => {
console.error('[myCode] base64ToPath 失败:', err)
handleSaveError(err)
jieping.value = false
})
}
const shareRef = ref();
// 分享APP
const shareToggle = () => {
shareRef.value.open();
};
// 关闭分享弹窗
const closeShare = () => {
shareRef.value.close();
};
// 分享到微信
const shareToWechat = () => {
// #ifdef APP-PLUS
// 使用系统分享
uni.downloadFile({
url:'https://doc.igandan.com/app/html/img/2016/20160714132557.png',
success: function (res) {
uni.compressImage({
src: res.tempFilePath,
quality: 60,
success: function (res2) {
uni.share({
provider: "weixin",
scene: "WXSceneSession",
type: 0,
title: title.value,
summary: summary.value,
href: link.value,
imageUrl: res2.tempFilePath,
success: function (res) {
console.log("success:" + JSON.stringify(res));
},
fail: function (err) {
console.log("fail:" + JSON.stringify(err));
},
});
},
});
},
});
// uni.share({
// provider: "weixin",
// scene: "WXSceneSession",
// type: 0,
// title: videoInfo.value.name,
// summary: videoInfo.value.note,
// href: shareLink.value,
// imageUrl: docUrl + videoInfo.value.imgpath,
// success: function (res) {
// //console.log("success:" + JSON.stringify(res));
// },
// fail: function (err) {
// //console.log("fail:" + JSON.stringify(err));
// },
// });
// #endif
// #ifdef H5
// H5环境下的分享
if (navigator.share) {
navigator
.share({
title: "肝胆相照APP",
text: "专业的医疗健康平台,快来下载体验吧!",
url: shareLink.value,
})
.then(() => {
uni.showToast({
title: "分享成功",
icon: "success",
});
})
.catch(() => {
uni.showToast({
title: "分享失败",
icon: "none",
});
});
} else {
// 复制到剪贴板
uni.setClipboardData({
data:
"肝胆相照APP - 专业的医疗健康平台,快来下载体验吧!\n下载链接:" +
shareLink.value,
success: () => {
uni.showToast({
title: "已复制到剪贴板",
icon: "success",
});
},
});
}
// #endif
// #ifdef MP-WEIXIN
// 微信小程序分享
uni.showShareMenu({
withShareTicket: true,
menus: ["shareAppMessage", "shareTimeline"],
});
// #endif
closeShare();
};
// 分享到朋友圈
const shareToMoments = () => {
// #ifdef APP-PLUS
uni.downloadFile({
url: 'https://doc.igandan.com/app/html/img/2016/20160714132557.png',
success: function (res) {
uni.compressImage({
src: res.tempFilePath,
quality: 60,
success: function (res2) {
uni.share({
provider: "weixin",
scene: "WXSceneTimeline",
type: 0,
title: title.value,
summary: summary.value,
href: link.value,
imageUrl: res2.tempFilePath,
success: function (res) {
//console.log("success:" + JSON.stringify(res));
},
fail: function (err) {
//console.log("fail:" + JSON.stringify(err));
},
});
},
});
},
});
// #endif
// #ifdef H5
// 复制到剪贴板
uni.setClipboardData({
data: "肝胆相照APP - 专业的医疗健康平台,快来下载体验吧!\n下载链接:https://www.igandan.com",
success: () => {
uni.showToast({
title: "已复制到剪贴板,可分享到朋友圈",
icon: "success",
});
},
});
// #endif
// #ifdef MP-WEIXIN
uni.showShareMenu({
withShareTicket: true,
menus: ["shareAppMessage", "shareTimeline"],
});
// #endif
closeShare();
};
// 分享到新浪微博
const shareToWeibo = () => {
// #ifdef APP-PLUS
uni.share({
provider: "sinaweibo",
type: 0,
title: title.value,
summary: summary.value,
href: link.value,
imageUrl: logoImg,
success: function (res) {
//console.log("分享成功");
},
fail: function (err) {
//console.log("fail:" + JSON.stringify(err));
},
});
// plus.share.sendWithSystem({
// type: 'text',
// content: '肝胆相照APP - 专业的医疗健康平台,快来下载体验吧!\n下载链接:https://www.igandan.com'
// }, () => {
// uni.showToast({
// title: '分享成功',
// icon: 'success'
// })
// }, (err) => {
// console.log('分享失败:', err)
// uni.showToast({
// title: '分享失败',
// icon: 'none'
// })
// })
// #endif
// #ifdef H5
// 复制到剪贴板
uni.setClipboardData({
data: "肝胆相照APP - 专业的医疗健康平台,快来下载体验吧!\n下载链接:https://www.igandan.com",
success: () => {
uni.showToast({
title: "已复制到剪贴板,可分享到微博",
icon: "success",
});
},
});
// #endif
closeShare();
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.share-img {
width: 100rpx;
height: 100rpx;
}
.share-img-icon {
width: 40rpx;
height:40rpx;
}
.nav-actions {
display: flex;
align-items: center;
justify-content: center;
}
.my-code-page { .my-code-page {
min-height: 100vh; min-height: 100vh;
background-color: #0d7dfd; background-color: #0d7dfd;
.active{
transform: scale(0.8);
margin-top: -calc(var(--status-bar-height) + 44px);
transition: all 0.3s ease-in-out;
} }
}
.squrebox{ .squrebox{
margin:0 30rpx; margin:0 30rpx;
@@ -226,7 +635,8 @@ const onSave = () => {
} }
} }
.page-scroll { .page-scroll {
height: calc(100vh - 100rpx); margin-top: calc(var(--status-bar-height) + 44px);
// height: calc(100vh - var(--status-bar-height) - 44px);
padding-bottom: 140rpx; padding-bottom: 140rpx;
} }
@@ -251,7 +661,7 @@ const onSave = () => {
.qr-card { .qr-card {
position: relative; position: relative;
margin: -120rpx 30rpx 30rpx; margin: -80rpx 30rpx 30rpx;
background: #ffffff; background: #ffffff;
border-radius: 20rpx; border-radius: 20rpx;
padding: 140rpx 30rpx 30rpx; padding: 140rpx 30rpx 30rpx;
@@ -275,6 +685,22 @@ const onSave = () => {
border-radius: 50%; border-radius: 50%;
background-color: #0d7dfd; background-color: #0d7dfd;
} }
.star{
position: absolute;
top: 48rpx;
height: 40rpx;
padding:0 16rpx;
left: 50%;
transform: translateX(-50%);
background-color:#ff9f1a;
color: #fff;
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
z-index:10;
font-size: 24rpx;
}
.halfCircle{ .halfCircle{
border-radius: 50%; border-radius: 50%;
position: absolute; position: absolute;
@@ -312,13 +738,17 @@ const onSave = () => {
.name-viewne { .name-viewne {
text-align: center; text-align: center;
font-size: 34rpx; font-size: 36rpx;
color: #1a76d2; color: #1a76d2;
font-weight: bold;
margin-top: 10rpx; margin-top: 10rpx;
.position-name{
font-weight: normal;
}
} }
.org-viewne { .org-viewne {
text-align: center; text-align: center;
font-size: 28rpx; font-size: 36rpx;
color: #4a90e2; color: #4a90e2;
margin-top: 16rpx; margin-top: 16rpx;
} }
@@ -400,4 +830,158 @@ const onSave = () => {
font-weight: normal; font-weight: normal;
} }
} }
/* 分享弹窗样式 */
.share-popup {
background-color: #fff;
border-radius: 20rpx 20rpx 0 0;
padding: 40rpx 0 0;
}
.share-title {
text-align: center;
font-size: 32rpx;
color: #333;
margin-bottom: 40rpx;
font-weight: 500;
}
.share-content {
display: flex;
justify-content: space-around;
padding: 0 40rpx 40rpx;
}
.share-item {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
}
.share-icon {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20rpx;
}
.wechat-icon {
background-color: #07c160;
}
.moments-icon {
background-color: #07c160;
}
.weibo-icon {
background-color: #e6162d;
}
.qq-icon {
background-color: #12b7f5;
}
.share-icon-text {
font-size: 50rpx;
color: #fff;
font-weight: bold;
}
.share-text {
font-size: 24rpx;
color: #666;
}
.share-cancel {
height: 100rpx;
display: flex;
align-items: center;
justify-content: center;
border-top: 1rpx solid #f0f0f0;
font-size: 32rpx;
color: #333;
}
.share-cancel:active {
background-color: #f5f5f5;
}
// 删除确认弹窗
.delete-modal-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
}
.delete-modal-content {
width: 600rpx;
background-color: #fff;
border-radius: 24rpx;
padding: 60rpx 40rpx 40rpx;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.15);
}
.delete-modal-title {
font-size: 36rpx;
font-weight: 600;
color: #333;
text-align: center;
margin-bottom: 30rpx;
}
.delete-modal-text {
font-size: 28rpx;
color: #666;
text-align: center;
line-height: 1.6;
margin-bottom: 50rpx;
word-break: break-all;
}
.delete-modal-buttons {
display: flex;
gap: 20rpx;
}
.delete-modal-btn {
flex: 1;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 12rpx;
font-size: 32rpx;
font-weight: 500;
transition: all 0.3s ease;
}
.cancel-btn {
background-color: #f5f5f5;
color: #333;
&:active {
background-color: #e0e0e0;
}
}
.confirm-btn {
background-color: #8b2316;
color: #fff;
&:active {
background-color: #6b1a0f;
}
}
</style> </style>
+3 -3
View File
@@ -7,7 +7,7 @@
@clickLeft="goBack" @clickLeft="goBack"
fixed fixed
color="#8B2316" color="#8B2316"
height="140rpx" height="180rpx"
:border="false" :border="false"
backgroundColor="#eeeeee" backgroundColor="#eeeeee"
> >
@@ -265,9 +265,9 @@ onMounted(() => {
} }
.journal-list { .journal-list {
height: calc(100vh - 140rpx); height: calc(100vh - 180rpx);
position: fixed; position: fixed;
top: 140rpx; top: 180rpx;
bottom: 0; bottom: 0;
width: 100%; width: 100%;
padding-bottom: 100rpx; /* 为底部操作栏留出空间 */ padding-bottom: 100rpx; /* 为底部操作栏留出空间 */
+3 -3
View File
@@ -7,7 +7,7 @@
@clickLeft="goBack" @clickLeft="goBack"
fixed fixed
color="#8B2316" color="#8B2316"
height="140rpx" height="180rpx"
:border="false" :border="false"
backgroundColor="#eeeeee" backgroundColor="#eeeeee"
> >
@@ -166,9 +166,9 @@ onMounted(() => {
} }
.document-list { .document-list {
height: calc(100vh - 140rpx); height: calc(100vh - 180rpx);
position: fixed; position: fixed;
top: 140rpx; top: 180rpx;
bottom: 0; bottom: 0;
width: 100%; width: 100%;
} }
+2 -2
View File
@@ -7,7 +7,7 @@
@clickLeft="goBack" @clickLeft="goBack"
fixed fixed
color="#8B2316" color="#8B2316"
height="140rpx" height="180rpx"
:border="false" :border="false"
backgroundColor="#eeeeee" backgroundColor="#eeeeee"
> >
@@ -172,7 +172,7 @@ const onLoadMore = () => {
} }
.history-list { .history-list {
height: calc(100vh - 140rpx); height: calc(100vh - 180rpx);
position: fixed; position: fixed;
top:140rpx; top:140rpx;
bottom:0; bottom:0;
+1 -1
View File
@@ -8,7 +8,7 @@
@clickLeft="goBack()" @clickLeft="goBack()"
fixed fixed
color="#8B2316" color="#8B2316"
height="140rpx" height="180rpx"
:border="false" :border="false"
backgroundColor="#eeeeee" backgroundColor="#eeeeee"
> >
+3 -3
View File
@@ -7,7 +7,7 @@
@clickLeft="goBack" @clickLeft="goBack"
fixed fixed
color="#8B2316" color="#8B2316"
height="140rpx" height="180rpx"
:border="false" :border="false"
backgroundColor="#eeeeee" backgroundColor="#eeeeee"
> >
@@ -177,9 +177,9 @@ onMounted(() => {
} }
.news-list { .news-list {
height: calc(100vh - 140rpx); height: calc(100vh - 180rpx);
position: fixed; position: fixed;
top: 140rpx; top: 180rpx;
bottom: 0; bottom: 0;
width: 100%; width: 100%;
} }
+2 -2
View File
@@ -7,7 +7,7 @@
@clickLeft="goBack" @clickLeft="goBack"
fixed fixed
color="#8B2316" color="#8B2316"
height="140rpx" height="180rpx"
:border="false" :border="false"
backgroundColor="#eeeeee" backgroundColor="#eeeeee"
> >
@@ -172,7 +172,7 @@ const onLoadMore = () => {
} }
.history-list { .history-list {
height: calc(100vh - 140rpx); height: calc(100vh - 180rpx);
position: fixed; position: fixed;
top:140rpx; top:140rpx;
bottom:0; bottom:0;
+218
View File
@@ -0,0 +1,218 @@
<template>
<view class="detail-page">
<navBar title="订单详情" />
<view class="order-card">
<view class="order-top">
<text class="download-no">下载课件号: {{ detail.downloadNo || "--" }}</text>
<text class="pay-status">{{ detail.statusText || "--" }}</text>
</view>
<view class="section-divider"></view>
<view class="course-title">{{ detail.title || "--" }}</view>
<view class="info-list">
<view class="info-row">
<text class="info-label">课件作者:</text>
<text class="info-value">{{ detail.author || "--" }}</text>
</view>
<view class="info-row">
<text class="info-label">金额:</text>
<text class="info-value amount">{{ detail.amountText || "--" }}</text>
</view>
<view class="info-row">
<text class="info-label">创建时间:</text>
<text class="info-value">{{ detail.createTime || "--" }}</text>
</view>
<view class="info-row">
<text class="info-label">支付方式:</text>
<text class="info-value">{{ detail.payType}}</text>
</view>
<view class="info-row">
<text class="info-label">支付时间:</text>
<text class="info-value">{{ detail.payTime || "--" }}</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import api from "@/api/api.js";
const coursewareId = ref("");
const detail = ref({
downloadNo: "",
statusText: "",
title: "",
author: "",
amountText: "",
createTime: "",
payType: "",
payTime: "",
});
const formatDateTime = (value) => {
if (!value) return "";
if (typeof value === "string" && value.includes("-")) return value;
const timeNum = Number(value);
if (!timeNum) return "";
const ms = timeNum < 10000000000 ? timeNum * 1000 : timeNum;
const d = new Date(ms);
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, "0");
const day = String(d.getDate()).padStart(2, "0");
const h = String(d.getHours()).padStart(2, "0");
const min = String(d.getMinutes()).padStart(2, "0");
const s = String(d.getSeconds()).padStart(2, "0");
return `${y}-${m}-${day} ${h}:${min}:${s}`;
};
const formatAmount = (value) => {
if (value === undefined || value === null || value === "") return "";
const num = Number(value);
if (Number.isNaN(num)) return "";
const amount = num / 100;
return `${amount.toFixed(2)}元`;
};
const formatStatus = (raw) => {
if (raw === undefined || raw === null) return "";
const text = String(raw).toLowerCase();
if (text === "paid" || text === "success" || text === "1" || text === "2") return "已支付";
if (text === "unpaid" || text === "0" || text === "created") return "未支付";
return String(raw);
};
const formatPayType = (raw) => {
if(raw=='balance'){
return '余额支付';
}else if(raw=='FREE'){
return '免费'
}else if(raw=='USEWELFARENUM'){
return '免费次数';
}else if(raw=='wx'){
return '微信支付';
}
};
const fetchCoursewareDetail = async () => {
if (!coursewareId.value) return;
try {
const res = await api.getGanDanFileOrder({
order_id: coursewareId.value,
});
if (res.code !== 200 || !res.data) return;
const data = res.data;
detail.value = {
downloadNo: data.order_id || data.download_no || data.gandanfile_order_id || data.id || "",
statusText: formatStatus(data.order_status || data.status || data.pay_status || ""),
title: data.title || data.name || data.file_name || "",
author: data.provider_name || data.author || data.nickname || "",
amountText: formatAmount(data.pay_amount || data.total_fee || data.amount || data.price || 0),
createTime: formatDateTime(data.create_date || data.create_time || data.order_time || ""),
payType: formatPayType(data.pay_channel),
payTime: formatDateTime(data.pay_date || data.pay_time || ""),
};
} catch (error) {
uni.showToast({
title: "获取详情失败",
icon: "none",
});
}
};
onLoad((options) => {
coursewareId.value = options.id;
fetchCoursewareDetail();
});
</script>
<style lang="scss" scoped>
.detail-page {
background: #f5f5f5;
}
.order-card {
background: #fff;
top: calc(var(--status-bar-height) + 44px);
position: fixed;
left: 0;
right:0;
z-index: 10;
overflow-y: scroll;
}
.order-top {
min-height: 88rpx;
padding: 0 30rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.download-no {
font-size: 30rpx;
color: #000;
}
.pay-status {
font-size: 32rpx;
color: #8b2316;
}
.section-divider {
height: 16rpx;
background: #f5f5f5;
}
.course-title {
padding: 20rpx 30rpx;
color: #8b2316;
font-size: 32rpx;
line-height: 1.5;
word-break: break-all;
border-bottom: 2rpx solid #ccc;
}
.info-list {
padding: 24rpx 30rpx;
}
.info-row {
min-height: 56rpx;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10rpx;
}
.info-row:last-child {
margin-bottom: 0;
}
.info-label {
font-size: 30rpx;
color: #000;
}
.info-value {
max-width: 68%;
font-size: 28rpx;
color: #666;
text-align: right;
word-break: break-all;
}
.amount {
color:red;
}
</style>
+99 -67
View File
@@ -1,18 +1,18 @@
<template> <template>
<view class="my-courseware-page"> <!-- <navBar title="我的课件" /> -->
<!-- 顶部导航栏 --> <view class="navbox">
<uni-nav-bar <view class="status_bar"></view>
<uni-nav-bar
left-icon="left" left-icon="left"
title="我的课件" title="我的课件"
@cviewckLeft="goBack" @clickLeft="goBack"
fixed
color="#8B2316" color="#8B2316"
height="140rpx"
:border="false" :border="false"
backgroundColor="#eeeeee" backgroundColor="#eeeeee"
> />
</view>
</uni-nav-bar> <view class="my-courseware-page">
<!-- 顶部导航栏 -->
<!-- 标签页导航 --> <!-- 标签页导航 -->
<view class="tab-nav"> <view class="tab-nav">
@@ -37,11 +37,11 @@
<view class="summary-bar"> <view class="summary-bar">
<view class="summary-item"> <view class="summary-item">
<up-image :src="downLoadImg" width="36rpx" height="36rpx" ></up-image> <up-image :src="downLoadImg" width="36rpx" height="36rpx" ></up-image>
<text class="summary-text">{{ activeTab === 'download' ? '下载账户' : '分享账户' }}: {{ downloadCount }}</text> <text class="summary-text">{{ totalAmount}}</text>
</view> </view>
<view class="summary-item"> <view class="summary-item">
<up-image :src="moneyImg" width="36rpx" height="36rpx" ></up-image> <up-image :src="moneyImg" width="36rpx" height="36rpx" ></up-image>
<text class="summary-text">{{ activeTab === 'download' ? '文件数量' : '分享文件' }}: {{ totalAmount }}</text> <text class="summary-text">{{ downloadCount }}</text>
</view> </view>
</view> </view>
@@ -49,12 +49,15 @@
<scroll-view <scroll-view
scroll-y scroll-y
class="courseware-list" class="courseware-list"
:scroll-top="listScrollTop"
:scroll-into-view="scrollIntoViewId"
refresher-enabled refresher-enabled
@refresherrefresh="onRefresh" @refresherrefresh="onRefresh"
:refresher-triggered="refreshing" :refresher-triggered="refreshing"
@scrolltolower="onLoadMore" @scrolltolower="onLoadMore"
:lower-threshold="100" :lower-threshold="100"
> >
<view id="courseware-top"></view>
<!-- 空状态 --> <!-- 空状态 -->
<view v-if="coursewareList.length === 0 && !loading" class="empty-state"> <view v-if="coursewareList.length === 0 && !loading" class="empty-state">
<text>{{ activeTab === 'download' ? '暂无下载数据' : '暂无分享数据' }}</text> <text>{{ activeTab === 'download' ? '暂无下载数据' : '暂无分享数据' }}</text>
@@ -62,21 +65,22 @@
<view class="courseware-item" v-for="(item, index) in coursewareList" :key="index" @click="onItemClick(item)"> <view class="courseware-item" v-for="(item, index) in coursewareList" :key="index" @click="onItemClick(item)">
<view class="item-content"> <view class="item-content">
<view class="courseware-provider" v-if="activeTab === 'share'">
<text class="label">下载医生</text>
<text class="value">:{{ item.providername }}</text>
</view>
<view class="courseware-name"> <view class="courseware-name">
<text class="label">课件名称:</text> <text class="label">课件名称</text>
<text class="value">{{ item.name }}</text> <text class="value">:{{ item.name }}</text>
</view>
<view class="courseware-provider" v-if="item.providername">
<text class="label">{{ activeTab === 'download' ? '提供者' : '下载者' }}:</text>
<text class="value">{{ item.providername }}</text>
</view> </view>
<view class="courseware-time"> <view class="courseware-time">
<text class="label">时间:</text> <text class="label">时间</text>
<text class="value">{{ item.time }}</text> <text class="value">:{{ item.time }}</text>
</view> </view>
<view class="courseware-status"> <view class="courseware-status" v-if="activeTab === 'download'">
<text class="label">状态:</text> <text class="label">状态</text>
<text class="value status-paid">{{ item.status }}</text> <text class="value status-paid">:{{item.status}}</text>
</view> </view>
</view> </view>
</view> </view>
@@ -95,31 +99,76 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from 'vue'; import { ref, onMounted, nextTick } from 'vue';
import api from '@/api/api'; import api from '@/api/api';
import navTo from '@/utils/navTo.js';
import { onLoad,onBackPress } from '@dcloudio/uni-app';
import downLoadImg from "@/static/course_download.png" import downLoadImg from "@/static/course_download.png"
import moneyImg from "@/static/course_yuan.png" import moneyImg from "@/static/course_yuan.png"
const activeTab = ref('download'); const activeTab = ref('download');
const listScrollTop = ref(0);
const scrollIntoViewId = ref('');
const refreshing = ref(false); const refreshing = ref(false);
const loading = ref(false); const loading = ref(false);
const noMore = ref(false); const noMore = ref(false);
const page = ref(1); const page = ref(1);
const pageSize = ref(10); const pageSize = ref(10);
const downloadCount = ref(4); const downloadCount = ref(0);
const totalAmount = ref('20.00'); const totalAmount = ref(0);
const coursewareList = ref([]); const coursewareList = ref([]);
const from = ref('');
onBackPress(() => {
if(!from.value){
plus.runtime.quit();
return true;
}
});
onLoad((options) => {
if(options.from){
from.value = options.from;
}
uni.sendNativeEvent("portraitReport",{
msg:{
event_nickname: "list_page",
page_type: "我的课件",
},
});
});
onMounted(() => { onMounted(() => {
onRefresh(); onRefresh();
}); });
const getStatusText = (status) => {
console.log(status);
if(status === 'paid'){
return '已支付';
}else if(status === 'unpaid'){
return '未支付';
}else{
return '已分享';
}
};
const goBack = () => { const goBack = () => {
uni.navigateBack(); if(!from.value){
plus.runtime.quit();
}else{
uni.navigateBack();
}
}; };
onLoad((options) => {
if(options.from){
//from.value = options.from;
switchTab('share')
}
})
const switchTab = (tab) => { const switchTab = (tab) => {
activeTab.value = tab; activeTab.value = tab;
listScrollTop.value = 0;
scrollIntoViewId.value = '';
nextTick(() => {
scrollIntoViewId.value = 'courseware-top';
});
// 切换标签时重置数据 // 切换标签时重置数据
page.value = 1; page.value = 1;
noMore.value = false; noMore.value = false;
@@ -132,36 +181,9 @@ const onItemClick = (item) => {
console.log('点击课件:', item); console.log('点击课件:', item);
if (activeTab.value === 'download') { if (activeTab.value === 'download') {
// 下载标签页的处理逻辑 navTo({
if (item.download_path) { url: `/pages_app/myCourseware/coursewareDetail?id=${item.order_id}`
uni.downloadFile({ })
url: item.download_path,
success: (res) => {
if (res.statusCode === 200) {
uni.openDocument({
filePath: res.tempFilePath,
success: () => {
console.log('打开文档成功');
},
fail: (err) => {
console.error('打开文档失败:', err);
uni.showToast({ title: '无法打开此文件', icon: 'none' });
}
});
}
},
fail: (err) => {
console.error('下载失败:', err);
uni.showToast({ title: '下载失败', icon: 'none' });
}
});
} else {
uni.showToast({ title: `点击了下载课件: ${item.name}`, icon: 'none' });
}
} else {
// 分享标签页的处理逻辑
uni.showToast({ title: `点击了分享课件: ${item.name}`, icon: 'none' });
// 这里可以添加分享相关的操作,比如查看分享详情、重新分享等
} }
}; };
@@ -175,7 +197,7 @@ const onRefresh = () => {
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
const data = res.data.list; const data = res.data.list;
// 更新摘要信息 // 更新摘要信息
downloadCount.value = res.data.downloadTotalAccount || 0; downloadCount.value = (res.data.downloadTotalAccount/100).toFixed(2) || 0;
totalAmount.value = res.data.downloadFileCount || 0; totalAmount.value = res.data.downloadFileCount || 0;
// 处理课件列表数据 // 处理课件列表数据
@@ -186,7 +208,7 @@ const onRefresh = () => {
uuid: item.uuid, uuid: item.uuid,
order_id: item.order_id, order_id: item.order_id,
type: item.type, type: item.type,
providername: item.providername providername: item.downloadername
})); }));
// 判断是否还有更多数据 // 判断是否还有更多数据
@@ -203,7 +225,7 @@ const onRefresh = () => {
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
const data = res.data.list; const data = res.data.list;
// 更新摘要信息 - 分享数据使用不同的字段 // 更新摘要信息 - 分享数据使用不同的字段
downloadCount.value = res.data.shareTotalAccount || 0; downloadCount.value = (res.data.shareTotalAccount/100).toFixed(2) || 0;
totalAmount.value = res.data.shareloadFileCount || 0; totalAmount.value = res.data.shareloadFileCount || 0;
// 处理分享列表数据 // 处理分享列表数据
@@ -305,7 +327,7 @@ const onLoadMore = () => {
background: #ffffff; background: #ffffff;
border-bottom: 2rpx solid #f0f0f0; border-bottom: 2rpx solid #f0f0f0;
position: fixed; position: fixed;
top: 140rpx; top: calc(var(--status-bar-height) + 44px);
left: 0; left: 0;
right: 0; right: 0;
z-index: 10; z-index: 10;
@@ -332,20 +354,21 @@ const onLoadMore = () => {
.summary-bar { .summary-bar {
display: flex; display: flex;
justify-content: space-between;
align-items: center; align-items: center;
background: #8B2316; background: #8B2316;
padding: 20rpx 30rpx; padding: 20rpx 30rpx;
position: fixed; position: fixed;
top: 260rpx; // 标签页导航下方 top: calc(var(--status-bar-height) + 44px + 124rpx); // 标签页导航下方
left: 0; left: 0;
right: 0; right: 0;
z-index: 10; z-index: 10;
.summary-item { .summary-item {
flex:1;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start;
.summary-text { .summary-text {
color: #ffffff; color: #ffffff;
font-size: 28rpx; font-size: 28rpx;
@@ -355,9 +378,9 @@ const onLoadMore = () => {
} }
.courseware-list { .courseware-list {
height: calc(100vh - 140rpx - 116rpx - 80rpx); // height: calc(100vh - 180rpx - 116rpx - 80rpx);
position: fixed; position: fixed;
top: 336rpx; // 调整顶部位置,为固定的摘要栏腾出空间 top: calc(var(--status-bar-height) + 44px + 124rpx + 80rpx); // 调整顶部位置,为固定的摘要栏腾出空间
bottom: 0; bottom: 0;
width: 100%; width: 100%;
} }
@@ -383,6 +406,15 @@ const onLoadMore = () => {
width: 120rpx; width: 120rpx;
color: #8B2316; color: #8B2316;
font-size: 28rpx; font-size: 28rpx;
display: inline-block;
text-align: justify;
text-align-last: justify;
text-justify: inter-ideograph;
}
.label::after {
content: '';
display: inline-block;
width: 100%;
} }
.value { .value {
+1 -1
View File
@@ -8,7 +8,7 @@
@clickLeft="goBack()" @clickLeft="goBack()"
fixed fixed
color="#8B2316" color="#8B2316"
height="140rpx" height="180rpx"
:border="false" :border="false"
backgroundColor="#eeeeee" backgroundColor="#eeeeee"
> >
+263 -72
View File
@@ -2,33 +2,38 @@
<view class="flower-page"> <view class="flower-page">
<!-- 顶部统计栏(与截图一致:两项) --> <!-- 顶部统计栏(与截图一致:两项) -->
<uni-nav-bar <view class="navbox">
<view class="status_bar"></view>
<uni-nav-bar
left-icon="left" left-icon="left"
title="我的鲜花" title="我的鲜花"
@clickLeft="goBack" @clickLeft="goBack"
fixed
color="#8B2316" color="#8B2316"
height="140rpx"
:border="false" :border="false"
backgroundColor="#eeeeee" backgroundColor="#eeeeee"
/> />
</view>
<view class="stats-bar"> <view class="fixed-top-block">
<view class="stat"> <view class="stats-bar">
<up-image :src="flowerImg" width="36rpx" height="36rpx" ></up-image> <view class="stat">
<text class="num">{{ stat.totalCount }}</text> <up-image :src="flowerImg" width="36rpx" height="36rpx" ></up-image>
<text class="num">{{ stat.totalCount }}</text>
</view>
<view class="stat">
<up-image :src="moneyImg" width="36rpx" height="36rpx" ></up-image>
<text class="num">{{ stat.totalAmount.toFixed(2) }}</text>
</view>
</view> </view>
<view class="stat">
<up-image :src="moneyImg" width="36rpx" height="36rpx" ></up-image>
<text class="num">{{ stat.totalAmount.toFixed(2) }}</text>
</view>
</view>
<!-- 表头条(红底白字) --> <!-- 表头条(红底白字) -->
<view class="table-header"> <view class="table-header">
<text class="col name">姓名</text> <text class="col name">姓名</text>
<text class="col time">时间</text> <text class="col time">时间</text>
<text class="col qty">数量</text> <text class="col qty">数量</text>
</view>
</view> </view>
<!-- 表体/列表或空状态 --> <!-- 表体/列表或空状态 -->
@@ -43,37 +48,62 @@
lower-threshold="100" lower-threshold="100"
:scroll-top="scrollTop" :scroll-top="scrollTop"
> >
<view v-if="records.length === 0" class="empty-wrap"> <view class="list-content">
<up-image :src="emptyImg" width="176rpx" height="204rpx" ></up-image> <view v-if="records.length === 0" class="empty-wrap">
<text class="empty-text">您暂未收到鲜花</text> <up-image :src="emptyImg" width="176rpx" height="204rpx" ></up-image>
</view> <text class="empty-text">您暂未收到鲜花</text>
</view>
<view v-else class="row" v-for="(item, idx) in records" :key="idx"> <view v-else class="row" v-for="(item, idx) in records" :key="idx">
<text class="cell name">{{ item.name }}</text> <text class="cell name">{{ item.name }}</text>
<text class="cell time">{{ item.time }}</text> <text class="cell time">{{ item.time }}</text>
<text class="cell qty" :class="{ plus: item.amount > 0, minus: item.amount < 0 }">{{ item.amount }}</text> <text class="cell qty" :class="{ plus: item.amount > 0, minus: item.amount < 0 }">{{ item.amount }}</text>
</view> </view>
<view v-if="loading" class="loading"> <view v-if="loading" class="loading">
<text>加载中...</text> <text>加载中...</text>
</view> </view>
<view v-if="noMore && records.length > 0" class="no-more"> <view v-if="noMore && records.length > 0" class="no-more">
<text>没有更多数据了</text> <text>没有更多数据了</text>
</view> </view>
<view class="debug-actions"> <!-- <view class="debug-actions">
<!-- <button @click="testLoadMore" size="mini" type="primary">测试加载更多</button> <button @click="testLoadMore" size="mini" type="primary">测试加载更多</button>
<text class="debug-info">当前页: {{ page }}, 加载中: {{ loading }}, 无更多: {{ noMore }}</text> --> <text class="debug-info">当前页: {{ page }}, 加载中: {{ loading }}, 无更多: {{ noMore }}</text>
</view> -->
</view> </view>
</scroll-view> </scroll-view>
</view> </view>
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from 'vue'; import { ref, onMounted, nextTick } from 'vue';
import flowerImg from "@/static/flowers.png" import flowerImg from "@/static/flowers.png"
import moneyImg from "@/static/mind_totle_money.png" import moneyImg from "@/static/mind_totle_money.png"
import emptyImg from "@/static/icon_empty.png" import emptyImg from "@/static/icon_empty.png"
import api from '@/api/api.js'; import api from '@/api/api.js';
import { onBackPress,onLoad } from '@dcloudio/uni-app';
const from=ref('');
const user_uuid=ref('');
onLoad((options) => {
if(options.from){
from.value = options.from;
}
let userInfo= uni.getStorageSync('userInfo')
user_uuid.value = userInfo.uuid;
loadFirstTwoPages();
uni.sendNativeEvent("portraitReport",{
msg:{
event_nickname: "list_page",
page_type: "我的鲜花",
},
});
});
onBackPress(() => {
if(!from.value){
plus.runtime.quit();
return true;
}
});
// 与截图一致的顶部两项统计:总鲜花数与累计金额 // 与截图一致的顶部两项统计:总鲜花数与累计金额
const stat = ref({ totalCount: 0, totalAmount: 0.0 }); const stat = ref({ totalCount: 0, totalAmount: 0.0 });
@@ -87,19 +117,44 @@ const noMore = ref(false);
const page = ref(1); const page = ref(1);
const pageSize = ref(10); const pageSize = ref(10);
const scrollTop = ref(0); const scrollTop = ref(0);
const autoLoading = ref(false);
const formatDateToYDM = (value) => {
if (!value) return '-';
const raw = String(value).trim();
const directMatch = raw.match(/^(\d{4})[-/.](\d{1,2})[-/.](\d{1,2})/);
if (directMatch) {
const [, year, month, day] = directMatch;
return `${year}-${day.padStart(2, '0')}-${month.padStart(2, '0')}`;
}
const parsedDate = new Date(raw);
if (Number.isNaN(parsedDate.getTime())) return '-';
const year = parsedDate.getFullYear();
const month = String(parsedDate.getMonth() + 1).padStart(2, '0');
const day = String(parsedDate.getDate()).padStart(2, '0');
return `${year}-${day}-${month}`;
};
const goBack = () => { const goBack = () => {
uni.navigateBack({ if(!from.value){
fail() { plus.runtime.quit();
uni.redirectTo({ url: '/pages/index/index' }); }else{
} uni.navigateBack();
}); }
}; };
const getFlowerList = () => { const getFlowerList = () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
console.log('[myFlower] getFlowerList:start', {
page: page.value,
loading: loading.value,
refreshing: refreshing.value,
noMore: noMore.value
});
// 只在第一页时设置loading状态,避免与onLoadMore冲突 // 只在第一页时设置loading状态,避免与onLoadMore冲突
if (page.value === 1) { if (page.value === 1) {
loading.value = true; loading.value = true;
@@ -107,20 +162,30 @@ const getFlowerList = () => {
console.log('正在获取第', page.value, '页数据...'); console.log('正在获取第', page.value, '页数据...');
api.getFlowerList({page: page.value}).then(res => { api.getFlowerList({
page: page.value,
user_uuid: user_uuid.value,
}).then(res => {
console.log('接口返回数据:', res); console.log('接口返回数据:', res);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
const { flower_data, total_amount, total_num } = res.data; const { flower_data, total_amount, total_num } = res.data;
console.log('[myFlower] getFlowerList:response', {
reqPage: page.value,
pageNum: flower_data?.pageNum,
pages: flower_data?.pages,
isLastPage: flower_data?.isLastPage,
listLength: flower_data?.list?.length || 0
});
// 更新统计数据 // 更新统计数据
stat.value.totalCount = total_num || 0; stat.value.totalCount = total_num || 0;
stat.value.totalAmount = total_amount || 0; stat.value.totalAmount = total_amount || 0;
// 处理列表数据 // 处理列表数据
if (flower_data && flower_data.list) { if (flower_data && Array.isArray(flower_data.list)) {
const newRecords = flower_data.list.map(item => ({ const newRecords = flower_data.list.map(item => ({
name: item.patient_name || '未知', name: item.patient_name || '账号已注销',
time: item.create_date || '', time: formatDateToYDM(item.create_date),
amount: item.num || 0, amount: item.num || 0,
// 保留原始数据以备后用 // 保留原始数据以备后用
original: item original: item
@@ -135,12 +200,28 @@ const getFlowerList = () => {
console.log('第', page.value, '页数据,追加到列表,新增', newRecords.length, '条'); console.log('第', page.value, '页数据,追加到列表,新增', newRecords.length, '条');
} }
// 判断是否还有更多数据 // 判断是否还有更多数据,兼容不同接口字段
noMore.value = flower_data.isLastPage; if (typeof flower_data.isLastPage === 'boolean') {
console.log('是否最后一页:', flower_data.isLastPage, ',总页数:', flower_data.pages); noMore.value = flower_data.isLastPage;
} else if (Number(flower_data.pages) > 0) {
noMore.value = page.value >= Number(flower_data.pages);
} else {
// 未返回分页字段时,仅在本页无数据时判定到底
noMore.value = newRecords.length === 0;
}
console.log('是否最后一页:', noMore.value, ',总页数:', flower_data.pages);
} }
console.log('[myFlower] getFlowerList:end', {
page: page.value,
records: records.value.length,
noMore: noMore.value
});
resolve(res); resolve(res);
} else { } else {
console.log('[myFlower] getFlowerList:invalid', {
code: res?.code,
msg: res?.msg
});
reject(new Error('接口返回错误')); reject(new Error('接口返回错误'));
} }
}).catch(err => { }).catch(err => {
@@ -156,18 +237,11 @@ const getFlowerList = () => {
}); });
}); });
}; };
onMounted(() => {
getFlowerList();
});
const onRefresh = async () => { const onRefresh = async () => {
if (refreshing.value) return; if (refreshing.value || loading.value) return;
refreshing.value = true; refreshing.value = true;
try { try {
page.value = 1; await loadFirstTwoPages();
noMore.value = false;
records.value = []; // 清空数据
getFlowerList(); // 重新获取第一页数据
uni.showToast({ title: '刷新成功', icon: 'none' }); uni.showToast({ title: '刷新成功', icon: 'none' });
} catch (error) { } catch (error) {
console.error('刷新失败:', error); console.error('刷新失败:', error);
@@ -177,39 +251,136 @@ const onRefresh = async () => {
const onLoadMore = async () => { const onLoadMore = async () => {
console.log('触发上拉加载更多事件'); console.log('触发上拉加载更多事件');
console.log('[myFlower] onLoadMore:enter', {
page: page.value,
loading: loading.value,
refreshing: refreshing.value,
noMore: noMore.value
});
// 检查各种状态 // 检查各种状态
if (loading.value) { if (loading.value || refreshing.value) {
console.log('正在加载中,忽略重复请求'); console.log('正在加载中,忽略重复请求');
console.log('[myFlower] onLoadMore:blockByState');
return; return;
} }
if (noMore.value) { if (noMore.value) {
console.log('已经没有更多数据了'); console.log('已经没有更多数据了');
console.log('[myFlower] onLoadMore:blockByNoMore');
return; return;
} }
console.log('开始加载下一页,当前页码:', page.value); console.log('开始加载下一页,当前页码:', page.value);
loading.value = true; loading.value = true;
const currentPage = page.value;
try { try {
page.value += 1; page.value = currentPage + 1;
console.log('[myFlower] onLoadMore:request', {
fromPage: currentPage,
toPage: page.value
});
await getFlowerList(); // 等待数据加载完成 await getFlowerList(); // 等待数据加载完成
console.log('加载完成,当前页码:', page.value); console.log('加载完成,当前页码:', page.value);
console.log('[myFlower] onLoadMore:done', {
page: page.value,
records: records.value.length,
noMore: noMore.value
});
} catch (error) { } catch (error) {
console.error('加载更多失败:', error); console.error('加载更多失败:', error);
page.value -= 1; // 恢复页码 page.value = currentPage; // 恢复页码
console.log('[myFlower] onLoadMore:rollback', { page: page.value });
uni.showToast({ title: '加载失败', icon: 'error' }); uni.showToast({ title: '加载失败', icon: 'error' });
} finally { } finally {
loading.value = false; loading.value = false;
} }
}; };
const getListLayoutInfo = () => {
return new Promise((resolve) => {
const query = uni.createSelectorQuery();
query.select('.table-body').boundingClientRect();
query.select('.list-content').boundingClientRect();
query.exec((res = []) => {
const bodyHeight = Number(res?.[0]?.height || 0);
const contentHeight = Number(res?.[1]?.height || 0);
resolve({ bodyHeight, contentHeight });
});
});
};
const ensureListCanScroll = async () => {
if (autoLoading.value || loading.value || refreshing.value || noMore.value) return;
autoLoading.value = true;
try {
while (!noMore.value) {
await nextTick();
await new Promise((resolve) => setTimeout(resolve, 50));
const { bodyHeight, contentHeight } = await getListLayoutInfo();
if (!bodyHeight || !contentHeight) break;
if (contentHeight > bodyHeight) break;
await onLoadMore();
if (loading.value) break;
}
} finally {
autoLoading.value = false;
}
};
const loadFirstTwoPages = async () => {
console.log('[myFlower] loadFirstTwoPages:start');
page.value = 1;
noMore.value = false;
records.value = [];
console.log('[myFlower] loadFirstTwoPages:reset', {
page: page.value,
records: records.value.length,
noMore: noMore.value
});
await getFlowerList();
// 第1页 Promise resolve 早于 finally 复位 loading,这里手动兜底复位
loading.value = false;
console.log('[myFlower] loadFirstTwoPages:afterPage1', {
page: page.value,
records: records.value.length,
noMore: noMore.value
});
if (!noMore.value) {
console.log('[myFlower] loadFirstTwoPages:triggerPage2');
const firstPage = page.value;
try {
page.value = firstPage + 1;
console.log('[myFlower] loadFirstTwoPages:requestPage2', {
fromPage: firstPage,
toPage: page.value,
refreshing: refreshing.value
});
await getFlowerList();
loading.value = false;
} catch (error) {
page.value = firstPage;
console.log('[myFlower] loadFirstTwoPages:page2FailedRollback', { page: page.value });
throw error;
}
} else {
console.log('[myFlower] loadFirstTwoPages:skipPage2(noMore=true)');
}
console.log('[myFlower] loadFirstTwoPages:end', {
page: page.value,
records: records.value.length,
noMore: noMore.value
});
};
// 测试加载更多功能 // 测试加载更多功能
const testLoadMore = () => { const testLoadMore = () => {
console.log('手动测试加载更多'); console.log('手动测试加载更多');
onLoadMore(); onLoadMore();
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -221,13 +392,27 @@ $card: #ffffff;
.flower-page { .flower-page {
background: $bg; background: $bg;
--nav-offset: calc(var(--status-bar-height) + 44px);
--stats-height: 86rpx;
--header-height: 74rpx;
/* 导航栏下方固定区域总高度:统计栏 + 表头 */
--fixed-block-height: calc(var(--stats-height) + var(--header-height));
}
.fixed-top-block {
position: fixed;
top: var(--nav-offset);
left: 0;
right: 0;
z-index: 20;
} }
.stats-bar { .stats-bar {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 24rpx 30rpx; height: var(--stats-height);
padding: 0 30rpx;
box-sizing: border-box;
background: #fff; background: #fff;
border-bottom: 2rpx solid #eee; border-bottom: 2rpx solid #eee;
@@ -244,20 +429,25 @@ $card: #ffffff;
color: #fff; color: #fff;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
padding: 22rpx 30rpx; align-items: center;
height: var(--header-height);
padding: 0 30rpx;
box-sizing: border-box;
font-size: 30rpx; font-size: 30rpx;
position: sticky;
top: 140rpx; /* nav 高度 */
z-index: 5;
.col { width: 33%; } .col { width: 33%; }
.name { text-align: left; } .name { text-align: left; }
.time { text-align: center; } .time { text-align: center; }
.qty { text-align: right; } .qty { text-align: center; }
} }
.table-body { .table-body {
height: calc(100vh - 140rpx - 88rpx - 70rpx); position: fixed;
top: calc(var(--nav-offset) + var(--fixed-block-height));
left: 0;
right: 0;
bottom: 0;
box-sizing: border-box;
/* 充满剩余高度,避免滚动穿透 */ /* 充满剩余高度,避免滚动穿透 */
} }
@@ -266,16 +456,17 @@ $card: #ffffff;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 26rpx 30rpx; padding: 26rpx 30rpx;
border-bottom: 2rpx solid #f2f2f2; background: #fff;
border-bottom: 2rpx solid #ccc;
font-size: 28rpx; font-size: 28rpx;
color: #333; color: #333;
.cell { width: 33%; } .cell { width: 33%; }
.name { text-align: left; } .name { text-align: left; }
.time { text-align: center; color: #666; } .time { text-align: center; color: #666; }
.qty { text-align: right; } .qty { text-align: center; }
.qty.plus { color: #2dbd85; } .qty.plus { color: #333; }
.qty.minus { color: #e34d4d; } .qty.minus { color: #333; }
} }
.empty-wrap { padding-top: 200rpx; display: flex; flex-direction: column; align-items: center; color: #bdbdbd; } .empty-wrap { padding-top: 200rpx; display: flex; flex-direction: column; align-items: center; color: #bdbdbd; }
+153
View File
@@ -0,0 +1,153 @@
<template>
<view class="page">
<view class="header">
<text class="title">scroll-view 上拉加载 Demo</text>
<text class="sub">下拉刷新 + 触底加载更多</text>
</view>
<scroll-view
class="list-scroll"
scroll-y
lower-threshold="80"
refresher-enabled
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
@scrolltolower="onLoadMore"
>
<view v-for="item in list" :key="item.id" class="list-item">
<text class="item-title">{{ item.title }}</text>
<text class="item-time">{{ item.time }}</text>
</view>
<view v-if="loading" class="tip">加载中...</view>
<view v-else-if="noMore && list.length > 0" class="tip">没有更多数据了</view>
<view v-else-if="list.length === 0" class="tip">暂无数据</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue';
const page = ref(1);
const pageSize = ref(10);
const list = ref([]);
const loading = ref(false);
const refreshing = ref(false);
const noMore = ref(false);
const totalMockCount = 37;
const fullData = Array.from({ length: totalMockCount }, (_, i) => {
const index = i + 1;
return {
id: index,
title: `第 ${index} 条数据`,
time: `2026-03-${String((index % 28) + 1).padStart(2, '0')}`
};
});
const getPageData = (pageNum, size) => {
return new Promise((resolve) => {
setTimeout(() => {
const start = (pageNum - 1) * size;
const end = start + size;
resolve(fullData.slice(start, end));
}, 350);
});
};
const fetchList = async (isRefresh = false) => {
if (loading.value) return;
loading.value = true;
try {
if (isRefresh) {
page.value = 1;
noMore.value = false;
}
const rows = await getPageData(page.value, pageSize.value);
if (isRefresh) {
list.value = rows;
} else {
list.value = [...list.value, ...rows];
}
noMore.value = rows.length < pageSize.value;
} finally {
loading.value = false;
refreshing.value = false;
}
};
const onRefresh = async () => {
if (refreshing.value) return;
refreshing.value = true;
await fetchList(true);
};
const onLoadMore = async () => {
if (loading.value || refreshing.value || noMore.value) return;
page.value += 1;
await fetchList(false);
};
onMounted(() => {
fetchList(true);
});
</script>
<style scoped>
.page {
height: 100vh;
background: #f7f8fa;
}
.header {
padding: 30rpx;
background: #ffffff;
border-bottom: 1rpx solid #ededed;
}
.title {
display: block;
font-size: 34rpx;
color: #222;
font-weight: 600;
}
.sub {
display: block;
margin-top: 8rpx;
font-size: 24rpx;
color: #888;
}
.list-scroll {
height: calc(100vh - 130rpx);
}
.list-item {
margin: 20rpx 24rpx 0;
padding: 24rpx;
background: #ffffff;
border-radius: 12rpx;
}
.item-title {
display: block;
font-size: 30rpx;
color: #222;
}
.item-time {
display: block;
margin-top: 10rpx;
font-size: 24rpx;
color: #999;
}
.tip {
padding: 28rpx 0 40rpx;
text-align: center;
color: #9aa0a6;
font-size: 24rpx;
}
</style>
+101 -50
View File
@@ -1,24 +1,11 @@
<template> <template>
<navBar :title="'新的患者'" />
<view class="new-patient-page"> <view class="new-patient-page">
<uni-nav-bar
left-icon="left"
title="我的患者"
@clickLeft="goBack"
fixed
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#eeeeee"
/>
<!-- 提醒区域 --> <!-- 提醒区域 -->
<view class="reminder-section"> <view class="contentbox">
<view class="reminder-section">
<view class="reminder-icon"> <view class="reminder-icon">
<uni-icons type="notification" size="16" color="#ff9500"></uni-icons> <up-image :src="tipImg" width="35rpx" height="42rpx"></up-image>
</view> </view>
<text class="reminder-text">提醒: 为了避免不必要的纠纷,请您务必选择线下就诊过的患者</text> <text class="reminder-text">提醒: 为了避免不必要的纠纷,请您务必选择线下就诊过的患者</text>
</view> </view>
@@ -29,15 +16,15 @@
<view class="pending-request" v-for="(item, index) in applyList" :key="index"> <view class="pending-request" v-for="(item, index) in applyList" :key="index">
<view class="request-item"> <view class="request-item">
<view class="avatar"> <view class="avatar">
<up-image :src="docUrl+item.photo" radius="10rpx" width="80rpx" height="80rpx" ></up-image> <up-image :src="docUrl+item.photo" radius="10rpx" width="120rpx" height="120rpx" ></up-image>
</view> </view>
<view class="request-content"> <view class="request-content">
<view class="request-time">{{ item.createDate }}</view> <view class="request-time">{{ item.createDate }}</view>
<view class="request-text">{{ item.content }}</view> <view class="request-text">{{ item.content }}</view>
<view class="action-buttons"> <view class="action-buttons">
<button class="reject-btn" @click="applyListOperate(item.uuid,3)">拒绝</button> <button class="reject-btn" @click="applyListOperate(item.uuid,3,item.content,item.patientUuid)">拒绝</button>
<button class="agree-btn" @click="applyListOperate(item.uuid,2)">同意</button> <button class="agree-btn" @click="applyListOperate(item.uuid,2,item.content,item.patientUuid)">同意</button>
</view> </view>
</view> </view>
</view> </view>
@@ -48,14 +35,14 @@
<view class="history-section" v-if="historyList.length > 0"> <view class="history-section" v-if="historyList.length > 0">
<view class="section-title">申请记录(近一月)</view> <view class="section-title">申请记录(近一月)</view>
<view class="history-list"> <view class="history-list">
<view class="history-item" v-for="(item, index) in historyList" :key="index"> <view class="history-item" v-for="(item, index) in historyList" :key="index" @click="goPatientDetail(item.patient_uuid,item.status)">
<view class="avatar"> <view class="avatar">
<up-image v-if="docUrl+item.patient_photo" :src="docUrl + item.patient_photo" radius="10rpx" width="80rpx" height="80rpx"></up-image> <up-image v-if="docUrl+item.patient_photo" :src="docUrl + item.patient_photo" radius="10rpx" width="120rpx" height="120rpx"></up-image>
</view> </view>
<view class="history-content"> <view class="history-content">
<view class="history-time">{{ formatDate(item.createDate) }}</view> <view class="history-time">{{ formatDate2(item.create_date) }}</view>
<view class="nickname">{{ item.nickname || item.patient_name }}</view> <view class="nickname">昵称:{{ item.patient_name || item.nickname }}</view>
<view class="history-text">{{ item.content}}</view> <view class="history-text">{{ item.content}}</view>
<view class="status-info"> <view class="status-info">
<up-image :src="goImg" width="30rpx" height="30rpx" v-if="item.status==2"></up-image> <up-image :src="goImg" width="30rpx" height="30rpx" v-if="item.status==2"></up-image>
@@ -66,6 +53,9 @@
</view> </view>
</view> </view>
<empty v-if="applyList.length == 0 && historyList.length == 0" :emptyDesc="'暂无新随访申请患者'" />
</view>
<!-- 底部按钮 --> <!-- 底部按钮 -->
<view class="bottom-button"> <view class="bottom-button">
<button class="add-patient-btn" @click="addPatient">加患者</button> <button class="add-patient-btn" @click="addPatient">加患者</button>
@@ -75,11 +65,27 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { onLoad,onShow} from '@dcloudio/uni-app'; import { onLoad,onShow,onBackPress} from '@dcloudio/uni-app';
import goImg from "@/static/go_big.png" import goImg from "@/static/go_big.png"
import api from '@/api/api.js' import api from '@/api/api.js'
import docUrl from "@/utils/docUrl" import docUrl from "@/utils/docUrl"
import dayjs from 'dayjs'
import navTo from "@/utils/navTo.js" import navTo from "@/utils/navTo.js"
import empty from "@/components/empty/empty.vue"
import navBar from "@/components/navBar/navBar.vue"
import tipImg from "@/static/sendgroup_tishi_big.png"
const from=ref('');
onLoad((options) => {
if(options.from){
from.value = options.from;
}
});
onBackPress(() => {
if(!from.value){
plus.runtime.quit();
return true;
}
});
// 响应式数据 // 响应式数据
const pendingRequest = ref({ const pendingRequest = ref({
name: '陈新华', name: '陈新华',
@@ -91,7 +97,11 @@ const historyList = ref([]);
// 方法 // 方法
const goBack = () => { const goBack = () => {
uni.navigateBack(); if(!from.value){
plus.runtime.quit();
}else{
uni.navigateBack();
}
}; };
const rejectRequest = () => { const rejectRequest = () => {
@@ -162,20 +172,27 @@ const getApplyList = async () => { // 申请列表
}); });
} }
}; };
const applyListOperate = async (uuid,status) => { const applyListOperate = async (uuid,status,content,patientUuid) => {
let data = { let data = {
uuid: uuid, uuid: uuid,
status: status status: status
} }
const res = await api.applyListOperate(data); const res = await api.applyListOperate(data);
if(res.code === 200){ if(res.code === 200){
uni.showToast({ uni.showToast({
title: '操作成功', title: '操作成功',
icon: 'none', icon: 'none',
duration: 1500 duration: 1500
}); });
let nickname = content.split(',')[0].replace('我是', '');
getApplyList(); getApplyList();
getRelationRecordLately(); getRelationRecordLately();
if(status == 2){
navTo({
url: `/pages_app/patientRemark/patientRemark?uuid=${patientUuid}&&nickname=${nickname}`
})
}
} }
}; };
onShow(() => { onShow(() => {
@@ -184,6 +201,15 @@ const getApplyList = async () => { // 申请列表
}); });
const goPatientDetail = (uuid,status) => {
if(status == 2){
navTo({
url: `/pages_app/patientDetail/patientDetail?uuid=${uuid}&from=myPatient`
})
}
}
// 辅助方法 // 辅助方法
// 格式化日期 // 格式化日期
const formatDate = (dateString) => { const formatDate = (dateString) => {
@@ -196,6 +222,9 @@ const formatDate = (dateString) => {
const minutes = String(date.getMinutes()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`; return `${year}-${month}-${day} ${hours}:${minutes}`;
}; };
const formatDate2 = (dateString) => {
return dayjs(dateString).format('YYYY-MM-DD HH:mm:ss');
}
// 获取状态文本 // 获取状态文本
const getStatusText = (status) => { const getStatusText = (status) => {
@@ -208,7 +237,7 @@ const getStatusText = (status) => {
const addPatient = () => { const addPatient = () => {
navTo({ navTo({
url:'/pages_app/myCode/myCode' url:'/pages_app/myCode/myCode?from=myPatient'
}); });
// 这里可以跳转到添加患者页面 // 这里可以跳转到添加患者页面
}; };
@@ -216,7 +245,7 @@ const addPatient = () => {
<style lang="scss" scoped> <style lang="scss" scoped>
.new-patient-page { .new-patient-page {
min-height: 100vh;
background-color: #f5f5f5; background-color: #f5f5f5;
} }
@@ -311,34 +340,47 @@ const addPatient = () => {
margin-right: 60rpx; // 为了保持标题居中 margin-right: 60rpx; // 为了保持标题居中
} }
} }
.contentbox{
top: calc(var(--status-bar-height) + 44px);
position: fixed;
left: 0;
right: 0;
bottom: 100rpx;
overflow-y: scroll;
z-index: 1;
box-sizing: border-box;
}
.reminder-section { .reminder-section {
display: flex; display: flex;
align-items: flex-start; align-items:center;
padding: 30rpx; padding: 30rpx;
background-color: #ffffff; background-color: #ffffff;
margin-bottom: 20rpx;
.reminder-icon { .reminder-icon {
width: 35rpx;
height: 42rpx;
margin-right: 20rpx; margin-right: 20rpx;
margin-top: 4rpx; margin-top: 4rpx;
} }
.reminder-text { .reminder-text {
flex: 1; flex: 1;
font-size: 28rpx; font-size: 32rpx;
color: #333; color: #999;
line-height: 1.5; line-height: 1.5;
} }
} }
.follow-up-section { .follow-up-section {
background-color: #ffffff; background-color: #ffffff;
margin-bottom: 20rpx;
.section-title { .section-title {
padding: 30rpx 30rpx 20rpx; padding: 30rpx 30rpx 30rpx;
font-size: 32rpx; font-size: 32rpx;
background-color: #f0f0f0;
font-weight: normal; font-weight: normal;
color: #333; color: #333;
border-bottom: 1rpx solid #f0f0f0; border-bottom: 1rpx solid #f0f0f0;
@@ -349,11 +391,12 @@ const addPatient = () => {
.request-item { .request-item {
display: flex; display: flex;
gap: 20rpx;
.avatar { .avatar {
width: 80rpx; width: 120rpx;
height: 80rpx; height: 120rpx;
margin-top: 30rpx;
background-color: #ffb6c1; background-color: #ffb6c1;
border-radius: 12rpx; border-radius: 12rpx;
display: flex; display: flex;
@@ -361,8 +404,8 @@ const addPatient = () => {
justify-content: center; justify-content: center;
.avatar-icon { .avatar-icon {
width: 40rpx; width: 120rpx;
height: 40rpx; height: 120rpx;
background-color: #ffffff; background-color: #ffffff;
border-radius: 50%; border-radius: 50%;
} }
@@ -370,7 +413,7 @@ const addPatient = () => {
.request-content { .request-content {
flex: 1; flex: 1;
margin-left: 20rpx;
.request-text { .request-text {
font-size: 28rpx; font-size: 28rpx;
color: #333; color: #333;
@@ -432,18 +475,19 @@ const addPatient = () => {
margin-bottom: 120rpx; // 为底部按钮留出空间 margin-bottom: 120rpx; // 为底部按钮留出空间
.section-title { .section-title {
padding: 30rpx 30rpx 20rpx; padding: 30rpx 30rpx 30rpx;
font-size: 32rpx; font-size: 32rpx;
font-weight: normal; font-weight: normal;
background-color: #f0f0f0;
color: #333; color: #333;
border-bottom: 1rpx solid #f0f0f0; border-bottom: 1rpx solid #f0f0f0;
} }
.history-list { .history-list {
margin-bottom: 100rpx; margin-bottom: 30rpx;
.history-item { .history-item {
display: flex; display: flex;
gap: 20rpx;
padding: 30rpx; padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0; border-bottom: 1rpx solid #f0f0f0;
@@ -452,8 +496,9 @@ const addPatient = () => {
} }
.avatar { .avatar {
width: 80rpx; width: 120rpx;
height: 80rpx; height: 120rpx;
margin-top: 30rpx;
background-color: #ffb6c1; background-color: #ffb6c1;
border-radius: 12rpx; border-radius: 12rpx;
display: flex; display: flex;
@@ -470,18 +515,24 @@ const addPatient = () => {
.history-content { .history-content {
flex: 1; flex: 1;
margin-left: 20rpx;
min-width: 0;
.nickname { .nickname {
font-size: 28rpx; font-size: 30rpx;
font-weight: normal; font-weight: normal;
color: #8B2316; color: #8B2316;
margin-bottom: 10rpx; margin-bottom: 10rpx;
} }
.history-text { .history-text {
font-size: 28rpx; display: block;
width: 100%;
font-size: 30rpx;
color: #333; color: #333;
line-height: 1.4; line-height: 1.4;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 20rpx; margin-bottom: 20rpx;
} }
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More