diff --git a/components/ActivateAgentPopup.vue b/components/ActivateAgentPopup.vue index da2445a..052cc85 100644 --- a/components/ActivateAgentPopup.vue +++ b/components/ActivateAgentPopup.vue @@ -73,13 +73,11 @@ export default { // 处理AGENT点击 handleAgentClick(agent) { - console.log("点击了AGENT:", agent); - if (agent.status ==1) { // 激活逻辑 - uni.navigateTo({ - url: "/pages/agent/index?id="+agent.agentId - }); + uni.navigateTo({ + url: "/subPackages/other/evita?id="+agent.agentId + }); } else { uni.navigateTo({ url:"/subPackages/equityGoods/detail?id="+agent.vos[0].benefitPackageId diff --git a/components/DynamicIsland.vue b/components/DynamicIsland.vue index 78c3848..0fa0af6 100644 --- a/components/DynamicIsland.vue +++ b/components/DynamicIsland.vue @@ -25,7 +25,7 @@ EVITA - DES介绍 >> + DES介绍 >> DES广播 >> @@ -363,7 +363,7 @@ // "https://des.js-dyyj.com/dist/#/", // }); uni.navigateTo({ - url:'/subPackages/other/evita' + url:'/subPackages/other/evita?id=1' }) }, diff --git a/components/GPT/.DS_Store b/components/GPT/.DS_Store new file mode 100644 index 0000000..ec30373 Binary files /dev/null and b/components/GPT/.DS_Store differ diff --git a/components/GPT/components/client-chat.vue b/components/GPT/components/client-chat.vue new file mode 100644 index 0000000..e689d03 --- /dev/null +++ b/components/GPT/components/client-chat.vue @@ -0,0 +1,414 @@ + + + + + diff --git a/components/GPT/components/pending.vue b/components/GPT/components/pending.vue new file mode 100644 index 0000000..d899534 --- /dev/null +++ b/components/GPT/components/pending.vue @@ -0,0 +1,232 @@ + + + + + diff --git a/components/GPT/index.vue b/components/GPT/index.vue new file mode 100644 index 0000000..102d15f --- /dev/null +++ b/components/GPT/index.vue @@ -0,0 +1,799 @@ + + + + \ No newline at end of file diff --git a/components/GPT/utils/ClientData.js b/components/GPT/utils/ClientData.js new file mode 100644 index 0000000..30e6428 --- /dev/null +++ b/components/GPT/utils/ClientData.js @@ -0,0 +1,347 @@ +import Vue from "vue"; +import { MESSAGE_TYPE } from "./chat_constant"; +import { getQueryVariable, generateRequestId, arrayUnique } from "./util"; +import { v4 as uuidv4 } from "uuid"; +import GLOBAL from "./global"; +import Observer from "./observer"; +const $e = Observer; +// const $s = this.$s; + +let cache = null; // 缓存 +let timeoutTasks = {}; // 超时任务管理 +const msgSendTimeout = 2 * 60 * 1000; // 发送消息超时ms,此处超时默认为2min + +class ClientData { + constructor(option) { + cache = { + session_id: "", // 会话ID + configInfo: null, // 配置信息 + chatsContent: [], // 会话聊天内容 + systemEvents: [], // 系统事件栈 + transferInfo: { + transferStatus: false, + transferAvatar: "", + }, // 当前转人工状态 + }; + + this.$s = null + + } + init() { + // 获取基础配置 + this.queryConfigInfo(); + } + setAttr(options) { + this.$s =options.socketObj + } + // 获取基础配置 + async queryConfigInfo() { + try { + const seatBizId = ''// getQueryVariable("seat_biz_id"); + console.log("seatBizId", seatBizId); + const sessionInfo = await this.createSession(); + console.log("createsession, res", sessionInfo); + if (sessionInfo.code === 0) { + cache.seat_biz_id = seatBizId; + cache.session_id = sessionInfo.data.session_id; + } else { + uni.showModal({ + title: "获取会话ID失败,请重试", + icon: "none", + }); + } + // 接着获取机器人基础信息 + const botInfo = { + code: 0, + data: { + name: "测试机器人", + avatar: + "https://qbot-1251316161.cos.ap-nanjing.myqcloud.com/avatar.png", + is_available: true, + bot_biz_id: "1664519736704069632", + }, + }; + if (botInfo.data) { + cache.configInfo = botInfo.data; + cache.configInfo.session_id = sessionInfo.data.session_id; + $e.$emit("client_configChange", cache.configInfo); + } else { + uni.showModal({ + title: "获取机器人信息失败", + icon: "none", + }); + } + } catch (e) { + console.log("获取机器人信息失败", e); + uni.showModal({ + title: "获取会话信息失败,请刷新页面重试", + icon: "none", + }); + } + } + async createSession() { + const session_id = uuidv4(); + return { code: 0, data: { session_id: session_id } }; + } + // 消息上行事件(用户端) + triggerSendMsg =async (msg, type='text', status= true) => { + console.log("[triggerSendMsg]", msg, wx); + if (!cache.configInfo || !cache.configInfo.session_id) { + await this.queryConfigInfo(); + } + const requestId = generateRequestId(); + const params = { + request_id: requestId, + session_id: cache.configInfo ? cache.configInfo.session_id : 0, + is_msg_status: status + }; + if (type == "text") { + params.content = msg; + } else if (type == "img") { + params.content = `![](${msg})`; + params.realContent = msg; + } + + this.$s.emit("send", params, type); + } + + // 监听token用量和详情事件 + listenTokenStat() { + this.$s.on("token_stat", (data) => { + $e.$emit("token_state_change", data); + if (data.session_id !== cache.session_id) return; // 若新消息不属于当前机器人时,则不做处理 + let loadingMsg = cache.chatsContent.find((el) => el.loading_message); + let loadingText = "思考中"; + if (loadingMsg) { + if (data.procedures && data.procedures.length > 0) { + loadingText = + data.procedures[data.procedures.length - 1].title || "思考中"; + } + let currentList = cache.chatsContent; + currentList.forEach((el) => { + if (el.loading_message) { + el.text = loadingText; + el.record_id = data.record_id; + el.tokens_msg = data; + // 只有标准模式加这个 + if (GLOBAL.webimToken[0].pattern === "standard") { + el.is_final = false; + } + } + }); + $e.$emit("client_msgContentChange", { + chatsContent: cache.chatsContent, + type: MESSAGE_TYPE.ANSWER, + }); + } else { + let findedMsg = cache.chatsContent.find( + (el) => el.record_id === data.record_id + ); + if (!findedMsg) return; + findedMsg.tokens_msg = data; + + $e.$emit("client_msgContentChange", { + chatsContent: cache.chatsContent, + type: MESSAGE_TYPE.ANSWER, + }); + } + }); + } + + // 组装消息队列数据 + // 问题确认消息:根据request_id关联覆盖(服务端收到问题后的确认消息) + // 答案消息:倒序遍历插入(服务端答案消息) + assembleMsgContent(msgList, type) { + console.log("assembleMsgContent", msgList, type); + let newMsg = msgList; + + if (type === MESSAGE_TYPE.QUESTION) { + // 发送的问题消息由前端临时插入消息队列 + cache.chatsContent.push(newMsg); + } else if (type === MESSAGE_TYPE.ANSWER) { + if (cache.chatsContent.length < 1) { + cache.chatsContent.push(newMsg); + } else { + let currentList = cache.chatsContent; + + timeoutTasks[newMsg.request_id] && + clearTimeout(timeoutTasks[newMsg.request_id]); + + if (currentList.length === 2 && newMsg.can_rating) { + currentList[0].transferRobot = true; + } + if (newMsg.transfer && newMsg.loading_message) { + currentList.pop(); + currentList[currentList.length - 1].loading_message = false; + currentList[currentList.length - 1] = { + ...newMsg, + ...currentList[currentList.length - 1], + transfer: true, + transferRobot: false, + }; + } else { + for (let i = currentList.length - 1; i >= 0; i--) { + const { transfer, quit, transferRobot } = currentList[i]; + let tmp = { + ...newMsg, + transfer, + quit, + transferRobot, + }; + // 保留tokens_msg,防止覆盖 + if (currentList[i].tokens_msg) { + tmp = { ...tmp, tokens_msg: currentList[i].tokens_msg }; + } + // 保留thought 放置被覆盖 + if (currentList[i].agent_thought) { + tmp = { ...tmp, agent_thought: currentList[i].agent_thought }; + } + // 保留reference + if (currentList[i].references) { + tmp = { ...tmp, references: currentList[i].references }; + } + // 答案消息流式输出覆盖(record_id) + if (newMsg.record_id === currentList[i].record_id) { + currentList[i] = tmp; + break; + } + // 服务端问题消息确认数据,覆盖前端插入的临时问题消息数据(request_id匹配 & 自己发出的问题消息) + if ( + newMsg.request_id && + newMsg.request_id === currentList[i].request_id && + newMsg.is_from_self + ) { + newMsg.is_loading = false; // 服务端确认收到问题消息,则去除”发送中“状态 + currentList[i] = tmp; + // 非人工状态时, 并且用户发送的不是敏感消息。插入临时[正在思考中...]消息 + if (!newMsg.is_evil && !cache.transferInfo.transferStatus) { + currentList.push({ + loading_message: true, + is_from_self: false, + content: "", + from_avatar: cache.configInfo.avatar, + timestamp: Number(currentList[i].timestamp), // 精确到秒 + }); + } + break; + } + // 插入最新答案消息 + if (Number(newMsg.timestamp) >= Number(currentList[i].timestamp)) { + if (currentList[i].loading_message) { + // 删除原来的[正在思考中...]消息 + currentList[currentList.length - 1] = newMsg; + } else { + currentList.splice(i + 1, 0, newMsg); + } + break; + } + if ( + i === 0 && + Number(newMsg.timestamp) < Number(currentList[i].timestamp) + ) { + currentList.splice(0, 0, newMsg); + } + } + } + } + } else if (type === MESSAGE_TYPE.HISTORY) { + let currentList = cache.chatsContent; + // 历史数据打上标签,无需展示”重新生成“和”停止生成“操作 + msgList = msgList.map((r) => { + return { + ...r, + is_history: true, + is_final: true, + }; + }); + + if (currentList.length === 0) { + // 若消息队列为空(用户端,初始拉取历史记录,用做判断欢迎页展示场景) + cache.chatsContent = [].concat(msgList); + } else { + // 若消息队列不为空 + let oldMsgCurrent = currentList[0]; + let newMsgHistory = msgList[msgList.length - 1]; + + // 将历史数据拼装到消息队列中(按照时间戳重排数据) + if (Number(newMsgHistory.timestamp) < Number(oldMsgCurrent.timestamp)) { + cache.chatsContent = [].concat(msgList).concat(cache.chatsContent); + } else { + msgList.reverse().forEach((msg) => { + for (let i = 0; i < cache.chatsContent.length; i++) { + if (msg.record_id === cache.chatsContent[i].record_id) { + // 重复覆盖 + cache.chatsContent[i] = msg; + break; + } else if ( + Number(msg.timestamp) <= Number(cache.chatsContent[i].timestamp) + ) { + cache.chatsContent.splice(i, 0, msg); + break; + } else if ( + i === cache.chatsContent.length - 1 && + Number(msg.timestamp) > Number(cache.chatsContent[i].timestamp) + ) { + cache.chatsContent.splice(i + 1, 0, msg); + } + } + }); + } + } + } + + // 消息去重。同一record_id取最新,同时保留消息最早的时间戳 + cache.chatsContent = arrayUnique( + cache.chatsContent, + "record_id", + "timestamp" + ); + + // 消息队列变更通知事件 + $e.$emit("client_msgContentChange", { + chatsContent: cache.chatsContent, + type, + }); + } + // 修改指定msgId的消息内容 + modifyMsgContent(msgId) { + const findedMsg = this.getMsgById(msgId); + + if (findedMsg) { + findedMsg.is_final = true; + findedMsg.content = findedMsg.content.concat( + `| 已停止生成` + ); + + $e.$emit("client_msgContentChange", { + chatsContent: cache.chatsContent, + type: MESSAGE_TYPE.STOP, // ”停止生成“事件 + }); + } + } + // 根据msgId获取消息 + getMsgById(msgId) { + const findedMsg = cache.chatsContent.find((r) => r.record_id === msgId); + return findedMsg; + } + // 根据msgId获取其关联问题消息 + getQmsgById(msgId) { + let findedQmsg = null; + const findedMsg = this.getMsgById(msgId); + + if (findedMsg) { + findedQmsg = cache.chatsContent.find( + (r) => r.record_id === findedMsg.related_record_id + ); + } + + return findedQmsg; + } + releaseCache() {} + destroy() { + // be careful to clear the cache to avoid errors + this.releaseCache(); + } +} + +export default ClientData; diff --git a/components/GPT/utils/EventHub.js b/components/GPT/utils/EventHub.js new file mode 100644 index 0000000..c289e6a --- /dev/null +++ b/components/GPT/utils/EventHub.js @@ -0,0 +1,50 @@ +import Vue from 'vue'; + +class EventHub { + constructor (vm) { + this.vm = vm; + this.curVm = null; + this.events = {}; + this.eventMapUid = {}; + } + $register (vm) { + this.curVm = vm; + } + setEventMapUid (uid, type) { + if (!this.eventMapUid[uid]) { + this.eventMapUid[uid] = []; + } + this.eventMapUid[uid].push(type); + } + $on (type, fn) { + if (!this.events[type]) { + this.events[type] = []; + } + this.events[type].push(fn); + if (this.curVm instanceof this.vm) { + this.setEventMapUid(this.curVm._uid, type); + } + } + $emit (type, ...args) { + if (this.events[type]) { + this.events[type].forEach(fn => fn(...args)); + } + } + $off (type, fn) { + if (fn && this.events[type]) { + const index = this.events[type].findIndex(f => f === fn); + if (index !== -1) { + this.events[type].splice(index, 1); + } + return; + } + delete this.events[type]; + } + $offAll (uid) { + const curAllTypes = this.eventMapUid[uid] || []; + curAllTypes.forEach(type => this.$off(type)); + delete this.eventMapUid[uid]; + } +} + +export default new EventHub(Vue);; diff --git a/components/GPT/utils/chat_constant.js b/components/GPT/utils/chat_constant.js new file mode 100644 index 0000000..13b3b80 --- /dev/null +++ b/components/GPT/utils/chat_constant.js @@ -0,0 +1,12 @@ +// type: Q-问题,A-答案,H-历史消息,S-停止生成,C-结束会话,T-转接会话,R-参考来源,F-点赞/点踩回执 +export const MESSAGE_TYPE = { + QUESTION: 'Q', + ANSWER: 'A', + HISTORY: 'H', + STOP: 'S', + CLOSE: 'C', + TRANSFER: 'T', + REFERENCE: 'R', + FEEDBACK: 'F', + WORKBENCH_HISTORY: 'WH' +}; diff --git a/components/GPT/utils/global.js b/components/GPT/utils/global.js new file mode 100644 index 0000000..b68e590 --- /dev/null +++ b/components/GPT/utils/global.js @@ -0,0 +1,8 @@ +wx.GLOBE = { + webimToken: [], + SOCKET: null, +} + + + +export default wx.GLOBE \ No newline at end of file diff --git a/components/GPT/utils/message.js b/components/GPT/utils/message.js new file mode 100644 index 0000000..4eb49ae --- /dev/null +++ b/components/GPT/utils/message.js @@ -0,0 +1,56 @@ +/** + * !插入数据 + * @param roomId + * @param sourceId + * @param orderId + * @param type + * @param fileId + * @param txt + * @param time + * @param userId + * @param nickName + * @param sendOrReceive + * @param address + */ +export const setMsgData = data => { + console.log('setMsgData', data) + const resData = data + const session_id = resData.chatId // 群id + + let msgData = wx.getStorageSync('imMsgData') || {} + // * 插入群数据 + if (msgData[session_id]) { + if (resData.timestamp > msgData[session_id].timestamp) { + msgData[session_id].timestamp = resData.timestamp + } + msgData[session_id].listMsg.push(resData) + } else { + msgData[session_id] = { + listMsg: [resData], + timestamp: resData.timestamp, + session_id: resData.session_id, + } + } + wx.setStorageSync('imMsgData', msgData) + return resData +} + + +// ! 获取群消息 +export const getHistroyMsg = id => { + let msgData = wx.getStorageSync('imMsgData') || {} + let msgList = [] + if (msgData[id]) { + const data = msgData[id] + // *处理历史消息并按时间排序 + const compare = property => { + return function(a, b) { + var value1 = a[property] + var value2 = b[property] + return value1 - value2 + } + } + msgList = data.listMsg.sort(compare('time')) + } + return { msgList } +} diff --git a/components/GPT/utils/observer.js b/components/GPT/utils/observer.js new file mode 100644 index 0000000..20390e7 --- /dev/null +++ b/components/GPT/utils/observer.js @@ -0,0 +1,45 @@ +import GLOBAL from './global'; +class Observer { + constructor() { + this._event = {} + } + $on(eventName, handler) { + if (this._event[eventName]) { + this._event[eventName].push(handler) + } else { + this._event[eventName] = [handler] + } + } + + $emit(eventName) { + let events = this._event[eventName] + let otherArgs = Array.prototype.slice.call(arguments, 1) + let that = this + if (events) { + events.forEach(event => { + event.apply(that, otherArgs) + }) + } + } + $off(eventName, handler) { + let events = this._event[eventName] + if (events) { + this._event[eventName] = events.filter(event => { + return event !== handler + }) + } + } + $once(eventName, handler) { + let that = this + function func() { + let args = Array.prototype.slice.call(arguments, 0) + handler.apply(that, args) + this.off(eventName, func) + } + this.on(eventName, func) + } +} +if (!GLOBAL.observer) { + GLOBAL.observer = new Observer() +} +export default GLOBAL.observer diff --git a/components/GPT/utils/socket.js b/components/GPT/utils/socket.js new file mode 100644 index 0000000..05b7d79 --- /dev/null +++ b/components/GPT/utils/socket.js @@ -0,0 +1,283 @@ +import Vue from "vue"; +import GLOBAL_OBJ from "./global"; +import "./EventHub"; +import { setMsgData } from "./message"; +// 心跳间隔 +const HEART_BEAT_TIME = 20000; +// 心跳最大失败次数(超过此次数重连) +const HEART_BEAT_FAIL_NUM = 1; +// 重连间隔 +const RECONNECT_TIME = 1000; + +export default class Socket { + constructor(option) { + this.socket = null; + this._options = option; + this.timeoutObj = null; + this.robotObj = {}; + this.selfCloseStatus = false + this.reconnectLock = false + console.log("Socket init", GLOBAL_OBJ); + } + getToken() { + return new Promise((resolve, reject) => { + uni.request({ + method: "GET", + dataType: "json", + // url: `http://192.168.124.118:8083/xcx/framework/agent/${this._options.agentId}`, + url: `https://des.js-dyyj.com/getDemoToken?id=${this._options.agentId}`, + success: (res) => { + console.log("请求token成功", res); + resolve(res); + }, + fail: (err) => { + wx.showToast({ + title: "创建失败", + icon: "none", + }); + console.log("请求token失败", err); + reject() + }, + }); + }); + } + async init() { + return this.createSocket(); + } + async createSocket(options) { + return new Promise(async (resolve, reject) => { + const origin = "wss://wss.lke.cloud.tencent.com"; + let path = "/v1/qbot/chat/conn/"; + let initSocket = 1; + let mainToken + const res = await this.getToken(); + if ( + res && + res.data && + res.data.apiResponse && + res.data.apiResponse.Token + ) { + mainToken = res.data.apiResponse.Token; + } + // 机器人信息 + this.robotObj = res.data.requestInfo; + console.log("获取token:", mainToken); + + // 建立连接 + const socket = wx.connectSocket({ + url: `${origin}${path}?EIO=4&transport=websocket`, + success: (e) => { + console.log("创建链接成功", e, socket); + }, + complete: (e) => { + console.log("socket - complete", e); + + }, + }); + this.socket = socket; + GLOBAL_OBJ.SOCKET = this; + + let systemEventEmit = (eventName, data) => { + Vue.prototype.$eventHub.$emit(eventName, data); + }; + + socket.onOpen((e) => { + // 监听发送 + if (initSocket === 1) { + const token = mainToken || ""; + if (token) { + // cb({ token: token }); + this.send({ + data: + "40" + + JSON.stringify({ + token: token, + }), + }); + } else { + // cb({ token: '' }); + this.send({ + data: JSON.stringify({ + token: "", + }), + }); + } + initSocket++; + } else { + const token = mainToken || ""; + // cb({ token: token }); + this.send({ + data: JSON.stringify({ + token: token, + }), + }); + initSocket++; + } + + options && options.complete && options.complete() + }); + + socket.onMessage((e) => { + // console.log("socket.onMessage", e); + const { data } = e; + if (data == 2) { + // 触发浪涌 + this.send({ + data: 3, + }); + } + + const params = this.getMsgData(data); + const { type } = params ?? {}; + this.on(type, params); + if (params && params.type === "reply" && !params.is_from_self) { + // 回复消息 + this._options.onMessage && this._options.onMessage(params); + } + const num = "" + data.substring(0, 2); + if (num == "40") { + resolve(); + } + + this.createInter(); + }); + // 失败 + socket.onError((e) => { + console.log("websocket error 长链接报错", e); + // 失败重建 + this.doConnectTimeout(); + // + }); + // 关闭 + socket.onClose((e) => { + console.log("websocket close 长链接关闭", e); + this.connectSocketTimeOut && clearTimeout(this.connectSocketTimeOut); + //非自动关闭重连 + if (e.code == 1006) { + this.doConnectTimeout(); + } + }); + }); + } + + doConnectTimeout() { + // 重连一次 + if (!this.reconnectLock) { + this.reconnectLock = true; + this.connectSocketTimeOut = setTimeout(() => { + this.createSocket({ + complete: function (res) { + this.reconnectLock = false; + }, + }); + }, RECONNECT_TIME); + } + } + + onConnect(e) { + console.log("websocket connect", e); + } + + send(e, t) { + this.socket && this.socket.send(e); + this.createInter(); + } + + getMsgData(e) { + if (e && typeof e == "string") { + const status = e.indexOf("42"); + const index = e.indexOf("["); + if (status > -1 && index > -1) { + const txt = e.substring(index); + const item = JSON.parse(txt); + const [type, obj] = item; + const payload = obj.payload ? obj.payload : {}; + const params = { + chatId: this._options.agentId, + type, + contentType: "text", //默认文字 + timestamp: new Date().getTime(), + ...payload, + }; + // 缓存聊天记录 + return params; + } + } + return null; + } + + emit(type, params, contentType) { + console.log("emit", type, params); + const data = { + payload: params, + }; + switch (type) { + case "send": + // 发送消息 + const socketParams = { data: "42" + JSON.stringify(["send", data]) }; + this.send(socketParams, contentType); + + console.log(params.request_id + '发送内容', data) + if (!params.is_msg_status) { + // 不写入缓存中 + return; + } + const msgParams = { + chatId: this._options.agentId, + contentType, + type, + timestamp: new Date().getTime(), + ...params, + content: params.realContent ? params.realContent : params.content, + }; + msgParams && setMsgData(msgParams); + + break; + } + } + // 监听 + on(type, params) { + switch (type) { + case "reply": + // 回复结束 + const tmpParams = { + ...params, + name: this.robotObj.name, + headImage: this.robotObj.headImage, + }; + if ( + params && + params.type === "reply" && + !params.is_from_self && + params.can_rating + ) { + // 回复消息 + this._options.onMessage && this._options.onMessage(tmpParams); + } + + if (!params.is_from_self && params.is_final) { + // 回复结束 写入缓存 + setMsgData(tmpParams); + } + // 回复 + break; + } + } + + // 关闭socket + destroy() { + if (this.socket && this.socket.readyState == 1) { + this.socket && this.socket.close(); + this.socket = null; + } + } + + createInter() { + if (this.timeoutObj) { + clearTimeout(this.timeoutObj); + } + this.timeoutObj = setTimeout(() => { + this.socket && this.socket.send && this.socket.send({ data: 3 }); + }, HEART_BEAT_TIME); + } +} diff --git a/components/GPT/utils/util.js b/components/GPT/utils/util.js new file mode 100644 index 0000000..942e0c8 --- /dev/null +++ b/components/GPT/utils/util.js @@ -0,0 +1,119 @@ +/** + * 获取 location hash 参数 + * @param {string} variable 参数名 + * @returns {string} 参数值 + */ +export const getQueryVariable = (variable) => { + const query = window.location.hash.split('?'); + + if (query.length < 2) { + return ''; + } + + const vars = query[1].split('&'); + + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let i = 0; i < vars.length; i++) { + const pair = vars[i].split('='); + if (pair[0] === variable) { + return decodeURI(pair[1]); + } + } + return ''; +}; + +/** + * 滚动至指定dom底部 + */ +export const scrollToBottom = (sDom, sTop) => { + if (!sDom) return; + sDom.scrollTo({ + top: sTop + // behavior: 'smooth' + }); +}; + +/** + * 数组去重 + */ +export const arrayUnique = (arr, replaceKey, holdKey) => { + let temp = {}; + + return arr.reduce((prev, cur) => { + if (!temp[cur[replaceKey]]) { + temp[cur[replaceKey]] = {index: prev.length}; + prev.push(cur); + } else { + const oldItem = temp[cur[replaceKey]]; + cur[holdKey] = oldItem[holdKey]; + prev.splice(oldItem['index'], 1, cur); + } + + return prev; + }, []); +}; + +export const generateRequestId = (length = 10) => { + const data = + ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', + 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', + 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']; + let nums = ''; + for (let i = 0; i < length; i++) { + const r = parseInt(Math.random() * 61, 10); + nums += data[r]; + } + return nums + '-' + parseInt(Math.random() * 10000000000, 10); +}; + +function escapeHtml (str) { + return str.replace(/[&<>"'/]/g, function (match) { + return { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/' + }[match]; + }); +} + + +export const splitTextForTTS = (text, maxLength = 30) => { + // 定义优先分割的标点符号 + const punctuation = ['。', ',', ';', '?', '!', ',', ';', '?', '!', '、']; + let segments = []; + let segment = ''; + let tempSegment = ''; + + for (let i = 0; i < text.length; i++) { + tempSegment += text[i]; // 如果超过最大长度,则尝试在上一个标点符号处分割 + + if (tempSegment.length > maxLength) { + let lastPunctuationIndex = -1; + for (let j = tempSegment.length - 1; j >= 0; j--) { + if (punctuation.includes(tempSegment[j])) { + lastPunctuationIndex = j; + break; + } + } // 如果找到标点符号,则在标点符号后分割 + + if (lastPunctuationIndex !== -1) { + segments.push(tempSegment.slice(0, lastPunctuationIndex + 1).trim()); + tempSegment = tempSegment.slice(lastPunctuationIndex + 1).trim(); + } else { + // 如果没有找到标点符号,则在最大长度处分割 + segments.push(tempSegment.slice(0, maxLength).trim()); + tempSegment = tempSegment.slice(maxLength).trim(); + } + } + } // 添加最后一个段落 + + if (tempSegment.length > 0) { + segments.push(tempSegment.trim()); + } + + return segments; + } diff --git a/components/ProductSection.vue b/components/ProductSection.vue index f2309ed..f4e7ac5 100644 --- a/components/ProductSection.vue +++ b/components/ProductSection.vue @@ -4,83 +4,92 @@
{{ title }} - 更多 + 更多
- - - - - - - - - - - 智能体 - - {{ item.agent.name }} - - - - - - {{ item.title }} - ¥{{ item.price }} - - - - - + + + +
@@ -124,10 +133,11 @@ export default { type: String, default: "#02fcfc", }, - isFeel: { - type: Boolean, - default: false, + type: { + type: Number, + default: 1, }, + color: { type: String, default: "#D3FCFF", @@ -180,9 +190,17 @@ export default { }, toAgent(e){ - uni.navigateTo({ - url: "/pages/agent/index?id="+e.agent.agentId - }); + if(e.agentStatus){ + uni.navigateTo({ + url: "/subPackages/other/evita?id="+e.agent.agentId + }); + }else{ + uni.showToast({ + title:'请先购买当前商品', + icon:'none' + }) + } + }, // 显示图片 diff --git a/components/WaterfallLayout.vue b/components/WaterfallLayout.vue index d3d3419..808376d 100644 --- a/components/WaterfallLayout.vue +++ b/components/WaterfallLayout.vue @@ -1,320 +1,299 @@ + .like-count { + font-size: 22rpx; + color: #666; + } + \ No newline at end of file diff --git a/manifest.json b/manifest.json index 3e3f8c0..e9579b6 100644 --- a/manifest.json +++ b/manifest.json @@ -64,7 +64,8 @@ } }, "lazyCodeLoading" : "requiredComponents", - "requiredPrivateInfos" : [ "getLocation", "chooseLocation" ] + "requiredPrivateInfos" : [ "getLocation", "chooseLocation" ], + "requiredBackgroundModes" : [ "audio" ] }, "mp-alipay" : { "usingComponents" : true diff --git a/pages/agent/index.vue b/pages/agent/index.vue index 02409e3..72e290b 100644 --- a/pages/agent/index.vue +++ b/pages/agent/index.vue @@ -1,8 +1,45 @@ - + + + \ No newline at end of file + + \ No newline at end of file diff --git a/pages/index/iSoul.vue b/pages/index/iSoul.vue index df6986b..13755f8 100644 --- a/pages/index/iSoul.vue +++ b/pages/index/iSoul.vue @@ -1,1511 +1,1735 @@ \ No newline at end of file +::v-deep .uni-popup { + z-index: 999 !important; +} +view { + box-sizing: border-box; +} + +.profile-container { + background: white; + color: white; +} + +/* 状态栏占位 */ +.status-bar-placeholder { + height: var(--status-bar-height); + width: 100%; +} + +/* 顶部导航 */ +.header { + display: flex; + justify-content: space-between; + align-items: center; + height: 48px; +} + +.back-btn, +.more-btn { + width: 60rpx; + height: 60rpx; + display: flex; + align-items: center; + justify-content: center; + font-size: 40rpx; + color: white; +} + +.dots { + font-size: 32rpx; + font-weight: bold; +} + +/* 用户信息区域 */ +.user-section { + display: flex; + align-items: center; + padding: 0 40rpx 30rpx; +} + +.user-avatar { + width: 120rpx; + height: 120rpx; + border-radius: 50%; + overflow: hidden; + margin-right: 30rpx; +} + +.avatar-img { + width: 100%; + height: 100%; +} + +.user-info { + flex: 1; +} + +.username { + font-size: 36rpx; + font-weight: bold; + color: white; + margin-bottom: 10rpx; +} + +.user-id { + font-size: 24rpx; + color: rgba(255, 255, 255, 0.8); + margin-bottom: 10rpx; +} + +.location { + display: flex; + align-items: center; + font-size: 24rpx; + color: rgba(255, 255, 255, 0.8); +} + +.location-icon, +.info-icon { + margin-right: 8rpx; +} + +/* 城市艺术漫游 */ +.city-art { + padding: 0 40rpx 20rpx; + display: flex; + align-items: center; + justify-content: space-between; +} + +.city-title { + display: flex; + align-items: center; + font-size: 28rpx; + color: rgba(255, 255, 255, 0.9); +} + +.pin-icon { + margin-right: 8rpx; +} + +.city-name { + font-size: 28rpx; + color: white; + font-weight: bold; +} + +.gender-icon { + width: 40rpx; + height: 40rpx; + background: rgba(255, 255, 255, 0.2); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 24rpx; +} + +/* 统计数据 */ +.stats-section { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 80rpx; + padding-bottom: 50rpx; + padding: 0 40rpx; +} + +.stats-left { + display: flex; + gap: 60rpx; +} + +.stat-item { + text-align: center; + min-width: 80rpx; +} + +.stat-number { + font-size: 32rpx; + font-weight: bold; + color: white; + margin-bottom: 8rpx; +} + +.stat-label { + font-size: 24rpx; + color: rgba(255, 255, 255, 0.8); +} + +/* 权益兑换入口 */ +.exchange-entry { + display: flex; + flex-direction: column; + align-items: center; + padding: 16rpx; + background: rgba(255, 255, 255, 0.1); + border-radius: 20rpx; + min-width: 120rpx; + transition: all 0.3s ease; + + &:active { + transform: scale(0.95); + background: rgba(255, 255, 255, 0.2); + } +} + +.exchange-icon { + font-size: 32rpx; + margin-bottom: 8rpx; +} + +.exchange-text { + font-size: 22rpx; + color: rgba(255, 255, 255, 0.9); + font-weight: 500; +} + +/* 操作按钮 */ +.action-buttons { + display: flex; + padding: 0 40rpx 40rpx; + gap: 20rpx; +} + +.follow-btn { + flex: 1; + background: #ff4757; + border-radius: 50rpx; + padding: 20rpx; + text-align: center; + color: white; + font-size: 28rpx; + font-weight: bold; +} + +.message-btn { + flex: 1; + background: rgba(255, 255, 255, 0.2); + border-radius: 50rpx; + padding: 20rpx; + text-align: center; + color: white; + font-size: 28rpx; + font-weight: bold; +} + +/* 待激活的Agent */ +.agent-section { + margin: 0 47rpx; + border-radius: 20rpx; + margin-bottom: 0rpx; + overflow: hidden; + display: flex; + align-items: center; + padding: 30rpx 0; +} + +.agent-header { + margin-right: 30rpx; + flex-shrink: 0; +} + +.agent-title { + font-size: 28rpx; + color: #000000; +} + +.agent-content { + flex: 1; +} + +.agent-scroll { + width: 430rpx; + white-space: nowrap; + /* 微信小程序隐藏横向滚动条 */ + overflow: hidden; + + /* Webkit浏览器 */ + &::-webkit-scrollbar { + display: none !important; + width: 0 !important; + height: 0 !important; + } + + /* Firefox */ + scrollbar-width: none; + /* IE和Edge */ + -ms-overflow-style: none; +} + +.agent-avatars { + display: inline-flex; + gap: 20rpx; +} + +.agent-avatar { + width: 104rpx; + height: 104rpx; + border-radius: 50%; + overflow: hidden; + flex-shrink: 0; +} + +.agent-img { + width: 100%; + height: 100%; +} + +/* 数字资产权益 */ +.digital-assets { + margin: 0 30rpx 30rpx; +} + +.asset-header { + margin-bottom: 20rpx; +} + +.asset-title { + font-size: 28rpx; + color: #000000; + font-weight: 500; +} + +.asset-content { + width: 100%; +} + +/* 单个数据铺满显示样式 */ +.asset-card-single { + width: 100%; + border-radius: 20rpx; + overflow: hidden; + box-shadow: 0 15rpx 12rpx rgba(0, 0, 0, 0.15); + transition: all 0.3s ease; + position: relative; +} + +.asset-card-single:active { + transform: scale(0.98); + box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.2); +} + +.single-card-wrapper { + position: relative; + width: 100%; + height: 400rpx; + overflow: hidden; +} + +.asset-img-single { + width: 100%; + height: 100%; + transition: transform 0.3s ease; +} + +.asset-card-single:active .asset-img-single { + transform: scale(1.05); +} + +.asset-overlay-single { + position: absolute; + bottom: 0; + left: 0; + right: 0; + background: linear-gradient(transparent, rgba(0, 0, 0, 1)); + padding: 60rpx 30rpx 30rpx; + color: white; +} + +.asset-badge-single { + background: linear-gradient(135deg, #fffdb7 0%, #97fffa 100%); + color: black; + font-size: 22rpx; + padding: 8rpx 16rpx; + border-radius: 20rpx; + display: inline-block; + margin-bottom: 16rpx; + font-weight: 500; +} + +.asset-name-single { + font-size: 32rpx; + font-weight: bold; + margin-bottom: 8rpx; + line-height: 1.3; +} + +.asset-desc-single { + font-size: 28rpx; + color: rgba(255, 255, 255, 0.9); + margin-bottom: 20rpx; + line-height: 1.4; +} + +.asset-action-single { + display: flex; + align-items: center; + justify-content: space-between; + background: linear-gradient(135deg, #fffdb7e6 0%, #97fffab5 100%); + border-radius: 25rpx; + padding: 12rpx 20rpx; + backdrop-filter: blur(10rpx); +} + +.action-text-single { + font-size: 28rpx; + font-weight: bold; + color: black; +} + +.action-arrow-single { + font-size: 28rpx; + font-weight: bold; + color: black; + transition: transform 0.3s ease; +} + +.asset-card-single:active .action-arrow-single { + transform: translateX(6rpx); +} + +.asset-scroll-container { +} + +.asset-scroll { + width: 100%; + white-space: nowrap; + overflow: hidden; + + /* 微信小程序隐藏横向滚动条 */ + &::-webkit-scrollbar { + display: none !important; + width: 0 !important; + height: 0 !important; + } + + /* Firefox */ + scrollbar-width: none; + /* IE和Edge */ + -ms-overflow-style: none; +} + +.asset-cards { + display: inline-flex; + gap: 20rpx; + padding: 30rpx 10rpx; +} + +.asset-card { + width: 500rpx; + border-radius: 20rpx; + overflow: hidden; + box-shadow: 0 15rpx 12rpx rgba(0, 0, 0, 0.15); + transition: all 0.3s ease; + position: relative; + flex-shrink: 0; +} + +.asset-card:active { + transform: scale(0.98); + box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.2); +} + +.asset-card-wrapper { + position: relative; + width: 100%; + height: 300rpx; + overflow: hidden; +} + +.asset-img { + width: 100%; + height: 100%; + transition: transform 0.3s ease; +} + +.asset-card:active .asset-img { + transform: scale(1.05); +} + +.asset-overlay { + position: absolute; + bottom: 0; + left: 0; + right: 0; + background: linear-gradient(transparent, rgba(0, 0, 0, 1)); + padding: 40rpx 30rpx 30rpx; + color: white; + // backdrop-filter: blur(10rpx); + width: 100%; + height: 100%; +} + +.asset-badge { + background: linear-gradient(135deg, #fffdb7 0%, #97fffa 100%); + color: black; + font-size: 20rpx; + padding: 6rpx 12rpx; + border-radius: 16rpx; + display: inline-block; + margin-bottom: 12rpx; + font-weight: 500; +} + +.asset-name { + font-size: 26rpx; + font-weight: 500; + margin-bottom: 6rpx; + line-height: 1.3; +} + +.asset-desc { + font-size: 34rpx; + color: white; + margin-bottom: 16rpx; + font-weight: bold; + line-height: 1.4; +} + +.asset-action { + display: flex; + align-items: center; + justify-content: space-between; + background: linear-gradient(135deg, #fffdb7e6 0%, #97fffab5 100%); + border-radius: 20rpx; + padding: 10rpx 16rpx; + backdrop-filter: blur(10rpx); + white-space: nowrap; + min-width: 0; +} + +.action-text-order { + font-size: 26rpx; + font-weight: bold; + color: black; + flex-shrink: 0; + overflow: hidden; + text-overflow: ellipsis; +} + +.action-arrow { + font-size: 26rpx; + font-weight: bold; + color: white; + transition: transform 0.3s ease; + flex-shrink: 0; + margin-left: 8rpx; +} + +.action-arrow-order { + font-size: 26rpx; + font-weight: bold; + color: black; + transition: transform 0.3s ease; + flex-shrink: 0; + margin-left: 8rpx; +} + +.asset-card:active .action-arrow { + transform: translateX(6rpx); +} + +/* 原来的图片风格 */ +.asset-card-old { + border-radius: 20rpx; + overflow: hidden; +} + +/* 底部操作按钮 */ +.asset-actions { + display: flex; + padding: 20rpx 0; + background: white; + border-radius: 20rpx; + font-size: 25rpx; +} + +.action-icon { + width: 36rpx; + height: 36rpx; + border-radius: 50%; + margin-bottom: 12rpx; +} + +.action-icon.red { + background: #ff4757; +} + +.action-icon.gray { + background: #ccc; +} + +/* 有感商品 */ +.feeling-goods { + margin: 20rpx 30rpx 30rpx; +} + +.goods-card { + border-radius: 20rpx; + overflow: hidden; +} + +.goods-bg { + padding: 40rpx 30rpx 20rpx; + position: relative; +} + +.goods-title { + font-size: 32rpx; + color: white; + font-weight: bold; + margin-bottom: 10rpx; +} + +.goods-brand { + font-size: 36rpx; + color: white; + font-weight: bold; + margin-bottom: 5rpx; +} + +.goods-subtitle { + font-size: 24rpx; + color: rgba(255, 255, 255, 0.8); +} + +.goods-actions { + display: flex; + background: white; +} + +.goods-action { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + padding: 20rpx 10rpx; + font-size: 26rpx; + color: #000000; +} + +.goods-action.active { + color: #ff4757; +} + +/* 数字资产纪念册 */ +.memorial-section { + margin: 0 47rpx; + margin-bottom: 30rpx; +} + +.memorial-divider { + display: flex; + align-items: center; + padding: 30rpx 0; + margin-bottom: 30rpx; +} + +.divider-line { + flex: 1; + height: 2rpx; + background: #e0e0e0; +} + +.divider-text { + padding: 0 30rpx; + font-size: 30rpx; + color: #000000; + font-weight: bold; + white-space: nowrap; +} + +.memorial-cards { + display: flex; + gap: 20rpx; +} + +/* 单条数据特殊展示样式 */ +.memorial-card-single { + width: 100%; + max-width: 100%; + margin: 0 auto; + border-radius: 20rpx; + overflow: hidden; + box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.15); + transition: all 0.3s ease; + position: relative; +} + +.memorial-card-single:active { + transform: scale(0.98); + box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.2); +} + +.single-card-wrapper { + position: relative; + width: 100%; + height: 400rpx; + overflow: hidden; +} + +.memorial-img-single { + width: 100%; + height: 100%; + transition: transform 0.3s ease; +} + +.memorial-card-single:active .memorial-img-single { + transform: scale(1.05); +} + +.single-overlay { + position: absolute; + bottom: 0; + left: 0; + right: 0; + background: linear-gradient(transparent, rgba(0, 0, 0, 0.7)); + padding: 60rpx 30rpx 30rpx; + color: white; +} + +.single-badge { + background: linear-gradient(135deg, #77f3f9 0%, #764ba2 100%); + color: white; + font-size: 22rpx; + padding: 8rpx 16rpx; + border-radius: 20rpx; + display: inline-block; + margin-bottom: 16rpx; + font-weight: 500; + box-shadow: 0 4rpx 12rpx rgba(102, 126, 234, 0.3); +} + +.single-title { + font-size: 32rpx; + font-weight: bold; + margin-bottom: 8rpx; + line-height: 1.3; +} + +.single-desc { + font-size: 24rpx; + color: rgba(255, 255, 255, 0.8); + margin-bottom: 20rpx; + line-height: 1.4; +} + +.single-action { + display: flex; + align-items: center; + justify-content: space-between; + background: rgba(255, 255, 255, 0.1); + border-radius: 25rpx; + padding: 12rpx 20rpx; + backdrop-filter: blur(10rpx); + border: 1rpx solid rgba(255, 255, 255, 0.2); +} + +.action-text { + font-size: 26rpx; + font-weight: 500; + color: white; +} + +.action-arrow { + font-size: 28rpx; + font-weight: bold; + color: white; + transition: transform 0.3s ease; +} + +.memorial-card-single:active .action-arrow { + transform: translateX(6rpx); +} + +.memorial-card { + flex: 1; + border-radius: 10rpx; + overflow: hidden; + background: white; +} + +.memorial-img { + width: 100%; + height: 425rpx; +} + +.memorial-info { + padding: 20rpx 10rpx; + display: flex; + justify-content: space-between; + align-items: center; +} + +.memorial-title { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 22rpx; + color: #000000; + flex: 1; +} + +.memorial-more { + font-size: 14rpx; + color: #999; +} + +/* 无数据时的空状态样式 */ +.memorial-empty { + display: flex; + justify-content: center; + align-items: center; + min-height: 280rpx; +} + +.empty-content { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + color: #333333; +} + +.empty-icon { + font-size: 80rpx; + margin-bottom: 20rpx; + opacity: 0.6; +} + +.empty-title { + font-size: 32rpx; + color: white; + font-weight: 500; + margin-bottom: 10rpx; +} + +.empty-desc { + font-size: 26rpx; + margin-bottom: 8rpx; +} + +.empty-tip { + font-size: 24rpx; + line-height: 1.4; +} + +/* 底部菜单 */ +.bottom-menu { + padding: 40rpx 30rpx; + background: white; + margin: 0 30rpx; + border-radius: 20rpx; + margin-bottom: 30rpx; +} + +.menu-item { + padding: 30rpx 0; + border-bottom: 1rpx solid #f5f5f5; + font-size: 28rpx; + color: #333; +} + +.menu-item:last-child { + border-bottom: none; +} + +// 用户顶部背景区域 +.user-top { + background-size: cover; + background-position: center; + background-repeat: no-repeat; + padding-bottom: 20rpx; +} + +.digital-img { + height: 265rpx; + width: 100%; + border-radius: 20rpx; +} + +/* 权益兑换弹窗 */ +.exchange-popup { + width: 600rpx; + background-color: #ffffff; + border-radius: 16rpx; + overflow: hidden; +} + +.popup-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 30rpx; + border-bottom: 1px solid #f0f0f0; +} + +.popup-title { + font-size: 32rpx; + font-weight: 600; + color: #333; +} + +.popup-close { + font-size: 40rpx; + color: #999; + padding: 0 10rpx; +} + +.popup-content { + padding: 40rpx 30rpx 30rpx; +} + +.input-section { + margin-bottom: 40rpx; +} + +.input-label { + display: block; + font-size: 28rpx; + color: #333; + margin-bottom: 20rpx; + font-weight: 500; +} + +.exchange-input { + width: 100%; + height: 88rpx; + border: 2rpx solid #e0e0e0; + border-radius: 12rpx; + padding: 0 20rpx; + font-size: 28rpx; + color: #333; + box-sizing: border-box; + + &:focus { + border-color: #77f3f9; + } +} + +.popup-actions { + display: flex; + gap: 20rpx; +} + +.cancel-btn, +.confirm-btn { + flex: 1; + height: 80rpx; + border-radius: 12rpx; + font-size: 28rpx; + font-weight: 600; + border: none; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s ease; + + &:active { + transform: scale(0.98); + } +} + +.cancel-btn { + background: #f5f5f5; + color: #77f3f9; +} + +.confirm-btn { + background: #77f3f9; + color: #000000; +} + +.column-divider { + width: 2rpx; + height: 60rpx; + background: rgba(0, 0, 0, 0.1); + margin: 35rpx 16rpx; + flex-shrink: 0; +} + +.action-box { + display: flex; + align-items: center; + justify-content: space-around; + padding: 20rpx 40rpx 0; +} + +.popup-actions-bottom { + color: #989898; + font-weight: bold; + font-size: 28rpx; + margin-top: 30rpx; + margin-bottom: 30rpx; +} + diff --git a/pages/index/index.vue b/pages/index/index.vue index 7a16720..afdcc3f 100644 --- a/pages/index/index.vue +++ b/pages/index/index.vue @@ -27,6 +27,7 @@ detailUrlPrefix="/subPackages/equityGoods/detail" @like-toggle="handleLikeToggle" color="#D2FFDE" + :type="1" /> + diff --git a/pages/index/readingBody.vue b/pages/index/readingBody.vue index b31743a..6f49f0a 100644 --- a/pages/index/readingBody.vue +++ b/pages/index/readingBody.vue @@ -1,17 +1,87 @@ \ No newline at end of file +.bg { + padding-bottom: 200rpx; +} + +.swiper-box, +.top-box { + margin: 10rpx 25rpx 0; + height: 830rpx; + box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1); + border-radius: 20rpx; + overflow: hidden; + // padding-top: 10rpx; + // background-color: #fff; + + image { + width: 100%; + height: 100%; + } +} + +.carousel-swiper { + width: 582.13rpx; + height: 706.24rpx; + margin: -40rpx auto 0; + + image { + width: 100%; + height: 100%; + } +} + +.exhibit-issue { + font-family: serif; + font-size: 28rpx; + color: #999; + font-style: italic; + padding: 40rpx 30rpx 0 0; + text-align: right; +} + +.custom-indicators { + display: flex; + justify-content: center; + gap: 8rpx; + margin-top: 40rpx; + + .indicator-dot { + width: 8rpx; + height: 8rpx; + border-radius: 4rpx; + background-color: rgba(150, 150, 150, 0.5); + transition: all 0.3s; + } + + .indicator-dot.active { + width: 20rpx; + background-color: rgb(133, 133, 133); + } +} + +.category-scroll { + // padding: 40rpx 0 0 40rpx; + // display: flex; + height: 393rpx; + margin: 50rpx 25rpx 0; + + image { + width: 100%; + height: 393rpx; + flex-shrink: 0; + // margin-right: 20rpx; + border-radius: 20rpx; + } + + video { + width: 100%; + height: 393rpx; + border-radius: 20rpx; + } +} + +.title-box { + font-size: 36rpx; + font-weight: 500; + margin-top: 40rpx; + padding-left: 25rpx; + + image { + width: 185.85rpx; + height: 20.98rpx; + margin-left: 21rpx; + } +} + +.title-box-about { + font-size: 36rpx; + font-weight: 500; + margin-top: 50rpx; + padding-left: 25rpx; + + image { + width: 106.5rpx; + height: 20.98rpx; + margin-left: 21rpx; + } +} + +.reading-title { + margin: 60rpx auto 0; + width: 585rpx; + height: 30rpx; + border-radius: 15rpx; + background-color: rgba(46, 220, 78, 0.8); + font-weight: bold; + font-size: 28rpx; + color: #000000; + padding: 0 40rpx; + position: relative; + + view { + position: absolute; + z-index: 20; + bottom: 15rpx; + } + + & > view:nth-child(2) { + left: 268rpx; + } + + & > view:nth-child(3) { + right: 55rpx; + } + + span { + width: 111rpx; + height: 100%; + background-color: rgb(9, 154, 60); + border-radius: 15rpx; + position: absolute; + left: 32rpx; + } +} + +.reading-box { + margin: 20rpx 25rpx 0; + + padding: 35rpx 10rpx 5rpx; + display: flex; + justify-content: space-around; + flex-wrap: wrap; + position: relative; + // top: -15rpx; + gap: 16rpx; + + image { + width: 100%; + height: 346rpx; + } + + .reading-box-item { + flex: 0 0 calc(50% - 8rpx); + border-radius: 20rpx; + box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1); + background-color: #fff; + padding: 20rpx; + font-size: 30rpx; + } + + .reading-box-item-title { + margin: 15rpx 0; + font-weight: bold; + } + + .reading-box-item-desc { + font-size: 20rpx; + color: #808080; + } +} + +.start-swiper { + width: 100vw; + height: calc(100vh - 123rpx); + position: fixed; + top: 0; + left: 0; + z-index: 100; + + image { + width: 100%; + height: 100%; + } +} + +.desc-box { + padding: 0 25rpx; + color: #616161; + margin: 30rpx 0; + font-size: 22rpx; + + view { + margin-bottom: 20rpx; + } +} + +.new-book-body { + margin: 20rpx 25rpx 0; + border-radius: 20rpx; + box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1); + padding: 50rpx 0rpx 30rpx; + background-color: #fff; + text-align: left; + + image { + margin: 0 auto; + display: block; + } + + .new-book-body-title { + width: 583rpx; + font-size: 37rpx; + font-weight: bold; + margin: 20rpx auto; + } + + .new-book-body-desc { + width: 583rpx; + color: #808080; + font-size: 24rpx; + margin: 20rpx auto; + } +} +.newBookCard { + width: 583rpx; + height: 706rpx; + margin: 0 auto; + background-size: 100% auto; + background-repeat: no-repeat; + background-position: top; + background-color: #fff; + background-image: url(https://des.js-dyyj.com/data/2025/08/29/a1a0009c-9248-48e3-b5a7-5d2970a1eb19.png); +} + diff --git a/pages/index/sensoryStore.vue b/pages/index/sensoryStore.vue index 8f33d7f..21997cc 100644 --- a/pages/index/sensoryStore.vue +++ b/pages/index/sensoryStore.vue @@ -1,5 +1,6 @@