Bladeren bron

Initial Commit

Zhangbw 3 maanden geleden
commit
32b0f576fc

+ 14 - 0
.gitignore

@@ -0,0 +1,14 @@
+# Windows
+[Dd]esktop.ini
+Thumbs.db
+$RECYCLE.BIN/
+
+# macOS
+.DS_Store
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+
+# Node.js
+node_modules/

+ 19 - 0
app.js

@@ -0,0 +1,19 @@
+// app.js
+App({
+  onLaunch() {
+    // 展示本地存储能力
+    const logs = wx.getStorageSync('logs') || []
+    logs.unshift(Date.now())
+    wx.setStorageSync('logs', logs)
+
+    // 登录
+    wx.login({
+      success: res => {
+        // 发送 res.code 到后台换取 openId, sessionKey, unionId
+      }
+    })
+  },
+  globalData: {
+    userInfo: null
+  }
+})

+ 56 - 0
app.json

@@ -0,0 +1,56 @@
+{
+  "pages": [
+    "pages/index/index",
+    "pages/logs/logs",
+    "pages/pool/pool",
+    "pages/strong/strong",
+    "pages/rank/rank",
+    "pages/mine/mine"
+  ],
+  "window": {
+    "navigationBarBackgroundColor": "#ffffff",
+    "navigationBarTextStyle": "black",
+    "navigationBarTitleText": "量化选股大师"
+  },
+  "tabBar": {
+    "color": "#999999",
+    "selectedColor": "#5d55e8",
+    "backgroundColor": "#ffffff",
+    "list": [
+      {
+        "pagePath": "pages/index/index",
+        "text": "打分查询",
+        "iconPath": "images/tab_search.png",
+        "selectedIconPath": "images/tab_search_active.png"
+      },
+      {
+        "pagePath": "pages/pool/pool",
+        "text": "超短池",
+        "iconPath": "images/tab_short.png",
+        "selectedIconPath": "images/tab_short_active.png"
+      },
+      {
+        "pagePath": "pages/strong/strong",
+        "text": "强势池",
+        "iconPath": "images/tab_strong.png",
+        "selectedIconPath": "images/tab_strong_active.png"
+      },
+      {
+        "pagePath": "pages/rank/rank",
+        "text": "模拟排名",
+        "iconPath": "images/tab_rank.png",
+        "selectedIconPath": "images/tab_rank_active.png"
+      },
+      {
+        "pagePath": "pages/mine/mine",
+        "text": "个人中心",
+        "iconPath": "images/tab_mine.png",
+        "selectedIconPath": "images/tab_mine_active.png"
+      }
+    ]
+  },
+  "style": "v2",
+  "componentFramework": "glass-easel",
+  "sitemapLocation": "sitemap.json",
+  "lazyCodeLoading": "requiredComponents"
+}

+ 10 - 0
app.wxss

@@ -0,0 +1,10 @@
+/**app.wxss**/
+.container {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: space-between;
+  padding: 200rpx 0;
+  box-sizing: border-box;
+} 

BIN
images/tab_mine.png


BIN
images/tab_mine_active.png


BIN
images/tab_rank.png


BIN
images/tab_rank_active.png


BIN
images/tab_search.png


BIN
images/tab_search_active.png


BIN
images/tab_short.png


BIN
images/tab_short_active.png


BIN
images/tab_strong.png


BIN
images/tab_strong_active.png


+ 86 - 0
pages/index/index.js

