scw 1 месяц назад
Сommit
0809c1597d

+ 23 - 0
.gitignore

@@ -0,0 +1,23 @@
+.DS_Store
+node_modules/
+unpackage/
+dist/
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Editor directories and files
+.project
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw*

+ 19 - 0
README.md

@@ -0,0 +1,19 @@
+# my-project
+
+## Project setup
+```
+npm install
+```
+
+### Compiles and hot-reloads for development
+```
+npm run serve
+```
+
+### Compiles and minifies for production
+```
+npm run build
+```
+
+### Customize configuration
+See [Configuration Reference](https://cli.vuejs.org/config/).

+ 81 - 0
babel.config.js

@@ -0,0 +1,81 @@
+const webpack = require('webpack')
+const plugins = []
+
+if (process.env.UNI_OPT_TREESHAKINGNG) {
+  plugins.push(require('@dcloudio/vue-cli-plugin-uni-optimize/packages/babel-plugin-uni-api/index.js'))
+}
+
+if (
+  (
+    process.env.UNI_PLATFORM === 'app-plus' &&
+    process.env.UNI_USING_V8
+  ) ||
+  (
+    process.env.UNI_PLATFORM === 'h5' &&
+    process.env.UNI_H5_BROWSER === 'builtin'
+  )
+) {
+  const path = require('path')
+
+  const isWin = /^win/.test(process.platform)
+
+  const normalizePath = path => (isWin ? path.replace(/\\/g, '/') : path)
+
+  const input = normalizePath(process.env.UNI_INPUT_DIR)
+  try {
+    plugins.push([
+      require('@dcloudio/vue-cli-plugin-hbuilderx/packages/babel-plugin-console'),
+      {
+        file (file) {
+          file = normalizePath(file)
+          if (file.indexOf(input) === 0) {
+            return path.relative(input, file)
+          }
+          return false
+        }
+      }
+    ])
+  } catch (e) { }
+}
+
+process.UNI_LIBRARIES = process.UNI_LIBRARIES || ['@dcloudio/uni-ui']
+process.UNI_LIBRARIES.forEach(libraryName => {
+  plugins.push([
+    'import',
+    {
+      'libraryName': libraryName,
+      'customName': (name) => {
+        return `${libraryName}/lib/${name}/${name}`
+      }
+    }
+  ])
+})
+
+if (process.env.UNI_PLATFORM !== 'h5') {
+  plugins.push('@babel/plugin-transform-runtime')
+}
+
+const config = {
+  presets: [
+    [
+      '@vue/app',
+      {
+        modules: webpack.version[0] > 4 ? 'auto' : 'commonjs',
+        useBuiltIns: process.env.UNI_PLATFORM === 'h5' ? 'usage' : 'entry'
+      }
+    ]
+  ],
+  plugins
+}
+
+const UNI_H5_TEST = '**/@dcloudio/uni-h5/dist/index.umd.min.js'
+if (process.env.NODE_ENV === 'production') {
+  config.overrides = [{
+    test: UNI_H5_TEST,
+    compact: true,
+  }]
+} else {
+  config.ignore = [UNI_H5_TEST]
+}
+
+module.exports = config

Разница между файлами не показана из-за своего большого размера
+ 37910 - 0
package-lock.json


+ 109 - 0
package.json

@@ -0,0 +1,109 @@
+{
+    "name": "my-project",
+    "version": "0.1.0",
+    "private": true,
+    "scripts": {
+        "serve": "npm run dev:h5",
+        "build": "npm run build:h5",
+        "build:app-plus": "cross-env NODE_ENV=production UNI_PLATFORM=app-plus vue-cli-service uni-build",
+        "build:custom": "cross-env NODE_ENV=production uniapp-cli custom",
+        "build:h5": "cross-env NODE_ENV=production UNI_PLATFORM=h5 vue-cli-service uni-build",
+        "build:mp-360": "cross-env NODE_ENV=production UNI_PLATFORM=mp-360 vue-cli-service uni-build",
+        "build:mp-alipay": "cross-env NODE_ENV=production UNI_PLATFORM=mp-alipay vue-cli-service uni-build",
+        "build:mp-baidu": "cross-env NODE_ENV=production UNI_PLATFORM=mp-baidu vue-cli-service uni-build",
+        "build:mp-harmony": "cross-env NODE_ENV=production UNI_PLATFORM=mp-harmony vue-cli-service uni-build",
+        "build:mp-jd": "cross-env NODE_ENV=production UNI_PLATFORM=mp-jd vue-cli-service uni-build",
+        "build:mp-kuaishou": "cross-env NODE_ENV=production UNI_PLATFORM=mp-kuaishou vue-cli-service uni-build",
+        "build:mp-lark": "cross-env NODE_ENV=production UNI_PLATFORM=mp-lark vue-cli-service uni-build",
+        "build:mp-qq": "cross-env NODE_ENV=production UNI_PLATFORM=mp-qq vue-cli-service uni-build",
+        "build:mp-toutiao": "cross-env NODE_ENV=production UNI_PLATFORM=mp-toutiao vue-cli-service uni-build",
+        "build:mp-weixin": "cross-env NODE_ENV=production UNI_PLATFORM=mp-weixin vue-cli-service uni-build",
+        "build:mp-xhs": "cross-env NODE_ENV=production UNI_PLATFORM=mp-xhs vue-cli-service uni-build",
+        "build:quickapp-native": "cross-env NODE_ENV=production UNI_PLATFORM=quickapp-native vue-cli-service uni-build",
+        "build:quickapp-webview": "cross-env NODE_ENV=production UNI_PLATFORM=quickapp-webview vue-cli-service uni-build",
+        "build:quickapp-webview-huawei": "cross-env NODE_ENV=production UNI_PLATFORM=quickapp-webview-huawei vue-cli-service uni-build",
+        "build:quickapp-webview-union": "cross-env NODE_ENV=production UNI_PLATFORM=quickapp-webview-union vue-cli-service uni-build",
+        "dev:app-plus": "cross-env NODE_ENV=development UNI_PLATFORM=app-plus vue-cli-service uni-build --watch",
+        "dev:custom": "cross-env NODE_ENV=development uniapp-cli custom",
+        "dev:h5": "cross-env NODE_ENV=development UNI_PLATFORM=h5 vue-cli-service uni-serve",
+        "dev:mp-360": "cross-env NODE_ENV=development UNI_PLATFORM=mp-360 vue-cli-service uni-build --watch",
+        "dev:mp-alipay": "cross-env NODE_ENV=development UNI_PLATFORM=mp-alipay vue-cli-service uni-build --watch",
+        "dev:mp-baidu": "cross-env NODE_ENV=development UNI_PLATFORM=mp-baidu vue-cli-service uni-build --watch",
+        "dev:mp-harmony": "cross-env NODE_ENV=development UNI_PLATFORM=mp-harmony vue-cli-service uni-build --watch",
+        "dev:mp-jd": "cross-env NODE_ENV=development UNI_PLATFORM=mp-jd vue-cli-service uni-build --watch",
+        "dev:mp-kuaishou": "cross-env NODE_ENV=development UNI_PLATFORM=mp-kuaishou vue-cli-service uni-build --watch",
+        "dev:mp-lark": "cross-env NODE_ENV=development UNI_PLATFORM=mp-lark vue-cli-service uni-build --watch",
+        "dev:mp-qq": "cross-env NODE_ENV=development UNI_PLATFORM=mp-qq vue-cli-service uni-build --watch",
+        "dev:mp-toutiao": "cross-env NODE_ENV=development UNI_PLATFORM=mp-toutiao vue-cli-service uni-build --watch",
+        "dev:mp-weixin": "cross-env NODE_ENV=development UNI_PLATFORM=mp-weixin vue-cli-service uni-build --watch",
+        "dev:mp-xhs": "cross-env NODE_ENV=development UNI_PLATFORM=mp-xhs vue-cli-service uni-build --watch",
+        "dev:quickapp-native": "cross-env NODE_ENV=development UNI_PLATFORM=quickapp-native vue-cli-service uni-build --watch",
+        "dev:quickapp-webview": "cross-env NODE_ENV=development UNI_PLATFORM=quickapp-webview vue-cli-service uni-build --watch",
+        "dev:quickapp-webview-huawei": "cross-env NODE_ENV=development UNI_PLATFORM=quickapp-webview-huawei vue-cli-service uni-build --watch",
+        "dev:quickapp-webview-union": "cross-env NODE_ENV=development UNI_PLATFORM=quickapp-webview-union vue-cli-service uni-build --watch",
+        "info": "node node_modules/@dcloudio/vue-cli-plugin-uni/commands/info.js",
+        "serve:quickapp-native": "node node_modules/@dcloudio/uni-quickapp-native/bin/serve.js",
+        "test:android": "cross-env UNI_PLATFORM=app-plus UNI_OS_NAME=android jest -i",
+        "test:h5": "cross-env UNI_PLATFORM=h5 jest -i",
+        "test:ios": "cross-env UNI_PLATFORM=app-plus UNI_OS_NAME=ios jest -i",
+        "test:mp-baidu": "cross-env UNI_PLATFORM=mp-baidu jest -i",
+        "test:mp-weixin": "cross-env UNI_PLATFORM=mp-weixin jest -i"
+    },
+    "dependencies": {
+        "@dcloudio/uni-app": "^2.0.2-4080720251210002",
+        "@dcloudio/uni-app-plus": "^2.0.2-4080720251210002",
+        "@dcloudio/uni-h5": "^2.0.2-4080720251210002",
+        "@dcloudio/uni-i18n": "^2.0.2-4080720251210002",
+        "@dcloudio/uni-mp-360": "^2.0.2-4080720251210002",
+        "@dcloudio/uni-mp-alipay": "^2.0.2-4080720251210002",
+        "@dcloudio/uni-mp-baidu": "^2.0.2-4080720251210002",
+        "@dcloudio/uni-mp-harmony": "^2.0.2-4080720251210002",
+        "@dcloudio/uni-mp-jd": "^2.0.2-4080720251210002",
+        "@dcloudio/uni-mp-kuaishou": "^2.0.2-4080720251210002",
+        "@dcloudio/uni-mp-lark": "^2.0.2-4080720251210002",
+        "@dcloudio/uni-mp-qq": "^2.0.2-4080720251210002",
+        "@dcloudio/uni-mp-toutiao": "^2.0.2-4080720251210002",
+        "@dcloudio/uni-mp-vue": "^2.0.2-4080720251210002",
+        "@dcloudio/uni-mp-weixin": "^2.0.2-4080720251210002",
+        "@dcloudio/uni-mp-xhs": "^2.0.2-4080720251210002",
+        "@dcloudio/uni-quickapp-native": "^2.0.2-4080720251210002",
+        "@dcloudio/uni-quickapp-webview": "^2.0.2-4080720251210002",
+        "@dcloudio/uni-stacktracey": "^2.0.2-4080720251210002",
+        "@dcloudio/uni-stat": "^2.0.2-4080720251210002",
+        "@vue/shared": "^3.0.0",
+        "core-js": "^3.8.3",
+        "flyio": "^0.6.2",
+        "uview-ui": "^2.0.38",
+        "vue": ">= 2.6.14 < 2.7",
+        "vuex": "^3.2.0"
+    },
+    "devDependencies": {
+        "@dcloudio/types": "^3.3.2",
+        "@dcloudio/uni-automator": "^2.0.2-4080720251210002",
+        "@dcloudio/uni-cli-i18n": "^2.0.2-4080720251210002",
+        "@dcloudio/uni-cli-shared": "^2.0.2-4080720251210002",
+        "@dcloudio/uni-helper-json": "*",
+        "@dcloudio/uni-migration": "^2.0.2-4080720251210002",
+        "@dcloudio/uni-template-compiler": "^2.0.2-4080720251210002",
+        "@dcloudio/vue-cli-plugin-hbuilderx": "^2.0.2-4080720251210002",
+        "@dcloudio/vue-cli-plugin-uni": "^2.0.2-4080720251210002",
+        "@dcloudio/vue-cli-plugin-uni-optimize": "^2.0.2-4080720251210002",
+        "@dcloudio/webpack-uni-mp-loader": "^2.0.2-4080720251210002",
+        "@dcloudio/webpack-uni-pages-loader": "^2.0.2-4080720251210002",
+        "@vue/cli-plugin-babel": "~5.0.0",
+        "@vue/cli-service": "~5.0.0",
+        "babel-plugin-import": "^1.11.0",
+        "cross-env": "^7.0.2",
+        "jest": "^25.4.0",
+        "postcss-comment": "^2.0.0",
+        "sass": "^1.97.3",
+        "vue-template-compiler": ">= 2.6.14 < 2.7"
+    },
+    "browserslist": [
+        "Android >= 4.4",
+        "ios >= 9"
+    ],
+    "uni-app": {
+        "scripts": {}
+    }
+}

+ 27 - 0
postcss.config.js

@@ -0,0 +1,27 @@
+const path = require('path')
+const webpack = require('webpack')
+const config = {
+  parser: require('postcss-comment'),
+  plugins: [
+    require('postcss-import')({
+      resolve (id, basedir, importOptions) {
+        if (id.startsWith('~@/')) {
+          return path.resolve(process.env.UNI_INPUT_DIR, id.substr(3))
+        } else if (id.startsWith('@/')) {
+          return path.resolve(process.env.UNI_INPUT_DIR, id.substr(2))
+        } else if (id.startsWith('/') && !id.startsWith('//')) {
+          return path.resolve(process.env.UNI_INPUT_DIR, id.substr(1))
+        }
+        return id
+      }
+    }),
+    require('autoprefixer')({
+      remove: process.env.UNI_PLATFORM !== 'h5'
+    }),
+    require('@dcloudio/vue-cli-plugin-uni/packages/postcss')
+  ]
+}
+if (webpack.version[0] > 4) {
+  delete config.parser
+}
+module.exports = config

+ 25 - 0
public/index.html

@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+
+    <head>
+        <meta charset="utf-8">
+        <meta http-equiv="X-UA-Compatible" content="IE=edge">
+        <title>
+            <%= htmlWebpackPlugin.options.title %>
+        </title>
+        <script>
+            var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))
+            document.write('<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' + (coverSupport ? ', viewport-fit=cover' : '') + '" />')
+        </script>
+        <link rel="stylesheet" href="<%= BASE_URL %>static/index.<%= VUE_APP_INDEX_CSS_HASH %>.css" />
+    </head>
+
+    <body>
+        <noscript>
+            <strong>Please enable JavaScript to continue.</strong>
+        </noscript>
+        <div id="app"></div>
+        <!-- built files will be auto injected -->
+    </body>
+
+</html>

