Compare commits

...

286 Commits

Author SHA1 Message Date
zhaowenkang 6784093cd2 跳转 3 weeks ago
zhaowenkang f0fbd2d2b4 更改 3 weeks ago
zhaowenkang 609e5e131e 行情定位 3 weeks ago
zhaowenkang 2e08839371 行情tab栏 4 weeks ago
zhaowenkang adfad32d1b Merge branch 'milestone-20251031-简版功能开发' into zhaowenkang/feature-20251028181547-行情页面 4 weeks ago
hongxilin 6f3e2bdab8 Merge branch 'hongxilin/feature-20251023103318-行情数据及页面' into milestone-20251031-简版功能开发 4 weeks ago
hongxilin 8d9267d63b 对接tcp全部接口,行情数据页面的左右翻页 4 weeks ago
宋杰 a60d29b0f2 创建了自定义的我的自选页面;点击添加自选股跳转到行情页面; 4 weeks ago
宋杰 19e325e83b 点击首页我的自选的查看更多跳转到对应页面; 4 weeks ago
宋杰 08b584483c 创建我的自选页面; 4 weeks ago
宋杰 86eb9a5e3e 新建今日核心看点的机构动向解析缺省页并配置跳转逻辑; 4 weeks ago
宋杰 ca90666703 Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into milestone-20251031-简版功能开发 4 weeks ago
宋杰 68715b32e5 Merge branch 'songjie/feature-20251023161635-首页' into milestone-20251031-简版功能开发 4 weeks ago
宋杰 c5cf0d8a9d 早盘解析缺省页面; 4 weeks ago
zhaowenkang 4033ca4d43 Merge branch 'milestone-20251031-简版功能开发' into zhaowenkang/feature-20251028181547-行情页面 4 weeks ago
lihui 7a2e448a5c Merge branch 'refs/heads/lihuilin/feature-20251024095243-我的' into milestone-20251031-简版功能开发 4 weeks ago
lihui 64a4aec305 add:分享跳转功能 4 weeks ago
宋杰 68610b43ee Merge branch 'songjie/feature-20251023161635-首页' into milestone-20251031-简版功能开发 4 weeks ago
宋杰 6345303249 注释掉初始化tcp连接的代码; 4 weeks ago
宋杰 c0cef2252c Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into milestone-20251031-简版功能开发 4 weeks ago
宋杰 8067177fb8 Merge branch 'songjie/feature-20251023161635-首页' into milestone-20251031-简版功能开发 4 weeks ago
宋杰 d0c1a19d3e 今日市场概览中点击早盘解析跳转到该页面; 4 weeks ago
lihui fb31937f05 Merge branch 'refs/heads/milestone-20251031-简版功能开发' into lihuilin/feature-20251024095243-我的 4 weeks ago
lihui bdf69e855f Merge remote-tracking branch 'origin/lihuilin/feature-20251024095243-我的' into lihuilin/feature-20251024095243-我的 4 weeks ago
dongqian f2981e95f1 Merge branch 'dongqian/feature-20251022181325-deepmate简版' into milestone-20251031-简版功能开发 4 weeks ago
dongqian b171b88fac 修改选股策略手机模拟器滚动和颜色修改 4 weeks ago
宋杰 5d0bdb613f Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into milestone-20251031-简版功能开发 4 weeks ago
宋杰 9f70c70893 Merge branch 'songjie/feature-20251023161635-首页' into milestone-20251031-简版功能开发 4 weeks ago
宋杰 6b7684795d 点击深度探索的查看更多跳转到对应页面; 4 weeks ago
hongxilin f28d600e16 删除打印语句 4 weeks ago
ZhangYong 56685b5011 合并 4 weeks ago
ZhangYong 5f2dfe5eef 历史数据对接完成 4 weeks ago
hongxilin a2b882746e Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into hongxilin/feature-20251023103318-行情数据及页面 4 weeks ago
宋杰 7cca04c21e Merge branch 'songjie/feature-20251023161635-首页' into milestone-20251031-简版功能开发 4 weeks ago
宋杰 4ab2074459 修改配置; 4 weeks ago
hongxilin 08cf2e3ba0 Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into hongxilin/feature-20251023103318-行情数据及页面 4 weeks ago
dongqian 769382fe25 联调选股策略接口 4 weeks ago
宋杰 4f4e2ef130 Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into milestone-20251031-简版功能开发 4 weeks ago
宋杰 842b2b766f 首页deepMate输入内容跳转到该页面并发起请求; 4 weeks ago
maziyang 47956e1c5b Merge branch 'maziyang/feature-20251025172218-智能客服中台' into milestone-20251031-简版功能开发 4 weeks ago
hongxilin dd57ceefb0 Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into hongxilin/feature-20251023103318-行情数据及页面 4 weeks ago
maziyang 3d0d54ef94 接口对接 4 weeks ago
hongxilin 5d5bc36596 marketOverview引入tcp 4 weeks ago
zhaowenkang 017f0b1a58 Merge branch 'milestone-20251031-简版功能开发' into zhaowenkang/feature-20251028181547-行情页面 4 weeks ago
lihuilin 1ae496045d 合并 4 weeks ago
lihuilin 3bfff8bd1e 对接完成 4 weeks ago
zhaowenkang 901a0fa365 增加hqchart插件 4 weeks ago
hongxilin a7ed01953d Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into milestone-20251031-简版功能开发 4 weeks ago
hongxilin 23a5b7e988 接入地区详情结构2 4 weeks ago
maziyang 69ca7dcdeb Merge branch 'maziyang/feature-20251025172218-智能客服中台' into milestone-20251031-简版功能开发 4 weeks ago
hongxilin 19913c6393 Merge branch 'hongxilin/feature-20251023103318-行情数据及页面' into milestone-20251031-简版功能开发 4 weeks ago
hongxilin f24fd1d1ee 引入各市场国旗,接入全球市场接口,地区分组列表接口,地区详情接口,一次性查询所有Tabs栏父子结构接口,修改IndexCards组件 4 weeks ago
ZhangYong e578879b50 对接 4 weeks ago
宋杰 bcb3e2d095 Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into milestone-20251031-简版功能开发 4 weeks ago
宋杰 1922787df7 登录弹出组件加入对首页游客登录的事件监听;首页游客登录成功后,重新加载页面; 4 weeks ago
maziyang 1a54ca33dd 加token 4 weeks ago
wangyi 224b92e05e 对deepmate的第一步意图识别添加trycatch 4 weeks ago
宋杰 96e176897c 重新适配首页轮播图大小; 4 weeks ago
maziyang 3ba6af2fd3 接口对接 4 weeks ago
wangyi c926bc38fb Merge branch 'wangyi/feature-20251026183100-deepmate王毅' into milestone-20251031-简版功能开发 4 weeks ago
wangyi 2dabe35ca4 当返回401的时候,弹出提示 4 weeks ago
lihui a4cc07130c add:完成微信跳转(使用的lihui的Appid),信息复制到粘贴板,自定义分享组件 4 weeks ago
宋杰 98a016d957 动态更新tcp命令; 4 weeks ago
lihui 654ec484c2 Merge branch 'refs/heads/milestone-20251031-简版功能开发' into lihuilin/feature-20251024095243-我的 4 weeks ago
lihui 10a8a0b96a Merge remote-tracking branch 'origin/lihuilin/feature-20251024095243-我的' into lihuilin/feature-20251024095243-我的 4 weeks ago
lihuilin 0e578db56d 缺设置修改 4 weeks ago
lihuilin e200fea054 没有设置的修改 4 weeks ago
dongqian 46cd063752 接上选股策略接口 4 weeks ago
wangyi 25c9896959 Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into wangyi/feature-20251026183100-deepmate王毅 4 weeks ago
ZhangYong d3237d22fe Merge branch 'zhangyong/feature-20251028115958-深度探索' into milestone-20251031-简版功能开发 4 weeks ago
ZhangYong 9c87f9ed98 工作流完成 4 weeks ago
宋杰 bd24b37066 新增游客查询默认的方法; 4 weeks ago
宋杰 ccf5b14ec7 新增游客查询默认的方法; 4 weeks ago
wangyi 76299f4d8a 完成忘记密码 4 weeks ago
hongxilin 26f9d15952 Merge branch 'hongxilin/feature-20251023103318-行情数据及页面' into milestone-20251031-简版功能开发 4 weeks ago
hongxilin e7629488c6 优化行情首页卡片样式 4 weeks ago
hongxilin d8331e4419 接入其他k线数据 4 weeks ago
宋杰 f1fbedfcfd 用户登录时才会调用接口;home353行用来写游客默认自选股数据; 4 weeks ago
wangyi ddb5992d42 Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into wangyi/feature-20251026183100-deepmate王毅 4 weeks ago
wangyi c57b7a27e3 Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into wangyi/feature-20251026183100-deepmate王毅 4 weeks ago
wangyi 97e1eae42b 忘记密码接口对接 4 weeks ago
ZhangYong 77ecd6819f 合并 4 weeks ago
ZhangYong c6c3f65a81 工作流 4 weeks ago
hongxilin 1b5735670d 删除图表示例页 4 weeks ago
lihui 8af11a2392 Merge branch 'refs/heads/milestone-20251031-简版功能开发' into lihuilin/feature-20251024095243-我的 4 weeks ago
hongxilin 3dfc567483 Merge branch 'hongxilin/feature-20251023103318-行情数据及页面' into milestone-20251031-简版功能开发 4 weeks ago
hongxilin 91b162dd01 分时数据对接 4 weeks ago
maziyang 67560daf56 问题详情页面 4 weeks ago
宋杰 65e9c59ba9 请求checkExist失败时不调用查看默认自选股的接口; 4 weeks ago
dongqian 62ab22e3d4 Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into dongqian/feature-20251022181325-deepmate简版 4 weeks ago
hongxilin 305683d746 Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into hongxilin/feature-20251023103318-行情数据及页面 4 weeks ago
dongqian 8c67bea900 调选股策略接口 4 weeks ago
ZhangYong 8697f5d4eb 拉代码 4 weeks ago
ZhangYong 54f190ef38 对接 4 weeks ago
wangyi dbd9b41940 Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into wangyi/feature-20251026183100-deepmate王毅 4 weeks ago
宋杰 356e457f13 Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into songjie/feature-20251023161635-首页 4 weeks ago
wangyi cbf88d106f 修改注册页面关闭 4 weeks ago
hongxilin f46711c992 接k线数据,加多语言 4 weeks ago
宋杰 a8eb4f3d52 修改新测试域名; 4 weeks ago
宋杰 be05468cea 修改我的自选方法文件;http中加入打印最终请求参数;首页弹登录提示;加入用户登录后才会调用接口的逻辑; 4 weeks ago
宋杰 76b233ab77 Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into songjie/feature-20251023161635-首页 4 weeks ago
maziyang 151bd9ec64 反馈api 4 weeks ago
wangyi 4aa1b1aca4 修改配置文件 4 weeks ago
wangyi 16d037ac2d Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into wangyi/feature-20251026183100-deepmate王毅 4 weeks ago
wangyi 3e7ac6fbf0 深度探索历史记录接口 4 weeks ago
maziyang c15e3bc4be Merge branch 'milestone-20251031-简版功能开发' into maziyang/feature-20251025172218-智能客服中台 4 weeks ago
maziyang 3f31c2e467 反馈弹窗 4 weeks ago
ZhangYong 6b995ce7a7 Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into milestone-20251031-简版功能开发 4 weeks ago
ZhangYong 3a7d249160 对接 4 weeks ago
宋杰 1c26940540 注释掉首页的tcp连接(需要时启用) 4 weeks ago
宋杰 3f05e3926e 封装我的自选接口文件; 4 weeks ago
wangyi 9cd30f4fa4 修改http 4 weeks ago
wangyi 52f7d4bd9d 优化登录接口 4 weeks ago
wangyi 9a0bad1bde 注释首页 4 weeks ago
宋杰 e8bd11a2b3 创建首页的我的自选的接口js文件; 4 weeks ago
Ethereal 16b4eb8bd9 Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into wangyi/feature-20251026183100-deepmate王毅 4 weeks ago
no99 188765185a 页面销毁时,移除监听器,断开连接 4 weeks ago
ZhangYong 60d2c9bd81 Merge branch 'zhangyong/feature-20251028115958-深度探索' into milestone-20251031-简版功能开发 4 weeks ago
ZhangYong 4a75acb248 对接 4 weeks ago
宋杰 1c3c3eea36 Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into milestone-20251031-简版功能开发 4 weeks ago
zhaowenkang 4561a5a3e9 更改 4 weeks ago
宋杰 bd49d785f6 我的自选tcp连接完成;tcp连接方法有修改;home页允许两个连接存在,并且是分别操作; 4 weeks ago
no99 b1e5cc586c Merge branch 'hongxilin/feature-20251023103318-行情数据及页面' into milestone-20251031-简版功能开发 4 weeks ago
no99 c5d106536b 合并冲突 4 weeks ago
ZhangYong 388ce4bd83 更新路径问题 4 weeks ago
no99 368a80fd68 Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into hongxilin/feature-20251023103318-行情数据及页面 4 weeks ago
ZhangYong 50c1bda05e 更新进度 4 weeks ago
ZhangYong 3cd29a9023 Merge branch 'zhangyong/feature-20251028115958-深度探索' into milestone-20251031-简版功能开发 4 weeks ago
ZhangYong e959085aff 深度探索接口接口 4 weeks ago
Ethereal 79c766af1a Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into wangyi/feature-20251026183100-deepmate王毅 4 weeks ago
Ethereal 6b01227736 修改导航栏的 4 weeks ago
ZhangYong 0ba0533396 对接 4 weeks ago
no99 2d61bf689c 接入TCP 4 weeks ago
wangyetao 10760a7da8 Merge branch 'dongqian/feature-20251022181325-deepmate简版' into milestone-20251031-简版功能开发 4 weeks ago
wangyetao 300d06ba4c 增加缺省页 4 weeks ago
zhaowenkang 931996ef6f Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into milestone-20251031-简版功能开发 4 weeks ago
zhaowenkang b19e866781 移动位置 4 weeks ago
宋杰 405a07f027 Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into milestone-20251031-简版功能开发 4 weeks ago
宋杰 738c34c3c2 提交launch.json 4 weeks ago
宋杰 4a52b310d7 合并苹果打包冲突; 4 weeks ago
zhaowenkang e7b05cd22d 移动文件 4 weeks ago
zhaowenkang aa8f3ddc16 新增行情 4 weeks ago
宋杰 2f286acece Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into milestone-20251031-简版功能开发 4 weeks ago
宋杰 376d8b559b 今日市场概览连接tcp成功;先断开再连接; 4 weeks ago
lihui c78175f93b add:对接用户个人信息接口 4 weeks ago
Ethereal 88a70a1c2d Merge branch 'wangyi/feature-20251026183100-deepmate王毅' into milestone-20251031-简版功能开发 4 weeks ago
Ethereal 55db8e0d80 修改token 4 weeks ago
maziyang 9d9d8a906b 智能客服中台页面搭建 4 weeks ago
Ethereal 8490e9bc79 Merge branch 'wangyi/feature-20251026183100-deepmate王毅' into milestone-20251031-简版功能开发 4 weeks ago
Ethereal 6772cfb676 增添device状态管理 4 weeks ago
wangyetao 4f021ab694 增加缺省页的返回键 4 weeks ago
Ethereal 4ac542ccf6 Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into wangyi/feature-20251026183100-deepmate王毅 4 weeks ago
Ethereal 5e9bad35cf 游客登录接口 4 weeks ago
wangyetao 618b66708b 完善接口路径 4 weeks ago
Ethereal 51d740aca9 Merge branch 'wangyi/feature-20251026183100-deepmate王毅' into milestone-20251031-简版功能开发 4 weeks ago
Ethereal 8151c1d9c0 全局注册登录提示组件 4 weeks ago
wangyetao eef3c35654 Merge branch 'dongqian/feature-20251022181325-deepmate简版' into milestone-20251031-简版功能开发 4 weeks ago
wangyetao 655141d95e markdown内容 4 weeks ago
wangyetao 77b49d8f6c 调通dmsecond接口 4 weeks ago
Ethereal 8f267cc82b deepmate对接接口 4 weeks ago
wangyetao 72b2395184 Merge branch 'dongqian/feature-20251022181325-deepmate简版' into milestone-20251031-简版功能开发 4 weeks ago
Ethereal 77afb0bb3c 登录注册页面优化登录按钮 4 weeks ago
wangyetao c1a563608b Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into dongqian/feature-20251022181325-deepmate简版 4 weeks ago
wangyetao 237128bb78 调poststock接口 4 weeks ago
Ethereal 5bfd381360 登录页面的叉号,返回首页 4 weeks ago
ZhangYong 1152a57609 深度探索 4 weeks ago
ZhangYong 05c8d04b39 深度探索 4 weeks ago
Ethereal 54f823f3e7 Merge branch 'wangyi/feature-20251026183100-deepmate王毅' into milestone-20251031-简版功能开发 4 weeks ago
Ethereal f52fafc18d Merge branch 'dongqian/feature-20251022181325-deepmate简版' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into wangyi/feature-20251026183100-deepmate王毅 4 weeks ago
Ethereal e9217e2dac 对接历史记录详情 4 weeks ago
wangyetao eae5a6ef33 修改请求头的token 4 weeks ago
宋杰 9a0f2f2adb 修改“我的”图片为英文名; 4 weeks ago
宋杰 0ec1138f32 Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into milestone-20251031-简版功能开发 4 weeks ago
宋杰 29b7c2afe5 封装了今日市场概览tcp连接的方法; 4 weeks ago
no99 026263009b 修改行情页面跳转方法 4 weeks ago
Ethereal ee3e1bab59 对接历史记录接口 4 weeks ago
wangyetao 74a3f7a25a 合并代码 4 weeks ago
no99 0a9fb991b6 修复page页少了个}的问题,添加左右翻页按钮 4 weeks ago
wangyetao 874b264a2b Merge branch 'wangyi/feature-20251026183100-deepmate王毅' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into dongqian/feature-20251022181325-deepmate简版 4 weeks ago
wangyetao f74e07e23f 对接意图识别接口 4 weeks ago
Ethereal 04065312d6 整合里程碑 4 weeks ago
no99 b9da90e5cb Merge branch 'hongxilin/feature-20251023103318-行情数据及页面' into milestone-20251031-简版功能开发 4 weeks ago
no99 c9b3d96fa3 行情页分时图 4 weeks ago
Ethereal 6e1b299fad 成功对接登录注册接口 4 weeks ago
wangyetao 7a036a7ee5 更换图片路径 4 weeks ago
Ethereal aa6eb261b2 对接登录接口 4 weeks ago
lihuilin e805070c78 合并 4 weeks ago
lihuilin 5af5edea1f 1027页面全部完成 4 weeks ago
wangyetao 8c9ab6af78 更换图片路径 4 weeks ago
wangyetao 34cbdf2f02 增加欢迎小机器人 4 weeks ago
wangyetao 8a77dedf96 修复文档 4 weeks ago
Ethereal 249f4c911a 优化登录接口 4 weeks ago
Ethereal fa5c939fd3 合并代码 4 weeks ago
Ethereal 309bb64c6c 优化找回密码页面验证逻辑 4 weeks ago
wangyetao 30de1a500e 优化历史记录滑动 4 weeks ago
wangyetao 63f8144af0 优化历史结构 4 weeks ago
Ethereal ae4799e702 优化deepmate滚动动画 4 weeks ago
Ethereal a3edcce138 优化登录注册的验证逻辑 4 weeks ago
Ethereal 09e7053323 注册页优化服务协议 4 weeks ago
Ethereal a218a53a15 优化服务协议弹窗 4 weeks ago
wangyetao c5ec095ebe 安装pinia 4 weeks ago
wangyetao 6af246bf65 Merge branch 'wangyi/feature-20251022162725-启动页登录注册' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into dongqian/feature-20251022181325-deepmate简版 4 weeks ago
Ethereal 3e640ea898 deepmate改成设计稿样式 4 weeks ago
Ethereal 88e76be174 合并分支0 4 weeks ago
wangyetao 253e01c5cb 合并代码 4 weeks ago
Ethereal 928f7d8711 合并代码 4 weeks ago
Ethereal e060eaca16 Merge branch 'dongqian/feature-20251022181325-deepmate简版' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into wangyi/feature-20251022162725-启动页登录注册 4 weeks ago
Ethereal 2ec6045bb2 添加login接口 4 weeks ago
Ethereal ef2ff18aa9 添加deepmate请求接口 4 weeks ago
Ethereal 4a9e5ab920 封装http请求 4 weeks ago
Ethereal eb18cf3c45 优化deepmate思考过程样式 4 weeks ago
wangyetao 47281ef48f Merge branch 'wangyi/feature-20251022162725-启动页登录注册' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into dongqian/feature-20251022181325-deepmate简版 4 weeks ago
Ethereal 8dded599c8 优化聊天区域滑动范围 4 weeks ago
wangyetao 6fc67da169 Merge branch 'wangyi/feature-20251022162725-启动页登录注册' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into dongqian/feature-20251022181325-deepmate简版 4 weeks ago
wangyetao 864045d7ae 合并代码 4 weeks ago
Ethereal 6885560fee 合并董倩代码 4 weeks ago
Ethereal 42603a03a9 优化deepmate 4 weeks ago
wangyetao 6eed33e9c2 增加历史记录 4 weeks ago
Ethereal 9418e86122 重新优化选择页样式 4 weeks ago
Ethereal 15e7bdce90 优化选择页滑动逻辑 4 weeks ago
wangyetao f4f5514df0 优化 4 weeks ago
Ethereal 1571a304ba 完成找回密码逻辑 4 weeks ago
Ethereal 60a21662d3 输入框增添隐藏图表 4 weeks ago
Ethereal 422de331d9 增添找回密码页面 4 weeks ago
宋杰 3255e57e05 插件打包;修复deepMate logo覆盖的问题; 4 weeks ago
Ethereal c3d7906515 增添邮箱验证规则 4 weeks ago
Ethereal 75fc4aaa60 增添手机号验证规则 4 weeks ago
wangyetao 36e554ab44 修复合并之后的代码 4 weeks ago
Ethereal 7ea9cd6ac4 Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into wangyi/feature-20251022162725-启动页登录注册 4 weeks ago
Ethereal dc7cc6f5c3 添加未勾选用户协议弹窗 4 weeks ago
Ethereal 7908314c32 优化思考过程,增添时间用时0 4 weeks ago
Ethereal f954ec4130 优化思考过程,增添时间用时 4 weeks ago
zhaowenkang b749ebe11b api文件夹 1 month ago
Ethereal 80380db0a8 开发请求接口0 1 month ago
wangyetao 437597a706 完善聊天页面的功能 1 month ago
Ethereal 170adc5bf1 优化思考过程代码 1 month ago
zhaowenkang a9662a0af8 自定义请求头 1 month ago
Ethereal 94682e1e24 注册markdown格式 1 month ago
zhaowenkang dee827ecb7 封装请求,行情页面完善 1 month ago
宋杰 32314d9a98 Merge branch 'songjie/feature-20251023161635-首页' into milestone-20251031-简版功能开发 1 month ago
宋杰 fb2d06a25a 首页deepMate输入框将用户输入内容发送到深度探索页面; 1 month ago
宋杰 f9c893bd90 Merge branch 'songjie/feature-20251023161635-首页' into milestone-20251031-简版功能开发 1 month ago
宋杰 d297bd32dd 首页deepmate部分默认展示3条数据; 1 month ago
宋杰 757ec0ec68 Merge branch 'songjie/feature-20251023161635-首页' into milestone-20251031-简版功能开发 1 month ago
宋杰 1d8de7df7c 首页的深度探索右侧查看更多点击后跳转深度探索页面; 1 month ago
宋杰 03c995fe41 Merge branch 'songjie/feature-20251023161635-首页' into milestone-20251031-简版功能开发 1 month ago
宋杰 305c8327bb deepMate的logo加入波纹动效。 1 month ago
Ethereal 3e23bcc130 优化滚动效果 1 month ago
Ethereal de93dcda75 添加文字滚动效果 1 month ago
Ethereal c9335705ed 优化打字机样式 1 month ago
wangyetao 78ac48ad64 聊天框上方保留机器人 1 month ago
宋杰 9c4744c957 Merge branch 'songjie/feature-20251023161635-首页' into milestone-20251031-简版功能开发 1 month ago
宋杰 cf375ab7ae 首页我的自选加入免责声明; 1 month ago
宋杰 09b6d61aad Merge branch 'songjie/feature-20251023161635-首页' into milestone-20251031-简版功能开发 1 month ago
宋杰 0b671bc3f6 为添加自选股添加点击事件,跳转到行情页。 1 month ago
wangyetao 87f4c41515 调整卡片样式 1 month ago
wangyetao 719ff43193 增加导入footerbar 1 month ago
wangyetao 52add52df0 Merge branch 'wangyi/feature-20251022162725-启动页登录注册' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into dongqian/feature-20251022181325-deepmate简版 1 month ago
wangyetao 37b3f53bf2 调整一下页面 1 month ago
宋杰 a73f499c4e Merge branch 'songjie/feature-20251023161635-首页' into milestone-20251031-简版功能开发 1 month ago
Ethereal 8c78b39294 优化DeepMate样式 1 month ago
宋杰 8acce471b5 新建早盘解析页面并跳转成功; 1 month ago
zhaowenkang fcb9eeac98 动态计算遮挡和地图尺寸 1 month ago
wangyetao 2a2a772462 增加词条滚动功能 1 month ago
Ethereal b678f207e8 注册tab组件到登录页 1 month ago
wangyetao 83c0f2eaaf 缺省页 1 month ago
zhaowenkang d6cd8fbc55 行情页面搭建 1 month ago
Ethereal 659968b678 Merge branch 'milestone-20251031-简版功能开发' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into wangyi/feature-20251022162725-启动页登录注册 1 month ago
Ethereal 82d3361d04 Merge branch 'dongqian/feature-20251022181325-deepmate简版' of http://39.101.133.168:8807/qimaohong/deepChartVueApp into wangyi/feature-20251022162725-启动页登录注册 1 month ago
wangyetao dbdd5d4229 修复 1 month ago
wangyetao 53d84192e0 修复完善deepmate页面 1 month ago
Ethereal 04c80b494d 完成登录注册页面布局 1 month ago
Ethereal 216e8a469e 完成登录界面 1 month ago
宋杰 97c39e1020 home页的耳机和铃铛图标换成图片; 1 month ago
宋杰 092b781ef4 首页样式完成。 1 month ago
宋杰 1aee77cf79 今日市场概览的logo垂直居中; 1 month ago
宋杰 62774fc1fd 首页框架 1 month ago
Ethereal 4bd1fd5198 修改未登录提示信息 1 month ago
Ethereal 1fde0e423c 增添手机验证码登录 1 month ago
Ethereal 0781fb1dd6 修改登录界面样式 1 month ago
wangyetao 0cd4dd619a 修改ui图标 1 month ago
Ethereal 14753b4af7 手机号增添不同国家的区号全 1 month ago
Ethereal 0232b2bac8 手机号增添不同国家的区号 1 month ago
Ethereal c9357ba44d 备份main 1 month ago
Ethereal 5cb7fa6101 优化配置 1 month ago
Ethereal 046e80e6d8 启动页初始化 1 month ago
  1. 14
      .hbuilderx/launch.json
  2. 41
      App.vue
  3. 46
      api/customerServicePlatform/customerServicePlatform.js
  4. 132
      api/deepExploration/deepExploration.js
  5. 68
      api/deepMate/deepMate.js
  6. 185
      api/home/mySelections.js
  7. 70
      api/marketSituation/marketSituation.js
  8. 37
      api/member.js
  9. 19
      api/setting/general.js
  10. 19
      api/setting/market.js
  11. 16
      api/setting/nextPwd.js
  12. 45
      api/setting/password.js
  13. 15
      api/setting/share.js
  14. 146
      api/start/login.js
  15. 258
      api/tcpConnection.js
  16. 392
      common/canvasMethod.js
  17. 59
      common/dailyData.js
  18. 354
      common/stockTimeInformation.js
  19. 143
      common/util.js
  20. 281
      components/DeepMate.vue
  21. 196
      components/FeedbackModal.vue
  22. 232
      components/IndexCard.vue
  23. 413
      components/MarketOverview.vue
  24. 190
      components/SharePopup.vue
  25. 516
      components/deepExploration_header.vue
  26. 49
      components/footerBar.vue
  27. 209
      components/login-prompt.vue
  28. 54
      main.js
  29. 45
      manifest.json
  30. 738
      package-lock.json
  31. 12
      package.json
  32. 330
      pages.json
  33. 55
      pages/analysisInstitutionalTrends/analysisInstitutionalTrends.vue
  34. 100
      pages/blank/institutionalTrendsBriefing.vue
  35. 100
      pages/blank/notice.vue
  36. 147
      pages/customStockList/customStockList.vue
  37. 682
      pages/customerServicePlatform/csPlatformIndex.vue
  38. 353
      pages/customerServicePlatform/historyRecord.vue
  39. 335
      pages/customerServicePlatform/questionDetail.vue
  40. 720
      pages/deepExploration/MainForceActions.vue
  41. 532
      pages/deepExploration/deepExploration.vue
  42. 388
      pages/deepExploration/stockSelectDetail.vue
  43. 1775
      pages/deepMate/deepMate.vue
  44. 28
      pages/home/deepExploration.vue
  45. 2139
      pages/home/home.vue
  46. 28
      pages/home/marketSituation.vue
  47. 300
      pages/home/member.vue
  48. 477
      pages/marketSituation/countryMarket.vue
  49. 301
      pages/marketSituation/forexMetals.vue
  50. 859
      pages/marketSituation/globalIndex.vue
  51. 2912
      pages/marketSituation/marketCondition.vue
  52. 780
      pages/marketSituation/marketDetail.vue
  53. 1041
      pages/marketSituation/marketOverview.vue
  54. 597
      pages/marketSituation/marketSituation.vue
  55. 55
      pages/morningMarketAnalysis/morningMarketAnalysis.vue
  56. 86
      pages/setting/about.vue
  57. 241
      pages/setting/account.vue
  58. 92
      pages/setting/bind.vue
  59. 257
      pages/setting/createPwd.vue
  60. 185
      pages/setting/email.vue
  61. 108
      pages/setting/font.vue
  62. 186
      pages/setting/general.vue
  63. 76
      pages/setting/introduce.vue
  64. 348
      pages/setting/market.vue
  65. 61
      pages/setting/message.vue
  66. 82
      pages/setting/newVersion.vue
  67. 175
      pages/setting/nextPwd.vue
  68. 255
      pages/setting/password.vue
  69. 185
      pages/setting/phone.vue
  70. 120
      pages/setting/push.vue
  71. 128
      pages/setting/server.vue
  72. 299
      pages/setting/share.vue
  73. 104
      pages/setting/theme.vue
  74. 1055
      pages/start/Registration/Registration.vue
  75. 1341
      pages/start/Registration/list.js
  76. 13
      pages/start/agreement/agreement.vue
  77. 1341
      pages/start/login/list.js
  78. 1190
      pages/start/login/login.vue
  79. 69
      pages/start/login/verification.js
  80. 13
      pages/start/privacy/privacy.vue
  81. 1085
      pages/start/recoverPassword/recoverPassword.vue
  82. 170
      pages/start/select/select.vue
  83. 73
      pages/start/startup/startup.vue
  84. 138
      server/login.json
  85. BIN
      static/customer-service-platform/camera.png
  86. BIN
      static/customer-service-platform/cs-platform-back.png
  87. BIN
      static/customer-service-platform/ellipse-dc-img.png
  88. BIN
      static/customer-service-platform/empty-content.png
  89. BIN
      static/customer-service-platform/fail-icon.png
  90. BIN
      static/customer-service-platform/message.png
  91. BIN
      static/customer-service-platform/refresh-icon.png
  92. BIN
      static/customer-service-platform/robot-head.png
  93. BIN
      static/customer-service-platform/smile-icon.png
  94. BIN
      static/customer-service-platform/success-icon.png
  95. BIN
      static/deepExploration-images/1.png
  96. BIN
      static/deepExploration-images/2.png
  97. BIN
      static/deepExploration-images/3.png
  98. BIN
      static/deepExploration-images/4.png
  99. BIN
      static/deepExploration-images/ASC.png
  100. BIN
      static/deepExploration-images/Americle.png

