Browse Source

Merge branch 'milestone-20251031-简版功能开发' into maziyang/feature-20251025172218-智能客服中台

zhaowenkang/feature-20251028181547-行情页面
maziyang 4 weeks ago
parent
commit
c15e3bc4be
  1. 6
      .hbuilderx/launch.json
  2. 88
      api/deepExploration/deepExploration.js
  3. 0
      api/deepMate.js
  4. 68
      api/deepMate/deepMate.js
  5. 116
      api/start/login.js
  6. 260
      api/tcpConnection.js
  7. 392
      common/canvasMethod.js
  8. 354
      common/stockTimeInformation.js
  9. 123
      common/util.js
  10. 34
      components/DeepMate.vue
  11. 4
      components/IndexCard.vue
  12. 58
      components/MarketOverview.vue
  13. 536
      components/deepExploration_header.vue
  14. 49
      components/footerBar.vue
  15. 183
      components/login-prompt.vue
  16. 22
      main.js
  17. 38
      manifest.json
  18. 469
      package-lock.json
  19. 10
      package.json
  20. 272
      pages.json
  21. 100
      pages/blank/institutionalTrendsBriefing.vue
  22. 100
      pages/blank/notice.vue
  23. 801
      pages/deepExploration/MainForceActions.vue
  24. 427
      pages/deepExploration/deepExploration.vue
  25. 354
      pages/deepExploration/stockSelectDetail.vue
  26. 1733
      pages/deepMate/deepMate.vue
  27. 28
      pages/home/deepExploration.vue
  28. 1214
      pages/home/home.vue
  29. 290
      pages/home/member.vue
  30. 655
      pages/marketSituation/chartExample.vue
  31. 493
      pages/marketSituation/countryMarket.vue
  32. 301
      pages/marketSituation/forexMetals.vue
  33. 62
      pages/marketSituation/globalIndex.vue
  34. 2258
      pages/marketSituation/marketCondition.vue
  35. 82
      pages/marketSituation/marketDetail.vue
  36. 757
      pages/marketSituation/marketOverview.vue
  37. 379
      pages/marketSituation/marketSituation.vue
  38. 86
      pages/setting/about.vue
  39. 218
      pages/setting/account.vue
  40. 85
      pages/setting/bind.vue
  41. 142
      pages/setting/email.vue
  42. 68
      pages/setting/font.vue
  43. 165
      pages/setting/general.vue
  44. 76
      pages/setting/introduce.vue
  45. 221
      pages/setting/market.vue
  46. 62
      pages/setting/message.vue
  47. 82
      pages/setting/newVersion.vue
  48. 144
      pages/setting/nextPwd.vue
  49. 171
      pages/setting/password.vue
  50. 143
      pages/setting/phone.vue
  51. 108
      pages/setting/push.vue
  52. 87
      pages/setting/server.vue
  53. 111
      pages/setting/share.vue
  54. 64
      pages/setting/theme.vue
  55. 1054
      pages/start/Registration/Registration.vue
  56. 1341
      pages/start/Registration/list.js
  57. 13
      pages/start/agreement/agreement.vue
  58. 56
      pages/start/index/index.vue
  59. 1341
      pages/start/login/list.js
  60. 1190
      pages/start/login/login.vue
  61. 69
      pages/start/login/verification.js
  62. 13
      pages/start/privacy/privacy.vue
  63. 1012
      pages/start/recoverPassword/recoverPassword.vue
  64. 170
      pages/start/select/select.vue
  65. 73
      pages/start/startup/startup.vue
  66. 138
      server/login.json
  67. BIN
      static/deepExploration-images/1.png
  68. BIN
      static/deepExploration-images/2.png
  69. BIN
      static/deepExploration-images/3.png
  70. BIN
      static/deepExploration-images/4.png
  71. BIN
      static/deepExploration-images/ASC.png
  72. BIN
      static/deepExploration-images/Americle.png
  73. BIN
      static/deepExploration-images/DESC.png
  74. BIN
      static/deepExploration-images/all.png
  75. BIN
      static/deepExploration-images/close.png
  76. BIN
      static/deepExploration-images/delete.png
  77. BIN
      static/deepExploration-images/history.png
  78. BIN
      static/deepExploration-images/icon1.png
  79. BIN
      static/deepExploration-images/icon2.png
  80. BIN
      static/deepExploration-images/icon3.png
  81. BIN
      static/deepExploration-images/icon4.png
  82. BIN
      static/deepExploration-images/last.png
  83. BIN
      static/deepExploration-images/next.png
  84. BIN
      static/deepExploration-images/notice.png
  85. BIN
      static/deepExploration-images/plus.png
  86. BIN
      static/deepExploration-images/search.png
  87. BIN
      static/deepExploration-images/showAll.png
  88. BIN
      static/flag/ad.png
  89. BIN
      static/flag/ae.png
  90. BIN
      static/flag/af.png
  91. BIN
      static/flag/ag.png
  92. BIN
      static/flag/ai.png
  93. BIN
      static/flag/al.png
  94. BIN
      static/flag/am.png
  95. BIN
      static/flag/an.png
  96. BIN
      static/flag/ao.png
  97. BIN
      static/flag/aq.png
  98. BIN
      static/flag/ar.png
  99. BIN
      static/flag/as.png
  100. BIN
      static/flag/at.png

6
.hbuilderx/launch.json

@ -2,9 +2,11 @@
"version" : "1.0", "version" : "1.0",
"configurations" : [ "configurations" : [
{ {
"customPlaygroundType" : "device",
"playground" : "standard",
"customPlaygroundType" : "local",
"playground" : "custom",
"type" : "uni-app:app-android" "type" : "uni-app:app-android"
// "playground" : "standard",
// "type" : "uni-app:app-ios"
} }
] ]
} }

88
api/deepExploration/deepExploration.js

@ -0,0 +1,88 @@
import { http } from '@/utils/http.js'
//主力追踪意图
export const getModel1First = (data) => {
return http({
method: 'POST',
url: '/api/coze/trackingFirst',
data
})
}
//主力追踪意图
export const getModel1Second = (data) => {
return http({
method: 'POST',
url: '/api/coze/trackingSecond',
data
})
}
//主力追踪意图
export const getModel2First = (data) => {
return http({
method: 'POST',
url: '/api/coze/radarFirst',
data
})
}
//主力追踪意图
export const getModel2Second = (data) => {
return http({
method: 'POST',
url: '/api/coze/radarSecond',
data
})
}
//主力追踪意图
export const getModel3First = (data) => {
return http({
method: 'POST',
url: '/api/coze/decodingFirst',
data
})
}
//主力追踪意图
export const getModel3Second = (data) => {
return http({
method: 'POST',
url: '/api/coze/decodingSecond',
data
})
}
//主力追踪意图
export const getModel4First = (data) => {
return http({
method: 'POST',
url: '/api/coze/fundsFirst',
data
})
}
//主力追踪意图
export const getModel4Second = (data) => {
return http({
method: 'POST',
url: '/api/coze/fundsSecond',
data
})
}
//历史记录列表
export const RecordListApi = (data) => {
return http({
method: 'POST',
url: '/api/coze/mainForceList',
data:data
})
}

0
api/deepMate.js

68
api/deepMate/deepMate.js

@ -0,0 +1,68 @@
import { http } from '../../utils/http'
export const getData = () => {
return http({
method: 'GET',
url: '/ka',
})
}
/**
* 意图识别
* POST /api/deepMate/dmFirst
* headers: token, content-type: application/json, contentType: application/json, version, client
* body: { content, language, marketList }
*/
export const postIntent = (data) => {
return http({
method: 'POST',
url: '/api/deepMate/dmFirst',
data
})
}
/**
* 获取股票信息
* headers: token, content-type: application/json, contentType: application/json, version, client
* body: { language, token, recordId, parentId, stockId }
*/
export const postStock = (data) => {
return http({
method: 'POST',
url: '/api/deepMate/dmSecond',
data
})
}
/**
* 获取历史记录
*/
export const postHistory = (data) => {
return http({
method: 'POST',
url: '/api/deepMate/dmList',
data
})
}
/**
* 历史记录详情
*/
export const postHistoryDetail = (data) => {
return http({
method: 'POST',
url: '/api/deepMate/clickRecord',
data
})
}

116
api/start/login.js

@ -0,0 +1,116 @@
import { http } from '../../utils/http'
/**
*
* @param data 模拟手机号码
* {
"loginType":"EMAIL", //登录方式
"account":"q614588746@163.com" , //登陆账号 手机号/邮箱/dccode
"verifyCode":"837012", //验证码
"password":"", //密码
"useCode":"true", //是否使用验证码 true/false
"idToken":"", //第三方登录idToken
}
*/
export const LoginApi = (data) => {
return http({
method: 'POST',
url: '/UserLogin/login',
data:
data
,
})
}
/**
* 发送邮箱验证码
* @param {*} email
* @returns
*/
export const SendEmailCodeApi = (data) => {
return http({
method: 'POST',
url: '/UserLogin/sendEmail',
data: data
})
}
/**
* 发送手机验证码
* @param {*} email
* @returns
*/
export const SendPhoneCodeApi = (data) => {
return http({
method: 'POST',
url: '/UserLogin/sendPhone',
data:data
})
}
/**
* 注册
*/
export const registerApi = (data) => {
return http({
method: 'POST',
url: '/UserLogin/register',
data: data,
})
}
/**
* 修改密码
*
*/
export const updatePassword = (data) => {
return http({
method: 'GET',
url: '/updatePassword',
data: {
data
},
})
}
/**
* 通过苹果登录
*/
export const postLoginAppleSimpleAPI = (phoneNumber) => {
return http({
method: 'POST',
url: '/login',
data: {
phoneNumber,
},
})
}
/**
* 通过谷歌登录
*/
export const postLoginGoogleSimpleAPI = (phoneNumber) => {
return http({
method: 'POST',
url: '/login/wxMin/simple',
data: {
phoneNumber,
},
})
}

260
api/tcpConnection.js

@ -0,0 +1,260 @@
/**
* TCP连接工具类
* 用于处理TCP连接发送消息和断开连接
*
* @format
*/
// 引用TCP插件
// const TCPSocket = uni.requireNativePlugin('Aimer-TCPPlugin');
// const TCPSocket = uni.requireNativePlugin("Aimer-TCPPlugin");
// TCP连接配置
const TCP_CONFIG = {
ip: "192.168.1.9",
port: "8080",
channel: "1", // 可选 1~20
charsetname: "UTF-8", // 默认UTF-8,可选GBK
};
/**
* TCP连接管理类
*/
class TCPConnection {
constructor() {
this.channelConnections = new Map(); // 存储每个channel的连接状态
this.connectionCallbacks = [];
this.messageCallbacks = [];
}
/**
* TCP初始化连接
* @param {Object} config - 连接配置 {ip, port, channel, charsetname}
* @param {Function} callback - 连接状态回调函数
*/
connect(config = {}, callback = null) {
const channel = config.channel || TCP_CONFIG.channel;
// 如果该channel已经连接,先断开现有连接
if (this.channelConnections.get(channel)) {
console.log(`检测到channel ${channel}现有TCP连接,先断开...`);
this.disconnect(config);
// 等待断开完成后再连接
setTimeout(() => {
this._performConnect(config, callback);
}, 300);
} else {
// 直接连接
this._performConnect(config, callback);
}
}
/**
* 执行TCP连接
* @param {Object} config - 连接配置
* @param {Function} callback - 连接状态回调函数
*/
_performConnect(config = {}, callback = null) {
const connectionConfig = {
channel: config.channel || TCP_CONFIG.channel,
ip: config.ip || TCP_CONFIG.ip,
port: config.port || TCP_CONFIG.port,
};
// 如果指定了字符集,添加到配置中
if (config.charsetname || TCP_CONFIG.charsetname) {
connectionConfig.charsetname = config.charsetname || TCP_CONFIG.charsetname;
}
console.log('开始建立TCP连接:', connectionConfig);
TCPSocket.connect(
connectionConfig,
result => {
/**
* status : 0 连接成功
* status : 1 断开连接
* receivedMsg : 服务器返回字符串(普通的字符串交互)
* receivedHexMsg : 服务器返回字节数组(单片机智能家居等硬件数据交互)
*/
if (result.status == '0') {
// TCP连接成功
this.channelConnections.set(connectionConfig.channel, true);
console.log(`TCP连接成功 - Channel ${connectionConfig.channel}`);
this._notifyConnectionCallbacks('connected', result, connectionConfig.channel);
} else if (result.status == '1') {
// TCP断开连接
this.channelConnections.set(connectionConfig.channel, false);
console.log(`TCP断开连接 - Channel ${connectionConfig.channel}`);
this._notifyConnectionCallbacks('disconnected', result, connectionConfig.channel);
}
if (result.receivedMsg) {
// 服务器返回字符串
console.log('收到字符串消息:', result.receivedMsg);
this._notifyMessageCallbacks('string', result.receivedMsg, null, connectionConfig.channel);
}
// if (result.receivedHexMsg) {
// // 硬件服务器返回16进制数据
// console.log('收到16进制消息:', result.receivedHexMsg);
// let msg = result.receivedHexMsg;
// let sum = msg.length / 2;
// let arr = [];
// for (let k = 0; k < sum; k++) {
// let i = msg.substring(k * 2, k * 2 + 2);
// arr.push(i);
// }
// console.log('解析后的16进制数组:', arr);
// this._notifyMessageCallbacks('hex', result.receivedHexMsg, arr);
// }
// 执行回调函数
if (callback && typeof callback === "function") {
callback(result);
}
});
}
/**
* TCP发送消息(普通的字符串交互)
* @param {String|Object} message - 要发送的消息如果是对象会自动转换为JSON字符串
* @param {Object} config - 发送配置 {channel, charsetname}
*/
send(message, config = {}) {
const channel = config.channel || '1';
if (!this.channelConnections.get(channel)) {
console.warn(`TCP Channel ${channel}未连接,无法发送消息`);
return false;
}
// 如果message是对象,转换为JSON字符串
let messageStr = message;
if (typeof message === "object") {
messageStr = JSON.stringify(message) + "\n";
}
const sendConfig = {
channel: config.channel || "1", // 注意:channel应该是字符串
message: messageStr,
};
// 如果指定了字符编码,添加到配置中
if (config.charsetname) {
sendConfig.charsetname = config.charsetname;
}
TCPSocket.send(sendConfig);
console.log("js成功发送TCP消息:", messageStr);
return true;
}
/**
* TCP断开连接
* @param {Object} config - 断开配置 {channel}
*/
disconnect(config = {}) {
const channel = config.channel || TCP_CONFIG.channel;
const disconnectConfig = {
channel: channel
};
TCPSocket.disconnect(disconnectConfig);
this.channelConnections.set(channel, false);
console.log(`TCP连接已断开 - Channel ${channel}`, disconnectConfig);
}
/**
* 添加连接状态监听器
* @param {Function} callback - 回调函数 (status, result) => {}
*/
onConnectionChange(callback) {
if (typeof callback === "function") {
this.connectionCallbacks.push(callback);
}
}
/**
* 添加消息监听器
* @param {Function} callback - 回调函数 (type, message, parsedArray) => {}
*/
onMessage(callback) {
if (typeof callback === "function") {
this.messageCallbacks.push(callback);
}
}
/**
* 移除连接状态监听器
* @param {Function} callback - 要移除的回调函数
*/
removeConnectionListener(callback) {
const index = this.connectionCallbacks.indexOf(callback);
if (index > -1) {
this.connectionCallbacks.splice(index, 1);
}
}
/**
* 移除消息监听器
* @param {Function} callback - 要移除的回调函数
*/
removeMessageListener(callback) {
const index = this.messageCallbacks.indexOf(callback);
if (index > -1) {
this.messageCallbacks.splice(index, 1);
}
}
/**
* 获取连接状态
* @param {String} channel - 要检查的channel如果不指定则返回所有channel的连接状态
* @returns {Boolean|Object} 连接状态
*/
getConnectionStatus(channel = null) {
if (channel) {
return this.channelConnections.get(channel) || false;
}
// 返回所有channel的连接状态
const allConnections = {};
for (const [ch, status] of this.channelConnections) {
allConnections[ch] = status;
}
return allConnections;
}
/**
* 通知连接状态回调
* @private
*/
_notifyConnectionCallbacks(status, result, channel) {
this.connectionCallbacks.forEach(callback => {
try {
callback(status, result, channel);
} catch (error) {
console.error('连接状态回调执行错误:', error);
}
});
}
/**
* 通知消息回调
* @private
*/
_notifyMessageCallbacks(type, message, parsedArray = null, channel = null) {
this.messageCallbacks.forEach(callback => {
try {
callback(type, message, parsedArray, channel);
} catch (error) {
console.error('消息回调执行错误:', error);
}
});
}
}
// 创建TCP连接实例
const tcpConnection = new TCPConnection();
// 导出TCP连接实例和类
export default tcpConnection;
export { TCPConnection, TCP_CONFIG };

392
common/canvasMethod.js

@ -0,0 +1,392 @@
/**
* 功能Canvas绘制方法
* 作者洪锡林
* 时间2025年10月25日
*
* @format
*/
import { utils } from "./util.js";
export const HCharts = {
// 清除画布
clearCanvas(ctx, width, height) {
ctx.clearRect(0, 0, width, height);
ctx.setFillStyle("#ffffff");
ctx.fillRect(0, 0, width, height);
},
// 设置画布颜色
setCanvasColor(ctx, width, height, color) {
ctx.clearRect(0, 0, width, height);
ctx.setFillStyle(color);
ctx.fillRect(0, 0, width, height);
},
// 绘制文本工具函数
drawText(ctx, text, x, y, fontSize = 12, color = "#333", align = "left") {
ctx.setFontSize(fontSize);
ctx.setFillStyle(color);
ctx.setTextAlign(align);
ctx.fillText(text, x, y);
},
/**
* 功能绘制网格系统
* 作者洪锡林
* 时间2025年10月25日
*
* grid:[{
* top:顶部距离
* bottom:底部距离
* left:左侧距离
* right:右侧距离
* lineColor:网格线颜色
* lineWidth:网格线宽度
* horizontalLineNum:水平网格线数量
* verticalLineNum:垂直网格线数量
* label:{
* fontSize:字体大小
* color:字体颜色
* onlyTwo:是否只有两个标签
* text:[{
* value:值标签
* ratio:比例标签
* },{
* value:值标签
* ratio:比例标签
* },...]
* },...]
*/
// 绘制网格系统
drawGrid(ctx, width, height, grid, openTime, closeTime) {
// 测试数据
// const preClosePrice = prevClosePrice;
for (let i = 0; i < grid.length; ++i) {
const top = grid[i].top;
const bottom = grid[i].bottom;
const left = grid[i].left;
const right = grid[i].right;
const lineColor = grid[i].lineColor;
const lineWidth = grid[i].lineWidth;
const horizontalLineNum = grid[i].horizontalLineNum - 1;
const verticalLineNum = grid[i].verticalLineNum - 1;
let label;
if (grid[i].label) {
label = grid[i].label;
}
ctx.setStrokeStyle(lineColor);
ctx.setLineWidth(lineWidth);
// 画图底的开盘收盘时间
if (i == 0 && openTime && closeTime) {
HCharts.drawText(ctx, openTime, 6, height - bottom + 12, 14, "#686868", "left");
HCharts.drawText(ctx, closeTime, width - 6, height - bottom + 12, 14, "#686868", "right");
}
// 绘制水平网格线
for (let j = 0; j <= horizontalLineNum; j++) {
const y = top + (j * (height - bottom - top)) / horizontalLineNum;
ctx.beginPath();
if (label.lineStyle[j] == "dash") {
ctx.setLineDash([5, 5]);
}
ctx.moveTo(left, y);
ctx.lineTo(width - right, y);
ctx.stroke();
ctx.setLineDash([]);
}
// 绘制垂直网格线
for (let i = 0; i <= verticalLineNum; i++) {
const x = ((width - left - right) * i) / verticalLineNum;
ctx.beginPath();
ctx.moveTo(x + left, top);
ctx.lineTo(x + left, height - bottom);
ctx.stroke();
}
}
},
// 绘制价格标签
drawAxisLabels(ctx, width, height, grid) {
for (let i = 0; i < grid.length; ++i) {
const top = grid[i].top;
const bottom = grid[i].bottom;
const left = grid[i].left;
const right = grid[i].right;
const horizontalLineNum = grid[i].horizontalLineNum - 1;
let label;
if (grid[i].label) {
label = grid[i].label;
}
// 绘制水平网格线
for (let j = 0; j <= horizontalLineNum; j++) {
const y = top + (j * (height - bottom - top)) / horizontalLineNum;
// 价格标签
if (label) {
let valueXText = left + 1;
let ratioXText = width - right - 1;
let yText = y + 10;
if (j == horizontalLineNum) {
yText = y - 1;
}
let valueAlign = "left";
let ratioAlign = "right";
let fontSize = label.fontSize;
let textColor = label.color[j];
if (label.onlyTwo) {
if (j == 0) {
HCharts.drawText(ctx, label.text[0].value, valueXText, yText, fontSize, label.color[0], valueAlign);
} else if (j == horizontalLineNum) {
HCharts.drawText(ctx, label.text[1].value, valueXText, yText, fontSize, label.color[1], valueAlign);
}
} else {
HCharts.drawText(ctx, label.text[j].value, valueXText, yText, fontSize, textColor, valueAlign);
}
if (typeof label.text[j]?.ratio !== "undefined") {
HCharts.drawText(ctx, label.text[j].ratio, ratioXText, yText, fontSize, textColor, ratioAlign);
}
}
}
}
},
// 绘制价格曲线
drawPriceLine(ctx, width, height, data, grid, priceRange) {
if (!data.length) return;
// 上下边距1
const top = grid[0].top;
const bottom = grid[0].bottom;
const left = grid[0].left;
const right = grid[0].right;
const pointLen = 240;
const priceDiff = priceRange.max - priceRange.min;
// 绘制价格曲线
ctx.setStrokeStyle("#000");
ctx.setLineWidth(1);
ctx.beginPath();
data.forEach((item, index) => {
const x = left + (index * (width - left - right)) / pointLen;
const y = top + (height - top - bottom) * (1 - (item.price - priceRange.min) / priceDiff);
if (index === 0) {
ctx.moveTo(x, y);
} else {
// 使用贝塞尔曲线平滑连接
const prevPoint = data[index - 1];
const prevX = left + ((index - 1) * (width - left - right)) / pointLen;
const prevY = top + (height - top + -bottom) * (1 - (prevPoint.price - priceRange.min) / priceDiff);
const cp1x = (prevX + x) / 2;
const cp1y = prevY;
const cp2x = (prevX + x) / 2;
const cp2y = y;
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
}
});
ctx.stroke();
// 绘制渐变背景
HCharts.drawGradientBackground(ctx, width, height, data, grid, priceRange);
},
// 绘制渐变背景
drawGradientBackground(ctx, width, height, data, grid, priceRange) {
// 上下边距1
const top = grid[0].top;
const bottom = grid[0].bottom;
const left = grid[0].left;
const right = grid[0].right;
const pointLen = 240;
const priceDiff = priceRange.max - priceRange.min;
const gradient = ctx.createLinearGradient(0, left, 0, height - top);
gradient.addColorStop(0, "rgba(0, 0, 0, 0.3)");
gradient.addColorStop(1, "rgba(0, 0, 0, 0.05)");
ctx.beginPath();
// 绘制价格曲线路径
data.forEach((item, index) => {
const x = left + (index * (width - left - right)) / pointLen;
const y = top + (height - top - bottom) * (1 - (item.price - priceRange.min) / priceDiff);
if (index === 0) {
ctx.moveTo(x, y);
} else {
const prevPoint = data[index - 1];
const prevX = left + ((index - 1) * (width - left - right)) / pointLen;
const prevY = top + (height - top - bottom) * (1 - (prevPoint.price - priceRange.min) / priceDiff);
const cp1x = (prevX + x) / 2;
const cp1y = prevY;
const cp2x = (prevX + x) / 2;
const cp2y = y;
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
}
});
// 闭合路径
const lastX = left + ((data.length - 1) * (width - left - right)) / pointLen;
ctx.lineTo(lastX, height - bottom);
ctx.lineTo(left, height - bottom);
ctx.closePath();
ctx.setFillStyle(gradient);
ctx.fill();
},
// 绘制成交量
drawVolume(ctx, width, height, data, index, pointLen, grid, volumeRange, offset) {
if (!data.length) return;
const top = grid[index - 1].top;
const bottom = grid[index - 1].bottom;
const left = grid[index - 1].left;
const right = grid[index - 1].right;
data.forEach((item, index) => {
const x = offset + left + (index * (width - left - right)) / pointLen;
const barWidth = (width - left - right) / pointLen - 0.5;
const barHeight = (item.volume / volumeRange.max) * (height - bottom - top);
// 根据涨跌设置颜色
const isRise = index === 0 || item.price >= data[index - 1].price || item.close >= data[index - 1].close;
ctx.setFillStyle(isRise ? "green" : "red");
ctx.fillRect(x - barWidth / 2, height - bottom - barHeight, barWidth, barHeight);
});
},
// 字符宽度近似计算(避免使用 measureText)
getApproximateTextWidth(text, fontSize = 10) {
// 中文字符约等于 fontSize,英文字符约等于 fontSize * 0.6
let width = 0;
for (let char of text) {
// 判断是否为中文字符
if (char.match(/[\u4e00-\u9fa5]/)) {
width += fontSize;
} else {
width += fontSize * 0.6;
}
}
return width;
},
// 绘制顶部价格显示
drawTopPriceDisplay(ctx, grid, text) {
for (let i = 0; i < text.length; i++) {
let x = grid[i].left;
let y = grid[i].top - 4;
for (let j = 0; j < text[i].length; j++) {
ctx.setFillStyle(text[i][j].color);
ctx.setFontSize(10);
ctx.setTextAlign("left");
ctx.fillText(text[i][j].name + ":" + text[i][j].value, x, y);
x += HCharts.getApproximateTextWidth(text[i][j].name + ":" + text[i][j].value) + 5;
}
}
},
// 绘制坐标轴标签
drawCrosshairAxisLabels(ctx, width, height, grid, crosshair) {
const { x, y } = crosshair;
// X轴时间标签
if (crosshair.currentData && (crosshair.currentData.time || crosshair.currentData.date)) {
const timeText = crosshair.currentData.time || crosshair.currentData.date;
const xBoxWidth = crosshair.currentData.time ? 40 : 70;
const xBoxHeight = 15;
ctx.setFillStyle("#629AF5");
if (x - xBoxWidth / 2 <= grid[0].left) {
ctx.fillRect(grid[0].left, height - grid[0].bottom, xBoxWidth, xBoxHeight);
} else if (x + xBoxWidth / 2 < width - grid[0].right) {
ctx.fillRect(x - xBoxWidth / 2, height - grid[0].bottom, xBoxWidth, xBoxHeight);
} else {
ctx.fillRect(width - grid[0].right - xBoxWidth, height - grid[0].bottom, xBoxWidth, xBoxHeight);
}
ctx.setFillStyle("#fff");
ctx.setFontSize(12);
ctx.setTextAlign("center");
if (x - xBoxWidth / 2 <= grid[0].left) {
ctx.fillText(timeText, grid[0].left + xBoxWidth / 2, height - grid[0].bottom + 12);
} else if (x + xBoxWidth / 2 < width - grid[0].right) {
ctx.fillText(timeText, x, height - grid[0].bottom + 12);
} else {
ctx.fillText(timeText, width - grid[0].right - xBoxWidth / 2, height - grid[0].bottom + 12);
}
}
// Y轴价格标签
if (crosshair.currentData) {
const priceText = utils.formatPrice(crosshair.currentData.price);
const yBoxWidth = 50;
const yBoxHeight = 14;
ctx.setFillStyle("#629AF5");
if (x < grid[0].left + yBoxWidth + 5) {
ctx.fillRect(width - grid[0].right - yBoxWidth, y - yBoxHeight / 2, yBoxWidth, yBoxHeight);
} else {
ctx.fillRect(grid[0].left, y - yBoxHeight / 2, yBoxWidth, yBoxHeight);
}
ctx.setFillStyle("#fff");
ctx.setFontSize(11);
ctx.setTextAlign("center");
if (x < grid[0].left + yBoxWidth + 5) {
ctx.fillText(priceText, width - grid[0].right - yBoxWidth / 2, y + 3);
} else {
ctx.fillText(priceText, grid[0].left + yBoxWidth / 2, y + 3);
}
}
},
// 绘制十字准线
drawCrosshair(ctx, width, height, grid, crosshair, text) {
if (!ctx) return;
const { x, y } = crosshair;
if (crosshair.show) {
// 每次绘制前先清除整个画布
ctx.clearRect(0, 0, width, height);
// 绘制垂直准线1
ctx.setStrokeStyle("#000");
ctx.setLineWidth(1);
// ctx.setLineDash([5, 5]);
for (let i = 0; i < grid.length; i++) {
ctx.beginPath();
ctx.moveTo(x, grid[i].top);
ctx.lineTo(x, height - grid[i].bottom);
ctx.stroke();
}
// ctx.beginPath();
// ctx.moveTo(x, grid[0].top);
// ctx.lineTo(x, height - grid[0].bottom);
// ctx.stroke();
// // 绘制垂直准线2
// ctx.beginPath();
// ctx.moveTo(x, grid[1].top);
// ctx.lineTo(x, height - grid[1].bottom);
// ctx.stroke();
// 绘制水平准线
ctx.beginPath();
ctx.moveTo(grid[0].left, y);
ctx.lineTo(width - grid[0].right, y);
ctx.stroke();
ctx.setLineDash([]);
// 绘制焦点圆点 - 黑边白心(更小)
// 先绘制白色填充
ctx.setFillStyle("#ffffff");
ctx.beginPath();
ctx.arc(x, y, 2, 0, Math.PI * 2);
ctx.fill();
// 再绘制黑色边框
ctx.setStrokeStyle("#000000");
ctx.setLineWidth(1);
ctx.setLineDash([]);
ctx.beginPath();
ctx.arc(x, y, 2, 0, Math.PI * 2);
ctx.stroke();
// 绘制坐标轴标签
HCharts.drawCrosshairAxisLabels(ctx, width, height, grid, crosshair);
}
// 绘制顶部价格显示
HCharts.drawTopPriceDisplay(ctx, grid, text);
ctx.draw(false);
},
};

354
common/stockTimeInformation.js