@@ -0,0 +1,86 @@
+Page({
+  data: {
+    keyword: '',
+    loading: false,
+    hasSearched: false,
+    errorMsg: '',
+    result: null,
+  },
+
+  onKeywordChange(e) {
+    this.setData({
+      keyword: e.detail.value || '',
+    });
+  },
+
+  onSearch() {
+    const keyword = (this.data.keyword || '').trim();
+    if (!keyword) {
+      wx.showToast({
+        title: '请输入股票代码或名称',
+        icon: 'none',
+      });
+      return;
+    }
+
+    this.setData({
+      loading: true,
+      hasSearched: true,
+      errorMsg: '',
+      result: null,
+    });
+
+    // TODO: 将 baseUrl 修改为你自己的后端域名(并在小程序后台配置 request 合法域名)
+    const baseUrl = 'https://api.your-domain.com';
+
+    wx.request({
+      url: `${baseUrl}/v1/score/query`,
+      method: 'POST',
+      header: {
+        'content-type': 'application/json',
+      },
+      data: {
+        keyword,
+      },
+      success: (res) => {
+        if (res.statusCode !== 200 || !res.data) {
+          this.setData({
+            errorMsg: '服务暂不可用,请稍后重试',
+          });
+          return;
+        }
+
+        const { code, message, data } = res.data;
+        if (code !== 0 || !data) {
+          this.setData({
+            errorMsg: message || '未查询到相关股票,请检查代码或名称',
+          });
+          return;
+        }
+
+        this.setData({
+          result: {
+            stockCode: data.stockCode,
+            stockName: data.stockName,
+            market: data.market || '',
+            score: data.score,
+            scoreLevel: data.scoreLevel || '',
+            scoreDate: data.scoreDate || '',
+            price: data.price || '',
+            changePercent: data.changePercent || 0,
+          },
+        });
+      },
+      fail: () => {
+        this.setData({
+          errorMsg: '网络异常,请检查网络后重试',
+        });
+      },
+      complete: () => {
+        this.setData({
+          loading: false,
+        });
+      },
+    });
+  },
+});

+ 4 - 0
pages/index/index.json

@@ -0,0 +1,4 @@
+{
+  "usingComponents": {
+  }
+}

+ 101 - 0
pages/index/index.wxml

@@ -0,0 +1,101 @@
+<scroll-view class="page" scroll-y>
+  <!-- 顶部标题(量化选股大师) -->
+  <view class="page-title">量化选股大师</view>
+
+  <!-- 顶部查询卡片 -->
+  <view class="card search-card">
+    <text class="card-title">量化分数实时查询 & 历史数据</text>
+
+    <view class="search-row">
+      <input
+        class="search-input"
+        type="text"
+        placeholder="请输入股票代码/名称 (如: 600030)"
+        placeholder-class="search-placeholder"
+        confirm-type="search"
+        value="{{keyword}}"
+        bindinput="onKeywordChange"
+        bindconfirm="onSearch"
+      />
+      <view class="search-button" bindtap="onSearch">
+        <text class="icon-search"></text>
+      </view>
+    </view>
+
+    <text class="search-tip">支持A股代码或名称模糊查询</text>
+  </view>
+
+  <!-- 查询结果 -->
+  <view wx:if="{{hasSearched}}" class="card result-card">
+    <view wx:if="{{loading}}">
+      <text class="result-loading">正在查询,请稍候...</text>
+    </view>
+    <view wx:elif="{{errorMsg}}">
+      <text class="result-error">{{errorMsg}}</text>
+    </view>
+    <view wx:elif="{{result}}">
+      <view class="result-header">
+        <view class="result-name-line">
+          <text class="result-name">{{result.stockName}}</text>
+          <text class="result-code">{{result.stockCode}}</text>
+          <text class="result-market">{{result.market}}</text>
+        </view>
+        <view class="result-score-block">
+          <text class="result-score">{{result.score}}</text>
+          <text class="result-score-label">量化分数</text>
+        </view>
+      </view>
+
+      <view class="result-meta">
+        <text class="meta-item">评分日期:{{result.scoreDate}}</text>
+        <text class="meta-item" wx:if="{{result.scoreLevel}}">评级:{{result.scoreLevel}}</text>
+      </view>
+
+      <view class="result-meta" wx:if="{{result.price}}">
+        <text class="meta-item">
+          现价:{{result.price}} 元
+          <text class="meta-change {{result.changePercent >= 0 ? 'up' : 'down'}}">
+            {{result.changePercent >= 0 ? '+' : ''}}{{result.changePercent}}%
+          </text>
+        </text>
+      </view>
+    </view>
+  </view>
+
+  <!-- 量化投资的优势 -->
+  <view class="card advantage-card">
+    <view class="card-header">
+      <view class="icon-circle">
+        <text class="icon-check">✓</text>
+      </view>
+      <text class="card-header-title">量化投资的优势</text>
+    </view>
+
+    <view class="advantage-item">
+      <text class="advantage-label">客观纪律性:</text>
+      <text class="advantage-desc">排除情绪干扰,严格执行预设的交易信号。</text>
+    </view>
+    <view class="advantage-item">
+      <text class="advantage-label">高效覆盖广度:</text>
+      <text class="advantage-desc">能同时分析数千只股票,人工无法企及。</text>
+    </view>
+  </view>
+
+  <!-- 风险提示(免责声明) -->
+  <view class="card risk-card">
+    <view class="card-header">
+      <view class="icon-warning">
+        <text class="icon-warning-text">!</text>
+      </view>
+      <text class="card-header-title">风险提示(免责声明)</text>
+    </view>
+
+    <text class="risk-text">
+      本系统的量化分数和股票池信息均基于历史数据和特定模型计算,并非对未来市场的保证或预测。市场环境瞬息万变,
+      量化模型可能存在失效或回撤风险。请勿将本系统数据作为投资决策的唯一依据,请您充分理解股票投资风险,并独立做出投资判断。
+    </text>
+  </view>
+
+  <!-- 预留底部空间,避免被 tabBar 遮挡 -->
+  <view class="bottom-safe-area"></view>
+</scroll-view>