14
.hbuilderx/launch.json

@ -0,0 +1,14 @@
{
"version" : "1.0",
"configurations" : [
{
"customPlaygroundType" : "device",
"packageName" : "io.dcloud.HBuilder",
"playground" : "custom",
"type" : "uni-app:app-android"
}
]
}
// "playground" : "standard",
// "type" : "uni-app:app-ios"

41
App.vue

@ -1,16 +1,33 @@
<script> <script>
export default {
onLaunch: function() {
console.warn('当前组件仅支持 uni_modules 目录结构 ,请升级 HBuilderX 到 3.1.0 版本以上!')
console.log('App Launch')
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
}
}
import { useDeviceStore } from './stores/modules/deviceInfo'
export default {
onLaunch: function() {
console.warn('当前组件仅支持 uni_modules 目录结构 ,请升级 HBuilderX 到 3.1.0 版本以上!')
console.log('App Launch')
//
// const deviceStore = useDeviceStore()
// try {
// const sys = uni.getSystemInfoSync()
// let deviceId = ''
// // #ifdef APP-PLUS
// try { deviceId = plus?.device?.uuid || '' } catch(e) {}
// // #endif
// if (!deviceId) deviceId = uni.getStorageSync('device_id')
// if (!deviceId) {
// deviceId = `web_${Date.now()}_${Math.random().toString(36).slice(2,10)}`
// uni.setStorageSync('device_id', deviceId)
// }
// deviceStore.setDeviceInfo({ deviceId, platform: sys.platform, model: sys.model })
// console.log('Device init:', deviceStore.deviceInfo)
// } catch(e) { console.warn('Init device info failed:', e) }
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
}
}
</script> </script>
<style lang="scss"> <style lang="scss">

46
api/customerServicePlatform/customerServicePlatform.js

@ -0,0 +1,46 @@
import { http } from '@/utils/http.js'
const baseURL = "http://39.101.133.168:8828"
//图片上传
export const uploadImageApi = (data) => {
return http({
method: 'POST',
url: baseURL +'/hljw/api/aws/upload',
data
})
}
//问题回答
export const getAnswerApi = (data) => {
return http({
method: 'POST',
url: 'http://pbb6edde.natappfree.cc' +'/api/customer/askQuestion',
data
})
}
//获取随机5条猜你想问问题
export const getQuestionApi = (data) => {
return http({
method: 'GET',
url: 'http://pbb6edde.natappfree.cc' +'/api/customer/getQuestion',
})
}
//反馈添加
export const addFeedbackRecordApi = (data) => {
return http({
method: 'POST',
url: baseURL +'/link/third/dcFeedBack/feedback/add',
data
})
}
//反馈历史记录
export const getFeedbackRecordsApi = (data) => {
return http({
method: 'POST',
url: baseURL+'/link/third/dcFeedBack/feedback/select',
data
})
}

132
api/deepExploration/deepExploration.js

@ -0,0 +1,132 @@
import { http } from '@/utils/http.js'
//主力追踪意图
export const getModel1First = (data) => {
return http({
method: 'POST',
url: '/api/coze/trackingFirst',
data
})
}
//主力追踪意图
export const getModel1Second = (data) => {
return http({
method: 'POST',
url: '/api/coze/trackingSecond',
data
})
}
//主力追踪意图
export const getModel2First = (data) => {
return http({
method: 'POST',
url: '/api/coze/radarFirst',
data
})
}
//主力追踪意图
export const getModel2Second = (data) => {
return http({
method: 'POST',
url: '/api/coze/radarSecond',
data
})
}
//主力追踪意图
export const getModel3First = (data) => {
return http({
method: 'POST',
url: '/api/coze/decodingFirst',
data
})
}
//主力追踪意图
export const getModel3Second = (data) => {
return http({
method: 'POST',
url: '/api/coze/decodingSecond',
data
})
}
//主力追踪意图
export const getModel4First = (data) => {
return http({
method: 'POST',
url: '/api/coze/fundsFirst',
data
})
}
//主力追踪意图
export const getModel4Second = (data) => {
return http({
method: 'POST',
url: '/api/coze/fundsSecond',
data
})
}
//不搜索时走这个
export const getModeldefault = (data) => {
return http({
method: 'POST',
url: '/api/coze/default',
data
})
}
//k线数据
export const getData = (data) => {
return http({
method: 'POST',
url: '/api/coze/WorkFlowData',
data
})
}
//历史记录列表
export const RecordListApi = (data) => {
return http({
method: 'POST',
url: '/api/coze/mainForceList',
data:data
})
}
// 选股策略
export const stocSelectApi = (data) => {
return http({
method: 'POST',
url: '/api/deep/getStrategy',
data:data
})
}
//根据名字选股策略
export const stocSelectByNameApi = (data) => {
return http({
method: 'POST',
url: '/api/deep/getStrategyByName',
data:data
})
}
//历史记录详情
export const RecordInfoApi = (data) => {
return http({
method: 'POST',
url: '/api/coze/clickRecord',
data:data
})
}

68
api/deepMate/deepMate.js

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

185
api/home/mySelections.js

@ -0,0 +1,185 @@
/**
* 我的自选股相关API接口封装
* 使用utils/http.js中的拦截器封装请求方法
*/
import { http } from '../../utils/http.js'
/**
* 我的自选股API接口类
*/
class MySelectionsAPI {
/**
* 判断用户是否存在自选股分组
* @param {Function} successCallback - 成功回调函数
* @param {Function} failCallback - 失败回调函数
* @param {Object} data - 请求参数
* @returns {Promise}
*/
static async checkExist(successCallback, failCallback = null, data = {}) {
const url = '/api/homePage/userStock/checkExist'
try {
const response = await http({
url: url,
method: 'POST',
data: data
})
console.log('检查用户自选股分组存在性 - 响应:', response)
if (successCallback && typeof successCallback === 'function') {
successCallback(response)
}
return response
} catch (error) {
console.error('检查用户自选股分组存在性 - 失败:', error)
if (failCallback && typeof failCallback === 'function') {
failCallback(error)
}
throw error
}
}
/**
* 查询用户所有自选股分组
* @param {Function} successCallback - 成功回调函数
* @param {Function} failCallback - 失败回调函数
* @param {Object} data - 请求参数
* @returns {Promise}
*/
static async getUserStockGroupList(successCallback, failCallback = null, data = {}) {
const url = '/api/homePage/userStockGroup/list'
try {
const response = await http({
url: url,
method: 'POST',
data: data
})
console.log('查询用户自选股分组列表 - 响应:', response)
if (successCallback && typeof successCallback === 'function') {
successCallback(response)
}
return response
} catch (error) {
console.error('查询用户自选股分组列表 - 失败:', error)
if (failCallback && typeof failCallback === 'function') {
failCallback(error)
}
throw error
}
}
/**
* 分页查询某一个分组下的所有自选股
* @param {Function} successCallback - 成功回调函数
* @param {Function} failCallback - 失败回调函数
* @param {Object} data - 请求参数 {groupId, pageNum, pageSize, ...}
* @returns {Promise}
*/
static async getUserStockList(successCallback, failCallback = null, data = {}) {
const url = '/api/homePage/userStock/list'
// 设置默认分页参数
const requestData = {
pageNum: 1,
pageSize: 20,
...data
}
try {
const response = await http({
url: url,
method: 'POST',
data: requestData
})
console.log('分页查询分组自选股 - 响应:', response)
if (successCallback && typeof successCallback === 'function') {
successCallback(response)
}
return response
} catch (error) {
console.error('分页查询分组自选股 - 失败:', error)
if (failCallback && typeof failCallback === 'function') {
failCallback(error)
}
throw error
}
}
/**
* 查询默认自选股
* @param {Function} successCallback - 成功回调函数
* @param {Function} failCallback - 失败回调函数
* @param {Object} data - 请求参数
* @returns {Promise}
*/
static async getUserOrDefault(successCallback, failCallback = null, data = {}) {
const url = '/api/homePage/userStock/getUserOrDefault'
try {
const response = await http({
url: url,
method: 'POST',
data: data
})
console.log('查询默认自选股 - 响应:', response)
if (successCallback && typeof successCallback === 'function') {
successCallback(response)
}
return response
} catch (error) {
console.error('查询默认自选股 - 失败:', error)
if (failCallback && typeof failCallback === 'function') {
failCallback(error)
}
throw error
}
}
/**
* 游客查询默认自选股
* @param {Function} successCallback - 成功回调函数
* @param {Function} failCallback - 失败回调函数
* @returns {Promise}
*/
static async getDefaultStocks(successCallback, failCallback = null) {
const url = '/api/homePage/userStock/getDefaultStocks'
try {
const response = await http({
url: url,
method: 'POST',
data: {}
})
console.log('游客查询默认自选股 - 响应:', response)
if (successCallback && typeof successCallback === 'function') {
successCallback(response)
}
return response
} catch (error) {
console.error('游客查询默认自选股 - 失败:', error)
if (failCallback && typeof failCallback === 'function') {
failCallback(error)
}
throw error
}
}
}
// 导出API类
export default MySelectionsAPI
// 也可以导出单个方法供直接使用
export const {
checkExist,
getUserStockGroupList,
getUserStockList,
getUserOrDefault,
getDefaultStocks
} = MySelectionsAPI

70
api/marketSituation/marketSituation.js

@ -0,0 +1,70 @@
/** @format */
import { http } from "../../utils/http";
export const getData = () => {
return http({
method: "GET",
url: "/ka",
});
};
/**
* 获取全球指数
* POST /api/global/getGlobalIndex
*/
export const getGlobalIndexAPI = (data) => {
return http({
method: "POST",
url: "/api/global/getGlobalIndex",
data,
});
};
/**
* 获取地区分组列表
* POST /api/global/regionalGroup
*/
export const getRegionalGroupAPI = (data) => {
return http({
method: "POST",
url: "/api/global/regionalGroup",
data,
});
};
/**
* 获取地区详情
* POST /api/global/regionalGroupList
*/
export const getRegionalGroupListAPI = (data) => {
return http({
method: "POST",
url: "/api/global/regionalGroupList",
data,
});
};
/**
* 一次性查询所有Tab栏父子结构
* POST /api/homework/tab/getAll
*/
export const getAllTabsAPI = (data) => {
return http({
method: "POST",
url: "/api/homework/tab/getAll",
data,
});
};
/**
* 通用市场数据分页查询接口默认1页20条
* POST /api/market/data/page/query
*/
export const queryStockDataAPI = (data) => {
return http({
method: "POST",
url: "/api/market/data/page/query",
data,
});
};

37
api/member.js

@ -0,0 +1,37 @@
import util from '../common/util.js'
/*export const getUserInfo = (data = {}) => {
return util.request(
'/api/my/userInfo',
(res) => {
console.log('用户信息请求成功:', res);
},
{data},
(err) => {
console.log('用户信息请求失败:', err);
}
);
};
*/
import {
http
} from '../utils/http'
/**
* 用户信息获取接口
* @param data
* @returns {Promise<unknown>}
*/
export const getUserInfo = (data) => {
return http({
method: 'POST',
url: '/api/my/userInfo',
data: data,
header:{
token:'014de5283d2930af6481ede591afd087'
}
})
}

19
api/setting/general.js

@ -0,0 +1,19 @@
import { http } from '../../utils/http'
export const getSetting = (data) => {
return http({
method: 'POST',
url: '/api/my/getSetting',
data: data,
})
}
export const updateSetting = (data) => {
return http({
method: 'POST',
url: '/api/my/updateSetting',
data: data,
})
}

19
api/setting/market.js

@ -0,0 +1,19 @@
import { http } from '../../utils/http'
export const getMarketSetting = (data) => {
return http({
method: 'POST',
url: '/api/my/getQuotationSetting',
data: data,
})
}
export const updateMarketSetting = (data) => {
return http({
method: 'POST',
url: '/api/my/updateQuotationSetting',
data: data,
})
}

16
api/setting/nextPwd.js

@ -0,0 +1,16 @@
import {http} from '../../utils/http'
/**
* 修改密码
* @param data
* @returns {*}
*/
export const updatePassword = (data) => {
return http({
method: 'POST',
url: '/api/my/updatePassword',
data:
data
,
})
}

45
api/setting/password.js

@ -0,0 +1,45 @@
import {
http
} from '../../utils/http'
/**
* 验证码发送
* @param data
* @returns {*}
*/
export const sendEmail = (data) => {
return http({
method: 'POST',
url: '/UserLogin/sendEmail',
data: data,
})
}
/**
* 验证码验证
* @param data
* @returns {*}
*/
export const validateCode = (data) => {
return http({
method: 'POST',
url: '/api/my/validateCode',
data: data,
})
}
export const sendPhone = (data) => {
return http({
method: 'POST',
url: '/UserLogin/sendPhone',
data: data,
})
}
export const changeBind = (data) => {
return http({
method: 'POST',
url: '/api/my/bindEmailOrPhone',
data: data,
})
}

15
api/setting/share.js

@ -0,0 +1,15 @@
import { http } from '../../utils/http'
/**
* 分享接口获取dccode
* @param data
* @returns {*}
*/
export const Share = (data) => {
return http({
method: 'POST',
url: '/api/my/share',
data: data,
})
}

146
api/start/login.js

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

258
api/tcpConnection.js

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

392
common/canvasMethod.js

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

59
common/dailyData.js

@ -0,0 +1,59 @@
function generateDailyPacket(stock_code, startDate, days, basePrice) {
const data = [];
let prevClose = Number(basePrice);
const toStrDate = (dateVal) => (typeof dateVal === 'number' ? String(dateVal) : dateVal);
const addDays = (yyyymmdd, delta) => {
const y = parseInt(yyyymmdd.slice(0, 4), 10);
const m = parseInt(yyyymmdd.slice(4, 6), 10) - 1;
const d = parseInt(yyyymmdd.slice(6, 8), 10);
const dt = new Date(y, m, d);
dt.setDate(dt.getDate() + delta);
const yyyy = dt.getFullYear();
const mm = String(dt.getMonth() + 1).padStart(2, '0');
const dd = String(dt.getDate()).padStart(2, '0');
return `${yyyy}${mm}${dd}`;
};
const randDrift = () => (Math.random() - 0.5) * 0.02; // 每日 ±1% 漂移
const spread = 0.00050; // 买卖点差(可按需要调整)
let date = toStrDate(startDate);
for (let i = 0; i < days; i++) {
const open = +(prevClose * (1 + randDrift())).toFixed(5);
const close = +(open * (1 + randDrift())).toFixed(5);
const highRaw = Math.max(open, close) * (1 + Math.random() * 0.01);
const lowRaw = Math.min(open, close) * (1 - Math.random() * 0.01);
const high = +highRaw.toFixed(5);
const low = +lowRaw.toFixed(5);
const tick_qty = Math.floor(1000 + Math.random() * 5000); // 成交量
const amount = +(close * tick_qty).toFixed(2); // 成交额
const item = {
ts_code: stock_code,
trade_date: date,
bid_open: open,
bid_high: high,
bid_low: low,
bid_close: close,
ask_open: +(open + spread).toFixed(5),
ask_high: +(high + spread).toFixed(5),
ask_low: +(low + spread).toFixed(5),
ask_close: +(close + spread).toFixed(5),
tick_qty,
amount,
};
data.push(item);
prevClose = close;
date = addDays(date, 1);
}
return { stock_code, data };
}
// ... existing code ...
export const dailyDataPackets = {
// 如已存在同名键,可合并或覆盖
'GBPAUD.FXCM': generateDailyPacket('GBPAUD.FXCM', '20240101', 3000, 2.03893),
'EURUSD.FXCM': generateDailyPacket('EURUSD.FXCM', '20240101', 3000, 1.08350),
// 可按需继续添加更多代码
};

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 },
];

143
common/util.js

@ -0,0 +1,143 @@
var util = {}
util.data = {}
util.data.base_url = 'https://dbqb.nfdxy.net/testApi'
// 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'),
'deviceId': uni.getSystemInfoSync().deviceId
},
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;
}
};
};

281
components/DeepMate.vue

@ -0,0 +1,281 @@
<template>
<view class="deepmate">
<view class="deepmate-container">
<view class="deepmate-header">
<view class="title-container">
<view class="title-left">
<text class="deepmate-title">DeepMate</text>
<text class="deepmate-subtitle">您的市场最佳顾问~</text>
</view>
<view class="title-right">
<image class="deepmate-icon" src="https://d31zlh4on95l9h.cloudfront.net/images/7faa683450cc071bcc746fea8191ff6b.png" mode="aspectFit"></image>
</view>
</view>
</view>
<view class="deepmate-content">
<view class="deepmate-hotspots">
<view class="deepmate-question">
<text class="question-text">今日股票策略晨报</text>
</view>
<view class="hotspot-item">
<text>热门股票分析</text>
</view>
<view class="hotspot-item">
<text>行业趋势预测</text>
</view>
<view class="hotspot-item">
<text>市场风险提示</text>
</view>
<view class="hotspot-item">
<text>投资策略建议</text>
</view>
<view class="hotspot-item">
<text>财经新闻解读</text>
</view>
</view>
<view class="deepmate-action">
<input
class="stock-input"
type="text"
placeholder="请输入股票代码/名称,获取AI洞察"
v-model="inputValue"
@confirm="handleSend"
/>
<view class="send-button-container" @click="handleSend">
<image class="send-button" src="https://d31zlh4on95l9h.cloudfront.net/images/3da018821a5c82b06a1d6ddc81b960ac.png" mode="aspectFit"></image>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'DeepMate',
data() {
return {
inputValue: ''
}
},
methods: {
handleSend() {
//
if (!this.inputValue.trim()) {
uni.showToast({
title: '请输入股票代码或名称',
icon: 'none',
duration: 2000
})
return
}
// deepMate
uni.navigateTo({
url: `/pages/deepMate/deepMate?query=${encodeURIComponent(this.inputValue.trim())}`
})
//
this.inputValue = ''
}
}
}
</script>
<style>
/* DeepMate样式 */
.deepmate-container {
background-color: #ffe6e6;
border-radius: 10px;
padding: 15px 15px 15px 15px;
margin: 0;
width: 100%;
box-sizing: border-box;
overflow: hidden;
}
.deepmate-header {
margin-bottom: 10px;
}
.title-container {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.title-left {
width: 50%;
display: flex;
flex-direction: column;
}
.title-right {
width: 50%;
display: flex;
justify-content: center;
align-items: center;
}
.deepmate-title {
font-size: 18px;
font-weight: bold;
color: #ff4d4f;
display: block;
}
.deepmate-icon {
width: 50px;
height: 50px;
margin-left: 0;
}
.deepmate-subtitle {
font-size: 12px;
color: #666;
display: block;
margin-top: 5px;
}
.deepmate-hotspots {
margin: 10px 0;
background-color: #ffffff;
border-radius: 10px;
padding: 10px;
}
.deepmate-question {
display: flex;
align-items: center;
margin-bottom: 10px;
padding-left: 10px;
position: relative;
}
.deepmate-question:before {
content: "";
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 4px;
background-color: #ff4d4f;
border-radius: 2px;
}
.question-text {
font-size: 16px;
font-weight: bold;
color: #333;
}
.hotspot-item {
background-color: #f5f5f5;
padding: 8px 12px;
border-radius: 6px;
margin-bottom: 8px;
}
.hotspot-item text {
font-size: 14px;
color: #333;
}
.deepmate-action {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #ff4d4f;
padding: 8px 15px;
border-radius: 25px;
margin-top: 10px;
border: none;
}
.stock-input {
flex: 1;
height: 36px;
font-size: 14px;
color: #ffffff;
padding: 0 10px;
border: none;
background-color: transparent;
}
.stock-input::placeholder {
color: rgba(255, 255, 255, 0.8);
}
.send-button-container {
display: flex;
justify-content: center;
align-items: center;
width: 36px;
height: 36px;
background-color: #ffffff;
border-radius: 50%;
margin-left: 10px;
}
.send-button {
width: 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>

196
components/FeedbackModal.vue

@ -0,0 +1,196 @@
<template>
<view>
<view v-if="internalVisible" class="fm-overlay"></view>
<view v-if="internalVisible" class="fm-wrap">
<view class="fm-card" :style="{ width: _width }">
<text class="fm-title">{{ _title }}</text>
<view class="fm-icon-wrap">
<image :src="imgSrcComputed" mode="aspectFit" class="fm-icon-img" />
</view>
<text class="fm-sub">{{ _subtitle }}</text>
<view class="fm-btn-wrap">
<button class="fm-btn" @click="onConfirm">{{ _buttonText }}</button>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'FeedbackModal',
props: {
title: {
type: String,
default: '提交成功'
},
subtitle: {
type: String,
default: '— 感谢您的反馈 —'
},
buttonText: {
type: String,
default: '确定'
},
status: {
type: String,
default: 'success'
},
lockScroll: {
type: Boolean,
default: true
},
width: {
type: String,
default: '600rpx'
}
},
data() {
return {
internalVisible: false,
_title: this.title,
_subtitle: this.subtitle,
_buttonText: this.buttonText,
_status: this.status,
_width: this.width,
};
},
computed: {
imgSrcComputed() {
return this._status === 'fail' ?
'/static/customer-service-platform/fail-icon.png' :
'/static/customer-service-platform/success-icon.png';
}
},
methods: {
show(options = {}) {
if (options.title) this._title = options.title;
if (options.subtitle) this._subtitle = options.subtitle;
if (options.buttonText) this._buttonText = options.buttonText;
if (options.status) this._status = options.status;
if (options.width) this._width = options.width;
if (this.lockScroll) this._lockScroll();
this.internalVisible = true;
},
hide() {
this.internalVisible = false;
if (this.lockScroll) this._unlockScroll();
},
onConfirm() {
this.hide();
this.$nextTick(() => {
this.$emit('confirm', {
status: this._status
});
});
},
//
_lockScroll() {
try {
document.body.style.overflow = 'hidden';
} catch (e) {}
},
_unlockScroll() {
try {
document.body.style.overflow = '';
} catch (e) {}
}
},
beforeDestroy() {
if (this.lockScroll) this._unlockScroll();
}
};
</script>
<style scoped>
.fm-overlay {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.35);
z-index: 999;
}
.fm-wrap {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
padding: 40rpx;
box-sizing: border-box;
}
.fm-card {
background: #fff;
border-radius: 24rpx;
padding: 40rpx;
box-sizing: border-box;
text-align: center;
box-shadow: 0 6rpx 30rpx rgba(0, 0, 0, 0.12);
}
.fm-title {
display: block;
font-size: 48rpx;
font-weight: 700;
color: #333333;
margin-bottom: 40rpx;
}
.fm-icon-wrap {
width: 200rpx;
height: 200rpx;
margin: 0 auto 40rpx;
display: flex;
align-items: center;
justify-content: center;
}
.fm-icon-img {
width: 100%;
height: 100%;
}
.fm-sub {
display: block;
color: #666666;
font-size: 32rpx;
margin-bottom: 40rpx;
font-weight: 500;
}
.fm-btn-wrap {
display: flex;
justify-content: center;
}
.fm-btn {
width: 220rpx;
height: 60rpx;
border-radius: 32rpx;
background: #000;
color: #ffffff;
font-weight: 700;
font-size: 32rpx;
font-style: normal;
border: none;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20rpx;
}
</style>