+ 11 - 0
shims-uni.d.ts

@@ -0,0 +1,11 @@
+/// <reference types='@dcloudio/types' />
+import Vue from 'vue'
+declare module "vue/types/options" {
+  type Hooks = App.AppInstance & Page.PageInstance;
+  interface ComponentOptions<V extends Vue> extends Hooks {
+    /**
+     * 组件类型
+     */
+    mpType?: string;
+  }
+}

+ 4 - 0
shims-vue.d.ts

@@ -0,0 +1,4 @@
+declare module "*.vue" {
+  import Vue from 'vue'
+  export default Vue
+}

+ 44 - 0
src/App.vue

@@ -0,0 +1,44 @@
+<script>
+export default {
+  globalData: {
+    statusBarHeight: 20,
+  },
+  onLaunch: function () {
+    console.log("App Launch");
+    // 获取系统信息中的状态栏高度
+    const systemInfo = uni.getSystemInfoSync();
+    this.globalData.statusBarHeight = systemInfo.statusBarHeight || 20;
+  },
+  onShow: function () {
+    console.log("App Show");
+  },
+  onHide: function () {
+    console.log("App Hide");
+  },
+};
+</script>
+
+<style>
+/* 全局样式 */
+page {
+  background-color: #f5f6fa;
+  box-sizing: border-box;
+}
+
+* {
+  box-sizing: border-box;
+}
+
+/* 清除button默认样式 */
+button {
+  background: transparent;
+  border: none;
+  padding: 0;
+  margin: 0;
+  line-height: normal;
+}
+
+button::after {
+  border: none;
+}
+</style>