+ 258 - 0
pages/index/index.wxss

@@ -0,0 +1,258 @@
+/** 首页量化查询界面样式 **/
+
+.page {
+  height: 100vh;
+  box-sizing: border-box;
+  padding: 32rpx 32rpx 48rpx;
+  background: #f5f6fb;
+}
+
+.page-title {
+  margin-top: 8rpx;
+  margin-bottom: 24rpx;
+  text-align: center;
+  font-size: 32rpx;
+  font-weight: 700;
+  color: #5d55e8;
+  letter-spacing: 4rpx;
+}
+
+.card {
+  background: #ffffff;
+  border-radius: 24rpx;
+  padding: 32rpx 32rpx 36rpx;
+  box-shadow: 0 16rpx 40rpx rgba(37, 52, 94, 0.08);
+  margin-bottom: 32rpx;
+}
+
+.search-card {
+  margin-top: 16rpx;
+}
+
+.card-title {
+  display: block;
+  font-size: 32rpx;
+  font-weight: 600;
+  color: #222222;
+  margin-bottom: 32rpx;
+}
+
+.search-row {
+  display: flex;
+  align-items: center;
+  background: #f7f8fc;
+  border-radius: 999rpx;
+  padding: 0 8rpx 0 32rpx;
+  height: 96rpx;
+  box-sizing: border-box;
+}
+
+.search-input {
+  flex: 1;
+  font-size: 28rpx;
+  color: #222222;
+}
+
+.search-placeholder {
+  color: #b4b8c6;
+}
+
+.search-button {
+  width: 96rpx;
+  height: 80rpx;
+  margin-left: 12rpx;
+  border-radius: 999rpx;
+  background: linear-gradient(135deg, #5d55e8, #7568ff);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-shadow: 0 12rpx 24rpx rgba(93, 85, 232, 0.4);
+}
+
+.icon-search {
+  width: 0;
+  height: 0;
+  border-radius: 50%;
+  border: 10rpx solid #ffffff;
+  border-color: #ffffff transparent transparent transparent;
+  position: relative;
+}
+
+.icon-search::after {
+  content: '';
+  position: absolute;
+  width: 24rpx;
+  height: 4rpx;
+  border-radius: 999rpx;
+  background: #ffffff;
+  transform: rotate(45deg);
+  right: -20rpx;
+  bottom: -6rpx;
+}
+
+.search-tip {
+  display: block;
+  margin-top: 20rpx;
+  font-size: 24rpx;
+  color: #9ca2b5;
+}
+
+.result-card {
+  margin-top: 8rpx;
+}
+
+.result-loading {
+  font-size: 26rpx;
+  color: #666a7f;
+}
+
+.result-error {
+  font-size: 26rpx;
+  color: #ff5b5b;
+}
+
+.result-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16rpx;
+}
+
+.result-name-line {
+  display: flex;
+  flex-direction: column;
+}
+
+.result-name {
+  font-size: 32rpx;
+  font-weight: 600;
+  color: #222222;
+}
+
+.result-code {
+  margin-top: 6rpx;
+  font-size: 24rpx;
+  color: #8187a0;
+}
+
+.result-market {
+  margin-top: 2rpx;
+  font-size: 22rpx;
+  color: #b0b4c5;
+}
+
+.result-score-block {
+  min-width: 140rpx;
+  padding: 12rpx 20rpx;
+  border-radius: 20rpx;
+  background: linear-gradient(135deg, #5d55e8, #7568ff);
+  text-align: right;
+  color: #ffffff;
+}
+
+.result-score {
+  font-size: 40rpx;
+  font-weight: 700;
+}
+
+.result-score-label {
+  display: block;
+  margin-top: 4rpx;
+  font-size: 20rpx;
+  opacity: 0.9;
+}
+
+.result-meta {
+  margin-top: 10rpx;
+}
+
+.meta-item {
+  font-size: 24rpx;
+  color: #666a7f;
+}
+
+.meta-change {
+  margin-left: 10rpx;
+}
+
+.meta-change.up {
+  color: #e53935;
+}
+
+.meta-change.down {
+  color: #1a9b4a;
+}
+
+.card-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 24rpx;
+}
+
+.card-header-title {
+  font-size: 30rpx;
+  font-weight: 600;
+  color: #222222;
+}
+
+.icon-circle {
+  width: 40rpx;
+  height: 40rpx;
+  border-radius: 50%;
+  background: #e7f7ef;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 16rpx;
+}
+
+.icon-check {
+  font-size: 24rpx;
+  color: #28a745;
+}
+
+.advantage-item {
+  margin-bottom: 12rpx;
+  font-size: 26rpx;
+  line-height: 1.6;
+  color: #4a4f63;
+}
+
+.advantage-label {
+  font-weight: 600;
+}
+
+.advantage-desc {
+  font-weight: 400;
+}
+
+.risk-card {
+  margin-bottom: 24rpx;
+}
+
+.icon-warning {
+  width: 40rpx;
+  height: 40rpx;
+  border-radius: 50%;
+  background: #ffecef;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 16rpx;
+}
+
+.icon-warning-text {
+  font-size: 26rpx;
+  color: #ff5b5b;
+}
+
+.risk-text {
+  display: block;
+  font-size: 24rpx;
+  line-height: 1.8;
+  color: #666a7f;
+}
+
+.bottom-safe-area {
+  height: 80rpx;
+}

