Browse Source

Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into dongqian/feature-20251022181325-deepmate简版

zhaowenkang/feature-20251028181547-行情页面
wangyetao 4 weeks ago
parent
commit
c1a563608b
  1. 5
      .hbuilderx/launch.json
  2. 14
      api/deepMate/deepMate.js
  3. 223
      api/tcpConnection.js
  4. 392
      common/canvasMethod.js
  5. 354
      common/stockTimeInformation.js
  6. 142
      common/util.js
  7. 56
      components/DeepMate.vue
  8. 190
      components/IndexCard.vue
  9. 78
      main.js
  10. 726
      package-lock.json
  11. 8
      package.json
  12. 115
      pages.json
  13. 362
      pages/deepMate/deepMate.vue
  14. 63
      pages/deepMate/scroll/scroll.vue
  15. 579
      pages/home/globalIndex.vue
  16. 2046
      pages/home/marketCondition.vue
  17. 488
      pages/home/marketDetail.vue
  18. 922
      pages/home/marketSituation.vue
  19. 296
      pages/home/member.vue
  20. 22
      pages/morningMarketAnalysis/morningMarketAnalysis.vue
  21. 86
      pages/setting/about.vue
  22. 213
      pages/setting/account.vue
  23. 85
      pages/setting/bind.vue
  24. 142
      pages/setting/email.vue
  25. 68
      pages/setting/font.vue
  26. 165
      pages/setting/general.vue
  27. 76
      pages/setting/introduce.vue
  28. 221
      pages/setting/market.vue
  29. 62
      pages/setting/message.vue
  30. 82
      pages/setting/newVersion.vue
  31. 144
      pages/setting/nextPwd.vue
  32. 171
      pages/setting/password.vue
  33. 143
      pages/setting/phone.vue
  34. 108
      pages/setting/push.vue
  35. 87
      pages/setting/server.vue
  36. 111
      pages/setting/share.vue
  37. 64
      pages/setting/theme.vue
  38. 2
      pages/start/login/login.vue
  39. BIN
      static/marketSituation-image/back.png
  40. BIN
      static/marketSituation-image/bg.png
  41. BIN
      static/marketSituation-image/history.png
  42. BIN
      static/marketSituation-image/map.png
  43. BIN
      static/marketSituation-image/marketCondition-image/favorites.png
  44. BIN
      static/marketSituation-image/marketCondition-image/function.png
  45. BIN
      static/marketSituation-image/marketCondition-image/index.png
  46. BIN
      static/marketSituation-image/marketCondition-image/setting2.png
  47. BIN
      static/marketSituation-image/menu.png
  48. BIN
      static/marketSituation-image/more.png
  49. BIN
      static/marketSituation-image/mySeclected.png
  50. BIN
      static/marketSituation-image/search.png
  51. BIN
      static/marketSituation-image/warn.png
  52. BIN
      static/my/BlackTheme.png
  53. BIN
      static/my/Check-in.png
  54. BIN
      static/my/MarketSettings.png
  55. BIN
      static/my/QRcode.png
  56. BIN
      static/my/Settings.png
  57. BIN
      static/my/about.png
  58. BIN
      static/my/aboutDC.png
  59. BIN
      static/my/award.png
  60. BIN
      static/my/bell.png
  61. BIN
      static/my/bindedEmail.png
  62. BIN
      static/my/bindedPhone.png
  63. BIN
      static/my/changeBindPhone.png
  64. BIN
      static/my/changeEmail.png
  65. BIN
      static/my/common.png
  66. BIN
      static/my/connection.png
  67. BIN
      static/my/editName.png
  68. BIN
      static/my/greenBackground.png
  69. BIN
      static/my/greenRise.png
  70. BIN
      static/my/hideEye.png
  71. BIN
      static/my/invite.png
  72. BIN
      static/my/menu.png
  73. BIN
      static/my/myFriends.png
  74. BIN
      static/my/openEye.png
  75. BIN
      static/my/opinion.png
  76. BIN
      static/my/outline.png
  77. BIN
      static/my/polylines.png
  78. BIN
      static/my/redRise.png
  79. BIN
      static/my/security.png
  80. BIN
      static/my/setting.png
  81. BIN
      static/my/share.png
  82. BIN
      static/my/shareBackground.png
  83. BIN
      static/my/shareFriends.png
  84. BIN
      static/my/unlock.png
  85. BIN
      static/my/update.png
  86. BIN
      static/my/verification.png
  87. BIN
      static/my/whiteTheme.png
  88. 2
      utils/http.js

5
.hbuilderx/launch.json

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

14
api/deepMate/deepMate.js

@ -20,7 +20,7 @@ export const getData = () => {
export const postIntent = (data) => { export const postIntent = (data) => {
return http({ return http({
method: 'POST', method: 'POST',
url: '/testApi/api/deepMate/dmFirst',
url: '/api/deepMate/dmFirst',
data data
}) })
} }
@ -47,14 +47,22 @@ export const postStock = (data) => {
export const postHistory = (data) => { export const postHistory = (data) => {
return http({ return http({
method: 'POST', method: 'POST',
url: '/history',
url: '/api/deepMate/dmList',
data data
}) })
} }
/** /**
*
* 历史记录详情
*/ */
export const postHistoryDetail = (data) => {
return http({
method: 'POST',
url: '/api/deepMate/clickRecord',
data
})
}

223
api/tcpConnection.js

@ -0,0 +1,223 @@
/**
* TCP连接工具类
* 用于处理TCP连接发送消息和断开连接
*/
// 引用TCP插件
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.isConnected = false;
this.connectionCallbacks = [];
this.messageCallbacks = [];
}
/**
* TCP初始化连接
* @param {Object} config - 连接配置 {ip, port, channel, charsetname}
* @param {Function} callback - 连接状态回调函数
*/
connect(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;
}
TCPSocket.connect(
connectionConfig,
result => {
/**
* status : 0 连接成功
* status : 1 断开连接
* receivedMsg : 服务器返回字符串(普通的字符串交互)
* receivedHexMsg : 服务器返回字节数组(单片机智能家居等硬件数据交互)
*/
if (result.status == '0') {
// TCP连接成功
this.isConnected = true;
console.log('TCP连接成功');
this._notifyConnectionCallbacks('connected', result);
} else if (result.status == '1') {
// TCP断开连接
this.isConnected = false;
console.log('TCP断开连接');
this._notifyConnectionCallbacks('disconnected', result);
}
if (result.receivedMsg) {
// 服务器返回字符串
console.log('收到字符串消息:', result.receivedMsg);
this._notifyMessageCallbacks('string', result.receivedMsg);
}
// 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 = {}) {
if (!this.isConnected) {
console.warn('TCP未连接,无法发送消息');
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 disconnectConfig = {
channel: config.channel || TCP_CONFIG.channel
};
TCPSocket.disconnect(disconnectConfig);
this.isConnected = false;
console.log('TCP连接已断开', 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);
}
}
/**
* 获取连接状态
* @returns {Boolean} 连接状态
*/
getConnectionStatus() {
return this.isConnected;
}
/**
* 通知连接状态回调
* @private
*/
_notifyConnectionCallbacks(status, result) {
this.connectionCallbacks.forEach(callback => {
try {
callback(status, result);
} catch (error) {
console.error('连接状态回调执行错误:', error);
}
});
}
/**
* 通知消息回调
* @private
*/
_notifyMessageCallbacks(type, message, parsedArray = null) {
this.messageCallbacks.forEach(callback => {
try {
callback(type, message, parsedArray);
} 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 },
];

142
common/util.js

@ -0,0 +1,142 @@
var util = {}
util.data = {}
util.data.base_url = 'https://dbqb.nfdxy.net/devApi'
// util.data.base_url = 'https://dbqb.nfdxy.net/prodApi'
// AJAX 请求方法
util.request = (url, callback, data = {}, failCallback) => {
url = util.data.base_url + url
console.log('请求该接口->', url, '请求参数为->', data);
uni.request({
url: url, //仅为示例,并非真实接口地址。
data,
method: 'post',
header: {
'content-type': 'application/json',
'version': uni.getSystemInfoSync().appVersion,
'client': uni.getSystemInfoSync().platform == 'ios' ? 'ios' : 'android',
'token': uni.getStorageSync('token')
},
sslVerify: false,
success: callback,
fail: failCallback
});
}
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;
}
};
};

56
components/DeepMate.vue

@ -195,4 +195,60 @@
width: 20px; width: 20px;
height: 20px; height: 20px;
} }
.icon-container {
position: relative;
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
background-color: #f0f8ff;
}
.deepmate-icon {
width: 40px;
height: 40px;
border-radius: 50%;
}
.ripple-effect {
position: absolute;
border-radius: 50%;
background: radial-gradient(circle, rgba(255, 0, 0, 0.4) 0%, rgba(255, 0, 0, 0.1) 70%);
animation-name: ripple;
animation-duration: 3s;
animation-timing-function: ease-out;
animation-iteration-count: infinite;
}
.ripple-1 {
width: 100%;
height: 100%;
animation-delay: 0s;
}
.ripple-2 {
width: 100%;
height: 100%;
animation-delay: 1s;
}
.ripple-3 {
width: 100%;
height: 100%;
animation-delay: 2s;
}
@keyframes ripple {
0% {
transform: scale(0.8);
opacity: 1;
}
100% {
transform: scale(1.5);
opacity: 0;
}
}
</style> </style>