+ 12 - 0
src/main.js

@@ -0,0 +1,12 @@
+import Vue from 'vue';
+import App from './App';
+import './uni.promisify.adaptor';
+
+Vue.config.productionTip = false;
+
+App.mpType = 'app';
+
+const app = new Vue({
+    ...App
+});
+app.$mount();

+ 78 - 0
src/manifest.json

@@ -0,0 +1,78 @@
+{
+    "name": "",
+    "appid": "wx4616e2d1f3e57253",
+    "description": "",
+    "versionName": "1.0.0",
+    "versionCode": "100",
+    "transformPx": false,
+    "app-plus": {
+        /* 5+App特有相关 */ "usingComponents": true,
+        "splashscreen": {
+            "alwaysShowBeforeRender": true,
+            "waiting": true,
+            "autoclose": true,
+            "delay": 0
+        },
+        "modules": {
+            /* 模块配置 */
+        },
+        "distribute": {
+            /* 应用发布信息 */
+            "android": {
+                /* android打包配置 */
+                "permissions": [
+                    "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>",
+                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+                    "<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
+                    "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+                    "<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+                    "<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
+                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
+                ]
+            },
+            "ios": {
+                /* ios打包配置 */
+            },
+            "sdkConfigs": {
+                /* SDK配置 */
+            }
+        }
+    },
+    "quickapp": {
+        /* 快应用特有相关 */
+    },
+    "mp-weixin": {
+        /* 微信小程序特有相关 */ "appid": "wx4616e2d1f3e57253",
+        "setting": {
+            "urlCheck": false
+        },
+        "usingComponents": true
+    },
+    "mp-alipay": {
+        "usingComponents": true
+    },
+    "mp-baidu": {
+        "usingComponents": true
+    },
+    "mp-toutiao": {
+        "usingComponents": true
+    },
+    "mp-qq": {
+        "usingComponents": true
+    }
+}

+ 70 - 0
src/pages.json

@@ -0,0 +1,70 @@
+{
+    "pages": [
+        {
+            "path": "pages/index/index",
+            "style": {
+                "navigationBarTitleText": "首页",
+                "navigationStyle": "custom"
+            }
+        },
+        {
+            "path": "pages/login/login",
+            "style": {
+                "navigationBarTitleText": "登录",
+                "navigationStyle": "custom"
+            }
+        },
+        {
+            "path": "pages/rank/rank",
+            "style": {
+                "navigationBarTitleText": "排名",
+                "navigationStyle": "custom"
+            }
+        },
+        {
+            "path": "pages/profile/profile",
+            "style": {
+                "navigationBarTitleText": "我的"
+            }
+        },
+        {
+            "path": "pages/activity-detail/activity-detail",
+            "style": {
+                "navigationBarTitleText": "活动详情",
+                "navigationStyle": "custom"
+            }
+        }
+    ],
+    "globalStyle": {
+        "navigationBarTextStyle": "black",
+        "navigationBarTitleText": "打卡",
+        "navigationBarBackgroundColor": "#F8F8F8",
+        "backgroundColor": "#F5F5F5"
+    },
+    "tabBar": {
+        "color": "#999999",
+        "selectedColor": "#1677FF",
+        "backgroundColor": "#FFFFFF",
+        "borderStyle": "black",
+        "list": [
+            {
+                "pagePath": "pages/index/index",
+                "text": "首页",
+                "iconPath": "static/tabbar/home.png",
+                "selectedIconPath": "static/tabbar/home-active.png"
+            },
+            {
+                "pagePath": "pages/rank/rank",
+                "text": "排名",
+                "iconPath": "static/tabbar/rank.png",
+                "selectedIconPath": "static/tabbar/rank-active.png"
+            },
+            {
+                "pagePath": "pages/profile/profile",
+                "text": "我的",
+                "iconPath": "static/tabbar/profile.png",
+                "selectedIconPath": "static/tabbar/profile-active.png"
+            }
+        ]
+    }
+}

+ 714 - 0
src/pages/activity-detail/activity-detail.vue