+ 18 - 0
pages/logs/logs.js

@@ -0,0 +1,18 @@
+// logs.js
+const util = require('../../utils/util.js')
+
+Page({
+  data: {
+    logs: []
+  },
+  onLoad() {
+    this.setData({
+      logs: (wx.getStorageSync('logs') || []).map(log => {
+        return {
+          date: util.formatTime(new Date(log)),
+          timeStamp: log
+        }
+      })
+    })
+  }
+})

+ 4 - 0
pages/logs/logs.json

@@ -0,0 +1,4 @@
+{
+  "usingComponents": {
+  }
+}

+ 6 - 0
pages/logs/logs.wxml

@@ -0,0 +1,6 @@
+<!--logs.wxml-->
+<scroll-view class="scrollarea" scroll-y type="list">
+  <block wx:for="{{logs}}" wx:key="timeStamp" wx:for-item="log">
+    <view class="log-item">{{index + 1}}. {{log.date}}</view>
+  </block>
+</scroll-view>

+ 16 - 0
pages/logs/logs.wxss

@@ -0,0 +1,16 @@
+page {
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+}
+.scrollarea {
+  flex: 1;
+  overflow-y: hidden;
+}
+.log-item {
+  margin-top: 20rpx;
+  text-align: center;
+}
+.log-item:last-child {
+  padding-bottom: env(safe-area-inset-bottom);
+}