190
components/IndexCard.vue

@ -0,0 +1,190 @@
<template>
<view class="index-card">
<view class="card-header">
<view class="flag-container">
<image :src="flagIcon" class="flag-icon" mode="aspectFit"></image>
</view>
<text class="index-name">{{ stockName }}</text>
</view>
<view class="price-info">
<text class="current-price" :style="{ color: priceColor }">{{ currentPrice }}</text>
<view class="change-info">
<text class="change-amount" :style="{ color: priceColor }">{{ changeAmount }}</text>
<text class="change-percent" :style="{ color: priceColor }">{{ changePercent }}</text>
</view>
</view>
<view class="chart-container">
<view class="mini-chart" :style="{ backgroundColor: chartBgColor }">
<!-- 这里可以放置实际的图表组件目前用简单的波浪线表示 -->
<view class="chart-line" :style="{ borderColor: priceColor }"></view>
</view>
</view>
</view>
</template>
<script setup>
import { computed } from 'vue'
//
const props = defineProps({
//
flagIcon: {
type: String,
required: true
},
//
stockName: {
type: String,
required: true
},
//
currentPrice: {
type: [String, Number],
required: true
},
//
changeAmount: {
type: [String, Number],
required: true
},
//
changePercent: {
type: [String, Number],
required: true
},
//
isRising: {
type: Boolean,
default: true
}
})
//
const priceColor = computed(() => {
return props.isRising ? '#00C853' : '#FF1744'
})
//
const chartBgColor = computed(() => {
return props.isRising ? '#E8F5E8' : '#FFEBEE'
})
</script>
<style scoped>
.index-card {
background-color: #ffffff;
border-radius: 12rpx;
padding: 20rpx;
margin: 16rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
border: 1rpx solid #f0f0f0;
}
.card-header {
display: flex;
align-items: center;
margin-bottom: 16rpx;
}
.flag-container {
width: 48rpx;
height: 32rpx;
margin-right: 12rpx;
border-radius: 4rpx;
overflow: hidden;
}
.flag-icon {
width: 100%;
height: 100%;
}
.index-name {
font-size: 28rpx;
font-weight: 500;
color: #333333;
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.price-info {
margin-bottom: 20rpx;
}
.current-price {
font-size: 36rpx;
font-weight: bold;
display: block;
margin-bottom: 8rpx;
}
.change-info {
display: flex;
align-items: center;
gap: 16rpx;
}
.change-amount {
font-size: 24rpx;
font-weight: 500;
}
.change-percent {
font-size: 24rpx;
font-weight: 500;
}
.chart-container {
height: 80rpx;
border-radius: 8rpx;
overflow: hidden;
}
.mini-chart {
width: 100%;
height: 100%;
position: relative;
border-radius: 8rpx;
}
.chart-line {
position: absolute;
bottom: 20rpx;
left: 10rpx;
right: 10rpx;
height: 2rpx;
border-top: 2rpx solid;
border-style: solid;
}
/* 添加一些波浪效果 */
.chart-line::before {
content: '';
position: absolute;
top: -10rpx;
left: 20%;
width: 20rpx;
height: 20rpx;
border: 2rpx solid;
border-color: inherit;
border-radius: 50%;
background: transparent;
}
.chart-line::after {
content: '';
position: absolute;
top: -6rpx;
right: 30%;
width: 12rpx;
height: 12rpx;
border: 2rpx solid;
border-color: inherit;
border-radius: 50%;
background: transparent;
}
</style>

78
main.js

@ -1,31 +1,32 @@
import pinia from './stores/index.js'
import App from './App.vue'
// #ifdef H5
import { createSSRApp } from 'vue'
// 导入需要全局注册的组件
import LoginPrompt from './components/login-prompt.vue'
import pinia from './stores/index.js'
// #ifndef VUE3
import Vue from 'vue'
import App from './App'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
export function createApp() {
const app = createSSRApp(App)
// 全局注册组件
app.component('LoginPrompt', LoginPrompt)
Vue.config.productionTip = false
// 挂载 store
app.use(pinia)
App.mpType = 'app'
return {
app
}
const app = new Vue({
...App,
pinia
})
app.use(ElementPlus)
for (const [key, component] of Object.entries(ElementPlusIconsVue)){
app.component(key, component)
} }
app.$mount()
// #endif // #endif
// #ifdef APP-PLUS
// #ifdef VUE3
import { createSSRApp } from 'vue' import { createSSRApp } from 'vue'
import App from './App.vue'
import { createI18n } from 'vue-i18n' import { createI18n } from 'vue-i18n'
import LoginPrompt from './components/login-prompt.vue'
// 导入语言文件 // 导入语言文件
import en from './static/language/en.js' import en from './static/language/en.js'
@ -41,7 +42,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', {
@ -56,7 +57,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', {
@ -98,27 +99,24 @@ function getCurrentLocale() {
} }
// 创建 i18n 实例 // 创建 i18n 实例
const i18n = createI18n({ const i18n = createI18n({
locale: getCurrentLocale(),
legacy: false, // 使用 Composition API 模式
globalInjection: true, // 全局注入 $t 函数
messages: {
'en': en,
'ms': ms,
'th': th,
'vi': vi,
'zh_CN': zh_CN,
'zh_HK': zh_HK
}
locale: getCurrentLocale(),
legacy: false, // 使用 Composition API 模式
globalInjection: true, // 全局注入 $t 函数
messages: {
'en': en,
'ms': ms,
'th': th,
'vi': vi,
'zh_CN': zh_CN,
'zh_HK': zh_HK
}
}) })
export function createApp() { export function createApp() {
const app = createSSRApp(App)
app.component('LoginPrompt', LoginPrompt)
app.use(i18n)
// 挂载 store
app.use(pinia)
return {
app
}
const app = createSSRApp(App)
app.use(pinia)
app.use(i18n)
return {
app
}
} }
// #endif // #endif

726
package-lock.json