@@ -0,0 +1,714 @@
+<template>
+  <view class="page-container">
+    <!-- 顶部导航 -->
+    <view class="header" :style="{ paddingTop: statusBarHeight + 'px' }">
+      <view class="back-btn" @tap="goBack">
+        <text class="back-icon">‹</text>
+      </view>
+      <view class="header-title">2026春季销售冲刺活动</view>
+    </view>
+
+    <!-- 统计卡片 -->
+    <view class="stats-cards">
+      <view class="stats-card">
+        <view class="stats-label">累计打卡天数</view>
+        <view class="stats-value">
+          <text class="stats-num">{{ stats.checkDays }}</text>
+          <text class="stats-unit"> 天</text>
+        </view>
+      </view>
+      <view class="stats-card">
+        <view class="stats-label">累计购物数量</view>
+        <view class="stats-value">
+          <text class="stats-num">{{ stats.saleCount }}</text>
+          <text class="stats-unit"> 件</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 打卡日历 -->
+    <view class="calendar-section">
+      <view class="calendar-header">
+        <view class="calendar-title">
+          <text class="calendar-icon">📅</text>
+          <text>打卡日历</text>
+        </view>
+        <picker
+          mode="date"
+          :value="currentDate"
+          fields="month"
+          @change="onMonthChange"
+        >
+          <view class="month-picker">
+            <text>{{ currentMonthText }}</text>
+            <text class="arrow">▼</text>
+          </view>
+        </picker>
+      </view>
+
+      <!-- 星期 -->
+      <view class="weekdays">
+        <text v-for="(day, index) in weekDays" :key="index" class="weekday">{{
+          day
+        }}</text>
+      </view>
+
+      <!-- 日历格子 -->
+      <view class="calendar-grid">
+        <view
+          v-for="(day, index) in calendarDays"
+          :key="index"
+          class="calendar-day"
+          :class="{
+            empty: !day.date,
+            checked: day.checked,
+            today: day.isToday,
+          }"
+          @tap="onDateClick(day)"
+        >
+          <text v-if="day.date" class="day-text">{{ day.day }}</text>
+          <text v-if="day.checked" class="check-icon">✓</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 打卡表单 -->
+    <view class="checkin-form">
+      <!-- 打卡说明 -->
+      <view class="checkin-intro">
+        <view class="intro-title">小票/朋友圈截图</view>
+      </view>
+
+      <!-- 图片上传 -->
+      <view class="upload-section">
+        <view class="upload-area" @tap="chooseImage">
+          <view class="upload-icon">📷</view>
+          <view class="upload-text">点击拍照或上传图片</view>
+        </view>
+        <!-- 预览已上传图片 -->
+        <view v-if="uploadedImage" class="image-preview">
+          <image :src="uploadedImage" mode="aspectFill" class="preview-img" />
+          <view class="delete-btn" @tap="deleteImage">✕</view>
+        </view>
+      </view>
+
+      <!-- 销售单品 -->
+      <view class="product-section">
+        <view class="section-title">销售单品</view>
+
+        <view class="product-list">
+          <view
+            v-for="(item, index) in products"
+            :key="index"
+            class="product-item"
+            :class="{ active: item.selected }"
+            @tap="selectProduct(index)"
+          >
+            <view class="product-name">{{ item.name }}</view>
+            <view class="product-input">
+              <text class="input-label">数量</text>
+              <input
+                type="number"
+                v-model="item.quantity"
+                class="quantity-input"
+                placeholder="0"
+                @tap.stop
+                @input="onQuantityChange(index)"
+              />
+              <text class="input-unit">盒</text>
+            </view>
+          </view>
+        </view>
+      </view>
+
+      <!-- 打卡按钮 -->
+      <view class="submit-section">
+        <button class="submit-btn" @tap="submitCheckin">立即打卡</button>
+      </view>
+
+      <!-- 打卡规则、活动奖励-->
+      <view class="checkin-rule">
+        <view class="item">打卡规则</view>
+        <view class="item">活动奖励</view>
+      </view>
+    </view>
+
+    <!-- 底部占位 -->
+    <view class="tabbar-placeholder"></view>
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      statusBarHeight: 0,
+      currentDate: "2026-01",
+      weekDays: ["日", "一", "二", "三", "四", "五", "六"],
+      calendarDays: [],
+      stats: {
+        checkDays: 15,
+        saleCount: 45,
+      },
+      // 已打卡的日期
+      checkedDates: [
+        "2026-01-03",
+        "2026-01-05",
+        "2026-01-08",
+        "2026-01-12",
+        "2026-01-15",
+        "2026-01-19",
+        "2026-01-20",
+        "2026-01-24",
+        "2026-01-25",
+        "2026-01-26",
+      ],
+      // 打卡表单数据
+      uploadedImage: "",
+      products: [
+        { name: "春季限定A款", quantity: 0, selected: false },
+        { name: "年度爆款B款", quantity: 0, selected: false },
+        { name: "高端定制C款", quantity: 0, selected: false },
+      ],
+    };
+  },
+  computed: {
+    currentMonthText() {
+      const [year, month] = this.currentDate.split("-");
+      return `${year}年${parseInt(month)}月`;
+    },
+  },
+  onLoad(options) {
+    this.statusBarHeight = getApp().globalData.statusBarHeight;
+    if (options.id) {
+      this.activityId = options.id;
+    }
+    this.generateCalendar();
+  },
+  methods: {
+    goBack() {
+      uni.navigateBack();
+    },
+    // 图片上传
+    chooseImage() {
+      uni.chooseImage({
+        count: 1,
+        sizeType: ["compressed"],
+        sourceType: ["album", "camera"],
+        success: (res) => {
+          this.uploadedImage = res.tempFilePaths[0];
+        },
+      });
+    },
+    deleteImage() {
+      this.uploadedImage = "";
+    },
+    // 商品选择
+    selectProduct(index) {
+      this.products[index].selected = !this.products[index].selected;
+    },
+    onQuantityChange(index) {
+      // 可以添加数量验证等逻辑
+    },
+    // 提交打卡
+    submitCheckin() {
+      // 检查是否上传图片
+      if (!this.uploadedImage) {
+        uni.showToast({
+          title: "请上传图片",
+          icon: "none",
+        });
+        return;
+      }
+
+      // 检查是否有填写销售数量
+      const selectedProducts = this.products.filter((p) => p.quantity > 0);
+      if (selectedProducts.length === 0) {
+        uni.showToast({
+          title: "请填写销售数量",
+          icon: "none",
+        });
+        return;
+      }
+
+      // 提交打卡
+      uni.showLoading({ title: "提交中..." });
+
+      setTimeout(() => {
+        uni.hideLoading();
+        // 更新统计数据
+        const totalQty = this.products.reduce(
+          (sum, p) => sum + (parseInt(p.quantity) || 0),
+          0
+        );
+        this.stats.checkDays += 1;
+        this.stats.saleCount += totalQty;
+
+        // 添加到已打卡日期
+        const today = new Date();
+        const todayStr = `${today.getFullYear()}-${String(
+          today.getMonth() + 1
+        ).padStart(2, "0")}-${String(today.getDate()).padStart(2, "0")}`;
+        if (!this.checkedDates.includes(todayStr)) {
+          this.checkedDates.push(todayStr);
+        }
+
+        // 重置表单
+        this.uploadedImage = "";
+        this.products.forEach((p) => {
+          p.quantity = 0;
+          p.selected = false;
+        });
+
+        // 刷新日历
+        this.generateCalendar();
+
+        uni.showToast({
+          title: "打卡成功",
+          icon: "success",
+        });
+      }, 1000);
+    },
+    onMonthChange(e) {
+      this.currentDate = e.detail.value.substring(0, 7);
+      this.generateCalendar();
+    },
+    onDateClick(day) {
+      if (!day.date) return;
+      // 点击日期可以查看当天打卡详情
+      const dateStr = `${this.currentDate}-${String(day.day).padStart(2, "0")}`;
+      uni.showToast({
+        title: dateStr,
+        icon: "none",
+      });
+    },
+    generateCalendar() {
+      const [year, month] = this.currentDate.split("-").map(Number);
+      const firstDay = new Date(year, month - 1, 1).getDay();
+      const daysInMonth = new Date(year, month, 0).getDate();
+      const today = new Date();
+      const todayStr = `${today.getFullYear()}-${String(
+        today.getMonth() + 1
+      ).padStart(2, "0")}-${String(today.getDate()).padStart(2, "0")}`;
+      const currentMonthStr = `${year}-${String(month).padStart(2, "0")}`;
+
+      const days = [];
+
+      // 填充空白
+      for (let i = 0; i < firstDay; i++) {
+        days.push({ date: "" });
+      }
+
+      // 填充日期
+      for (let d = 1; d <= daysInMonth; d++) {
+        const dateStr = `${currentMonthStr}-${String(d).padStart(2, "0")}`;
+        days.push({
+          day: d,
+          date: dateStr,
+          checked: this.checkedDates.includes(dateStr),
+          isToday: dateStr === todayStr,
+        });
+      }
+
+      this.calendarDays = days;
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.page-container {
+  min-height: 100vh;
+  background: #f5f6fa;
+}
+
+.header {
+  position: relative;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 88rpx;
+  background: #fff;
+}
+
+.back-btn {
+  position: absolute;
+  left: 24rpx;
+  width: 60rpx;
+  height: 60rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.back-icon {
+  font-size: 44rpx;
+  color: #333;
+  font-weight: bold;
+}
+
+.header-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #1a1a1a;
+}
+
+/* 统计卡片 */
+.stats-cards {
+  display: flex;
+  padding: 24rpx;
+  gap: 20rpx;
+}
+
+.stats-card {
+  flex: 1;
+  background: #1677ff;
+  border-radius: 16rpx;
+  padding: 28rpx 24rpx;
+}
+
+.stats-label {
+  font-size: 24rpx;
+  color: rgba(255, 255, 255, 0.85);
+  margin-bottom: 12rpx;
+}
+
+.stats-value {
+  display: flex;
+  align-items: baseline;
+}
+
+.stats-num {
+  font-size: 52rpx;
+  font-weight: bold;
+  color: #ffffff;
+}
+
+.stats-unit {
+  font-size: 26rpx;
+  color: rgba(255, 255, 255, 0.9);
+  margin-left: 4rpx;
+}
+
+/* 打卡按钮 */
+.checkin-btn-wrap {
+  padding: 0 24rpx 24rpx;
+}
+
+.checkin-btn {
+  width: 100%;
+  height: 88rpx;
+  background: #1677ff;
+  border-radius: 44rpx;
+  color: #fff;
+  font-size: 32rpx;
+  font-weight: bold;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border: none;
+
+  &::after {
+    border: none;
+  }
+}
+
+/* 打卡表单 */
+.checkin-form {
+  margin: 24rpx;
+  background: #ffffff;
+  border-radius: 16rpx;
+  padding: 24rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
+}
+
+.checkin-intro {
+  margin-bottom: 20rpx;
+}
+
+.intro-title {
+  font-size: 28rpx;
+  color: #666;
+}
+.checkin-rule {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-top: 50rpx;
+  margin-bottom: 30rpx;
+  .item {
+    padding: 0 36rpx;
+    font-size: 24rpx;
+    line-height: 1;
+    color: #1677ff;
+
+    &:first-child {
+      border-right: 1rpx solid #ddd;
+    }
+  }
+}
+/* 图片上传 */
+.upload-section {
+  position: relative;
+  margin-bottom: 24rpx;
+}
+
+.upload-area {
+  width: 100%;
+  height: 320rpx;
+  background: #f5f5f5;
+  border-radius: 16rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  border: 2rpx dashed #ddd;
+}
+
+.upload-icon {
+  font-size: 80rpx;
+  margin-bottom: 16rpx;
+}
+
+.upload-text {
+  font-size: 26rpx;
+  color: #999;
+}
+
+.image-preview {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  height: 320rpx;
+}
+
+.preview-img {
+  width: 100%;
+  height: 100%;
+  border-radius: 16rpx;
+}
+
+.delete-btn {
+  position: absolute;
+  top: 16rpx;
+  right: 16rpx;
+  width: 48rpx;
+  height: 48rpx;
+  background: rgba(0, 0, 0, 0.5);
+  border-radius: 50%;
+  color: #fff;
+  font-size: 28rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+/* 销售单品 */
+.product-section {
+  margin-bottom: 24rpx;
+}
+
+.section-title {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #1a1a1a;
+  margin-bottom: 20rpx;
+}
+
+.product-list {
+  display: flex;
+  flex-direction: column;
+  gap: 16rpx;
+}
+
+.product-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  background: #fafafa;
+  border-radius: 12rpx;
+  padding: 24rpx;
+  border: 2rpx solid transparent;
+
+  &.active {
+    border-color: #1677ff;
+    background: #f0f7ff;
+  }
+}
+
+.product-name {
+  font-size: 28rpx;
+  color: #1a1a1a;
+}
+
+.product-input {
+  display: flex;
+  align-items: center;
+}
+
+.input-label {
+  font-size: 24rpx;
+  color: #666;
+  margin-right: 12rpx;
+}
+
+.quantity-input {
+  width: 100rpx;
+  height: 56rpx;
+  background: #fff;
+  border-radius: 8rpx;
+  text-align: center;
+  font-size: 28rpx;
+  padding: 0 8rpx;
+  border: 1rpx solid #eee;
+}
+
+.input-unit {
+  font-size: 24rpx;
+  color: #666;
+  margin-left: 12rpx;
+}
+
+/* 提交按钮 */
+.submit-section {
+  margin-top: 24rpx;
+}
+
+.submit-btn {
+  width: 100%;
+  height: 88rpx;
+  background: #1677ff;
+  border-radius: 44rpx;
+  color: #fff;
+  font-size: 32rpx;
+  font-weight: bold;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border: none;
+
+  &::after {
+    border: none;
+  }
+}
+
+/* 日历 */
+.calendar-section {
+  margin: 0 24rpx;
+  background: #ffffff;
+  border-radius: 16rpx;
+  padding: 24rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
+}
+
+.calendar-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 24rpx;
+}
+
+.calendar-title {
+  display: flex;
+  align-items: center;
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #1a1a1a;
+}
+
+.calendar-icon {
+  margin-right: 8rpx;
+}
+
+.month-picker {
+  display: flex;
+  align-items: center;
+  font-size: 26rpx;
+  color: #666;
+}
+
+.arrow {
+  margin-left: 8rpx;
+  font-size: 20rpx;
+}
+
+/* 星期 */
+.weekdays {
+  display: flex;
+  margin-bottom: 16rpx;
+}
+
+.weekday {
+  flex: 1;
+  text-align: center;
+  font-size: 24rpx;
+  color: #999;
+}
+
+/* 日历格子 */
+.calendar-grid {
+  display: flex;
+  flex-wrap: wrap;
+}
+
+.calendar-day {
+  width: calc(100% / 7);
+  height: 72rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  position: relative;
+  box-sizing: border-box;
+
+  &.empty {
+    background: transparent;
+  }
+
+  &.checked {
+    .day-text {
+      background: #1677ff;
+      color: #fff;
+    }
+  }
+
+  &.today {
+    .day-text {
+      border: 2rpx solid #1677ff;
+      color: #1677ff;
+    }
+  }
+}
+
+.day-text {
+  width: 56rpx;
+  height: 56rpx;
+  line-height: 56rpx;
+  text-align: center;
+  border-radius: 50%;
+  font-size: 26rpx;
+  color: #333;
+  background: #f5f5f5;
+}
+
+.check-icon {
+  position: absolute;
+  bottom: 4rpx;
+  right: 50%;
+  margin-right: -20rpx;
+  font-size: 18rpx;
+  color: #fff;
+  background: #52c41a;
+  width: 24rpx;
+  height: 24rpx;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.tabbar-placeholder {
+  height: 120rpx;
+}
+</style>

+ 303 - 0
src/pages/index/index.vue

@@ -0,0 +1,303 @@
+<template>
+  <view class="page-container" :style="{ paddingTop: statusBarHeight + 'px' }">
+    <!-- 顶部Banner -->
+    <view class="banner-wrap">
+      <!-- <image class="banner-img" src="/static/banner.jpg" mode="aspectFill" /> -->
+      <view class="banner-placeholder"></view>
+      <view class="banner-overlay">
+        <view class="banner-title">每日打卡,赢取大奖</view>
+        <view class="banner-subtitle">2026春季销售竞赛现已开启</view>
+      </view>
+    </view>
+
+    <!-- 进行中的活动 -->
+    <view class="section">
+      <view class="section-title">进行中的活动</view>
+
+      <view class="activity-list">
+        <view
+          class="activity-card"
+          v-for="(item, index) in activities"
+          :key="index"
+          @tap="goActivityDetail(item)"
+        >
+          <view class="activity-icon">
+            <view class="icon-clock">🕐</view>
+          </view>
+          <view class="activity-info">
+            <view class="activity-name">{{ item.name }}</view>
+            <view class="activity-time"
+              >时间:{{ item.startDate }} ~ {{ item.endDate }}</view
+            >
+            <view class="activity-tags">
+              <text class="tag-status">进行中</text>
+              <text class="tag-days">已打卡 {{ item.days }} 天</text>
+            </view>
+          </view>
+          <view class="activity-arrow">›</view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 统计面板 -->
+    <view class="stats-panel">
+      <view class="stats-grid">
+        <view class="stats-item">
+          <view class="stats-label">本月累计打卡次数</view>
+          <view class="stats-value">
+            <text class="stats-num">{{ stats.checkCount }}</text>
+            <text class="stats-unit"> 次</text>
+          </view>
+        </view>
+        <view class="stats-item">
+          <view class="stats-label">本月累计销售</view>
+          <view class="stats-value">
+            <text class="stats-num">{{ stats.saleCount }}</text>
+            <text class="stats-unit"> 件</text>
+          </view>
+        </view>
+        <view class="stats-item">
+          <view class="stats-label">当前排名</view>
+          <view class="stats-value">
+            <text class="stats-rank">第 {{ stats.rank }} 名</text>
+          </view>
+        </view>
+        <view class="stats-item">
+          <view class="stats-label">所属门店</view>
+          <view class="stats-value">
+            <text class="stats-rank">{{ stats.storeName }}</text>
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 底部间距(tabbar占位) -->
+    <view class="tabbar-placeholder"></view>
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      statusBarHeight: 0,
+      activities: [
+        {
+          id: 1,
+          name: "2026春季销售冲刺活动",
+          startDate: "2026-01-01",
+          endDate: "2026-03-31",
+          days: 15,
+        },
+        {
+          id: 2,
+          name: "全国冬季特惠节",
+          startDate: "2025-12-15",
+          endDate: "2026-02-15",
+          days: 30,
+        },
+      ],
+      stats: {
+        checkCount: 15,
+        saleCount: 128,
+        rank: 12,
+        storeName: "静安旗舰店",
+      },
+    };
+  },
+  onLoad() {
+    this.statusBarHeight = getApp().globalData.statusBarHeight;
+  },
+  methods: {
+    goActivityDetail(item) {
+      uni.navigateTo({
+        url: `/pages/activity-detail/activity-detail?id=${item.id}`,
+      });
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.page-container {
+  min-height: 100vh;
+  background: #f5f6fa;
+}
+
+/* Banner */
+.banner-wrap {
+  position: relative;
+  width: 100%;
+  height: 380rpx;
+  overflow: hidden;
+}
+
+.banner-img {
+  width: 100%;
+  height: 100%;
+}
+
+.banner-placeholder {
+  width: 100%;
+  height: 100%;
+  background: linear-gradient(160deg, #c8d4f5 0%, #d8e2ff 40%, #b8c8f0 100%);
+}
+
+.banner-overlay {
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  padding: 32rpx 32rpx 28rpx;
+  background: linear-gradient(to top, rgba(0, 0, 0, 0.55) 0%, transparent 100%);
+}
+
+.banner-title {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #ffffff;
+  text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
+}
+
+.banner-subtitle {
+  font-size: 24rpx;
+  color: rgba(255, 255, 255, 0.9);
+  margin-top: 8rpx;
+}
+
+/* Section */
+.section {
+  padding: 32rpx 24rpx 0;
+}
+
+.section-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #1a1a1a;
+  margin-bottom: 24rpx;
+}
+
+/* Activity Card */
+.activity-list {
+  display: flex;
+  flex-direction: column;
+  gap: 16rpx;
+}
+
+.activity-card {
+  background: #ffffff;
+  border-radius: 16rpx;
+  padding: 28rpx 24rpx;
+  display: flex;
+  align-items: center;
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
+}
+
+.activity-icon {
+  width: 72rpx;
+  height: 72rpx;
+  background: #eef4ff;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+}
+
+.icon-clock {
+  font-size: 36rpx;
+}
+
+.activity-info {
+  flex: 1;
+  padding: 0 20rpx;
+}
+
+.activity-name {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #1a1a1a;
+  margin-bottom: 8rpx;
+}
+
+.activity-time {
+  font-size: 24rpx;
+  color: #888888;
+  margin-bottom: 12rpx;
+}
+
+.activity-tags {
+  display: flex;
+  align-items: center;
+  gap: 12rpx;
+}
+
+.tag-status {
+  font-size: 22rpx;
+  color: #52c41a;
+  background: #f6ffed;
+  border: 1rpx solid #b7eb8f;
+  border-radius: 6rpx;
+  padding: 2rpx 10rpx;
+}
+
+.tag-days {
+  font-size: 22rpx;
+  color: #888888;
+}
+
+.activity-arrow {
+  font-size: 36rpx;
+  color: #cccccc;
+  flex-shrink: 0;
+}
+
+/* Stats Panel */
+.stats-panel {
+  margin: 24rpx 24rpx 0;
+  background: #1677ff;
+  border-radius: 20rpx;
+  padding: 36rpx 32rpx;
+}
+
+.stats-grid {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 32rpx 0;
+}
+
+.stats-item {
+  .stats-label {
+    font-size: 24rpx;
+    color: rgba(255, 255, 255, 0.8);
+    margin-bottom: 8rpx;
+  }
+
+  .stats-value {
+    display: flex;
+    align-items: baseline;
+  }
+
+  .stats-num {
+    font-size: 52rpx;
+    font-weight: bold;
+    color: #ffffff;
+  }
+
+  .stats-unit {
+    font-size: 26rpx;
+    color: rgba(255, 255, 255, 0.9);
+    margin-left: 4rpx;
+  }
+
+  .stats-rank {
+    font-size: 32rpx;
+    font-weight: bold;
+    color: #ffffff;
+  }
+}
+
+.tabbar-placeholder {
+  height: 120rpx;
+}
+</style>

+ 154 - 0
src/pages/login/login.vue

@@ -0,0 +1,154 @@
+<template>
+  <view class="login-container" :style="{ paddingTop: statusBarHeight + 'px' }">
+    <view class="login-card">
+      <view class="login-title">店员打卡系统</view>
+      <view class="login-subtitle">请使用管理员分配的账号登录</view>
+      <view class="form-group">
+        <view class="form-label">账号</view>
+        <input
+          class="form-input"
+          v-model="form.account"
+          placeholder="请输入工号/手机号"
+          placeholder-style="color:#BFBFBF;"
+        />
+      </view>
+      <view class="form-group">
+        <view class="form-label">密码</view>
+        <input
+          class="form-input"
+          v-model="form.password"
+          placeholder="请输入登录密码"
+          placeholder-style="color:#BFBFBF;"
+          password
+        />
+      </view>
+      <button class="login-btn" @tap="handleLogin">立即登录</button>
+      <view class="login-footer">
+        <text>© 2026 门店管理系统</text>
+        <text>如需账号请联系各区域代理商管理员</text>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      statusBarHeight: 0,
+      form: {
+        account: "",
+        password: "",
+      },
+    };
+  },
+  onLoad() {
+    this.statusBarHeight = getApp().globalData.statusBarHeight;
+  },
+  methods: {
+    handleLogin() {
+      if (!this.form.account) {
+        uni.showToast({ title: "请输入账号", icon: "none" });
+        return;
+      }
+      if (!this.form.password) {
+        uni.showToast({ title: "请输入密码", icon: "none" });
+        return;
+      }
+      // 跳转到首页
+      uni.switchTab({ url: "/pages/index/index" });
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.login-container {
+  min-height: 100vh;
+  background: linear-gradient(135deg, #e8eeff 0%, #f0f4ff 50%, #e4eaff 100%);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 40rpx;
+  box-sizing: border-box;
+}
+
+.login-card {
+  background: #ffffff;
+  border-radius: 24rpx;
+  padding: 64rpx 48rpx;
+  width: 100%;
+  box-shadow: 0 8rpx 40rpx rgba(0, 0, 0, 0.08);
+}
+
+.login-title {
+  font-size: 48rpx;
+  font-weight: bold;
+  color: #1a1a1a;
+  text-align: center;
+  margin-bottom: 16rpx;
+}
+
+.login-subtitle {
+  font-size: 26rpx;
+  color: #999999;
+  text-align: center;
+  margin-bottom: 64rpx;
+}
+
+.form-group {
+  margin-bottom: 32rpx;
+}
+
+.form-label {
+  font-size: 28rpx;
+  color: #1677ff;
+  margin-bottom: 16rpx;
+  font-weight: 500;
+}
+
+.form-input {
+  width: 100%;
+  height: 88rpx;
+  background: #f7f8fa;
+  border-radius: 12rpx;
+  padding: 0 28rpx;
+  font-size: 28rpx;
+  color: #333333;
+  box-sizing: border-box;
+  border: 2rpx solid #f0f0f0;
+}
+
+.login-btn {
+  width: 100%;
+  height: 96rpx;
+  background: #1677ff;
+  color: #ffffff;
+  font-size: 34rpx;
+  font-weight: bold;
+  border-radius: 16rpx;
+  margin-top: 48rpx;
+  border: none;
+  line-height: 96rpx;
+  letter-spacing: 4rpx;
+}
+
+.login-btn::after {
+  border: none;
+}
+
+.login-footer {
+  margin-top: 48rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 8rpx;
+
+  text {
+    font-size: 22rpx;
+    color: #bfbfbf;
+    display: block;
+    text-align: center;
+  }
+}
+</style>

+ 236 - 0
src/pages/profile/profile.vue

@@ -0,0 +1,236 @@
+<template>
+  <!-- :style="{ paddingTop: statusBarHeight + 'px' }" -->
+  <view class="page-container">
+    <!-- 用户信息卡片 -->
+    <view class="user-card">
+      <image
+        class="user-avatar"
+        :src="userInfo.avatar || '/static/default_avatar.png'"
+        mode="aspectFill"
+      />
+      <view class="user-detail">
+        <view class="user-name">{{ userInfo.name }}</view>
+        <view class="user-role-tag">{{ userInfo.role }}</view>
+        <view class="user-store">{{ userInfo.store }}</view>
+      </view>
+    </view>
+
+    <!-- 编辑菜单 -->
+    <view class="menu-card">
+      <view class="menu-item" @tap="handleEdit('name')">
+        <view class="menu-left">
+          <text class="menu-icon">👤</text>
+          <text class="menu-text">编辑姓名</text>
+        </view>
+        <text class="menu-arrow">›</text>
+      </view>
+      <view class="menu-divider"></view>
+      <view class="menu-item" @tap="handleEdit('avatar')">
+        <view class="menu-left">
+          <text class="menu-icon">📷</text>
+          <text class="menu-text">修改头像</text>
+        </view>
+        <text class="menu-arrow">›</text>
+      </view>
+      <view class="menu-divider"></view>
+      <view class="menu-item" @tap="handleEdit('password')">
+        <view class="menu-left">
+          <text class="menu-icon">🔒</text>
+          <text class="menu-text">修改密码</text>
+        </view>
+        <text class="menu-arrow">›</text>
+      </view>
+    </view>
+
+    <!-- 其他菜单 -->
+    <view class="menu-card">
+      <view class="menu-item" @tap="handleAbout">
+        <view class="menu-left">
+          <text class="menu-icon">ℹ️</text>
+          <text class="menu-text">关于打卡</text>
+        </view>
+        <text class="menu-arrow">›</text>
+      </view>
+      <view class="menu-divider"></view>
+      <view class="menu-item" @tap="handlePrivacy">
+        <view class="menu-left">
+          <text class="menu-icon">📋</text>
+          <text class="menu-text">隐私协议</text>
+        </view>
+        <text class="menu-arrow">›</text>
+      </view>
+    </view>
+
+    <!-- 退出登录 -->
+    <view class="logout-card" @tap="handleLogout">
+      <text class="logout-text">→ 退出登录</text>
+    </view>
+
+    <view class="tabbar-placeholder"></view>
+  </view>
+</template>
+
+<script>
+export default {
+  components: {},
+  data() {
+    return {
+      statusBarHeight: 0,
+      userInfo: {
+        name: "张小明",
+        role: "华东大区代理商",
+        store: "上海静安旗舰店",
+        avatar: "",
+      },
+    };
+  },
+  onLoad() {
+    this.statusBarHeight = getApp().globalData.statusBarHeight;
+  },
+  methods: {
+    handleEdit(type) {
+      const titles = {
+        name: "编辑姓名",
+        avatar: "修改头像",
+        password: "修改密码",
+      };
+      uni.showToast({ title: titles[type], icon: "none" });
+    },
+    handleAbout() {
+      uni.showToast({ title: "关于打卡", icon: "none" });
+    },
+    handlePrivacy() {
+      uni.showToast({ title: "隐私协议", icon: "none" });
+    },
+    handleLogout() {
+      uni.showModal({
+        title: "提示",
+        content: "确认退出登录?",
+        success: (res) => {
+          if (res.confirm) {
+            uni.reLaunch({ url: "/pages/login/login" });
+          }
+        },
+      });
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.page-container {
+  min-height: 100vh;
+  background: #f5f6fa;
+  padding-top: 40rpx;
+}
+
+/* 用户信息卡片 */
+.user-card {
+  background: #ffffff;
+  margin: 0 24rpx 24rpx;
+  border-radius: 20rpx;
+  padding: 40rpx 32rpx;
+  display: flex;
+  align-items: center;
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
+}
+
+.user-avatar {
+  width: 120rpx;
+  height: 120rpx;
+  border-radius: 16rpx;
+  background: #eeeeee;
+  flex-shrink: 0;
+}
+
+.user-detail {
+  margin-left: 28rpx;
+  flex: 1;
+}
+
+.user-name {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #1a1a1a;
+  margin-bottom: 12rpx;
+}
+
+.user-role-tag {
+  display: inline-block;
+  font-size: 22rpx;
+  color: #1677ff;
+  background: #eef4ff;
+  border-radius: 6rpx;
+  padding: 4rpx 14rpx;
+  margin-bottom: 10rpx;
+}
+
+.user-store {
+  font-size: 24rpx;
+  color: #888888;
+}
+
+/* 菜单卡片 */
+.menu-card {
+  background: #ffffff;
+  margin: 0 24rpx 20rpx;
+  border-radius: 20rpx;
+  overflow: hidden;
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
+}
+
+.menu-item {
+  padding: 36rpx 32rpx;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.menu-left {
+  display: flex;
+  align-items: center;
+  gap: 20rpx;
+}
+
+.menu-icon {
+  font-size: 32rpx;
+  width: 44rpx;
+  text-align: center;
+}
+
+.menu-text {
+  font-size: 30rpx;
+  color: #333333;
+}
+
+.menu-arrow {
+  font-size: 40rpx;
+  color: #cccccc;
+  font-weight: 300;
+}
+
+.menu-divider {
+  height: 1rpx;
+  background: #f5f5f5;
+  margin: 0 24rpx;
+}
+
+/* 退出登录 */
+.logout-card {
+  background: #ffffff;
+  margin: 0 24rpx 20rpx;
+  border-radius: 20rpx;
+  padding: 36rpx;
+  text-align: center;
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
+}
+
+.logout-text {
+  font-size: 30rpx;
+  color: #ff4d4f;
+}
+
+.tabbar-placeholder {
+  height: 120rpx;
+}
+</style>

+ 396 - 0
src/pages/rank/rank.vue

@@ -0,0 +1,396 @@
+<template>
+  <view class="page-container" :style="{ paddingTop: statusBarHeight + 'px' }">
+    <!-- 顶部标题区 -->
+    <view class="header-bg">
+      <view class="trophy-icon">🏆</view>
+      <view class="header-title">销售风云榜</view>
+      <view class="header-subtitle">实时更新,争当销售冠军</view>
+    </view>
+
+    <!-- 筛选栏 -->
+    <view class="filter-bar">
+      <view class="filter-select" @tap="toggleActivityPicker">
+        <text class="filter-text">{{ currentActivity }}</text>
+        <text class="filter-arrow">▼</text>
+      </view>
+      <view class="filter-select" @tap="toggleRolePicker">
+        <text class="filter-text">{{ currentRole }}</text>
+        <text class="filter-arrow">▼</text>
+      </view>
+    </view>
+
+    <!-- 排行榜列表 -->
+    <scroll-view class="rank-scroll" scroll-y>
+      <view class="rank-list">
+        <view
+          class="rank-item"
+          v-for="(item, index) in rankList"
+          :key="item.id"
+          :class="{ top3: index < 3 }"
+        >
+          <!-- 排名 -->
+          <view class="rank-num-wrap">
+            <text v-if="index === 0" class="medal-emoji">🥇</text>
+            <text v-else-if="index === 1" class="medal-emoji">🥈</text>
+            <text v-else-if="index === 2" class="medal-emoji">🥉</text>
+            <text v-else class="rank-number">{{ index + 1 }}</text>
+          </view>
+
+          <!-- 头像 -->
+          <view class="avatar-wrap">
+            <image
+              class="avatar"
+              :src="item.avatar || '/static/default_avatar.png'"
+              mode="aspectFill"
+            />
+          </view>
+
+          <!-- 信息 -->
+          <view class="user-info">
+            <view class="user-name">{{ item.name }}</view>
+            <view class="user-store">{{ item.store }}</view>
+          </view>
+
+          <!-- 销售数 -->
+          <view class="sale-info">
+            <text class="sale-num">{{ item.saleCount }}</text>
+            <text class="sale-label">销售件数</text>
+          </view>
+        </view>
+      </view>
+      <view class="scroll-placeholder"></view>
+    </scroll-view>
+
+    <view class="my-rank-bar">
+      <view class="my-avatar-wrap">
+        <image
+          class="my-avatar"
+          src="/static/default_avatar.png"
+          mode="aspectFill"
+        />
+      </view>
+      <view class="my-rank-info">
+        <view class="my-rank-label">我的实时排名</view>
+        <view class="my-rank-value">第 12 名</view>
+      </view>
+      <view class="my-sale-info">
+        <view class="my-sale-label">累计销售</view>
+        <view class="my-sale-value">128 件</view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  components: {},
+  data() {
+    return {
+      statusBarHeight: 0,
+      currentActivity: "2026春季销售冲刺",
+      currentRole: "全代理商",
+      rankList: [
+        {
+          id: 1,
+          name: "王美丽",
+          store: "北京西单店",
+          saleCount: 245,
+          avatar: "",
+        },
+        {
+          id: 2,
+          name: "李大伟",
+          store: "深圳万象城",
+          saleCount: 232,
+          avatar: "",
+        },
+        {
+          id: 3,
+          name: "赵铁柱",
+          store: "广州正佳店",
+          saleCount: 218,
+          avatar: "",
+        },
+        {
+          id: 4,
+          name: "陈菁菁",
+          store: "杭州万象城",
+          saleCount: 198,
+          avatar: "",
+        },
+        {
+          id: 5,
+          name: "刘洋",
+          store: "成都远西太古里",
+          saleCount: 185,
+          avatar: "",
+        },
+        {
+          id: 6,
+          name: "张三",
+          store: "上海静安店",
+          saleCount: 176,
+          avatar: "",
+        },
+        {
+          id: 7,
+          name: "李四",
+          store: "北京朝阳店",
+          saleCount: 162,
+          avatar: "",
+        },
+        {
+          id: 8,
+          name: "王五",
+          store: "广州天环店",
+          saleCount: 155,
+          avatar: "",
+        },
+      ],
+    };
+  },
+  onLoad() {
+    this.statusBarHeight = getApp().globalData.statusBarHeight;
+  },
+  methods: {
+    toggleActivityPicker() {
+      uni.showActionSheet({
+        itemList: ["2026春季销售冲刺", "全国冬季特惠节"],
+        success: (res) => {
+          const list = ["2026春季销售冲刺", "全国冬季特惠节"];
+          this.currentActivity = list[res.tapIndex];
+        },
+      });
+    },
+    toggleRolePicker() {
+      uni.showActionSheet({
+        itemList: ["全代理商", "本区域"],
+        success: (res) => {
+          const list = ["全代理商", "本区域"];
+          this.currentRole = list[res.tapIndex];
+        },
+      });
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.page-container {
+  min-height: 100vh;
+  background: #f5f6fa;
+  display: flex;
+  flex-direction: column;
+}
+
+/* Header */
+.header-bg {
+  background: linear-gradient(160deg, #c8d4f5 0%, #d8e2ff 40%, #b8c8f0 100%);
+  padding: 80rpx 32rpx 48rpx;
+  text-align: center;
+}
+
+.trophy-icon {
+  font-size: 88rpx;
+  margin-bottom: 16rpx;
+}
+
+.header-title {
+  font-size: 40rpx;
+  font-weight: bold;
+  color: #1a1a1a;
+  margin-bottom: 8rpx;
+}
+
+.header-subtitle {
+  font-size: 24rpx;
+  color: #666666;
+}
+
+/* Filter Bar */
+.filter-bar {
+  display: flex;
+  gap: 16rpx;
+  padding: 24rpx 24rpx 16rpx;
+}
+
+.filter-select {
+  flex: 1;
+  background: #ffffff;
+  border-radius: 12rpx;
+  padding: 16rpx 20rpx;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
+}
+
+.filter-text {
+  font-size: 26rpx;
+  color: #333333;
+}
+
+.filter-arrow {
+  font-size: 20rpx;
+  color: #999999;
+}
+
+/* Rank List */
+.rank-scroll {
+  flex: 1;
+  padding: 0 24rpx;
+  box-sizing: border-box;
+}
+
+.rank-list {
+  display: flex;
+  flex-direction: column;
+  gap: 0;
+}
+
+.rank-item {
+  background: #ffffff;
+  margin-bottom: 2rpx;
+  padding: 24rpx 20rpx;
+  display: flex;
+  align-items: center;
+  border-radius: 0;
+
+  &:first-child {
+    border-radius: 16rpx 16rpx 0 0;
+  }
+
+  &:last-child {
+    border-radius: 0 0 16rpx 16rpx;
+  }
+
+  &:only-child {
+    border-radius: 16rpx;
+  }
+}
+
+.rank-num-wrap {
+  width: 60rpx;
+  height: 60rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+}
+
+.medal-emoji {
+  font-size: 44rpx;
+}
+
+.rank-number {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #888888;
+  width: 52rpx;
+  text-align: center;
+}
+
+.avatar-wrap {
+  margin: 0 20rpx;
+}
+
+.avatar {
+  width: 80rpx;
+  height: 80rpx;
+  border-radius: 50%;
+  background: #eeeeee;
+}
+
+.user-info {
+  flex: 1;
+}
+
+.user-name {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #1a1a1a;
+  margin-bottom: 6rpx;
+}
+
+.user-store {
+  font-size: 22rpx;
+  color: #999999;
+}
+
+.sale-info {
+  text-align: right;
+}
+
+.sale-num {
+  display: block;
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #ff7a00;
+}
+
+.sale-label {
+  display: block;
+  font-size: 20rpx;
+  color: #aaaaaa;
+}
+
+/* 底部我的排名 */
+.my-rank-bar {
+  position: fixed;
+  bottom: 0rpx;
+  left: 0;
+  right: 0;
+  background: #1677ff;
+  padding: 20rpx 32rpx;
+  display: flex;
+  align-items: center;
+  z-index: 98;
+}
+
+.my-avatar-wrap {
+  margin-right: 20rpx;
+}
+
+.my-avatar {
+  width: 72rpx;
+  height: 72rpx;
+  border-radius: 50%;
+  border: 3rpx solid rgba(255, 255, 255, 0.6);
+}
+
+.my-rank-info {
+  flex: 1;
+}
+
+.my-rank-label {
+  font-size: 22rpx;
+  color: rgba(255, 255, 255, 0.8);
+  margin-bottom: 4rpx;
+}
+
+.my-rank-value {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #ffffff;
+}
+
+.my-sale-info {
+  text-align: right;
+}
+
+.my-sale-label {
+  font-size: 22rpx;
+  color: rgba(255, 255, 255, 0.8);
+  margin-bottom: 4rpx;
+}
+
+.my-sale-value {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #ffffff;
+}
+
+.scroll-placeholder {
+  height: 160rpx;
+}
+</style>

BIN
src/static/banner.jpg


BIN
src/static/default_avatar.png


BIN
src/static/logo.png


BIN
src/static/tabbar/home-active.png


BIN
src/static/tabbar/home.png


BIN
src/static/tabbar/profile-active.png


BIN
src/static/tabbar/profile.png


BIN
src/static/tabbar/rank-active.png


BIN
src/static/tabbar/rank.png


+ 13 - 0
src/uni.promisify.adaptor.js

@@ -0,0 +1,13 @@
+uni.addInterceptor({
+  returnValue (res) {
+    if (!(!!res && (typeof res === "object" || typeof res === "function") && typeof res.then === "function")) {
+      return res;
+    }
+    return new Promise((resolve, reject) => {
+      if (!res) {
+        return resolve(res) 
+      }
+      res.then((res) => res[0] ? reject(res[0]) : resolve(res[1]));
+    });
+  },
+});

+ 76 - 0
src/uni.scss

@@ -0,0 +1,76 @@
+/**
+ * 这里是uni-app内置的常用样式变量
+ *
+ * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
+ * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
+ *
+ */
+
+/**
+ * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
+ *
+ * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
+ */
+
+/* 颜色变量 */
+
+/* 行为相关颜色 */
+$uni-color-primary: #007aff;
+$uni-color-success: #4cd964;
+$uni-color-warning: #f0ad4e;
+$uni-color-error: #dd524d;
+
+/* 文字基本颜色 */
+$uni-text-color: #333; // 基本色
+$uni-text-color-inverse: #fff; // 反色
+$uni-text-color-grey: #999; // 辅助灰色,如加载更多的提示信息
+$uni-text-color-placeholder: #808080;
+$uni-text-color-disable: #c0c0c0;
+
+/* 背景颜色 */
+$uni-bg-color: #fff;
+$uni-bg-color-grey: #f8f8f8;
+$uni-bg-color-hover: #f1f1f1; // 点击状态颜色
+$uni-bg-color-mask: rgba(0, 0, 0, 0.4); // 遮罩颜色
+
+/* 边框颜色 */
+$uni-border-color: #c8c7cc;
+
+/* 尺寸变量 */
+
+/* 文字尺寸 */
+$uni-font-size-sm: 12px;
+$uni-font-size-base: 14px;
+$uni-font-size-lg: 16px;
+
+/* 图片尺寸 */
+$uni-img-size-sm: 20px;
+$uni-img-size-base: 26px;
+$uni-img-size-lg: 40px;
+
+/* Border Radius */
+$uni-border-radius-sm: 2px;
+$uni-border-radius-base: 3px;
+$uni-border-radius-lg: 6px;
+$uni-border-radius-circle: 50%;
+
+/* 水平间距 */
+$uni-spacing-row-sm: 5px;
+$uni-spacing-row-base: 10px;
+$uni-spacing-row-lg: 15px;
+
+/* 垂直间距 */
+$uni-spacing-col-sm: 4px;
+$uni-spacing-col-base: 8px;
+$uni-spacing-col-lg: 12px;
+
+/* 透明度 */
+$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
+
+/* 文章场景相关 */
+$uni-color-title: #2c405a; // 文章标题颜色
+$uni-font-size-title: 20px;
+$uni-color-subtitle: #555; // 二级标题颜色
+$uni-font-size-subtitle: 18px;
+$uni-color-paragraph: #3f536e; // 文章段落颜色
+$uni-font-size-paragraph: 15px;