232
components/IndexCard.vue

@ -0,0 +1,232 @@
<!-- @format -->
<template>
<view class="index-card">
<view class="card-header">
<view class="flag-container">
<image :src="getMarketFlag(market)" 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({
//
market: {
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 judgeSymbol = (num) => {
// undefined/null//
if (num === null || num === undefined) return '';
const n = Number(num);
if (!isNaN(n)) {
// '+'
return (n < 0 ? '' : '+') + n;
}
//
const s = String(num).trim();
if (s.startsWith('-')) return s;
if (s.startsWith('+')) return s;
return '+' + s;
};
const getMarketFlag = (market) => {
let imagePath;
if (market === "cn") {
imagePath = "/static/marketSituation-image/country-flag/cn.png";
} else if (market === "hk") {
imagePath = "/static/marketSituation-image/country-flag/hk.png";
} else if (market === "can") {
imagePath = "/static/marketSituation-image/country-flag/can.png";
} else if (market === "my") {
imagePath = "/static/marketSituation-image/country-flag/my.png";
} else if (market === "sg") {
imagePath = "/static/marketSituation-image/country-flag/sg.png";
} else if (market === "th") {
imagePath = "/static/marketSituation-image/country-flag/th.png";
} else if (market === "vi") {
imagePath = "/static/marketSituation-image/country-flag/vi.png";
} else if (market === "us" || market === "usa") {
imagePath = "/static/marketSituation-image/country-flag/us.png";
} else {
imagePath = "/static/marketSituation-image/country-flag/global.png";
}
return imagePath;
};
//
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>

413
components/MarketOverview.vue

@ -0,0 +1,413 @@
<template>
<view>
<!-- 第一行白色背景标题和按钮 -->
<view class="market-header">
<text class="section-title">今日市场概览</text>
<text class="more-btn" @click="showMarketSelector">{{ buttonText }}</text>
</view>
<!-- 第二行灰色圆角背景市场数据 -->
<view class="market-content">
<!-- 默认显示图片 -->
<view class="selected-market" v-if="!showForexMarket">
<image class="market-image" src="https://d31zlh4on95l9h.cloudfront.net/images/dee46373b5593d5f315d91675a38fb61.png" mode="widthFix"></image>
</view>
<!-- 外汇市场样式 -->
<view class="selected-market" v-if="showForexMarket">
<view class="forex-market">
<!-- 上部分三个汇率容器 -->
<view class="forex-rates">
<view class="forex-rate-item"
v-for="(stock, index) in stockInfoList"
:key="index"
:class="stock.change_percent >= 0 ? 'up' : 'down'">
<text class="forex-pair">{{ stock.stock_name }}</text>
<text class="forex-value">{{ stock.current_price }}</text>
<text class="forex-change">{{ stock.change }}</text>
</view>
</view>
<!-- 中部分智能解读标题 -->
<view class="forex-analysis-title">
<text class="analysis-title">智能解读</text>
</view>
<!-- 下部分智能解读内容 -->
<view class="forex-analysis-content">
<view class="analysis-icon-container">
<image class="analysis-brain-icon" src="https://d31zlh4on95l9h.cloudfront.net/images/8f35e54c52412467101370aa70d8fdb2.png" mode="aspectFit"></image>
</view>
<view class="analysis-items">
<view class="analysis-item">
<text class="analysis-dot orange"></text>
<text class="analysis-text">今日市场情报: 偏乐观</text>
</view>
<view class="analysis-item">
<text class="analysis-dot blue"></text>
<text class="analysis-text">市场风险评级: 需警惕潜在风险</text>
</view>
<view class="analysis-item" @click="goToMorningAnalysis">
<text class="analysis-dot green"></text>
<text class="analysis-text">早盘解析: 今日高开, 芯片稀土公共</text>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 市场选择对话框 -->
<uni-popup ref="marketPopup" type="center" :mask-click="true" @change="popupChange">
<view class="market-dialog">
<view class="dialog-title">
<text>选择市场</text>
</view>
<view class="market-list">
<view class="market-option" v-for="(market, index) in marketOptions" :key="index" @click="selectMarket(market.id)">
<view class="market-flag">
<image :src="market.flag" mode="aspectFit" class="flag-image"></image>
</view>
<view class="market-name-container">
<text class="market-option-name">{{ market.name }}</text>
</view>
<view class="market-checkbox">
<radio :checked="selectedMarket === market.id" color="#4080ff" />
</view>
</view>
</view>
<view class="dialog-buttons">
<button class="confirm-btn" @click="confirmMarketSelection">确认</button>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
export default {
name: 'MarketOverview',
props: {
// 3
stockInfoList: {
type: Array,
default: () => ([
{
stock_name: '美元/日元',
current_price: '151.13',
change: '+1.62%',
change_value: 0,
change_percent: 0
},
{
stock_name: '美元/韩元',
current_price: '1424.900',
change: '-2.92%',
change_value: 0,
change_percent: 0
},
{
stock_name: '美元/英镑',
current_price: '0.730',
change: '+2.92%',
change_value: 0,
change_percent: 0
}
])
}
},
data() {
return {
selectedMarket: 'forex',
tempSelectedMarket: 'forex', //
previousMarket: null,
buttonText: '切换市场',
showSelector: false,
showForexMarket: false, //
marketOptions: [
{ id: 'forex', name: '外汇市场', flag: '/static/c2.png', value: '+1.62%', trend: 'up' }
],
marketData: {
'forex': {
name: '外汇市场',
value: '+1.62%',
trend: 'up',
indicators: [
{ name: '美元/日元', value: '151.13 +1.62%', trend: 'up' },
{ name: '美元/韩元', value: '1424.900 -2.92%', trend: 'down' },
{ name: '美元/英镑', value: '0.79 +2.92%', trend: 'up' }
]
}
}
}
},
methods: {
//
goToMorningAnalysis() {
uni.navigateTo({
url: '/pages/morningMarketAnalysis/morningMarketAnalysis'
})
},
showMarketSelector() {
//
this.showForexMarket = !this.showForexMarket;
//
this.buttonText = this.showForexMarket ? '外汇市场' : '切换市场';
},
popupChange(e) {
//
if (!e.show && this.tempSelectedMarket !== this.selectedMarket) {
this.tempSelectedMarket = this.selectedMarket;
}
},
selectMarket(marketId) {
//
this.tempSelectedMarket = marketId;
},
confirmMarketSelection() {
//
this.selectedMarket = this.tempSelectedMarket;
this.showSelector = false;
this.$refs.marketPopup.close();
},
getSelectedMarketName() {
if (!this.selectedMarket) return '';
return this.marketData[this.selectedMarket].name;
},
getSelectedMarketValue() {
if (!this.selectedMarket) return '';
return this.marketData[this.selectedMarket].value;
},
getSelectedMarketTrend() {
if (!this.selectedMarket) return '';
return this.marketData[this.selectedMarket].trend;
},
getSelectedMarketIndicators() {
if (!this.selectedMarket) return [];
return this.marketData[this.selectedMarket].indicators;
}
}
}
</script>
<style>
/* 市场概览样式 */
.market-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 15px;
background-color: #ffffff;
}
.section-title {
font-size: 16px;
font-weight: bold;
color: #333;
}
.more-btn {
font-size: 14px;
color: #4080ff;
}
.market-content {
margin: 0 0 15px;
background-color: #f5f7fa;
border-radius: 8px;
overflow: hidden;
}
/* 外汇市场样式 */
.forex-market {
padding: 15px;
}
.forex-rates {
display: flex;
justify-content: space-between;
gap: 10px;
margin-bottom: 15px;
}
.forex-rate-item {
width: 30%;
padding: 10px;
border-radius: 6px;
display: flex;
flex-direction: column;
align-items: center;
}
.forex-rate-item.up {
background-color: rgba(82, 196, 26, 0.1);
border: 1px solid #52c41a;
}
.forex-rate-item.down {
background-color: rgba(245, 34, 45, 0.1);
border: 1px solid #f5222d;
}
.forex-pair {
font-size: 12px;
color: #666;
margin-bottom: 5px;
}
.forex-value {
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 5px;
}
.forex-change {
font-size: 12px;
color: #52c41a;
}
.forex-rate-item.down .forex-change {
color: #f5222d;
}
.forex-analysis-title {
display: flex;
align-items: center;
margin-bottom: 10px;
border-left: 3px solid #f5222d;
padding-left: 8px;
}
.analysis-title {
font-size: 14px;
font-weight: bold;
color: #333;
}
.forex-analysis-content {
display: flex;
background-color: #fff;
border-radius: 6px;
padding: 12px;
}
.analysis-icon-container {
width: 40px;
height: 40px;
margin-right: 10px;
display: flex;
align-items: center;
justify-content: center;
align-self: center;
}
.analysis-brain-icon {
width: 100%;
height: 100%;
}
.analysis-items {
flex: 1;
}
.analysis-item {
display: flex;
align-items: center;
margin-bottom: 8px;
}
.analysis-item:last-child {
margin-bottom: 0;
}
.analysis-dot {
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 8px;
}
.analysis-dot.orange {
background-color: #fa8c16;
}
.analysis-dot.blue {
background-color: #1890ff;
}
.analysis-dot.green {
background-color: #52c41a;
}
.analysis-text {
font-size: 12px;
color: #333;
line-height: 1.4;
}
/* 市场选择对话框样式 */
.market-dialog {
width: 300px;
background-color: #fff;
border-radius: 10px;
overflow: hidden;
}
.dialog-title {
padding: 15px;
text-align: center;
border-bottom: 1px solid #eee;
}
.market-list {
max-height: 300px;
overflow-y: auto;
}
.market-option {
display: flex;
align-items: center;
padding: 12px 15px;
border-bottom: 1px solid #f5f5f5;
}
.market-flag {
width: 24px;
height: 24px;
margin-right: 10px;
}
.flag-image {
width: 100%;
height: 100%;
border-radius: 50%;
}
.market-name-container {
flex: 1;
}
.market-option-name {
font-size: 14px;
color: #333;
}
.dialog-buttons {
padding: 10px 15px 15px;
display: flex;
justify-content: center;
}
.confirm-btn {
width: 80%;
height: 40px;
line-height: 40px;
text-align: center;
background-color: #4080ff;
color: #fff;
border-radius: 20px;
font-size: 14px;
}
</style>

190
components/SharePopup.vue

@ -0,0 +1,190 @@
<!--自定义分享弹窗 使用uni的更改-->
<template>
<view class="uni-popup-share">
<!-- <view class="uni-share-title">-->
<!-- <text class="uni-share-title-text">{{ shareTitleText }}</text>-->
<!-- </view>-->
<view class="uni-share-content">
<view class="uni-share-content-box">
<view class="uni-share-content-item" v-for="(item,index) in bottomData" :key="index"
@click.stop="select(item,index)">
<image class="uni-share-image" :src="item.icon" mode="aspectFill"></image>
<text class="uni-share-text">{{ item.text }}</text>
</view>
</view>
</view>
<view class="uni-share-button-box">
<button class="uni-share-button" @click="close">{{ cancelText }}</button>
</view>
</view>
</template>
<script>
import popup from '../uni_modules/uni-popup/components/uni-popup/popup.js'
// import popup from '../uni-popup/popup.js'
import {initVueI18n} from '@dcloudio/uni-i18n'
import messages from '../uni_modules/uni-popup/components/uni-popup/i18n/index.js'
const {t} = initVueI18n(messages)
export default {
name: 'SharePopup',
mixins: [popup],
emits: ['select'],
props: {
title: {
type: String,
default: ''
},
beforeClose: {
type: Boolean,
default: false
}
},
data() {
return {
bottomData: [{
text: 'WhatsApp',
icon: '/static/my/share/WhatsApp.png',
name: 'WhatsApp'
},
{
text: 'Line',
icon: '/static/my/share/Line.png',
name: 'Line'
},
{
text: 'KakaoTalk',
icon: '/static/my/share/KakaoTalk.png',
name: 'KakaoTalk'
},
{
text: 'WeChat',
icon: '/static/my/share/WeChat.png',
name: 'WeChat'
},
{
text: '复制链接',
icon: '/static/my/share/share.png',
name: '复制链接'
},
]
}
},
created() {
},
computed: {
cancelText() {
return t("uni-popup.cancel")
},
shareTitleText() {
return this.title || t("uni-popup.shareTitle")
}
},
methods: {
/**
* 选择内容
*/
select(item, index) {
this.$emit('select', {
item,
index
})
// this.close()
},
/**
* 关闭窗口
*/
close() {
if (this.beforeClose) return
this.popup.close()
}
}
}
</script>
<style lang="scss">
.uni-popup-share {
background-color: #fff;
border-top-left-radius: 11px;
border-top-right-radius: 11px;
}
.uni-share-title {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: center;
height: 40px;
}
.uni-share-title-text {
font-size: 14px;
color: #666;
}
.uni-share-content {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
padding-top: 10px;
}
.uni-share-content-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
flex-wrap: wrap;
width: 360px;
}
.uni-share-content-item {
width: 72px;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: center;
padding: 10px 0;
align-items: center;
}
.uni-share-content-item:active {
background-color: #f5f5f5;
}
.uni-share-image {
width: 42px;
height: 42px;
}
.uni-share-text {
margin-top: 10px;
font-size: 14px;
color: #3B4144;
}
.uni-share-button-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
padding: 10px 15px;
}
.uni-share-button {
flex: 1;
border-radius: 50px;
color: #666;
font-size: 16px;
}
.uni-share-button::after {
border-radius: 50px;
}
</style>

516
components/deepExploration_header.vue

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

49
components/footerBar.vue

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

209
components/login-prompt.vue

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

54
main.js

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

45
manifest.json

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

738
package-lock.json

@ -0,0 +1,738 @@
{
"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",
"highlight.js": "^11.11.1",
"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/highlight.js": {
"version": "11.11.1",
"resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.11.1.tgz",
"integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=12.0.0"
}
},
"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",
"resolved": "https://registry.npmjs.org/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-4.5.0.tgz",
"integrity": "sha512-QTkP1xJVyCdr2I2p3AKUZM84/e+IS+HktRxKGAIuDzkyaKKV48mQcYkJFVVDuvTxlI5j6X3oZObpqoVB8JnWpw==",
"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"
}
}
}

12
package.json

@ -0,0 +1,12 @@
{
"dependencies": {
"@dcloudio/uni-ui": "^1.5.11",
"@element-plus/icons-vue": "^2.3.2",
"element-plus": "^2.11.5",
"highlight.js": "^11.11.1",
"marked": "^2.0.1",
"pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.5.0",
"vue-i18n": "^9.14.5"
}
}

330
pages.json