@ -0,0 +1,726 @@
{
"name": "deepChartVueApp",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"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"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.27.1",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.28.5",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.28.5",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/types": "^7.28.5"
},
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/types": {
"version": "7.28.5",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.28.5"
},
"engines": {
"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": {
"version": "9.14.5",
"resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.14.5.tgz",
"integrity": "sha512-5ah5FqZG4pOoHjkvs8mjtv+gPKYU0zCISaYNjBNNqYiaITxW8ZtVih3GS/oTOqN8d9/mDLyrjD46GBApNxmlsA==",
"license": "MIT",
"dependencies": {
"@intlify/message-compiler": "9.14.5",
"@intlify/shared": "9.14.5"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@intlify/message-compiler": {
"version": "9.14.5",
"resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-9.14.5.tgz",
"integrity": "sha512-IHzgEu61/YIpQV5Pc3aRWScDcnFKWvQA9kigcINcCBXN8mbW+vk9SK+lDxA6STzKQsVJxUPg9ACC52pKKo3SVQ==",
"license": "MIT",
"dependencies": {
"@intlify/shared": "9.14.5",
"source-map-js": "^1.0.2"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@intlify/shared": {
"version": "9.14.5",
"resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-9.14.5.tgz",
"integrity": "sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ==",
"license": "MIT",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5",
"license": "MIT",
"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": {
"version": "3.5.22",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/parser": "^7.28.4",
"@vue/shared": "3.5.22",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-dom": {
"version": "3.5.22",
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/compiler-core": "3.5.22",
"@vue/shared": "3.5.22"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.5.22",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/parser": "^7.28.4",
"@vue/compiler-core": "3.5.22",
"@vue/compiler-dom": "3.5.22",
"@vue/compiler-ssr": "3.5.22",
"@vue/shared": "3.5.22",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.19",
"postcss": "^8.5.6",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-ssr": {
"version": "3.5.22",
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/compiler-dom": "3.5.22",
"@vue/shared": "3.5.22"
}
},
"node_modules/@vue/devtools-api": {
"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": {
"version": "3.5.22",
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/shared": "3.5.22"
}
},
"node_modules/@vue/runtime-core": {
"version": "3.5.22",
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/reactivity": "3.5.22",
"@vue/shared": "3.5.22"
}
},
"node_modules/@vue/runtime-dom": {
"version": "3.5.22",
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/reactivity": "3.5.22",
"@vue/runtime-core": "3.5.22",
"@vue/shared": "3.5.22",
"csstype": "^3.1.3"
}
},
"node_modules/@vue/server-renderer": {
"version": "3.5.22",
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/compiler-ssr": "3.5.22",
"@vue/shared": "3.5.22"
},
"peerDependencies": {
"vue": "3.5.22"
}
},
"node_modules/@vue/shared": {
"version": "3.5.22",
"license": "MIT",
"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": {
"version": "3.1.3",
"license": "MIT",
"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": {
"version": "4.5.0",
"license": "BSD-2-Clause",
"peer": true,
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/estree-walker": {
"version": "2.0.2",
"license": "MIT",
"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": {
"version": "0.30.21",
"license": "MIT",
"peer": true,
"dependencies": {
"@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": {
"version": "3.3.11",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"peer": true,
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"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": {
"version": "1.1.1",
"license": "ISC",
"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": {
"version": "8.5.6",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/rfdc": {
"version": "1.4.1",
"license": "MIT"
},
"node_modules/source-map-js": {
"version": "1.2.1",
"license": "BSD-3-Clause",
"engines": {
"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": {
"version": "3.5.22",
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/compiler-dom": "3.5.22",
"@vue/compiler-sfc": "3.5.22",
"@vue/runtime-dom": "3.5.22",
"@vue/server-renderer": "3.5.22",
"@vue/shared": "3.5.22"
},
"peerDependencies": {
"typescript": "*"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/vue-i18n": {
"version": "9.14.5",
"resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-9.14.5.tgz",
"integrity": "sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g==",
"license": "MIT",
"dependencies": {
"@intlify/core-base": "9.14.5",
"@intlify/shared": "9.14.5",
"@vue/devtools-api": "^6.5.0"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
},
"peerDependencies": {
"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"
}
}
}

8
package.json

@ -1,10 +1,12 @@
{ {
"dependencies": { "dependencies": {
"json-server": "^1.0.0-beta.3",
"@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", "marked": "^2.0.1",
"pinia": "^3.0.3", "pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.5.0",
"vue-i18n": "^11.1.12"
"pinia-plugin-persistedstate": "^4.5.0"
} }

115
pages.json

@ -1,4 +1,7 @@
{ {
"easycom": {
"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue"
},
"pages": [{ "pages": [{
"path": "pages/start/startup/startup", "path": "pages/start/startup/startup",
"style": { "style": {
@ -150,10 +153,118 @@
} }
}, },
{ {
"path" : "pages/deepMate/scroll/scroll",
"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/home/marketCondition",
"style" : "style" :
{ {
"navigationBarTitleText" : ""
"navigationBarTitleText" : "行情",
"navigationStyle": "custom"
} }
} }
], ],

362
pages/deepMate/deepMate.vue

@ -3,18 +3,29 @@
<!-- 顶部导航栏 --> <!-- 顶部导航栏 -->
<view class="header" :style="{ paddingTop: safeAreaInsets?.top + 'px' }"> <view class="header" :style="{ paddingTop: safeAreaInsets?.top + 'px' }">
<view class="header-left"> <view class="header-left">
<image src="https://d31zlh4on95l9h.cloudfront.net/images/f91e09b5987802185e7679055dafd272.svg" class="icon">
<image
src="https://d31zlh4on95l9h.cloudfront.net/images/f91e09b5987802185e7679055dafd272.svg"
class="icon"
>
</image> </image>
</view> </view>
<view class="header-center"> <view class="header-center">
<text class="title" :style="{ paddingTop: safeAreaInsets?.top + 'px' }">DeepMate</text>
<text class="title" :style="{ paddingTop: safeAreaInsets?.top + 'px' }"
>DeepMate</text
>
</view> </view>
<view class="header-right"> <view class="header-right">
<image src="https://d31zlh4on95l9h.cloudfront.net/images/d7c4e74201213a25dd9574e908233928.svg" class="icon">
<image
src="https://d31zlh4on95l9h.cloudfront.net/images/d7c4e74201213a25dd9574e908233928.svg"
class="icon"
>
</image> </image>
<image style="margin-left: 10px"
src="https://d31zlh4on95l9h.cloudfront.net/images/099903c4aabf5713488b5cb60815e3f7.svg" class="icon"
@click="openHistoryDrawer"></image>
<image
style="margin-left: 10px"
src="https://d31zlh4on95l9h.cloudfront.net/images/099903c4aabf5713488b5cb60815e3f7.svg"
class="icon"
@click="openHistoryDrawer"
></image>
<!-- 新增新会话按钮 <!-- 新增新会话按钮
<button class="new-chat-button" @click="newChat"> <button class="new-chat-button" @click="newChat">
<text class="new-chat-text">新会话</text> <text class="new-chat-text">新会话</text>
@ -25,15 +36,19 @@
<!-- 主要内容区域 --> <!-- 主要内容区域 -->
<view class="main-content"> <view class="main-content">
<view class="banner-panel" v-if="messages.length === 0"> <view class="banner-panel" v-if="messages.length === 0">
<image src="https://d31zlh4on95l9h.cloudfront.net/images/42e18bd7fe97d4f4f37aa70439a0990b.svg"
class="pray-banner"></image>
<image
src="https://d31zlh4on95l9h.cloudfront.net/images/42e18bd7fe97d4f4f37aa70439a0990b.svg"
class="pray-banner"
></image>
<view class="contain"> <view class="contain">
<!-- 机器人头像和欢迎语 --> <!-- 机器人头像和欢迎语 -->
<view class="robot-container" v-if="messages.length === 0"> <view class="robot-container" v-if="messages.length === 0">
<image src="/static/icons/机器人.svg" class="robot-avatar"></image> <image src="/static/icons/机器人.svg" class="robot-avatar"></image>
<view class="welcome-message"> <view class="welcome-message">
<text class="greeting">Hi, 我是您的股市随身顾问~</text> <text class="greeting">Hi, 我是您的股市随身顾问~</text>
<text class="description">个股诊断市场情绪解读都可以找我</text>
<text class="description"
>个股诊断市场情绪解读都可以找我</text
>
</view> </view>
</view> </view>
@ -45,19 +60,28 @@
</view> --> </view> -->
<!-- 特斯拉推荐卡片 --> <!-- 特斯拉推荐卡片 -->
<view class="recommend-card" v-if="messages.length === 0" @click="goBlank">
<view
class="recommend-card"
v-if="messages.length === 0"
@click="goBlank"
>
<view class="arrow" v-if="messages.length === 0"></view> <view class="arrow" v-if="messages.length === 0"></view>
<view class="card-content"> <view class="card-content">
<image src="../../static/images/tesla-logo.png" class="logo"></image>
<image
src="../../static/images/tesla-logo.png"
class="logo"
></image>
<view class="card-text"> <view class="card-text">
<text class="main-question">当前特斯拉该如何布局</text> <text class="main-question">当前特斯拉该如何布局</text>
<text class="stock-code">TSLA</text> <text class="stock-code">TSLA</text>
</view> </view>
<image src="https://d31zlh4on95l9h.cloudfront.net/images/40d94054644f6e3f1c366751f07f0010.svg"
class="arrow-icon" @click="goBlank"></image>
<image
src="https://d31zlh4on95l9h.cloudfront.net/images/40d94054644f6e3f1c366751f07f0010.svg"
class="arrow-icon"
@click="goBlank"
></image>
</view> </view>
</view> </view>
</view> </view>
</view> </view>
@ -73,33 +97,50 @@
</view> </view>
</view> </view>
</view> --> </view> -->
<view v-if="messages.length === 0" class="welcome-section">
</view>
<view v-if="messages.length === 0" class="welcome-section"> </view>
<!-- 聊天区域 --> <!-- 聊天区域 -->
<!-- 顶部粘性欢迎块始终保留在聊天上方 --> <!-- 顶部粘性欢迎块始终保留在聊天上方 -->
<view class="chat-header" v-if="messages.length > 0"> <view class="chat-header" v-if="messages.length > 0">
<view class="robot-container"> <view class="robot-container">
<image src="/static/icons/机器人.svg" class="robot-avatar"></image> <image src="/static/icons/机器人.svg" class="robot-avatar"></image>
<view class="welcome-message"> <text class="greeting">Hi, 我是您的股市随身顾问~</text>
<view class="welcome-message">
<text class="greeting">Hi, 我是您的股市随身顾问~</text>
</view> </view>
</view> </view>
</view> </view>
<scroll-view class="chat-container" scroll-y="true" :scroll-top="chatScrollTop"
:scroll-with-animation="!isSending" @scroll="onChatScroll" v-if="messages.length > 0">
<scroll-view
class="chat-container"
scroll-y="true"
:scroll-top="chatScrollTop"
:scroll-with-animation="!isSending"
@scroll="onChatScroll"
v-if="messages.length > 0"
>
<view class="message-list" id="messageList"> <view class="message-list" id="messageList">
<view v-for="(message, index) in messages" :key="index" :class="message.isUser ? 'message user-message' : 'message bot-message'
">
<view
v-for="(message, index) in messages"
:key="index"
:class="
message.isUser ? 'message user-message' : 'message bot-message'
"
>
<!-- 会话图标 --> <!-- 会话图标 -->
<text :class="message.isUser
? 'fa-solid fa-user message-icon'
: 'fa-solid fa-robot message-icon'
"></text>
<text
:class="
message.isUser
? 'fa-solid fa-user message-icon'
: 'fa-solid fa-robot message-icon'
"
></text>
<!-- 会话内容 --> <!-- 会话内容 -->
<view class="message-content"> <view class="message-content">
<!-- <text class="message-text">{{ message.content }}</text> --> <!-- <text class="message-text">{{ message.content }}</text> -->
<!-- loading --> <!-- loading -->
<view class="loading-dots" v-if="message.isThinking || !message.isUser">
<view
class="loading-dots"
v-if="message.isThinking || !message.isUser"
>
<view class="thinking-process"> <view class="thinking-process">
<view class="thinking-header"> <view class="thinking-header">
<view class="thinking-icon"></view> <view class="thinking-icon"></view>
@ -107,7 +148,10 @@
message.isTyping ? "正在思考" : "思考完成" message.isTyping ? "正在思考" : "思考完成"
}}</view> }}</view>
<view class="thinking-count"> </view> <view class="thinking-count"> </view>
<view class="thinking-toggle" @click="message.isThinking = !message.isThinking">
<view
class="thinking-toggle"
@click="message.isThinking = !message.isThinking"
>
<span v-if="message.isThinking"></span> <span v-if="message.isThinking"></span>
<span v-else></span> <span v-else></span>
</view> </view>
@ -129,8 +173,11 @@
</view> </view>
</view> </view>
<!-- 使用 rich-text 渲染 Markdown 内容 --> <!-- 使用 rich-text 渲染 Markdown 内容 -->
<rich-text v-if="!message.isUser" class="message-text"
:nodes="renderMarkdown(message.content)"></rich-text>
<rich-text
v-if="!message.isUser"
class="message-text"
:nodes="renderMarkdown(message.content)"
></rich-text>
<text v-else class="message-text">{{ message.content }}</text> <text v-else class="message-text">{{ message.content }}</text>
</view> </view>
</view> </view>
@ -141,8 +188,14 @@
<!-- 输入框区域 --> <!-- 输入框区域 -->
<view class="input-area"> <view class="input-area">
<view class="input-wrapper"> <view class="input-wrapper">
<input type="text" placeholder="请输入股票代码/名称,获取AI洞察" placeholder-style="color:#fff;opacity:1" class="input-field"
v-model="inputMessage" @confirm="sendMessage" />
<input
type="text"
placeholder="请输入股票代码/名称,获取AI洞察"
placeholder-style="color:#fff;opacity:1"
class="input-field"
v-model="inputMessage"
@confirm="sendMessage"
/>
<image class="send-button" @click="sendMessage" :disabled="isSending"> <image class="send-button" @click="sendMessage" :disabled="isSending">
<!-- <image <!-- <image
src="https://d31zlh4on95l9h.cloudfront.net/images/95f1ea2262e9157db13c93c0dc1c5d96.svg" src="https://d31zlh4on95l9h.cloudfront.net/images/95f1ea2262e9157db13c93c0dc1c5d96.svg"
@ -150,18 +203,26 @@
></image> --> ></image> -->
</image> </image>
</view> </view>
<text class="disclaimer">以上数据由AI生成不作为最终投资建议决策需独立</text>
<text class="disclaimer"
>以上数据由AI生成不作为最终投资建议决策需独立</text
>
</view> </view>
<image class="back-to-top" src="https://d31zlh4on95l9h.cloudfront.net/images/ba357635d2bb480241952bb1cabacd73.svg"
<image
class="back-to-top"
src="https://d31zlh4on95l9h.cloudfront.net/images/ba357635d2bb480241952bb1cabacd73.svg"
:style="{ :style="{
transform: 'translate3d(' + backTopX + 'px,' + backTopY + 'px,0)', transform: 'translate3d(' + backTopX + 'px,' + backTopY + 'px,0)',
}" @touchstart="onBackTopTouchStart" @touchmove="onBackTopTouchMove" @touchend="onBackTopTouchEnd"
@click="onBackTopClick"></image>
}"
@touchstart="onBackTopTouchStart"
@touchmove="onBackTopTouchMove"
@touchend="onBackTopTouchEnd"
@click="onBackTopClick"
></image>
<!-- 搜索历史侧拉框 --> <!-- 搜索历史侧拉框 -->
<view class="drawer-overlay" v-show="showHistoryDrawer"></view> <view class="drawer-overlay" v-show="showHistoryDrawer"></view>
<view class="drawer-panel" v-show="showHistoryDrawer" @click.stop @touchmove.stop.prevent
<!-- <view class="drawer-panel" v-show="showHistoryDrawer" @click.stop @touchmove.stop.prevent
:style="{ transform: 'translateY(' + drawerOffsetY + 'px)' }"> :style="{ transform: 'translateY(' + drawerOffsetY + 'px)' }">
<view class="drawer-header"> <view class="drawer-header">
@ -193,6 +254,59 @@
</view> </view>
</view> </view>
</scroll-view> </scroll-view>
</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="historyList.length === 0" class="empty-history">
<text>暂无历史记录</text>
</view>
<view
v-for="(section, sIdx) in historyList"
: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">{{ item.createdTime }}</text>
</view>
</view>
</view>
</scroll-view>
</view> </view>
<footerBar class="static-footer" :type="type"></footerBar> <footerBar class="static-footer" :type="type"></footerBar>
@ -206,7 +320,12 @@ import { ref, computed, onMounted, onUnmounted, watch, nextTick } from "vue";
import footerBar from "../../components/footerBar"; import footerBar from "../../components/footerBar";
import marked from "marked"; // marked import marked from "marked"; // marked
import { onPageScroll } from "@dcloudio/uni-app"; import { onPageScroll } from "@dcloudio/uni-app";
import { postStock, postIntent } from "../../api/deepMate/deepMate";
import {
postStock,
postIntent,
postHistory,
postHistoryDetail,
} from "../../api/deepMate/deepMate";
// marked // marked
marked.setOptions({ marked.setOptions({
renderer: new marked.Renderer(), renderer: new marked.Renderer(),
@ -237,6 +356,7 @@ const messages = ref([]);
const showHistoryDrawer = ref(false); const showHistoryDrawer = ref(false);
const drawerOffsetY = ref(0); const drawerOffsetY = ref(0);
const searchHistory = ref([]); const searchHistory = ref([]);
const historyList = ref([]);
const hotTopics = ref([ const hotTopics = ref([
{ {
id: 1, id: 1,
@ -289,7 +409,7 @@ onMounted(() => {
// YYYY-MM-DD // YYYY-MM-DD
const todayStr = new Date().toISOString().slice(0, 10); const todayStr = new Date().toISOString().slice(0, 10);
uni.setStorageSync('today_date', todayStr);
uni.setStorageSync("today_date", todayStr);
}); });
// UUID // UUID
@ -336,12 +456,26 @@ const goBlank = () => {
// //
const openHistoryDrawer = () => { const openHistoryDrawer = () => {
const res = postHistory({
model: 5,
});
if (res.code === 200) {
historyList.value = res.data;
}
console.log("historyList.value", historyList.value);
const hideDistance = uni.upx2px(900); const hideDistance = uni.upx2px(900);
drawerOffsetY.value = hideDistance; drawerOffsetY.value = hideDistance;
showHistoryDrawer.value = true; showHistoryDrawer.value = true;
setTimeout(() => { drawerOffsetY.value = 0; }, 10);
setTimeout(() => {
drawerOffsetY.value = 0;
}, 10);
};
const closeHistoryDrawer = () => {
showHistoryDrawer.value = false;
}; };
const closeHistoryDrawer = () => { showHistoryDrawer.value = false; };
const onDrawerBackClick = () => { const onDrawerBackClick = () => {
const hideDistance = uni.upx2px(900); const hideDistance = uni.upx2px(900);
drawerOffsetY.value = hideDistance; drawerOffsetY.value = hideDistance;
@ -363,65 +497,65 @@ const formatTime = (t) => {
return `${y}-${m}-${day} ${hh}:${mm}`; return `${y}-${m}-${day} ${hh}:${mm}`;
}; };
// ///
const groupedHistory = computed(() => {
const sections = [];
// 使
const cachedTodayStr = uni.getStorageSync('today_date');
const now = cachedTodayStr ? new Date(cachedTodayStr + 'T00:00:00') : 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();
searchHistory.value.forEach((item) => {
const dt = new Date(item.time);
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: `${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;
});
// // ///
// const groupedHistory = computed(() => {
// const sections = [];
// // 使
// const cachedTodayStr = uni.getStorageSync('today_date');
// const now = cachedTodayStr ? new Date(cachedTodayStr + 'T00:00:00') : 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();
// searchHistory.value.forEach((item) => {
// const dt = new Date(item.time);
// 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: `${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;
// });
const clearAllHistory = () => { const clearAllHistory = () => {
searchHistory.value = []; searchHistory.value = [];
uni.setStorageSync('search_history', []);
uni.setStorageSync("search_history", []);
}; };
// //
@ -465,7 +599,6 @@ const simulateBotResponse = async (userMessage) => {
messages.value.push(botMsg); messages.value.push(botMsg);
await new Promise((resolve) => setTimeout(resolve, 2000));
isSending.value = true; isSending.value = true;
// //
@ -488,7 +621,6 @@ const simulateBotResponse = async (userMessage) => {
const parentId = res.data.parentId; const parentId = res.data.parentId;
const stockId = res.data.stockId; const stockId = res.data.stockId;
await new Promise((resolve) => setTimeout(resolve, 2000));
// //
const StockInfo = await postStock({ const StockInfo = await postStock({
language: 'cn', language: 'cn',
@ -550,8 +682,8 @@ const simulateBotResponse = async (userMessage) => {
const delay = slowPunct.test(ch) const delay = slowPunct.test(ch)
? 220 ? 220
: midPunct.test(ch) : midPunct.test(ch)
? 120
: baseDelay;
? 120
: baseDelay;
setTimeout(typeWriter, delay); setTimeout(typeWriter, delay);
} else { } else {
const current = messages.value[botIndex]; const current = messages.value[botIndex];
@ -698,6 +830,27 @@ const onBackTopClick = () => {
if (backTopDragging.value) return; // if (backTopDragging.value) return; //
scrollToTop(); scrollToTop();
}; };
//
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);
}
}
</script> </script>
<style scoped> <style scoped>
@ -866,7 +1019,7 @@ const onBackTopClick = () => {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: url('https://d31zlh4on95l9h.cloudfront.net/images/eca84d9fb54712cb3bc6c6174773b83b.svg');
background: url("https://d31zlh4on95l9h.cloudfront.net/images/eca84d9fb54712cb3bc6c6174773b83b.svg");
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center top; background-position: center top;
/* 放在容器顶部,正好在灰色卡片下方 */ /* 放在容器顶部,正好在灰色卡片下方 */
@ -1014,7 +1167,7 @@ const onBackTopClick = () => {
} }
.bot-message .message-icon { .bot-message .message-icon {
background: url('/static/images/机器人 (1).svg');
background: url("/static/images/机器人 (1).svg");
color: white; color: white;
} }
@ -1081,7 +1234,6 @@ const onBackTopClick = () => {
} }
@keyframes loading { @keyframes loading {
0%, 0%,
80%, 80%,
100% { 100% {
@ -1285,8 +1437,6 @@ const onBackTopClick = () => {
gap: 14rpx; gap: 14rpx;
} }
.delete-icon { .delete-icon {
width: 45rpx; width: 45rpx;
height: 40rpx; height: 40rpx;
@ -1297,7 +1447,7 @@ const onBackTopClick = () => {
} }
.drawer-close { .drawer-close {
background: url('/static/icons/关闭2.svg');
background: url("/static/icons/关闭2.svg");
width: 48rpx; width: 48rpx;
height: 48rpx; height: 48rpx;
border-radius: 24rpx; border-radius: 24rpx;

63
pages/deepMate/scroll/scroll.vue

@ -1,63 +0,0 @@
<template>
<view class="container">
<scroll-view
class="scroll-view"
scroll-y="true"
@scrolltolower="onScrollToLower"
>
<button @click="scrollTo">点击滚动</button>
<view v-for="(item, index) in list" :key="index">
{{ item }}
</view>
</scroll-view>
<button @click="addItem">添加内容</button>
</view>
</template>
<script>
export default {
data() {
return {
list: Array.from({ length: 20 }, (_, i) => `Item ${i + 1}`),
};
},
methods: {
onScrollToLower() {
console.log("滚动到底部了");
//
},
addItem() {
this.list.push(`Item ${this.list.length + 1}`);
this.$nextTick(() => {
this.$refs.scrollView.scrollTo({
top: 99999,
duration: 300,
});
});
},
scrollTo() {
uni.pageScrollTo({
scrollTop: 100,
duration: 300,
});
},
},
};
</script>
<style>
.container {
height: 100%;
}
.scroll-view {
height: 80%;
}
button {
width: 100%;
height: 50px;
line-height: 50px;
text-align: center;
background-color: #007aff;
color: white;
}
</style>

579
pages/home/globalIndex.vue

@ -0,0 +1,579 @@
<!-- @format -->
<template>
<view class="main">
<!-- 固定头部 -->
<view class="header_fixed" :style="{ top: iSMT + 'px' }">
<view class="header_content">
<view class="header_back" @click="goBack">
<image src="/static/marketSituation-image/back.png" mode=""></image>
</view>
<view class="header_input_wrapper">
<image class="search_icon" src="/static/marketSituation-image/search.png" mode="" @click="onSearchClick"></image>
<input class="header_input" type="text" placeholder="搜索" placeholder-style="color: #A6A6A6; font-size: 22rpx;" v-model="searchValue" @input="onSearchInput" @confirm="onSearchConfirm" />
</view>
<view class="header_icons">
<view class="header_icon" @click="selected">
<image src="/static/marketSituation-image/mySeclected.png" mode=""></image>
</view>
<view class="header_icon" @click="history">
<image src="/static/marketSituation-image/history.png" mode=""></image>
</view>
</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>
<!-- 内容区域 -->
<scroll-view class="content" :style="{ top: contentTopPosition + 'px' }" scroll-y="true">
<!-- 亚太-中华 -->
<view class="market-section">
<view class="market-header">
<text class="market-title">亚太-中华</text>
<view class="market-more" @click="viewMore('asia-china')">
<text class="more-text">查看更多</text>
<text class="more-arrow">></text>
</view>
</view>
<view class="cards-grid-three">
<view v-for="(item, index) in asiachinaIndexes" :key="index" class="card-item">
<IndexCard :flagIcon="item.flagIcon" :stockName="item.stockName" :currentPrice="item.currentPrice" :changeAmount="item.changeAmount" :changePercent="item.changePercent" :isRising="item.isRising" @click="viewIndexDetail(item)" />
</view>
</view>
</view>
<!-- 亚太 -->
<view class="market-section">
<view class="market-header">
<text class="market-title">亚太</text>
<view class="market-more" @click="viewMore('asia')">
<text class="more-text">查看更多</text>
<text class="more-arrow">></text>
</view>
</view>
<view class="cards-grid-three">
<view v-for="(item, index) in asiaIndexes" :key="index" class="card-item">
<IndexCard :flagIcon="item.flagIcon" :stockName="item.stockName" :currentPrice="item.currentPrice" :changeAmount="item.changeAmount" :changePercent="item.changePercent" :isRising="item.isRising" @click="viewIndexDetail(item)" />
</view>
</view>
</view>
<!-- 美洲 -->
<view class="market-section">
<view class="market-header">
<text class="market-title">美洲</text>
<view class="market-more" @click="viewMore('america')">
<text class="more-text">查看更多</text>
<text class="more-arrow">></text>
</view>
</view>
<view class="cards-grid-three">
<view v-for="(item, index) in americaIndexes" :key="index" class="card-item">
<IndexCard :flagIcon="item.flagIcon" :stockName="item.stockName" :currentPrice="item.currentPrice" :changeAmount="item.changeAmount" :changePercent="item.changePercent" :isRising="item.isRising" @click="viewIndexDetail(item)" />
</view>
</view>
</view>
<!-- 底部安全区域 -->
<view class="bottom-safe-area"></view>
</scroll-view>
</view>
<!-- 底部导航栏 -->
<footerBar class="static-footer" :type="'marketSituation'"></footerBar>
</template>
<script setup>
import { ref, onMounted, computed, nextTick, watch } from "vue";
import footerBar from "../../components/footerBar.vue";
import IndexCard from "../../components/IndexCard.vue";
//
const iSMT = ref(0); //
const contentHeight = ref(0);
const headerHeight = ref(0); //
const searchValue = ref(""); //
const isWarnTextOverflow = ref(false); // warn
// warnclass
const warnTextClass = computed(() => {
return isWarnTextOverflow.value ? "warn_text scroll-active" : "warn_text";
});
// 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 asiachinaIndexes = ref([
{
flagIcon: "/static/c1.png",
stockName: "上证指数",
stockCode: "1A0001",
currentPrice: "3933.96",
changeAmount: "+24.32",
changePercent: "+0.62%",
isRising: true,
},
{
flagIcon: "/static/c2.png",
stockName: "深证成指",
stockCode: "2A01",
currentPrice: "45757.90",
changeAmount: "-123.45",
changePercent: "-0.27%",
isRising: false,
},
{
flagIcon: "/static/c3.png",
stockName: "创业板指",
stockCode: "399006",
currentPrice: "6606.08",
changeAmount: "+89.76",
changePercent: "+1.38%",
isRising: true,
},
{
flagIcon: "/static/c4.png",
stockName: "沪深300",
stockCode: "1B0300",
currentPrice: "45757.90",
changeAmount: "-89.12",
changePercent: "-0.19%",
isRising: false,
},
{
flagIcon: "/static/c5.png",
stockName: "上证50",
stockCode: "1B0011",
currentPrice: "45757.90",
changeAmount: "+234.56",
changePercent: "+0.52%",
isRising: true,
},
{
flagIcon: "/static/c6.png",
stockName: "科创50",
stockCode: "1B0688",
currentPrice: "22333.96",
changeAmount: "+156.78",
changePercent: "+0.71%",
isRising: true,
},
]);
//
const asiaIndexes = ref([
{
flagIcon: "/static/c7.png",
stockName: "日经225",
stockCode: "noCode",
currentPrice: "28456.78",
changeAmount: "+234.56",
changePercent: "+0.83%",
isRising: true,
},
{
flagIcon: "/static/c8.png",
stockName: "韩国KOSPI",
stockCode: "noCode",
currentPrice: "2567.89",
changeAmount: "-12.34",
changePercent: "-0.48%",
isRising: false,
},
{
flagIcon: "/static/c9.png",
stockName: "印度孟买",
stockCode: "noCode",
currentPrice: "65432.10",
changeAmount: "+456.78",
changePercent: "+0.70%",
isRising: true,
},
]);
//
const americaIndexes = ref([
{
flagIcon: "/static/c7.png",
stockName: "道琼斯指数",
stockCode: "noCode",
currentPrice: "34567.89",
changeAmount: "+123.45",
changePercent: "+0.36%",
isRising: true,
},
{
flagIcon: "/static/c8.png",
stockName: "纳斯达克",
stockCode: "noCode",
currentPrice: "13456.78",
changeAmount: "-67.89",
changePercent: "-0.50%",
isRising: false,
},
{
flagIcon: "/static/c9.png",
stockName: "标普500",
stockCode: "noCode",
currentPrice: "4234.56",
changeAmount: "+23.45",
changePercent: "+0.56%",
isRising: true,
},
]);
//
const contentTopPosition = computed(() => {
const statusBarHeight = iSMT.value || 0;
const currentHeaderHeight = headerHeight.value > 0 ? headerHeight.value : 100;
return statusBarHeight + currentHeaderHeight;
});
//
const goBack = () => {
uni.navigateBack();
};
//
const onSearchInput = (e) => {
searchValue.value = e.detail.value;
};
//
const clearSearch = () => {
searchValue.value = "";
};
//
const viewMore = (market) => {
console.log("查看更多:", market);
uni.navigateTo({
url: `/pages/home/marketDetail?market=${market}`,
});
};
//
const viewIndexDetail = (item) => {
console.log("查看指数详情:", item.stockName);
// uni.showToast({
// title: ` ${item.stockName} `,
// icon: 'none',
// duration: 2000
// })
//
uni.navigateTo({
url: `/pages/home/marketCondition?stockInformation=${encodeURIComponent(JSON.stringify(item))}`,
});
};
//
onMounted(() => {
//
const systemInfo = uni.getSystemInfoSync();
iSMT.value = systemInfo.statusBarHeight || 0;
console.log("全球指数页面加载完成");
// 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 lang="scss" scoped>
.main {
position: relative;
height: 100vh;
overflow: hidden;
background-color: #f5f5f5;
}
/* 状态栏占位 */
.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);
}
.header_content {
display: flex;
align-items: center;
justify-content: space-between;
height: 80rpx;
padding: 0 20rpx;
margin-bottom: 10rpx;
}
.header_back {
margin-right: 20rpx;
width: 25rpx;
height: 30rpx;
}
.header_back image {
width: 25rpx;
height: 30rpx;
}
.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;
}
.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秒开始滚动,让用户先看到开头 */
}
/* 内容区域 */
.content {
position: fixed;
left: 0;
right: 0;
bottom: 120rpx;
background-color: #f5f5f5;
padding: 0;
}
/* 市场分组 */
.market-section {
background-color: white;
border-radius: 20rpx;
}
.market-header {
margin: 20rpx 20rpx 0 20rpx;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10rpx;
padding-bottom: 10rpx;
border-bottom: 2rpx solid #f0f0f0;
}
.market-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.market-more {
display: flex;
align-items: center;
gap: 8rpx;
}
.more-text {
font-size: 24rpx;
color: #666;
}
.more-arrow {
font-size: 20rpx;
color: #666;
font-weight: bold;
}
/* 三列卡片网格 */
.cards-grid-three {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
.card-item {
background-color: white;
border-radius: 16rpx;
overflow: hidden;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.card-item:active {
transform: scale(0.98);
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.12);
}
/* 底部安全区域 */
.bottom-safe-area {
height: 40rpx;
background-color: transparent;
}
/* 底部导航栏 */
.static-footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
}
/* 响应式设计 */
@media (max-width: 400rpx) {
.cards-grid-three {
grid-template-columns: repeat(2, 1fr);
}
}
</style>

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

488
pages/home/marketDetail.vue

@ -0,0 +1,488 @@
<!-- @format -->
<template>
<view class="main">
<!-- 自定义导航栏 -->
<view class="header_fixed" :style="{ top: iSMT + 'px' }">
<view class="header-content">
<view class="header-left" @click="goBack">
<text class="back-text"></text>
</view>
<view class="header-center">
<text class="header-title">{{ marketTitle }}</text>
</view>
<view class="header-right">
<image src="/static/marketSituation-image/search.png" class="header-icon" mode="aspectFit"></image>
<text class="more-text">···</text>
</view>
</view>
<!-- 表头 -->
<view class="table-header">
<view class="header-item name-column">
<text class="header-text">名称</text>
</view>
<view class="header-item price-column" @click="sortByPrice">
<text class="header-text">最新</text>
<text class="sort-icon">{{ sortType === "price" ? (sortOrder === "asc" ? "↑" : "↓") : "↕" }}</text>
</view>
<view class="header-item change-column" @click="sortByChange">
<text class="header-text">涨幅</text>
<text class="sort-icon">{{ sortType === "change" ? (sortOrder === "asc" ? "↑" : "↓") : "↕" }}</text>
</view>
</view>
</view>
<!-- 内容区域 -->
<scroll-view class="content" :style="{ top: contentTopPosition + 'px' }" scroll-y="true">
<!-- 股票列表 -->
<view class="stock-list">
<view class="stock-row" v-for="(stock, index) in sortedStockList" :key="index" @click="viewStockDetail(stock)">
<view class="stock-cell name-column">
<view class="stock-name">{{ stock.stockName }}</view>
<view class="stock-code">{{ stock.stockCode }}</view>
</view>
<view class="stock-cell price-column">
<text class="stock-price" :class="stock.isRising ? 'rising' : 'falling'">
{{ typeof stock.price === "number" ? stock.price.toFixed(2) : stock.price }}
</text>
</view>
<view class="stock-cell change-column">
<text class="stock-change" :class="stock.isRising ? 'rising' : 'falling'">
{{ stock.change || stock.changePercent }}
</text>
</view>
</view>
</view>
<!-- 底部安全区域 -->
<!-- <view class="bottom-safe-area"></view> -->
</scroll-view>
</view>
<!-- 底部导航栏 -->
<!-- <footerBar class="static-footer" :type="'marketSituation'"></footerBar> -->
</template>
<script setup>
import { ref, computed, onMounted, watch } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import footerBar from "@/components/footerBar.vue";
//
const iSMT = ref(0);
const headerHeight = ref(80);
const marketType = ref("america");
const marketTitle = ref("美洲");
const sortType = ref(""); // 'price' 'change'
const sortOrder = ref("desc"); // 'asc' 'desc'
//
const stockList = ref([
{
stockName: "Telecommunication",
stockCode: "888607",
price: 1349.47,
change: "+7.67%",
isRising: true,
},
{
stockName: "Other",
stockCode: "888607",
price: 1349.47,
change: "+6.67%",
isRising: true,
},
{
stockName: "Consumer Discretio...",
stockCode: "888610",
price: 1349.47,
change: "+5.67%",
isRising: true,
},
{
stockName: "Telecommunication",
stockCode: "888607",
price: 1349.47,
change: "+4.67%",
isRising: true,
},
{
stockName: "Other",
stockCode: "888611",
price: 1359.47,
change: "+3.67%",
isRising: true,
},
{
stockName: "Consumer Discretio...",
stockCode: "888610",
price: 1349.47,
change: "+2.67%",
isRising: true,
},
{
stockName: "Telecommunication",
stockCode: "888607",
price: 1349.47,
change: "+1.67%",
isRising: true,
},
{
stockName: "Other",
stockCode: "888611",
price: 1009.98,
change: "-1.67%",
isRising: false,
},
{
stockName: "Consumer Discretio...",
stockCode: "888610",
price: 1009.98,
change: "-0.67%",
isRising: false,
},
{
stockName: "Telecommunication",
stockCode: "888607",
price: 1009.98,
change: "-0.67%",
isRising: false,
},
{
stockName: "Other",
stockCode: "888611",
price: 1009.98,
change: "-1.67%",
isRising: false,
},
{
stockName: "Consumer Discretio...",
stockCode: "888610",
price: 1009.98,
change: "-4.67%",
isRising: false,
},
{
stockName: "Consumer Discretio...",
stockCode: "888610",
price: 1009.98,
change: "-3.67%",
isRising: false,
},
{
stockName: "Consumer Discretio...",
stockCode: "888610",
price: 1009.98,
change: "-3.67%",
isRising: false,
},
]);
//
const contentTopPosition = computed(() => {
return iSMT.value + headerHeight.value;
});
const sortedStockList = computed(() => {
console.log("计算sortedStockList,原始数据长度:", stockList.value.length);
let list = [...stockList.value];
if (sortType.value === "price") {
list.sort((a, b) => {
return sortOrder.value === "asc" ? a.price - b.price : b.price - a.price;
});
} else if (sortType.value === "change") {
list.sort((a, b) => {
const aChange = parseFloat(a.change.replace(/[+%-]/g, ""));
const bChange = parseFloat(b.change.replace(/[+%-]/g, ""));
return sortOrder.value === "asc" ? aChange - bChange : bChange - aChange;
});
}
console.log("排序后数据长度:", list.length);
return list;
});
//
onLoad((options) => {
if (options && options.market) {
marketType.value = options.market;
switch (options.market) {
case "america":
marketTitle.value = "美洲";
break;
case "asia":
marketTitle.value = "亚太";
break;
case "asia-china":
marketTitle.value = "亚太-中华";
break;
default:
marketTitle.value = "全球指数";
}
}
});
//
const goBack = () => {
uni.navigateBack();
};
const sortByPrice = () => {
if (sortType.value === "price") {
sortOrder.value = sortOrder.value === "asc" ? "desc" : "asc";
} else {
sortType.value = "price";
sortOrder.value = "desc";
}
};
const sortByChange = () => {
if (sortType.value === "change") {
sortOrder.value = sortOrder.value === "asc" ? "desc" : "asc";
} else {
sortType.value = "change";
sortOrder.value = "desc";
}
};
const viewStockDetail = (stock) => {
console.log("查看股票详情:", stock);
//
uni.navigateTo({
url: `/pages/home/marketCondition?stockInformation=${encodeURIComponent(JSON.stringify(stock))}`,
});
};
onMounted(() => {
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
// header
uni
.createSelectorQuery()
.select(".header_fixed")
.boundingClientRect((rect) => {
if (rect) {
headerHeight.value = rect.height;
console.log("Header实际高度:", headerHeight.value, "px");
}
})
.exec();
});
// 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>
.main {
width: 100%;
height: 100vh;
background-color: #f5f5f5;
position: relative;
}
/* 自定义导航栏 */
.header_fixed {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
background-color: #ffffff;
border-bottom: 1px solid #f0f0f0;
}
.header-content {
display: flex;
align-items: center;
justify-content: space-between;
height: 44px;
padding: 0 15px;
}
.header-left,
.header-right {
width: 60px;
display: flex;
align-items: center;
}
.header-left {
justify-content: flex-start;
}
.header-right {
justify-content: flex-end;
gap: 10px;
}
.back-text {
font-size: 24px;
color: #333333;
font-weight: 500;
line-height: 1;
}
.header-center {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.header-title {
font-size: 18px;
font-weight: 600;
color: #333333;
}
.header-icon {
width: 20px;
height: 20px;
}
.more-text {
font-size: 20px;
color: #666666;
font-weight: bold;
}
/* 内容区域 */
.content {
position: fixed;
left: 0;
right: 0;
bottom: 0;
background-color: #ffffff;
}
/* 表头样式 */
.table-header {
display: flex;
padding: 12px 15px;
background-color: #f8f9fa;
border-bottom: 1px solid #e9ecef;
}
.header-item {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.header-item.name-column {
flex: 2;
justify-content: flex-start;
}
.header-item.price-column,
.header-item.change-column {
flex: 1;
justify-content: center;
}
.header-text {
font-size: 14px;
color: #666666;
font-weight: 500;
}
.sort-icon {
margin-left: 4px;
font-size: 12px;
color: #999999;
}
/* 股票列表 */
.stock-list {
background-color: #ffffff;
}
.stock-row {
display: flex;
align-items: center;
padding: 12px 15px;
border-bottom: 1px solid #f5f5f5;
}
.stock-row:active {
background-color: #f8f8f8;
}
.stock-cell {
display: flex;
flex-direction: column;
align-items: center;
}
.stock-cell.name-column {
flex: 2;
align-items: flex-start;
}
.stock-cell.price-column,
.stock-cell.change-column {
flex: 1;
align-items: center;
}
.stock-name {
font-size: 15px;
color: #333333;
font-weight: 500;
line-height: 1.2;
margin-bottom: 2px;
}
.stock-code {
font-size: 11px;
color: #999999;
line-height: 1.2;
}
.stock-price {
font-size: 15px;
font-weight: 600;
line-height: 1.2;
}
.stock-change {
font-size: 13px;
font-weight: 500;
line-height: 1.2;
}
.rising {
color: #00c851;
}
.falling {
color: #ff4444;
}
/* 底部安全区域 */
/* .bottom-safe-area {
height: 20px;
} */
/* 底部导航栏 */
/* .static-footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
} */
</style>

922
pages/home/marketSituation.vue

@ -1,28 +1,920 @@
<!-- @format -->
<template> <template>
<view class="main">
<!-- 顶部状态栏占位 -->
<view class="top" :style="{height:iSMT+'px'}"></view>
<view>行情</view>
<footerBar class="static-footer" :type="type"></footerBar>
</view>
<view class="main">
<!-- 固定头部 -->
<view class="header_fixed" :style="{ top: iSMT + 'px' }">
<view class="header_content">
<view class="header_input_wrapper">
<image class="search_icon" src="/static/marketSituation-image/search.png" mode="" @click="onSearchClick"></image>
<input class="header_input" type="text" placeholder="搜索" placeholder-style="color: #A6A6A6; font-size: 22rpx;" v-model="searchValue" @input="onSearchInput" @confirm="onSearchConfirm" />
</view>
<view class="header_icons">
<view class="header_icon" @click="selected">
<image src="/static/marketSituation-image/mySeclected.png" mode=""></image>
</view>
<view class="header_icon" @click="history">
<image src="/static/marketSituation-image/history.png" mode=""></image>
</view>
</view>
</view>
<view class="channel_li" v-if="channelData.length > 0">
<scroll-view class="channel_wrap" scroll-x="true" :scroll-into-view="scrollToView" :scroll-with-animation="true" show-scrollbar="false">
<view class="channel_innerWrap">
<view v-for="(item, index) in channelData" :key="item.id" :id="'nav' + item.id" :class="['channel_item', index === pageIndex ? 'active' : '']" @click="navClick(index)">
<text class="channel_text">{{ item.title }}</text>
<view v-if="index === pageIndex" class="active_indicator"></view>
</view>
</view>
</scroll-view>
<view class="scroll_indicator" @click="channel_more">
<image src="/static/marketSituation-image/menu.png" mode="aspectFit"></image>
</view>
</view>
</view>
<!-- 可滚动内容区域 -->
<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" :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>
<footerBar class="static-footer" :type="type"></footerBar>
<!-- 更多tab弹窗 -->
<view v-if="showCountryModal" class="modal_overlay" @click="closeModal">
<view class="modal_content" @click.stop>
<view class="modal_header">
<text class="modal_title">全部栏目</text>
<view class="modal_close" @click="closeModal">
<text>×</text>
</view>
</view>
<view class="modal_body">
<view class="country_grid">
<view v-for="(country, index) in countryList" :key="index" :class="['country_item', selectedCountry === country ? 'selected' : '']" @click="selectCountry(country)">
<text class="country_text">{{ country }}</text>
</view>
</view>
</view>
</view>
</view>
</template> </template>
<script setup> <script setup>
import { ref,onMounted } from 'vue'
import footerBar from '../../components/footerBar.vue'
import { ref, onMounted, watch, nextTick, computed } from "vue";
import util from "../../common/util.js";
import footerBar from "../../components/footerBar.vue";
import IndexCard from "../../components/IndexCard.vue";
const type = ref("marketSituation");
const iSMT = ref(0);
const searchValue = ref("");
const contentHeight = ref(0);
const headerHeight = ref(0); // header
const isWarnTextOverflow = ref(false); // warn
// Tab
const channelData = ref([
{ id: 1, title: "概况" },
{ id: 2, title: "新加坡" },
{ id: 3, title: "马来西亚" },
{ id: 4, title: "印度尼西亚" },
{ id: 5, title: "美国" },
{ id: 6, title: "中国香港" },
{ id: 7, title: "泰国" },
{ id: 8, title: "中国" },
{ id: 9, title: "加拿大" },
{ id: 10, title: "越南" },
{ id: 11, title: "外汇" },
{ id: 12, title: "贵金属" },
]);
const pageIndex = ref(0);
const scrollToView = ref("");
// 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: "DJIA",
currentPrice: "45757.90",
changeAmount: "-125.22",
changePercent: "-0.27%",
isRising: false,
},
{
flagIcon: "🇺🇸",
stockName: "纳斯达克",
stockCode: "NDX",
currentPrice: "22333.96",
changeAmount: "+125.22",
changePercent: "+0.47%",
isRising: true,
},
{
flagIcon: "🇺🇸",
stockName: "标普500",
stockCode: "SPX",
currentPrice: "6606.08",
changeAmount: "+125.22",
changePercent: "+0.27%",
isRising: true,
},
{
flagIcon: "🇨🇳",
stockName: "上证指数",
stockCode: "1A0001",
currentPrice: "3333.96",
changeAmount: "+125.22",
changePercent: "+0.27%",
isRising: true,
},
{
flagIcon: "🇨🇳",
stockName: "科创50",
stockCode: "1B0688",
currentPrice: "757.90",
changeAmount: "-25.22",
changePercent: "-0.27%",
isRising: false,
},
{
flagIcon: "🇭🇰",
stockName: "恒生指数",
stockCode: "HSI",
currentPrice: "19757.90",
changeAmount: "-125.22",
changePercent: "-0.63%",
isRising: false,
},
{
flagIcon: "🇸🇬",
stockName: "道琼斯",
stockCode: "DJIA",
currentPrice: "3757.90",
changeAmount: "+85.22",
changePercent: "+2.31%",
isRising: true,
},
{
flagIcon: "🇲🇾",
stockName: "纳斯达克",
stockCode: "NDX",
currentPrice: "1657.90",
changeAmount: "-15.22",
changePercent: "-0.91%",
isRising: false,
},
{
flagIcon: "🇹🇭",
stockName: "标普500",
stockCode: "SPX",
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",
});
//
};
//
const selected = () => {
uni.showToast({
title: "我的收藏",
icon: "none",
});
//
};
//
const history = () => {
uni.showToast({
title: "历史记录",
icon: "none",
});
//
};
// Tab
const navClick = (index) => {
pageIndex.value = index;
const currentItem = channelData.value[index];
scrollToView.value = "nav" + currentItem.id;
//
selectedCountry.value = currentItem.title;
const type = ref('marketSituation')
const iSMT = ref(0)
uni.showToast({
title: `切换到: ${currentItem.title}`,
icon: "none",
});
// tab
console.log("当前选中的 tab:", currentItem);
};
//
const channel_more = () => {
showCountryModal.value = true;
};
//
const selectCountry = (country) => {
selectedCountry.value = country;
// tab
const targetIndex = channelData.value.findIndex((item) => item.title === country);
if (targetIndex !== -1) {
// tab
pageIndex.value = targetIndex;
const currentItem = channelData.value[targetIndex];
scrollToView.value = "nav" + currentItem.id;
console.log("选中了:" + country + ",同步到tab索引:" + targetIndex);
uni.showToast({
title: "已切换到:" + country,
icon: "none",
duration: 2000,
});
} else {
// ""tab
if (country === "概况" || country === "全部") {
pageIndex.value = 0;
scrollToView.value = "nav" + channelData.value[0].id;
}
console.log("选中了:" + country);
uni.showToast({
title: "已选择:" + country,
icon: "none",
duration: 2000,
});
}
// /
// loadMarketData(country)
closeModal();
};
//
const closeModal = () => {
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",
});
};
//
const viewIndexDetail = (item) => {
console.log("查看指数详情:", item.stockName);
// uni.showToast({
// title: ` ${item.stockName} `,
// icon: 'none',
// duration: 2000
// })
//
uni.navigateTo({
url: `/pages/home/marketCondition?stockInformation=${encodeURIComponent(JSON.stringify(item))}`,
});
};
onMounted(() => { onMounted(() => {
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
})
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
// tab
if (channelData.value.length > 0) {
pageIndex.value = 0;
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
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> </script>
<style scoped> <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 { .static-footer {
position: fixed;
bottom: 0;
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%;
}
.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>

296
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 type = ref('member')
const iSMT = ref(0)
const goToAccount = () => {
uni.navigateTo({
url:'../setting/account'
})
}
onMounted(() => {
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
})
const goToNewVersion = () =>{
uni.navigateTo({
url:'../setting/newVersion'
})
}
const goToAbout = () =>{
uni.navigateTo({
url:'../setting/about'
})
}
const goToShare = () =>{
uni.navigateTo({
url:'../setting/share'
})
}
onMounted(() => {
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight
console.log('??????????????', iSMT.value)
})
</script> </script>
<style scoped> <style scoped>
.static-footer {
position: fixed;
bottom: 0;
}
.static-footer {
position: fixed;
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>

22
pages/morningMarketAnalysis/morningMarketAnalysis.vue

@ -0,0 +1,22 @@
<template>
<view>
早盘解析页面
</view>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
}
}
</script>
<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>

213
pages/setting/account.vue

@ -0,0 +1,213 @@
<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'
const iSMT = ref(0)
const jwcode = ref('90047681')
const showLogout = ref(false)
const handleConfirmLogout = () => {
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>

2
pages/start/login/login.vue

@ -308,7 +308,7 @@ function showCountryPicker() {
function goToIndex() { function goToIndex() {
// //
uni.navigateTo({ uni.navigateTo({
url: "/pages/start/index/index",
url: "/pages/home/home",
}); });
} }

BIN
static/marketSituation-image/back.png

After

Width: 18  |  Height: 32  |  Size: 576 B

BIN
static/marketSituation-image/bg.png

After

Width: 82  |  Height: 24  |  Size: 3.0 KiB

BIN
static/marketSituation-image/history.png

After

Width: 36  |  Height: 33  |  Size: 2.0 KiB

BIN
static/marketSituation-image/map.png

After

Width: 623  |  Height: 400  |  Size: 113 KiB

BIN
static/marketSituation-image/marketCondition-image/favorites.png

After

Width: 33  |  Height: 32  |  Size: 460 B

BIN
static/marketSituation-image/marketCondition-image/function.png

After

Width: 36  |  Height: 36  |  Size: 539 B

BIN
static/marketSituation-image/marketCondition-image/index.png

After

Width: 46  |  Height: 46  |  Size: 1.7 KiB

BIN
static/marketSituation-image/marketCondition-image/setting2.png

After

Width: 32  |  Height: 32  |  Size: 2.3 KiB

BIN
static/marketSituation-image/menu.png

After

Width: 60  |  Height: 60  |  Size: 238 B

BIN
static/marketSituation-image/more.png

After

Width: 18  |  Height: 32  |  Size: 437 B

BIN
static/marketSituation-image/mySeclected.png

After

Width: 32  |  Height: 32  |  Size: 1.8 KiB

BIN
static/marketSituation-image/search.png

After

Width: 60  |  Height: 60  |  Size: 867 B

BIN
static/marketSituation-image/warn.png

After

Width: 30  |  Height: 30  |  Size: 1.5 KiB

BIN
static/my/BlackTheme.png

After

Width: 158  |  Height: 181  |  Size: 3.4 KiB

BIN
static/my/Check-in.png

After

Width: 22  |  Height: 22  |  Size: 614 B

BIN
static/my/MarketSettings.png

After

Width: 33  |  Height: 33  |  Size: 687 B

BIN
static/my/QRcode.png

After

Width: 160  |  Height: 160  |  Size: 17 KiB

BIN
static/my/Settings.png

After

Width: 27  |  Height: 27  |  Size: 1.2 KiB

BIN
static/my/about.png

After

Width: 21  |  Height: 19  |  Size: 636 B

BIN
static/my/aboutDC.png

After

Width: 167  |  Height: 155  |  Size: 8.7 KiB

BIN
static/my/award.png

After

Width: 150  |  Height: 60  |  Size: 2.8 KiB

BIN
static/my/bell.png

After

Width: 13  |  Height: 16  |  Size: 358 B

BIN
static/my/bindedEmail.png

After

Width: 20  |  Height: 20  |  Size: 380 B

BIN
static/my/bindedPhone.png

After

Width: 20  |  Height: 20  |  Size: 337 B

BIN
static/my/changeBindPhone.png

After

Width: 20  |  Height: 20  |  Size: 351 B

BIN
static/my/changeEmail.png

After

Width: 20  |  Height: 20  |  Size: 413 B

BIN
static/my/common.png

After

Width: 31  |  Height: 23  |  Size: 237 B

BIN
static/my/connection.png

After

Width: 20  |  Height: 20  |  Size: 748 B

BIN
static/my/editName.png

After

Width: 15  |  Height: 15  |  Size: 424 B

BIN
static/my/greenBackground.png

After

Width: 335  |  Height: 870  |  Size: 11 KiB

BIN
static/my/greenRise.png

After

Width: 26  |  Height: 25  |  Size: 993 B

BIN
static/my/hideEye.png

After

Width: 20  |  Height: 9  |  Size: 429 B

BIN
static/my/invite.png

After

Width: 375  |  Height: 1059  |  Size: 454 KiB

BIN
static/my/menu.png

After

Width: 14  |  Height: 14  |  Size: 228 B

BIN
static/my/myFriends.png

After

Width: 150  |  Height: 44  |  Size: 2.1 KiB

BIN
static/my/openEye.png

After

Width: 20  |  Height: 12  |  Size: 660 B

BIN
static/my/opinion.png

After

Width: 21  |  Height: 20  |  Size: 551 B

BIN
static/my/outline.png

After

Width: 31  |  Height: 25  |  Size: 1.3 KiB

BIN
static/my/polylines.png

After

Width: 31  |  Height: 23  |  Size: 1.3 KiB

BIN
static/my/redRise.png

After

Width: 26  |  Height: 25  |  Size: 966 B

BIN
static/my/security.png

After

Width: 23  |  Height: 23  |  Size: 669 B

BIN
static/my/setting.png

After

Width: 28  |  Height: 28  |  Size: 1.4 KiB

BIN
static/my/share.png

After

Width: 360  |  Height: 80  |  Size: 28 KiB

BIN
static/my/shareBackground.png

After

Width: 375  |  Height: 1059  |  Size: 456 KiB

BIN
static/my/shareFriends.png

After

Width: 301  |  Height: 397  |  Size: 50 KiB

BIN
static/my/unlock.png

After

Width: 20  |  Height: 20  |  Size: 488 B

BIN
static/my/update.png

After

Width: 21  |  Height: 17  |  Size: 488 B

BIN
static/my/verification.png

After

Width: 20  |  Height: 20  |  Size: 526 B

BIN
static/my/whiteTheme.png

After

Width: 158  |  Height: 181  |  Size: 3.4 KiB

2
utils/http.js

@ -1,6 +1,6 @@
import { useUserStore } from "../stores/modules/userInfo" import { useUserStore } from "../stores/modules/userInfo"
const baseURL = "https://hwjb.homilychart.com"
const baseURL = "https://hwjb.homilychart.com/testApi"
const httpInterceptor = { const httpInterceptor = {

Loading…
Cancel
Save