@ -0,0 +1,354 @@
/** @format */
export const prevClosePrice = 14.95; // 前一日收盘价(元)
export const timeData = [
// 上午时段:9:30-11:30(共120个数据点)
{ time: "09:30", price: 15.0, volume: 28500 }, // 开盘价15.00元,开盘放量
{ time: "09:31", price: 15.08, volume: 25300 },
{ time: "09:32", price: 15.12, volume: 22800 },
{ time: "09:33", price: 15.09, volume: 19600 },
{ time: "09:34", price: 15.15, volume: 17200 },
{ time: "09:35", price: 15.18, volume: 15800 },
{ time: "09:36", price: 15.16, volume: 14300 },
{ time: "09:37", price: 15.2, volume: 13500 },
{ time: "09:38", price: 15.17, volume: 12800 },
{ time: "09:39", price: 15.22, volume: 12100 },
{ time: "09:40", price: 15.25, volume: 11500 },
{ time: "09:41", price: 15.23, volume: 10800 },
{ time: "09:42", price: 15.26, volume: 10200 },
{ time: "09:43", price: 15.24, volume: 9800 },
{ time: "09:44", price: 15.28, volume: 9500 },
{ time: "09:45", price: 15.3, volume: 9200 },
{ time: "09:46", price: 15.27, volume: 8800 },
{ time: "09:47", price: 15.29, volume: 8500 },
{ time: "09:48", price: 15.32, volume: 8200 },
{ time: "09:49", price: 15.3, volume: 7900 },
{ time: "09:50", price: 15.33, volume: 7600 },
{ time: "09:51", price: 15.31, volume: 7400 },
{ time: "09:52", price: 15.34, volume: 7200 },
{ time: "09:53", price: 15.32, volume: 7000 },
{ time: "09:54", price: 15.35, volume: 6800 },
{ time: "09:55", price: 15.33, volume: 6600 },
{ time: "09:56", price: 15.36, volume: 6500 },
{ time: "09:57", price: 15.34, volume: 6300 },
{ time: "09:58", price: 15.37, volume: 6200 },
{ time: "09:59", price: 15.35, volume: 6100 },
{ time: "10:00", price: 15.38, volume: 6000 },
{ time: "10:01", price: 15.36, volume: 5900 },
{ time: "10:02", price: 15.39, volume: 5800 },
{ time: "10:03", price: 15.37, volume: 5700 },
{ time: "10:04", price: 15.4, volume: 5600 },
{ time: "10:05", price: 15.38, volume: 5500 },
{ time: "10:06", price: 15.41, volume: 15400 },
{ time: "10:07", price: 15.39, volume: 5300 },
{ time: "10:08", price: 15.42, volume: 5200 },
{ time: "10:09", price: 15.4, volume: 5100 },
{ time: "10:10", price: 15.43, volume: 5000 },
{ time: "10:11", price: 15.41, volume: 5100 },
{ time: "10:12", price: 15.44, volume: 5200 },
{ time: "10:13", price: 15.42, volume: 5300 },
{ time: "10:14", price: 15.45, volume: 5400 },
{ time: "10:15", price: 15.43, volume: 5500 },
{ time: "10:16", price: 15.46, volume: 5600 },
{ time: "10:17", price: 15.44, volume: 5700 },
{ time: "10:18", price: 15.47, volume: 5800 },
{ time: "10:19", price: 15.45, volume: 5900 },
{ time: "10:20", price: 15.48, volume: 6000 },
{ time: "10:21", price: 15.46, volume: 6100 },
{ time: "10:22", price: 15.49, volume: 6200 },
{ time: "10:23", price: 15.47, volume: 6300 },
{ time: "10:24", price: 15.5, volume: 6400 },
{ time: "10:25", price: 15.48, volume: 6500 },
{ time: "10:26", price: 15.51, volume: 6600 },
{ time: "10:27", price: 15.49, volume: 6700 },
{ time: "10:28", price: 15.52, volume: 6800 },
{ time: "10:29", price: 15.5, volume: 6900 },
{ time: "10:30", price: 15.53, volume: 7000 },
{ time: "10:31", price: 15.51, volume: 7100 },
{ time: "10:32", price: 15.54, volume: 7200 },
{ time: "10:33", price: 15.52, volume: 7300 },
{ time: "10:34", price: 15.55, volume: 7400 },
{ time: "10:35", price: 15.53, volume: 7500 },
{ time: "10:36", price: 15.56, volume: 7600 },
{ time: "10:37", price: 15.54, volume: 7700 },
{ time: "10:38", price: 15.57, volume: 7800 },
{ time: "10:39", price: 15.55, volume: 7900 },
{ time: "10:40", price: 15.58, volume: 8000 },
{ time: "10:41", price: 15.56, volume: 8100 },
{ time: "10:42", price: 15.59, volume: 8200 },
{ time: "10:43", price: 15.57, volume: 8300 },
{ time: "10:44", price: 15.6, volume: 8400 }, // 全天最高价15.60元
{ time: "10:45", price: 15.58, volume: 8300 },
{ time: "10:46", price: 15.56, volume: 8200 },
{ time: "10:47", price: 15.54, volume: 8100 },
{ time: "10:48", price: 15.52, volume: 8000 },
{ time: "10:49", price: 15.5, volume: 7900 },
{ time: "10:50", price: 15.48, volume: 7800 },
{ time: "10:51", price: 15.46, volume: 7700 },
{ time: "10:52", price: 15.44, volume: 7600 },
{ time: "10:53", price: 15.42, volume: 7500 },
{ time: "10:54", price: 15.4, volume: 7400 },
{ time: "10:55", price: 15.38, volume: 7300 },
{ time: "10:56", price: 15.36, volume: 7200 },
{ time: "10:57", price: 15.34, volume: 7100 },
{ time: "10:58", price: 15.32, volume: 7000 },
{ time: "10:59", price: 15.3, volume: 6900 },
{ time: "11:00", price: 15.28, volume: 6800 },
{ time: "11:01", price: 15.26, volume: 6700 },
{ time: "11:02", price: 15.24, volume: 6600 },
{ time: "11:03", price: 15.22, volume: 6500 },
{ time: "11:04", price: 15.2, volume: 6400 }, // 全天最低价15.20元
{ time: "11:05", price: 15.22, volume: 6500 },
{ time: "11:06", price: 15.24, volume: 6600 },
{ time: "11:07", price: 15.26, volume: 6700 },
{ time: "11:08", price: 15.28, volume: 6800 },
{ time: "11:09", price: 15.3, volume: 6900 },
{ time: "11:10", price: 15.32, volume: 7000 },
{ time: "11:11", price: 15.34, volume: 7100 },
{ time: "11:12", price: 15.36, volume: 7200 },
{ time: "11:13", price: 15.38, volume: 7300 },
{ time: "11:14", price: 15.4, volume: 7400 },
{ time: "11:15", price: 15.42, volume: 7500 },
{ time: "11:16", price: 15.44, volume: 7600 },
{ time: "11:17", price: 15.46, volume: 7700 },
{ time: "11:18", price: 15.48, volume: 7800 },
{ time: "11:19", price: 15.5, volume: 7900 },
{ time: "11:20", price: 15.45, volume: 8300 },
{ time: "11:21", price: 15.47, volume: 8600 },
{ time: "11:22", price: 15.43, volume: 9100 },
{ time: "11:23", price: 15.46, volume: 9500 },
{ time: "11:24", price: 15.49, volume: 10200 },
{ time: "11:25", price: 15.5, volume: 11500 },
{ time: "11:26", price: 15.48, volume: 12800 },
{ time: "11:27", price: 15.52, volume: 14300 },
{ time: "11:28", price: 15.5, volume: 16500 },
{ time: "11:29", price: 15.53, volume: 19800 }, // 午盘收盘价15.53元
// 下午时段:13:00-15:00(共120个数据点)
{ time: "13:00", price: 15.55, volume: 24600 }, // 午后开盘冲高
{ time: "13:01", price: 15.58, volume: 21300 },
{ time: "13:02", price: 15.6, volume: 18700 }, // 再次触及全天最高价
{ time: "13:03", price: 15.57, volume: 16200 },
{ time: "13:04", price: 15.55, volume: 14500 },
{ time: "13:05", price: 15.52, volume: 12800 },
{ time: "13:06", price: 15.5, volume: 11300 },
{ time: "13:07", price: 15.48, volume: 10100 },
{ time: "13:08", price: 15.5, volume: 9500 },
{ time: "13:09", price: 15.47, volume: 8900 },
{ time: "13:10", price: 15.45, volume: 8300 },
{ time: "13:11", price: 15.43, volume: 7800 },
{ time: "13:12", price: 15.46, volume: 7500 },
{ time: "13:13", price: 15.44, volume: 7200 },
{ time: "13:14", price: 15.42, volume: 6900 },
{ time: "13:15", price: 15.45, volume: 6700 },
{ time: "13:16", price: 15.43, volume: 6500 },
{ time: "13:17", price: 15.4, volume: 6300 },
{ time: "13:18", price: 15.42, volume: 6100 },
{ time: "13:19", price: 15.39, volume: 5900 },
{ time: "13:20", price: 15.41, volume: 5800 },
{ time: "13:21", price: 15.39, volume: 5700 },
{ time: "13:22", price: 15.42, volume: 5600 },
{ time: "13:23", price: 15.4, volume: 5500 },
{ time: "13:24", price: 15.43, volume: 5400 },
{ time: "13:25", price: 15.41, volume: 5300 },
{ time: "13:26", price: 15.44, volume: 5200 },
{ time: "13:27", price: 15.42, volume: 5100 },
{ time: "13:28", price: 15.45, volume: 5000 },
{ time: "13:29", price: 15.43, volume: 5100 },
{ time: "13:30", price: 15.46, volume: 5200 },
{ time: "13:31", price: 15.44, volume: 5300 },
{ time: "13:32", price: 15.47, volume: 5400 },
{ time: "13:33", price: 15.45, volume: 5500 },
{ time: "13:34", price: 15.48, volume: 5600 },
{ time: "13:35", price: 15.46, volume: 5700 },
{ time: "13:36", price: 15.49, volume: 5800 },
{ time: "13:37", price: 15.47, volume: 5900 },
{ time: "13:38", price: 15.5, volume: 6000 },
{ time: "13:39", price: 15.48, volume: 6100 },
{ time: "13:40", price: 15.51, volume: 6200 },
{ time: "13:41", price: 15.49, volume: 6300 },
{ time: "13:42", price: 15.52, volume: 6400 },
{ time: "13:43", price: 15.5, volume: 6500 },
{ time: "13:44", price: 15.53, volume: 6600 },
{ time: "13:45", price: 15.51, volume: 6700 },
{ time: "13:46", price: 15.54, volume: 6800 },
{ time: "13:47", price: 15.52, volume: 6900 },
{ time: "13:48", price: 15.55, volume: 7000 },
{ time: "13:49", price: 15.53, volume: 7100 },
{ time: "13:50", price: 15.56, volume: 7200 },
{ time: "13:51", price: 15.54, volume: 7300 },
{ time: "13:52", price: 15.57, volume: 7400 },
{ time: "13:53", price: 15.55, volume: 7500 },
{ time: "13:54", price: 15.58, volume: 7600 },
{ time: "13:55", price: 15.56, volume: 7700 },
{ time: "13:56", price: 15.59, volume: 7800 },
{ time: "13:57", price: 15.57, volume: 7900 },
{ time: "13:58", price: 15.6, volume: 8000 }, // 第三次触及全天最高价
{ time: "13:59", price: 15.58, volume: 8100 },
{ time: "14:00", price: 15.56, volume: 8200 },
{ time: "14:01", price: 15.54, volume: 8300 },
{ time: "14:02", price: 15.52, volume: 8400 },
{ time: "14:03", price: 15.5, volume: 8300 },
{ time: "14:04", price: 15.48, volume: 8200 },
{ time: "14:05", price: 15.46, volume: 8100 },
{ time: "14:06", price: 15.44, volume: 8000 },
{ time: "14:07", price: 15.42, volume: 7900 },
{ time: "14:08", price: 15.4, volume: 7800 },
{ time: "14:09", price: 15.38, volume: 7700 },
{ time: "14:10", price: 15.36, volume: 7600 },
{ time: "14:11", price: 15.34, volume: 7500 },
{ time: "14:12", price: 15.32, volume: 7400 },
{ time: "14:13", price: 15.3, volume: 7300 },
{ time: "14:14", price: 15.28, volume: 7200 },
{ time: "14:15", price: 15.26, volume: 7100 },
{ time: "14:16", price: 15.24, volume: 7000 },
{ time: "14:17", price: 15.22, volume: 6900 },
{ time: "14:18", price: 15.2, volume: 6800 }, // 再次触及全天最低价
{ time: "14:19", price: 15.22, volume: 6700 },
{ time: "14:20", price: 15.24, volume: 6600 },
{ time: "14:21", price: 15.26, volume: 6500 },
{ time: "14:22", price: 15.28, volume: 6400 },
{ time: "14:23", price: 15.3, volume: 6300 },
{ time: "14:24", price: 15.32, volume: 6200 },
{ time: "14:25", price: 15.34, volume: 6100 },
{ time: "14:26", price: 15.36, volume: 6000 },
{ time: "14:27", price: 15.38, volume: 5900 },
{ time: "14:28", price: 15.4, volume: 5800 },
{ time: "14:29", price: 15.42, volume: 5700 },
{ time: "14:30", price: 15.44, volume: 5600 },
{ time: "14:31", price: 15.46, volume: 5500 },
{ time: "14:32", price: 15.48, volume: 5400 },
{ time: "14:33", price: 15.5, volume: 5300 },
{ time: "14:34", price: 15.52, volume: 5200 },
{ time: "14:35", price: 15.54, volume: 5100 },
{ time: "14:36", price: 15.56, volume: 5000 },
{ time: "14:37", price: 15.54, volume: 5100 },
{ time: "14:38", price: 15.52, volume: 5200 },
{ time: "14:39", price: 15.5, volume: 5300 },
{ time: "14:40", price: 15.48, volume: 5400 },
{ time: "14:41", price: 15.46, volume: 5500 },
{ time: "14:42", price: 15.44, volume: 5600 },
{ time: "14:43", price: 15.42, volume: 5700 },
{ time: "14:44", price: 15.4, volume: 5800 },
{ time: "14:45", price: 15.38, volume: 5900 },
{ time: "14:46", price: 15.36, volume: 6000 },
{ time: "14:47", price: 15.34, volume: 6100 },
{ time: "14:48", price: 15.32, volume: 6200 },
{ time: "14:49", price: 15.3, volume: 6300 },
{ time: "14:50", price: 15.42, volume: 9800 }, // 尾盘开始放量
{ time: "14:51", price: 15.45, volume: 11500 },
{ time: "14:52", price: 15.43, volume: 13200 },
{ time: "14:53", price: 15.46, volume: 15800 },
{ time: "14:54", price: 15.44, volume: 18500 },
{ time: "14:55", price: 15.47, volume: 21300 },
{ time: "14:56", price: 15.45, volume: 24600 },
{ time: "14:57", price: 15.48, volume: 27800 },
{ time: "14:58", price: 15.46, volume: 31200 }, // 尾盘成交量峰值
{ time: "14:59", price: 15.45, volume: 28500 }, // 当日收盘价15.45元
];
export const klineData = [
// 第1天(起始点,位于区间中部)
{ date: "2015-10-11", open: 16.5, high: 16.8, low: 16.2, close: 16.6, volume: 185000 },
// 第2-90天(区间震荡:15.5-17.5元)
{ date: "2015-10-12", open: 16.6, high: 16.9, low: 16.4, close: 16.7, volume: 192000 },
{ date: "2015-10-13", open: 16.7, high: 17.0, low: 16.5, close: 16.6, volume: 188000 },
{ date: "2015-10-14", open: 16.6, high: 16.8, low: 16.3, close: 16.4, volume: 175000 },
{ date: "2015-10-15", open: 16.4, high: 16.7, low: 16.2, close: 16.5, volume: 181000 },
{ date: "2015-10-16", open: 16.5, high: 16.9, low: 16.3, close: 16.8, volume: 195000 },
{ date: "2015-10-17", open: 16.8, high: 17.1, low: 16.6, close: 16.7, volume: 202000 },
{ date: "2015-10-18", open: 16.7, high: 16.9, low: 16.4, close: 16.5, volume: 183000 },
{ date: "2015-10-19", open: 16.5, high: 16.7, low: 16.1, close: 16.3, volume: 172000 },
{ date: "2015-10-20", open: 16.3, high: 16.6, low: 16.0, close: 16.4, volume: 178000 },
{ date: "2015-10-21", open: 16.4, high: 16.8, low: 16.2, close: 16.6, volume: 189000 },
{ date: "2015-10-22", open: 16.6, high: 17.0, low: 16.5, close: 16.9, volume: 205000 },
{ date: "2015-10-23", open: 16.9, high: 17.2, low: 16.7, close: 16.8, volume: 212000 },
{ date: "2015-10-24", open: 16.8, high: 17.0, low: 16.5, close: 16.6, volume: 193000 },
{ date: "2015-10-25", open: 16.6, high: 16.8, low: 16.2, close: 16.3, volume: 176000 },
{ date: "2015-10-26", open: 16.3, high: 16.6, low: 16.0, close: 16.5, volume: 184000 },
{ date: "2015-10-27", open: 16.5, high: 16.9, low: 16.4, close: 16.7, volume: 196000 },
{ date: "2015-10-28", open: 16.7, high: 17.1, low: 16.6, close: 16.9, volume: 208000 },
{ date: "2015-10-29", open: 16.9, high: 17.3, low: 16.8, close: 17.0, volume: 215000 },
{ date: "2015-10-30", open: 17.0, high: 17.2, low: 16.7, close: 16.8, volume: 201000 },
{ date: "2015-10-31", open: 16.8, high: 17.0, low: 16.5, close: 16.6, volume: 189000 },
{ date: "2015-11-01", open: 16.6, high: 16.8, low: 16.2, close: 16.4, volume: 175000 },
{ date: "2015-11-02", open: 16.4, high: 16.7, low: 16.1, close: 16.3, volume: 171000 },
{ date: "2015-11-03", open: 16.3, high: 16.6, low: 16.0, close: 16.5, volume: 182000 },
{ date: "2015-11-04", open: 16.5, high: 16.9, low: 16.3, close: 16.7, volume: 194000 },
{ date: "2015-11-05", open: 16.7, high: 17.1, low: 16.6, close: 16.8, volume: 203000 },
{ date: "2015-11-06", open: 16.8, high: 17.0, low: 16.5, close: 16.6, volume: 190000 },
{ date: "2015-11-07", open: 16.6, high: 16.8, low: 16.3, close: 16.4, volume: 178000 },
{ date: "2015-11-08", open: 16.4, high: 16.7, low: 16.1, close: 16.3, volume: 173000 },
{ date: "2015-11-09", open: 16.3, high: 16.6, low: 15.9, close: 16.2, volume: 168000 }, // 触及区间下沿
{ date: "2015-11-10", open: 16.2, high: 16.5, low: 16.0, close: 16.4, volume: 176000 },
{ date: "2015-11-11", open: 16.4, high: 16.8, low: 16.3, close: 16.6, volume: 187000 },
{ date: "2015-11-12", open: 16.6, high: 17.0, low: 16.5, close: 16.8, volume: 198000 },
{ date: "2015-11-13", open: 16.8, high: 17.2, low: 16.7, close: 16.9, volume: 206000 },
{ date: "2015-11-14", open: 16.9, high: 17.3, low: 16.8, close: 17.1, volume: 218000 },
{ date: "2015-11-15", open: 17.1, high: 17.4, low: 16.9, close: 17.0, volume: 212000 },
{ date: "2015-11-16", open: 17.0, high: 17.2, low: 16.7, close: 16.8, volume: 197000 },
{ date: "2015-11-17", open: 16.8, high: 17.0, low: 16.5, close: 16.6, volume: 185000 },
{ date: "2015-11-18", open: 16.6, high: 16.8, low: 16.3, close: 16.4, volume: 177000 },
{ date: "2015-11-19", open: 16.4, high: 16.7, low: 16.1, close: 16.3, volume: 172000 },
{ date: "2015-11-20", open: 16.3, high: 16.6, low: 16.0, close: 16.5, volume: 183000 },
{ date: "2015-11-21", open: 16.5, high: 16.9, low: 16.4, close: 16.7, volume: 195000 },
{ date: "2015-11-22", open: 16.7, high: 17.1, low: 16.6, close: 16.9, volume: 204000 },
{ date: "2015-11-23", open: 16.9, high: 17.2, low: 16.8, close: 17.0, volume: 213000 },
{ date: "2015-11-24", open: 17.0, high: 17.3, low: 16.9, close: 17.1, volume: 221000 },
{ date: "2015-11-25", open: 17.1, high: 17.4, low: 17.0, close: 17.2, volume: 228000 }, // 触及区间上沿
{ date: "2015-11-26", open: 17.2, high: 17.3, low: 16.8, close: 16.9, volume: 215000 },
{ date: "2015-11-27", open: 16.9, high: 17.1, low: 16.6, close: 16.7, volume: 199000 },
{ date: "2015-11-28", open: 16.7, high: 16.9, low: 16.4, close: 16.5, volume: 186000 },
{ date: "2015-11-29", open: 16.5, high: 16.7, low: 16.2, close: 16.3, volume: 175000 },
{ date: "2015-11-30", open: 16.3, high: 16.6, low: 16.0, close: 16.4, volume: 179000 },
{ date: "2015-12-01", open: 16.4, high: 16.8, low: 16.3, close: 16.6, volume: 188000 },
{ date: "2015-12-02", open: 16.6, high: 17.0, low: 16.5, close: 16.8, volume: 199000 },
{ date: "2015-12-03", open: 16.8, high: 17.2, low: 16.7, close: 16.9, volume: 207000 },
{ date: "2015-12-04", open: 16.9, high: 17.1, low: 16.6, close: 16.7, volume: 193000 },
{ date: "2015-12-05", open: 16.7, high: 16.9, low: 16.4, close: 16.5, volume: 182000 },
{ date: "2015-12-06", open: 16.5, high: 16.7, low: 16.2, close: 16.3, volume: 173000 },
{ date: "2015-12-07", open: 16.3, high: 16.6, low: 15.9, close: 16.1, volume: 167000 }, // 触及区间下沿
{ date: "2015-12-08", open: 16.1, high: 16.4, low: 16.0, close: 16.3, volume: 174000 },
{ date: "2015-12-09", open: 16.3, high: 16.7, low: 16.2, close: 16.5, volume: 185000 },
{ date: "2015-12-10", open: 16.5, high: 16.9, low: 16.4, close: 16.7, volume: 196000 },
{ date: "2015-12-11", open: 16.7, high: 17.1, low: 16.6, close: 16.9, volume: 205000 },
{ date: "2015-12-12", open: 16.9, high: 17.3, low: 16.8, close: 17.0, volume: 214000 },
{ date: "2015-12-13", open: 17.0, high: 17.2, low: 16.8, close: 16.9, volume: 203000 },
{ date: "2015-12-14", open: 16.9, high: 17.1, low: 16.6, close: 16.7, volume: 191000 },
{ date: "2015-12-15", open: 16.7, high: 16.9, low: 16.4, close: 16.5, volume: 180000 },
{ date: "2015-12-16", open: 16.5, high: 16.7, low: 16.2, close: 16.3, volume: 172000 },
{ date: "2015-12-17", open: 16.3, high: 16.6, low: 16.0, close: 16.4, volume: 178000 },
{ date: "2015-12-18", open: 16.4, high: 16.8, low: 16.3, close: 16.6, volume: 189000 },
{ date: "2015-12-19", open: 16.6, high: 17.0, low: 16.5, close: 16.8, volume: 200000 },
{ date: "2015-12-20", open: 16.8, high: 17.2, low: 16.7, close: 16.9, volume: 208000 },
{ date: "2015-12-21", open: 16.9, high: 17.3, low: 16.8, close: 17.1, volume: 219000 },
{ date: "2015-12-22", open: 17.1, high: 17.4, low: 17.0, close: 17.2, volume: 226000 }, // 触及区间上沿
{ date: "2015-12-23", open: 17.2, high: 17.3, low: 16.8, close: 16.9, volume: 213000 },
{ date: "2015-12-24", open: 16.9, high: 17.1, low: 16.6, close: 16.7, volume: 198000 },
{ date: "2015-12-25", open: 16.7, high: 16.9, low: 16.4, close: 16.5, volume: 185000 },
{ date: "2015-12-26", open: 16.5, high: 16.7, low: 16.2, close: 16.3, volume: 174000 },
{ date: "2015-12-27", open: 16.3, high: 16.6, low: 16.0, close: 16.5, volume: 183000 },
{ date: "2015-12-28", open: 16.5, high: 16.9, low: 16.4, close: 16.7, volume: 195000 },
{ date: "2015-12-29", open: 16.7, high: 17.1, low: 16.6, close: 16.9, volume: 204000 },
{ date: "2015-12-30", open: 16.9, high: 17.2, low: 16.8, close: 17.0, volume: 212000 },
{ date: "2015-12-31", open: 17.0, high: 17.3, low: 16.9, close: 17.1, volume: 220000 },
{ date: "2016-01-01", open: 17.1, high: 17.2, low: 16.8, close: 16.9, volume: 207000 },
{ date: "2016-01-02", open: 16.9, high: 17.1, low: 16.6, close: 16.7, volume: 193000 },
{ date: "2016-01-03", open: 16.7, high: 16.9, low: 16.4, close: 16.5, volume: 181000 },
{ date: "2016-01-04", open: 16.5, high: 16.7, low: 16.2, close: 16.3, volume: 172000 },
{ date: "2016-01-05", open: 16.3, high: 16.6, low: 15.9, close: 16.2, volume: 168000 }, // 触及区间下沿
{ date: "2016-01-06", open: 16.2, high: 16.5, low: 16.0, close: 16.4, volume: 175000 },
{ date: "2016-01-07", open: 16.4, high: 16.8, low: 16.3, close: 16.6, volume: 186000 },
{ date: "2016-01-08", open: 16.6, high: 17.0, low: 16.5, close: 16.8, volume: 197000 },
{ date: "2016-01-09", open: 16.8, high: 17.2, low: 16.7, close: 16.9, volume: 206000 },
{ date: "2016-01-10", open: 16.9, high: 17.3, low: 16.8, close: 17.1, volume: 217000 },
{ date: "2016-01-11", open: 17.1, high: 17.4, low: 17.0, close: 17.2, volume: 225000 }, // 触及区间上沿
{ date: "2016-01-12", open: 17.2, high: 17.3, low: 16.8, close: 16.9, volume: 212000 },
{ date: "2016-01-13", open: 16.9, high: 17.1, low: 16.6, close: 16.7, volume: 197000 },
{ date: "2016-01-14", open: 16.7, high: 16.9, low: 16.4, close: 16.5, volume: 184000 },
{ date: "2016-01-15", open: 16.5, high: 16.7, low: 16.2, close: 16.4, volume: 175000 },
{ date: "2016-01-16", open: 16.4, high: 16.7, low: 16.1, close: 16.3, volume: 171000 },
{ date: "2016-01-17", open: 16.3, high: 16.6, low: 16.0, close: 16.5, volume: 182000 },
{ date: "2016-01-18", open: 16.5, high: 16.9, low: 16.4, close: 16.7, volume: 194000 },
{ date: "2016-01-19", open: 16.7, high: 17.1, low: 16.6, close: 16.9, volume: 203000 },
{ date: "2016-01-20", open: 16.9, high: 17.2, low: 16.8, close: 17.0, volume: 212000 },
];

123
common/util.js