@ -1,5 +1,83 @@
{ {
"pages": [
"easycom": {
"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue"
},
"pages": [{
"path": "pages/start/startup/startup",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#000000",
"navigationBarTextStyle": "white",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{
"path": "pages/start/select/select",
"style": {
"navigationBarBackgroundColor": "#000000",
"navigationBarTitleText": "",
"animationType": "pop-in",
"navigationStyle": "custom",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{
"path": "pages/start/Registration/Registration",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{
"path": "pages/start/login/login",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{
"path": "pages/start/agreement/agreement",
"style": {
"navigationBarTitleText": "",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{
"path": "pages/start/privacy/privacy",
"style": {
"navigationBarTitleText": "",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{
"path": "pages/deepMate/deepMate",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{ {
"path": "pages/home/home", "path": "pages/home/home",
"style": { "style": {
@ -10,7 +88,32 @@
} }
}, },
{ {
"path": "pages/home/marketSituation",
"path" : "pages/morningMarketAnalysis/morningMarketAnalysis",
"style" :
{
"navigationBarTitleText" : "早盘解析"
}
},
{
"path": "pages/marketSituation/marketSituation",
"style": {
"navigationStyle": "custom",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{
"path": "pages/marketSituation/globalIndex",
"style": {
"navigationStyle": "custom",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{
"path": "pages/marketSituation/marketDetail",
"style": { "style": {
"navigationStyle": "custom", "navigationStyle": "custom",
"disableSwipeBack": true, "disableSwipeBack": true,
@ -28,7 +131,7 @@
} }
}, },
{ {
"path": "pages/home/deepExploration",
"path": "pages/deepExploration/deepExploration",
"style": { "style": {
"navigationStyle": "custom", "navigationStyle": "custom",
"disableSwipeBack": true, "disableSwipeBack": true,
@ -44,7 +147,222 @@
"titleNView": false, "titleNView": false,
"bounce": false "bounce": false
} }
},
{
"path": "pages/blank/institutionalTrendsBriefing",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{
"path": "pages/blank/notice",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{
"path" : "pages/start/recoverPassword/recoverPassword",
"style" :
{
"navigationBarTitleText": "",
"navigationStyle": "custom",
"titleNView": false
}
},
{
"path": "pages/setting/general",
"style": {
"navigationBarTitleText": "通用设置"
}
},
{
"path": "pages/setting/font",
"style": {
"navigationBarTitleText": "字体大小"
}
},
{
"path": "pages/setting/theme",
"style": {
"navigationBarTitleText": "主题切换"
}
},
{
"path": "pages/setting/message",
"style": {
"navigationBarTitleText": "消息推送"
}
},
{
"path": "pages/setting/push",
"style": {
"navigationBarTitleText": "推送设置"
}
},
{
"path": "pages/setting/server",
"style": {
"navigationBarTitleText": "选择服务器"
}
},
{
"path": "pages/setting/market",
"style": {
"navigationBarTitleText": "行情设置"
}
},
{
"path": "pages/setting/account",
"style": {
"navigationBarTitleText": "账号与安全"
}
},
{
"path": "pages/setting/newVersion",
"style": {
"navigationBarTitleText": "新版本更新"
}
},
{
"path": "pages/setting/about",
"style": {
"navigationBarTitleText": "关于DeepChart"
}
},
{
"path": "pages/setting/introduce",
"style": {
"navigationBarTitleText": "产品介绍"
}
},
{
"path": "pages/setting/bind",
"style": {
"navigationBarTitleText": "绑定账号"
}
},
{
"path": "pages/setting/phone",
"style": {
"navigationBarTitleText": "绑定账号"
}
},
{
"path": "pages/setting/email",
"style": {
"navigationBarTitleText": "绑定账号"
}
},
{
"path": "pages/setting/password",
"style":
{
"navigationBarTitleText": "修改密码"
}
},
{
"path" : "pages/setting/nextPwd",
"style" :
{
"navigationBarTitleText" : "修改密码"
}
},
{
"path" : "pages/setting/share",
"style" :
{
"navigationBarTitleText" : "分享领取奖励"
}
},
{
"path" : "pages/marketSituation/marketCondition",
"style" :
{
"navigationBarTitleText" : "行情",
"navigationStyle": "custom"
}
},{
"path": "pages/deepExploration/MainForceActions",
"style": {
"navigationStyle": "custom",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{
"path" : "pages/deepExploration/stockSelectDetail",
"style" :
{
"navigationBarTitleText": "选股策略"
}
},
{
"path" : "pages/setting/createPwd",
"style" :
{
"navigationBarTitleText" : "创建密码"
}
},
{
"path": "pages/customerServicePlatform/csPlatformIndex",
"style": {
"navigationStyle": "custom",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{
"path": "pages/customerServicePlatform/historyRecord",
"style": {
"navigationStyle": "custom",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{
"path": "pages/customerServicePlatform/questionDetail",
"style": {
"navigationStyle": "custom",
"disableSwipeBack": true,
"titleNView": false,
"bounce": false
}
},
{
"path" : "pages/analysisInstitutionalTrends/analysisInstitutionalTrends",
"style" :
{
"navigationBarTitleText" : "机构动向解析 "
}
},
{
"path" : "pages/customStockList/customStockList",
"style" :
{
"navigationBarTitleText" : "我的自选",
"app-plus": {
"titleNView": false
},
"h5": {
"titleNView": false
},
"mp-weixin": {
"navigationStyle": "custom"
}
}
} }
], ],
"globalStyle": { "globalStyle": {
"navigationBarTextStyle": "black", "navigationBarTextStyle": "black",
@ -54,5 +372,11 @@
"app-plus": { "app-plus": {
"background": "#efeff4" "background": "#efeff4"
} }
},
"style": {
"app-plus": {
"animationType": "fade-in",
"animationDuration": 500
} }
}
} }

55
pages/analysisInstitutionalTrends/analysisInstitutionalTrends.vue

@ -0,0 +1,55 @@
<template>
<view class="container">
<view class="content">
<image
class="no-data-image"
src="https://d31zlh4on95l9h.cloudfront.net/images/f5a9bd32c81bc7cca47252b51357c12f.png"
mode="aspectFit"
></image>
<text class="no-data-text">暂无数据~</text>
</view>
</view>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
}
}
</script>
<style>
.container {
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background-color: #f5f5f5;
}
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.no-data-image {
width: 200px;
height: 200px;
margin-bottom: 20px;
}
.no-data-text {
font-size: 16px;
color: #999999;
text-align: center;
}
</style>

100
pages/blank/institutionalTrendsBriefing.vue

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

100
pages/blank/notice.vue

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

147
pages/customStockList/customStockList.vue

@ -0,0 +1,147 @@
<!-- 自选股页面 -->
<template>
<view class="container">
<!-- 自定义导航栏 -->
<view class="custom-navbar">
<view class="navbar-content">
<view class="navbar-left">
<view class="back-btn" @click="goBack">
<text class="back-icon"></text>
</view>
</view>
<view class="navbar-center">
<text class="navbar-title">我的自选</text>
</view>
<view class="navbar-right">
<image
class="navbar-btn"
src="https://d31zlh4on95l9h.cloudfront.net/images/ba5c8a2eda065274e868bcd9b2d7d914.png"
@click="onFirstButtonClick"
mode="aspectFit"
></image>
<image
class="navbar-btn"
src="https://d31zlh4on95l9h.cloudfront.net/images/a4ae8952aeae90dac6d2b4c221c65fa9.png"
@click="onSecondButtonClick"
mode="aspectFit"
></image>
</view>
</view>
</view>
<!-- 页面内容 -->
<view class="page-content">
</view>
</view>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
//
goBack() {
uni.navigateBack()
},
//
onFirstButtonClick() {
console.log('第一个按钮被点击')
//
},
//
onSecondButtonClick() {
console.log('第二个按钮被点击')
//
}
}
}
</script>
<style>
.container {
width: 100%;
height: 100vh;
background-color: #f5f5f5;
}
/* 自定义导航栏 */
.custom-navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 999;
background-color: #ffffff;
border-bottom: 1px solid #e5e5e5;
}
.navbar-content {
display: flex;
align-items: center;
justify-content: space-between;
height: 44px;
padding: 0 15px;
/* 适配状态栏高度 */
padding-top: var(--status-bar-height, 20px);
min-height: calc(44px + var(--status-bar-height, 20px));
}
.navbar-left {
flex: 0 0 auto;
display: flex;
align-items: center;
}
.back-btn {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
font-size: 24px;
color: #333333;
font-weight: bold;
}
.navbar-center {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.navbar-title {
font-size: 18px;
font-weight: 500;
color: #333333;
}
.navbar-right {
flex: 0 0 auto;
display: flex;
align-items: center;
gap: 10px;
}
.navbar-btn {
width: 24px;
height: 24px;
}
/* 页面内容 */
.page-content {
padding-top: calc(44px + var(--status-bar-height, 20px) + 1px);
min-height: calc(100vh - 44px - var(--status-bar-height, 20px) - 1px);
}
</style>

682
pages/customerServicePlatform/csPlatformIndex.vue

@ -0,0 +1,682 @@
<template>
<view class="main">
<view class="top" :style="{height:iSMT+'px'}"></view>
<!-- 头部导航 -->
<view class="header">
<view class="back-icon">
<image @click="onBack" src="/static/customer-service-platform/cs-platform-back.png"
class="header-icon-image"></image>
</view>
<view class="title">{{headerTitle}}</view>
<view class="notification-icon">
<image src="/static/customer-service-platform/message.png" class="header-icon-image"></image>
</view>
</view>
<!-- 内容区域 - 使用滚动视图 -->
<scroll-view scroll-y class="content-container">
<view class="content-header">
<view class="content-header-area">
<view class="logo">
<image mode="aspectFit" src="/static/customer-service-platform/ellipse-dc-img.png"></image>
</view>
<view class="greeting">
<text class="greet-title">我能为你做点什么</text>
<text class="greet-sub">DeepChart随时为您提供服务</text>
</view>
</view>
</view>
<!--猜你想问卡片部分-->
<view class="card">
<view class="suggest-header">
<text class="suggest-title">猜你想问</text>
<view class="swap" @click="getQuestionList()">
<image class="swap-icon" src="/static/customer-service-platform/refresh-icon.png"></image>
<text class="swap-title">换一换</text>
</view>
</view>
<view class="card-line"></view>
<view class="suggest-list">
<view class="suggest-item" v-for="(q, idx) in showQuestions" :key="idx" @click="onQuestionClick(q)">
<view class="left">
<view :class="['num', 'num-' + ((idx % 5) + 1)]">{{ idx + 1 }}</view>
<text class="q-text">{{ q }}</text>
</view>
<view class="right">
<text class="arrow"></text>
</view>
</view>
</view>
</view>
<!--反馈卡片部分-->
<text class="feedback-card-title">反馈中心</text>
<view class="card">
<view class="suggest-header">
<text class="feedback-title">填写反馈内容</text>
</view>
<view class="card-line"></view>
<textarea class="feedback-input" placeholder="请描述您想反馈的内容 最多可输入200字" maxlength="200"
v-model="feedbackText" />
<view class="meta-row">
<text class="char-count">{{ feedbackText.length }}/200</text>
</view>
<view class="suggest-header">
<text class="upload-img-tip">上传图片</text>
</view>
<view class="upload-row">
<view class="img-slot" v-for="(img, index) in images" :key="index">
<image :src="img" mode="scaleToFill" class="slot-img" />
<button v-if="img" class="remove" @click="removeImage(index)">×</button>
</view>
<view class="img-slot" v-if="images.length < 3">
<view class="slot-empty" @click="chooseImage()">
<image src="/static/customer-service-platform/camera.png" class="camera-icon" />
</view>
</view>
<text class="tip-text" v-if="images.length === 0">最多添加3张图片</text>
</view>
<button class="feedback-btn" @click="onSumbitFeedback()">提交</button>
<feedback-modal ref="feedback" @confirm="onConfirm" />
</view>
<!--历史反馈卡片部分-->
<view class="card">
<text class="feedback-title">历史反馈内容</text>
<view class="card-line"></view>
<button class="feedback-btn" @click="viewHistory">查看</button>
</view>
</scroll-view>
</view>
</template>
<script>
import {
getQuestionApi,
addFeedbackRecordApi,
uploadImageApi
} from "../../api/customerServicePlatform/customerServicePlatform";
import FeedbackModal from '@/components/FeedbackModal.vue'
export default {
components: {
FeedbackModal
},
data() {
return {
headerTitle: '智能客服中台',
iSMT: 0,
questions: [
"DeepChart 有免费功能和付费功能的区分吗?具体有哪些?",
"如何参与平台的用户反馈活动?反馈的问题会被采纳吗?",
"我的自选股最多能添加多少只?能否按市场分类管理?",
"注册时必须提供手机号 / 邮箱吗?能否匿名使用?",
"忘记登录密码了,如何找回?",
'如何注册账号?',
'为什么无法注册账户?'
],
showQuestions: [],
feedbackText: '',
images: [],
}
},
mounted() {
//
this.iSMT = uni.getSystemInfoSync().statusBarHeight;
this.getQuestionList()
},
methods: {
onSuccess() {
this.$refs.feedback.show({
status: 'success',
title: '提交成功',
subtitle: '— 感谢您的反馈 —',
buttonText: '确定',
width: '80%',
});
},
onFail() {
this.$refs.feedback.show({
status: 'fail',
title: '提交失败',
subtitle: '— 请重新提交 —',
buttonText: '确定',
width: '80%',
});
},
onBack() {
if (typeof uni !== 'undefined') uni.navigateBack();
},
async getQuestionList() {
const res = await getQuestionApi()
console.log(res)
if (res.code == 200) {
this.showQuestions = res.data
}
},
onQuestionClick(q) {
if (typeof uni !== 'undefined') uni.navigateTo({
url: `/pages/customerServicePlatform/questionDetail?question=${encodeURIComponent(q)}`
});
},
chooseImage() {
const that = this;
if (typeof uni === 'undefined' || !uni.chooseImage) return;
const remain = 3 - (that.images ? that.images.length : 0);
if (remain <= 0) {
if (typeof uni !== 'undefined') uni.showToast({
title: '最多只能上传3张',
icon: 'none'
});
return;
}
uni.chooseImage({
count: remain,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success(res) {
const paths = res.tempFilePaths || (res.tempFiles && res.tempFiles.map(f => f.path)) || [];
for (let p of paths) {
if (that.images.length < 3) {
that.images.push(p);
}
}
},
fail(err) {
uni.showToast({
title: `选择图片失败`,
icon: 'none'
});
}
});
},
removeImage(index) {
//
this.images.splice(index, 1);
},
async onSumbitFeedback() {
if (!this.feedbackText.trim()) {
if (typeof uni !== 'undefined') uni.showToast({
title: '请填写反馈内容',
icon: 'none'
});
return;
}
if (typeof uni !== 'undefined') uni.showLoading({
title: '提交中...'
});
try {
let uploadedImages = [];
let imgFlag = true
for (let i = 0; i < this.images.length; i++) {
const f = this.images[i];
await new Promise((resolve, reject) => {
uni.getImageInfo({
src: f,
success: () => resolve(),
fail: reject
});
});
const uploadRes = await new Promise((resolve, reject) => {
uni.uploadFile({
url: 'http://39.101.133.168:8828/hljw/api/aws/upload',
filePath: f,
name: 'file',
formData: {
dir: 'deepchart'
},
success: (res) => {
try {
const data = JSON.parse(res.data);
if (data.code === 200) {
uploadedImages.push(data.data.url);
resolve(data);
} else {
uni.showToast({
title: `${i + 1}张图片上传失败`,
icon: 'none'
});
imgFlag = false;
reject(data);
}
} catch (err) {
imgFlag = false;
reject(err);
}
},
fail: (err) => {
imgFlag = false;
uni.showToast({
title: `${i + 1}张图片上传失败`,
icon: 'none'
});
reject(err);
}
});
});
}
if (!imgFlag) {
return
}
const [image1 = '', image2 = '', image3 = ''] = uploadedImages;
const res = await addFeedbackRecordApi({
content: this.feedbackText,
image1,
image2,
image3
})
if (res.code == 200) {
this.onSuccess()
} else {
this.onFail()
}
} catch {
this.onFail()
} finally {
uni.hideLoading();
this.feedbackText = '';
this.images = [];
}
},
viewHistory() {
//
if (typeof uni !== 'undefined') uni.navigateTo({
url: '/pages/customerServicePlatform/historyRecord'
});
}
}
}
</script>
<style scoped>
.main {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #ffffff;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 30rpx;
background-color: #ffffff;
}
.title {
color: #000000;
text-align: center;
font-size: 32rpx;
font-style: normal;
font-weight: 400;
}
.back-icon,
.notification-icon {
width: 40rpx;
display: flex;
align-items: center;
justify-content: center;
}
.header-icon-image {
width: 40rpx;
height: 40rpx;
object-fit: contain;
}
.content-container {
padding: 20rpx;
width: 100%;
box-sizing: border-box;
overflow-x: hidden;
}
.content-header {
display: flex;
align-items: center;
justify-content: center;
gap: 24rpx;
padding: 0 60rpx;
width: 100%;
box-sizing: border-box;
height: 188rpx;
}
.content-header-area {
display: flex;
gap: 20rpx;
}
.logo {
width: 120rpx;
height: 120rpx;
display: flex;
align-items: center;
justify-content: center;
flex: 0 0 112rpx;
}
.greeting {
display: flex;
flex-direction: column;
justify-content: center;
flex: 1 1 auto;
}
.greet-title {
color: #000;
font-size: 40rpx;
font-style: normal;
font-weight: 500;
line-height: normal;
margin: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.greet-sub {
color: #838383;
font-size: 28rpx;
font-style: normal;
font-weight: 400;
line-height: normal;
margin-top: 12rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.card {
width: 90%;
margin: 0 auto;
border-radius: 16rpx;
padding: 20rpx 40rpx;
box-sizing: border-box;
border-radius: 12rpx;
border: 4rpx solid #FCC8D4;
background: linear-gradient(180deg, #FCC8D3 0%, #FEF0F3 30%, #FFF 100%);
margin-bottom: 20rpx;
}
.suggest-header {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.suggest-title {
color: #000000;
font-size: 32rpx;
font-style: normal;
font-weight: 400;
line-height: normal;
}
.swap {
display: flex;
align-items: center;
transition: transform 0.1s ease, background-color 0.1s ease;
}
.swap:active {
transform: scale(0.95);
}
.swap-icon {
width: 30rpx;
height: 30rpx;
}
.swap-title {
padding-left: 8rpx;
color: #000000;
font-size: 24rpx;
font-style: normal;
font-weight: 400;
line-height: normal;
}
.suggest-list {
margin-top: 20rpx;
}
.card-line {
margin-top: 20rpx;
width: 100%;
height: 2rpx;
border-radius: 2rpx;
background: #FFF;
}
.suggest-item {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 10rpx;
border-radius: 12rpx;
margin-bottom: 20rpx;
width: 100%;
box-sizing: border-box;
}
.left {
width: 90%;
display: flex;
align-items: center;
}
.num {
font-size: 40rpx;
font-style: normal;
font-weight: 700;
line-height: normal;
}
.num-1 {
color: #df5662;
}
.num-2 {
color: #ec6d4f;
}
.num-3 {
color: #f3ba40;
}
.num-4 {
color: #9296a0;
}
.num-5 {
color: #9296a0;
}
.q-text {
padding-left: 14rpx;
display: block;
color: #333;
font-size: 28rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.right {
width: 48rpx;
display: flex;
align-items: center;
justify-content: flex-end;
}
.arrow {
color: #cfcfcf;
font-size: 36rpx;
}
.suggest-item:active {
background: rgba(255, 77, 128, 0.06);
}
.feedback-card-title {
display: flex;
justify-content: center;
color: #000000;
font-size: 32rpx;
font-weight: 700;
line-height: 40rpx;
width: 100%;
margin-bottom: 20rpx;
}
.feedback-title {
color: #000000;
font-size: 32rpx;
font-style: normal;
font-weight: 400;
line-height: normal;
}
.feedback-input {
width: 100%;
display: flex;
padding: 20rpx;
flex-direction: column;
box-sizing: border-box;
align-items: flex-start;
gap: 12rpx;
align-self: stretch;
border-radius: 12rpx;
border: 2rpx solid #F0F1F1;
margin-top: 20rpx;
display: flex;
background: #FFF;
color: #8a8a8a;
font-size: 24rpx;
font-weight: 700;
line-height: normal;
}
.meta-row {
display: flex;
justify-content: flex-end;
margin-top: 12rpx;
}
.char-count {
color: #999
}
.upload-img-tip {
color: #000000;
font-size: 24rpx;
font-style: normal;
font-weight: 400;
line-height: normal;
}
.upload-row {
display: flex;
justify-content: flex-start;
align-items: flex-start;
align-content: flex-start;
flex-wrap: wrap;
gap: 20rpx;
margin-top: 20rpx;
width: 100%;
}
.img-slot {
width: calc((100% - 2 * 30rpx) / 3);
aspect-ratio: 1 / 1;
border-radius: 6px;
border: 1px solid #F0F1F1;
background: #FFF;
position: relative;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
transition: transform 0.2s ease;
}
.slot-empty {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.camera-icon {
width: 34rpx;
height: 34rpx;
}
.slot-img {
width: 100%;
height: 100%;
border-radius: 16rpx;
}
.remove {
position: absolute;
right: 6rpx;
top: 6rpx;
border-radius: 50%;
background: #fd5c58;
padding: 0;
color: #fff;
width: 36rpx;
height: 36rpx;
font-size: 28rpx;
line-height: 36rpx;
text-align: center;
border: none;
outline: none;
cursor: pointer;
}
.remove:active {
background: rgba(0, 0, 0, 0.75);
}
.image-upload-tip {
display: flex;
align-items: center;
}
.tip-text {
color: #999999;
font-size: 24rpx;
font-style: normal;
font-weight: 400;
line-height: 40rpx;
padding-top: 64rpx;
}
.feedback-btn {
margin-top: 24rpx;
width: 180rpx;
height: 60rpx;
aspect-ratio: 89/30;
border-radius: 30rpx;
background: #090A08;
color: #ffffff;
display: flex;
justify-content: center;
align-items: center;
font-size: 32rpx;
font-style: normal;
font-weight: 700;
line-height: normal;
}
</style>

353
pages/customerServicePlatform/historyRecord.vue

@ -0,0 +1,353 @@
<template>
<view class="main">
<view class="top" :style="{height:iSMT+'px'}"></view>
<!-- 头部导航 -->
<view class="header">
<view class="back-icon">
<image @click="goBack()" src="/static/customer-service-platform/cs-platform-back.png"
class="header-icon-image"></image>
</view>
<view class="title">智能客服中台</view>
<view class="notification-icon">
<image src="/static/customer-service-platform/message.png" class="header-icon-image"></image>
</view>
</view>
<!-- 内容区域 - 使用滚动视图 -->
<scroll-view scroll-y class="content-container">
<view class="list-wrapper" v-if="historyList.length > 0">
<view class="content-header">
<text class="content-title">历史反馈内容</text>
</view>
<view class="card-line"></view>
<view v-for="(item, idx) in historyList" :key="item.id" class="history-item card">
<view class="item-line" v-if="idx != 0"></view>
<view class="item-head">
<view class="dot-outer">
<view class="dot-inner"></view>
</view>
<text class="feedback-time">{{ formatTime(item.createdAt) }}</text>
<text class="feedback-status">
{{ statusText }}
<image class="smile-icon" src="/static/customer-service-platform/smile-icon.png"></image>
</text>
</view>
<view class="content-box">
<text class="content-text">{{ item.content }}</text>
<text class="count">{{ item.content.length }}/200</text>
</view>
<view v-if="item.images && item.images.length" class="thumb-row">
<view v-for="(img, i) in item.images" :key="i" class="thumb-slot"
@click="previewImage(item.images, i)">
<image :src="img" mode="scaleToFill" class="thumb-img" />
</view>
</view>
</view>
</view>
<!-- 如果没有历史显示空态 -->
<view v-if="historyList.length === 0" class="empty">
<image mode="aspectFit" class="empty-img" src="/static/customer-service-platform/empty-content.png">
</image>
<text class="empty-tip">暂无内容~</text>
</view>
</scroll-view>
</view>
</template>
<script>
import {
getFeedbackRecordsApi,
} from "../../api/customerServicePlatform/customerServicePlatform";
export default {
data() {
return {
iSMT: 0,
statusText: '反馈成功',
historyList: [],
};
},
mounted() {
this.iSMT = uni.getSystemInfoSync().statusBarHeight;
this.loadHistoryList()
},
methods: {
formatTime(str) {
if (!str) return '';
const d = new Date(str);
const yyyy = d.getFullYear();
const mm = String(d.getMonth() + 1).padStart(2, '0');
const dd = String(d.getDate()).padStart(2, '0');
const hh = String(d.getHours()).padStart(2, '0');
const mi = String(d.getMinutes()).padStart(2, '0');
return `${yyyy}-${mm}-${dd} ${hh}:${mi}`;
},
goBack() {
if (typeof uni !== 'undefined' && uni.navigateBack) {
uni.navigateBack();
} else {
window.history.back();
}
},
async loadHistoryList() {
const res = await getFeedbackRecordsApi()
console.log(res)
if (res.code == 200) {
this.historyList = res.data.map(item => {
const images = [item.image1, item.image2, item.image3].filter(img => !!img)
return {
id: item.id,
createdAt: item.createdAt,
content: item.content,
images,
dccode: item.dccode
}
})
}
},
previewImage(list, index) {
if (typeof uni !== 'undefined' && uni.previewImage) {
uni.previewImage({
current: list[index],
urls: list
});
} else {
window.open(list[index], '_blank');
}
}
}
};
</script>
<style scoped>
.main {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #ffffff;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 30rpx;
background-color: #ffffff;
}
.title {
color: #000000;
text-align: center;
font-size: 32rpx;
font-style: normal;
font-weight: 400;
}
.back-icon,
.notification-icon {
width: 40rpx;
display: flex;
align-items: center;
justify-content: center;
}
.header-icon-image {
width: 40rpx;
height: 40rpx;
object-fit: contain;
}
.content-container {
padding: 20rpx;
padding-top: 0;
width: 100%;
box-sizing: border-box;
overflow-x: hidden;
}
/* 列表包装器,居中卡片 */
.list-wrapper {
width: 90%;
margin: 0 auto;
padding: 20rpx 40rpx;
flex-direction: column;
align-items: center;
gap: 20rpx;
box-sizing: border-box;
border-radius: 12rpx;
border: 4rpx solid #FCC8D4;
background: linear-gradient(180deg, #FCC8D3 0%, #FEF0F3 30%, #FFF 100%);
}
.content-header {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.content-title {
color: #000000;
font-size: 32rpx;
font-style: normal;
font-weight: 400;
line-height: normal;
}
.card-line {
margin-top: 20rpx;
width: 100%;
height: 2rpx;
border-radius: 2rpx;
background: #FFF;
}
/* 每一条历史卡片 */
.history-item {
border-radius: 10rpx;
padding: 20rpx;
margin-bottom: 20rpx;
box-sizing: border-box;
box-shadow: 0 4rpx 12rpx rgba(255, 77, 128, 0.06);
}
.item-line{
margin-bottom: 20rpx;
width: 100%;
height: 2rpx;
border-radius: 2rpx;
background: #D9D9D9;
}
.item-head {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 12rpx;
}
.dot-outer {
width: 24rpx;
height: 24rpx;
border-radius: 50%;
background: rgba(255, 214, 230, 0.5);
/*粉色外圈*/
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 0 4rpx #ffffff;
/* 最外层白色 */
}
.dot-inner {
width: 14rpx;
height: 14rpx;
border-radius: 50%;
background: #ff4150;
/* 中心红色 */
}
.feedback-time {
color: #000000;
flex: 1;
font-size: 22rpx;
font-style: normal;
font-weight: 400;
line-height: normal;
padding-left: 26rpx;
}
.feedback-status {
color: #ff4150;
font-size: 12rpx;
font-style: normal;
font-weight: 400;
line-height: normal;
display: flex;
align-items: center;
}
.smile-icon {
width: 32rpx;
height: 32rpx;
}
/* 内容框 */
.content-box {
border: 2rpx solid #f0e6ea;
background: #fff;
border-radius: 8rpx;
padding: 18rpx;
position: relative;
box-sizing: border-box;
min-height: 160rpx;
}
.content-text {
display: block;
white-space: pre-wrap;
word-break: break-word;
color: #8a8a8a;
font-size: 24rpx;
font-style: normal;
font-weight: 700;
line-height: normal;
padding-bottom: 26rpx;
}
.count {
position: absolute;
right: 14rpx;
bottom: 10rpx;
color: #000000;
font-size: 24rpx;
font-style: normal;
font-weight: 400;
line-height: normal;
}
.thumb-row {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
align-items: flex-start;
align-content: flex-start;
width: 100%;
box-sizing: border-box;
gap: 20rpx;
margin-top: 14rpx;
background: #F9FAFE;
padding: 20rpx;
}
.thumb-slot {
width: calc((100% - 2 * 25rpx) / 3);
aspect-ratio: 1 / 1;
border-radius: 7rpx;
border: 1.2rpx solid #F0F1F1;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.thumb-img {
width: 100%;
height: 100%;
}
.empty {
padding: 50rpx 0;
text-align: center;
color: #afafaf;
font-size: 32rpx;
font-style: normal;
font-weight: 500;
line-height: 48rpx;
}
.empty-img {
width: 100%;
}
</style>

335
pages/customerServicePlatform/questionDetail.vue

@ -0,0 +1,335 @@
<template>
<view class="main">
<view class="top" :style="{ height: iSMT + 'px' }"></view>
<!-- 头部导航 -->
<view class="header">
<view class="back-icon">
<image @click="onBack" src="/static/customer-service-platform/cs-platform-back.png"
class="header-icon-image"></image>
</view>
<view class="title">{{ headerTitle }}</view>
<view class="notification-icon">
<image src="/static/customer-service-platform/message.png" class="header-icon-image"></image>
</view>
</view>
<scroll-view scroll-y class="content-container">
<view class="content-header">
<view class="content-header-area">
<view class="logo">
<image mode="aspectFit" src="/static/customer-service-platform/ellipse-dc-img.png"></image>
</view>
<view class="greeting">
<text class="greet-title">我能为你做点什么</text>
<text class="greet-sub">DeepChart随时为您提供服务</text>
</view>
</view>
</view>
<!-- 卡片部分 -->
<view class="card">
<!-- 问题头部-->
<view class="question-header">
<view class="question-row">
<image class="question-avatar" src="/static/customer-service-platform/robot-head.png"
mode="aspectFill"></image>
<view class="question-title">{{ questionTitle }}</view>
</view>
</view>
<!-- 卡片内容区-->
<view class="card-body">
<image class="card-logo" src="/static/customer-service-platform/ellipse-dc-img.png"
mode="aspectFit"></image>
<view class="card-text">
<text class="card-paragraph">
{{answerContent}}
</text>
</view>
</view>
</view>
<view class="login-row" v-if="showLoginRegister">
<button class="login-btn" @click="toLogin">登录</button>
<button class="register-btn" @click="toRegistration">注册</button>
</view>
</scroll-view>
</view>
</template>
<script>
import {
getAnswerApi
} from "../../api/customerServicePlatform/customerServicePlatform";
export default {
data() {
return {
headerTitle: '智能客服中台',
iSMT: 0,
questionTitle: '',
answerContent: '正在思考...',
showLoginRegister:false,
};
},
mounted() {
this.iSMT = uni.getSystemInfoSync().statusBarHeight || 0;
this.getAnswerContent()
},
onLoad(options) {
if (options.question) {
this.questionTitle = decodeURIComponent(options.question);
if (this.questionTitle.includes("如何注册")) {
this.showLoginRegister = true
} else {
this.showLoginRegister = false
}
}
},
methods: {
async getAnswerContent() {
let conversationId = '';
try {
const cache = uni.getStorageSync('conversationId');
if (cache) conversationId = cache;
} catch (e) {
conversationId = '';
}
const res = await getAnswerApi({
question: this.questionTitle,
conversationId: conversationId,
})
console.log(res)
if (res.code == 200) {
uni.setStorageSync('conversationId', res.data.conversationId);
const answer = res.data.answer
this.answerContent = '';
for (let i = 0; i < answer.length; i++) {
this.answerContent += answer[i];
await this.sleepTime(150);
}
} else {
this.answerContent = '获取回答失败,请重试';
}
},
async sleepTime() {
const ms = Math.floor(Math.random() * (300 - 30 + 1)) + 30;
return new Promise(resolve => setTimeout(resolve, ms));
},
toRegistration() {
uni.redirectTo({
url: "/pages/start/Registration/Registration",
});
},
toLogin() {
uni.redirectTo({
url: "/pages/start/login/login",
});
},
onBack() {
if (typeof uni !== 'undefined') uni.navigateBack();
}
}
};
</script>
<style scoped>
.main {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #ffffff;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 30rpx;
background-color: #ffffff;
}
.title {
color: #000000;
text-align: center;
font-size: 32rpx;
font-weight: 400;
}
.back-icon,
.notification-icon {
width: 40rpx;
display: flex;
align-items: center;
justify-content: center;
}
.header-icon-image {
width: 40rpx;
height: 40rpx;
object-fit: contain;
}
.content-container {
padding: 20rpx;
width: 100%;
box-sizing: border-box;
overflow-x: hidden;
}
.content-header {
display: flex;
align-items: center;
justify-content: center;
gap: 24rpx;
padding: 0 60rpx;
width: 100%;
box-sizing: border-box;
height: 188rpx;
}
.content-header-area {
display: flex;
gap: 20rpx;
}
.logo {
width: 120rpx;
height: 120rpx;
display: flex;
align-items: center;
justify-content: center;
flex: 0 0 112rpx;
}
.greeting {
display: flex;
flex-direction: column;
justify-content: center;
flex: 1 1 auto;
}
.greet-title {
color: #000;
font-size: 40rpx;
font-style: normal;
font-weight: 500;
line-height: normal;
margin: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.greet-sub {
color: #838383;
font-size: 28rpx;
font-style: normal;
font-weight: 400;
line-height: normal;
margin-top: 12rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.card {
width: 90%;
margin: 0 auto 20rpx;
padding: 28rpx;
box-sizing: border-box;
border-radius: 16rpx;
border: 4rpx solid #FF7C99;
background: #fff;
}
/* 问题头部 */
.question-header {
width: 100%;
margin-bottom: 48rpx;
}
.question-row {
display: flex;
align-items: center;
}
.question-avatar {
width: 52rpx;
height: 52rpx;
border-radius: 999rpx;
margin-right: 20rpx;
flex-shrink: 0;
}
.question-title {
color: #000000;
font-size: 34rpx;
}
/* 卡片内部布局 */
.card-body {
display: flex;
gap: 20rpx;
align-items: flex-start;
}
.card-logo {
width: 52rpx;
height: 52rpx;
flex: 0 0 52rpx;
border-radius: 8rpx;
}
.card-text {
flex: 1 1 auto;
}
.card-paragraph {
display: block;
color: #000000;
font-size: 28rpx;
margin-bottom: 14rpx;
font-style: normal;
font-weight: 500;
}
.login-row {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
margin-top: 100rpx;
}
.login-btn {
width: 260rpx;
height: 100rpx;
border-radius: 50rpx;
background: #F3F3F3;
color: #000000;
display: flex;
justify-content: center;
align-items: center;
font-size: 28rpx;
margin-right: 20rpx;
}
.register-btn {
width: 260rpx;
height: 100rpx;
border-radius: 60rpx;
background: #000;
color: #ffffff;
display: flex;
justify-content: center;
align-items: center;
font-size: 28rpx;
}
</style>

720
pages/deepExploration/MainForceActions.vue

@ -0,0 +1,720 @@
<template>
<view class="main">
<!-- 顶部状态栏占位 -->
<view class="top" :style="{height:iSMT+'px'}"></view>
<deepExploration_header></deepExploration_header>
<view class="search">
<input v-model="searchName" class="searchInput" type="text" placeholder="请输入股票名称、股票代码"
placeholder-style="color: #A6A6A6; font-size: 22rpx;" />
<image @click="searchStock" class="seachIcon" src="/static/deepExploration-images/search.png"
mode="aspectFill"></image>
</view>
<view class="content">
<view class="select">
<image class="img" :src="navItems[currentIndex].icon" mode=""></image>
<view v-for="(item, index) in navItems" :key="index" class="selectItem"
:class="{ active: currentIndex === index }" @click="handleModel(index)">
<button class="btn"></button>
</view>
</view>
<view class="graphAndTxt">
<view class="graph">
<view class="graph_header">
<view class="left">{{stockCode}}</view>
<view class="center">
<text>{{stockName}}</text>
</view>
<view class="right">{{stockTime}}</view>
</view>
<view class="graph_data">
<text>{{stockPrice}}</text>
<text>{{stockChange}}</text>
<text>{{stockAdd}}</text>
</view>
<view class="graph_content">
<view class="charts-box">
<!-- uCharts 蜡烛图组件 -->
<qiun-data-charts type="candle" :opts="opts" :chartData="chartData" :disableScroll="true"
:ontouch="true" :onzoom="true" :key="chartKey" />
</view>
</view>
</view>
<view class="txt">
<view class="txtHeader">
<image src="/static/deepExploration-images/plus.png" mode="aspectFill"></image>
<text>{{navItems[currentIndex].name}}</text>
</view>
<view class="txtContent">
<view v-if="loading" class="loading">加载中...</view>
<rich-text :nodes="htmlContent"></rich-text>
</view>
</view>
</view>
</view>
<!-- 底部切换栏 -->
<footerBar class="static-footer" :type="type"></footerBar>
</view>
</template>
<script setup>
import {
ref,
onMounted,
watch
} from 'vue'
import deepExploration_header from '@/components/deepExploration_header.vue'
import footerBar from '@/components/footerBar.vue'
import {
onLoad
} from '@dcloudio/uni-app'
import {
getModel1First,
getModel1Second,
getModel2First,
getModel2Second,
getModel3First,
getModel3Second,
getModel4First,
getModel4Second,
getModeldefault,
getData
} from '/api/deepExploration/deepExploration.js'
import marked from 'marked'; // marked
import hljs from 'highlight.js';
import 'highlight.js/styles/atom-one-dark.css'; //
import {
useDeepExplorationStore
} from '@/stores/modules/deepExploration'
const deepExplorationStore = useDeepExplorationStore()
const historyData = ref({})
//
const type = ref('deepExploration')
const iSMT = ref(0)
const currentIndex = ref(0)
const navItems = ref([{
name: '主力追踪',
icon: '/static/deepExploration-images/1.png'
},
{
name: '主力雷达',
icon: '/static/deepExploration-images/2.png'
},
{
name: '主力解码',
icon: '/static/deepExploration-images/3.png'
},
{
name: '主力资金流',
icon: '/static/deepExploration-images/4.png'
},
])
//
const searchStock = () => {
htmlContent.value = ''
console.log('搜索参数:', stockName.value, currentIndex.value);
if (currentIndex.value >= 0 && currentIndex.value <= 3) {
handleModels()
} else {
uni.showToast({
title: '请选择模块',
icon: 'none',
duration: 2000
})
}
}
//
const handleModel = async (index) => {
htmlContent.value = ''
currentIndex.value = index
await handleModels()
// await getServerData()
}
const stockName = ref('Tesla Inc.')
const searchName = ref('')
const stockCode = ref('TSLA')
const language = ref('')
const recordId = ref('')
const parentId = ref('')
const stockId = ref('')
const market = ref('')
const stockTime = ref('2025/10/24')
const loading = ref(true);
const error = ref('');
const htmlContent = ref('');
const markdownContent = ref('');
const renderer = new marked.Renderer();
renderer.heading = function(text, level) {
return `<p>${text}</p>`;
};
// marked
marked.setOptions({
highlight: (code, lang) => {
if (lang && hljs.getLanguage(lang)) {
return hljs.highlight(code, {
language: lang
}).value;
}
return hljs.highlightAuto(code).value;
},
renderer,
breaks: true, // <br>
gfm: true, // GitHub flavored Markdown
sanitize: false, // HTML<span style>
});
//
const handleModels = async () => {
try {
// markdownContent.value = '\n## 📊 \n\n### 🕵 \n\t1. 📊 360.249 412.577 444.330\n\t2. 🔍 \n\t3. 📈 \n\n### 📊 :\n\t- 📉 : <font color=\"#13c2c2\">443.092</font> \n - 📈 : <font color=\"#ff4d4f\">466.458</font>\n\t- 📉 : <font color=\"#13c2c2\">447.354</font>\n\t- 📈 : <font color=\"#ff4d4f\">462.514</font>\n\t<font color=\"#722ed1\">AI线</font>\n\n### \n\t\t\t<font color=\"#fa8c16\">K线</font>\n\t\t\t<font color=\"#eb2f96\"></font>\n\n---\n<font color=\"#8c8c8c\">*AI*</font>\n '
// htmlContent.value = marked.parse(markdownContent.value);
loading.value = true;
if (searchName.value == '') {
console.log('没有搜索', searchName.value);
handleDefault()
} else {
console.log('搜索', searchName.value);
const result = await getModel1First({
content: searchName.value,
language: "cn",
marketList: "hk,cn,usa,my,sg,vi,in,gb",
model: currentIndex.value + 1
})
console.log('result', result);
if (result.code == 200) {
stockCode.value = result.data.code
// stockName.value = result.data.name
recordId.value = result.data.recordId
parentId.value = result.data.parentId
stockId.value = result.data.stockId
language.value = result.data.language
market.value = result.data.market
const res = await getModel1Second({
language: language.value,
recordId: recordId.value,
parentId: parentId.value,
stockId: stockId.value,
token: 'pCtw6AYK0EHAaIexoFHsbZjtsfEAIhcmwkCFm6uKko8VPfMvyDiODL9v9c0veic9fIpQbvT8zN4sH/Si6Q'
})
if (res.code == 200) {
const rawMarkdown = res.data.markdown;
const adaptedMarkdown = rawMarkdown.replace(/^### /gm, ''); // ###
markdownContent.value = adaptedMarkdown;
// markdownContent.value = res.data.markdown
htmlContent.value = marked.parse(markdownContent.value);
}
console.log('res', res);
await getServerData()
} else if (result.code == 400) {
markdownContent.value = result.message;
htmlContent.value = marked.parse(markdownContent.value);
} else {
return
}
}
} catch (e) {
error.value = e.message || '加载失败,请重试';
} finally {
loading.value = false;
}
}
const handleDefault = async () => {
const result = await getModeldefault({
token: "pCtw6AYK0EHAaIexoFHsbZjtsfEAIhcmwkCFm6uKko8VPfMvyDiODL9v9c0veic9fIpQbvT8zN4sH/Si6Q",
model: currentIndex.value + 1
})
if (result.code == 200) {
const rawMarkdown = result.data.markdown;
const adaptedMarkdown = rawMarkdown.replace(/^### /gm, ''); // ###
markdownContent.value = adaptedMarkdown;
htmlContent.value = marked.parse(markdownContent.value);
} else {
}
}
const stockPrice = ref('435.900')
const stockAdd = ref('22.410')
const stockChange = ref('5.120%')
const chartKey = ref(0);
const getServerData = async () => {
const result = await getData({
market: market.value || '',
code: searchName.value || '',
language: "cn",
brainPrivilegeState: 1,
marketList: "usa.sg.my.hk.cn.can.vi.th.in.gb"
})
console.log('k线数据', result);
stockName.value = result.data.StockInformation.Name || 'Tesla Inc.'
stockCode.value = result.data.StockInformation.Code || 'TSLA'
stockTime.value = result.data.StockInformation.Time || '2025/10/29'
stockChange.value = result.data.StockInformation.Zhang || '5.120%'
stockAdd.value = result.data.StockInformation.ZhangFu || '22.410'
stockPrice.value = result.data.StockInformation.Price || '435.900'
if (result.data.chartData) {
chartData.value = {
...JSON.parse(JSON.stringify(result.data.chartData))
}
chartKey.value++;
console.log('chartData', chartData.value);
}
}
// 1. K线
const opts = ref({
rotate: false,
rotateLock: false,
color: ["#1890FF", "#91CB74", "#FAC858", "#EE6666", "#73C0DE", "#3CA272", "#FC8452", "#9A60B4", "#ea7ccc"],
padding: [15, 15, 0, 15],
dataLabel: false,
enableScroll: true,
enableMarkLine: false,
legend: {},
xAxis: {
labelCount: 4,
itemCount: 30,
disableGrid: true,
gridColor: "#CCCCCC",
gridType: "solid",
dashLength: 4,
scrollShow: false,
scrollAlign: "left",
scrollColor: "#A6A6A6",
scrollBackgroundColor: "#EFEBEF",
labelColor: "#8C8C8C",
fontSize: 9
},
yAxis: {
labelColor: "#8C8C8C",
fontSize: 9
},
extra: {
candle: {
color: {
upLine: "#f04864",
upFill: "#f04864",
downLine: "#2fc25b",
downFill: "#2fc25b"
},
average: {
show: false,
name: ["MA5", "MA10", "MA30"],
day: [5, 10, 20],
color: ["#1890ff", "#2fc25b", "#facc14"]
}
},
markLine: {
type: "dash",
dashLength: 5,
data: [{
value: 2150,
lineColor: "#f04864",
showLabel: false
},
{
value: 2350,
lineColor: "#f04864",
showLabel: false
}
]
},
tooltip: {
showCategory: true
}
}
})
// 2. K线
const chartData = ref({
categories: [],
series: [{
name: '',
data: []
}]
})
//K线methods
// const getServerData1 = () => {
// //
// setTimeout(() => {
// const res = {
// "categories": [
// "2025/10/23",
// "2025/10/24",
// "2025/10/27"
// ],
// series: [{
// "name": "",
// "data": [
// [
// 1455.0,
// 1468.8,
// 1447.2,
// 1467.98
// ],
// [
// 1467.95,
// 1478.88,
// 1449.34,
// 1450.0
// ],
// [
// 1440.0,
// 1452.49,
// 1435.99,
// 1440.41
// ]
// ],
// }]
// }
// // .value
// chartData.value = JSON.parse(JSON.stringify(res))
// }, 500)
// }
let unwatch = null;
// onReady
onMounted(async () => {
iSMT.value = uni.getSystemInfoSync().statusBarHeight
await getServerData() //
await handleModels()
unwatch = watch(
() => deepExplorationStore.deepExplorationInfo, //
(newVal, oldVal) => {
console.log('deepExplorationInfo 变化了:', newVal)
historyData.value = {
...newVal
}
console.log(historyData.value.wokeFlowData);
console.log('222', historyData.value.stockData.StockInformation);
//
const rawMarkdown = historyData.value.wokeFlowData.One.markdown;
const adaptedMarkdown = rawMarkdown.replace(/^### /gm, ''); // ###
markdownContent.value = adaptedMarkdown;
// markdownContent.value = res.data.markdown
htmlContent.value = marked.parse(markdownContent.value);
//k线
chartData.value = {
...JSON.parse(JSON.stringify(historyData.value.stockData.chartData))
}
chartKey.value++;
console.log('chartData', chartData.value);
stockName.value = historyData.value.stockData.StockInformation.Name || 'Tesla Inc.'
stockCode.value = historyData.value.stockData.StockInformation.Code || 'TSLA'
stockTime.value = historyData.value.stockData.StockInformation.Time || '2025/10/29'
stockChange.value = historyData.value.stockData.StockInformation.Zhang || '5.120%'
stockAdd.value = historyData.value.stockData.StockInformation.ZhangFu || '22.410'
stockPrice.value = historyData.value.stockData.StockInformation.Price || '435.900'
}, {
deep: true,
immediate: true
} //
)
})
//
onLoad((e) => {
if (e.index) {
currentIndex.value = e.index - 1
console.log('模块:', currentIndex.value)
}
if (e.stockName) {
searchName.value = e.stockName
console.log('股票名称:', searchName.value)
}
})
</script>
<style scoped lang="scss">
.main {
width: 100%;
min-height: 100vh;
background-color: #fff;
padding-bottom: 120rpx;
.search {
position: relative;
display: flex;
align-items: center;
background-color: #F3F3F3;
width: calc(100% - 60rpx);
height: 80rpx;
border-radius: 50rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
padding: 0 40rpx;
margin: 15rpx 30rpx 0 30rpx;
.seachIcon {
position: absolute;
right: 50rpx;
width: 32rpx;
height: 32rpx;
}
.searchInput {
color: #111;
}
}
.content {
margin-top: 30rpx;
padding-top: 30rpx;
background-color: rgb(248, 248, 248);
.select {
position: relative;
margin-bottom: -5rpx;
.img {
width: 750rpx;
height: 198rpx;
}
.selectItem {
.btn {
position: absolute;
width: 120rpx;
height: 150rpx;
background-color: transparent;
&::after {
border: none;
}
}
&:nth-of-type(1) .btn {
top: 30rpx;
left: 60rpx;
}
&:nth-of-type(2) .btn {
top: 30rpx;
left: 230rpx;
}
&:nth-of-type(3) .btn {
top: 30rpx;
left: 400rpx;
}
&:nth-of-type(4) .btn {
top: 30rpx;
left: 570rpx;
}
}
}
.graphAndTxt {
background-color: #fff;
border-radius: 50rpx 50rpx 0 0;
padding: 68.6rpx 36.5rpx 0 36.5rpx;
.graph {
border: 1rpx solid #e2e2e2;
border-radius: 30rpx 30rpx 0 0;
.graph_header {
padding: 32rpx 20.5rpx 0 24rpx;
display: flex;
align-items: center;
.left {
color: #333333;
font-family: "PingFang SC";
font-size: 15px;
font-style: normal;
font-weight: 400;
line-height: 15px;
}
.center {
margin-left: 155rpx;
display: flex;
align-items: center;
text {
width: 160rpx;
height: 36rpx;
padding-left: 10rpx;
color: #000000;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
font-family: "PingFang SC";
font-size: 18px;
font-style: normal;
font-weight: 500;
line-height: 18px;
}
}
.right {
margin-left: 60rpx;
color: #6a6a6a;
font-family: "PingFang SC";
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 15px;
}
}
.graph_data {
display: flex;
padding: 48rpx 24rpx;
text {
display: flex;
color: #25ba5d;
font-family: "PingFang SC";
font-size: 17px;
line-height: 17px;
}
text:nth-child(2) {
margin-left: 120rpx;
}
text:nth-child(3) {
margin-left: 150rpx;
}
}
.graph_content {
min-height: 500rpx;
.charts-box {
width: 100%;
height: 100%;
}
}
}
.txt {
background-color: #F3F3F3;
margin-top: 48rpx;
border-radius: 30rpx;
.txtHeader {
padding: 30rpx 24rpx;
image {
width: 20rpx;
height: 20rpx;
}
text {
background-color: #FFFFFF;
color: #000000;
padding: 0 22rpx;
border-radius: 22rpx;
font-size: 28rpx;
font-weight: 400;
line-height: 37rpx;
}
}
.txtContent {
min-height: 200rpx;
padding: 20rpx 30rpx;
margin-bottom: 100rpx;
::v-deep * {
box-sizing: border-box;
width: 100% !important; //
white-space: normal !important; //
word-wrap: break-word !important; // /
}
//
::v-deep h2 {
font-size: 32rpx;
color: #333;
margin: 25rpx 0 15rpx;
line-height: 1.5;
}
//
::v-deep p {
font-size: 26rpx;
color: #666;
margin: 15rpx 0;
line-height: 1.8; //
text-align: justify; //
}
//
::v-deep ul,
::v-deep ol {
margin: 15rpx 0 15rpx 30rpx;
}
::v-deep li {
margin: 10rpx 0;
line-height: 1.6;
}
//
.loading {
text-align: center;
padding: 50rpx 0;
color: #666;
font-size: 26rpx;
}
}
}
}
}
.static-footer {
position: fixed;
bottom: 0;
width: 100%;
}
}
* {
box-sizing: border-box;
}
</style>

532
pages/deepExploration/deepExploration.vue

@ -0,0 +1,532 @@
<template>
<view class="main">
<!-- 顶部状态栏占位 -->
<view class="top" :style="{ height: iSMT + 'px' }"></view>
<!-- 标题图标部分 -->
<deepExploration_header
></deepExploration_header>
<view class="search">
<input
v-model="stockName"
class="searchInput"
type="text"
placeholder="请输入股票名称、股票代码"
placeholder-style="color: #A6A6A6; font-size: 22rpx;"
/>
<image
@click="searchStock"
class="seachIcon"
src="/static/deepExploration-images/search.png"
mode="aspectFill"
></image>
</view>
<!-- 四大功能模块 -->
<view class="select">
<view class="selectItem" @click="toMain('主力追踪')">
<image
class="img"
src="/static/deepExploration-images/icon3.png"
mode="aspectFill"
></image>
<view class="txt">主力追踪</view>
</view>
<view class="selectItem" @click="toMain('主力雷达')">
<image
class="img"
src="/static/deepExploration-images/icon2.png"
mode="aspectFill"
></image>
<view class="txt">主力雷达</view>
</view>
<view class="selectItem" @click="toMain('主力解码')">
<image
class="img"
src="/static/deepExploration-images/icon1.png"
mode="aspectFill"
></image>
<view class="txt">主力解码</view>
</view>
<view class="selectItem" @click="toMain('主力资金流')">
<image
class="img"
src="/static/deepExploration-images/icon4.png"
mode="aspectFill"
></image>
<view class="txt">主力资金流</view>
</view>
</view>
<!-- 灰色间隔 -->
<view class="gap"></view>
<!-- 选股策略 -->
<view class="stockSelection">
<view class="stockSelection_top">
<view class="txt">
<text>选股策略</text>
</view>
<view class="viewAll" @click="viewAll">
<text>查看全部</text>
</view>
</view>
<view class="stockSelection_content">
<view class="selectionItem">
<view class="header">
<view class="left">
<image
src="/static/deepExploration-images/plus.png"
mode="aspectFill"
></image>
<text>抄底卖顶</text>
</view>
<view class="right">
<image
src="/static/deepExploration-images/Americle.png"
mode="aspectFill"
></image>
<text>美股</text>
</view>
</view>
<view class="content">
<view class="contentTitle">
<view class="contentTitle_name">股票名称</view>
<view class="contentTitle_close">最新收盘价</view>
<view class="contentTitle_price">选股价格</view>
</view>
<view class="contentItem">
<view class="row" v-for="(item, index) in stockData" :key="index">
<view class="nameItem">{{ item.tscode }}</view>
<view class="closeItem">{{ item.close }}</view>
<view class="priceItem">{{ item.preClose }}</view>
</view>
</view>
</view>
</view>
</view>
<view class="stockSelection_content">
<view class="selectionItem">
<view class="header">
<view class="left">
<image
src="/static/deepExploration-images/plus.png"
mode="aspectFill"
></image>
<text>波段行情</text>
</view>
<view class="right">
<image
src="/static/deepExploration-images/Americle.png"
mode="aspectFill"
></image>
<text>美股</text>
</view>
</view>
<view class="content">
<view class="contentTitle">
<view class="contentTitle_name">股票名称</view>
<view class="contentTitle_close">最新收盘价</view>
<view class="contentTitle_price">选股价格</view>
</view>
<view class="contentItem">
<view class="row" v-for="(item, index) in stockDataByName" :key="index">
<view class="nameItem">{{ item.tscode }}</view>
<view class="closeItem">{{ item.close }}</view>
<view class="priceItem">{{ item.preClose }}</view>
</view>
</view>
</view>
</view>
</view>
</view>
<footerBar class="static-footer" :type="type"></footerBar>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
import footerBar from '@/components/footerBar.vue'
import deepExploration_header from '@/components/deepExploration_header.vue'
import { stocSelectApi, stocSelectByNameApi } from '@/api/deepExploration/deepExploration.js'
const type = ref("deepExploration");
const iSMT = ref(0);
//
const toMain = (val) => {
if (val == "主力追踪") {
uni.navigateTo({
url: "/pages/deepExploration/MainForceActions?index=1",
});
} else if (val == "主力雷达") {
uni.navigateTo({
url: "/pages/deepExploration/MainForceActions?index=2",
});
} else if (val == "主力解码") {
uni.navigateTo({
url: "/pages/deepExploration/MainForceActions?index=3",
});
} else if (val == "主力资金流") {
uni.navigateTo({
url: "/pages/deepExploration/MainForceActions?index=4",
});
}
};
const stockName = ref("");
//
const searchStock = () => {
console.log("搜索参数:", stockName.value);
uni.navigateTo({
url: `/pages/deepExploration/MainForceActions?stockName=${stockName.value}`,
});
};
//
const viewAll = () => {
uni.navigateTo({
url: '/pages/deepExploration/stockSelectDetail'
})
}
//
const stockData = ref([]);
const stockDataByName = ref([]); //
//
const loadStockSelection = async () => {
try {
const res = await stocSelectApi({ language: 'cn', size: 3 })
// console.log(':', typeof res === 'object' ? JSON.stringify(res) : res)
const raw = res?.data
const listCandidates = [
raw?.list,
raw?.data?.list,
raw?.data?.rows,
raw?.rows,
Array.isArray(raw) ? raw : null
].filter(Array.isArray)
let list = listCandidates.length ? listCandidates[0] : []
//
if ((!Array.isArray(list) || !list.length) && raw && typeof raw === 'object' && !Array.isArray(raw)) {
const arrays = Object.values(raw).filter(Array.isArray)
if (arrays.length) {
list = arrays.flat()
}
}
if (Array.isArray(list) && list.length) {
const mapped = list.map(item => ({
tscode: item.tsCode ?? item.tscode ?? item.code ?? '',
close: item.close ?? item.lastClose ?? '',
preClose: item.preClose ?? item.preclose ?? item.prevClose ?? ''
}))
stockData.value = mapped.slice(0, 3)
console.log('选股策略列表长度:', stockData.value.length, '首项:', stockData.value[0])
} else {
console.warn('选股策略接口返回空列表或结构不匹配', raw)
}
} catch (e) {
console.error('选股策略接口调用失败', e)
uni.showToast({ title: '选股策略加载失败', icon: 'none' })
}
}
//
const loadStockSelectionByName = async () => {
try {
const res = await stocSelectByNameApi({ name: '安徽' })
const raw = res?.data
const dataObj = raw?.data || raw
let list = []
if (Array.isArray(dataObj)) {
list = dataObj
} else if (dataObj && typeof dataObj === 'object') {
const target = dataObj['安徽']
if (Array.isArray(target)) {
list = target
} else {
const firstArr = Object.values(dataObj).find(v => Array.isArray(v))
if (Array.isArray(firstArr)) list = firstArr
}
}
if (Array.isArray(list) && list.length) {
const mapped = list.map(item => ({
tscode: item.tsCode ?? item.tscode ?? item.code ?? '',
close: item.close ?? item.lastClose ?? '',
preClose: item.preClose ?? item.preclose ?? item.prevClose ?? ''
}))
stockDataByName.value = mapped.slice(0, 3)
console.log('安徽板块列表长度:', stockDataByName.value.length, '首项:', stockDataByName.value[0])
} else {
console.warn('按名称(安徽)接口返回空列表或结构不匹配', raw)
}
} catch (e) {
console.error('按名称(安徽)接口调用失败', e)
uni.showToast({ title: '安徽板块加载失败', icon: 'none' })
}
}
onMounted(() => {
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
//
loadStockSelection()
loadStockSelectionByName() //
})
</script>
<style scoped lang="scss">
.main {
width: 100%;
min-height: 100vh; //
height: auto; //
overflow-y: auto; //
background-color: #fff;
padding-bottom: 120rpx; //
.search {
position: relative;
display: flex;
align-items: center;
background-color: #f3f3f3;
width: calc(100% - 60rpx);
height: 80rpx;
border-radius: 50rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
padding: 0 40rpx;
margin: 15rpx 30rpx 0 30rpx;
.seachIcon {
position: absolute;
right: 50rpx;
width: 32rpx;
height: 32rpx;
}
.searchInput {
color: #111;
}
}
.select {
display: flex;
padding: 60rpx 10rpx 30rpx 30rpx;
gap: 70rpx;
align-items: center;
justify-content: center;
.selectItem {
.img {
width: 80rpx;
height: 80rpx;
display: block;
margin: 0 auto;
}
.txt {
color: #6a6a6a;
font-family: "PingFang SC";
font-size: 11px;
font-style: normal;
font-weight: 400;
line-height: 14.5px;
margin-top: 13rpx;
white-space: nowrap;
}
}
}
.gap {
width: 100%;
height: 15rpx;
background-color: #f3f3f3;
}
.stockSelection {
width: 100%;
padding: 32rpx 15rpx;
.stockSelection_top {
display: flex;
justify-content: space-between;
.txt {
color: #000000;
font-family: "PingFang SC";
font-size: 38rpx;
font-style: normal;
font-weight: 400;
line-height: 50rpx;
}
.viewAll {
background-color: #000000;
border-radius: 10rpx;
padding: 6rpx 20rpx;
color: #ffffff;
font-family: "PingFang SC";
font-size: 10rpx;
font-style: normal;
font-weight: 100;
line-height: 29rpx;
height: 40rpx;
}
}
.stockSelection_content {
.selectionItem {
background-color: #f3f3f3;
padding: 30rpx 15rpx 17rpx 30rpx;
border-radius: 30rpx;
margin-top: 30rpx;
.header {
display: flex;
justify-content: space-between;
align-items: center;
.left {
display: flex;
justify-content: space-between;
align-items: center;
image {
display: flex;
justify-content: center;
align-items: center;
width: 15rpx;
height: 15rpx;
}
text {
margin-left: 15rpx;
color: #000000;
font-family: "PingFang SC";
font-size: 28rpx;
font-style: normal;
font-weight: 400;
line-height: 18.5px;
}
}
.right {
display: flex;
justify-content: space-between;
align-items: center;
border-radius: 15rpx;
background-color: #ffffff;
padding: 6rpx 20rpx;
image {
display: flex;
justify-content: center;
align-items: center;
width: 40rpx;
height: 26.5rpx;
}
text {
margin-left: 10rpx;
color: #6a6a6a;
font-family: "PingFang SC";
font-size: 18rpx;
font-style: normal;
font-weight: 400;
line-height: 24rpx;
}
}
}
.content {
.contentTitle {
display: flex;
color: #6a6a6a;
font-family: "PingFang SC";
font-size: 11px;
font-style: normal;
font-weight: 400;
line-height: 14.5px;
margin-top: 24rpx;
margin-bottom: 20rpx;
.contentTitle_name {
width: 100rpx;
}
.contentTitle_close {
width: 130rpx;
margin-left: 260rpx;
}
.contentTitle_price {
width: 120rpx;
margin-left: 60rpx;
}
}
.contentItem {
.row {
display: flex;
box-shadow: 0 -2rpx 5rpx rgba(0, 0, 0, 0.05);
padding: 10rpx 0;
margin-bottom: 10rpx;
.nameItem {
width: 260rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #000000;
font-family: "PingFang SC";
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 17.5px;
}
.closeItem {
width: 120rpx;
margin-left: 100rpx;
color: #25ba5d;
font-family: "PingFang SC";
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 17.5px;
}
.priceItem {
width: 120rpx;
margin-left: 73rpx;
color: #25ba5d;
font-family: "PingFang SC";
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 17.5px;
}
}
}
}
}
}
}
.static-footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
}
}
* {
box-sizing: border-box;
}
</style>

388
pages/deepExploration/stockSelectDetail.vue

@ -0,0 +1,388 @@
<template>
<view class="main">
<view class="table">
<view class="tableHeader">
<scroll-view class="tabs" scroll-x="true">
<view v-for="(item,index) in tabsData" :key="index" :class="['tabItem', { 'tabItem-active': item === activeTab }]" @click="handleTab(item)">
{{item}}
</view>
</scroll-view>
</view>
<view class="tableContent">
<image class="showAll" src="/static/deepExploration-images/showAll.png" mode="aspectFill"></image>
<scroll-view scroll-x="true" show-scrollbar="false">
<view class="tableBox">
<view class="box_header">
<view class="name">名称</view>
<view class="other" v-for="(item,index) in tableContentHeaderData" :key="index">
<text>{{item}}</text>
<image v-show="ifASC" src="/static/deepExploration-images/ASC.png" mode="aspectFill">
</image>
<image v-show="!ifASC" src="/static/deepExploration-images/DESC.png" mode="aspectFill">
</image>
</view>
</view>
<view class="box_content">
<view class="row" v-for="(item,index) in strategyData" :key="index" :class="{ 'increase-positive': item.increase.startsWith('+'),
'increase-negative': item.increase.startsWith('-')}">
<view class="name_colum">
<text class="stockName">{{item.name}}</text>
<text class="stockCode">{{item.stockCode}}</text>
</view>
<view class="other_colum">{{item.latest}}</view>
<view class="other_colum">{{item.increase}}</view>
<view class="other_colum">{{item.decrease}}</view>
<view class="other_colum">{{item.previousClose}}</view>
<view class="other_colum">{{item.volume}}</view>
<view class="other_colum">{{item.turnover}}</view>
<view class="other_colum">{{item.openingPrice}}</view>
<view class="other_colum">{{item.highestPrice}}</view>
<view class="other_colum">{{item.lowestPrice}}</view>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { stocSelectApi, stocSelectByNameApi } from '@/api/deepExploration/deepExploration.js'
import { useUserStore } from '@/stores/modules/userInfo.js'
import { useDeviceStore } from '@/stores/modules/deviceInfo.js'
import { LoginApi } from '@/api/start/login.js'
const tabsData = ref(['全部', '抄底卖顶', '波段行情', '价值投资', '资金及仓位管理', ])
const activeTab = ref('全部')
//
const handleTab = async (item) => {
activeTab.value = item
const nameMap = {
'抄底卖顶': '北京',
'波段行情': '安徽',
'价值投资': '重庆',
'资金及仓位管理': '黑龙江'
}
if (item === '全部') {
await loadStrategy()
uni.showToast({ title: `查看 ${item} 详情`, icon: 'none', duration: 1500 })
return
}
const apiName = nameMap[item]
if (apiName) {
await loadByName(apiName)
uni.showToast({ title: `${item}数据已更新`, icon: 'none', duration: 1500 })
} else {
uni.showToast({ title: `暂不支持:${item}`, icon: 'none' })
}
}
//
const ifASC = ref(true)
//
const tableContentHeaderData = ref(['最新', '涨幅', '涨跌', '昨收', '成交量', '成交额', '开盘价', '最高价', '最低价'])
//
const strategyData = ref([])
// token
const ensureAuth = async () => {
const userStore = useUserStore()
if (userStore.userInfo?.token) return
try {
const deviceStore = useDeviceStore()
let deviceId = deviceStore.deviceInfo?.deviceId
if (!deviceId) {
const cached = uni.getStorageSync('deviceId')
deviceId = cached || `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`
uni.setStorageSync('deviceId', deviceId)
deviceStore.setDeviceInfo({ ...(deviceStore.deviceInfo || {}), deviceId })
}
const res = await LoginApi({
loginType: 'VISITOR',
account: deviceId,
useCode: false
})
if (res?.code === 200 && res?.data?.token) {
userStore.setUserInfo(res.data)
console.log('游客登录成功,token=', res.data.token)
} else {
console.warn('游客登录失败', res)
}
} catch (err) {
console.warn('游客登录异常', err)
}
}
const formatPctChg = (val) => {
if (val === null || val === undefined || val === '') return ''
const num = Number(val)
if (!isFinite(num)) return String(val)
const sign = num > 0 ? '+' : ''
return `${sign}${num.toFixed(2)}%`
}
// ( pctChg )
const sortByPctDesc = (arr) => arr.sort((a, b) => (Number(b.pctChg) || -Infinity) - (Number(a.pctChg) || -Infinity))
// ///
const loadByName = async (apiName) => {
try {
const userStore = useUserStore()
if (!userStore.userInfo?.token) {
await ensureAuth()
}
const token = useUserStore().userInfo?.token
const res = await stocSelectByNameApi({ name: apiName, token })
const raw = res?.data
const dataObj = raw?.data || raw
let list = []
if (dataObj && typeof dataObj === 'object' && !Array.isArray(dataObj)) {
const target = dataObj[apiName]
if (Array.isArray(target)) list = target
else {
const firstArr = Object.values(dataObj).find(v => Array.isArray(v))
if (Array.isArray(firstArr)) list = firstArr
}
}
if ((!Array.isArray(list) || !list.length) && Array.isArray(raw)) {
list = raw
}
//
if (Array.isArray(list)) list = sortByPctDesc(list)
if (Array.isArray(list) && list.length) {
strategyData.value = list.map(item => ({
name: item.tsCode ?? item.tscode ?? '',
stockCode: item.tsCode ?? item.tscode ?? '',
latest: item.close ?? '',
increase: formatPctChg(item.pctChg),
decrease: item.change ?? '',
previousClose: item.preClose ?? item.preclose ?? '',
volume: item.vol ?? '',
turnover: item.amount ?? '',
openingPrice: item.open ?? '',
highestPrice: item.high ?? '',
lowestPrice: item.low ?? ''
}))
console.log(`按名称(${apiName})加载成功,条数:`, strategyData.value.length, '首项:', strategyData.value[0])
} else {
console.warn('getStrategyByName 返回空列表或结构不匹配', raw)
}
} catch (e) {
console.error('getStrategyByName 接口调用失败', e)
uni.showToast({ title: '按名称加载失败', icon: 'none' })
}
}
const loadStrategy = async () => {
try {
const userStore = useUserStore()
if (!userStore.userInfo?.token) {
await ensureAuth()
}
const token = useUserStore().userInfo?.token
const res = await stocSelectApi({ language: 'cn', token })
const raw = res?.data
const listCandidates = [
raw?.list,
raw?.data?.list,
raw?.data?.rows,
raw?.rows,
Array.isArray(raw) ? raw : null
].filter(Array.isArray)
let list = listCandidates.length ? listCandidates[0] : []
if ((!Array.isArray(list) || !list.length) && raw && typeof raw === 'object' && !Array.isArray(raw)) {
const arrays = Object.values(raw).filter(Array.isArray)
if (arrays.length) list = arrays.flat()
}
//
if (Array.isArray(list)) list = sortByPctDesc(list)
if (Array.isArray(list) && list.length) {
strategyData.value = list.map(item => ({
name: item.tsCode ?? item.tscode ?? '',
stockCode: item.tsCode ?? item.tscode ?? '',
latest: item.close ?? '',
increase: formatPctChg(item.pctChg),
decrease: item.change ?? '',
previousClose: item.preClose ?? item.preclose ?? '',
volume: item.vol ?? '',
turnover: item.amount ?? '',
openingPrice: item.open ?? '',
highestPrice: item.high ?? '',
lowestPrice: item.low ?? ''
}))
console.log('stockSelectDetail 加载成功(已按涨幅降序),条数:', strategyData.value.length, '首项:', strategyData.value[0])
} else {
console.warn('stockSelectDetail 接口返回空列表或结构不匹配', raw)
}
} catch (e) {
console.error('stockSelectDetail 接口调用失败', e)
uni.showToast({ title: '选股策略详情加载失败', icon: 'none' })
}
}
onMounted(() => {
loadStrategy()
})
</script>
<style scoped lang="scss">
.main {
width: 100%;
height: 100vh;
background-color: #fff;
.table {
margin-top: 10rpx;
box-shadow: 0 -2rpx 3rpx -1rpx rgba(0, 0, 0, 0.5);
.tableHeader {
.tabs {
white-space: nowrap;
padding-top: 20rpx;
padding-left: 40rpx;
::-webkit-scrollbar {
//
display: none;
}
.tabItem {
display: inline-block;
color: rgb(175, 175, 175);
border-radius: 10rpx;
padding: 5rpx 30rpx;
margin-right: 20rpx;
font-size: 28rpx;
background-color: rgb(243, 243, 243);
}
.tabItem-active {
background-color: #DB1F1D; //
color: #fff;
}
}
}
.tableContent {
width: 100%;
background-color: #fff;
position: relative;
.showAll {
position: absolute;
top: 35rpx;
right: 20rpx;
width: 40rpx;
height: 40rpx;
z-index: 100;
}
scroll-view {
width: 100%;
white-space: nowrap;
::-webkit-scrollbar {
//
display: none;
}
}
.tableBox {
padding-left: 40rpx;
.box_header {
margin-bottom: 19rpx;
display: flex;
width: max-content;
margin-top: 40rpx;
color: rgb(109, 109, 109);
border-radius: 10rpx;
margin-right: 20rpx;
font-size: 23rpx;
.name {
flex: 0 0 375rpx;
}
.other {
flex: 0 0 195rpx;
text {
margin-right: 5rpx;
}
image {
width: 20rpx;
height: 20rpx;
}
}
}
.box_content {
width: max-content;
.row {
padding: 5rpx;
display: flex;
border-top: 1rpx dashed #eee;
width: 210%;
&.increase-positive {
.other_colum {
color: #2DD357;
font-weight: 200;
}
}
&.increase-negative {
.other_colum {
color: #FF4150;
font-weight: 200;
}
}
.name_colum {
flex: 0 0 375rpx;
display: flex;
flex-direction: column;
gap: 4rpx;
.stockName {
color: #333333;
width: 100%;
max-width: 305rpx;
font-size: 28rpx;
font-weight: 400;
line-height: 36rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.stockCode {
color: #c5c5c5;
;
font-size: 24rpx;
font-weight: 400;
line-height: 30rpx;
}
}
.other_colum {
flex: 0 0 195rpx;
display: flex;
align-items: center;
}
}
}
}
}
}
}
</style>

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

28
pages/home/deepExploration.vue

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

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

28
pages/home/marketSituation.vue

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

300
pages/home/member.vue

@ -1,28 +1,306 @@
<template> <template>
<view class="main">
<!-- 顶部状态栏占位 -->
<view class="top" :style="{height:iSMT+'px'}"></view>
<view>我的</view>
<footerBar class="static-footer" :type="type"></footerBar>
</view>
<LoginPrompt ref="loginPrompt"></LoginPrompt>
<view class="main">
<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>
</view>
</template> </template>
<script setup> <script setup>
import { ref,onMounted } from 'vue'
import {
ref,
onMounted
} from 'vue'
import {
ArrowRight
} from '@element-plus/icons-vue'
import footerBar from '../../components/footerBar.vue' import footerBar from '../../components/footerBar.vue'
import {getUserInfo} from "@/api/member"
const type = ref('member') const type = ref('member')
const iSMT = ref(0) const iSMT = ref(0)
const username = ref('')
const dccode = ref('')
const userInfoRes = ref()//
userInfoRes.value = getUserInfo()
userInfoRes.value.then(res => {
username.value = res.data.username
dccode.value = res.data.dccode
console.log('用户信息', userInfoRes.value)
})
const goToGeneral = () => {
uni.navigateTo({
url: '/pages/setting/general'
})
}
const goToMarket = () => {
uni.navigateTo({
url: '../setting/market'
})
}
const goToAccount = () => {
uni.navigateTo({
url: '../setting/account'
})
}
const goToNewVersion = () => {
uni.navigateTo({
url: '../setting/newVersion'
})
}
const goToAbout = () => {
uni.navigateTo({
url: '../setting/about'
})
}
const goToShare = () => {
uni.navigateTo({
url: '../setting/share'
})
}
onMounted(() => { onMounted(() => {
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight
console.log('??????????????', iSMT.value)
}) })
</script> </script>
<style scoped> <style scoped>
.static-footer { .static-footer {
position: fixed;
bottom: 0;
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>

477
pages/marketSituation/countryMarket.vue

@ -0,0 +1,477 @@
<!-- @format -->
<template>
<view class="content">
<!-- 市场子Tab -->
<view class="sub_tabs">
<view v-for="(tab, i) in marketTabs" :key="tab" :class="['tab_item', i === activeTabIndex ? 'active' : '']" @click="switchTab(i)">
<text>{{ tab }}</text>
</view>
</view>
<!-- 大盘指数 -->
<view class="section" v-if="activeTabIndex === 0">
<view class="section_header">
<text class="section_title">大盘指数</text>
<text class="section_action" @click="viewMore('indices')">查看更多 ></text>
</view>
<view class="indices_grid">
<view v-for="(index, i) in countryInfo" :key="i" class="index_item">
<IndexCard :market="index.market" :stockName="index.name" :currentPrice="index.price" :changeAmount="index.change" :changePercent="index.changePercent" :isRising="index.isRising" @click="viewIndexDetail(index,i)"/>
</view>
</view>
<!-- 今日市场情绪温度 -->
<view class="sentiment">
<view class="section_subtitle">
<text>今日市场情绪温度</text>
</view>
<view class="meters">
<view class="meter_item" v-for="(m, i) in sentimentMeters" :key="i">
<image class="meter_icon" :class="m.theme" :src="selectIcons[m.theme]" mode="aspectFit"></image>
<view class="meter_info">
<text class="meter_value" :class="m.theme">{{ m.value }}°C</text>
<text class="meter_label" :class="m.theme">{{ m.label }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 板块 -->
<view class="section" v-if="activeTabIndex === 1">
<view class="section_header">
<text class="section_title">板块</text>
<text class="section_action" @click="viewMore('sectors')">查看更多 ></text>
</view>
<view class="sectors_grid">
<view v-for="(sec, i) in countryInfo" :key="i" class="sector_item">
<view class="sector_header">
<text class="sector_name">{{ sec.name }}</text>
<text :class="['sector_change', sec.isRising ? 'rising' : 'falling']">
{{ sec.change }}
</text>
</view>
<view class="sector_price">{{ sec.price }}</view>
</view>
</view>
</view>
<!-- 股票 -->
<view class="section" v-if="activeTabIndex === 2">
<view class="section_header">
<text class="section_title">股票</text>
<text class="section_action" @click="viewMore('stocks')">查看更多 ></text>
</view>
<view class="table">
<view class="table_header">
<text class="cell name">名称</text>
<text class="cell price">最新</text>
<text class="cell change">涨幅</text>
</view>
<view class="table_row" v-for="(stk, i) in countryInfo" :key="i">
<view class="cell name">
<text class="stk_name">{{ stk.name }}</text>
<text class="stk_code">{{ stk.code }}</text>
</view>
<view class="cell price">
<text class="stk_price">{{ stk.price }}</text>
</view>
<view class="cell change">
<text :class="['stk_change', stk.isRising ? 'rising' : 'falling']">
{{ stk.change }}
</text>
</view>
</view>
</view>
</view>
<!-- 底部安全区域 -->
<view class="bottom_safe_area"></view>
</view>
</template>
<script setup>
import { ref, computed, onMounted, watch } from "vue";
import IndexCard from "../../components/IndexCard.vue";
import { queryStockDataAPI } from "@/api/marketSituation/marketSituation";
import { useMarketSituationStore } from "../../stores/modules/marketSituation.js";
const marketSituationStore = useMarketSituationStore();
onMounted(() => {
switchTab(0);
});
// Tab
// const marketTabs = ["", "", "", ""];
const activeTabIndex = ref(0);
const switchTab = (i) => {
activeTabIndex.value = i;
queryStockDataAPI({
parentId: props.countryId,
tradeId: activeTabIndex.value+1,
}).then((res) => {
if (res.code === 200) {
countryInfo.value = res.data.dataPage.records;
marketSituationStore.countryMarketCardData = countryInfo.value.map((item) => ({
market: item.market,
stockCode: item.code,
stockName: item.name,
}));
console.log(res.data)
console.log(res.data.dataPage.records)
console.log(countryInfo.value);
}
});
};
//
const sentimentMeters = [
{ value: 90, label: "道琼斯", theme: "hot" },
{ value: 60, label: "纳斯达克", theme: "warm" },
{ value: 20, label: "标普500", theme: "cool" },
];
//
const selectIcons = {
hot: "/static/marketSituation-image/hot.png",
warm: "/static/marketSituation-image/warm.png",
cool: "/static/marketSituation-image/cool.png",
};
// Props
const props = defineProps({
countryId: {
type: Number,
required: true,
},
marketTabs: {
type: Array,
},
tabData: {
type: Object,
default: null
},
});
//
const countryInfo = ref('')
//
const viewIndexDetail = (item, index) => {
console.log("查看指数详情:", item);
uni.navigateTo({
url: `/pages/marketSituation/marketCondition?stockInformation=${encodeURIComponent(JSON.stringify(item))}&index=${index}&from=countryMarket`,
});
};
//
const handleTabData = (tabData) => {
if (tabData && tabData.type === 'country' && tabData.data) {
if (tabData.data.dataPage && tabData.data.dataPage.records) {
countryInfo.value = tabData.data.dataPage.records;
marketSituationStore.countryMarketCardData = countryInfo.value.map((item) => ({
market: item.market,
stockCode: item.code,
stockName: item.name,
}));
console.log('countryMarket接收到数据:', countryInfo.value);
}
}
};
// tabData
watch(() => props.tabData, (newTabData) => {
if (newTabData) {
handleTabData(newTabData);
}
}, { immediate: true });
//
const viewMore = (type) => {
};
</script>
<style scoped>
.content {
padding: 0 20rpx 20rpx 20rpx;
}
/* 子Tab */
.sub_tabs {
display: flex;
gap: 16rpx;
padding: 0 20rpx 20rpx 20rpx;
}
.tab_item {
padding: 6rpx 20rpx;
border-radius: 5rpx;
background: #f5f5f5;
color: #666;
font-size: 24rpx;
}
.tab_item.active {
background: #ff4444;
color: #fff;
}
.section {
padding: 20rpx;
border-radius: 16rpx;
}
.section_header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
}
.section_title {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.section_action {
font-size: 24rpx;
color: #999;
}
.indices_grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
/* 情绪温度 */
.sentiment {
background-color: #f6f6f6;
padding: 0 20rpx 20rpx 20rpx;
}
.section_subtitle {
font-size: 24rpx;
color: #000000;
padding: 20rpx 0;
}
.meters {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16rpx;
}
.meter_item {
display: flex;
align-items: center;
/* padding: 10rpx; */
background: #ffffff;
border-radius: 12rpx;
}
.meter_item image {
width: 100rpx;
height: 100rpx;
}
.meter_info {
display: flex;
flex-direction: column;
}
.meter_value {
font-size: 36rpx;
}
.meter_value.hot {
color: #ff6b6b;
}
.meter_value.warm {
color: #ffd166;
}
.meter_value.cool {
color: #60a5fa;
}
.meter_label {
font-size: 22rpx;
}
.meter_label.hot {
color: #ff6b6b;
}
.meter_label.warm {
color: #ffd166;
}
.meter_label.cool {
color: #60a5fa;
}
/* 板块 */
.sectors_grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16rpx;
}
.sector_item {
background: #fafafa;
border-radius: 12rpx;
padding: 16rpx;
}
.sector_header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12rpx;
}
.sector_name {
font-size: 24rpx;
color: #333;
}
.sector_change {
font-size: 22rpx;
}
.sector_change.rising {
color: #e74c3c;
}
.sector_change.falling {
color: #27ae60;
}
.sector_price {
font-size: 24rpx;
color: #666;
}
/* 股票表 */
.table {
background: #fff;
border-radius: 12rpx;
overflow: hidden;
}
.table_header,
.table_row {
display: grid;
grid-template-columns: 2fr 1fr 1fr;
padding: 18rpx 20rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.table_header {
background: #fafafa;
}
.cell.name {
display: flex;
flex-direction: column;
}
.stk_name {
font-size: 26rpx;
color: #333;
}
.stk_code {
font-size: 22rpx;
color: #999;
}
.stk_price {
font-size: 26rpx;
color: #333;
}
.stk_change {
font-size: 24rpx;
}
.stk_change.rising {
color: #e74c3c;
}
.stk_change.falling {
color: #27ae60;
}
.index_item {
background: #fff;
border-radius: 12rpx;
overflow: hidden;
}
.hot_stocks {
margin-bottom: 30rpx;
}
.stocks_list {
background: #fff;
border-radius: 12rpx;
overflow: hidden;
}
.stock_item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 20rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.stock_item:last-child {
border-bottom: none;
}
.stock_info {
flex: 1;
}
.stock_name {
font-size: 28rpx;
color: #333;
display: block;
margin-bottom: 8rpx;
}
.stock_code {
font-size: 24rpx;
color: #999;
}
.stock_price {
text-align: right;
}
.price {
font-size: 28rpx;
color: #333;
display: block;
margin-bottom: 8rpx;
}
.change {
font-size: 24rpx;
}
.change.rising {
color: #e74c3c;
}
.change.falling {
color: #27ae60;
}
.bottom_safe_area {
height: 120rpx;
}
</style>

301
pages/marketSituation/forexMetals.vue

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

859
pages/marketSituation/globalIndex.vue

@ -0,0 +1,859 @@
<!-- @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" v-for="(item, parentIndex) in marketSituationStore.gloablCardData" :key="item">
<view class="market-header">
<text class="market-title">{{ item.ac }}</text>
<view class="market-more" @click="viewMore(item.ac)">
<text class="more-text">查看更多</text>
<text class="more-arrow">></text>
</view>
</view>
<view class="cards-grid-three">
<view v-for="(iitem, index) in item.list" :key="iitem" class="card-item">
<IndexCard
:market="iitem.market"
:stockName="iitem.name"
:currentPrice="iitem.currentPrice"
:changeAmount="iitem.changeAmount"
:changePercent="iitem.changePercent"
:isRising="iitem.isRising"
@click="viewIndexDetail(iitem, parentIndex, index)"
/>
</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, onUnmounted, computed, nextTick, watch } from "vue";
import footerBar from "../../components/footerBar.vue";
import IndexCard from "../../components/IndexCard.vue";
import { getRegionalGroupAPI } from "../../api/marketSituation/marketSituation.js";
import { useMarketSituationStore } from "../../stores/modules/marketSituation.js";
const marketSituationStore = useMarketSituationStore();
//
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 globalIndexArray = ref([]);
// -
const asiachinaIndexes = ref([
{
flagIcon: "/static/c1.png",
stockName: "上证指数",
stockCode: "noCode",
currentPrice: "3933.96",
changeAmount: "+24.32",
changePercent: "+0.62%",
isRising: true,
},
{
flagIcon: "/static/c2.png",
stockName: "深证成指",
stockCode: "noCode",
currentPrice: "45757.90",
changeAmount: "-123.45",
changePercent: "-0.27%",
isRising: false,
},
{
flagIcon: "/static/c3.png",
stockName: "创业板指",
stockCode: "noCode",
currentPrice: "6606.08",
changeAmount: "+89.76",
changePercent: "+1.38%",
isRising: true,
},
{
flagIcon: "/static/c4.png",
stockName: "HSI50",
stockCode: "noCode",
currentPrice: "22333.96",
changeAmount: "+156.78",
changePercent: "+0.71%",
isRising: true,
},
{
flagIcon: "/static/c5.png",
stockName: "沪深300",
stockCode: "noCode",
currentPrice: "45757.90",
changeAmount: "-89.12",
changePercent: "-0.19%",
isRising: false,
},
{
flagIcon: "/static/c6.png",
stockName: "上证50",
stockCode: "noCode",
currentPrice: "45757.90",
changeAmount: "+234.56",
changePercent: "+0.52%",
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/marketSituation/marketDetail?market=${market}`,
});
};
//
const viewIndexDetail = (item, parentIndex, index) => {
console.log("查看指数详情:", item.stockName);
// uni.showToast({
// title: ` ${item.stockName} `,
// icon: 'none',
// duration: 2000
// })
//
uni.navigateTo({
url: `/pages/marketSituation/marketCondition?stockInformation=${encodeURIComponent(JSON.stringify(item))}&parentIndex=${parentIndex}&index=${index}&from=globalIndex`,
});
};
const getRegionalGroup = async () => {
try {
const result = await getRegionalGroupAPI();
globalIndexArray.value = result.data;
marketSituationStore.gloablCardData = result.data;
} catch (e) {
console.log("获取区域指数失败", e);
}
};
// TCP
import tcpConnection, { TCPConnection, TCP_CONFIG } from "@/api/tcpConnection.js";
const tcpConnected = ref(false);
const connectionListener = ref(null);
const messageListener = ref(null);
// TCP
const initTcpListeners = () => {
//
connectionListener.value = (status, result) => {
tcpConnected.value = status === "connected";
console.log("TCP连接状态变化:", status, tcpConnected.value);
//
//
if (status === "connected") {
sendTcpMessage("batch_real_time");
}
};
//
messageListener.value = (type, message, parsedArray) => {
const messageObj = {
type: type,
content: message,
parsedArray: parsedArray,
timestamp: new Date().toLocaleTimeString(),
direction: "received",
};
//
parseStockData(message);
};
//
tcpConnection.onConnectionChange(connectionListener.value);
tcpConnection.onMessage(messageListener.value);
};
// TCP
const connectTcp = () => {
console.log("开始连接TCP服务器...");
tcpConnection.connect();
};
// TCP
const disconnectTcp = () => {
console.log("断开TCP连接...");
tcpConnection.disconnect();
tcpConnected.value = false;
};
// TCP
const sendTcpMessage = (command) => {
let messageData;
let messageDataArray = [];
if (command == "batch_real_time") {
for (let i = 0; i < globalIndexArray.value.length; ++i) {
for (let j = 0; j < globalIndexArray.value[i].list.length; ++j) {
messageDataArray.push(globalIndexArray.value[i].list[j].code);
}
}
}
console.log(messageDataArray);
switch (command) {
//
case "real_time":
messageData = {
command: "real_time",
stock_code: "SH.000001",
};
break;
//
case "init_real_time":
messageData = {
command: "init_real_time",
stock_code: "SH.000001",
};
break;
case "stop_real_time":
messageData = {
command: "stop_real_time",
};
break;
//
case "stock_list":
messageData = {
command: "stock_list",
};
break;
case "batch_real_time":
messageData = {
command: "batch_real_time",
stock_codes: messageDataArray,
};
break;
case "help":
messageData = {
command: "help",
};
break;
}
if (!messageData) {
return;
} else {
try {
//
const success = tcpConnection.send(messageData);
if (success) {
console.log("home发送TCP消息:", messageData);
}
} catch (error) {
console.error("发送TCP消息时出错:", error);
}
}
};
// TCP
const getTcpStatus = () => {
const status = tcpConnection.getConnectionStatus();
uni.showModal({
title: "TCP连接状态",
content: `当前状态: ${status ? "已连接" : "未连接"}`,
showCancel: false,
});
};
let isMorePacket = {
init_batch_real_time: false,
batch_real_time: false,
};
let receivedMessage;
// TCP
const parseStockData = (message) => {
try {
console.log("进入parseStockData, message类型:", typeof message);
let parsedMessage;
// isMorePackettrue
// message{JSON
//
if (message.includes("欢迎连接到股票数据服务器")) {
console.log("服务器命令列表,不予处理");
return;
}
if ((typeof message === "string" && message.includes("batch_data_start")) || isMorePacket.init_batch_real_time) {
if (typeof message === "string" && message.includes("batch_data_start")) {
console.log("开始接受分包数据");
receivedMessage = "";
} else {
console.log("接收分包数据过程中");
}
isMorePacket.init_batch_real_time = true;
receivedMessage += message;
// }JSON
if (receivedMessage.includes("batch_data_complete")) {
console.log("接受分包数据结束");
isMorePacket.init_batch_real_time = false;
console.log("展示数据", receivedMessage);
let startIndex = 0;
let startCount = 0;
let endIndex = receivedMessage.indexOf("batch_data_complete");
for (let i = 0; i < receivedMessage.length; ++i) {
if (receivedMessage[i] == "{") {
startCount++;
if (startCount == 2) {
startIndex = i;
break;
}
}
}
for (let i = receivedMessage.indexOf("batch_data_complete"); i >= 0; --i) {
if (receivedMessage[i] == "}" || startIndex == endIndex) {
endIndex = i;
break;
}
}
if (startIndex >= endIndex) {
throw new Error("JSON字符串格式错误");
}
console.log("message", startIndex, endIndex, receivedMessage[endIndex], receivedMessage[startIndex]);
parsedMessage = JSON.parse(receivedMessage.substring(startIndex, endIndex + 1));
console.log("JSON解析成功,解析后类型:", typeof parsedMessage, parsedMessage);
const stockDataArray = parsedMessage.data;
for (let i = 0; i < globalIndexArray.value.length; ++i) {
for (let j = 0; j < globalIndexArray.value[i].list.length; ++j) {
const stockCode = globalIndexArray.value[i].list[j].code;
marketSituationStore.gloablCardData[i].list[j].currentPrice = stockDataArray[stockCode][0].current_price.toFixed(2);
marketSituationStore.gloablCardData[i].list[j].changeAmount = (stockDataArray[stockCode][0].current_price - stockDataArray[stockCode][0].pre_close).toFixed(2);
marketSituationStore.gloablCardData[i].list[j].changePercent = ((100 * (stockDataArray[stockCode][0].current_price - stockDataArray[stockCode][0].pre_close)) / stockDataArray[stockCode][0].pre_close).toFixed(2) + "%";
marketSituationStore.gloablCardData[i].list[j].isRising = stockDataArray[stockCode][0].current_price - stockDataArray[stockCode][0].pre_close >= 0;
}
}
}
} else if ((typeof message === "string" && message.includes('{"count')) || isMorePacket.batch_real_time) {
if (typeof message === "string" && message.includes('{"count')) {
console.log("开始接受分包数据");
receivedMessage = "";
} else {
console.log("接收分包数据过程中");
}
isMorePacket.batch_real_time = true;
receivedMessage += message;
// }JSON
if (receivedMessage.includes("batch_realtime_data")) {
console.log("接受分包数据结束");
isMorePacket.batch_real_time = false;
console.log("展示数据", receivedMessage);
let startIndex = 0;
let endIndex = receivedMessage.length - 1;
for (let i = 0; i < receivedMessage.length; ++i) {
if (receivedMessage[i] == "{") {
startIndex = i;
break;
}
}
for (let i = receivedMessage.length - 1; i >= 0; --i) {
if (receivedMessage[i] == "}" || startIndex == endIndex) {
endIndex = i;
break;
}
}
if (startIndex >= endIndex) {
throw new Error("JSON字符串格式错误");
}
parsedMessage = JSON.parse(receivedMessage.substring(startIndex, endIndex + 1));
console.log("JSON解析成功,解析后类型:", typeof parsedMessage, parsedMessage);
const stockDataArray = parsedMessage.data;
for (let i = 0; i < globalIndexArray.value.length; ++i) {
for (let j = 0; j < globalIndexArray.value[i].list.length; ++j) {
const stockCode = globalIndexArray.value[i].list[j].code;
marketSituationStore.gloablCardData[i].list[j].currentPrice = stockDataArray[stockCode][0].current_price.toFixed(2);
marketSituationStore.gloablCardData[i].list[j].changeAmount = (stockDataArray[stockCode][0].current_price - stockDataArray[stockCode][0].pre_close).toFixed(2);
marketSituationStore.gloablCardData[i].list[j].changePercent = ((100 * (stockDataArray[stockCode][0].current_price - stockDataArray[stockCode][0].pre_close)) / stockDataArray[stockCode][0].pre_close).toFixed(2) + "%";
marketSituationStore.gloablCardData[i].list[j].isRising = stockDataArray[stockCode][0].current_price - stockDataArray[stockCode][0].pre_close >= 0;
}
}
}
} else {
// JSON
console.log("不是需要的数据,不做处理");
}
} catch (error) {
console.error("解析TCP股票数据失败:", error.message);
console.error("错误详情:", error);
}
};
// TCP
const removeTcpListeners = () => {
if (connectionListener.value) {
tcpConnection.removeConnectionListener(connectionListener.value);
connectionListener.value = null;
console.log("已移除TCP连接状态监听器");
}
if (messageListener.value) {
tcpConnection.removeMessageListener(messageListener.value);
messageListener.value = null;
console.log("已移除TCP消息监听器");
}
};
const startTcp = () => {
try {
removeTcpListeners();
disconnectTcp();
initTcpListeners();
connectTcp();
} catch (error) {
console.error("建立连接并设置监听出错:", error);
}
};
onUnmounted(() => {
sendTcpMessage("stop_real_time");
removeTcpListeners();
disconnectTcp();
});
//
onMounted(async () => {
await getRegionalGroup();
initTcpListeners();
await nextTick();
//
startTcp();
//
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>

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

780
pages/marketSituation/marketDetail.vue

@ -0,0 +1,780 @@
<!-- @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="(item,index) in sortedStockList" :key="item" @click="viewIndexDetail(item,index)">
<view class="stock-cell name-column">
<view class="stock-name">{{ item.stockName }}</view>
<view class="stock-code">{{ item.stockCode }}</view>
</view>
<view class="stock-cell price-column">
<text class="stock-price" :class="item.isRising ? 'rising' : 'falling'">
{{ typeof item.currentPrice === "number" ? item.currentPrice.toFixed(2) : item.currentPrice }}
</text>
</view>
<view class="stock-cell change-column">
<text class="stock-change" :class="item.isRising ? 'rising' : 'falling'">
{{ item.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, onUnmounted, nextTick, watch } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import footerBar from "@/components/footerBar.vue";
import { getRegionalGroupListAPI } from "../../api/marketSituation/marketSituation.js";
import { useMarketSituationStore } from "../../stores/modules/marketSituation.js";
const marketSituationStore = useMarketSituationStore();
//
const iSMT = ref(0);
const contentHeight = ref(0);
const headerHeight = ref(80);
const marketTitle = ref();
const sortType = ref(""); // 'price' 'change'
const sortOrder = ref("desc"); // 'asc' 'desc'
const regionalGroupArray = ref([]);
//
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,原始数据长度:", marketSituationStore.marketDetailCardData.length);
let list = [...marketSituationStore.marketDetailCardData];
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.changePercent.replace(/[+%-]/g, ""));
const bChange = parseFloat(b.changePercent.replace(/[+%-]/g, ""));
return sortOrder.value === "asc" ? aChange - bChange : bChange - aChange;
});
}
console.log("排序后数据长度:", list.length);
return list;
});
const getRegionalGroupList = async () => {
try {
const result = await getRegionalGroupListAPI({
name: marketTitle.value,
});
regionalGroupArray.value = result.data;
marketSituationStore.marketDetailCardData = result.data;
} catch (e) {
console.error("获取区域分组列表失败:", e);
}
};
//
const goBack = () => {
uni.navigateBack();
};
//
const viewIndexDetail = (item,index) => {
console.log("查看指数详情:", item.stockName);
//
uni.navigateTo({
url: `/pages/marketSituation/marketCondition?stockInformation=${encodeURIComponent(JSON.stringify(item))}&index=${index}&from=marketDetail`,
});
};
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";
}
};
// TCP
import tcpConnection, { TCPConnection, TCP_CONFIG } from "@/api/tcpConnection.js";
const tcpConnected = ref(false);
const connectionListener = ref(null);
const messageListener = ref(null);
// TCP
const initTcpListeners = () => {
//
connectionListener.value = (status, result) => {
tcpConnected.value = status === "connected";
console.log("TCP连接状态变化:", status, tcpConnected.value);
//
if (status === "connected") {
sendTcpMessage("batch_real_time");
}
};
//
messageListener.value = (type, message, parsedArray) => {
const messageObj = {
type: type,
content: message,
parsedArray: parsedArray,
timestamp: new Date().toLocaleTimeString(),
direction: "received",
};
//
parseStockData(message);
};
//
tcpConnection.onConnectionChange(connectionListener.value);
tcpConnection.onMessage(messageListener.value);
};
// TCP
const connectTcp = () => {
console.log("开始连接TCP服务器...");
tcpConnection.connect();
};
// TCP
const disconnectTcp = () => {
console.log("断开TCP连接...");
tcpConnection.disconnect();
tcpConnected.value = false;
};
// TCP
const sendTcpMessage = (command) => {
let messageData;
let messageDataArray = [];
if (command == "batch_real_time") {
messageDataArray = regionalGroupArray.value.map((item) => item.code);
}
console.log(messageDataArray);
switch (command) {
//
case "real_time":
messageData = {
command: "real_time",
stock_code: "SH.000001",
};
break;
//
case "init_real_time":
messageData = {
command: "init_real_time",
stock_code: "SH.000001",
};
break;
case "stop_real_time":
messageData = {
command: "stop_real_time",
};
break;
//
case "stock_list":
messageData = {
command: "stock_list",
};
break;
case "batch_real_time":
messageData = {
command: "batch_real_time",
stock_codes: messageDataArray,
};
break;
case "help":
messageData = {
command: "help",
};
break;
}
if (!messageData) {
return;
} else {
try {
//
const success = tcpConnection.send(messageData);
if (success) {
console.log("home发送TCP消息:", messageData);
}
} catch (error) {
console.error("发送TCP消息时出错:", error);
}
}
};
// TCP
const getTcpStatus = () => {
const status = tcpConnection.getConnectionStatus();
uni.showModal({
title: "TCP连接状态",
content: `当前状态: ${status ? "已连接" : "未连接"}`,
showCancel: false,
});
};
let isMorePacket = {
init_batch_real_time: false,
batch_real_time: false,
};
let receivedMessage;
// TCP
const parseStockData = (message) => {
try {
console.log("进入parseStockData, message类型:", typeof message);
let parsedMessage;
// isMorePackettrue
// message{JSON
//
if (message.includes("欢迎连接到股票数据服务器")) {
console.log("服务器命令列表,不予处理");
return;
}
if ((typeof message === "string" && message.includes("batch_data_start")) || isMorePacket.init_batch_real_time) {
if (typeof message === "string" && message.includes("batch_data_start")) {
console.log("开始接受分包数据");
receivedMessage = "";
} else {
console.log("接收分包数据过程中");
}
isMorePacket.init_batch_real_time = true;
receivedMessage += message;
// }JSON
if (receivedMessage.includes("batch_data_complete")) {
console.log("接受分包数据结束");
isMorePacket.init_batch_real_time = false;
console.log("展示数据", receivedMessage);
let startIndex = 0;
let startCount = 0;
let endIndex = receivedMessage.indexOf("batch_data_complete");
for (let i = 0; i < receivedMessage.length; ++i) {
if (receivedMessage[i] == "{") {
startCount++;
if (startCount == 2) {
startIndex = i;
break;
}
}
}
for (let i = receivedMessage.indexOf("batch_data_complete"); i >= 0; --i) {
if (receivedMessage[i] == "}" || startIndex == endIndex) {
endIndex = i;
break;
}
}
if (startIndex >= endIndex) {
throw new Error("JSON字符串格式错误");
}
console.log("message", startIndex, endIndex, receivedMessage[endIndex], receivedMessage[startIndex]);
parsedMessage = JSON.parse(receivedMessage.substring(startIndex, endIndex + 1));
console.log("JSON解析成功,解析后类型:", typeof parsedMessage, parsedMessage);
const stockDataArray = parsedMessage.data;
marketSituationStore.marketDetailCardData = regionalGroupArray.value.map((item) => ({
market: item.market,
stockCode: item.code,
stockName: item.name,
id: item.id,
currentPrice: stockDataArray[item.code][0].current_price.toFixed(2),
changeAmount: (stockDataArray[item.code][0].current_price - stockDataArray[item.code][0].pre_close).toFixed(2),
changePercent: ((100 * (stockDataArray[item.code][0].current_price - stockDataArray[item.code][0].pre_close)) / stockDataArray[item.code][0].pre_close).toFixed(2) + "%",
isRising: stockDataArray[item.code][0].current_price - stockDataArray[item.code][0].pre_close >= 0,
}));
}
} else if ((typeof message === "string" && message.includes('{"count')) || isMorePacket.batch_real_time) {
if (typeof message === "string" && message.includes('{"count')) {
console.log("开始接受分包数据");
receivedMessage = "";
} else {
console.log("接收分包数据过程中");
}
isMorePacket.batch_real_time = true;
receivedMessage += message;
// }JSON
if (receivedMessage.includes("batch_realtime_data")) {
console.log("接受分包数据结束");
isMorePacket.batch_real_time = false;
console.log("展示数据", receivedMessage);
let startIndex = 0;
let endIndex = receivedMessage.length - 1;
for (let i = 0; i < receivedMessage.length; ++i) {
if (receivedMessage[i] == "{") {
startIndex = i;
break;
}
}
for (let i = receivedMessage.length - 1; i >= 0; --i) {
if (receivedMessage[i] == "}" || startIndex == endIndex) {
endIndex = i;
break;
}
}
if (startIndex >= endIndex) {
throw new Error("JSON字符串格式错误");
}
parsedMessage = JSON.parse(receivedMessage.substring(startIndex, endIndex + 1));
console.log("JSON解析成功,解析后类型:", typeof parsedMessage, parsedMessage);
const stockDataArray = parsedMessage.data;
marketSituationStore.marketDetailCardData = regionalGroupArray.value.map((item) => ({
market: item.market,
stockCode: item.code,
stockName: item.name,
id: item.id,
currentPrice: stockDataArray[item.code][0].current_price.toFixed(2),
changeAmount: (stockDataArray[item.code][0].current_price - stockDataArray[item.code][0].pre_close).toFixed(2),
changePercent: ((100 * (stockDataArray[item.code][0].current_price - stockDataArray[item.code][0].pre_close)) / stockDataArray[item.code][0].pre_close).toFixed(2) + "%",
isRising: stockDataArray[item.code][0].current_price - stockDataArray[item.code][0].pre_close >= 0,
}));
}
} else {
// JSON
console.log("不是需要的数据,不做处理");
}
} catch (error) {
console.error("解析TCP股票数据失败:", error.message);
console.error("错误详情:", error);
}
};
// TCP
const removeTcpListeners = () => {
if (connectionListener.value) {
tcpConnection.removeConnectionListener(connectionListener.value);
connectionListener.value = null;
console.log("已移除TCP连接状态监听器");
}
if (messageListener.value) {
tcpConnection.removeMessageListener(messageListener.value);
messageListener.value = null;
console.log("已移除TCP消息监听器");
}
};
const startTcp = () => {
try {
removeTcpListeners();
disconnectTcp();
initTcpListeners();
connectTcp();
} catch (error) {
console.error("建立连接并设置监听出错:", error);
}
};
//
onLoad(async (options) => {
if (options && options.market) {
marketTitle.value = options.market;
await getRegionalGroupList();
initTcpListeners();
await nextTick();
//
startTcp();
}
});
onUnmounted(() => {
sendTcpMessage("stop_real_time");
removeTcpListeners();
disconnectTcp();
});
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>

1041
pages/marketSituation/marketOverview.vue
File diff suppressed because it is too large
View File

597
pages/marketSituation/marketSituation.vue

@ -0,0 +1,597 @@
<!-- @format -->
<template>
<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' }">
<!-- 动态组件切换 -->
<component :is="currentComponent" :countryId="currentChannelId" :marketTabs="getChildMarketTabs(currentChannelId)" :tabData="currentTabData"/>
</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="(item, index) in channelData" :key="index" :class="['country_item', selectedCountry === item.title ? 'selected' : '']" @click="selectCountry(item.title)">
<text class="country_text">{{ item.title }}</text>
</view>
</view>
</view>
</view>
</view>
<LoginPrompt ref="loginPrompt"></LoginPrompt>
</view>
</template>
<script setup>
import { ref, onMounted, watch, nextTick, computed } from "vue";
import footerBar from "../../components/footerBar.vue";
import forexMetals from "./forexMetals.vue";
import marketOverview from "./marketOverview.vue";
import countryMarket from "./countryMarket.vue";
import { getAllTabsAPI, queryStockDataAPI } from "../../api/marketSituation/marketSituation.js";
const type = ref("marketSituation");
const iSMT = ref(0);
const searchValue = ref("");
const contentHeight = ref(0);
const headerHeight = ref(0); // header
const currentTabData = ref(null);
// Tab
const channelData = ref([{ id: 1, title: "概况" }]);
const pageIndex = ref(0);
const scrollToView = ref("");
//
const currentChannelId = computed(() => {
return channelData.value[pageIndex.value]?.id || 1;
});
const currentComponent = computed(() => {
const channelId = currentChannelId.value;
// 使 MarketOverview
if (pageIndex.value === 0) {
return marketOverview;
}
// (id=11)(id=12)使 ForexMetals
else if (channelId === 11 || channelId === 12) {
return forexMetals;
}
// /使 CountryMarket
else {
return countryMarket;
}
});
// contenttop
const contentTopPosition = computed(() => {
const statusBarHeight = iSMT.value || 0;
const currentHeaderHeight = headerHeight.value > 0 ? headerHeight.value : 140;
return statusBarHeight + currentHeaderHeight;
});
//
const showCountryModal = ref(false);
const selectedCountry = ref("概况");
const getAllTabs = async () => {
try {
const result = await getAllTabsAPI();
channelData.value = result.data.map((item) => ({ ...item, id: item.id, title: item.tradeName }));
} catch (e) {
console.error("获取地区分组列表失败", e);
}
};
const getChildMarketTabs = (id) => {
return channelData.value.filter((item) => item.id === id)[0]?.children?.map((child) => child.tradeName);
};
//
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.navigateTo({
url: '/pages/customStockList/customStockList'
})
};
// Tab
const navClick = (index) => {
pageIndex.value = index;
const currentItem = channelData.value[index];
scrollToView.value = "nav" + currentItem.id;
//
selectedCountry.value = currentItem.title;
// tab
queryStockDataAPI({
parentId: currentItem.id,
tradeId: 1,
}).then((res) => {
if (res.code == 200) {
currentTabData.value = {
type: 'country',
data: res.data
};
}
});
// 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;
};
onMounted(async () => {
await getAllTabs();
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
// tab
if (channelData.value.length > 0) {
pageIndex.value = 0;
scrollToView.value = "nav" + channelData.value[0].id;
}
// DOM
nextTick(() => {
// 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 {
position: relative;
height: 100vh;
overflow: hidden;
background-color: white;
}
/* 状态栏占位 */
.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;
}
.static-footer {
position: fixed;
bottom: 0;
}
/* 弹窗样式 */
.modal_overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: flex-end;
z-index: 1000;
}
.modal_content {
width: 100%;
background-color: #fff;
border-radius: 20rpx 20rpx 0 0;
max-height: 80vh;
overflow: hidden;
}
.modal_header {
position: relative;
display: flex;
justify-content: center;
align-items: center;
padding: 30rpx 40rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.modal_title {
font-size: 32rpx;
font-weight: bold;
color: #333333;
text-align: center;
}
.modal_close {
position: absolute;
right: 40rpx;
top: 50%;
transform: translateY(-50%);
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 40rpx;
color: #999;
}
.modal_body {
padding: 40rpx;
}
.country_grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20rpx;
}
.country_item {
padding: 24rpx 30rpx;
border-radius: 12rpx;
background-color: #f8f8f8;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.country_item.selected {
background-color: #ff4444;
color: #fff;
}
.country_text {
font-size: 28rpx;
color: #333;
}
.country_item.selected .country_text {
color: #fff;
}
</style>

55
pages/morningMarketAnalysis/morningMarketAnalysis.vue

@ -0,0 +1,55 @@
<template>
<view class="container">
<view class="content">
<image
class="no-data-image"
src="https://d31zlh4on95l9h.cloudfront.net/images/f5a9bd32c81bc7cca47252b51357c12f.png"
mode="aspectFit"
></image>
<text class="no-data-text">暂无数据~</text>
</view>
</view>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
}
}
</script>
<style>
.container {
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background-color: #f5f5f5;
}
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.no-data-image {
width: 200px;
height: 200px;
margin-bottom: 20px;
}
.no-data-text {
font-size: 16px;
color: #999999;
text-align: center;
}
</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>

241
pages/setting/account.vue

@ -0,0 +1,241 @@
<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"></uni-icons>
</view>
</view>
<view class="setting-item">
<text class="item-label">昵称</text>
<view class="item-right">
<text class="item-text">{{userInfoRes.dcname}}</text>
<uni-icons type="arrowright" size="16"></uni-icons>
</view>
</view>
<view class="setting-item">
<text class="item-label">ID</text>
<view class="item-right">
<text class="item-text">{{ userInfoRes.dccode }}</text>
</view>
</view>
<view class="setting-item" @click="goToPassword">
<text class="item-label">
{{ userInfoRes.hasPwd === 0 ? '创建密码' : '修改密码' }}
</text>
<uni-icons type="arrowright" size="16"></uni-icons>
</view>
<view class="setting-item">
<text class="item-label">注销账号</text>
<uni-icons type="arrowright" size="16"></uni-icons>
</view>
<view class="setting-item" @click="goToBind">
<text class="item-label">绑定账号</text>
<uni-icons type="arrowright" size="16"></uni-icons>
</view>
</view>
<view style="height:1.5vh;"></view>
<view class="logout" @click="showLogout = true">
<text>退出登录</text>
</view>
<view class="logout-confirm" v-if="showLogout">
<view class="logoutDialog">
<view class="tips">是否退出登录</view>
<view class="tips-button">
<button class="confirm-btn" @click="handleConfirmLogout">确认</button>
<button class="cancel-btn" @click="showLogout = false">取消</button>
</view>
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
import {
getUserInfo
} from "@/api/member"
import {
useUserStore
} from "../../stores/modules/userInfo"
const userStore = useUserStore()
const iSMT = ref(0)
// const dccode = ref('')
const userInfoRes = ref({})
const showLogout = ref(false)
const userInfoPromise = getUserInfo()
userInfoPromise.then(res => {
if (res.code === 200) {
userInfoRes.value.dccode = res.data.dccode;
userInfoRes.value.dcname = res.data.dcname;
userInfoRes.value.hasPwd = res.data.hasPassword;
console.log('用户信息', userInfoRes.value)
} else {
uni.showToast({
title: '用户信息请求失败',
icon: 'none',
})
}
})
const handleConfirmLogout = () => {
userStore.clearUserInfo()
showLogout.value = false
uni.showToast({
title: '退出登录成功',
icon: 'none',
})
uni.navigateTo({
url: '/pages/start/login/login'
})
}
const goToBind = () => {
uni.navigateTo({
url: '../setting/bind'
})
}
const goToPassword = () => {
if (userInfoRes.value.hasPwd === 0) {
uni.navigateTo({
url: '../setting/createPwd'
})
} else {
uni.navigateTo({
url: '../setting/password'
})
}
}
onMounted(() => {
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
})
</script>
<style scoped>
.setting-list {
height: 42vh;
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>

92
pages/setting/bind.vue

@ -0,0 +1,92 @@
<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;">
{{ userStore.userInfo?.phone || '未绑定' }}
</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;">
{{ userStore.userInfo?.email || '未绑定' }}
</text>
<uni-icons type="arrowright" size="16" />
</view>
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
import { useUserStore } from "../../stores/modules/userInfo"
const userStore = useUserStore()
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)
console.log('看看用户信息',userStore.userInfo)
})
</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>

257
pages/setting/createPwd.vue

@ -0,0 +1,257 @@
<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" v-model="userEmail" />
<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" v-model="userPhone" />
<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" v-model="verifyCode" />
</view>
</view>
<view class="btn-area">
<button class="next-btn" @click="goToPwdNext">下一步</button>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
import {
sendEmail,
validateCode,
sendPhone
} from "@/api/setting/password";
const iSMT = ref(0)
const activeTab = ref('email')
const gettingCode = ref(false)
const time = ref(60)
const userEmail = ref('')
const userPhone = ref('')
const verifyCode = ref('')
const getCode = () => {
if (gettingCode.value) return
gettingCode.value = true
time.value = 2
const timer = setInterval(() => {
time.value--
if (time.value <= 0) {
clearInterval(timer)
gettingCode.value = false
time.value = 2
}
}, 1000)
if (activeTab.value === 'email') {
sendEmail({
email: userEmail.value
})
} else {
sendPhone({
phone: userPhone.value
})
}
}
const goToPwdNext = async () => {
console.log('发请求之前的activeTab', activeTab.value)
if (activeTab.value === 'email') {
if (!userEmail.value) {
uni.showToast({
title: '请输入邮箱',
icon: 'none'
})
return
}
} else {
if (!userPhone.value) {
uni.showToast({
title: '请输入手机号',
icon: 'none'
})
return
}
}
if (!verifyCode.value) {
uni.showToast({
title: '请输入验证码',
icon: 'none'
})
return
}
try {
let param;
if (activeTab.value === 'email') {
param = {
loginType: 'EMAIL',
account: userEmail.value,
verifyCode: verifyCode.value
}
} else {
param = {
loginType: 'PHONE',
account: userPhone.value,
verifyCode: verifyCode.value
}
}
const res = await validateCode(param)
console.log('看看参数', param)
console.log('看看结果', res)
//
if (res.code === 200) {
uni.showToast({
title: '验证成功',
icon: 'success'
})
uni.navigateTo({
url: '../setting/nextPwd'
})
} else {
uni.showToast({
title: res.msg || '验证失败',
icon: 'none'
})
}
} catch (err) {
console.error(err)
uni.showToast({
title: '请求出错',
icon: 'none'
})
}
}
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>

185
pages/setting/email.vue

@ -0,0 +1,185 @@
<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" />
<input v-model="userEmail" placeholder="请输入您的换绑邮箱" class="input" />
</view>
<view class="right">
<button class="verification" :class="{ 'disabled': gettingCode }" @click="getCode"
: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" @click="changeAccount">换绑</button>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
import {
getUserInfo
} from "@/api/member"
import {
sendEmail,
changeBind
} from "@/api/setting/password"
const iSMT = ref(0)
const email = ref('')
const gettingCode = ref(false)
const time = ref(60)
const userEmail = ref('')
const userInfoPromise = getUserInfo()
userInfoPromise.then(res => {
if (res.code === 200) {
console.log('个人信息', res.data)
email.value = res.data.email
} else {
uni.showToast({
title: '用户信息请求失败',
icon: 'none',
})
}
})
const changeAccount = () => {
const res = changeBind({
verificateType: 0,
account: userEmail.value
})
if(res.code === 200){
uni.showToast({
title: '绑定成功',
icon: 'none',
})
}else {
uni.showToast({
title: '用户绑定失败',
icon: 'none',
})
}
}
const getCode = () => {
if (gettingCode.value) return
gettingCode.value = true
time.value = 2
const timer = setInterval(() => {
time.value--
if (time.value <= 0) {
clearInterval(timer)
gettingCode.value = false
time.value = 2
}
}, 1000)
sendEmail({
email: userEmail.value
})
}
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/font.vue

@ -0,0 +1,108 @@
<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('small')" />
</view>
<view class="top-list">
<text>中号</text>
<radio value="1" class="radio-btn" activeBackgroundColor="red" :checked="selectedIndex === 1"
@click="selectFont('medium')" />
</view>
<view class="top-list">
<text>大号</text>
<radio value="2" class="radio-btn" activeBackgroundColor="red" :checked="selectedIndex === 2"
@click="selectFont('large')" />
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
import {
getSetting,
updateSetting
} from "@/api/setting/general"
const iSMT = ref(0)
const selectedIndex = ref(0)
const fontTypeMap = {
'small': 0,
'medium': 1,
'large': 2
}
const getFont = async () => {
try {
const res = await getSetting()
if (res.code === 200) {
const fontSize = res.data.fontSize
selectedIndex.value = fontTypeMap[fontSize] ?? 0
}
} catch (err) {
console.error("获取字体设置失败:", err)
}
}
const selectFont = async (fontType) => {
try {
selectedIndex.value = fontTypeMap[fontType]
console.log('字体类型:', fontType, ',looklook索引:', selectedIndex.value)
const updateRes = await updateSetting({
fontSize: fontType
})
if (updateRes.code === 200) {
uni.showToast({
title: '字体大小设置成功',
icon: 'none'
})
}
} catch (err) {
console.error("更新字体设置失败:", err)
uni.showToast({
title: '设置失败,请重试',
icon: 'none'
})
}
}
onMounted(() => {
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
console.log('看看高度', iSMT.value)
getFont()
})
</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>

186
pages/setting/general.vue

@ -0,0 +1,186 @@
<template>
<view class="main">
<view :style="{height:iSMT+'px'}"></view>
<view class="top">
<view class="top-list">
<text class="label">语言</text>
<text class="language">{{settingRes.language}}</text>
<uni-icons type="arrowright" size="16" />
</view>
<view class="top-list" @click="goToFont">
<text class="label">字体大小</text>
<uni-icons type="arrowright" size="16" />
</view>
<view class="top-list" @click="goToTheme">
<text class="label">主题切换</text>
<uni-icons type="arrowright" size="16" />
</view>
</view>
<view class="center">
<view class="center-list" @click="goToMessage">
<text class="label">消息推送</text>
<uni-icons type="arrowright" size="16" />
</view>
</view>
<view class="bottom">
<view class="bottom-list" @click="goToServer">
<text class="label">切换服务器</text>
<uni-icons type="arrowright" size="16" />
</view>
<view class="bottom-list" @click="clearCache">
<text class="label">清理缓存</text>
<text class="cache">{{ cache }}M</text>
<uni-icons type="arrowright" size="16" />
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
import {getUserInfo} from "@/api/member";
const iSMT = ref(0)
const cache = ref('45.5')
const settingRes = ref({})
const settingPromise = getUserInfo()
settingPromise.then(res => {
if (res.code === 200){
settingRes.value.language = res.data.language;
settingRes.value.fontSize = res.data.fontSize;
settingRes.value.theme = res.data.theme;
settingRes.value.serverSelection = res.data.serverSelection;
console.log('用户信息', res.data)
}else {
console.log('用户信息请求失败:', res.message);
}
})
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: 70%;
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 {
font-size: 14px;
color: rgb(203, 203, 203);
}
.bottom-list:last-child {
border-bottom: none;
}
.label{
flex:1;
font-size:28rpx;
}
</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>

348
pages/setting/market.vue

@ -0,0 +1,348 @@
<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 === 'auto' }"
@click="handleAStockBidChange('auto')"
>
<text>智能开启</text>
<view class="active-dot" v-if="aStockBid === 'auto'"></view>
</view>
<view
class="option-btn"
:class="{ 'active': aStockBid === 'open' }"
@click="handleAStockBidChange('open')"
>
<text>保持开启</text>
<view class="active-dot" v-if="aStockBid === 'open'"></view>
</view>
<view
class="option-btn"
:class="{ 'active': aStockBid === 'close' }"
@click="handleAStockBidChange('close')"
>
<text>保持关闭</text>
<view class="active-dot" v-if="aStockBid === 'close'"></view>
</view>
</view>
<view class="title">K线样式</view>
<view class="top-options">
<view
class="option-btn"
:class="{ 'active': kStyle === 'common' }"
@click="handleKStyleChange('common')"
>
<img src="/static/my/common.png" class="kline-icon" />
<text>普通</text>
<view class="active-dot" v-if="kStyle === 'common'"></view>
</view>
<view
class="option-btn"
:class="{ 'active': kStyle === 'Outline' }"
@click="handleKStyleChange('Outline')"
>
<img src="/static/my/outline.png" class="kline-icon" />
<text>轮廓图</text>
<view class="active-dot" v-if="kStyle === 'Outline'"></view>
</view>
<view
class="option-btn"
:class="{ 'active': kStyle === 'polylines' }"
@click="handleKStyleChange('polylines')"
>
<img src="/static/my/polylines.png" class="kline-icon" />
<text>折线图</text>
<view class="active-dot" v-if="kStyle === 'polylines'"></view>
</view>
</view>
<view class="title">除权类型</view>
<view class="top-options">
<view
class="option-btn"
:class="{ 'active': exRights === 'exRights' }"
@click="handleExRightsChange('exRights')"
>
<text>除权</text>
<view class="active-dot" v-if="exRights === 'exRights'"></view>
</view>
<view
class="option-btn"
:class="{ 'active': exRights === 'normal' }"
@click="handleExRightsChange('normal')"
>
<text>普通</text>
<view class="active-dot" v-if="exRights === 'normal'"></view>
</view>
<view
class="option-btn"
:class="{ 'active': exRights === 'Weighted' }"
@click="handleExRightsChange('Weighted')"
>
<text>加权</text>
<view class="active-dot" v-if="exRights === 'Weighted'"></view>
</view>
</view>
<view class="title">涨跌颜色</view>
<view class="top-options">
<view
class="option-btn"
:class="{ 'active': rfColor === 'green' }"
@click="handleRfColorChange('green')"
>
<view class="color-icon">
<img src="/static/my/greenRise.png" class="kline-icon" />
</view>
<text>绿涨红跌</text>
<view class="active-dot" v-if="rfColor === 'green'"></view>
</view>
<view
class="option-btn"
:class="{ 'active': rfColor === 'red' }"
@click="handleRfColorChange('red')"
>
<view class="color-icon">
<img src="/static/my/redRise.png" class="kline-icon" />
</view>
<text>红涨绿跌</text>
<view class="active-dot" v-if="rfColor === 'red'"></view>
</view>
</view>
<view class="title">副图指标个数</view>
<view class="top-options">
<view
class="option-btn"
:class="{ 'active': indexCount === 1 }"
@click="handleIndexCountChange(1)"
>
<text>1</text>
</view>
<view
class="option-btn"
:class="{ 'active': indexCount === 2 }"
@click="handleIndexCountChange(2)"
>
<text>2</text>
</view>
<view
class="option-btn"
:class="{ 'active': indexCount === 3 }"
@click="handleIndexCountChange(3)"
>
<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'
import { getMarketSetting, updateMarketSetting } from "@/api/setting/market"
const iSMT = ref(0)
const aStockBid = ref('auto') // Aauto/open/close
const kStyle = ref('common') // K线common/Outline/polylines
const exRights = ref('exRights') // exRights/normal/Weighted
const rfColor = ref('green') // green/red
const indexCount = ref(1) // 1/2/3
const indicatorList = ref(['K线', '均线', '成交量', 'KDJ', 'MACD', 'RSI'])
const getMarketSettings = async () => {
try {
const res = await getMarketSetting()
if (res.code === 200) {
aStockBid.value = res.data.auctionDisplay ?? 'auto'
kStyle.value = res.data.klineStyle ?? 'common'
exRights.value = res.data.rightsIssueType ?? 'exRights'
rfColor.value = res.data.priceColorScheme ?? 'green'
indexCount.value = res.data.subChartCount ?? 1
}
} catch (err) {
console.error("获取市场设置失败:", err)
}
}
const updateSetting = async () => {
try {
const params = {
auctionDisplay: aStockBid.value,
klineStyle: kStyle.value,
rightsIssueType: exRights.value,
priceColorScheme: rfColor.value,
subChartCount: indexCount.value
}
const res = await updateMarketSetting(params)
if (res.code === 200) {
uni.showToast({ title: '设置已更新', icon: 'none' })
} else {
uni.showToast({ title: '更新失败', icon: 'none' })
}
} catch (err) {
console.error("更新设置失败:", err)
uni.showToast({ title: '更新失败', icon: 'none' })
}
}
const handleAStockBidChange = (newValue) => {
if (newValue !== aStockBid.value) {
aStockBid.value = newValue
updateSetting()
}
}
const handleKStyleChange = (newValue) => {
if (newValue !== kStyle.value) {
kStyle.value = newValue
updateSetting()
}
}
const handleExRightsChange = (newValue) => {
if (newValue !== exRights.value) {
exRights.value = newValue
updateSetting()
}
}
const handleRfColorChange = (newValue) => {
if (newValue !== rfColor.value) {
rfColor.value = newValue
updateSetting()
}
}
const handleIndexCountChange = (newValue) => {
if (newValue !== indexCount.value) {
indexCount.value = newValue
updateSetting()
}
}
onMounted(() => {
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
getMarketSettings()
})
</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;
flex: 1;
}
.indicator-icons {
display: flex;
gap: 100rpx;
margin-left: auto;
}
.icon {
width: 28rpx;
height: 28rpx;
}
</style>

61
pages/setting/message.vue

@ -0,0 +1,61 @@
<template>
<view class="main">
<view :style="{height:iSMT+'px'}"></view>
<view class="top">
<view class="top-list" @click="goToPush">
<text class="text">消息推送</text>
<text class="message" v-if="isMessage">通知已开启</text>
<text class="message" v-if="!isMessage">通知未开启</text>
<uni-icons type="arrowright" size="16" />
</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 {
font-size: 14px;
color: rgb(203, 203, 203);
}
.text{
flex:1;
}
</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>

175
pages/setting/nextPwd.vue

@ -0,0 +1,175 @@
<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" v-model="oldPassword"
/>
<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" v-model="newPassword"/>
<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" @click="confirmChange">确认</button>
</view>
</view>
</template>
<script setup>
import {onMounted, ref} from 'vue'
import {updatePassword} from "@/api/setting/nextPwd";
const iSMT = ref(0)
const pwdType = ref('password')
const pwdType2 = ref('password')
//
const oldPassword = ref('')
const newPassword = ref('')
//
const confirmChange = async () => {
if (newPassword.value !== oldPassword.value) {
uni.showToast({title: '两次输入的密码不一致', icon: 'none'})
return
}
const updatePasswordPromise = updatePassword({
oldPassword: oldPassword.value,
newPassword: newPassword.value
})
updatePasswordPromise
.then(res => {
if (res.code === 200) {
uni.showToast({ title: '修改成功', icon: 'success' });
} else {
uni.showToast({ title: res.message,icon: 'none' });
}
})
.catch(err => {
console.log('修改密码失败:', err);
});
}
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>

255
pages/setting/password.vue

@ -0,0 +1,255 @@
<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" v-model="userEmail" />
<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" v-model="userPhone" />
<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" v-model="verifyCode" />
</view>
</view>
<view class="btn-area">
<button class="next-btn" @click="goToPwdNext">下一步</button>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
import {
sendEmail,
validateCode,
sendPhone
} from "@/api/setting/password";
const iSMT = ref(0)
const activeTab = ref('email')
const gettingCode = ref(false)
const time = ref(60)
const userEmail = ref('')
const userPhone = ref('')
const verifyCode = ref('')
const getCode = () => {
if (gettingCode.value) return
gettingCode.value = true
time.value = 2
const timer = setInterval(() => {
time.value--
if (time.value <= 0) {
clearInterval(timer)
gettingCode.value = false
time.value = 2
}
}, 1000)
if (activeTab.value === 'email') {
sendEmail({
email: userEmail.value
})
} else {
sendPhone({
phone: userPhone.value
})
}
}
const goToPwdNext = async () => {
if (activeTab.value === 'email') {
if (!userEmail.value) {
uni.showToast({
title: '请输入邮箱',
icon: 'none'
})
return
}
}else{
if (!userPhone.value) {
uni.showToast({
title: '请输入手机号',
icon: 'none'
})
return
}
}
if (!verifyCode.value) {
uni.showToast({
title: '请输入验证码',
icon: 'none'
})
return
}
try {
let param;
if (activeTab.value === 'email') {
param = {
loginType: 'EMAIL',
account: userEmail.value,
verifyCode: verifyCode.value
}
} else {
param = {
loginType: 'PHONE',
account: userPhone.value,
verifyCode: verifyCode.value
}
}
const res = await validateCode(param)
console.log('看看参数', param)
console.log('看看结果', res)
//
if (res.code === 200) {
uni.showToast({
title: '验证成功',
icon: 'success'
})
uni.navigateTo({
url: '../setting/nextPwd'
})
} else {
uni.showToast({
title: res.msg || '验证失败',
icon: 'none'
})
}
} catch (err) {
console.error(err)
uni.showToast({
title: '请求出错',
icon: 'none'
})
}
}
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>

185
pages/setting/phone.vue

@ -0,0 +1,185 @@
<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" v-model="userPhone" placeholder="请输入您的换绑手机号" class="input" />
</view>
<view class="right">
<button class="verification" :class="{ 'disabled': gettingCode }" @click="getCode"
: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" @click="changeAccount">换绑</button>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
import {
sendPhone,
changeBind
} from "@/api/setting/password"
import {
getUserInfo
} from "@/api/member"
const iSMT = ref(0)
const phone = ref('')
const gettingCode = ref(false)
const time = ref(60)
const userPhone = ref('')
const userInfoPromise = getUserInfo()
userInfoPromise.then(res => {
if (res.code === 200) {
console.log('个人信息', res.data)
phone.value = res.data.phone
} else {
uni.showToast({
title: '用户信息请求失败',
icon: 'none',
})
}
})
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
}
}, 1000)
sendPhone({
phone: userPhone.value
})
}
const changeAccount = () => {
const res = changeBind({
verificateType: 1,
account: userPhone.value
})
if(res.code === 200){
uni.showToast({
title: '绑定成功',
icon: 'none',
})
}else {
uni.showToast({
title: '用户绑定失败',
icon: 'none',
})
}
}
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>

120
pages/setting/push.vue

@ -0,0 +1,120 @@
<template>
<view class="main">
<view :style="{height:iSMT+'px'}"></view>
<view class="top">
<view class="top-list">
<text class="label">公共消息</text>
<view class="right">
<text class="public">重大咨询财经要闻等系统提醒</text>
<switch class="switch-btn" />
</view>
</view>
<view class="top-list">
<text class="label">指标消息提醒</text>
<view class="right">
<text class="public">所有指标消息的提醒</text>
<switch class="switch-btn" />
</view>
</view>
</view>
<view class="bottom">
<view class="bottom-list">
<text class="label">盯盘预警</text>
<view class="right">
<text class="public">自选股预警和个性化预警设置</text>
<uni-icons type="arrowright" size="16" />
</view>
</view>
<view class="bottom-list">
<text class="label">订阅服务</text>
<view class="right">
<text class="public">订阅你感兴趣的专题服务</text>
<uni-icons type="arrowright" size="16" />
</view>
</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;
margin: 0 40rpx;
padding: 0 10rpx;
border-bottom: 1rpx solid #eee;
}
.top-list:last-child {
border-bottom: none;
}
.right {
display: flex;
align-items: center;
gap: 10rpx;
}
.switch-btn {
transform: scale(0.6);
transform-origin: center right;
}
.public {
font-size: 10px;
color: rgb(203, 203, 203);
}
.bottom {
height: 14vh;
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;
}
.label {
flex: 1;
}
</style>

128
pages/setting/server.vue

@ -0,0 +1,128 @@
<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('auto')" />
</view>
<view class="top-list">
<text>新加坡服务器</text>
<radio value="1" class="radio-btn" activeBackgroundColor="red"
:checked="selectedIndex === 1" @click="selectFont('singapore')" />
</view>
<view class="top-list">
<text>香港服务器</text>
<radio value="2" class="radio-btn" activeBackgroundColor="red"
:checked="selectedIndex === 2" @click="selectFont('hongkong')" />
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
import {
getSetting,
updateSetting
} from "@/api/setting/general"
const iSMT = ref(0)
const selectedIndex = ref(0)
const servertypeMap = {
'auto': 0,
'singapore': 1,
'hongkong': 2
}
const getServer = async () => {
try {
const res = await getSetting()
if (res.code === 200) {
const serverSelection = res.data.serverSelection
selectedIndex.value = servertypeMap[serverSelection] ?? 0;
}
} catch (err) {
console.error("获取服务器设置失败:", err);
}
}
const selectFont = async (servertype) => {
try {
selectedIndex.value = servertypeMap[servertype]
console.log('服务器类型:', servertype, ',looklook索引:', selectedIndex.value)
const updateRes = await updateSetting({
serverSelection: servertype
})
if (updateRes.code === 200) {
uni.showToast({
title: '服务器大小设置成功',
icon: 'none'
})
}
} catch (err) {
console.error("更新服务器设置失败:", err);
uni.showToast({
title: '设置失败,请重试',
icon: 'none'
})
}
}
onMounted(() => {
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
console.log('看看高度', iSMT.value)
getServer()
})
</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>

299
pages/setting/share.vue

@ -0,0 +1,299 @@
<template>
<view class="all">
<!-- 背景图部分 -->
<image class="img-share" src="/static/my/shareBackground.png"/>
<image class="img-greenBack" src="/static/my/greenBackground.png"/>
<!-- todo 这里给我个码-->
<image class="img-QRcode" src="/static/my/QRcode.png"/>
<image class="img-award" src="/static/my/award.png"/>
<image class="img-myFriends" src="/static/my/myFriends.png"/>
<image class="img-friends" src="/static/my/shareFriends.png"/>
<!-- dccode -->
<text class="jwcode">{{ dccode }}</text>
<!-- 邀请按钮 -->
<button class="invite" @click="openShare">立即邀请</button>
<!-- 分享弹窗 -->
<uni-popup ref="shareRef" type="share" safeArea>
<SharePopup @select="onShareSelect" @close="closeShare" title=" "/>
</uni-popup>
<!-- 二次弹窗 -->
<uni-popup ref="secondPopup" type="share">
<view class="second-popup">
<view style=" display: flex;justify-content: center;align-items: center; font-size: 17px">
<image style="width: 16px; height: 16px; margin-right: 8rpx" src="/static/my/share/success.png"/>
<text>已复制</text>
</view>
<view class="popup-msg-box">
<text>{{ popupMsg }}</text>
</view>
<view style="justify-content: center; align-items: center;">
<!-- 二次弹窗中的按钮图标 -->
<button
style="border-radius: 40rpx; background-color: black; color: white; display: flex; align-items: center; justify-content: center; padding: 12rpx 24rpx;"
@click="closeSecondPopup">
<image style="width: 25px; height: 25px; margin-right: 8rpx;"
:src="platformIconMap[selectedPlatform]"/>
去粘贴给好友
</button>
</view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import {ref} from 'vue'
import SharePopup from '@/components/SharePopup.vue'
import {getUserInfo} from "@/api/member";
import {Share} from "@/api/setting/share";
/* =============== 数据与引用 =============== */
const shareRef = ref(null)
const secondPopup = ref(null)
const popupMsg = ref('')
// const jwcode = ref('90047681')
//
const selectedPlatform = ref('')
// dccode
const dccode = ref('')
// token
const token = ref('1ab8f83f391ca866191385d0e5048938')
//
const deviceId = ref(100)
//
const version = ref(100)
//
const client = ref('android')
//
const platformIconMap = ref({
'WeChat': '/static/my/share/WeChat.png',
'WhatsApp': '/static/my/share/WhatsApp.png',
'Line': '/static/my/share/Line.png',
'KakaoTalk': '/static/my/share/KakaoTalk.png',
'复制链接': '/static/my/share/share.png'
})
//
const userInfoRes = ref()
// dccode
const shareLink = ref('')
/* =============== 方法 =============== */
userInfoRes.value = getUserInfo()
userInfoRes.value.then(res => {
dccode.value = res.data.dccode
console.log('用户信息', res.data)
})
const ShareRes = ref()
ShareRes.value = Share()
ShareRes.value.then(res => {
if (res.code === 200){
shareLink.value = res.message
console.log('分享接口返回', res.data)
}else {
console.log('分享接口返回失败', res.data)
}
})
//
function openShare() {
Share()
shareRef.value.open()
}
//
function closeShare() {
shareRef.value.close()
}
//
//
function onShareSelect({item}) {
console.log('选择了:', item.name)
selectedPlatform.value = item.name //
// //
// const baseUrl = 'https:'
// // const shareLink = `${baseUrl}?token=${encodeURIComponent(token.value)}&deviceId=${encodeURIComponent(deviceId.value)}&version=${encodeURIComponent(version.value)}&client=${encodeURIComponent(client.value)}`
// const shareLink = `$ `
//
shareRef.value.close()
popupMsg.value = `【DeepChart】邀请你加入,点击链接帮我助力: ${shareLink.value}`
uni.setClipboardData({
data: popupMsg.value,
showToast: false
});
/* // 根据分享选项显示不同提示
if (item.name === '复制链接') {
popupMsg.value = '链接已复制,快去分享给好友吧~'
} else if (item.name === 'WeChat') {
popupMsg.value = '请在微信中分享~'
} else {
popupMsg.value = `你选择了 ${item.name}`
}*/
//
secondPopup.value.open()
}
//
function closeSecondPopup() {
if (selectedPlatform.value === 'WeChat') {
uni.share({
provider: "weixin",
scene: "WXSceneSession",
type: 1,
summary: popupMsg.value,
success: function (res) {
console.log("success:" + JSON.stringify(res));
},
fail: function (err) {
console.log("fail:" + JSON.stringify(err));
}
});
secondPopup.value.close()
}
//
else if (selectedPlatform.value === 'WhatsApp' || selectedPlatform.value === 'Line' || selectedPlatform.value === 'KakaoTalk') {
secondPopup.value.close()
uni.showToast({title: '开发中……', icon: 'none'})
} else if (selectedPlatform.value === '复制链接') {
uni.showToast({title: '已复制', icon: 'success'})
secondPopup.value.close()
}
}
</script>
<style scoped>
.all {
position: relative;
width: 750rpx;
height: auto;
}
/* 背景图片部分 */
.img-share {
width: 750rpx;
height: 2118rpx;
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
.img-greenBack {
width: 670rpx;
height: 1740rpx;
position: absolute;
top: 16vh;
left: 40rpx;
z-index: 2;
}
.img-QRcode {
width: 320rpx;
height: 320rpx;
position: absolute;
top: 26vh;
left: 215rpx;
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;
}
.img-friends {
width: 602rpx;
height: 840rpx;
position: absolute;
top: 68vh;
left: 74rpx;
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;
}
/* 第二个弹窗样式 */
.second-popup {
background-color: #fff;
border-radius: 12px;
padding: 30rpx;
text-align: center;
}
.popup-msg-box {
background-color: #F3F3F3;
border-radius: 8px;
padding: 12px 16px;
margin: 10px;
align-items: center;
justify-content: center;
overflow: hidden; /* 隐藏溢出内容 */
text-overflow: ellipsis; /* 溢出部分显示... */
}
</style>

104
pages/setting/theme.vue

@ -0,0 +1,104 @@
<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="updateTheme('light')" />
</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="updateTheme('dark')" />
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
import {
getSetting,
updateSetting
} from "@/api/setting/general"
const iSMT = ref(0)
const selectedIndex = ref(0)
const themeTypeMap = {
'light': 0,
'dark': 1
}
const getTheme = async () => {
try {
const res = await getSetting()
if (res.code === 200) {
const theme = res.data.theme
selectedIndex.value = themeTypeMap[theme] ?? 0
}
} catch (err) {
console.error("获取主题设置失败:", err);
}
}
const updateTheme = async (themeType) => {
try {
selectedIndex.value = themeTypeMap[themeType]
console.log('主题:', themeType, ',looklook索引:', selectedIndex.value)
const updateRes = await updateSetting({
theme: themeType
})
if (updateRes.code === 200) {
uni.showToast({
title: '主题设置成功',
icon: 'none'
})
}
} catch (err) {
console.error("更新主题设置失败:", err);
uni.showToast({
title: '设置失败,请重试',
icon: 'none'
})
}
}
onMounted(() => {
//
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
console.log('看看高度', iSMT.value)
getTheme()
})
</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>

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

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

13
pages/start/agreement/agreement.vue

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

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

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

69
pages/start/login/verification.js

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

13
pages/start/privacy/privacy.vue

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

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

170
pages/start/select/select.vue

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

73
pages/start/startup/startup.vue

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

138
server/login.json

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

BIN
static/customer-service-platform/camera.png

After

Width: 18  |  Height: 16  |  Size: 428 B

BIN
static/customer-service-platform/cs-platform-back.png

After

Width: 20  |  Height: 20  |  Size: 247 B

BIN
static/customer-service-platform/ellipse-dc-img.png

After

Width: 60  |  Height: 60  |  Size: 2.1 KiB

BIN
static/customer-service-platform/empty-content.png

After

Width: 213  |  Height: 228  |  Size: 18 KiB

BIN
static/customer-service-platform/fail-icon.png

After

Width: 100  |  Height: 100  |  Size: 4.2 KiB

BIN
static/customer-service-platform/message.png

After

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

BIN
static/customer-service-platform/refresh-icon.png

After

Width: 15  |  Height: 15  |  Size: 339 B

BIN
static/customer-service-platform/robot-head.png

After

Width: 21  |  Height: 19  |  Size: 1.2 KiB

BIN
static/customer-service-platform/smile-icon.png

After

Width: 16  |  Height: 16  |  Size: 440 B

BIN
static/customer-service-platform/success-icon.png

After

Width: 100  |  Height: 100  |  Size: 4.2 KiB

BIN
static/deepExploration-images/1.png

After

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

BIN
static/deepExploration-images/2.png

After

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

BIN
static/deepExploration-images/3.png

After

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

BIN
static/deepExploration-images/4.png

After

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

BIN
static/deepExploration-images/ASC.png

After

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

BIN
static/deepExploration-images/Americle.png

After

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

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

Loading…
Cancel
Save