+ 3 - 0
pages/mine/mine.js

@@ -0,0 +1,3 @@
+Page({
+  data: {},
+});

+ 3 - 0
pages/mine/mine.json

@@ -0,0 +1,3 @@
+{
+  "navigationBarTitleText": "个人中心"
+}

+ 15 - 0
pages/mine/mine.wxml

@@ -0,0 +1,15 @@
+<view class="page-mine">
+  <text class="title">个人中心</text>
+  <text class="sub">这里后续可以展示用户信息和设置</text>
+</view>
+
+{
+  "cells": [],
+  "metadata": {
+    "language_info": {
+      "name": "python"
+    }
+  },
+  "nbformat": 4,
+  "nbformat_minor": 2
+}

+ 14 - 0
pages/mine/mine.wxss

@@ -0,0 +1,14 @@
+.page-mine {
+  padding: 32rpx;
+}
+
+.title {
+  font-size: 32rpx;
+  font-weight: 600;
+  margin-bottom: 16rpx;
+}
+
+.sub {
+  font-size: 26rpx;
+  color: #888888;
+}

+ 66 - 0
pages/pool/pool.js

@@ -0,0 +1,66 @@
+// pages/pool/pool.js
+Page({
+
+  /**
+   * 页面的初始数据
+   */
+  data: {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面加载
+   */
+  onLoad(options) {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面初次渲染完成
+   */
+  onReady() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面显示
+   */
+  onShow() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面隐藏
+   */
+  onHide() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面卸载
+   */
+  onUnload() {
+
+  },
+
+  /**
+   * 页面相关事件处理函数--监听用户下拉动作
+   */
+  onPullDownRefresh() {
+
+  },
+
+  /**
+   * 页面上拉触底事件的处理函数
+   */
+  onReachBottom() {
+
+  },
+
+  /**
+   * 用户点击右上角分享
+   */
+  onShareAppMessage() {
+
+  }
+})

+ 3 - 0
pages/pool/pool.json

@@ -0,0 +1,3 @@
+{
+  "usingComponents": {}
+}

+ 2 - 0
pages/pool/pool.wxml

@@ -0,0 +1,2 @@
+<!--pages/pool/pool.wxml-->
+<text>pages/pool/pool.wxml</text>

+ 1 - 0
pages/pool/pool.wxss

@@ -0,0 +1 @@
+/* pages/pool/pool.wxss */

+ 3 - 0
pages/rank/rank.js

@@ -0,0 +1,3 @@
+Page({
+  data: {},
+});

+ 3 - 0
pages/rank/rank.json

@@ -0,0 +1,3 @@
+{
+  "navigationBarTitleText": "模拟排名"
+}

+ 15 - 0
pages/rank/rank.wxml

@@ -0,0 +1,15 @@
+<view class="page-rank">
+  <text class="title">模拟排名</text>
+  <text class="sub">这里后续可以展示策略或股票排名</text>
+</view>
+
+{
+  "cells": [],
+  "metadata": {
+    "language_info": {
+      "name": "python"
+    }
+  },
+  "nbformat": 4,
+  "nbformat_minor": 2
+}

+ 14 - 0
pages/rank/rank.wxss

@@ -0,0 +1,14 @@
+.page-rank {
+  padding: 32rpx;
+}
+
+.title {
+  font-size: 32rpx;
+  font-weight: 600;
+  margin-bottom: 16rpx;
+}
+
+.sub {
+  font-size: 26rpx;
+  color: #888888;
+}

+ 3 - 0
pages/strong/strong.js

@@ -0,0 +1,3 @@
+Page({
+  data: {},
+});

+ 4 - 0
pages/strong/strong.json

@@ -0,0 +1,4 @@
+{
+  "navigationBarTitleText": "强势池"
+}
+

+ 15 - 0
pages/strong/strong.wxml

@@ -0,0 +1,15 @@
+<view class="page-strong">
+  <text class="title">强势池</text>
+  <text class="sub">这里后续可以展示强势股票列表</text>
+</view>
+
+{
+  "cells": [],
+  "metadata": {
+    "language_info": {
+      "name": "python"
+    }
+  },
+  "nbformat": 4,
+  "nbformat_minor": 2
+}

+ 14 - 0
pages/strong/strong.wxss

@@ -0,0 +1,14 @@
+.page-strong {
+  padding: 32rpx;
+}
+
+.title {
+  font-size: 32rpx;
+  font-weight: 600;
+  margin-bottom: 16rpx;
+}
+
+.sub {
+  font-size: 26rpx;
+  color: #888888;
+}

+ 41 - 0
project.config.json

@@ -0,0 +1,41 @@
+{
+  "compileType": "miniprogram",
+  "libVersion": "trial",
+  "packOptions": {
+    "ignore": [],
+    "include": []
+  },
+  "setting": {
+    "coverView": true,
+    "es6": true,
+    "postcss": true,
+    "minified": true,
+    "enhance": true,
+    "showShadowRootInWxmlPanel": true,
+    "packNpmRelationList": [],
+    "babelSetting": {
+      "ignore": [],
+      "disablePlugins": [],
+      "outputPath": ""
+    },
+    "compileWorklet": false,
+    "uglifyFileName": false,
+    "uploadWithSourceMap": true,
+    "packNpmManually": false,
+    "minifyWXSS": true,
+    "minifyWXML": true,
+    "localPlugins": false,
+    "condition": false,
+    "swc": false,
+    "disableSWC": true,
+    "disableUseStrict": false,
+    "useCompilerPlugins": false
+  },
+  "condition": {},
+  "editorSetting": {
+    "tabIndent": "auto",
+    "tabSize": 2
+  },
+  "appid": "wx6e0cde280c822198",
+  "simulatorPluginLibVersion": {}
+}

+ 23 - 0
project.private.config.json

@@ -0,0 +1,23 @@
+{
+  "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
+  "projectname": "miniprogram-1",
+  "setting": {
+    "compileHotReLoad": true,
+    "urlCheck": true,
+    "coverView": true,
+    "lazyloadPlaceholderEnable": false,
+    "skylineRenderEnable": false,
+    "preloadBackgroundData": false,
+    "autoAudits": false,
+    "useApiHook": true,
+    "useApiHostProcess": true,
+    "showShadowRootInWxmlPanel": true,
+    "useStaticServer": false,
+    "useLanDebug": false,
+    "showES6CompileOption": false,
+    "bigPackageSizeSupport": false,
+    "checkInvalidKey": true,
+    "ignoreDevUnusedFiles": true
+  },
+  "libVersion": "3.12.1"
+}

+ 7 - 0
sitemap.json

@@ -0,0 +1,7 @@
+{
+  "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
+  "rules": [{
+  "action": "allow",
+  "page": "*"
+  }]
+}

+ 19 - 0
utils/util.js

@@ -0,0 +1,19 @@
+const formatTime = date => {
+  const year = date.getFullYear()
+  const month = date.getMonth() + 1
+  const day = date.getDate()
+  const hour = date.getHours()
+  const minute = date.getMinutes()
+  const second = date.getSeconds()
+
+  return `${[year, month, day].map(formatNumber).join('/')} ${[hour, minute, second].map(formatNumber).join(':')}`
+}
+
+const formatNumber = n => {
+  n = n.toString()
+  return n[1] ? n : `0${n}`
+}
+
+module.exports = {
+  formatTime
+}