@ -1,12 +1,12 @@
var util = {} var util = {}
util.data = {} util.data = {}
util.data.base_url = 'https://dbqb.nfdxy.net/devApi'
util.data.base_url = 'https://hwjb.homilychart.com/testApi'
// util.data.base_url = 'https://dbqb.nfdxy.net/prodApi' // util.data.base_url = 'https://dbqb.nfdxy.net/prodApi'
// AJAX 请求方法 // AJAX 请求方法
util.request = (url, callback, data = {}, failCallback) => { util.request = (url, callback, data = {}, failCallback) => {
url = util.data.base_url + url url = util.data.base_url + url
console.log('请求该接口->', url,'请求参数为->',data);
console.log('请求该接口->', url, '请求参数为->', data);
uni.request({ uni.request({
url: url, //仅为示例,并非真实接口地址。 url: url, //仅为示例,并非真实接口地址。
data, data,
@ -15,7 +15,8 @@ util.request = (url, callback, data = {}, failCallback) => {
'content-type': 'application/json', 'content-type': 'application/json',
'version': uni.getSystemInfoSync().appVersion, 'version': uni.getSystemInfoSync().appVersion,
'client': uni.getSystemInfoSync().platform == 'ios' ? 'ios' : 'android', 'client': uni.getSystemInfoSync().platform == 'ios' ? 'ios' : 'android',
'token': uni.getStorageSync('token')
'token': uni.getStorageSync('token'),
'deviceId': uni.getSystemInfoSync().deviceId
}, },
sslVerify: false, sslVerify: false,
success: callback, success: callback,
@ -24,3 +25,119 @@ util.request = (url, callback, data = {}, failCallback) => {
} }
export default util export default util
// 画图需要用到的方法
export const utils = {
// 格式化价格
formatPrice(price) {
return price.toFixed(2);
},
// 计算数据范围
calculateDataRange(data, key) {
if (!data || data.length === 0) {
return {
min: 0,
max: 0,
};
}
const values = data.map((item) => item[key]);
return {
min: Math.min(...values),
max: Math.max(...values),
};
},
// 计算标签
calculateLabel(data, type = 2, preClosePrice = 0, key, num) {
let label = [];
if (key === "price") {
// 分时价格区间
if (type == 1) {
const priceRange = utils.calculateDataRange(data, "price");
const theMost = Math.max(priceRange.max - preClosePrice, preClosePrice - priceRange.min);
const mid = (num - 1) / 2;
// 计算分时价格标签
label[mid] = {
value: utils.formatPrice(preClosePrice),
ratio: utils.formatPrice(0) + "%",
};
for (let i = 0; i < mid; i++) {
label[i] = {
value: utils.formatPrice(preClosePrice + (theMost * (mid - i)) / mid),
ratio: utils.formatPrice((100 * (theMost * (mid - i))) / mid / preClosePrice) + "%",
};
label[num - 1 - i] = {
value: utils.formatPrice(preClosePrice - (theMost * (mid - i)) / mid),
ratio: utils.formatPrice((-1 * 100 * (theMost * (mid - i))) / mid / preClosePrice) +
"%",
};
}
timeChartObject.value.max = preClosePrice + theMost;
timeChartObject.value.min = preClosePrice - theMost;
return label;
} else {
const highPriceRange = utils.calculateDataRange(data, "high");
const lowPriceRange = utils.calculateDataRange(data, "low");
const priceDiff = highPriceRange.max * 1.01 - lowPriceRange.min * 0.99;
for (let i = 0; i < num; ++i) {
label[i] = {
value: utils.formatPrice(highPriceRange.max - (i * priceDiff) / num),
};
}
return label;
}
} else if (key === "volume") {
const volumeRange = utils.calculateDataRange(data, "volume");
label[0] = {
value: utils.formatPrice(volumeRange.max),
};
label[1] = {
value: utils.formatPrice(0),
};
return label;
}
return null;
},
// 线性插值
lerp(start, end, factor) {
return start + (end - start) * factor;
},
// 股市数值格式化方法
formatStockNumber(value, decimalPlaces = 2) {
const num = Number(value);
if (isNaN(num)) return "0";
const absNum = Math.abs(num);
const sign = num < 0 ? "-" : "";
if (absNum >= 1000000000000) {
// 万亿级别
return sign + (absNum / 1000000000000).toFixed(decimalPlaces) + "万亿";
} else if (absNum >= 100000000) {
// 亿级别
return sign + (absNum / 100000000).toFixed(decimalPlaces) + "亿";
} else if (absNum >= 10000) {
// 万级别
return sign + (absNum / 10000).toFixed(decimalPlaces) + "万";
} else {
// 小于万的直接显示
return sign + absNum.toFixed(decimalPlaces);
}
},
};
// 防抖函数
export const throttle = (fn, delay = 1000) => {
//距离上一次的执行时间
let lastTime = 0;
return function() {
let _this = this;
let _arguments = arguments;
let now = new Date().getTime();
//如果距离上一次执行超过了delay才能再次执行
if (now - lastTime > delay) {
fn.apply(_this, _arguments);
lastTime = now;
}
};
};

34
components/DeepMate.vue

@ -8,15 +8,10 @@
<text class="deepmate-subtitle">您的市场最佳顾问~</text> <text class="deepmate-subtitle">您的市场最佳顾问~</text>
</view> </view>
<view class="title-right"> <view class="title-right">
<view class="icon-container">
<view class="ripple-effect ripple-1"></view>
<view class="ripple-effect ripple-2"></view>
<view class="ripple-effect ripple-3"></view>
<image class="deepmate-icon" src="https://d31zlh4on95l9h.cloudfront.net/images/7faa683450cc071bcc746fea8191ff6b.png" mode="aspectFit"></image> <image class="deepmate-icon" src="https://d31zlh4on95l9h.cloudfront.net/images/7faa683450cc071bcc746fea8191ff6b.png" mode="aspectFit"></image>
</view> </view>
</view> </view>
</view> </view>
</view>
<view class="deepmate-content"> <view class="deepmate-content">
<view class="deepmate-hotspots"> <view class="deepmate-hotspots">
<view class="deepmate-question"> <view class="deepmate-question">
@ -31,16 +26,16 @@
<view class="hotspot-item"> <view class="hotspot-item">
<text>市场风险提示</text> <text>市场风险提示</text>
</view> </view>
<!-- <view class="hotspot-item">
<view class="hotspot-item">
<text>投资策略建议</text> <text>投资策略建议</text>
</view> </view>
<view class="hotspot-item"> <view class="hotspot-item">
<text>财经新闻解读</text> <text>财经新闻解读</text>
</view> -->
</view>
</view> </view>
<view class="deepmate-action"> <view class="deepmate-action">
<input class="stock-input" type="text" v-model="stockInput" placeholder="请输入股票代码/名称,获取AI洞察" />
<view class="send-button-container" @click="sendStockQuery">
<input class="stock-input" type="text" placeholder="请输入股票代码/名称,获取AI洞察" />
<view class="send-button-container">
<image class="send-button" src="https://d31zlh4on95l9h.cloudfront.net/images/3da018821a5c82b06a1d6ddc81b960ac.png" mode="aspectFit"></image> <image class="send-button" src="https://d31zlh4on95l9h.cloudfront.net/images/3da018821a5c82b06a1d6ddc81b960ac.png" mode="aspectFit"></image>
</view> </view>
</view> </view>
@ -50,31 +45,14 @@
</template> </template>
<script> <script>
export default {
export default {
name: 'DeepMate', name: 'DeepMate',
data() { data() {
return { return {
stockInput: ''
}
},
methods: {
sendStockQuery() {
//
if (!this.stockInput.trim()) {
uni.showToast({
title: '请输入股票代码或名称',
icon: 'none'
});
return;
}
//
uni.navigateTo({
url: `/pages/home/deepExploration?query=${encodeURIComponent(this.stockInput)}`
});
} }
} }
}
}
</script> </script>
<style> <style>

4
components/IndexCard.vue

@ -4,7 +4,7 @@
<view class="flag-container"> <view class="flag-container">
<image :src="flagIcon" class="flag-icon" mode="aspectFit"></image> <image :src="flagIcon" class="flag-icon" mode="aspectFit"></image>
</view> </view>
<text class="index-name">{{ indexName }}</text>
<text class="index-name">{{ stockName }}</text>
</view> </view>
<view class="price-info"> <view class="price-info">
@ -35,7 +35,7 @@ const props = defineProps({
required: true required: true
}, },
// //
indexName: {
stockName: {
type: String, type: String,
required: true required: true
}, },

58
components/MarketOverview.vue

@ -18,20 +18,13 @@
<view class="forex-market"> <view class="forex-market">
<!-- 上部分三个汇率容器 --> <!-- 上部分三个汇率容器 -->
<view class="forex-rates"> <view class="forex-rates">
<view class="forex-rate-item up">
<text class="forex-pair">美元/日元</text>
<text class="forex-value">151.13</text>
<text class="forex-change">+1.62%</text>
</view>
<view class="forex-rate-item down">
<text class="forex-pair">美元/韩元</text>
<text class="forex-value">1424.900</text>
<text class="forex-change">-2.92%</text>
</view>
<view class="forex-rate-item up">
<text class="forex-pair">美元/英镑</text>
<text class="forex-value">0.730</text>
<text class="forex-change">+2.92%</text>
<view class="forex-rate-item"
v-for="(stock, index) in stockInfoList"
:key="index"
:class="stock.change_percent >= 0 ? 'up' : 'down'">
<text class="forex-pair">{{ stock.stock_name }}</text>
<text class="forex-value">{{ stock.current_price }}</text>
<text class="forex-change">{{ stock.change }}</text>
</view> </view>
</view> </view>
@ -54,7 +47,7 @@
<text class="analysis-dot blue"></text> <text class="analysis-dot blue"></text>
<text class="analysis-text">市场风险评级: 需警惕潜在风险</text> <text class="analysis-text">市场风险评级: 需警惕潜在风险</text>
</view> </view>
<view class="analysis-item" @click="goToMorningMarketAnalysis">
<view class="analysis-item">
<text class="analysis-dot green"></text> <text class="analysis-dot green"></text>
<text class="analysis-text">早盘解析: 今日高开, 芯片稀土公共</text> <text class="analysis-text">早盘解析: 今日高开, 芯片稀土公共</text>
</view> </view>
@ -94,6 +87,35 @@
<script> <script>
export default { export default {
name: 'MarketOverview', name: 'MarketOverview',
props: {
// 3
stockInfoList: {
type: Array,
default: () => ([
{
stock_name: '美元/日元',
current_price: '151.13',
change: '+1.62%',
change_value: 0,
change_percent: 0
},
{
stock_name: '美元/韩元',
current_price: '1424.900',
change: '-2.92%',
change_value: 0,
change_percent: 0
},
{
stock_name: '美元/英镑',
current_price: '0.730',
change: '+2.92%',
change_value: 0,
change_percent: 0
}
])
}
},
data() { data() {
return { return {
selectedMarket: 'forex', selectedMarket: 'forex',
@ -142,12 +164,6 @@ export default {
this.showSelector = false; this.showSelector = false;
this.$refs.marketPopup.close(); this.$refs.marketPopup.close();
}, },
goToMorningMarketAnalysis() {
//
uni.navigateTo({
url: '/pages/morningMarketAnalysis/morningMarketAnalysis'
});
},
getSelectedMarketName() { getSelectedMarketName() {
if (!this.selectedMarket) return ''; if (!this.selectedMarket) return '';
return this.marketData[this.selectedMarket].name; return this.marketData[this.selectedMarket].name;

536
components/deepExploration_header.vue

@ -0,0 +1,536 @@
<template>
<view class="titleContent">
<view class="left">
<uni-icons
@click="handleBack"
type="back"
size="23"
color="#111"
></uni-icons>
</view>
<view class="title">深度探索</view>
<view class="right">
<image
class="notice"
src="/static/deepExploration-images/notice.png"
mode="aspectFill"
></image>
<image
@click="openHistoryDrawer"
class="history"
src="/static/deepExploration-images/history.png"
mode="aspectFill"
></image>
</view>
</view>
<view class="drawer-overlay" v-show="showHistoryDrawer"></view>
<view
class="drawer-panel"
v-show="showHistoryDrawer"
@click.stop
@touchmove.stop.prevent
:style="{ transform: 'translateY(' + drawerOffsetY + 'px)' }"
>
<view class="drawer-header">
<text class="drawer-title">历史对话</text>
<view class="drawer-actions">
<view class="delete-all-container">
<image
class="delete-icon"
src="/static/icons/Group_48095481.svg"
></image>
<text class="delete-all" @click="clearAllHistory">删除全部</text>
</view>
<view class="drawer-close" @click="onDrawerBackClick"
><text class="drawer-close-icon"></text
></view>
</view>
</view>
<scroll-view scroll-y="true" class="drawer-content">
<view class="drawer-inner">
<view v-if="groupedHistory.length === 0" class="empty-history">
<text>暂无历史记录</text>
</view>
<view
v-for="(section, sIdx) in groupedHistory"
:key="sIdx"
class="history-section"
>
<text class="section-title">{{ section.title }}</text>
<view
v-for="(item, idx) in section.items"
:key="idx"
class="history-item"
>
<view class="history-left">
<view class="flag-circle"
><text class="flag-emoji">🇺🇸</text></view
>
</view>
<view class="history-main" @click="itemClick(item)">
<text class="history-query">{{ item.stockName }}</text>
<text class="history-query">{{ item.stockCode }}</text>
</view>
<text class="history-time">{{
formatTimeForHistory(item.createdTime)
}}</text>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { RecordListApi } from "../api/deepExploration/deepExploration";
import { ref, onMounted, computed } from "vue";
const props = defineProps({
name: {
type: String,
default: "",
},
});
const showHistoryDrawer = ref(false);
const drawerOffsetY = ref(0);
// const handleHistory = () => {
// showHistoryDrawer.value = true;
// };
//
const openHistoryDrawer = async () => {
const res = await RecordListApi({
model: 1,
});
if (res.code === 200) {
historyList.value = res.data;
}
console.log("historyList.value", historyList.value);
const hideDistance = uni.upx2px(900);
drawerOffsetY.value = hideDistance;
showHistoryDrawer.value = true;
setTimeout(() => {
drawerOffsetY.value = 0;
}, 10);
};
const clearAllHistory = () => {
searchHistory.value = [];
// uni.setStorageSync("search_history", []);
};
//
const handleBack = () => {
uni.navigateBack();
};
const closeHistoryDrawer = () => {
showHistoryDrawer.value = false;
};
const onDrawerBackClick = () => {
const hideDistance = uni.upx2px(900);
drawerOffsetY.value = hideDistance;
setTimeout(() => {
closeHistoryDrawer();
drawerOffsetY.value = 0;
}, 180);
};
//
async function itemClick(item) {
// const res = await postHistoryDetail({
// recordId: item.id,
// parentId: item.parentId,
// model: 5,
// });
// if (res.code == 200) {
// const message = res.data.wokeFlowData.One.markdown;
// messages.value = [];
// const botMsg = {
// content: message,
// isUser: false,
// isTyping: false,
// isThinking: false,
// };
// messages.value.push(botMsg);
// }
}
const historyList = ref([
{
title: "今天", //
items: [
{
icon: "/static/deepExploration-images/Americle.png",
stockName: "TechCore", //
stockCode: "600001", // 6
createdTime: "14:35", // :
},
{
icon: "/static/deepExploration-images/Americle.png",
stockName: "MediaLink",
stockCode: "600002",
createdTime: "10:12",
},
],
},
{
title: "昨天",
items: [
{
icon: "/static/deepExploration-images/Americle.png",
stockName: "FinServ",
stockCode: "600003",
createdTime: "09:48",
},
],
},
]);
// YYYY-MM-DD HH:mm
const formatTimeForHistory = (timeString) => {
// timeString "YYYY-MM-DD HH:mm:ss"
const parts = timeString.split(" ");
if (parts.length >= 2) {
const datePart = parts[0];
const timePart = parts[1];
const timeParts = timePart.split(":");
if (timeParts.length >= 2) {
return `${datePart} ${timeParts[0]}:${timeParts[1]}`;
}
}
return timeString;
};
// ///
const groupedHistory = computed(() => {
const sections = [];
// 使
const now = new Date();
const startOfDay = (d) =>
new Date(d.getFullYear(), d.getMonth(), d.getDate());
const isSameDay = (a, b) =>
startOfDay(a).getTime() === startOfDay(b).getTime();
const isYesterday = (d) => {
const y = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
return isSameDay(d, y);
};
const isToday = (d) => isSameDay(d, now);
const withinLast7Days = (d) => {
const seven = new Date(
now.getFullYear(),
now.getMonth(),
now.getDate() - 7
);
return d >= seven && !isToday(d) && !isYesterday(d);
};
const monthLabel = (d) => `${d.getMonth() + 1}`;
const today = [];
const yesterday = [];
const last7 = [];
const byMonth = new Map();
// 使 historyList.value searchHistory.value
historyList.value.forEach((item) => {
// 使 createdTime
const dt = new Date(item.createdTime);
if (isToday(dt)) {
today.push(item);
} else if (isYesterday(dt)) {
yesterday.push(item);
} else if (withinLast7Days(dt)) {
last7.push(item);
} else {
const year = dt.getFullYear();
const month = dt.getMonth() + 1;
const key = `${year}-${month}`;
if (!byMonth.has(key))
byMonth.set(key, {
title: `${year}${month}`,
year,
month,
items: [],
});
byMonth.get(key).items.push(item);
}
});
if (today.length) sections.push({ title: "今天", items: today });
if (yesterday.length) sections.push({ title: "昨天", items: yesterday });
if (last7.length) sections.push({ title: "近一周", items: last7 });
const monthSections = Array.from(byMonth.values()).sort((a, b) => {
if (a.year !== b.year) return b.year - a.year;
return b.month - a.month; // 10 9
});
sections.push(...monthSections);
return sections;
});
onMounted(() => {});
</script>
<style scoped lang="scss">
* {
box-sizing: border-box;
}
.titleContent {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx 60rpx 40rpx 35rpx;
box-shadow: 2rpx 2rpx 8rpx rgba(0, 0, 0, 0.1);
.left {
display: flex;
align-items: center;
width: 36rpx;
height: 36rpx;
}
.title {
position: absolute;
left: 50%;
transform: translateX(-50%);
color: #000000;
font-family: "PingFang SC";
font-size: 19px;
font-style: normal;
font-weight: 400;
line-height: 25px;
}
.right {
display: flex;
align-items: center;
gap: 20rpx;
.notice {
width: 35rpx;
height: 35rpx;
gap: 20rpx;
}
.history {
width: 32rpx;
height: 32rpx;
align-items: center;
}
}
}
/* 搜索历史侧拉框样式 */
.drawer-overlay {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.35);
z-index: 900;
}
.drawer-panel {
position: fixed;
left: 0;
right: 0;
bottom: 0;
height: 75vh;
max-height: 80vh;
width: 100%;
background: #ffffff;
box-shadow: 0 -8rpx 20rpx rgba(0, 0, 0, 0.06);
z-index: 1000;
display: flex;
flex-direction: column;
border-top-left-radius: 20rpx;
border-top-right-radius: 20rpx;
transition: transform 0.22s ease;
}
.drawer-back {
position: absolute;
left: 50%;
top: -14px;
transform: translateX(-50%);
width: 28px;
height: 48px;
border-radius: 12px;
background: #fff;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
display: flex;
align-items: center;
justify-content: center;
}
.drawer-back-icon {
font-size: 16px;
color: #8a8a8a;
}
.drawer-actions {
display: flex;
align-items: center;
gap: 40rpx;
}
.delete-all-container {
display: flex;
align-items: center;
gap: 14rpx;
}
.delete-icon {
width: 28rpx;
height: 32rpx;
}
.delete-all {
font-size: 28rpx;
}
.drawer-close {
background: url("../static/icons/关闭2.svg");
width: 48rpx;
height: 48rpx;
border-radius: 24rpx;
/* background: #f5f5f5; */
/* box-shadow: 0 2px 8px rgba(0,0,0,0.08); */
display: flex;
align-items: center;
justify-content: center;
image {
width: 100%;
height: 100%;
}
}
.drawer-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 52rpx 28rpx 0rpx 28rpx;
/* border-bottom: 2rpx solid #f0f0f0; */
}
.drawer-title {
font-size: 32rpx;
font-weight: 600;
color: #333333;
}
.drawer-content {
flex: 1;
min-height: 0;
/* 让 flex 子元素可在容器内收缩以启用滚动 */
height: 100%;
overscroll-behavior-y: contain;
/* 防止滚动串联到页面 */
-webkit-overflow-scrolling: touch;
/* iOS 惯性滚动 */
touch-action: pan-y;
/* 优化触控滚动,仅垂直 */
}
.drawer-inner {
padding: 20rpx 24rpx 20rpx 24rpx;
}
.history-section {
margin-bottom: 20rpx;
/* margin: 0 24rpx; */
}
.section-title {
font-size: 26rpx;
color: #888;
margin: 10rpx 6rpx 16rpx;
}
.history-item {
display: flex;
align-items: center;
padding: 19rpx 18rpx;
background-color: #f6f7f9;
border-radius: 12rpx;
margin-bottom: 12rpx;
}
.history-left {
margin-right: 20rpx;
width: 50rpx;
height: 50rpx;
}
.flag-circle {
width: 50rpx;
height: 50rpx;
border-radius: 50%;
background: #fff;
border: 2rpx solid #eee;
display: flex;
align-items: center;
justify-content: center;
image {
width: 50rpx;
height: 50rpx;
border-radius: 50%;
}
}
.history-main {
flex: 1;
}
.history-query {
color: #000000;
text-align: center;
font-size: 24rpx;
font-weight: 400;
line-height: 40rpx;
}
.history-time {
font-size: 22rpx;
color: #999;
}
.history-card {
background-color: #fff;
border: 2rpx solid #f2f2f2;
border-left: 8rpx solid rgb(220, 31, 29);
border-radius: 12rpx;
padding: 18rpx 20rpx;
margin-bottom: 16rpx;
box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.03);
}
.history-query {
font-size: 28rpx;
color: #333333;
font-weight: 500;
}
.history-time {
margin-top: 8rpx;
font-size: 22rpx;
color: #888888;
}
.empty-history {
padding: 40rpx;
color: #999999;
text-align: center;
}
</style>

49
components/footerBar.vue

@ -1,4 +1,45 @@
<template> <template>
<!-- #ifdef H5 -->
<view class="static-footer-bar" :style="{ 'padding-bottom': safeAreaInsets.bottom + 'px' }">
<view class="static-footer-li" @click="tabChange(1)">
<image src="../static/footBar-image/home.png" class="static-footer-li-icon" v-if="type != 'home'"></image>
<image src="../static/footBar-image/home-selected.png" class="static-footer-li-icon" v-if="type == 'home'"></image>
<view :class="type == 'home' ? 'static-footer-li-title1' : 'static-footer-li-title'">
首页</view>
</view>
<view class="static-footer-li" @click="tabChange(2)">
<image src="../static/footBar-image/marketSituation.png" class="static-footer-li-icon" v-if="type != 'marketSituation'">
</image>
<image src="../static/footBar-image/marketSituation-selected.png" class="static-footer-li-icon"
v-if="type == 'marketSituation'"></image>
<view :class="type == 'marketSituation' ? 'static-footer-li-title1' : 'static-footer-li-title'">
行情</view>
</view>
<view class="static-footer-li static-footer-li-special" @click="tabChange(3)">
<image src="../static/footBar-image/deepMate.png" class="static-footer-li-icon static-footer-li-icon-special" v-if="type != 'deepMate'"></image>
<image src="../static/footBar-image/deepMate-selected.png" class="static-footer-li-icon static-footer-li-icon-special" v-if="type == 'deepMate'">
</image>
<view :class="type == 'deepMate' ? 'static-footer-li-title1' : 'static-footer-li-title'">
DeepMate</view>
</view>
<view class="static-footer-li" @click="tabChange(4)">
<image src="../static/footBar-image/deepExploration.png" class="static-footer-li-icon" v-if="type != 'deepExploration'">
</image>
<image src="../static/footBar-image/deepExploration-selected.png" class="static-footer-li-icon"
v-if="type == 'deepExploration'"></image>
<view :class="type == 'deepExploration' ? 'static-footer-li-title1' : 'static-footer-li-title'">
深度探索</view>
</view>
<view class="static-footer-li" @click="tabChange(5)">
<image src="../static/footBar-image/member.png" class="static-footer-li-icon" v-if="type != 'member'"></image>
<image src="../static/footBar-image/member-selected.png" class="static-footer-li-icon" v-if="type == 'member'"></image>
<view :class="type == 'member' ? 'static-footer-li-title1' : 'static-footer-li-title'">
我的</view>
</view>
</view>
<!-- #endif -->
<!-- #ifndef H5 -->
<view class="static-footer-bar" :style="{ 'padding-bottom': safeAreaInsets.bottom + 'px' }"> <view class="static-footer-bar" :style="{ 'padding-bottom': safeAreaInsets.bottom + 'px' }">
<view class="static-footer-li" @click="tabChange(1)"> <view class="static-footer-li" @click="tabChange(1)">
<image src="../static/footBar-image/home.png" class="static-footer-li-icon" v-if="type != 'home'"></image> <image src="../static/footBar-image/home.png" class="static-footer-li-icon" v-if="type != 'home'"></image>
@ -36,6 +77,8 @@
{{ $t('components.footerBar.member') }}</view> {{ $t('components.footerBar.member') }}</view>
</view> </view>
</view> </view>
<!-- #endif -->
</template> </template>
<script setup> <script setup>
@ -68,18 +111,18 @@ const tabChange = (value) => {
}) })
} else if (value == 2) { // } else if (value == 2) { //
uni.redirectTo({ uni.redirectTo({
url: '/pages/home/marketSituation',
url: '/pages/marketSituation/marketSituation',
animationType: 'fade-in' animationType: 'fade-in'
}) })
} else if (value == 3) { //DeepMate } else if (value == 3) { //DeepMate
uni.redirectTo({ uni.redirectTo({
url: '/pages/home/deepMate',
url: '/pages/deepMate/deepMate',
animationType: 'fade-in' animationType: 'fade-in'
}) })
} else if (value == 4) { // } else if (value == 4) { //
if (props.type == 'deepExploration') return; if (props.type == 'deepExploration') return;
uni.redirectTo({ uni.redirectTo({
url: '/pages/home/deepExploration',
url: '/pages/deepExploration/deepExploration',
animationType: 'fade-in' animationType: 'fade-in'
}) })
} else if (value == 5) { // } else if (value == 5) { //

183
components/login-prompt.vue

@ -0,0 +1,183 @@
<template>
<view class="login-prompt" v-if="showPrompt">
<view class="mask" :class="{ 'mask-show': showAnimation }"></view>
<view class="prompt-content" :class="{ 'slide-up': showAnimation }">
<text class="prompt-title">登录以获得更好的体验</text>
<button class="login-btn" @click="goLogin">登录</button>
<button class="visitor-btn" @click="continueAsVisitor">
以访客身份继续
</button>
<text class="prompt-title-small" @click="goRegister"
>如果您还没有账号点击注册</text
>
</view>
</view>
</template>
<script setup>
import { ref, nextTick, onMounted } from "vue";
import { useUserStore } from "../stores/modules/userInfo";
import { useDeviceStore } from "../stores/modules/deviceInfo";
import { LoginApi } from "../api/start/login";
const deviceId = ref("");
const userStore = useUserStore();
const deviceStore = useDeviceStore();
//
onMounted(() => {
if (!userStore.userInfo) {
show();
}
});
//
const showPrompt = ref(false);
const showAnimation = ref(false);
//
const show = () => {
showPrompt.value = true;
//
nextTick(() => {
setTimeout(() => {
showAnimation.value = true;
}, 10);
});
};
//
const hide = () => {
showAnimation.value = false;
//
setTimeout(() => {
showPrompt.value = false;
}, 300);
};
//
const goLogin = () => {
uni.navigateTo({
url: "/pages/start/login/login",
});
hide();
};
//
const goRegister = () => {
uni.navigateTo({
url: "/pages/start/Registration/Registration",
});
hide();
};
// 访
const continueAsVisitor = async () => {
// 访
const res = await LoginApi({
loginType: "VISITOR", //EMAIL,PHONE,DCCODE,APPLE,GOOGLE,VISITOR
account: "", // //dccode
verifyCode: "", //
password: "", //
useCode: "", //使 true/false
idToken: "", //idToken
deviceId: deviceStore.deviceInfo.deviceId,
});
if (res.code === 200) {
userStore.setUserInfo(res.data);
hide();
}
};
// 使
defineExpose({
show,
hide,
});
</script>
<style scoped>
.login-prompt {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 999;
}
.mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.8);
opacity: 0;
transition: opacity 0.3s ease;
}
.mask.mask-show {
opacity: 1;
}
.prompt-content {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 400rpx;
border-radius: 20rpx 20rpx 0 0;
background-color: white;
padding: 20rpx 70rpx;
transform: translateY(100%);
transition: transform 0.3s ease;
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.3);
}
.prompt-content.slide-up {
transform: translateY(0);
}
.prompt-title {
display: block;
text-align: center;
font-size: 40rpx;
font-weight: 700;
color: #000000;
margin-top: 30rpx;
margin-bottom: 40rpx;
}
.login-btn {
width: 100%;
height: 80rpx;
background-color: rgb(0, 0, 0);
color: white;
font-size: 32rpx;
line-height: 80rpx;
border-radius: 40rpx;
margin-bottom: 20rpx;
}
.visitor-btn {
width: 100%;
height: 80rpx;
background-color: #f5f5f5;
color: #333;
font-size: 32rpx;
line-height: 80rpx;
border-radius: 40rpx;
}
.prompt-title-small {
display: block;
text-align: center;
font-size: 24rpx;
color: #6a6a6a;
margin-top: 30rpx;
margin-bottom: 40rpx;
line-height: 15.5px;
letter-spacing: 0.3px;
font-style: normal;
font-family: "PingFang SC";
}
</style>

22
main.js

@ -1,15 +1,26 @@
import LoginPrompt from './components/login-prompt.vue'
import pinia from './stores/index.js'
// #ifndef VUE3 // #ifndef VUE3
import Vue from 'vue' import Vue from 'vue'
import App from './App' import App from './App'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
Vue.component('LoginPrompt', LoginPrompt)
Vue.config.productionTip = false Vue.config.productionTip = false
App.mpType = 'app' App.mpType = 'app'
const app = new Vue({ const app = new Vue({
...App
...App,
pinia
}) })
app.use(ElementPlus)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.$mount() app.$mount()
// #endif // #endif
@ -32,7 +43,7 @@ function getCurrentLocale() {
return uni.getStorageSync('languageData').code; return uni.getStorageSync('languageData').code;
} else { } else {
let language = uni.getSystemInfoSync().osLanguage; let language = uni.getSystemInfoSync().osLanguage;
// language = 'zh_CN'
language = 'zh_CN'
if (language.indexOf('th') != -1) { if (language.indexOf('th') != -1) {
language = 'th' language = 'th'
uni.setStorageSync('languageData', { uni.setStorageSync('languageData', {
@ -47,7 +58,7 @@ function getCurrentLocale() {
}) })
console.log(language); console.log(language);
return language return language
}else if (language.indexOf('zh') != -1) {
} else if (language.indexOf('zh') != -1) {
if (language.indexOf('CN') != -1) { if (language.indexOf('CN') != -1) {
language = 'zh_CN' language = 'zh_CN'
uni.setStorageSync('languageData', { uni.setStorageSync('languageData', {
@ -103,6 +114,9 @@ const i18n = createI18n({
}) })
export function createApp() { export function createApp() {
const app = createSSRApp(App) const app = createSSRApp(App)
app.component('LoginPrompt', LoginPrompt)
app.use(pinia)
app.use(i18n) app.use(i18n)
return { return {
app app

38
manifest.json

@ -1,6 +1,6 @@
{ {
"name" : "DeepChartApp", "name" : "DeepChartApp",
"appid" : "__UNI__D8DF433",
"appid" : "__UNI__9C9AB28",
"description" : "", "description" : "",
"versionName" : "1.0.0", "versionName" : "1.0.0",
"versionCode" : "100", "versionCode" : "100",
@ -16,7 +16,9 @@
"autoclose" : true, "autoclose" : true,
"delay" : 0 "delay" : 0
}, },
"modules" : {},
"modules" : {
"OAuth" : {}
},
/* */ /* */
"distribute" : { "distribute" : {
/* */ /* */
@ -38,11 +40,37 @@
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>", "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<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\"/>"
]
],
"minSdkVersion" : 21
},
"ios" : {
"dSYMs" : false
}, },
"ios" : {},
/* ios */ /* ios */
"sdkConfigs" : {}
"sdkConfigs" : {
"oauth" : {
"apple" : {},
"google" : {
"clientid" : "135"
}
}
}
},
"nativePlugins" : {
"Aimer-TCPPlugin" : {
"__plugin_info__" : {
"name" : "TCP-Socket原生插件(支持Android和IOS) - [试用版,仅用于自定义调试基座]",
"description" : "Uniapp实现基于TCP的数据通信,支持单片机、智能家居等硬件交互,联系QQ: 462108858",
"platforms" : "Android,iOS",
"url" : "https://ext.dcloud.net.cn/plugin?id=2029",
"android_package_name" : "",
"ios_bundle_id" : "",
"isCloud" : true,
"bought" : 0,
"pid" : "2029",
"parameters" : {}
}
}
} }
}, },
/* SDK */ /* SDK */

469
package-lock.json

@ -1,17 +1,21 @@
{ {
"name": "DeepChartApp",
"name": "deepChartVueApp",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"dependencies": { "dependencies": {
"@dcloudio/uni-ui": "^1.5.11",
"@element-plus/icons-vue": "^2.3.2",
"element-plus": "^2.11.5",
"marked": "^2.0.1",
"pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.5.0",
"vue-i18n": "^9.14.5" "vue-i18n": "^9.14.5"
} }
}, },
"node_modules/@babel/helper-string-parser": { "node_modules/@babel/helper-string-parser": {
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"engines": { "engines": {
@ -19,9 +23,7 @@
} }
}, },
"node_modules/@babel/helper-validator-identifier": { "node_modules/@babel/helper-validator-identifier": {
"version": "7.27.1",
"resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
"version": "7.28.5",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"engines": { "engines": {
@ -29,13 +31,11 @@
} }
}, },
"node_modules/@babel/parser": { "node_modules/@babel/parser": {
"version": "7.28.4",
"resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.4.tgz",
"integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
"version": "7.28.5",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"@babel/types": "^7.28.4"
"@babel/types": "^7.28.5"
}, },
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
@ -45,19 +45,66 @@
} }
}, },
"node_modules/@babel/types": { "node_modules/@babel/types": {
"version": "7.28.4",
"resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.4.tgz",
"integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
"version": "7.28.5",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"@babel/helper-string-parser": "^7.27.1", "@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1"
"@babel/helper-validator-identifier": "^7.28.5"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@ctrl/tinycolor": {
"version": "3.6.1",
"resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
"integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/@dcloudio/uni-ui": {
"version": "1.5.11",
"resolved": "https://registry.npmmirror.com/@dcloudio/uni-ui/-/uni-ui-1.5.11.tgz",
"integrity": "sha512-DBtk046ofmeFd82zRI7d89SoEwrAxYzUN3WVPm1DIBkpLPG5F5QDNkHMnZGu2wNrMEmGBjBpUh3vqEY1L3jaMw==",
"license": "Apache-2.0"
},
"node_modules/@element-plus/icons-vue": {
"version": "2.3.2",
"resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz",
"integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==",
"license": "MIT",
"peerDependencies": {
"vue": "^3.2.0"
}
},
"node_modules/@floating-ui/core": {
"version": "1.7.3",
"resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.7.3.tgz",
"integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
"license": "MIT",
"dependencies": {
"@floating-ui/utils": "^0.2.10"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.7.4",
"resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.4.tgz",
"integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==",
"license": "MIT",
"dependencies": {
"@floating-ui/core": "^1.7.3",
"@floating-ui/utils": "^0.2.10"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.2.10",
"resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.10.tgz",
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
"license": "MIT"
},
"node_modules/@intlify/core-base": { "node_modules/@intlify/core-base": {
"version": "9.14.5", "version": "9.14.5",
"resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.14.5.tgz", "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.14.5.tgz",
@ -104,15 +151,43 @@
}, },
"node_modules/@jridgewell/sourcemap-codec": { "node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5", "version": "1.5.5",
"resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"license": "MIT", "license": "MIT",
"peer": true "peer": true
}, },
"node_modules/@popperjs/core": {
"name": "@sxzz/popperjs-es",
"version": "2.11.7",
"resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz",
"integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@types/lodash": {
"version": "4.17.20",
"resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==",
"license": "MIT"
},
"node_modules/@types/lodash-es": {
"version": "4.17.12",
"resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz",
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
"license": "MIT",
"dependencies": {
"@types/lodash": "*"
}
},
"node_modules/@types/web-bluetooth": {
"version": "0.0.16",
"resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
"integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==",
"license": "MIT"
},
"node_modules/@vue/compiler-core": { "node_modules/@vue/compiler-core": {
"version": "3.5.22", "version": "3.5.22",
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.22.tgz",
"integrity": "sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
@ -125,8 +200,6 @@
}, },
"node_modules/@vue/compiler-dom": { "node_modules/@vue/compiler-dom": {
"version": "3.5.22", "version": "3.5.22",
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.22.tgz",
"integrity": "sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
@ -136,8 +209,6 @@
}, },
"node_modules/@vue/compiler-sfc": { "node_modules/@vue/compiler-sfc": {
"version": "3.5.22", "version": "3.5.22",
"resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.22.tgz",
"integrity": "sha512-tbTR1zKGce4Lj+JLzFXDq36K4vcSZbJ1RBu8FxcDv1IGRz//Dh2EBqksyGVypz3kXpshIfWKGOCcqpSbyGWRJQ==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
@ -154,8 +225,6 @@
}, },
"node_modules/@vue/compiler-ssr": { "node_modules/@vue/compiler-ssr": {
"version": "3.5.22", "version": "3.5.22",
"resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.22.tgz",
"integrity": "sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
@ -164,15 +233,34 @@
} }
}, },
"node_modules/@vue/devtools-api": { "node_modules/@vue/devtools-api": {
"version": "6.6.4",
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
"license": "MIT"
"version": "7.7.7",
"license": "MIT",
"dependencies": {
"@vue/devtools-kit": "^7.7.7"
}
},
"node_modules/@vue/devtools-kit": {
"version": "7.7.7",
"license": "MIT",
"dependencies": {
"@vue/devtools-shared": "^7.7.7",
"birpc": "^2.3.0",
"hookable": "^5.5.3",
"mitt": "^3.0.1",
"perfect-debounce": "^1.0.0",
"speakingurl": "^14.0.1",
"superjson": "^2.2.2"
}
},
"node_modules/@vue/devtools-shared": {
"version": "7.7.7",
"license": "MIT",
"dependencies": {
"rfdc": "^1.4.1"
}
}, },
"node_modules/@vue/reactivity": { "node_modules/@vue/reactivity": {
"version": "3.5.22", "version": "3.5.22",
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.22.tgz",
"integrity": "sha512-f2Wux4v/Z2pqc9+4SmgZC1p73Z53fyD90NFWXiX9AKVnVBEvLFOWCEgJD3GdGnlxPZt01PSlfmLqbLYzY/Fw4A==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
@ -181,8 +269,6 @@
}, },
"node_modules/@vue/runtime-core": { "node_modules/@vue/runtime-core": {
"version": "3.5.22", "version": "3.5.22",
"resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.22.tgz",
"integrity": "sha512-EHo4W/eiYeAzRTN5PCextDUZ0dMs9I8mQ2Fy+OkzvRPUYQEyK9yAjbasrMCXbLNhF7P0OUyivLjIy0yc6VrLJQ==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
@ -192,8 +278,6 @@
}, },
"node_modules/@vue/runtime-dom": { "node_modules/@vue/runtime-dom": {
"version": "3.5.22", "version": "3.5.22",
"resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.22.tgz",
"integrity": "sha512-Av60jsryAkI023PlN7LsqrfPvwfxOd2yAwtReCjeuugTJTkgrksYJJstg1e12qle0NarkfhfFu1ox2D+cQotww==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
@ -205,8 +289,6 @@
}, },
"node_modules/@vue/server-renderer": { "node_modules/@vue/server-renderer": {
"version": "3.5.22", "version": "3.5.22",
"resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.22.tgz",
"integrity": "sha512-gXjo+ao0oHYTSswF+a3KRHZ1WszxIqO7u6XwNHqcqb9JfyIL/pbWrrh/xLv7jeDqla9u+LK7yfZKHih1e1RKAQ==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
@ -219,22 +301,171 @@
}, },
"node_modules/@vue/shared": { "node_modules/@vue/shared": {
"version": "3.5.22", "version": "3.5.22",
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.22.tgz",
"integrity": "sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==",
"license": "MIT", "license": "MIT",
"peer": true "peer": true
}, },
"node_modules/@vueuse/core": {
"version": "9.13.0",
"resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz",
"integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==",
"license": "MIT",
"dependencies": {
"@types/web-bluetooth": "^0.0.16",
"@vueuse/metadata": "9.13.0",
"@vueuse/shared": "9.13.0",
"vue-demi": "*"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/core/node_modules/vue-demi": {
"version": "0.14.10",
"resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
"hasInstallScript": true,
"license": "MIT",
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/@vueuse/metadata": {
"version": "9.13.0",
"resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.13.0.tgz",
"integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/shared": {
"version": "9.13.0",
"resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.13.0.tgz",
"integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==",
"license": "MIT",
"dependencies": {
"vue-demi": "*"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/shared/node_modules/vue-demi": {
"version": "0.14.10",
"resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
"hasInstallScript": true,
"license": "MIT",
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/async-validator": {
"version": "4.2.5",
"resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz",
"integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==",
"license": "MIT"
},
"node_modules/birpc": {
"version": "2.6.1",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/copy-anything": {
"version": "4.0.5",
"license": "MIT",
"dependencies": {
"is-what": "^5.2.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/mesqueeb"
}
},
"node_modules/csstype": { "node_modules/csstype": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT", "license": "MIT",
"peer": true "peer": true
}, },
"node_modules/dayjs": {
"version": "1.11.18",
"license": "MIT"
},
"node_modules/deep-pick-omit": {
"version": "1.2.1",
"license": "MIT"
},
"node_modules/defu": {
"version": "6.1.4",
"license": "MIT"
},
"node_modules/destr": {
"version": "2.0.5",
"license": "MIT"
},
"node_modules/element-plus": {
"version": "2.11.5",
"resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.11.5.tgz",
"integrity": "sha512-O+bIVHQCjUDm4GiIznDXRoS8ar2TpWLwfOGnN/Aam0VXf5kbuc4SxdKKJdovWNxmxeqbcwjsSZPKgtXNcqys4A==",
"license": "MIT",
"dependencies": {
"@ctrl/tinycolor": "^3.4.1",
"@element-plus/icons-vue": "^2.3.2",
"@floating-ui/dom": "^1.0.1",
"@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7",
"@types/lodash": "^4.17.20",
"@types/lodash-es": "^4.17.12",
"@vueuse/core": "^9.1.0",
"async-validator": "^4.2.5",
"dayjs": "^1.11.18",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"lodash-unified": "^1.0.3",
"memoize-one": "^6.0.0",
"normalize-wheel-es": "^1.2.0"
},
"peerDependencies": {
"vue": "^3.2.0"
}
},
"node_modules/entities": { "node_modules/entities": {
"version": "4.5.0", "version": "4.5.0",
"resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"peer": true, "peer": true,
"engines": { "engines": {
@ -246,25 +477,76 @@
}, },
"node_modules/estree-walker": { "node_modules/estree-walker": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"license": "MIT", "license": "MIT",
"peer": true "peer": true
}, },
"node_modules/hookable": {
"version": "5.5.3",
"license": "MIT"
},
"node_modules/is-what": {
"version": "5.5.0",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/mesqueeb"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT"
},
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"license": "MIT"
},
"node_modules/lodash-unified": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz",
"integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==",
"license": "MIT",
"peerDependencies": {
"@types/lodash-es": "*",
"lodash": "*",
"lodash-es": "*"
}
},
"node_modules/magic-string": { "node_modules/magic-string": {
"version": "0.30.19",
"resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.19.tgz",
"integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==",
"version": "0.30.21",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5" "@jridgewell/sourcemap-codec": "^1.5.5"
} }
}, },
"node_modules/marked": {
"version": "2.0.1",
"license": "MIT",
"bin": {
"marked": "bin/marked"
},
"engines": {
"node": ">= 8.16.2"
}
},
"node_modules/memoize-one": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz",
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
"license": "MIT"
},
"node_modules/mitt": {
"version": "3.0.1",
"license": "MIT"
},
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.11", "version": "3.3.11",
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -280,17 +562,67 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
} }
}, },
"node_modules/normalize-wheel-es": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
"integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==",
"license": "BSD-3-Clause"
},
"node_modules/perfect-debounce": {
"version": "1.0.0",
"license": "MIT"
},
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"license": "ISC", "license": "ISC",
"peer": true "peer": true
}, },
"node_modules/pinia": {
"version": "3.0.3",
"license": "MIT",
"dependencies": {
"@vue/devtools-api": "^7.7.2"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"typescript": ">=4.4.4",
"vue": "^2.7.0 || ^3.5.11"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/pinia-plugin-persistedstate": {
"version": "4.5.0",
"license": "MIT",
"dependencies": {
"deep-pick-omit": "^1.2.1",
"defu": "^6.1.4",
"destr": "^2.0.5"
},
"peerDependencies": {
"@nuxt/kit": ">=3.0.0",
"@pinia/nuxt": ">=0.10.0",
"pinia": ">=3.0.0"
},
"peerDependenciesMeta": {
"@nuxt/kit": {
"optional": true
},
"@pinia/nuxt": {
"optional": true
},
"pinia": {
"optional": true
}
}
},
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.5.6", "version": "8.5.6",
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -316,19 +648,36 @@
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/rfdc": {
"version": "1.4.1",
"license": "MIT"
},
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/speakingurl": {
"version": "14.0.1",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/superjson": {
"version": "2.2.3",
"license": "MIT",
"dependencies": {
"copy-anything": "^4"
},
"engines": {
"node": ">=16"
}
},
"node_modules/vue": { "node_modules/vue": {
"version": "3.5.22", "version": "3.5.22",
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.22.tgz",
"integrity": "sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
@ -366,6 +715,12 @@
"peerDependencies": { "peerDependencies": {
"vue": "^3.0.0" "vue": "^3.0.0"
} }
},
"node_modules/vue-i18n/node_modules/@vue/devtools-api": {
"version": "6.6.4",
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
"license": "MIT"
} }
} }
} }

10
package.json

@ -1,5 +1,13 @@
{ {
"dependencies": { "dependencies": {
"vue-i18n": "^9.14.5"
"@dcloudio/uni-ui": "^1.5.11",
"@element-plus/icons-vue": "^2.3.2",
"element-plus": "^2.11.5",
"vue-i18n": "^9.14.5",
"marked": "^2.0.1",
"pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.5.0"
} }
} }

272
pages.json

@ -1,5 +1,93 @@
{ {
"pages": [
"easycom": {
"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue"
},
"pages": [{
"path": "pages/start/startup/startup",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#000000",
"navigationBarTextStyle": "white",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{
"path": "pages/start/select/select",
"style": {
"navigationBarBackgroundColor": "#000000",
"navigationBarTitleText": "",
"animationType": "pop-in",
"navigationStyle": "custom",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{
"path": "pages/start/index/index",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "uni-app",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{
"path": "pages/start/Registration/Registration",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{
"path": "pages/start/login/login",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{
"path": "pages/start/agreement/agreement",
"style": {
"navigationBarTitleText": "",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{
"path": "pages/start/privacy/privacy",
"style": {
"navigationBarTitleText": "",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{
"path": "pages/deepMate/deepMate",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{ {
"path": "pages/home/home", "path": "pages/home/home",
"style": { "style": {
@ -10,7 +98,33 @@
} }
}, },
{ {
"path": "pages/home/marketSituation",
"path": "pages/marketSituation/marketSituation",
"style": {
"navigationStyle": "custom",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{
"path": "pages/marketSituation/chartExample",
"style": {
"navigationBarTitleText": "图表示例",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/marketSituation/globalIndex",
"style": {
"navigationStyle": "custom",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{
"path": "pages/marketSituation/marketDetail",
"style": { "style": {
"navigationStyle": "custom", "navigationStyle": "custom",
"disableSwipeBack": true, "disableSwipeBack": true,
@ -28,7 +142,7 @@
} }
}, },
{ {
"path": "pages/home/deepExploration",
"path": "pages/deepExploration/deepExploration",
"style": { "style": {
"navigationStyle": "custom", "navigationStyle": "custom",
"disableSwipeBack": true, "disableSwipeBack": true,
@ -46,15 +160,19 @@
} }
}, },
{ {
"path" : "pages/morningMarketAnalysis/morningMarketAnalysis",
"style" :
{
"navigationBarTitleText" : ""
"path": "pages/blank/institutionalTrendsBriefing",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
} }
}, },
{ {
"path": "pages/home/globalIndex",
"path": "pages/blank/notice",
"style": { "style": {
"navigationBarTitleText": "",
"navigationStyle": "custom", "navigationStyle": "custom",
"disableSwipeBack": true, "disableSwipeBack": true,
"titleNView": false, "titleNView": false,
@ -62,7 +180,130 @@
} }
}, },
{ {
"path": "pages/home/marketDetail",
"path" : "pages/start/recoverPassword/recoverPassword",
"style" :
{
"navigationBarTitleText": "",
"navigationStyle": "custom",
"titleNView": false
}
},
{
"path": "pages/setting/general",
"style": {
"navigationBarTitleText": "通用设置"
}
},
{
"path": "pages/setting/font",
"style": {
"navigationBarTitleText": "字体大小"
}
},
{
"path": "pages/setting/theme",
"style": {
"navigationBarTitleText": "主题切换"
}
},
{
"path": "pages/setting/message",
"style": {
"navigationBarTitleText": "消息推送"
}
},
{
"path": "pages/setting/push",
"style": {
"navigationBarTitleText": "推送设置"
}
},
{
"path": "pages/setting/server",
"style": {
"navigationBarTitleText": "选择服务器"
}
},
{
"path": "pages/setting/market",
"style": {
"navigationBarTitleText": "行情设置"
}
},
{
"path": "pages/setting/account",
"style": {
"navigationBarTitleText": "账号与安全"
}
},
{
"path": "pages/setting/newVersion",
"style": {
"navigationBarTitleText": "新版本更新"
}
},
{
"path": "pages/setting/about",
"style": {
"navigationBarTitleText": "关于DeepChart"
}
},
{
"path": "pages/setting/introduce",
"style": {
"navigationBarTitleText": "产品介绍"
}
},
{
"path": "pages/setting/bind",
"style": {
"navigationBarTitleText": "绑定账号"
}
},
{
"path": "pages/setting/phone",
"style": {
"navigationBarTitleText": "绑定账号"
//
}
},
{
"path": "pages/setting/email",
"style": {
"navigationBarTitleText": "绑定账号"
//
}
},
{
"path": "pages/setting/password",
"style":
{
"navigationBarTitleText": "修改密码"
}
},
{
"path" : "pages/setting/nextPwd",
"style" :
{
"navigationBarTitleText" : "修改密码"
}
},
{
"path" : "pages/setting/share",
"style" :
{
"navigationBarTitleText" : "分享领取奖励"
}
},
{
"path" : "pages/marketSituation/marketCondition",
"style" :
{
"navigationBarTitleText" : "行情",
"navigationStyle": "custom"
}
},{
"path": "pages/deepExploration/MainForceActions",
"style": { "style": {
"navigationStyle": "custom", "navigationStyle": "custom",
"disableSwipeBack": true, "disableSwipeBack": true,
@ -71,6 +312,13 @@
} }
}, },
{ {
"path" : "pages/deepExploration/stockSelectDetail",
"style" :
{
"navigationBarTitleText": "选股策略"
}
},
{
"path": "pages/customerServicePlatform/csPlatformIndex", "path": "pages/customerServicePlatform/csPlatformIndex",
"style": { "style": {
"navigationStyle": "custom", "navigationStyle": "custom",
@ -98,5 +346,11 @@
"app-plus": { "app-plus": {
"background": "#efeff4" "background": "#efeff4"
} }
},
"style": {
"app-plus": {
"animationType": "fade-in",
"animationDuration": 500
} }
} }
}

100
pages/blank/institutionalTrendsBriefing.vue

@ -0,0 +1,100 @@
<template>
<view class="blank-page">
<view class="header" :style="{ paddingTop: safeAreaInsets?.top + 'px' }">
<!-- 返回按钮 -->
<view class="head-left">
<image class="back-button" @click="goBack" src="/static/icons/Left_(左).png">
<!-- <text class="tip">当前特斯拉该如何布局</text> -->
</image>
</view>
<view class="header-center">
<text class="title" :style="{ paddingTop: safeAreaInsets?.top + 'px' }"
>机构动向解析</text
>
</view>
</view>
<image class="picture" src="/static/images/缺省.png" />
<text class="tip">暂无内容~</text>
</view>
</template>
<script setup>
// deepMate
const goBack = () => {
uni.navigateTo({
url: '/pages/deepMate/deepMate'
});
};
</script>
<style scoped>
.blank-page {
display: flex;
flex-direction: column;
position: fixed;
/* 充满视口,彻底禁用页面滚动 */
top: 0;
left: 0;
right: 0;
bottom: 0;
height: 100vh;
overflow: hidden;
/* 锁定页面滚动 */
background-color: #ffffff;
padding: 20rpx 0rpx;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 30rpx;
background-color: #ffffff;
box-shadow: 0 2rpx rgba(0, 0, 0, 0.1);
}
.head-left {
display: flex;
align-items: center;
}
.back-button {
width: 40rpx;
height: 40rpx;
}
.header-center .title {
position: fixed;
top: 25rpx;
left: 50%;
transform: translateX(-50%);
font-size: 36rpx;
font-weight: bold;
color: #333333;
}
.back-button:hover {
background-color: #e0e0e0;
}
.back-button:active {
transform: scale(0.95);
}
.back-icon {
font-size: 32rpx;
color: #333333;
margin-right: 10rpx;
}
.picture {
display: block;
margin: 200rpx auto 0; /* 图片水平居中 */
width: 60%;
height: 600rpx;
}
.tip {
color: #999999;
font-size: 28rpx;
text-align: center;
margin-top: 20rpx;
}
</style>

100
pages/blank/notice.vue

@ -0,0 +1,100 @@
<template>
<view class="blank-page">
<view class="header" :style="{ paddingTop: safeAreaInsets?.top + 'px' }">
<!-- 返回按钮 -->
<view class="head-left">
<image class="back-button" @click="goBack" src="/static/icons/Left_(左).png">
<!-- <text class="tip">当前特斯拉该如何布局</text> -->
</image>
</view>
<view class="header-center">
<text class="title" :style="{ paddingTop: safeAreaInsets?.top + 'px' }"
>消息推送通知</text
>
</view>
</view>
<image class="picture" src="/static/images/缺省.png" />
<text class="tip">暂无内容~</text>
</view>
</template>
<script setup>
// deepMate
const goBack = () => {
uni.navigateTo({
url: '/pages/deepMate/deepMate'
});
};
</script>
<style scoped>
.blank-page {
display: flex;
flex-direction: column;
position: fixed;
/* 充满视口,彻底禁用页面滚动 */
top: 0;
left: 0;
right: 0;
bottom: 0;
height: 100vh;
overflow: hidden;
/* 锁定页面滚动 */
background-color: #ffffff;
padding: 20rpx 0rpx;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 30rpx;
background-color: #ffffff;
box-shadow: 0 2rpx rgba(0, 0, 0, 0.1);
}
.head-left {
display: flex;
align-items: center;
}
.back-button {
width: 40rpx;
height: 40rpx;
}
.header-center .title {
position: fixed;
top: 25rpx;
left: 50%;
transform: translateX(-50%);
font-size: 36rpx;
font-weight: bold;
color: #333333;
}
.back-button:hover {
background-color: #e0e0e0;
}
.back-button:active {
transform: scale(0.95);
}
.back-icon {
font-size: 32rpx;
color: #333333;
margin-right: 10rpx;
}
.picture {
display: block;
margin: 200rpx auto 0; /* 图片水平居中 */
width: 60%;
height: 600rpx;
}
.tip {
color: #999999;
font-size: 28rpx;
text-align: center;
margin-top: 20rpx;
}
</style>

801
pages/deepExploration/MainForceActions.vue

@ -0,0 +1,801 @@
<template>
<view class="main">
<!-- 顶部状态栏占位 -->
<view class="top" :style="{height:iSMT+'px'}"></view>
<deepExploration_header></deepExploration_header>
<view class="search">
<input v-model="searchName" class="searchInput" type="text" placeholder="请输入股票名称、股票代码"
placeholder-style="color: #A6A6A6; font-size: 22rpx;" />
<image @click="searchStock" class="seachIcon" src="/static/deepExploration-images/search.png"
mode="aspectFill"></image>
</view>
<view class="content">
<view class="select">
<image class="img" :src="navItems[currentIndex].icon" mode=""></image>
<view v-for="(item, index) in navItems" :key="index" class="selectItem"
:class="{ active: currentIndex === index }" @click="handleModel(index)">
<button class="btn"></button>
</view>
</view>
<view class="graphAndTxt">
<view class="graph">
<view class="graph_header">
<view class="left">TESA</view>
<view class="center">
<image class="last" src="/static/deepExploration-images/last.png" mode="aspectFill"></image>
<text>Taewlkj.sejssssssssf</text>
<image class="next" src="/static/deepExploration-images/next.png" mode="aspectFill"></image>
</view>
<view class="right">2025/10/26</view>
</view>
<view class="graph_data">
<text>435.900</text>
<text>22.410</text>
<text>5.120%</text>
</view>
<view class="graph_content">
<view class="charts-box">
<!-- uCharts 蜡烛图组件 -->
<qiun-data-charts type="candle" :opts="opts" :chartData="chartData" :disableScroll="true"
:ontouch="true" :onzoom="true" />
</view>
</view>
</view>
<view class="txt">
<view class="txtHeader">
<image src="/static/deepExploration-images/plus.png" mode="aspectFill"></image>
<text>主力追踪</text>
</view>
<view class="txtContent"></view>
</view>
</view>
</view>
<!-- 底部切换栏 -->
<footerBar class="static-footer" :type="type"></footerBar>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
import deepExploration_header from '@/components/deepExploration_header.vue'
import footerBar from '@/components/footerBar.vue'
import {
onLoad
} from '@dcloudio/uni-app'
import {
getModel1First,
getModel1Second,
getModel2First,
getModel2Second,
getModel3First,
getModel3Second,
getModel4First,
getModel4Second,
} from '/api/deepExploration/deepExploration.js'
//
const type = ref('deepExploration')
const iSMT = ref(0)
const currentIndex = ref(0)
const navItems = ref([{
name: '主力追踪',
icon: '/static/deepExploration-images/1.png'
},
{
name: '主力雷达',
icon: '/static/deepExploration-images/2.png'
},
{
name: '主力解码',
icon: '/static/deepExploration-images/3.png'
},
{
name: '主力资金流',
icon: '/static/deepExploration-images/4.png'
},
])
//
const searchStock = () => {
console.log('搜索参数:', stockName.value);
if (currentIndex.value == 0) {
handleModel(0)
} else if (currentIndex.value == 1) {
console.log(index);
handleModel(1)
} else if (currentIndex.value == 2) {
console.log(index);
handleModel(2)
} else if (currentIndex.value == 3) {
console.log(index);
handleModel(3)
}else{
uni.showToast({
title: '请选择模块',
icon: 'none',
duration: 2000
})
}
}
//
const handleModel = (index) => {
currentIndex.value = index
if (currentIndex.value == 0) {
console.log(index);
handleTrack()
} else if (currentIndex.value == 1) {
console.log(index);
handleRadar()
} else if (currentIndex.value == 2) {
console.log(index);
handleDecode()
} else if (currentIndex.value == 3) {
console.log(index);
handleCapitalFlow()
}
}
const stockName = ref('')
const searchName = ref('')
const stockCode = ref('')
const language = ref('')
const recordId = ref('')
const parentId = ref('')
const stockId = ref('')
//
const handleTrack = async () => {
try {
const result = await getModel1First({
content: searchName.value,
language: "cn",
marketList: "hk,cn,usa,my,sg,vi,in,gb",
model: 1
})
console.log('result', result);
if (result.code == 200) {
stockCode.value = result.data.code
stockName.value = result.data.name
recordId.value = result.data.recordId
parentId.value = result.data.parentId
stockId.value = result.data.stockId
language.value = result.data.language
const res = await getModel1Second({
language: language.value,
recordId: recordId.value,
parentId: parentId.value,
stockId: stockId.value
})
console.log('res', res);
}
} catch {
}
}
//
const handleRadar = () => {
}
//
const handleDecode = () => {
}
//
const handleCapitalFlow = () => {
}
// 1. K线
const opts = ref({
rotate: false,
rotateLock: false,
color: ["#1890FF", "#91CB74", "#FAC858", "#EE6666", "#73C0DE", "#3CA272", "#FC8452", "#9A60B4", "#ea7ccc"],
padding: [15, 15, 0, 15],
dataLabel: false,
enableScroll: true,
enableMarkLine: false,
legend: {},
xAxis: {
labelCount: 4,
itemCount: 30,
disableGrid: true,
gridColor: "#CCCCCC",
gridType: "solid",
dashLength: 4,
scrollShow: false,
scrollAlign: "left",
scrollColor: "#A6A6A6",
scrollBackgroundColor: "#EFEBEF",
labelColor: "#8C8C8C",
fontSize: 9
},
yAxis: {
labelColor: "#8C8C8C",
fontSize: 9
},
extra: {
candle: {
color: {
upLine: "#f04864",
upFill: "#f04864",
downLine: "#2fc25b",
downFill: "#2fc25b"
},
average: {
show: false,
name: ["MA5", "MA10", "MA30"],
day: [5, 10, 20],
color: ["#1890ff", "#2fc25b", "#facc14"]
}
},
markLine: {
type: "dash",
dashLength: 5,
data: [{
value: 2150,
lineColor: "#f04864",
showLabel: false
},
{
value: 2350,
lineColor: "#f04864",
showLabel: false
}
]
},
tooltip: {
showCategory: true
}
}
})
// 2. K线
const chartData = ref({})
// K线methods
const getServerData = () => {
//
setTimeout(() => {
const res = {
"categories": [
"2025/08/25",
"2025/08/26",
"2025/08/27",
"2025/08/28",
"2025/08/29",
"2025/09/01",
"2025/09/02",
"2025/09/03",
"2025/09/04",
"2025/09/05",
"2025/09/08",
"2025/09/09",
"2025/09/10",
"2025/09/11",
"2025/09/12",
"2025/09/15",
"2025/09/16",
"2025/09/17",
"2025/09/18",
"2025/09/19",
"2025/09/22",
"2025/09/23",
"2025/09/24",
"2025/09/25",
"2025/09/26",
"2025/09/29",
"2025/09/30",
"2025/10/09",
"2025/10/10",
"2025/10/13",
"2025/10/14",
"2025/10/15",
"2025/10/16",
"2025/10/17",
"2025/10/20",
"2025/10/21",
"2025/10/22",
"2025/10/23",
"2025/10/24",
"2025/10/27"
],
series: [{
"name": "贵州茅台",
"data": [
[
1470.01,
1496.0,
1466.0,
1499.33
],
[
1490.32,
1474.23,
1480.01,
1481.61
],
[
1481.88,
1484.93,
1448.0,
1448.0
],
[
1447.97,
1456.1,
1438.77,
1446.1
],
[
1453.0,
1482.58,
1452.0,
1480.0
],
[
1482.2,
1488.0,
1465.7,
1476.1
],
[
1478.66,
1509.0,
1478.0,
1491.3
],
[
1491.0,
1503.5,
1466.0,
1480.55
],
[
1472.0,
1479.3,
1460.47,
1480.66
],
[
1471.0,
1486.97,
1464.0,
1483.0
],
[
1483.0,
1506.44,
1477.5,
1501.23
],
[
1505.0,
1509.95,
1493.42,
1505.0
],
[
1506.66,
1529.95,
1496.0,
1522.01
],
[
1522.01,
1526.02,
1508.5,
1523.5
],
[
1526.0,
1538.02,
1510.53,
1516.0
],
[
1515.87,
1517.48,
1501.5,
1515.1
],
[
1515.1,
1520.99,
1496.21,
1499.98
],
[
1499.99,
1510.28,
1490.01,
1493.0
],
[
1492.0,
1497.8,
1463.5,
1467.96
],
[
1467.99,
1475.5,
1457.01,
1467.97
],
[
1465.09,
1467.97,
1450.01,
1453.35
],
[
1450.5,
1457.5,
1440.0,
1447.42
],
[
1434.07,
1456.78,
1434.07,
1442.0
],
[
1442.83,
1445.21,
1436.0,
1439.0
],
[
1441.18,
1447.11,
1428.01,
1435.0
],
[
1439.38,
1469.99,
1435.0,
1460.86
],
[
1460.0,
1460.76,
1440.0,
1443.99
],
[
1436.0,
1439.38,
1420.0,
1436.78
],
[
1437.6,
1439.94,
1427.5,
1430.0
],
[
1415.7,
1422.85,
1415.12,
1419.2
],
[
1429.99,
1464.0,
1429.99,
1451.02
],
[
1450.98,
1463.0,
1445.08,
1462.0
],
[
1461.92,
1484.95,
1458.88,
1484.91
],
[
1483.1,
1488.0,
1454.03,
1455.0
],
[
1455.0,
1469.5,
1454.88,
1457.93
],
[
1459.0,
1469.94,
1455.5,
1462.26
],
[
1462.08,
1465.73,
1456.0,
1458.7
],
[
1455.0,
1468.8,
1447.2,
1467.98
],
[
1467.95,
1478.88,
1449.34,
1450.0
],
[
1440.0,
1452.49,
1435.99,
1440.41
]
],
}]
}
// .value
chartData.value = JSON.parse(JSON.stringify(res))
}, 500)
}
// onReady
onMounted(() => {
iSMT.value = uni.getSystemInfoSync().statusBarHeight
getServerData() //
})
//
onLoad((e) => {
if (e.index) {
currentIndex.value = e.index - 1
console.log('模块:', currentIndex.value)
}
if (e.stockName) {
stockName.value = e.stockName
console.log('股票名称:', stockName.value)
}
})
</script>
<style scoped lang="scss">
.main {
width: 100%;
height: 100vh;
background-color: #fff;
.search {
position: relative;
display: flex;
align-items: center;
background-color: #F3F3F3;
width: calc(100% - 60rpx);
height: 80rpx;
border-radius: 50rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
padding: 0 40rpx;
margin: 15rpx 30rpx 0 30rpx;
.seachIcon {
position: absolute;
right: 50rpx;
width: 32rpx;
height: 32rpx;
}
.searchInput {
color: #111;
}
}
.content {
margin-top: 30rpx;
padding-top: 30rpx;
background-color: rgb(248, 248, 248);
.select {
position: relative;
margin-bottom: -5rpx;
.img {
width: 750rpx;
height: 198rpx;
}
.selectItem {
.btn {
position: absolute;
width: 120rpx;
height: 150rpx;
background-color: transparent;
&::after {
border: none;
}
}
&:nth-of-type(1) .btn {
top: 30rpx;
left: 60rpx;
}
&:nth-of-type(2) .btn {
top: 30rpx;
left: 230rpx;
}
&:nth-of-type(3) .btn {
top: 30rpx;
left: 400rpx;
}
&:nth-of-type(4) .btn {
top: 30rpx;
left: 570rpx;
}
}
}
.graphAndTxt {
height: 300rpx;
background-color: #fff;
border-radius: 50rpx 50rpx 0 0;
padding: 68.6rpx 36.5rpx 0 36.5rpx;
.graph {
border: 1rpx solid #e2e2e2;
border-radius: 30rpx 30rpx 0 0;
.graph_header {
padding: 32rpx 20.5rpx 0 24rpx;
display: flex;
align-items: center;
.left {
color: #333333;
font-family: "PingFang SC";
font-size: 15px;
font-style: normal;
font-weight: 400;
line-height: 15px;
}
.center {
margin-left: 105rpx;
display: flex;
align-items: center;
text {
width: 160rpx;
height: 36rpx;
padding-left: 10rpx;
color: #000000;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
font-family: "PingFang SC";
font-size: 18px;
font-style: normal;
font-weight: 500;
line-height: 18px;
}
.last {
width: 15rpx;
height: 20rpx;
margin-right: 30rpx;
}
.next {
width: 15rpx;
height: 20rpx;
margin-left: 30rpx;
}
}
.right {
margin-left: 50rpx;
color: #6a6a6a;
font-family: "PingFang SC";
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 15px;
}
}
.graph_data {
display: flex;
padding: 48rpx 24rpx;
text {
display: flex;
color: #25ba5d;
font-family: "PingFang SC";
font-size: 17px;
line-height: 17px;
}
text:nth-child(2) {
margin-left: 120rpx;
}
text:nth-child(3) {
margin-left: 150rpx;
}
}
.graph_content {
.charts-box {
width: 100%;
height: 100%;
}
}
}
.txt {
background-color: #F3F3F3;
margin-top: 48rpx;
border-radius: 30rpx;
.txtHeader {
padding: 30rpx 24rpx;
image {
width: 20rpx;
height: 20rpx;
}
text {
background-color: #FFFFFF;
color: #000000;
padding: 0 22rpx;
border-radius: 22rpx;
font-size: 28rpx;
font-weight: 400;
line-height: 37rpx;
}
}
.txtContent {
min-height: 200rpx;
}
}
}
}
.static-footer {
position: fixed;
bottom: 0;
width: 100%;
}
}
* {
box-sizing: border-box;
}
</style>

427
pages/deepExploration/deepExploration.vue

@ -0,0 +1,427 @@
<template>
<view class="main">
<!-- 顶部状态栏占位 -->
<view class="top" :style="{height:iSMT+'px'}"></view>
<!-- 标题图标部分 -->
<deepExploration_header></deepExploration_header>
<view class="search">
<input v-model="stockName" class="searchInput" type="text" placeholder="请输入股票名称、股票代码"
placeholder-style="color: #A6A6A6; font-size: 22rpx;" />
<image @click="searchStock" class="seachIcon" src="/static/deepExploration-images/search.png"
mode="aspectFill"></image>
</view>
<!-- 四大功能模块 -->
<view class="select">
<view class="selectItem" @click="toMain('主力追踪')">
<image class="img" src="/static/deepExploration-images/icon3.png" mode="aspectFill"></image>
<view class="txt">主力追踪</view>
</view>
<view class="selectItem" @click="toMain('主力雷达')">
<image class="img" src="/static/deepExploration-images/icon2.png" mode="aspectFill"></image>
<view class="txt">主力雷达</view>
</view>
<view class="selectItem" @click="toMain('主力解码')">
<image class="img" src="/static/deepExploration-images/icon1.png" mode="aspectFill"></image>
<view class="txt">主力解码</view>
</view>
<view class="selectItem" @click="toMain('主力资金流')">
<image class="img" src="/static/deepExploration-images/icon4.png" mode="aspectFill"></image>
<view class="txt">主力资金流</view>
</view>
</view>
<!-- 灰色间隔 -->
<view class="gap"></view>
<!-- 选股策略 -->
<view class="stockSelection">
<view class="stockSelection_top">
<view class="txt">
<text>选股策略</text>
</view>
<view class="viewAll" @click='viewAll'>
<text>查看全部</text>
</view>
</view>
<view class="stockSelection_content">
<view class="selectionItem">
<view class="header">
<view class="left">
<image src="/static/deepExploration-images/plus.png" mode="aspectFill"></image>
<text>抄底卖顶</text>
</view>
<view class="right">
<image src="/static/deepExploration-images/Americle.png" mode="aspectFill"></image>
<text>美股</text>
</view>
</view>
<view class="content">
<view class="contentTitle">
<view class="contentTitle_name">股票名称</view>
<view class="contentTitle_close">最新收盘价</view>
<view class="contentTitle_price">选股价格</view>
</view>
<view class="contentItem">
<view class="row" v-for="(item,index) in stockData" :key="index">
<view class="nameItem">{{item.name}}</view>
<view class="closeItem">{{item.close}}</view>
<view class="priceItem">{{item.select}}</view>
</view>
</view>
</view>
</view>
</view>
<view class="stockSelection_content">
<view class="selectionItem">
<view class="header">
<view class="left">
<image src="/static/deepExploration-images/plus.png" mode="aspectFill"></image>
<text>抄底卖顶</text>
</view>
<view class="right">
<image src="/static/deepExploration-images/Americle.png" mode="aspectFill"></image>
<text>美股</text>
</view>
</view>
<view class="content">
<view class="contentTitle">
<view class="contentTitle_name">股票名称</view>
<view class="contentTitle_close">最新收盘价</view>
<view class="contentTitle_price">选股价格</view>
</view>
<view class="contentItem">
<view class="row" v-for="(item,index) in stockData" :key="index">
<view class="nameItem">{{item.name}}</view>
<view class="closeItem">{{item.close}}</view>
<view class="priceItem">{{item.select}}</view>
</view>
</view>
</view>
</view>
</view>
</view>
<footerBar class="static-footer" :type="type"></footerBar>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
import footerBar from '@/components/footerBar.vue'
import deepExploration_header from '@/components/deepExploration_header.vue'
const type = ref('deepExploration')
const iSMT = ref(0)
//
const toMain = (val) => {
if (val == '主力追踪') {
uni.navigateTo({
url: '/pages/deepExploration/MainForceActions?index=1'
})
} else if (val == '主力雷达') {
uni.navigateTo({
url: '/pages/deepExploration/MainForceActions?index=2'
})
} else if (val == '主力解码') {
uni.navigateTo({
url: '/pages/deepExploration/MainForceActions?index=3'
})
} else if (val == '主力资金流') {
uni.navigateTo({
url: '/pages/deepExploration/MainForceActions?index=4'
})
}
}
const stockName = ref('')
//
const searchStock = () => {
console.log('搜索参数:', stockName.value);
uni.navigateTo({
url: `/pages/deepExploration/MainForceActions?stockName=${stockName.value}`
})
}
//
const viewAll = () => {
uni.navigateTo({
url: '/pages/deepExploration/stockSelectDetail'
})
}
//
const stockData = [{
name: "(MKTW)MarketWise Inc",
close: "$14.190",
select: "$13.180"
},
{
name: "(MTCH)Match Group Inc",
close: "$32.120",
select: "$28.120"
},
{
name: "(MKTW)MarketWise Inc",
close: "$14.190",
select: "$13.180"
}
];
onMounted(() => {
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
})
</script>
<style scoped lang="scss">
.main {
width: 100%;
height: 100vh;
background-color: #fff;
.search {
position: relative;
display: flex;
align-items: center;
background-color: #F3F3F3;
width: calc(100% - 60rpx);
height: 80rpx;
border-radius: 50rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
padding: 0 40rpx;
margin: 15rpx 30rpx 0 30rpx;
.seachIcon {
position: absolute;
right: 50rpx;
width: 32rpx;
height: 32rpx;
}
.searchInput {
color: #111;
}
}
.select {
display: flex;
padding: 60rpx 10rpx 30rpx 30rpx;
gap: 70rpx;
align-items: center;
justify-content: center;
.selectItem {
.img {
width: 80rpx;
height: 80rpx;
display: block;
margin: 0 auto;
}
.txt {
color: #6a6a6a;
font-family: "PingFang SC";
font-size: 11px;
font-style: normal;
font-weight: 400;
line-height: 14.5px;
margin-top: 13rpx;
white-space: nowrap;
}
}
}
.gap {
width: 100%;
height: 15rpx;
background-color: #F3F3F3;
}
.stockSelection {
width: 100%;
padding: 32rpx 15rpx;
.stockSelection_top {
display: flex;
justify-content: space-between;
.txt {
color: #000000;
font-family: "PingFang SC";
font-size: 38rpx;
font-style: normal;
font-weight: 400;
line-height: 50rpx;
}
.viewAll {
background-color: #000000;
border-radius: 10rpx;
padding: 6rpx 20rpx;
color: #ffffff;
font-family: "PingFang SC";
font-size: 10rpx;
font-style: normal;
font-weight: 100;
line-height: 29rpx;
height: 40rpx;
}
}
.stockSelection_content {
.selectionItem {
background-color: #F3F3F3;
padding: 30rpx 15rpx 17rpx 30rpx;
border-radius: 30rpx;
margin-top: 30rpx;
.header {
display: flex;
justify-content: space-between;
align-items: center;
.left {
display: flex;
justify-content: space-between;
align-items: center;
image {
display: flex;
justify-content: center;
align-items: center;
width: 15rpx;
height: 15rpx;
}
text {
margin-left: 15rpx;
color: #000000;
font-family: "PingFang SC";
font-size: 28rpx;
font-style: normal;
font-weight: 400;
line-height: 18.5px;
}
}
.right {
display: flex;
justify-content: space-between;
align-items: center;
border-radius: 15rpx;
background-color: #FFFFFF;
padding: 6rpx 20rpx;
image {
display: flex;
justify-content: center;
align-items: center;
width: 40rpx;
height: 26.5rpx;
}
text {
margin-left: 10rpx;
color: #6a6a6a;
font-family: "PingFang SC";
font-size: 18rpx;
font-style: normal;
font-weight: 400;
line-height: 24rpx;
}
}
}
.content {
.contentTitle {
display: flex;
color: #6a6a6a;
font-family: "PingFang SC";
font-size: 11px;
font-style: normal;
font-weight: 400;
line-height: 14.5px;
margin-top: 24rpx;
margin-bottom: 20rpx;
.contentTitle_name {
width: 100rpx;
}
.contentTitle_close {
width: 130rpx;
margin-left: 260rpx;
}
.contentTitle_price {
width: 120rpx;
margin-left: 60rpx;
}
}
.contentItem {
.row {
display: flex;
box-shadow: 0 -2rpx 5rpx rgba(0, 0, 0, 0.05);
padding: 10rpx 0;
margin-bottom: 10rpx;
.nameItem {
width: 260rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #000000;
font-family: "PingFang SC";
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 17.5px;
}
.closeItem {
width: 120rpx;
margin-left: 100rpx;
color: #25ba5d;
font-family: "PingFang SC";
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 17.5px;
}
.priceItem {
width: 120rpx;
margin-left: 73rpx;
color: #25ba5d;
font-family: "PingFang SC";
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 17.5px;
}
}
}
}
}
}
}
.static-footer {
position: fixed;
bottom: 0;
}
}
* {
box-sizing: border-box;
}
</style>

354
pages/deepExploration/stockSelectDetail.vue

@ -0,0 +1,354 @@
<template>
<view class="main">
<view class="table">
<view class="tableHeader">
<scroll-view class="tabs" scroll-x="true">
<view v-for="(item,index) in tabsData" :key="index" class="tabItem" @click="handleTab(item)">
{{item}}
</view>
</scroll-view>
</view>
<view class="tableContent">
<image class="showAll" src="/static/deepExploration-images/showAll.png" mode="aspectFill"></image>
<scroll-view scroll-x="true" show-scrollbar="false">
<view class="tableBox">
<view class="box_header">
<view class="name">名称</view>
<view class="other" v-for="(item,index) in tableContentHeaderData" :key="index">
<text>{{item}}</text>
<image v-show="ifASC" src="/static/deepExploration-images/ASC.png" mode="aspectFill">
</image>
<image v-show="!ifASC" src="/static/deepExploration-images/DESC.png" mode="aspectFill">
</image>
</view>
</view>
<view class="box_content">
<view class="row" v-for="(item,index) in fakeData" :key="index" :class="{ 'increase-positive': item.increase.startsWith('+'),
'increase-negative': item.increase.startsWith('-')}">
<view class="name_colum">
<text class="stockName">{{item.name}}</text>
<text class="stockCode">{{item.stockCode}}</text>
</view>
<view class="other_colum">{{item.latest}}</view>
<view class="other_colum">{{item.increase}}</view>
<view class="other_colum">{{item.decrease}}</view>
<view class="other_colum">{{item.previousClose}}</view>
<view class="other_colum">{{item.volume}}</view>
<view class="other_colum">{{item.turnover}}</view>
<view class="other_colum">{{item.openingPrice}}</view>
<view class="other_colum">{{item.highestPrice}}</view>
<view class="other_colum">{{item.lowestPrice}}</view>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script setup>
import {
ref
} from 'vue'
const tabsData = ref(['全部', '抄底卖顶', '波段行情', '价值投资', '资金及仓位管理', '价值投资', '价值投资', '价值投资', ])
const handleTab = (item) => {
uni.showToast({
title: `查看 ${item} 详情`,
icon: 'none',
duration: 2000
})
}
//
const ifASC = ref(true)
//
const tableContentHeaderData = ref(['最新', '涨幅', '跌幅', '昨收', '成交量', '成交额', '开盘价', '最高价', '最低价'])
const fakeData = [{
name: "TechCore",
stockCode: "600001",
latest: 1315.00,
increase: "+5.2%",
decrease: "+5.2%",
previousClose: 1250.00,
volume: 12000,
turnover: "15780K",
openingPrice: 1237.50,
highestPrice: 1320.00,
lowestPrice: 1230.00
},
{
name: "MediaLink",
stockCode: "600002",
latest: 1138.70,
increase: "-3.5%",
decrease: "-3.5%",
previousClose: 1180.00,
volume: 8500,
turnover: "967.9K",
openingPrice: 1191.80,
highestPrice: 1195.00,
lowestPrice: 1130.00
},
{
name: "FinServ",
stockCode: "600003",
latest: 1413.72,
increase: "+7.1%",
decrease: "+7.1%",
previousClose: 1320.00,
volume: 15000,
turnover: "2120.6K",
openingPrice: 1293.60,
highestPrice: 1420.00,
lowestPrice: 1290.00
},
{
name: "AutoDrive",
stockCode: "600004",
latest: 1080.40,
increase: "+2.8%",
decrease: "+2.8%",
previousClose: 1050.00,
volume: 9000,
turnover: "972.4K",
openingPrice: 1055.25,
highestPrice: 1085.00,
lowestPrice: 1050.00
},
{
name: "EduSmart",
stockCode: "600005",
latest: 968.24,
increase: "-1.2%",
decrease: "-1.2%",
previousClose: 980.00,
volume: 7000,
turnover: "677.8K",
openingPrice: 975.10,
highestPrice: 978.00,
lowestPrice: 965.00
},
{
name: "HealthPlusqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq",
stockCode: "600006",
latest: 1463.00,
increase: "+4.5%",
decrease: "+4.5%",
previousClose: 1400.00,
volume: 13000,
turnover: "1901.9K",
openingPrice: 1393.00,
highestPrice: 1470.00,
lowestPrice: 1385.00
},
{
name: "AgriTech",
stockCode: "600007",
latest: 1038.36,
increase: "+1.8%",
decrease: "+1.8%",
previousClose: 1020.00,
volume: 6500,
turnover: "674.9K",
openingPrice: 1028.16,
highestPrice: 1040.00,
lowestPrice: 1025.00
},
{
name: "LogiFlow",
stockCode: "600008",
latest: 1094.24,
increase: "-2.3%",
decrease: "-2.3%",
previousClose: 1120.00,
volume: 8000,
turnover: "875.4K",
openingPrice: 1122.24,
highestPrice: 1125.00,
lowestPrice: 1090.00
},
{
name: "EnergySol",
stockCode: "600009",
latest: 1435.05,
increase: "+6.3%",
decrease: "+6.3%",
previousClose: 1350.00,
volume: 14000,
turnover: "2009.1K",
openingPrice: 1339.75,
highestPrice: 1440.00,
lowestPrice: 1335.00
},
{
name: "RealEstate",
stockCode: "600010",
latest: 995.00,
increase: "-0.5%",
decrease: "-0.5%",
previousClose: 1000.00,
volume: 7500,
turnover: "746.3K",
openingPrice: 1003.00,
highestPrice: 1005.00,
lowestPrice: 990.00
}
];
</script>
<style scoped lang="scss">
.main {
width: 100%;
height: 100vh;
background-color: #fff;
.table {
margin-top: 10rpx;
box-shadow: 0 -2rpx 3rpx -1rpx rgba(0, 0, 0, 0.5);
.tableHeader {
.tabs {
white-space: nowrap;
padding-top: 20rpx;
padding-left: 40rpx;
::-webkit-scrollbar {
//
display: none;
}
.tabItem {
display: inline-block;
color: rgb(175, 175, 175);
border-radius: 10rpx;
padding: 5rpx 30rpx;
margin-right: 20rpx;
font-size: 28rpx;
background-color: rgb(243, 243, 243);
}
}
}
.tableContent {
width: 100%;
position: relative;
.showAll {
position: absolute;
top: 35rpx;
right: 20rpx;
width: 40rpx;
height: 40rpx;
z-index: 100;
}
scroll-view {
width: 100%;
white-space: nowrap;
::-webkit-scrollbar {
//
display: none;
}
}
.tableBox {
padding-left: 40rpx;
.box_header {
margin-bottom: 19rpx;
display: flex;
width: max-content;
margin-top: 40rpx;
color: rgb(109, 109, 109);
border-radius: 10rpx;
margin-right: 20rpx;
font-size: 23rpx;
.name {
flex: 0 0 375rpx;
}
.other {
flex: 0 0 195rpx;
text {
margin-right: 5rpx;
}
image {
width: 20rpx;
height: 20rpx;
}
}
}
.box_content {
width: max-content;
.row {
padding: 5rpx;
display: flex;
border-top: 1rpx dashed #eee;
width: 210%;
&.increase-positive {
.other_colum {
color: #2DD357;
font-weight: 200;
}
}
&.increase-negative {
.other_colum {
color: #FF4150;
font-weight: 200;
}
}
.name_colum {
flex: 0 0 375rpx;
display: flex;
flex-direction: column;
gap: 4rpx;
.stockName {
color: #333333;
width: 100%;
max-width: 305rpx;
font-size: 28rpx;
font-weight: 400;
line-height: 36rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.stockCode {
color: #c5c5c5;
;
font-size: 24rpx;
font-weight: 400;
line-height: 30rpx;
}
}
.other_colum {
flex: 0 0 195rpx;
display: flex;
align-items: center;
}
}
}
}
}
}
}
</style>

1733
pages/deepMate/deepMate.vue
File diff suppressed because it is too large
View File

28
pages/home/deepExploration.vue

@ -1,28 +0,0 @@
<template>
<view class="main">
<!-- 顶部状态栏占位 -->
<view class="top" :style="{height:iSMT+'px'}"></view>
<view>深度探索</view>
<footerBar class="static-footer" :type="type"></footerBar>
</view>
</template>
<script setup>
import { ref,onMounted } from 'vue'
import footerBar from '../../components/footerBar.vue'
const type = ref('deepExploration')
const iSMT = ref(0)
onMounted(() => {
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
})
</script>
<style scoped>
.static-footer {
position: fixed;
bottom: 0;
}
</style>

1214
pages/home/home.vue
File diff suppressed because it is too large
View File

290
pages/home/member.vue

@ -1,28 +1,294 @@
<template> <template>
<view class="main"> <view class="main">
<!-- 顶部状态栏占位 -->
<view class="top" :style="{height:iSMT+'px'}"></view>
<view>我的</view>
<view class="top">
<view class="bell">
<image class="image-bell" src="/static/my/bell.png"></image>
</view>
<view class="msg">
<view class="msg-left">
<view class="avatar"></view>
</view>
<view class="msg-center">
<view style="display: flex;">
<view class="userInfo">{{ username }}</view>
<image class="image-editName" src="/static/my/editName.png"></image>
</view>
<view class="userId">ID:{{ dccode }}</view>
</view>
<view class="msg-right">
<image class="image-attendance" src="/static/my/Check-in.png" />
<span style="font-size:10px;">签到</span>
</view>
</view>
<view class="settings-buttons">
<view class="setting-btn" @click="goToMarket">
<image src="/static/my/MarketSettings.png" class="setting-icon" />
<text>行情设置</text>
</view>
<view class="setting-btn" @click="goToGeneral">
<image src="/static/my/Settings.png" class="setting-icon" />
<text>通用设置</text>
</view>
</view>
<view class="share" @click="goToShare">
<image class="img-share" src="/static/my/share.png" mode="widthFix" />
</view>
</view>
<view class="bottom">
<view class="list-item" @click="goToAccount">
<image src="/static/my/security.png" class="list-icon" />
<text>账号与安全</text>
<uni-icons type="arrowright" size="16" class="arrow" />
</view>
<view class="list-item">
<image src="/static/my/connection.png" class="list-icon" />
<text>联系我们</text>
<uni-icons type="arrowright" size="16" class="arrow" />
</view>
<view class="list-item" @click="goToNewVersion">
<image src="/static/my/update.png" class="list-icon" />
<text>新版本更新</text>
<view class="update-tip">有新版本可更新
<view class="circle"></view>
</view>
<uni-icons type="arrowright" size="16" class="arrow" />
</view>
<view class="list-item">
<image src="/static/my/opinion.png" class="list-icon" />
<text>意见反馈</text>
<uni-icons type="arrowright" size="16" class="arrow" />
</view>
<view class="list-item" @click="goToAbout">
<image src="/static/my/about.png" class="list-icon" />
<text>关于DeepChart</text>
<uni-icons type="arrowright" size="16" class="arrow" />
</view>
</view>
<footerBar class="static-footer" :type="type"></footerBar> <footerBar class="static-footer" :type="type"></footerBar>
</view> </view>
</template> </template>
<script setup> <script setup>
import { ref,onMounted } from 'vue'
import footerBar from '../../components/footerBar.vue'
import {
ref,
onMounted
} from 'vue'
import {
ArrowRight
} from '@element-plus/icons-vue'
import footerBar from '../../components/footerBar.vue'
const type = ref('member')
const iSMT = ref(0)
const username = ref('演示机EVA')
const dccode = ref('90047681')
const goToGeneral = () => {
uni.navigateTo({
url: '/pages/setting/general'
})
}
const goToMarket = () => {
uni.navigateTo({
url: '../setting/market'
})
}
const goToAccount = () => {
uni.navigateTo({
url:'../setting/account'
})
}
const goToNewVersion = () =>{
uni.navigateTo({
url:'../setting/newVersion'
})
}
const goToAbout = () =>{
uni.navigateTo({
url:'../setting/about'
})
}
const type = ref('member')
const iSMT = ref(0)
const goToShare = () =>{
uni.navigateTo({
url:'../setting/share'
})
}
onMounted(() => {
onMounted(() => {
// //
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
})
iSMT.value = uni.getSystemInfoSync().statusBarHeight
console.log('??????????????', iSMT.value)
})
</script> </script>
<style scoped> <style scoped>
.static-footer {
.static-footer {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
}
}
.top {
height: 47vh;
background-color: white;
}
.bell {
height: 9.6vh;
display: flex;
align-items: flex-end;
justify-content: flex-end;
padding-right: 50rpx;
}
.image-bell {
width: 13px;
height: 16px;
}
.msg {
height: 10.7vh;
display: flex;
margin-top: 3vh;
margin-bottom: 3vh;
}
.msg-left {
width: 33.6vw;
display: flex;
justify-content: center;
align-items: center;
}
.avatar {
width: 175rpx;
height: 175rpx;
border-radius: 50%;
background-color: black;
}
.msg-center {
width: 51.7vw;
padding-left: 2.5vh;
display: flex;
flex-direction: column;
justify-content: center;
}
.userInfo {
font-size: 20px;
}
.userId {
font-size: 14px;
margin-top: 1vh;
}
.image-editName {
width: 40rpx;
height: 40rpx;
margin-left: 2vw;
}
.msg-right {
width: 14.7vw;
display: flex;
flex-direction: column;
justify-content: center;
}
.image-attendance {
width: 43rpx;
height: 43rpx;
}
.settings-buttons {
display: flex;
justify-content: space-around;
}
.setting-btn {
width: 349rpx;
height: 135rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: rgb(243, 243, 243);
border-radius: 8%;
}
.setting-icon {
width: 64.7rpx;
height: 64.7rpx;
margin-right: 25rpx;
}
.setting-btn text {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.share {
height: 12.6vh;
display: flex;
justify-content: center;
align-items: center;
}
.img-share {
width: 720rpx;
height: 160rpx;
}
.bottom {
height: 44.5vh;
margin-top: 1vh;
background-color: rgb(255, 255, 255);
}
.list-item {
width: 670rpx;
height: 7vh;
display: flex;
align-items: center;
margin: 0rpx 40rpx;
border-bottom: 1rpx solid #eee;
}
.list-item:last-child{
border-bottom: none;
}
.list-icon {
width: 42rpx;
height: 42rpx;
margin-right: 18rpx;
}
.arrow {
margin-left: auto;
}
.update-tip {
display: flex;
color: #999;
font-size: 24rpx;
align-items: center;
margin-left: 200rpx;
justify-content: center;
}
.circle {
width: 10rpx;
height: 10rpx;
border-radius: 50%;
background-color: red;
margin-left: 10rpx;
}
</style> </style>

655
pages/marketSituation/chartExample.vue

@ -0,0 +1,655 @@
<template>
<view style="width: 750rpx; height: 750rpx;">
<l-echart ref="chartRef" @finished="initChart"></l-echart>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
const chartRef = ref(null)
// window.innerWidth
const systemInfo = uni.getSystemInfoSync()
const screenWidth = ref(systemInfo.screenWidth || 375) // 375px
// 30
const generateAIGoldBullData = () => {
const data = []
for (let i = 0; i < 30; i++) {
// [, , , , , ]
const buySignal = Math.random() > 0.7 ? 1 : 0 // 30%
const sellSignal = Math.random() > 0.8 ? 1 : 0 // 20%
const holdSignal = Math.random() > 0.5 ? 1 : 0 // 50%
const strength = Math.floor(Math.random() * 3) + 1 // 1-3
const volume = Math.floor(Math.random() * 2000) + 500 // 500-2500
data.push([i, buySignal, sellSignal, holdSignal, strength, volume])
}
return data
}
//
var option
const AIGoldBull = ref({
JN: generateAIGoldBullData()
})
//
const t = ref({
suoxie: 'zh',
tianxian: '天线',
feixian: '飞线',
zhoongxian: '中线',
liuxian: '流线',
Klinetext_5: 'K线5',
Klinetext_6: 'K线6',
maipan: '买盘',
maipan1: '卖盘'
})
// 30K线 [, , , , ]
const generateKLineData = () => {
const data = []
let basePrice = 2450 //
for (let i = 0; i < 30; i++) {
const date = new Date(2024, 0, i + 1).toISOString().split('T')[0]
//
const volatility = (Math.random() - 0.5) * 50 // ±25
const open = basePrice + volatility
const highVolatility = Math.random() * 30 + 10 // 10-40
const lowVolatility = Math.random() * 30 + 10 // 10-40
const high = Math.max(open, open + highVolatility)
const low = Math.min(open, open - lowVolatility)
const closeVolatility = (Math.random() - 0.5) * 20
const close = Math.max(low, Math.min(high, open + closeVolatility))
data.push([date,
Math.round(open * 100) / 100,
Math.round(high * 100) / 100,
Math.round(low * 100) / 100,
Math.round(close * 100) / 100
])
basePrice = close //
}
return data
}
// 30 [, 1, 2, 1, 2, 3]
const generateWaveVolData = () => {
const data = []
for (let i = 0; i < 30; i++) {
const date = new Date(2024, 0, i + 1).toISOString().split('T')[0]
//
const vol1 = Math.floor(Math.random() * 2000) + 800 // 800-2800
const vol2 = Math.floor(Math.random() * 1500) + 600 // 600-2100
//
const indicator1 = Math.floor(Math.random() * 30) + 40 // 40-70
const indicator2 = Math.floor(Math.random() * 40) + 50 // 50-90
const indicator3 = Math.floor(Math.random() * 35) + 60 // 60-95
data.push([date, vol1, vol2, indicator1, indicator2, indicator3])
}
return data
}
// 30线 [MA5, MA10, MA20, MA30]
const generateFTLineData = () => {
const data = []
let ma5Base = 2450
let ma10Base = 2445
let ma20Base = 2440
let ma30Base = 2435
for (let i = 0; i < 30; i++) {
// 线
ma5Base += (Math.random() - 0.5) * 10
ma10Base += (Math.random() - 0.5) * 8
ma20Base += (Math.random() - 0.5) * 6
ma30Base += (Math.random() - 0.5) * 4
data.push([
Math.round(ma5Base * 100) / 100,
Math.round(ma10Base * 100) / 100,
Math.round(ma20Base * 100) / 100,
Math.round(ma30Base * 100) / 100
])
}
return data
}
//
const mockKLineData = generateKLineData()
const mockWaveVolData = generateWaveVolData()
const mockFTLineData = generateFTLineData()
// RSI ()
const generateRSIData = () => {
const data = []
for (let i = 0; i < 30; i++) {
const rsi = Math.random() * 60 + 20 // RSI20-80
data.push(Math.round(rsi * 100) / 100)
}
return data
}
// MACD
const generateMACDData = () => {
const data = []
for (let i = 0; i < 30; i++) {
const macd = (Math.random() - 0.5) * 20 // MACD-1010
const signal = (Math.random() - 0.5) * 15 // 线
const histogram = macd - signal //
data.push([
Math.round(macd * 100) / 100,
Math.round(signal * 100) / 100,
Math.round(histogram * 100) / 100
])
}
return data
}
//
const generateBollingerData = () => {
const data = []
let middleLine = 2450
for (let i = 0; i < 30; i++) {
middleLine += (Math.random() - 0.5) * 10
const upperBand = middleLine + Math.random() * 30 + 20 //
const lowerBand = middleLine - Math.random() * 30 - 20 //
data.push([
Math.round(upperBand * 100) / 100,
Math.round(middleLine * 100) / 100,
Math.round(lowerBand * 100) / 100
])
}
return data
}
//
const generateVolumeAnalysisData = () => {
const data = []
for (let i = 0; i < 30; i++) {
const buyVolume = Math.floor(Math.random() * 1500) + 500 //
const sellVolume = Math.floor(Math.random() * 1500) + 500 //
const netVolume = buyVolume - sellVolume //
data.push([buyVolume, sellVolume, netVolume])
}
return data
}
//
const generateMarketSentimentData = () => {
const sentiments = ['极度恐慌', '恐慌', '中性', '贪婪', '极度贪婪']
const data = []
for (let i = 0; i < 30; i++) {
const sentimentIndex = Math.floor(Math.random() * 100) // 0-100
const sentimentLabel = sentiments[Math.floor(sentimentIndex / 20)]
data.push({
date: new Date(2024, 0, i + 1).toISOString().split('T')[0],
index: sentimentIndex,
label: sentimentLabel,
fearGreedRatio: Math.random() * 100
})
}
return data
}
//
const generateNewsEventsData = () => {
const events = [
'美联储利率决议',
'非农就业数据发布',
'通胀数据公布',
'地缘政治紧张',
'央行政策变化',
'经济数据超预期',
'市场技术突破',
'大宗商品价格波动'
]
const data = []
for (let i = 0; i < 10; i++) { // 10
const randomDay = Math.floor(Math.random() * 30) + 1
const event = events[Math.floor(Math.random() * events.length)]
const impact = Math.floor(Math.random() * 5) + 1 // 1-5
data.push({
date: new Date(2024, 0, randomDay).toISOString().split('T')[0],
event: event,
impact: impact,
type: Math.random() > 0.5 ? 'positive' : 'negative'
})
}
return data.sort((a, b) => new Date(a.date) - new Date(b.date))
}
//
const generatePricePredictionData = () => {
const data = []
let currentPrice = 2450
for (let i = 0; i < 7; i++) { // 7
const date = new Date(2024, 1, i + 1).toISOString().split('T')[0] // 2
// AI
const prediction = currentPrice + (Math.random() - 0.5) * 100
const confidence = Math.random() * 40 + 60 // 60-100%
const upperBound = prediction + Math.random() * 50
const lowerBound = prediction - Math.random() * 50
data.push({
date: date,
predicted_price: Math.round(prediction * 100) / 100,
confidence: Math.round(confidence),
upper_bound: Math.round(upperBound * 100) / 100,
lower_bound: Math.round(lowerBound * 100) / 100
})
currentPrice = prediction
}
return data
}
//
const extractedDrawData = {
KLine20: mockKLineData,
WAVEVOL: mockWaveVolData,
FTLINE: mockFTLineData,
RSI: generateRSIData(),
MACD: generateMACDData(),
BOLLINGER: generateBollingerData(),
VOLUME_ANALYSIS: generateVolumeAnalysisData(),
MARKET_SENTIMENT: generateMarketSentimentData(),
NEWS_EVENTS: generateNewsEventsData(),
PRICE_PREDICTION: generatePricePredictionData()
}
const fnShowEcharts4 = (extractedDrawData) => {
const splitData = (b) => {
const a = JSON.parse(JSON.stringify(b))
let categoryData = []
let values = []
for (let i = 0; i < a.length; i++) {
categoryData.push(a[i].splice(0, 1)[0])
values.push(a[i])
}
return {
categoryData,
values
}
}
var bodongliang = splitData(extractedDrawData.WAVEVOL)
function bodongliangData(values, i) {
return values.map((subArray) => subArray[i])
}
function calculateMA(index, data) {
let result = []
if (data.FTLINE) {
data.FTLINE.forEach((item) => {
result.push(item[index])
})
}
return result
}
function vwToPx(vw) {
return (screenWidth.value * vw) / 100
}
var dealData = splitData(extractedDrawData.KLine20)
var dealGnBullData = AIGoldBull.value.JN
const textEcharts = t.value
const firstLegend = computed(() => {
if (screenWidth.value < 768) {
if (textEcharts.suoxie === 'en' || textEcharts.suoxie === 'th') {
return '2%'
} else if (textEcharts.suoxie === 'kr') {
return '2%'
} else {
return '2%'
}
} else {
return textEcharts.suoxie === 'en' ||
textEcharts.suoxie === 'th' ||
textEcharts.suoxie === 'kr'
? '9%'
: '9%'
}
})
const processBarData = (data) => {
const barData = []
data.forEach((item) => {
let color
switch (item[4]) {
case 1:
color = '#13E113'
break
case 2:
color = '#FF0E00'
break
case 3:
color = '#0000FE'
break
case 4:
color = '#1397FF'
break
}
barData.push({
value: item[5],
itemStyle: {
normal: {
color: color
}
}
})
})
return { barData }
}
const { barData } = processBarData(dealGnBullData)
option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
},
backgroundColor: 'rgba(119, 120, 125, 0.6)',
borderWidth: 1,
borderColor: '#77787D',
padding: 10,
textStyle: {
color: '#fff'
}
},
axisPointer: {
link: [
{
xAxisIndex: 'all'
}
],
label: {
backgroundColor: '#77787D'
}
},
toolbox: {
show: false
},
grid: [
{
left: screenWidth.value > 768 ? '10%' : '12%',
right: screenWidth.value > 768 ? '4%' : '6%',
top: screenWidth.value > 768 ? '10%' : '12%',
height: screenWidth.value > 768 ? '35%' : '34%',
containLabel: false
},
{
left: screenWidth.value > 768 ? '10%' : '12%',
right: screenWidth.value > 768 ? '4%' : '6%',
top: screenWidth.value > 768 ? '48%' : '48%',
height: screenWidth.value > 768 ? '19%' : '21%',
containLabel: false
},
{
left: screenWidth.value > 768 ? '10%' : '12%',
right: screenWidth.value > 768 ? '4%' : '6%',
top: screenWidth.value > 768 ? '70%' : '71%',
height: screenWidth.value > 768 ? '19%' : '21%',
containLabel: false
}
],
xAxis: [
{
type: 'category',
data: dealData.categoryData,
boundaryGap: true,
axisLine: { onZero: false },
splitLine: { show: false },
min: 'dataMin',
max: 'dataMax',
axisPointer: {
z: 100,
label: {
show: false //
}
},
axisLine: {
lineStyle: {
color: 'black'
}
}, //
axisLabel: { show: false },
axisTick: { show: false }
},
{
type: 'category',
gridIndex: 1,
data: dealData.categoryData,
boundaryGap: true,
axisPointer: {
z: 100,
label: {
show: false //
}
},
axisLine: { lineStyle: { color: 'black' } },
axisLabel: {
show: false,
interval: 'auto'
},
axisTick: { show: false }
},
{
type: 'category',
gridIndex: 2,
data: dealData.categoryData,
boundaryGap: true,
axisLine: { lineStyle: { color: 'black' } },
axisLabel: {
show: true,
interval: 'auto',
fontSize: screenWidth.value > 768 ? 15 : 9
},
axisTick: { show: false }
}
],
yAxis: [
{
scale: true,
gridIndex: 0,
position: 'left',
axisLabel: {
inside: false,
align: 'right',
fontSize: screenWidth.value > 768 ? 15 : 9
},
axisLine: {
show: true,
lineStyle: {
fontSize: '',
color: 'black'
}
},
axisTick: { show: false },
splitLine: { show: false }
},
{
scale: true,
gridIndex: 1,
splitNumber: 4,
min: 0,
minInterval: 1,
axisLabel: {
show: true,
fontSize: screenWidth.value > 768 ? 15 : 9,
margin: 8,
},
axisLine: { show: true, lineStyle: { color: 'black' } },
axisTick: { show: false },
splitLine: { show: true, lineStyle: { type: 'dashed' } },
boundaryGap: ['20%', '20%']
},
{
scale: true,
gridIndex: 2,
splitNumber: 2,
axisLabel: {
show: true,
fontSize: screenWidth.value > 768 ? 15 : 9
},
axisLine: { show: true, lineStyle: { color: 'black' } },
axisTick: { show: false },
splitLine: { show: false }
}
],
dataZoom: [
{
type: 'inside',
xAxisIndex: [0, 1, 2],
start: 50,
end: 100
},
{
show: true,
xAxisIndex: [0, 1, 2],
type: 'slider',
start: 50,
end: 100
}
],
series: [
{
type: 'candlestick',
name: '日K',
xAxisIndex: 0,
yAxisIndex: 0,
data: dealData.values,
itemStyle: {
normal: {
color0: 'red',
color: 'green',
borderColor0: 'red',
borderColor: 'green'
}
},
gridIndex: 1
},
{
name: '成交量',
type: 'bar',
barWidth: '70%',
xAxisIndex: 1,
yAxisIndex: 1,
data: barData,
},
// {
// name: textEcharts.feixian,
// type: 'line',
// data: calculateMA(1, extractedDrawData),
// smooth: true,
// symbol: 'none',
// xAxisIndex: 2,
// yAxisIndex: 2,
// itemStyle: {
// normal: {
// color: '#00a32e',
// lineStyle: {
// color: '#00a32e',
// width: 2,
// type: 'solid'
// }
// }
// }
// },
// {
// name: textEcharts.zhoongxian,
// type: 'line',
// data: calculateMA(2, extractedDrawData),
// smooth: true,
// symbol: 'none',
// xAxisIndex: 2,
// yAxisIndex: 2,
// itemStyle: {
// normal: {
// color: '#de0000',
// lineStyle: {
// color: '#de0000',
// width: 2,
// type: 'solid'
// }
// }
// }
// },
// {
// name: textEcharts.tianxian,
// type: 'line',
// data: calculateMA(3, extractedDrawData),
// smooth: true,
// symbol: 'none',
// xAxisIndex: 2,
// yAxisIndex: 2,
// itemStyle: {
// normal: {
// color: '#ffb300',
// lineStyle: {
// color: '#ffb300',
// width: 2,
// type: 'solid'
// }
// }
// }
// },
// {
// name: textEcharts.liuxian,
// type: 'line',
// data: calculateMA(4, extractedDrawData),
// smooth: true,
// symbol: 'none',
// xAxisIndex: 2,
// yAxisIndex: 2,
// itemStyle: {
// normal: {
// color: '#00c8ff',
// lineStyle: {
// color: '#00c8ff',
// width: 2,
// type: 'solid'
// }
// }
// }
// },
]
}
initChart()
}
//
onMounted(() => {
//
fnShowEcharts4(extractedDrawData)
})
//
const initChart = async () => {
if (!chartRef.value) return
try {
const chart = await chartRef.value.init(echarts)
chart.setOption(option)
} catch (error) {
console.error('图表初始化失败:', error)
}
}
</script>

493
pages/marketSituation/countryMarket.vue

@ -0,0 +1,493 @@
<template>
<view class="content">
<!-- 市场子Tab -->
<view class="sub_tabs">
<view v-for="(tab, i) in marketTabs" :key="tab" :class="['tab_item', i === activeTabIndex ? 'active' : '']"
@click="switchTab(i)">
<text>{{ tab }}</text>
</view>
</view>
<!-- 大盘指数 -->
<view class="section">
<view class="section_header">
<text class="section_title">大盘指数</text>
<text class="section_action" @click="viewMore('indices')">查看更多 ></text>
</view>
<view class="indices_grid">
<view v-for="(index, i) in countryInfo.mainIndices" :key="i" class="index_item">
<IndexCard :flagIcon="countryInfo.flag" :indexName="index.name" :currentPrice="index.price"
:changeAmount="index.change" :changePercent="index.changePercent" :isRising="index.isRising" />
</view>
</view>
<!-- 今日市场情绪温度 -->
<view class="sentiment">
<view class="section_subtitle">
<text>今日市场情绪温度</text>
</view>
<view class="meters">
<view class="meter_item" v-for="(m, i) in sentimentMeters" :key="i">
<image class="meter_icon" :class="m.theme" :src="selectIcons[m.theme]" mode="aspectFit"></image>
<view class="meter_info">
<text class="meter_value" :class="m.theme">{{ m.value }}°C</text>
<text class="meter_label" :class="m.theme">{{ m.label }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 板块 -->
<view class="section">
<view class="section_header">
<text class="section_title">板块</text>
<text class="section_action" @click="viewMore('sectors')">查看更多 ></text>
</view>
<view class="sectors_grid">
<view v-for="(sec, i) in sectors" :key="i" class="sector_item">
<view class="sector_header">
<text class="sector_name">{{ sec.name }}</text>
<text :class="['sector_change', sec.isRising ? 'rising' : 'falling']">
{{ sec.change }}
</text>
</view>
<view class="sector_price">{{ sec.price }}</view>
</view>
</view>
</view>
<!-- 股票 -->
<view class="section">
<view class="section_header">
<text class="section_title">股票</text>
<text class="section_action" @click="viewMore('stocks')">查看更多 ></text>
</view>
<view class="table">
<view class="table_header">
<text class="cell name">名称</text>
<text class="cell price">最新</text>
<text class="cell change">涨幅</text>
</view>
<view class="table_row" v-for="(stk, i) in stocks" :key="i">
<view class="cell name">
<text class="stk_name">{{ stk.name }}</text>
<text class="stk_code">{{ stk.code }}</text>
</view>
<view class="cell price">
<text class="stk_price">{{ stk.price }}</text>
</view>
<view class="cell change">
<text :class="['stk_change', stk.isRising ? 'rising' : 'falling']">
{{ stk.change }}
</text>
</view>
</view>
</view>
</view>
<!-- 底部安全区域 -->
<view class="bottom_safe_area"></view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import IndexCard from '../../components/IndexCard.vue'
// Tab
const marketTabs = ['全部', '美股', '纽交所', '纳斯达克']
const activeTabIndex = ref(0)
const switchTab = (i) => {
activeTabIndex.value = i
}
//
const sentimentMeters = [
{ value: 90, label: '道琼斯', theme: 'hot' },
{ value: 60, label: '纳斯达克', theme: 'warm' },
{ value: 20, label: '标普500', theme: 'cool' }
]
//
const selectIcons = {
hot: '/static/marketSituation-image/hot.png',
warm: '/static/marketSituation-image/warm.png',
cool: '/static/marketSituation-image/cool.png'
}
// Props
const props = defineProps({
countryId: {
type: Number,
required: true
}
})
// /
const countryInfoMap = {
2: { //
name: '新加坡',
flag: '🇸🇬',
isMarketOpen: true,
mainIndices: [
{ name: '海峡时报指数', price: '3,234.56', change: '+12.34', changePercent: '+0.38%', isRising: true },
{ name: 'FTSE ST Mid Cap', price: '1,234.56', change: '-5.67', changePercent: '-0.46%', isRising: false }
],
hotStocks: [
{ name: '星展银行', code: 'D05.SI', price: '35.20', change: '+0.15', isRising: true },
{ name: '华侨银行', code: 'O39.SI', price: '13.45', change: '-0.05', isRising: false }
]
},
3: { // 西
name: '马来西亚',
flag: '🇲🇾',
isMarketOpen: false,
mainIndices: [
{ name: '富时大马KLCI指数', price: '1,567.89', change: '+8.90', changePercent: '+0.57%', isRising: true }
],
hotStocks: [
{ name: '马来亚银行', code: '1155.KL', price: '9.85', change: '+0.10', isRising: true },
{ name: '大众银行', code: '1295.KL', price: '4.32', change: '-0.02', isRising: false }
]
},
4: { // 西
name: '印度尼西亚',
flag: '🇮🇩',
isMarketOpen: true,
mainIndices: [
{ name: '雅加达综合指数', price: '7,234.56', change: '+45.67', changePercent: '+0.63%', isRising: true }
],
hotStocks: []
},
5: { //
name: '美国',
flag: '🇺🇸',
isMarketOpen: false,
mainIndices: [
{ name: '道琼斯', price: '45,757.90', change: '-125.22', changePercent: '-0.27%', isRising: false },
{ name: '纳斯达克', price: '22,333.96', change: '+125.22', changePercent: '+0.47%', isRising: true },
{ name: '标普500', price: '6,606.08', change: '+125.22', changePercent: '+0.27%', isRising: true }
],
hotStocks: [
{ name: '苹果', code: 'AAPL', price: '195.89', change: '+2.34', isRising: true },
{ name: '微软', code: 'MSFT', price: '378.85', change: '-1.23', isRising: false }
]
}
}
//
const countryInfo = computed(() => {
return countryInfoMap[props.countryId] || {
name: '未知地区',
flag: '🌍',
isMarketOpen: false,
mainIndices: [],
hotStocks: []
}
})
//
const sectors = computed(() => {
return countryInfoMap[props.countryId]?.sectors || []
})
const stocks = computed(() => {
return countryInfoMap[props.countryId]?.stocks || []
})
//
const viewMore = (type) => {
// type
// indices/sectors/stocks
// uni.navigateTo({ url: `/pages/marketSituation/${type}List` })
}
</script>
<style scoped>
.content {
padding: 0 20rpx 20rpx 20rpx;
}
/* 子Tab */
.sub_tabs {
display: flex;
gap: 16rpx;
padding: 0 20rpx 20rpx 20rpx;
}
.tab_item {
padding: 6rpx 20rpx;
border-radius: 5rpx;
background: #f5f5f5;
color: #666;
font-size: 24rpx;
}
.tab_item.active {
background: #ff4444;
color: #fff;
}
.section {
padding: 20rpx;
border-radius: 16rpx;
}
.section_header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
}
.section_title {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.section_action {
font-size: 24rpx;
color: #999;
}
.indices_grid {
padding: 20rpx;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20rpx;
background-color: #F6F6F6;
}
/* 情绪温度 */
.sentiment {
background-color: #F6F6F6;
padding: 0 20rpx 20rpx 20rpx;
}
.section_subtitle {
font-size: 24rpx;
color: #000000;
padding: 20rpx 0;
}
.meters {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16rpx;
}
.meter_item {
display: flex;
align-items: center;
/* padding: 10rpx; */
background: #ffffff;
border-radius: 12rpx;
}
.meter_item image {
width: 100rpx;
height: 100rpx;
}
.meter_info {
display: flex;
flex-direction: column;
}
.meter_value {
font-size: 36rpx;
}
.meter_value.hot {
color: #ff6b6b;
}
.meter_value.warm {
color: #ffd166;
}
.meter_value.cool {
color: #60a5fa;
}
.meter_label {
font-size: 22rpx;
}
.meter_label.hot {
color: #ff6b6b;
}
.meter_label.warm {
color: #ffd166;
}
.meter_label.cool {
color: #60a5fa;
}
/* 板块 */
.sectors_grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16rpx;
}
.sector_item {
background: #fafafa;
border-radius: 12rpx;
padding: 16rpx;
}
.sector_header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12rpx;
}
.sector_name {
font-size: 24rpx;
color: #333;
}
.sector_change {
font-size: 22rpx;
}
.sector_change.rising {
color: #e74c3c;
}
.sector_change.falling {
color: #27ae60;
}
.sector_price {
font-size: 24rpx;
color: #666;
}
/* 股票表 */
.table {
background: #fff;
border-radius: 12rpx;
overflow: hidden;
}
.table_header,
.table_row {
display: grid;
grid-template-columns: 2fr 1fr 1fr;
padding: 18rpx 20rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.table_header {
background: #fafafa;
}
.cell.name {
display: flex;
flex-direction: column;
}
.stk_name {
font-size: 26rpx;
color: #333;
}
.stk_code {
font-size: 22rpx;
color: #999;
}
.stk_price {
font-size: 26rpx;
color: #333;
}
.stk_change {
font-size: 24rpx;
}
.stk_change.rising {
color: #e74c3c;
}
.stk_change.falling {
color: #27ae60;
}
.index_item {
background: #fff;
border-radius: 12rpx;
overflow: hidden;
}
.hot_stocks {
margin-bottom: 30rpx;
}
.stocks_list {
background: #fff;
border-radius: 12rpx;
overflow: hidden;
}
.stock_item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 20rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.stock_item:last-child {
border-bottom: none;
}
.stock_info {
flex: 1;
}
.stock_name {
font-size: 28rpx;
color: #333;
display: block;
margin-bottom: 8rpx;
}
.stock_code {
font-size: 24rpx;
color: #999;
}
.stock_price {
text-align: right;
}
.price {
font-size: 28rpx;
color: #333;
display: block;
margin-bottom: 8rpx;
}
.change {
font-size: 24rpx;
}
.change.rising {
color: #e74c3c;
}
.change.falling {
color: #27ae60;
}
.bottom_safe_area {
height: 120rpx;
}
</style>

301
pages/marketSituation/forexMetals.vue

@ -0,0 +1,301 @@
<template>
<view class="content">
<view class="section" v-if="type === 'forex'">
<view class="section_title">
<text class="title_icon">💱</text>
<text>外汇市场</text>
</view>
<view class="forex_grid">
<view v-for="(item, index) in forexData" :key="index" class="forex_item">
<view class="forex_pair">
<text class="base_currency">{{ item.base }}</text>
<text class="separator">/</text>
<text class="quote_currency">{{ item.quote }}</text>
</view>
<view class="forex_price">
<text class="price">{{ item.price }}</text>
<text :class="['change', item.isRising ? 'rising' : 'falling']">
{{ item.change }}
</text>
</view>
</view>
</view>
</view>
<view class="section" v-if="type === 'metals'">
<view class="section_title">
<text class="title_icon">🥇</text>
<text>贵金属</text>
</view>
<view class="metals_grid">
<view v-for="(item, index) in metalsData" :key="index" class="metal_item">
<view class="metal_info">
<text class="metal_icon">{{ item.icon }}</text>
<text class="metal_name">{{ item.name }}</text>
</view>
<view class="metal_price">
<text class="price">{{ item.price }}</text>
<text class="unit">{{ item.unit }}</text>
<text :class="['change', item.isRising ? 'rising' : 'falling']">
{{ item.change }}
</text>
</view>
</view>
</view>
</view>
<!-- 市场动态 -->
<view class="market_news">
<view class="section_title">
<text class="title_icon">📰</text>
<text>市场动态</text>
</view>
<view class="news_list">
<view v-for="(news, index) in newsData" :key="index" class="news_item">
<view class="news_content">
<text class="news_title">{{ news.title }}</text>
<text class="news_time">{{ news.time }}</text>
</view>
<view class="news_impact" :class="news.impact">
<text>{{ news.impactText }}</text>
</view>
</view>
</view>
</view>
<!-- 底部安全区域 -->
<view class="bottom_safe_area"></view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
// Props
const props = defineProps({
countryId: {
type: Number,
required: true
}
})
//
const type = computed(() => {
return props.countryId === 11 ? 'forex' : 'metals'
})
//
const forexData = ref([
{ base: 'USD', quote: 'CNY', price: '7.2456', change: '+0.0123', isRising: true },
{ base: 'EUR', quote: 'USD', price: '1.0876', change: '-0.0034', isRising: false },
{ base: 'GBP', quote: 'USD', price: '1.2654', change: '+0.0087', isRising: true },
{ base: 'USD', quote: 'JPY', price: '149.87', change: '+0.45', isRising: true },
{ base: 'AUD', quote: 'USD', price: '0.6543', change: '-0.0021', isRising: false },
{ base: 'USD', quote: 'SGD', price: '1.3456', change: '+0.0012', isRising: true }
])
//
const metalsData = ref([
{ icon: '🥇', name: '黄金', price: '2,034.56', unit: 'USD/盎司', change: '+12.34', isRising: true },
{ icon: '🥈', name: '白银', price: '24.87', unit: 'USD/盎司', change: '-0.23', isRising: false },
{ icon: '⚪', name: '铂金', price: '987.65', unit: 'USD/盎司', change: '+5.67', isRising: true },
{ icon: '⚫', name: '钯金', price: '1,234.56', unit: 'USD/盎司', change: '-8.90', isRising: false }
])
//
const newsData = ref([
{
title: '美联储暗示可能暂停加息',
time: '2小时前',
impact: 'high',
impactText: '高影响'
},
{
title: '欧洲央行维持利率不变',
time: '4小时前',
impact: 'medium',
impactText: '中影响'
},
{
title: '黄金价格创新高',
time: '6小时前',
impact: 'medium',
impactText: '中影响'
},
{
title: '美元指数小幅下跌',
time: '8小时前',
impact: 'low',
impactText: '低影响'
}
])
</script>
<style scoped>
.content {
padding: 20rpx;
}
.section {
margin-bottom: 40rpx;
}
.section_title {
display: flex;
align-items: center;
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
}
.title_icon {
font-size: 32rpx;
margin-right: 12rpx;
}
.forex_grid, .metals_grid {
background: #fff;
border-radius: 12rpx;
overflow: hidden;
}
.forex_item, .metal_item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 20rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.forex_item:last-child, .metal_item:last-child {
border-bottom: none;
}
.forex_pair {
display: flex;
align-items: center;
}
.base_currency {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.separator {
font-size: 24rpx;
color: #999;
margin: 0 8rpx;
}
.quote_currency {
font-size: 28rpx;
color: #666;
}
.forex_price, .metal_price {
text-align: right;
}
.price {
font-size: 28rpx;
color: #333;
display: block;
margin-bottom: 8rpx;
}
.unit {
font-size: 20rpx;
color: #999;
margin-left: 8rpx;
}
.change {
font-size: 24rpx;
}
.change.rising {
color: #e74c3c;
}
.change.falling {
color: #27ae60;
}
.metal_info {
display: flex;
align-items: center;
}
.metal_icon {
font-size: 32rpx;
margin-right: 16rpx;
}
.metal_name {
font-size: 28rpx;
color: #333;
}
.market_news {
margin-bottom: 30rpx;
}
.news_list {
background: #fff;
border-radius: 12rpx;
overflow: hidden;
}
.news_item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 20rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.news_item:last-child {
border-bottom: none;
}
.news_content {
flex: 1;
}
.news_title {
font-size: 28rpx;
color: #333;
display: block;
margin-bottom: 8rpx;
}
.news_time {
font-size: 24rpx;
color: #999;
}
.news_impact {
padding: 6rpx 12rpx;
border-radius: 16rpx;
font-size: 20rpx;
color: #fff;
}
.news_impact.high {
background: #e74c3c;
}
.news_impact.medium {
background: #f39c12;
}
.news_impact.low {
background: #95a5a6;
}
.bottom_safe_area {
height: 120rpx;
}
</style>

62
pages/home/globalIndex.vue → pages/marketSituation/globalIndex.vue

@ -43,7 +43,7 @@
</view> </view>
<view class="cards-grid-three"> <view class="cards-grid-three">
<view v-for="(item, index) in asiachinaIndexes" :key="index" class="card-item"> <view v-for="(item, index) in asiachinaIndexes" :key="index" class="card-item">
<IndexCard :flagIcon="item.flagIcon" :indexName="item.indexName"
<IndexCard :flagIcon="item.flagIcon" :stockName="item.stockName"
:currentPrice="item.currentPrice" :changeAmount="item.changeAmount" :currentPrice="item.currentPrice" :changeAmount="item.changeAmount"
:changePercent="item.changePercent" :isRising="item.isRising" :changePercent="item.changePercent" :isRising="item.isRising"
@click="viewIndexDetail(item)" /> @click="viewIndexDetail(item)" />
@ -62,7 +62,7 @@
</view> </view>
<view class="cards-grid-three"> <view class="cards-grid-three">
<view v-for="(item, index) in asiaIndexes" :key="index" class="card-item"> <view v-for="(item, index) in asiaIndexes" :key="index" class="card-item">
<IndexCard :flagIcon="item.flagIcon" :indexName="item.indexName"
<IndexCard :flagIcon="item.flagIcon" :stockName="item.stockName"
:currentPrice="item.currentPrice" :changeAmount="item.changeAmount" :currentPrice="item.currentPrice" :changeAmount="item.changeAmount"
:changePercent="item.changePercent" :isRising="item.isRising" :changePercent="item.changePercent" :isRising="item.isRising"
@click="viewIndexDetail(item)" /> @click="viewIndexDetail(item)" />
@ -81,7 +81,7 @@
</view> </view>
<view class="cards-grid-three"> <view class="cards-grid-three">
<view v-for="(item, index) in americaIndexes" :key="index" class="card-item"> <view v-for="(item, index) in americaIndexes" :key="index" class="card-item">
<IndexCard :flagIcon="item.flagIcon" :indexName="item.indexName"
<IndexCard :flagIcon="item.flagIcon" :stockName="item.stockName"
:currentPrice="item.currentPrice" :changeAmount="item.changeAmount" :currentPrice="item.currentPrice" :changeAmount="item.changeAmount"
:changePercent="item.changePercent" :isRising="item.isRising" :changePercent="item.changePercent" :isRising="item.isRising"
@click="viewIndexDetail(item)" /> @click="viewIndexDetail(item)" />
@ -145,7 +145,8 @@ const checkWarnTextOverflow = () => {
const asiachinaIndexes = ref([ const asiachinaIndexes = ref([
{ {
flagIcon: '/static/c1.png', flagIcon: '/static/c1.png',
indexName: '上证指数',
stockName: '上证指数',
stockCode:'noCode',
currentPrice: '3933.96', currentPrice: '3933.96',
changeAmount: '+24.32', changeAmount: '+24.32',
changePercent: '+0.62%', changePercent: '+0.62%',
@ -153,7 +154,8 @@ const asiachinaIndexes = ref([
}, },
{ {
flagIcon: '/static/c2.png', flagIcon: '/static/c2.png',
indexName: '深证成指',
stockName: '深证成指',
stockCode:'noCode',
currentPrice: '45757.90', currentPrice: '45757.90',
changeAmount: '-123.45', changeAmount: '-123.45',
changePercent: '-0.27%', changePercent: '-0.27%',
@ -161,7 +163,8 @@ const asiachinaIndexes = ref([
}, },
{ {
flagIcon: '/static/c3.png', flagIcon: '/static/c3.png',
indexName: '创业板指',
stockName: '创业板指',
stockCode:'noCode',
currentPrice: '6606.08', currentPrice: '6606.08',
changeAmount: '+89.76', changeAmount: '+89.76',
changePercent: '+1.38%', changePercent: '+1.38%',
@ -169,7 +172,8 @@ const asiachinaIndexes = ref([
}, },
{ {
flagIcon: '/static/c4.png', flagIcon: '/static/c4.png',
indexName: 'HSI50',
stockName: 'HSI50',
stockCode:'noCode',
currentPrice: '22333.96', currentPrice: '22333.96',
changeAmount: '+156.78', changeAmount: '+156.78',
changePercent: '+0.71%', changePercent: '+0.71%',
@ -177,7 +181,8 @@ const asiachinaIndexes = ref([
}, },
{ {
flagIcon: '/static/c5.png', flagIcon: '/static/c5.png',
indexName: '沪深300',
stockName: '沪深300',
stockCode:'noCode',
currentPrice: '45757.90', currentPrice: '45757.90',
changeAmount: '-89.12', changeAmount: '-89.12',
changePercent: '-0.19%', changePercent: '-0.19%',
@ -185,7 +190,8 @@ const asiachinaIndexes = ref([
}, },
{ {
flagIcon: '/static/c6.png', flagIcon: '/static/c6.png',
indexName: '上证50',
stockName: '上证50',
stockCode:'noCode',
currentPrice: '45757.90', currentPrice: '45757.90',
changeAmount: '+234.56', changeAmount: '+234.56',
changePercent: '+0.52%', changePercent: '+0.52%',
@ -197,7 +203,8 @@ const asiachinaIndexes = ref([
const asiaIndexes = ref([ const asiaIndexes = ref([
{ {
flagIcon: '/static/c7.png', flagIcon: '/static/c7.png',
indexName: '日经225',
stockName: '日经225',
stockCode:'noCode',
currentPrice: '28456.78', currentPrice: '28456.78',
changeAmount: '+234.56', changeAmount: '+234.56',
changePercent: '+0.83%', changePercent: '+0.83%',
@ -205,7 +212,8 @@ const asiaIndexes = ref([
}, },
{ {
flagIcon: '/static/c8.png', flagIcon: '/static/c8.png',
indexName: '韩国KOSPI',
stockName: '韩国KOSPI',
stockCode:'noCode',
currentPrice: '2567.89', currentPrice: '2567.89',
changeAmount: '-12.34', changeAmount: '-12.34',
changePercent: '-0.48%', changePercent: '-0.48%',
@ -213,7 +221,8 @@ const asiaIndexes = ref([
}, },
{ {
flagIcon: '/static/c9.png', flagIcon: '/static/c9.png',
indexName: '印度孟买',
stockName: '印度孟买',
stockCode:'noCode',
currentPrice: '65432.10', currentPrice: '65432.10',
changeAmount: '+456.78', changeAmount: '+456.78',
changePercent: '+0.70%', changePercent: '+0.70%',
@ -225,7 +234,8 @@ const asiaIndexes = ref([
const americaIndexes = ref([ const americaIndexes = ref([
{ {
flagIcon: '/static/c7.png', flagIcon: '/static/c7.png',
indexName: '道琼斯指数',
stockName: '道琼斯指数',
stockCode:'noCode',
currentPrice: '34567.89', currentPrice: '34567.89',
changeAmount: '+123.45', changeAmount: '+123.45',
changePercent: '+0.36%', changePercent: '+0.36%',
@ -233,7 +243,8 @@ const americaIndexes = ref([
}, },
{ {
flagIcon: '/static/c8.png', flagIcon: '/static/c8.png',
indexName: '纳斯达克',
stockName: '纳斯达克',
stockCode:'noCode',
currentPrice: '13456.78', currentPrice: '13456.78',
changeAmount: '-67.89', changeAmount: '-67.89',
changePercent: '-0.50%', changePercent: '-0.50%',
@ -241,7 +252,8 @@ const americaIndexes = ref([
}, },
{ {
flagIcon: '/static/c9.png', flagIcon: '/static/c9.png',
indexName: '标普500',
stockName: '标普500',
stockCode:'noCode',
currentPrice: '4234.56', currentPrice: '4234.56',
changeAmount: '+23.45', changeAmount: '+23.45',
changePercent: '+0.56%', changePercent: '+0.56%',
@ -275,22 +287,22 @@ const clearSearch = () => {
const viewMore = (market) => { const viewMore = (market) => {
console.log('查看更多:', market) console.log('查看更多:', market)
uni.navigateTo({ uni.navigateTo({
url: `/pages/home/marketDetail?market=${market}`
url: `/pages/marketSituation/marketDetail?market=${market}`
}) })
} }
// //
const viewIndexDetail = (item) => { const viewIndexDetail = (item) => {
console.log('查看指数详情:', item.indexName)
uni.showToast({
title: `查看 ${item.indexName} 详情`,
icon: 'none',
duration: 2000
})
//
// uni.navigateTo({
// url: `/pages/detail/indexDetail?id=${item.id}`
console.log('查看指数详情:', item.stockName)
// uni.showToast({
// title: ` ${item.stockName} `,
// icon: 'none',
// duration: 2000
// }) // })
//
uni.navigateTo({
url: `/pages/marketSituation/marketCondition?stockInformation=${encodeURIComponent(JSON.stringify(item))}`
})
} }
// //

2258
pages/marketSituation/marketCondition.vue
File diff suppressed because it is too large
View File

82
pages/home/marketDetail.vue → pages/marketSituation/marketDetail.vue

@ -37,10 +37,10 @@
<!-- 股票列表 --> <!-- 股票列表 -->
<view class="stock-list"> <view class="stock-list">
<view class="stock-row" v-for="(stock, index) in sortedStockList" :key="index" <view class="stock-row" v-for="(stock, index) in sortedStockList" :key="index"
@click="viewStockDetail(stock)">
@click="viewIndexDetail(stock)">
<view class="stock-cell name-column"> <view class="stock-cell name-column">
<view class="stock-name">{{ stock.name }}</view>
<view class="stock-code">{{ stock.code }}</view>
<view class="stock-name">{{ stock.stockName }}</view>
<view class="stock-code">{{ stock.stockCode }}</view>
</view> </view>
<view class="stock-cell price-column"> <view class="stock-cell price-column">
<text class="stock-price" <text class="stock-price"
@ -73,6 +73,7 @@ import footerBar from '@/components/footerBar.vue'
// //
const iSMT = ref(0) const iSMT = ref(0)
const contentHeight = ref(0)
const headerHeight = ref(80) const headerHeight = ref(80)
const marketType = ref('america') const marketType = ref('america')
const marketTitle = ref('美洲') const marketTitle = ref('美洲')
@ -82,99 +83,99 @@ const sortOrder = ref('desc') // 排序顺序:'asc' 或 'desc'
// //
const stockList = ref([ const stockList = ref([
{ {
name: 'Telecommunication',
code: '888607',
stockName: 'Telecommunication',
stockCode: '888607',
price: 1349.47, price: 1349.47,
change: '+7.67%', change: '+7.67%',
isRising: true isRising: true
}, },
{ {
name: 'Other',
code: '888607',
stockName: 'Other',
stockCode: '888607',
price: 1349.47, price: 1349.47,
change: '+6.67%', change: '+6.67%',
isRising: true isRising: true
}, },
{ {
name: 'Consumer Discretio...',
code: '888610',
stockName: 'Consumer Discretio...',
stockCode: '888610',
price: 1349.47, price: 1349.47,
change: '+5.67%', change: '+5.67%',
isRising: true isRising: true
}, },
{ {
name: 'Telecommunication',
code: '888607',
stockName: 'Telecommunication',
stockCode: '888607',
price: 1349.47, price: 1349.47,
change: '+4.67%', change: '+4.67%',
isRising: true isRising: true
}, },
{ {
name: 'Other',
code: '888611',
stockName: 'Other',
stockCode: '888611',
price: 1359.47, price: 1359.47,
change: '+3.67%', change: '+3.67%',
isRising: true isRising: true
}, },
{ {
name: 'Consumer Discretio...',
code: '888610',
stockName: 'Consumer Discretio...',
stockCode: '888610',
price: 1349.47, price: 1349.47,
change: '+2.67%', change: '+2.67%',
isRising: true isRising: true
}, },
{ {
name: 'Telecommunication',
code: '888607',
stockName: 'Telecommunication',
stockCode: '888607',
price: 1349.47, price: 1349.47,
change: '+1.67%', change: '+1.67%',
isRising: true isRising: true
}, },
{ {
name: 'Other',
code: '888611',
stockName: 'Other',
stockCode: '888611',
price: 1009.98, price: 1009.98,
change: '-1.67%', change: '-1.67%',
isRising: false isRising: false
}, },
{ {
name: 'Consumer Discretio...',
code: '888610',
stockName: 'Consumer Discretio...',
stockCode: '888610',
price: 1009.98, price: 1009.98,
change: '-0.67%', change: '-0.67%',
isRising: false isRising: false
}, },
{ {
name: 'Telecommunication',
code: '888607',
stockName: 'Telecommunication',
stockCode: '888607',
price: 1009.98, price: 1009.98,
change: '-0.67%', change: '-0.67%',
isRising: false isRising: false
}, },
{ {
name: 'Other',
code: '888611',
stockName: 'Other',
stockCode: '888611',
price: 1009.98, price: 1009.98,
change: '-1.67%', change: '-1.67%',
isRising: false isRising: false
}, },
{ {
name: 'Consumer Discretio...',
code: '888610',
stockName: 'Consumer Discretio...',
stockCode: '888610',
price: 1009.98, price: 1009.98,
change: '-4.67%', change: '-4.67%',
isRising: false isRising: false
}, },
{ {
name: 'Consumer Discretio...',
code: '888610',
stockName: 'Consumer Discretio...',
stockCode: '888610',
price: 1009.98, price: 1009.98,
change: '-3.67%', change: '-3.67%',
isRising: false isRising: false
}, },
{ {
name: 'Consumer Discretio...',
code: '888610',
stockName: 'Consumer Discretio...',
stockCode: '888610',
price: 1009.98, price: 1009.98,
change: '-3.67%', change: '-3.67%',
isRising: false isRising: false
@ -231,6 +232,20 @@ const goBack = () => {
uni.navigateBack() uni.navigateBack()
} }
//
const viewIndexDetail = (item) => {
console.log('查看指数详情:', item.stockName)
// uni.showToast({
// title: ` ${item.stockName} `,
// icon: 'none',
// duration: 2000
// })
//
uni.navigateTo({
url: `/pages/marketSituation/marketCondition?stockInformation=${encodeURIComponent(JSON.stringify(item))}`
})
}
const sortByPrice = () => { const sortByPrice = () => {
if (sortType.value === 'price') { if (sortType.value === 'price') {
sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc' sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc'
@ -249,11 +264,6 @@ const sortByChange = () => {
} }
} }
const viewStockDetail = (stock) => {
console.log('查看股票详情:', stock)
//
}
onMounted(() => { onMounted(() => {
// //
iSMT.value = uni.getSystemInfoSync().statusBarHeight; iSMT.value = uni.getSystemInfoSync().statusBarHeight;

757
pages/marketSituation/marketOverview.vue

@ -0,0 +1,757 @@
<!-- @format -->
<template>
<view class="main">
<!-- 可滚动内容区域 -->
<scroll-view class="content_scroll" scroll-y="true" :style="{ top: contentTopPosition + 'px' }">
<view class="content">
<button @click="goToChartExample">图表</button>
<view class="map">
<image src="/static/marketSituation-image/map.png" mode="widthFix"></image>
</view>
<view class="global_index">
<view class="global_index_title">
{{ $t("marketSituation.globalIndex") }}
</view>
<view class="global_index_more" @click="goToGlobalIndex">
<text>{{ $t("marketSituation.globalIndexMore") }}</text>
<image src="/static/marketSituation-image/more.png" mode="aspectFit"></image>
</view>
</view>
<!-- 卡片网格 -->
<view class="cards_grid">
<view v-for="(card, index) in cardData" :key="index" class="card_item">
<IndexCard :flagIcon="card.flagIcon" :stockName="card.stockName" :currentPrice="card.currentPrice" :changeAmount="card.changeAmount" :changePercent="card.changePercent" :isRising="card.isRising" @click="viewIndexDetail(card)" />
</view>
</view>
<view class="warn">
<image src="/static/marketSituation-image/warn.png" mode="aspectFit"></image>
<view class="warn_text_container">
<text :class="warnTextClass">{{ $t("marketSituation.warn") }}</text>
</view>
</view>
<!-- 底部安全区域防止被导航栏遮挡 -->
<view class="bottom_safe_area"></view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, onMounted, watch, nextTick, computed } from "vue";
import util from "../../common/util.js";
import IndexCard from "../../components/IndexCard.vue";
const iSMT = ref(0);
const searchValue = ref("");
const contentHeight = ref(0);
const headerHeight = ref(0); // header
const isWarnTextOverflow = ref(false); // warn
const pageIndex = ref(0);
const scrollToView = ref("");
//
const goToChartExample = () => {
uni.navigateTo({
url: "/pages/marketSituation/chartExample",
});
};
// contenttop
const contentTopPosition = computed(() => {
const statusBarHeight = iSMT.value || 0;
const currentHeaderHeight = headerHeight.value > 0 ? headerHeight.value : 140;
return statusBarHeight + currentHeaderHeight;
});
// warnclass
const warnTextClass = computed(() => {
return isWarnTextOverflow.value ? "warn_text scroll-active" : "warn_text";
});
//
const showCountryModal = ref(false);
const selectedCountry = ref("概况");
const countryList = ref(["概况", "新加坡", "马来西亚", "印度尼西亚", "美国", "中国香港", "泰国", "中国", "加拿大", "越南", "外汇", "贵金属"]);
//
const cardData = ref([
{
flagIcon: "🇺🇸",
stockName: "道琼斯",
stockCode: "noCode",
currentPrice: "45757.90",
changeAmount: "-125.22",
changePercent: "-0.27%",
isRising: false,
},
{
flagIcon: "🇺🇸",
stockName: "纳斯达克",
stockCode: "noCode",
currentPrice: "22333.96",
changeAmount: "+125.22",
changePercent: "+0.47%",
isRising: true,
},
{
flagIcon: "🇺🇸",
stockName: "标普500",
stockCode: "noCode",
currentPrice: "6606.08",
changeAmount: "+125.22",
changePercent: "+0.27%",
isRising: true,
},
{
flagIcon: "🇨🇳",
stockName: "上证指数",
stockCode: "noCode",
currentPrice: "3333.96",
changeAmount: "+125.22",
changePercent: "+0.27%",
isRising: true,
},
{
flagIcon: "🇨🇳",
stockName: "科创50",
stockCode: "noCode",
currentPrice: "757.90",
changeAmount: "-25.22",
changePercent: "-0.27%",
isRising: false,
},
{
flagIcon: "🇭🇰",
stockName: "恒生指数",
stockCode: "noCode",
currentPrice: "19757.90",
changeAmount: "-125.22",
changePercent: "-0.63%",
isRising: false,
},
{
flagIcon: "🇸🇬",
stockName: "道琼斯",
stockCode: "noCode",
currentPrice: "3757.90",
changeAmount: "+85.22",
changePercent: "+2.31%",
isRising: true,
},
{
flagIcon: "🇲🇾",
stockName: "纳斯达克",
stockCode: "noCode",
currentPrice: "1657.90",
changeAmount: "-15.22",
changePercent: "-0.91%",
isRising: false,
},
{
flagIcon: "🇹🇭",
stockName: "标普500",
stockCode: "noCode",
currentPrice: "1457.90",
changeAmount: "+35.22",
changePercent: "+2.48%",
isRising: true,
},
]);
//
const onSearchInput = (e) => {
searchValue.value = e.detail.value;
};
//
const onSearchConfirm = (e) => {
console.log("搜索内容:", e.detail.value);
//
performSearch(e.detail.value);
};
//
const onSearchClick = () => {
if (searchValue.value.trim()) {
performSearch(searchValue.value);
}
};
//
const performSearch = (keyword) => {
if (!keyword.trim()) {
uni.showToast({
title: "请输入搜索内容",
icon: "none",
});
return;
}
uni.showToast({
title: `搜索: ${keyword}`,
icon: "none",
});
//
};
// warn
const checkWarnTextOverflow = () => {
nextTick(() => {
setTimeout(() => {
const query = uni.createSelectorQuery();
//
query.select(".warn_text_container").boundingClientRect();
query.select(".warn_text").boundingClientRect();
query.exec((res) => {
const containerRect = res[0];
const textRect = res[1];
if (!containerRect || !textRect) {
return;
}
//
const isOverflow = textRect.width > containerRect.width - 10;
isWarnTextOverflow.value = isOverflow;
});
}, 500);
});
};
//
const viewIndexDetail = (item) => {
console.log("查看指数详情:", item.stockName);
// uni.showToast({
// title: ` ${item.stockName} `,
// icon: 'none',
// duration: 2000
// })
//
uni.navigateTo({
url: `/pages/marketSituation/marketCondition?stockInformation=${encodeURIComponent(JSON.stringify(item))}`,
});
};
//
const goToGlobalIndex = () => {
uni.navigateTo({
url: "/pages/marketSituation/globalIndex",
});
};
onMounted(() => {
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
// DOM
nextTick(() => {
// header
uni
.createSelectorQuery()
.select(".header_fixed")
.boundingClientRect((rect) => {
if (rect) {
headerHeight.value = rect.height;
console.log("Header实际高度:", headerHeight.value, "px");
}
})
.exec();
// warn
checkWarnTextOverflow();
});
});
// headerHeightcontentHeight
watch(headerHeight, (newHeight) => {
if (newHeight > 0) {
const systemInfo = uni.getSystemInfoSync();
const windowHeight = systemInfo.windowHeight;
const statusBarHeight = systemInfo.statusBarHeight || 0;
const footerHeight = 100;
contentHeight.value = windowHeight - statusBarHeight - newHeight - footerHeight;
console.log("重新计算contentHeight:", contentHeight.value);
}
});
</script>
<style scoped>
/* 状态栏占位 */
.top {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1001;
background-color: #ffffff;
}
/* 固定头部样式 */
.header_fixed {
position: fixed;
left: 0;
right: 0;
z-index: 1000;
background-color: #ffffff;
padding: 20rpx 0 0 0;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
/* 可滚动内容区域 */
.content_scroll {
position: fixed;
left: 0;
right: 0;
bottom: 100rpx;
/* 底部导航栏高度 */
overflow-y: auto;
}
.header_content {
display: flex;
align-items: center;
justify-content: space-between;
height: 80rpx;
padding: 0 20rpx;
margin-bottom: 10rpx;
}
.header_input_wrapper {
display: flex;
align-items: center;
width: 100%;
margin: 0 20rpx 0 0;
height: 70rpx;
border-radius: 35rpx;
background-color: #ffffff;
border: 1rpx solid #e9ecef;
padding: 0 80rpx 0 30rpx;
font-size: 28rpx;
color: #5c5c5c;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
.search_icon {
width: 40rpx;
height: 40rpx;
opacity: 0.6;
}
.header_input {
margin-left: 10rpx;
}
.header_icons {
display: flex;
align-items: center;
gap: 15rpx;
}
.header_icon {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
}
.header_icon image {
width: 40rpx;
height: 40rpx;
}
/* Tab 栏样式 */
.channel_li {
display: flex;
align-items: center;
height: 80rpx;
background-color: #ffffff;
border-bottom: 1rpx solid #f0f0f0;
}
.channel_wrap {
width: calc(100% - 60rpx);
height: 100%;
overflow: hidden;
flex-shrink: 0;
}
.channel_innerWrap {
display: flex;
align-items: center;
height: 100%;
padding: 0 20rpx;
white-space: nowrap;
}
.channel_item {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 60rpx;
padding: 0 20rpx;
border-radius: 30rpx;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
white-space: nowrap;
flex-shrink: 0;
}
.channel_item:active {
transform: scale(0.98);
}
.channel_item.active {
color: #333;
font-weight: bold;
}
.channel_text {
font-size: 28rpx;
font-weight: 500;
color: #666666;
transition: color 0.3s ease;
white-space: nowrap;
}
.channel_item.active .channel_text {
color: #333333;
font-weight: 400;
z-index: 2;
}
.active_indicator {
position: absolute;
left: 50%;
top: 60%;
transform: translateX(-45%);
width: calc(100% - 20rpx);
min-width: 40rpx;
max-width: 120rpx;
height: 8rpx;
background-image: url("/static/marketSituation-image/bg.png");
background-size: cover;
background-position: center;
background-repeat: no-repeat;
animation: slideIn 0.1s ease;
border-radius: 8rpx;
z-index: 1;
}
@keyframes slideIn {
from {
width: 0;
opacity: 0;
}
to {
width: 40rpx;
opacity: 1;
}
}
.scroll_indicator {
border-left: 1rpx solid #b6b6b6;
display: flex;
align-items: center;
justify-content: center;
width: 60rpx;
height: 30rpx;
background-color: #ffffff;
flex-shrink: 0;
}
.scroll_indicator image {
width: 20rpx;
height: 20rpx;
opacity: 0.5;
}
.content {
margin-top: 20rpx;
background-color: white;
}
.map {
width: calc(100% - 60rpx);
margin: 0 30rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: #f3f3f3;
border-radius: 30rpx;
border: 1rpx solid #e0e0e0;
padding: 30rpx 20rpx;
box-sizing: border-box;
/* 设置最小高度保护,但允许内容撑开 */
min-height: 200rpx;
}
.map image {
width: 100%;
height: auto;
max-width: 100%;
display: block;
/* widthFix模式下,高度会自动按比例调整 */
/* 设置最大高度避免图片过大 */
max-height: 60vh;
/* 添加平滑过渡效果 */
transition: all 0.3s ease;
max-height: 60vh;
}
/* 响应式优化 */
@media screen and (max-width: 750rpx) {
.map {
margin: 0 20rpx;
width: calc(100% - 40rpx);
padding: 20rpx 15rpx;
}
}
@media screen and (max-width: 480rpx) {
.map {
margin: 0 15rpx;
width: calc(100% - 30rpx);
padding: 15rpx 10rpx;
}
}
.static-footer {
position: fixed;
bottom: 0;
}
/* 弹窗样式 */
.modal_overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: flex-end;
z-index: 1000;
}
.modal_content {
width: 100%;
background-color: #fff;
border-radius: 20rpx 20rpx 0 0;
max-height: 80vh;
overflow: hidden;
}
.modal_header {
position: relative;
display: flex;
justify-content: center;
align-items: center;
padding: 30rpx 40rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.modal_title {
font-size: 32rpx;
font-weight: bold;
color: #333333;
text-align: center;
}
.modal_close {
position: absolute;
right: 40rpx;
top: 50%;
transform: translateY(-50%);
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 40rpx;
color: #999;
}
.modal_body {
padding: 40rpx;
}
.country_grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20rpx;
}
.country_item {
padding: 24rpx 30rpx;
border-radius: 12rpx;
background-color: #f8f8f8;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.country_item.selected {
background-color: #ff4444;
color: #fff;
}
.country_text {
font-size: 28rpx;
color: #333;
}
.country_item.selected .country_text {
color: #fff;
}
.global_index {
margin: 30rpx 20rpx 0 20rpx;
display: flex;
justify-content: space-between;
}
.global_index_title {
margin-left: 20rpx;
font-size: 40rpx;
font-weight: 100;
color: #333333;
align-items: center;
}
.global_index_more {
display: flex;
gap: 10rpx;
font-size: 28rpx;
color: #333333;
align-items: center;
}
.global_index_more image {
width: 40rpx;
height: 40rpx;
align-items: center;
}
/* 卡片网格样式 */
.cards_grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
margin: 0;
box-sizing: border-box;
width: 100%;
padding: 30rpx 20rpx;
gap: 20rpx;
}
.card_item {
width: 100%;
box-sizing: border-box;
min-width: 0;
/* 防止内容溢出 */
}
/* 响应式布局 - 小屏幕时改为两列 */
@media (max-width: 600rpx) {
.cards_grid {
grid-template-columns: repeat(2, 1fr);
padding: 30rpx 20rpx;
}
}
/* 超小屏幕时改为单列 */
@media (max-width: 400rpx) {
.cards_grid {
grid-template-columns: 1fr;
padding: 30rpx 20rpx;
}
}
.warn {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 10rpx;
font-size: 28rpx;
color: #666666;
padding: 20rpx;
max-width: 100%;
overflow: hidden;
position: relative;
}
.warn image {
width: 40rpx;
height: 40rpx;
flex-shrink: 0;
/* 防止图片被压缩 */
position: relative;
z-index: 2;
/* 确保图片在最上层 */
}
.warn_text_container {
flex: 1;
overflow: hidden;
position: relative;
min-width: 0;
/* 允许容器收缩 */
}
.warn_text {
display: block;
white-space: nowrap;
will-change: transform;
/* 优化动画性能 */
}
/* 文字滚动动画 */
@keyframes scrollText {
0% {
transform: translateX(0);
}
20% {
transform: translateX(0);
}
80% {
transform: translateX(-85%);
}
100% {
transform: translateX(-85%);
}
}
/* 当文字超长时启用滚动动画 */
.warn_text.scroll-active {
animation: scrollText 12s linear infinite;
animation-delay: 2s;
/* 延迟2秒开始滚动,让用户先看到开头 */
}
/* 底部安全区域 */
.bottom_safe_area {
height: 40rpx;
background-color: transparent;
}
/* 主容器样式调整 */
.main {
position: relative;
height: 100vh;
overflow: hidden;
background-color: white;
}
</style>

379
pages/home/marketSituation.vue → pages/marketSituation/marketSituation.vue

@ -1,4 +1,5 @@
<template> <template>
<view>
<view class="main"> <view class="main">
<!-- 固定头部 --> <!-- 固定头部 -->
<view class="header_fixed" :style="{ top: iSMT + 'px' }"> <view class="header_fixed" :style="{ top: iSMT + 'px' }">
@ -38,37 +39,8 @@
<!-- 可滚动内容区域 --> <!-- 可滚动内容区域 -->
<scroll-view class="content_scroll" scroll-y="true" :style="{ top: contentTopPosition + 'px' }"> <scroll-view class="content_scroll" scroll-y="true" :style="{ top: contentTopPosition + 'px' }">
<view class="content">
<view class="map">
<image src="/static/marketSituation-image/map.png" mode="widthFix"></image>
</view>
<view class="global_index">
<view class="global_index_title">
{{ $t('marketSituation.globalIndex') }}
</view>
<view class="global_index_more" @click="goToGlobalIndex">
<text>{{ $t('marketSituation.globalIndexMore') }}</text>
<image src="/static/marketSituation-image/more.png" mode="aspectFit"></image>
</view>
</view>
<!-- 卡片网格 -->
<view class="cards_grid">
<view v-for="(card, index) in cardData" :key="index" class="card_item">
<IndexCard :flagIcon="card.flagIcon" :indexName="card.indexName"
:currentPrice="card.currentPrice" :changeAmount="card.changeAmount"
:changePercent="card.changePercent" :isRising="card.isRising" />
</view>
</view>
<view class="warn">
<image src="/static/marketSituation-image/warn.png" mode="aspectFit"></image>
<view class="warn_text_container">
<text :class="warnTextClass">{{ $t('marketSituation.warn') }}</text>
</view>
</view>
<!-- 底部安全区域防止被导航栏遮挡 -->
<view class="bottom_safe_area"></view>
</view>
<!-- 动态组件切换 -->
<component :is="currentComponent" :countryId="currentChannelId" />
</scroll-view> </scroll-view>
</view> </view>
@ -94,20 +66,21 @@
</view> </view>
</view> </view>
</view> </view>
</view>
</template> </template>
<script setup> <script setup>
import { ref, onMounted, watch, nextTick, computed } from 'vue' import { ref, onMounted, watch, nextTick, computed } from 'vue'
import util from '../../common/util.js'
import footerBar from '../../components/footerBar.vue' import footerBar from '../../components/footerBar.vue'
import IndexCard from '../../components/IndexCard.vue'
import forexMetals from './forexMetals.vue'
import marketOverview from './marketOverview.vue'
import countryMarket from './countryMarket.vue'
const type = ref('marketSituation') const type = ref('marketSituation')
const iSMT = ref(0) const iSMT = ref(0)
const searchValue = ref('') const searchValue = ref('')
const contentHeight = ref(0) const contentHeight = ref(0)
const headerHeight = ref(0) // header const headerHeight = ref(0) // header
const isWarnTextOverflow = ref(false) // warn
// Tab // Tab
const channelData = ref([ const channelData = ref([
@ -127,6 +100,28 @@ const channelData = ref([
const pageIndex = ref(0) const pageIndex = ref(0)
const scrollToView = ref('') const scrollToView = ref('')
//
const currentChannelId = computed(() => {
return channelData.value[pageIndex.value]?.id || 1
})
const currentComponent = computed(() => {
const channelId = currentChannelId.value
// 使 MarketOverview
if (pageIndex.value === 0) {
return marketOverview
}
// (id=11)(id=12)使 ForexMetals
else if (channelId === 11 || channelId === 12) {
return forexMetals
}
// /使 CountryMarket
else {
return countryMarket
}
})
// contenttop // contenttop
const contentTopPosition = computed(() => { const contentTopPosition = computed(() => {
const statusBarHeight = iSMT.value || 0 const statusBarHeight = iSMT.value || 0
@ -134,11 +129,6 @@ const contentTopPosition = computed(() => {
return statusBarHeight + currentHeaderHeight return statusBarHeight + currentHeaderHeight
}) })
// warnclass
const warnTextClass = computed(() => {
return isWarnTextOverflow.value ? 'warn_text scroll-active' : 'warn_text'
})
// //
const showCountryModal = ref(false) const showCountryModal = ref(false)
const selectedCountry = ref('概况') const selectedCountry = ref('概况')
@ -147,82 +137,6 @@ const countryList = ref([
'泰国', '中国', '加拿大', '越南', '外汇', '贵金属' '泰国', '中国', '加拿大', '越南', '外汇', '贵金属'
]) ])
//
const cardData = ref([
{
flagIcon: '🇺🇸',
indexName: '道琼斯',
currentPrice: '45757.90',
changeAmount: '-125.22',
changePercent: '-0.27%',
isRising: false
},
{
flagIcon: '🇺🇸',
indexName: '纳斯达克',
currentPrice: '22333.96',
changeAmount: '+125.22',
changePercent: '+0.47%',
isRising: true
},
{
flagIcon: '🇺🇸',
indexName: '标普500',
currentPrice: '6606.08',
changeAmount: '+125.22',
changePercent: '+0.27%',
isRising: true
},
{
flagIcon: '🇨🇳',
indexName: '上证指数',
currentPrice: '3333.96',
changeAmount: '+125.22',
changePercent: '+0.27%',
isRising: true
},
{
flagIcon: '🇨🇳',
indexName: '科创50',
currentPrice: '757.90',
changeAmount: '-25.22',
changePercent: '-0.27%',
isRising: false
},
{
flagIcon: '🇭🇰',
indexName: '恒生指数',
currentPrice: '19757.90',
changeAmount: '-125.22',
changePercent: '-0.63%',
isRising: false
},
{
flagIcon: '🇸🇬',
indexName: '道琼斯',
currentPrice: '3757.90',
changeAmount: '+85.22',
changePercent: '+2.31%',
isRising: true
},
{
flagIcon: '🇲🇾',
indexName: '纳斯达克',
currentPrice: '1657.90',
changeAmount: '-15.22',
changePercent: '-0.91%',
isRising: false
},
{
flagIcon: '🇹🇭',
indexName: '标普500',
currentPrice: '1457.90',
changeAmount: '+35.22',
changePercent: '+2.48%',
isRising: true
}
])
// //
const onSearchInput = (e) => { const onSearchInput = (e) => {
searchValue.value = e.detail.value searchValue.value = e.detail.value
@ -344,39 +258,6 @@ const closeModal = () => {
showCountryModal.value = false showCountryModal.value = false
} }
// warn
const checkWarnTextOverflow = () => {
nextTick(() => {
setTimeout(() => {
const query = uni.createSelectorQuery()
//
query.select('.warn_text_container').boundingClientRect()
query.select('.warn_text').boundingClientRect()
query.exec((res) => {
const containerRect = res[0]
const textRect = res[1]
if (!containerRect || !textRect) {
return
}
//
const isOverflow = textRect.width > (containerRect.width - 10)
isWarnTextOverflow.value = isOverflow
})
}, 500)
})
}
//
const goToGlobalIndex = () => {
uni.navigateTo({
url: '/pages/home/globalIndex'
})
}
onMounted(() => { onMounted(() => {
// //
iSMT.value = uni.getSystemInfoSync().statusBarHeight; iSMT.value = uni.getSystemInfoSync().statusBarHeight;
@ -387,14 +268,6 @@ onMounted(() => {
scrollToView.value = 'nav' + channelData.value[0].id scrollToView.value = 'nav' + channelData.value[0].id
} }
util.request('link/api/brain/privilege', (res) => {
console.log(res)
},{
'token': '9ior41AF0xTIbIG2pRnnbZi0+fEeMx8pywnIlrmTwo5FbqJ9lWrSWOxp9MkpKiNtedtUafqvzIwpFKrwuMs'
}, (err) => {
console.log(err)
})
// DOM // DOM
nextTick(() => { nextTick(() => {
// header // header
@ -404,9 +277,6 @@ onMounted(() => {
console.log('Header实际高度:', headerHeight.value, 'px') console.log('Header实际高度:', headerHeight.value, 'px')
} }
}).exec() }).exec()
// warn
checkWarnTextOverflow()
}) })
}) })
@ -425,6 +295,14 @@ watch(headerHeight, (newHeight) => {
</script> </script>
<style scoped> <style scoped>
/* 主容器样式调整 */
.main {
position: relative;
height: 100vh;
overflow: hidden;
background-color: white;
}
/* 状态栏占位 */ /* 状态栏占位 */
.top { .top {
position: fixed; position: fixed;
@ -623,51 +501,6 @@ watch(headerHeight, (newHeight) => {
background-color: white; background-color: white;
} }
.map {
width: calc(100% - 60rpx);
margin: 0 30rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: #F3F3F3;
border-radius: 30rpx;
border: 1rpx solid #E0E0E0;
padding: 30rpx 20rpx;
box-sizing: border-box;
/* 设置最小高度保护,但允许内容撑开 */
min-height: 200rpx;
}
.map image {
width: 100%;
height: auto;
max-width: 100%;
display: block;
/* widthFix模式下,高度会自动按比例调整 */
/* 设置最大高度避免图片过大 */
max-height: 60vh;
/* 添加平滑过渡效果 */
transition: all 0.3s ease;
max-height: 60vh;
}
/* 响应式优化 */
@media screen and (max-width: 750rpx) {
.map {
margin: 0 20rpx;
width: calc(100% - 40rpx);
padding: 20rpx 15rpx;
}
}
@media screen and (max-width: 480rpx) {
.map {
margin: 0 15rpx;
width: calc(100% - 30rpx);
padding: 15rpx 10rpx;
}
}
.static-footer { .static-footer {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
@ -757,142 +590,4 @@ watch(headerHeight, (newHeight) => {
.country_item.selected .country_text { .country_item.selected .country_text {
color: #fff; color: #fff;
} }
.global_index {
margin: 30rpx 20rpx 0 20rpx;
display: flex;
justify-content: space-between;
}
.global_index_title {
margin-left: 20rpx;
font-size: 40rpx;
font-weight: 100;
color: #333333;
align-items: center;
}
.global_index_more {
display: flex;
gap: 10rpx;
font-size: 28rpx;
color: #333333;
align-items: center;
}
.global_index_more image {
width: 40rpx;
height: 40rpx;
align-items: center;
}
/* 卡片网格样式 */
.cards_grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
margin: 0;
box-sizing: border-box;
width: 100%;
}
.card_item {
width: 100%;
box-sizing: border-box;
min-width: 0;
/* 防止内容溢出 */
}
/* 响应式布局 - 小屏幕时改为两列 */
@media (max-width: 600rpx) {
.cards_grid {
grid-template-columns: repeat(2, 1fr);
padding: 30rpx 20rpx;
}
}
/* 超小屏幕时改为单列 */
@media (max-width: 400rpx) {
.cards_grid {
grid-template-columns: 1fr;
padding: 30rpx 20rpx;
}
}
.warn {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 10rpx;
font-size: 28rpx;
color: #666666;
padding: 20rpx;
max-width: 100%;
overflow: hidden;
position: relative;
}
.warn image {
width: 40rpx;
height: 40rpx;
flex-shrink: 0;
/* 防止图片被压缩 */
position: relative;
z-index: 2;
/* 确保图片在最上层 */
}
.warn_text_container {
flex: 1;
overflow: hidden;
position: relative;
min-width: 0;
/* 允许容器收缩 */
}
.warn_text {
display: block;
white-space: nowrap;
will-change: transform;
/* 优化动画性能 */
}
/* 文字滚动动画 */
@keyframes scrollText {
0% {
transform: translateX(0);
}
20% {
transform: translateX(0);
}
80% {
transform: translateX(-85%);
}
100% {
transform: translateX(-85%);
}
}
/* 当文字超长时启用滚动动画 */
.warn_text.scroll-active {
animation: scrollText 12s linear infinite;
animation-delay: 2s;
/* 延迟2秒开始滚动,让用户先看到开头 */
}
/* 底部安全区域 */
.bottom_safe_area {
height: 40rpx;
background-color: transparent;
}
/* 主容器样式调整 */
.main {
position: relative;
height: 100vh;
overflow: hidden;
background-color: white;
}
</style> </style>

86
pages/setting/about.vue

@ -0,0 +1,86 @@
<template>
<view class="main">
<view :style="{height:iSMT+'px'}"></view>
<view style="height:1.5vh" />
<view class="top">
<img src="/static/my/aboutDC.png"></img>
</view>
<view class="bottom">
<view class="bottom-list" @click="goToIntroduce">
<text class="label">产品介绍</text>
<uni-icons type="arrowright" size="16" />
</view>
<view class="bottom-list">
<text class="label">免责声明</text>
<uni-icons type="arrowright" size="16" />
</view>
<view class="bottom-list">
<text class="label">隐私政策</text>
<uni-icons type="arrowright" size="16" />
</view>
<view class="bottom-list">
<text class="label">服务协议</text>
<uni-icons type="arrowright" size="16" />
</view>
<view class="bottom-list">
<text class="label">鼓励一下</text>
<uni-icons type="arrowright" size="16" />
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
const iSMT = ref(0)
const goToIntroduce = () =>{
uni.navigateTo({
url: '../setting/introduce'
})
}
onMounted(() => {
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
console.log('看看高度', iSMT.value)
})
</script>
<style>
.top {
height: 23vh;
display: flex;
justify-content: center;
align-items: center;
background-color: white;
}
.bottom {
height: 35vh;
background-color: white;
}
.bottom-list {
width: 630rpx;
height: 7vh;
display: flex;
align-items: center;
justify-content: center;
margin: 0 40rpx;
padding: 0 10rpx;
border-bottom: 1rpx solid #eee;
}
.bottom-list:last-child {
border-bottom: none;
}
.label{
flex:1;
}
</style>

218
pages/setting/account.vue

@ -0,0 +1,218 @@
<template>
<view class="main">
<view :style="{height:iSMT+'px'}"></view>
<view style="height:1.5vh;"></view>
<view class="setting-list">
<view class="setting-item">
<text class="item-label">头像</text>
<view class="item-right">
<image src="/static/avatar.png" class="avatar" mode="aspectFill"></image>
<uni-icons type="arrowright" size="16" class="arrow"></uni-icons>
</view>
</view>
<view class="setting-item">
<text class="item-label">昵称</text>
<view class="item-right">
<text class="item-text">DeepChart</text>
<uni-icons type="arrowright" size="16" class="arrow"></uni-icons>
</view>
</view>
<view class="setting-item">
<text class="item-label">ID</text>
<view class="item-right">
<text class="item-text">{{ jwcode }}</text>
<uni-icons type="arrowright" size="16" class="arrow"></uni-icons>
</view>
</view>
<view class="setting-item">
<text class="item-label">密码</text>
<view class="item-right">
<text class="item-text">qwertyuiop</text>
<uni-icons type="eye" size="16" class="eye-icon"></uni-icons>
</view>
</view>
<view class="setting-item" @click="goToPassword">
<text class="item-label">修改密码</text>
<uni-icons type="arrowright" size="16" class="arrow"></uni-icons>
</view>
<view class="setting-item">
<text class="item-label">注销账号</text>
<uni-icons type="arrowright" size="16" class="arrow"></uni-icons>
</view>
<view class="setting-item" @click="goToBind">
<text class="item-label">绑定账号</text>
<uni-icons type="arrowright" size="16" class="arrow"></uni-icons>
</view>
</view>
<view style="height:1.5vh;"></view>
<view class="logout" @click="showLogout = true">
<text>退出登录</text>
</view>
<view class="logout-confirm" v-if="showLogout">
<view class="logoutDialog">
<view class="tips">是否退出登录</view>
<view class="tips-button">
<button class="confirm-btn" @click="handleConfirmLogout">确认</button>
<button class="cancel-btn" @click="showLogout = false">取消</button>
</view>
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
import {useUserStore} from "../../stores/modules/userInfo"
const iSMT = ref(0)
const jwcode = ref('90047681')
const showLogout = ref(false)
const userStore = useUserStore()
const handleConfirmLogout = () => {
userStore.clearUserInfo()
showLogout.value = false
uni.showToast({
title: '退出登录成功',
icon: 'none',
})
}
const goToBind = () =>{
uni.navigateTo({
url:'../setting/bind'
})
}
const goToPassword = () =>{
uni.navigateTo({
url:'../setting/password'
})
}
onMounted(() => {
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
})
</script>
<style scoped>
.setting-list {
height: 49vh;
background-color: #fff;
}
.setting-item {
display: flex;
align-items: center;
height: 7vh;
padding: 0 40rpx;
border-bottom: 1rpx solid #eee;
}
.setting-item:last-child {
border-bottom: none;
}
.item-label {
font-size: 28rpx;
flex: 1;
}
.item-right {
display: flex;
align-items: center;
}
.avatar {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
margin-right: 20rpx;
background-color: #999;
}
.item-text {
margin-right: 20rpx;
font-size: 28rpx;
}
.arrow,
.eye-icon {
color: #999;
}
.logout {
display: flex;
justify-content: center;
align-items: center;
height: 7vh;
font-size: 28rpx;
color: #f56c6c;
background-color: white;
}
.logout-confirm {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 999;
}
.logoutDialog {
width: 500rpx;
background-color: #fff;
border-radius: 12rpx;
padding: 40rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.tips {
font-size: 32rpx;
margin-bottom: 40rpx;
}
.tips-button {
display: flex;
width: 100%;
justify-content: space-between;
}
.confirm-btn,
.cancel-btn {
width: 180rpx;
height: 60rpx;
line-height: 60rpx;
border-radius: 30rpx;
font-size: 28rpx;
}
.confirm-btn {
background-color: #fff;
color: #000;
border: 1rpx solid #ddd;
}
.cancel-btn {
background-color: #000;
color: #fff;
border: none;
}
</style>

85
pages/setting/bind.vue

@ -0,0 +1,85 @@
<template>
<view class="main">
<view :style="{height:iSMT+'px'}"></view>
<view style="height:1.5vh;" />
<view class="top">
<view class="top-list" @click="goToBindPhone">
<text class="label">手机号</text>
<view class="right">
<text style="font-size: 28rpx;">未绑定</text>
<uni-icons type="arrowright" size="16" />
</view>
</view>
<view class="top-list" @click="goToBindEmail">
<text class="label">邮箱</text>
<view class="right">
<text style="font-size: 28rpx;">analsak@163.com</text>
<uni-icons type="arrowright" size="16" />
</view>
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
const iSMT = ref(0)
const goToBindPhone = () =>{
uni.navigateTo({
url:'../setting/phone'
})
}
const goToBindEmail = () =>{
uni.navigateTo({
url:'../setting/email'
})
}
onMounted(() => {
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
console.log('看看高度', iSMT.value)
})
</script>
<style>
.top {
height: 14vh;
background-color: white;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.top-list {
width: 630rpx;
height: 7vh;
margin: 0rpx 40rpx;
display: flex;
align-items: center;
border-bottom: 1rpx solid #eee;
}
.top-list:last-child {
border: none;
}
.label {
font-size: 28rpx;
flex: 1;
}
.right{
display: flex;
align-items: center;
justify-content: center;
}
</style>

142
pages/setting/email.vue

@ -0,0 +1,142 @@
<template>
<view class="main">
<view :style="{height:iSMT+'px'}"></view>
<view style="height:1.5vh;" />
<view class="top">
<view class="top-list">
<view class="left">
<img src="/static/my/bindedEmail.png" />
<text class="label">已绑邮箱{{ email }}</text>
</view>
</view>
<view class="top-list">
<view class="left">
<img src="/static/my/changeEmail.png" />
<text class="label">+86</text>
<input type="number" placeholder="请输入您的换绑邮箱" class="input" />
</view>
<view class="right">
<button class="verification" :class="{ 'disabled': gettingCode }" @click="getVerification"
:disabled="gettingCode">
{{ gettingCode ? `重新发送 ${time}s` : '获取验证码' }}
</button>
</view>
</view>
<view class="top-list">
<view class="left">
<img src="/static/my/verification.png" />
<input type="text" placeholder="请输入验证码" class="input" />
</view>
</view>
</view>
<view class="bottom">
<button class="change-btn">换绑</button>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
const iSMT = ref(0)
const email = ref('analsak@16.com')
const gettingCode = ref(false)
const time = ref(60)
const getVerification = () => {
if (gettingCode.value) return
gettingCode.value = true
time.value = 60
const timer = setInterval(() => {
time.value--
if (time.value <= 0) {
clearInterval(timer)
gettingCode.value = false
}
}, 1000)
}
onMounted(() => {
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
console.log('看看高度', iSMT.value)
})
</script>
<style>
.top {
height: auto;
background-color: white;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.top-list {
width: 630rpx;
height: 7vh;
margin: 0rpx 40rpx;
display: flex;
align-items: center;
border-bottom: 1rpx solid #eee;
}
.left {
flex: 1;
display: flex;
align-items: center;
}
.label {
font-size: 28rpx;
margin-left: 10rpx;
}
.right {
display: flex;
align-items: center;
justify-content: center;
}
.input {
flex: 1;
height: 70rpx;
font-size: 29rpx;
margin-left: 20rpx;
}
.verification {
font-size: 24rpx;
border-radius: 10rpx;
background-color: rgb(230, 230, 230);
}
.bottom {
height: 22vh;
background-color: white;
display: flex;
align-items: center;
justify-content: center;
}
.change-btn {
height: 85rpx;
width: 610rpx;
padding:0 20rpx;
background-color: black;
color: white;
border-radius: 40rpx;
display: flex;
align-items: center;
justify-content: center;
}
</style>

68
pages/setting/font.vue

@ -0,0 +1,68 @@
<template>
<view class="main">
<view :style="{height:iSMT+'px'}"></view>
<view class="top">
<view class="top-list">
<text>标准</text>
<radio value="0" class="radio-btn" activeBackgroundColor="red"
:checked="selectedIndex === 0" @click="selectFont(0)" />
</view>
<view class="top-list">
<text>中号</text>
<radio value="1" class="radio-btn" activeBackgroundColor="red"
:checked="selectedIndex === 1" @click="selectFont(1)" />
</view>
<view class="top-list">
<text>大号</text>
<radio value="2" class="radio-btn" activeBackgroundColor="red"
:checked="selectedIndex === 2" @click="selectFont(2)" />
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
const iSMT = ref(0)
const selectedIndex = ref(0)
const selectFont = (index) => {
selectedIndex.value = index
console.log('看看选中状态',selectedIndex.value)
}
onMounted(() => {
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
console.log('看看高度', iSMT.value)
})
</script>
<style>
.top {
margin-top: 1.5vh;
height: 21vh;
background-color: white;
}
.top-list {
width: 630rpx;
height: 7vh;
display: flex;
align-items: center;
margin: 0 40rpx;
padding:0 10rpx;
border-bottom: 1rpx solid #eee;
}
.top-list:last-child {
border-bottom: none;
}
.radio-btn {
margin-left: auto;
transform: scale(0.6);
}
</style>

165
pages/setting/general.vue

@ -0,0 +1,165 @@
<template>
<view class="main">
<view :style="{height:iSMT+'px'}"></view>
<view class="top">
<view class="top-list">
<text>语言</text>
<text class="language">中文简体</text>
<uni-icons type="arrowright" size="16" class="arrow" />
</view>
<view class="top-list" @click="goToFont">
<text>字体大小</text>
<uni-icons type="arrowright" size="16" class="arrow" />
</view>
<view class="top-list" @click="goToTheme">
<text>主题切换</text>
<uni-icons type="arrowright" size="16" class="arrow" />
</view>
</view>
<view class="center">
<view class="center-list" @click="goToMessage">
<text>消息推送</text>
<uni-icons type="arrowright" size="16" class="arrow" />
</view>
</view>
<view class="bottom">
<view class="bottom-list" @click="goToServer">
<text>切换服务器</text>
<uni-icons type="arrowright" size="16" class="arrow" />
</view>
<view class="bottom-list" @click="clearCache">
<text>清理缓存</text>
<text class="cache">{{ cache }}M</text>
<uni-icons type="arrowright" size="16" class="arrow" />
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
const iSMT = ref(0)
const cache = ref('45.5')
const goToFont = () => {
uni.navigateTo({
url: '/pages/setting/font'
})
}
const goToTheme = () => {
uni.navigateTo({
url: '/pages/setting/theme'
})
}
const goToMessage = () => {
uni.navigateTo({
url: '/pages/setting/message'
})
}
const goToServer = () => {
uni.navigateTo({
url: '/pages/setting/server'
})
}
const clearCache = () => {
cache.value = 0
uni.showToast({
title: '清理成功',
icon: 'success',
duration: 1500
})
}
onMounted(() => {
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
console.log('看看高度', iSMT.value)
})
</script>
<style>
.top {
margin-top: 1.5vh;
height: 21vh;
background-color: white;
}
.top-list {
width: 630rpx;
height: 7vh;
display: flex;
align-items: center;
margin: 0 40rpx;
padding: 0 10rpx;
border-bottom: 1rpx solid #eee;
}
.top-list:last-child {
border-bottom: none;
}
.language {
margin-left: 55%;
font-size: 14px;
color: rgb(203, 203, 203);
}
.arrow {
margin-left: auto;
}
.center {
background-color: white;
height: 7vh;
display: flex;
align-items: center;
margin-top: 1vh;
}
.center-list {
width: 630rpx;
margin: 0rpx 40rpx;
display: flex;
padding: 0 10rpx;
}
.center-list>.arrow {
margin-right: 0;
}
.bottom {
height: 13.5vh;
background-color: white;
margin-top: 1vh;
}
.bottom-list {
width: 630rpx;
height: 7vh;
display: flex;
align-items: center;
margin: 0 40rpx;
padding: 0 10rpx;
border-bottom: 1rpx solid #eee;
}
.cache {
margin-left: 55%;
font-size: 14px;
color: rgb(203, 203, 203);
}
.bottom-list:last-child {
border-bottom: none;
}
</style>

76
pages/setting/introduce.vue

@ -0,0 +1,76 @@
<template>
<view class="main">
<view :style="{height:iSMT+'px'}"></view>
<view style="height:1.5vh" />
<view class="top">
<img src="/static/my/aboutDC.png"></img>
</view>
<view class="bottom">
<view class="title">1.产品定位</view>
<view class="main-text">DeepChart全球最懂机构行为的AI你的AI投资伙伴强化"深度分析"
的品牌标签DeepChart=全球最懂机构行为的AI主打"深度解读机构行为"的APP</view>
<view class="title">2.产品介绍</view>
<view class="main-text">DeepChart是一款以"Al智能体为决策核心的智能投资分析平台
专注于深度研究机构行为专为全球散户投资者量身打造它重新定义了人与投资工具之间的关系
是一个真正懂投资懂市场更懂用户的AI投资伙伴</view>
<view class="title">3.产品理念</view>
<view class="main-text">人找信息AI智能体替你思考和管理</view>
<view class="title">4.功能定位全景AI决策体系</view>
<view class="main-text">黄其振是大笨蛋</view>
<view class="main-text">李建霖是大笨蛋</view>
<view class="main-text">double是大笨蛋</view>
<view class="main-text">张鲁平是大笨蛋</view>
<view style="height:1.5vh;background-color: white;" />
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
const iSMT = ref(0)
onMounted(() => {
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
console.log('看看高度', iSMT.value)
})
</script>
<style>
.top {
height: 26vh;
display: flex;
justify-content: center;
align-items: center;
background-color: white;
}
.bottom {
height: 35vh;
padding: 0 60rpx;
background-color: white;
height: auto;
}
.title {
font-size: 30rpx;
font-weight: bold;
margin-bottom: 20rpx;
}
.main-text {
font-size: 27rpx;
margin-bottom: 20rpx;
color: rgb(122, 122, 122);
text-align: justify;
text-justify: inter-character;/* 两端对齐哈哈哈哈 */
}
</style>

221
pages/setting/market.vue

@ -0,0 +1,221 @@
<template>
<view class="main">
<view :style="{height:iSMT+'px'}"></view>
<view class="time-share-title">
<text>分时设计</text>
</view>
<view style="height:57.5vh;background-color: white;">
<view class="title">A股竞价</view>
<view class="top-options">
<view class="option-btn" :class="{ 'active': aStockBid === 0 }" @click="aStockBid = 0">
<text>智能开启</text>
<view class="active-dot" v-if="aStockBid === 0"></view>
</view>
<view class="option-btn" :class="{ 'active': aStockBid === 1 }" @click="aStockBid = 1">
<text>保持开启</text>
<view class="active-dot" v-if="aStockBid === 1"></view>
</view>
<view class="option-btn" :class="{ 'active': aStockBid === 2 }" @click="aStockBid = 2">
<text>保持关闭</text>
<view class="active-dot" v-if="aStockBid === 2"></view>
</view>
</view>
<view class="title">K线样式</view>
<view class="top-options">
<view class="option-btn" :class="{ 'active': kStyle === 0 }" @click="kStyle = 0">
<img src="/static/my/common.png" class="kline-icon" />
<text>普通</text>
<view class="active-dot" v-if="kStyle === 0"></view>
</view>
<view class="option-btn" :class="{ 'active': kStyle === 1 }" @click="kStyle = 1">
<img src="/static/my/outline.png" class="kline-icon" />
<text>轮廓图</text>
<view class="active-dot" v-if="kStyle === 1"></view>
</view>
<view class="option-btn" :class="{ 'active': kStyle === 2 }" @click="kStyle = 2">
<img src="/static/my/polylines.png" class="kline-icon" />
<text>折线图</text>
<view class="active-dot" v-if="kStyle === 2"></view>
</view>
</view>
<view class="title">除权类型</view>
<view class="top-options">
<view class="option-btn" :class="{ 'active': exRights === 0 }" @click="exRights = 0">
<text>除权</text>
<view class="active-dot" v-if="exRights === 0"></view>
</view>
<view class="option-btn" :class="{ 'active': exRights === 1 }" @click="exRights = 1">
<text>普通</text>
<view class="active-dot" v-if="exRights === 1"></view>
</view>
<view class="option-btn" :class="{ 'active': exRights === 2 }" @click="exRights = 2">
<text>加权</text>
<view class="active-dot" v-if="exRights === 2"></view>
</view>
</view>
<view class="title">涨跌颜色</view>
<view class="top-options">
<view class="option-btn" :class="{ 'active': rfColor === 0 }" @click="rfColor = 0">
<view class="color-icon">
<img src="/static/my/greenRise.png" class="kline-icon" />
</view>
<text>绿涨红跌</text>
<view class="active-dot" v-if="rfColor === 0"></view>
</view>
<view class="option-btn" :class="{ 'active': rfColor === 1 }" @click="rfColor = 1">
<view class="color-icon">
<img src="/static/my/redRise.png" class="kline-icon" />
</view>
<text>红涨绿跌</text>
<view class="active-dot" v-if="rfColor === 1"></view>
</view>
</view>
<view class="title">副图指标个数</view>
<view class="top-options">
<view class="option-btn" :class="{ 'active': indexCount === 0 }" @click="indexCount = 0">
<text>1</text>
</view>
<view class="option-btn" :class="{ 'active': indexCount === 1 }" @click="indexCount = 1">
<text>2</text>
</view>
<view class="option-btn" :class="{ 'active': indexCount === 2 }" @click="indexCount = 2">
<text>3</text>
</view>
</view>
</view>
<view class="indicator-title">
<text>指标设置</text>
</view>
<view class="indicator-list">
<view class="indicator-item" v-for="(item, index) in indicatorList" :key="index">
<text class="indicator-text">{{ item }}</text>
<view class="indicator-icons">
<img src="/static/my/setting.png" class="icon" />
<img src="/static/my/menu.png" class="icon" />
</view>
</view>
<view style="height:10vh;background-color: white;"></view>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
const iSMT = ref(0)
const aStockBid = ref(0) // Stock bidding
const kStyle = ref(0) // k线
const exRights = ref(0) // Ex-rights
const rfColor = ref(0) // rise-fall
const indexCount = ref(0) //
const indicatorList = ref(['K线', '均线', '成交量', 'KDJ', 'MACD', 'RSI'])
onMounted(() => {
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
})
</script>
<style scoped>
.time-share-title {
height: 4.5vh;
padding: 0 40rpx;
display: flex;
align-items: center;
}
.title {
height: 5.5vh;
padding: 0 40rpx;
display: flex;
align-items: center;
font-size: 26rpx;
color: #666;
}
.top-options {
height: 5.5vh;
display: flex;
padding: 0 40rpx;
}
.option-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
border: 1rpx solid #ddd;
border-radius: 8rpx;
margin: 0 10rpx;
padding: 15rpx 0;
font-size: 28rpx;
}
.option-btn.active {
border-color: red;
}
.active-dot {
width: 16rpx;
height: 16rpx;
background-color: red;
border-radius: 50%;
margin-left: 10rpx;
}
.kline-icon {
margin-right: 10rpx;
font-size: 32rpx;
}
.color-icon {
margin-right: 10rpx;
display: flex;
gap: 4rpx;
}
.indicator-title {
height: 6vh;
padding: 0 40rpx;
display: flex;
align-items: center;
}
.indicator-list {
display: flex;
flex-direction: column;
justify-content: center;
padding: 0 40rpx;
background-color: white;
}
.indicator-item {
display: flex;
align-items: center;
height: 7.5vh;
border-bottom: 1rpx solid #eee;
}
.indicator-text {
font-size: 28rpx;
}
.indicator-icons {
display: flex;
gap: 100rpx;
margin-left: auto;
}
.icon {
width: 28rpx;
height: 28rpx;
}
</style>

62
pages/setting/message.vue

@ -0,0 +1,62 @@
<template>
<view class="main">
<view :style="{height:iSMT+'px'}"></view>
<view class="top">
<view class="top-list" @click="goToPush">
<text>语言</text>
<text class="message" v-if="isMessage">通知已开启</text>
<text class="message" v-if="!isMessage">通知未开启</text>
<uni-icons type="arrowright" size="16" class="arrow" />
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
const iSMT = ref(0)
const isMessage = ref(true)
const goToPush = () =>{
uni.navigateTo({
url: '/pages/setting/push'
})
}
onMounted(() => {
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
console.log('看看高度', iSMT.value)
})
</script>
<style>
.top {
margin-top: 1.5vh;
height: 7vh;
background-color: white;
display: flex;
justify-content: center;
}
.top-list {
width: 630rpx;
height: 7vh;
display: flex;
align-items: center;
margin: 0rpx 40rpx;
}
.message {
margin-left: 60%;
font-size: 14px;
color: rgb(203, 203, 203);
}
.arrow {
margin-left: auto;
}
</style>

82
pages/setting/newVersion.vue

@ -0,0 +1,82 @@
<template>
<view class="main">
<view :style="{height:iSMT+'px'}"></view>
<view style="height:1.5vh;" />
<view class="top">
<view class="top-list">
<text v-if="hasNew === true" class="label">已有新版本</text>
<text v-if="hasNew === false" class="label">已是最新版本</text>
<view class="right">
<text style="font-size: 28rpx;">{{ version }}</text>
</view>
</view>
</view>
<view style="height:1vh;" />
<view class="bottom">
<button v-if="hasNew === true" class="bottom-btn">立即更新</button>
<button v-if="hasNew === false" class="bottom-btn" disabled
style="background-color: rgb(204,204,204);color:white;">暂无更新</button>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
const iSMT = ref(0)
const hasNew = ref(true)
const version = ref('2.0')
onMounted(() => {
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
console.log('看看高度', iSMT.value)
})
</script>
<style>
.top {
height: 7vh;
background-color: white;
display: flex;
justify-content: center;
}
.top-list {
width: 630rpx;
height: 7vh;
display: flex;
align-items: center;
margin: 0rpx 40rpx;
}
.label {
font-size: 28rpx;
flex: 1;
}
.bottom {
height: 11vh;
background-color: white;
padding: 0 50rpx;
display: flex;
justify-content: center;
align-items: center;
}
.bottom-btn {
width: 670rpx;
height: 84rpx;
border-radius: 40rpx;
background-color: #000;
color: #fff;
font-size: 28rpx;
display: flex;
align-items: center;
justify-content: center;
}
</style>

144
pages/setting/nextPwd.vue

@ -0,0 +1,144 @@
<template>
<view class="main">
<view :style="{height:iSMT+'px'}"></view>
<view style="height:1.5vh;" />
<view class="title">
<text class="label">确认新密码</text>
</view>
<view class="top">
<view class="top-list">
<view class="left">
<img src="/static/my/unlock.png" />
<input type="password" :type="pwdType" placeholder="请输入新密码" class="input" />
<img :src="pwdType === 1 ? '/static/my/hideEye.png' : '/static/my/openEye.png'"
@click="changeEye(1)" />
</view>
</view>
<view class="top-list">
<view class="left">
<img src="/static/my/unlock.png" />
<input type="password" :type="pwdType2" placeholder="再次确认" class="input" />
<img :src="pwdType === 1 ? '/static/my/hideEye.png' : '/static/my/openEye.png'"
@click="changeEye(2)" />
</view>
</view>
<text class="tips">密码最少8位数</text>
</view>
<view class="bottom">
<button class="change-btn">确认</button>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
const iSMT = ref(0)
const pwdType = ref('password')
const pwdType2 = ref('password')
const changeEye = (type) => {
if (type === 1) {
pwdType.value = pwdType.value === 'password' ? 'text' : 'password'
} else {
pwdType2.value = pwdType2.value === 'password' ? 'text' : 'password'
}
}
onMounted(() => {
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
console.log('看看高度', iSMT.value)
})
</script>
<style>
.title {
height: 8.5vh;
background-color: white;
}
.label {
height: 8.5vh;
font-size: 40rpx;
font-weight: bold;
display: flex;
align-items: center;
padding: 0 60rpx;
}
.top {
height: auto;
background-color: white;
display: flex;
flex-direction: column;
align-items: center;
}
.top-list {
width: 630rpx;
height: 7vh;
margin: 0rpx 40rpx;
display: flex;
align-items: center;
border-bottom: 1rpx solid #eee;
}
.left {
flex: 1;
display: flex;
align-items: center;
}
.input {
flex: 1;
height: 70rpx;
font-size: 29rpx;
margin-left: 20rpx;
}
.bottom {
height: 22vh;
background-color: white;
display: flex;
align-items: center;
justify-content: center;
}
.change-btn {
height: 85rpx;
width: 610rpx;
padding: 0 20rpx;
background-color: black;
color: white;
border-radius: 40rpx;
display: flex;
align-items: center;
justify-content: center;
}
.img {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
}
.tips {
font-size: 24rpx;
color: #999;
margin-top: 20rpx;
margin-left: 60rpx;
align-self: flex-start;
/* 这是左对齐 */
}
</style>

171
pages/setting/password.vue

@ -0,0 +1,171 @@
<template>
<view class="main">
<view :style="{height:iSMT+'px'}"></view>
<view class="tab">
<view class="tab-item" :class="{active: activeTab === 'email'}" @click="activeTab = 'email'">邮箱</view>
<view class="tab-item" :class="{active: activeTab === 'phone'}" @click="activeTab = 'phone'">手机号</view>
</view>
<view class="switch-tab">
<view class="input-list" v-if="activeTab === 'email'">
<image src="/static/my/changeEmail.png" mode="aspectFit"></image>
<input type="text" placeholder="请输入邮箱" class="input" />
<button class="code-btn" :class="{disabled: gettingCode}" @click="getCode" :disabled="gettingCode">
{{ gettingCode ? `重新发送 ${time}s` : '获取验证码' }}
</button>
</view>
<view class="input-list" v-else>
<image src="/static/my/changeBindPhone.png" mode="aspectFit"></image>
<input type="number" placeholder="请输入手机号" class="input" />
<button class="code-btn" :class="{disabled: gettingCode}" @click="getCode" :disabled="gettingCode">
{{ gettingCode ? `重新发送 ${time}s` : '获取验证码' }}
</button>
</view>
<view class="input-list">
<image src="/static/my/verification.png" mode="aspectFit"></image>
<input type="text" placeholder="请输入验证码" class="input" />
</view>
</view>
<view class="btn-area">
<button class="next-btn" @click="goToPwdNext">下一步</button>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
const iSMT = ref(0)
const activeTab = ref('email')
const gettingCode = ref(false)
const time = ref(60)
const getCode = () => {
if (gettingCode.value) return
gettingCode.value = true
time.value = 60
const timer = setInterval(() => {
time.value--
if (time.value <= 0) {
clearInterval(timer)
gettingCode.value = false
time.value = 60
}
}, 1000)
}
const goToPwdNext = () =>{
uni.navigateTo({
url:'../setting/nextPwd'
})
}
onMounted(() => {
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
})
</script>
<style>
.tab {
display: flex;
height: 8vh;
background-color: #fff;
border-bottom: 1rpx solid #eee;
}
.tab-item {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
font-size: 32rpx;
position: relative;
}
.tab-item.active {
color: #000;
font-weight: bold;
}
.tab-item.active::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 40rpx;
height: 6rpx;
background-color: #000;/* ????? */
}
.switch-tab {
background-color: #fff;
padding: 0 60rpx;
}
.input-list {
display: flex;
align-items: center;
justify-content: center;
height: 7vh;
border-bottom: 1rpx solid #eee;
}
.input-list image {
width: 40rpx;
height: 40rpx;
margin-right: 20rpx;
}
.input {
flex: 1;
height: 14vh;
font-size: 28rpx;
}
.code-btn {
width: 200rpx;
height: 60rpx;
font-size: 24rpx;
border-radius: 10rpx;
background-color: #eee;
color: #666;
display: flex;
align-items: center;
justify-content: center;
}
.code-btn.disabled {
background-color: #ccc;
color: #999;
}
.btn-area{
height:8vh;
background-color: white;
padding-top: 120rpx;
}
.next-btn {
width: 610rpx;
height: 85rpx;
background-color: #000;
color: #fff;
font-size: 30rpx;
border-radius: 40rpx;
display: flex;
align-items: center;
justify-content: center;
}
</style>

143
pages/setting/phone.vue

@ -0,0 +1,143 @@
<template>
<view class="main">
<view :style="{height:iSMT+'px'}"></view>
<view style="height:1.5vh;" />
<view class="top">
<view class="top-list">
<view class="left">
<img src="/static/my/bindedPhone.png" />
<text class="label">已绑手机号{{ phone }}</text>
</view>
</view>
<view class="top-list">
<view class="left">
<img src="/static/my/changeBindPhone.png" />
<text class="label">+86</text>
<input type="number" placeholder="请输入您的换绑手机号" class="input" />
</view>
<view class="right">
<button class="verification" :class="{ 'disabled': gettingCode }" @click="getVerification"
:disabled="gettingCode">
{{ gettingCode ? `重新发送 ${time}s` : '获取验证码' }}
</button>
</view>
</view>
<view class="top-list">
<view class="left">
<img src="/static/my/verification.png" />
<input type="text" placeholder="请输入验证码" class="input" />
</view>
</view>
</view>
<view class="bottom">
<button class="change-btn">换绑</button>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
const iSMT = ref(0)
const phone = ref('15105421566')
const gettingCode = ref(false)
const time = ref(60)
const getVerification = () => {
if (gettingCode.value) return
gettingCode.value = true
time.value = 60
const timer = setInterval(() => {
time.value--
if (time.value <= 0) {
clearInterval(timer)
gettingCode.value = false
}
}, 1000)
}
onMounted(() => {
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
console.log('看看高度', iSMT.value)
})
</script>
<style>
.top {
height: auto;
background-color: white;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.top-list {
width: 630rpx;
height: 7vh;
margin: 0rpx 40rpx;
display: flex;
align-items: center;
border-bottom: 1rpx solid #eee;
}
.left {
flex: 1;
display: flex;
align-items: center;
}
.label {
font-size: 28rpx;
margin-left: 10rpx;
}
.right {
display: flex;
align-items: center;
justify-content: center;
}
.input {
flex: 1;
height: 70rpx;
font-size: 29rpx;
margin-left: 20rpx;
}
.verification {
font-size: 24rpx;
border-radius: 10rpx;
background-color: rgb(230, 230, 230);
}
.bottom {
height: 22vh;
background-color: white;
display: flex;
align-items: center;
justify-content: center;
}
.change-btn {
height: 85rpx;
width: 610rpx;
padding:0 20rpx;
background-color: black;
color: white;
border-radius: 40rpx;
display: flex;
align-items: center;
justify-content: center;
}
</style>

108
pages/setting/push.vue

@ -0,0 +1,108 @@
<template>
<view class="main">
<view :style="{height:iSMT+'px'}"></view>
<view class="top">
<view class="top-list">
<text style="width:180rpx;">公共消息</text>
<text class="public">重大咨询财经要闻等系统提醒</text>
<switch class="arrow switch-btn" />
</view>
<view class="top-list">
<text>字体大小</text>
<switch class="arrow switch-btn" />
</view>
</view>
<view class="bottom">
<view class="bottom-list">
<text>盯盘预警</text>
<uni-icons type="arrowright" size="16" class="arrow" />
</view>
<view class="bottom-list">
<text>订阅服务</text>
<text class="cache">45.5M</text>
<uni-icons type="arrowright" size="16" class="arrow" />
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
const iSMT = ref(0)
onMounted(() => {
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
console.log('看看高度', iSMT.value)
})
</script>
<style>
.top {
margin-top: 1.5vh;
height: 14vh;
background-color: white;
}
.top-list {
width: 630rpx;
height: 7vh;
display: flex;
align-items: center;
justify-content: center;
margin: 0 40rpx;
padding: 0 10rpx;
border-bottom: 1rpx solid #eee;
}
.top-list:last-child {
border-bottom: none;
}
.switch-btn {
width: 100rpx;
transform: scale(0.6);
transform-origin: center right;
}
.public {
width: 450rpx;
margin-left: auto;
font-size: 10px;
color: rgb(203, 203, 203);
}
.arrow {
margin-left: auto;
}
.bottom {
height: 13.5vh;
background-color: white;
margin-top: 1vh;
}
.bottom-list {
width: 630rpx;
height: 7vh;
display: flex;
align-items: center;
margin: 0 40rpx;
padding: 0 10rpx;
border-bottom: 1rpx solid #eee;
}
.cache {
margin-left: 55%;
font-size: 14px;
color: rgb(203, 203, 203);
}
.bottom-list:last-child {
border-bottom: none;
}
</style>

87
pages/setting/server.vue

@ -0,0 +1,87 @@
<template>
<view class="main">
<view :style="{height:iSMT+'px'}"></view>
<view class="top">
<view class="top-list">
<text>自动选择</text>
<radio value="0" class="radio-btn" activeBackgroundColor="red"
:checked="selectedIndex === 0" @click="selectFont(0)" />
</view>
<view class="top-list">
<text>新加坡服务器</text>
<radio value="1" class="radio-btn" activeBackgroundColor="red"
:checked="selectedIndex === 1" @click="selectFont(1)" />
</view>
<view class="top-list">
<text>香港服务器</text>
<radio value="2" class="radio-btn" activeBackgroundColor="red"
:checked="selectedIndex === 2" @click="selectFont(2)" />
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
const iSMT = ref(0)
const selectedIndex = ref(0)
const selectFont = (index) => {
selectedIndex.value = index
console.log('看看选中状态',selectedIndex.value)
}
onMounted(() => {
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
console.log('看看高度', iSMT.value)
})
</script>
<style>
.top {
margin-top: 1.5vh;
height: 21vh;
background-color: white;
}
.top-list {
width: 630rpx;
height: 7vh;
display: flex;
align-items: center;
justify-content: center;
margin: 0 40rpx;
padding: 0 10rpx;
border-bottom: 1rpx solid #eee;
}
.top-list:last-child {
border-bottom: none;
}
.switch-btn {
width: 100rpx;
transform: scale(0.6);
transform-origin: center right;
}
.public {
width: 450rpx;
margin-left: auto;
font-size: 10px;
color: rgb(203, 203, 203);
}
.arrow {
margin-left: auto;
}
.radio-btn {
margin-left: auto;
transform: scale(0.6);
}
</style>

111
pages/setting/share.vue

@ -0,0 +1,111 @@
<template>
<view class="all">
<img class="img-share" src="/static/my/shareBackground.png" />
<img class="img-greenBack" src="/static/my/greenBackground.png" />
<img class="img-QRcode" src="/static/my/QRcode.png" />
<img class="img-award" src="/static/my/award.png" />
<img class="img-myFriends" src="/static/my/myFriends.png" />
<img class="img-friends" src="/static/my/shareFriends.png" />
<text class="jwcode">{{ jwcode }}</text>
<button class="invite">立即邀请</button>
</view>
</template>
<script setup>
import {
ref
} from 'vue'
const jwcode = ref('90047681')
</script>
<style>
.all {
position: relative;
width: 750rpx;
height: auto;
}
.img-share {
width: 750rpx;
height: 2118rpx;
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
.img-QRcode{
width:320rpx;
height:320rpx;
position:absolute;
top:26vh;
left:215rpx;
z-index: 3;
}
.img-greenBack {
width: 670rpx;
height: 1740rpx;
position: absolute;
top: 16vh;
/* 为什么要用这个替代 margin-top */
left: 40rpx;
/* 还有 padding-left */
z-index: 2;
}
.img-friends {
width: 602rpx;
height: 840rpx;
position: absolute;
top: 68vh;
left: 74rpx;
z-index: 3;
}
.img-award {
width: 300rpx;
height: 120rpx;
position: absolute;
top: 61vh;
left: 75rpx;
z-index: 3;
}
.img-myFriends {
width: 300rpx;
height: 88rpx;
position: absolute;
top: 61vh;
right: 75rpx;
z-index: 3;
}
.jwcode {
width: 320rpx;
height: 38rpx;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 19vh;
left: 212rpx;
z-index: 999;
}
.invite {
width: 320rpx;
height: 80rpx;
border-radius: 40rpx;
background-color: black;
color:white;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 50.7vh;
left: 212rpx;
z-index: 999;
}
</style>

64
pages/setting/theme.vue

@ -0,0 +1,64 @@
<template>
<view class="main">
<view :style="{height:iSMT+'px'}"></view>
<view class="theme">
<view class="left">
<image class="img-theme" src="/static/my/whiteTheme.png" mode="widthFix" />
<radio value="0" class="radio-btn" activeBackgroundColor="red" :checked="selectedIndex === 0"
@click="selectFont(0)" />
</view>
<view class="left">
<image class="img-theme" src="/static/my/blackTheme.png" mode="widthFix" />
<radio value="1" class="radio-btn" activeBackgroundColor="red" :checked="selectedIndex === 1"
@click="selectFont(1)" />
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
const iSMT = ref(0)
const selectedIndex = ref(0)
const selectFont = (index) => {
selectedIndex.value = index
console.log('看看选中状态', selectedIndex.value)
}
onMounted(() => {
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
console.log('看看高度', iSMT.value)
})
</script>
<style>
.theme {
margin-top: 1.5vh;
height: 34vh;
background-color: white;
display: flex;
justify-content: space-around;
}
.img-theme {
width: 316rpx;
height: 362rpx;
}
.left {
width: 350rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.radio-btn {
margin-top: 36rpx;
transform: scale(0.8);
}
</style>

1054
pages/start/Registration/Registration.vue
File diff suppressed because it is too large
View File

1341
pages/start/Registration/list.js
File diff suppressed because it is too large
View File

13
pages/start/agreement/agreement.vue

@ -0,0 +1,13 @@
<template>
<view>
用户协议
</view>
</template>
<script setup>
</script>
<style>
</style>

56
pages/start/index/index.vue

@ -0,0 +1,56 @@
<template>
<view class="content">
<image class="logo" src="/static/logo.png"></image>
<view class="text-area">
<text class="title" @click="showLoginPrompt">{{ title }}</text>
</view>
<LoginPrompt ref="loginPrompt"></LoginPrompt>
<button @click="toDeepMate">deepMate</button>
</view>
</template>
<script setup>
import { ref } from "vue";
import { useUserStore } from "../../../stores/modules/userInfo";
const title = ref("请先登录");
const loginPrompt = ref(null);
const userStore = useUserStore();
function showLoginPrompt() {
userStore.clearUserInfo();
}
function toDeepMate() {
uni.navigateTo({
url: "/pages/deepMate/deepMate",
});
}
</script>
<style>
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.logo {
height: 200rpx;
width: 200rpx;
margin-top: 200rpx;
margin-left: auto;
margin-right: auto;
margin-bottom: 50rpx;
}
.text-area {
display: flex;
justify-content: center;
}
.title {
font-size: 36rpx;
color: #8f8f94;
}
</style>

1341
pages/start/login/list.js
File diff suppressed because it is too large
View File

1190
pages/start/login/login.vue
File diff suppressed because it is too large
View File

69
pages/start/login/verification.js

@ -0,0 +1,69 @@
function verificationPhone(countryCode,phoneNumber) {
switch (countryCode) {
case '+86':
return verificationChina(phoneNumber);
case '+1':
return verificationAmerica(phoneNumber);
case '+65':
return verificationSingapore(phoneNumber);
case '+60':
return verificationMalaysia(phoneNumber);
case '+66':
return verificationThailand(phoneNumber);
case '+852':
return verificationHongKong(phoneNumber);
case '+84':
return verificationVietnam(phoneNumber);
default:
return true;
}
}
function verificationChina(phoneNumber){
const phoneRegex = /^1[3-9]\d{9}$/;
return phoneRegex.test(phoneNumber);
}
function verificationAmerica(phoneNumber){
const phoneRegex = /^[2-9]\d{2}[- ]?\d{4}$/;
return phoneRegex.test(phoneNumber);
}
function verificationSingapore(phoneNumber){
const phoneRegex = /^[89]\d{7}$/;
return phoneRegex.test(phoneNumber);
}
function verificationMalaysia(phoneNumber){
const phoneRegex = /^01\d{8}$/;
return phoneRegex.test(phoneNumber);
}
function verificationHongKong(phoneNumber){
const phoneRegex = /^0[896]\d{8}$/;
return phoneRegex.test(phoneNumber);
}
function verificationThailand(phoneNumber){
const phoneRegex = /^[5-9]\d{7}$/;
return phoneRegex.test(phoneNumber);
}
function verificationVietnam(phoneNumber){
const phoneRegex = /^(0)?[3-9]\d{8}$/;
return phoneRegex.test(phoneNumber);
}
function verificationEmail(email) {
const emailRegex = /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/;
return emailRegex.test(email);
}
export {verificationPhone,verificationEmail}

13
pages/start/privacy/privacy.vue

@ -0,0 +1,13 @@
<template>
<view>
隐私政策
</view>
</template>
<script setup>
</script>
<style>
</style>

1012
pages/start/recoverPassword/recoverPassword.vue
File diff suppressed because it is too large
View File

170
pages/start/select/select.vue

@ -0,0 +1,170 @@
<template>
<view class="login-container">
<!-- 顶部标题 -->
<view class="title-section">
<text class="main-title">DeepChart</text>
</view>
<view class="subtitle-section">
<text class="subtitle">您的股市随身顾问</text>
</view>
<!-- 手机卡片样式 -->
<view class="phone-card"> </view>
<!-- 登录注册按钮 -->
<view class="button-group">
<button class="login-button" @click="toLogin">登录</button>
<button class="register-button" @click="toRegistration">注册</button>
</view>
<footerBar class="static-footer" :type="type"></footerBar>
</view>
</template>
<script setup>
import footerBar from "../../../components/footerBar";
import { ref, reactive, toRefs, watch } from "vue";
const type = ref("");
function toRegistration() {
uni.redirectTo({
url: "/pages/start/Registration/Registration",
});
}
function toLogin() {
uni.redirectTo({
url: "/pages/start/login/login",
});
}
</script>
<style scoped>
.login-container {
display: flex;
flex-direction: column;
align-items: center;
/* justify-content: space-between; */
padding: 40rpx;
height: 90vh;
background-color: #ffffff;
overflow: hidden;
max-height: 100vh;
-webkit-overflow-scrolling: none;
}
.title-section {
text-align: center;
margin-top: 120rpx;
margin-bottom: 20rpx;
}
.main-title {
text-align: center;
font-size: 64rpx;
font-weight: 300;
color: #000000;
margin-bottom: 10rpx;
}
.subtitle-section {
margin-bottom: 80rpx;
}
.subtitle {
/* font-weight: bold; */
font-size: 32rpx;
color: #333333;
}
.phone-card {
background-image: url("/static/select.png");
background-repeat: no-repeat;
background-size: contain;
background-position: center;
/* background-position: center; */
/* min-width: 300rpx; */
width: 420rpx;
height: 786rpx;
border-radius: 50rpx;
padding: 40rpx;
/* box-shadow: 0 20rpx 40rpx rgba(0, 0, 0, 0.3); */
position: relative;
overflow: hidden;
}
.button-group {
display: flex;
gap: 40rpx;
margin: 80rpx 0;
width: 100%;
max-width: 600rpx;
}
.login-button {
flex: 1;
background-color: #f5f5f5;
color: #000000;
border-radius: 60rpx;
font-size: 32rpx;
padding: 20rpx;
}
.register-button {
flex: 1;
background-color: #000000;
color: #ffffff;
border-radius: 60rpx;
font-size: 32rpx;
padding: 20rpx;
}
.bottom-nav {
display: flex;
justify-content: space-around;
align-items: center;
width: 100%;
height: 100rpx;
background-color: #ffffff;
position: fixed;
bottom: 0;
left: 0;
border-top: 1rpx solid #e5e5e5;
}
.nav-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 10rpx;
}
.nav-item.active .nav-icon {
transform: scale(1.2);
}
.nav-icon {
width: 40rpx;
height: 40rpx;
margin-bottom: 10rpx;
}
.nav-text {
font-size: 24rpx;
color: #666666;
}
.nav-item.active .nav-text {
color: #000000;
}
.static-footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
}
</style>

73
pages/start/startup/startup.vue

@ -0,0 +1,73 @@
<template>
<view class="background">
<image
class="logo"
src="../../../static/icons/start-logo.png"
mode="scaleToFill"
/>
<view class="logo-text"> DeepChart </view>
</view>
</template>
<script setup>
import { onShow } from "@dcloudio/uni-app";
import { useUserStore } from "../../../stores/modules/userInfo";
import { useDeviceStore } from "../../../stores/modules/deviceInfo";
onShow(() => {
const deviceInfo = useDeviceStore();
// ID
uni.getSystemInfo({
success: (res) => {
deviceInfo.setDeviceInfo(res)
},
});
setTimeout(() => {
const userStore = useUserStore();
if (!userStore.userInfo)
uni.redirectTo({
url: "/pages/start/select/select",
animationType: "slide-in-right",
animationDuration: 1000,
});
else {
uni.redirectTo({
url: "/pages/home/home",
animationType: "slide-in-right",
animationDuration: 1000,
});
}
}, 1500);
});
</script>
<style>
.background {
background: linear-gradient(180deg, #fb6967, #fb6967);
width: 100vw;
height: 100vh;
}
.logo-text {
display: flex;
justify-content: center;
align-items: center;
color: white;
font-weight: bold;
font-size: 24px;
position: absolute;
bottom: 5%;
left: 50%;
transform: translateX(-50%);
}
.logo {
width: 320rpx;
height: 200rpx;
position: absolute;
top: 30%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>

138
server/login.json

@ -0,0 +1,138 @@
{
"loginSuccessByEmail": {
"code": 200,
"message": "c3e9ed50ad72073b94dfb04860562b58",
"data": {
"loginType": null,
"device": "unknown",
"dccode": "90047686",
"account": null,
"password": null,
"verifyCode": null,
"useCode": false,
"idToken": null,
"token": "c3e9ed50ad72073b94dfb04860562b58",
"market": "新加坡",
"phone": "17861484516",
"email": "q614588746@163.com",
"language": "中文",
"avatar": "123"
}
},
"loginFailureEmailNotFound": {
"code": 404,
"message": "账号不存在",
"interface": "login"
},
"loginFailureWrongCode": {
"code": 400,
"message": "验证码错误",
"interface": "login"
},
"loginSuccessByPhone": {
"code": 200,
"message": "登录成功",
"interface": "login",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"userInfo": {
"id": "987654321",
"username": "13800138000",
"phone": "13800138000"
}
}
},
"loginSuccessByPassword": {
"code": 200,
"message": "登录成功",
"interface": "login",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"userInfo": {
"id": "556677889",
"username": "testuser"
}
}
},
"sendCodeSuccess": {
"code": 200,
"message": "验证码发送成功",
"interface": "sendCode",
"data": {
"expireTime": 180
}
},
"sendCodeFailureInvalidFormat": {
"code": 400,
"message": "手机号格式错误",
"interface": "sendCode"
},
"sendCodeFailureTooFrequent": {
"code": 429,
"message": "发送频率过高,请稍后再试",
"interface": "sendCode"
},
"registerSuccess": {
"code": 200,
"message": "注册成功",
"interface": "register",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"userInfo": {
"id": "112233445",
"username": "newuser",
"email": "newuser@example.com"
}
}
},
"registerFailureEmailExist": {
"code": 400,
"message": "邮箱已被注册",
"interface": "register"
},
"registerFailurePhoneExist": {
"code": 400,
"message": "手机号已被注册",
"interface": "register"
},
"registerFailureWrongCode": {
"code": 400,
"message": "验证码错误",
"interface": "register"
},
"updatePasswordSuccess": {
"code": 200,
"message": "密码修改成功",
"interface": "updatePassword"
},
"updatePasswordFailureWrongOldPassword": {
"code": 400,
"message": "旧密码错误",
"interface": "updatePassword"
},
"updatePasswordFailureWrongCode": {
"code": 400,
"message": "验证码错误",
"interface": "updatePassword"
},
"forgotPasswordSuccessByEmail": {
"code": 200,
"message": "密码重置成功,新密码已发送至邮箱",
"interface": "forgotPassword"
},
"forgotPasswordSuccessByPhone": {
"code": 200,
"message": "密码重置成功,新密码已发送至手机",
"interface": "forgotPassword"
},
"forgotPasswordFailureAccountNotFound": {
"code": 404,
"message": "账号不存在",
"interface": "forgotPassword"
},
"forgotPasswordFailureWrongCode": {
"code": 400,
"message": "验证码错误",
"interface": "forgotPassword"
}
}

BIN
static/deepExploration-images/1.png

After

Width: 750  |  Height: 198  |  Size: 35 KiB

BIN
static/deepExploration-images/2.png

After

Width: 750  |  Height: 198  |  Size: 36 KiB

BIN
static/deepExploration-images/3.png

After

Width: 750  |  Height: 198  |  Size: 37 KiB

BIN
static/deepExploration-images/4.png

After

Width: 750  |  Height: 198  |  Size: 35 KiB

BIN
static/deepExploration-images/ASC.png

After

Width: 200  |  Height: 200  |  Size: 4.8 KiB

BIN
static/deepExploration-images/Americle.png

After

Width: 305  |  Height: 200  |  Size: 7.9 KiB

BIN
static/deepExploration-images/DESC.png

After

Width: 200  |  Height: 200  |  Size: 4.8 KiB

BIN
static/deepExploration-images/all.png

After

Width: 48  |  Height: 48  |  Size: 1.6 KiB

BIN
static/deepExploration-images/close.png

After

Width: 24  |  Height: 24  |  Size: 232 B

BIN
static/deepExploration-images/delete.png

After

Width: 28  |  Height: 31  |  Size: 894 B

BIN
static/deepExploration-images/history.png

After

Width: 16  |  Height: 15  |  Size: 797 B

BIN
static/deepExploration-images/icon1.png

After

Width: 40  |  Height: 40  |  Size: 1.9 KiB

BIN
static/deepExploration-images/icon2.png

After

Width: 40  |  Height: 40  |  Size: 2.6 KiB

BIN
static/deepExploration-images/icon3.png

After

Width: 40  |  Height: 40  |  Size: 1.7 KiB

BIN
static/deepExploration-images/icon4.png

After

Width: 40  |  Height: 40  |  Size: 1.5 KiB

BIN
static/deepExploration-images/last.png

After

Width: 200  |  Height: 200  |  Size: 3.1 KiB

BIN
static/deepExploration-images/next.png

After

Width: 200  |  Height: 200  |  Size: 4.4 KiB

BIN
static/deepExploration-images/notice.png

After

Width: 18  |  Height: 18  |  Size: 523 B

BIN
static/deepExploration-images/plus.png

After

Width: 9  |  Height: 9  |  Size: 274 B

BIN
static/deepExploration-images/search.png

After

Width: 16  |  Height: 16  |  Size: 522 B

BIN
static/deepExploration-images/showAll.png

After

Width: 34  |  Height: 34  |  Size: 292 B

BIN
static/flag/ad.png

After

Width: 100  |  Height: 70  |  Size: 2.1 KiB

BIN
static/flag/ae.png

After

Width: 100  |  Height: 50  |  Size: 170 B

BIN
static/flag/af.png

After

Width: 100  |  Height: 67  |  Size: 2.6 KiB

BIN
static/flag/ag.png

After

Width: 100  |  Height: 67  |  Size: 2.0 KiB

BIN
static/flag/ai.png

After

Width: 100  |  Height: 50  |  Size: 1.3 KiB

BIN
static/flag/al.png

After

Width: 100  |  Height: 71  |  Size: 1.7 KiB

BIN
static/flag/am.png

After

Width: 100  |  Height: 50  |  Size: 122 B

BIN
static/flag/an.png

After

Width: 100  |  Height: 67  |  Size: 610 B

BIN
static/flag/ao.png

After

Width: 100  |  Height: 67  |  Size: 1.4 KiB

BIN
static/flag/aq.png

After

Width: 100  |  Height: 100  |  Size: 6.0 KiB

BIN
static/flag/ar.png

After

Width: 100  |  Height: 63  |  Size: 991 B

BIN
static/flag/as.png

After

Width: 100  |  Height: 50  |  Size: 2.0 KiB

BIN
static/flag/at.png

After

Width: 100  |  Height: 67  |  Size: 133 B

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

Loading…
Cancel
Save