Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0d48c1ffc | ||
|
|
ccda415cd7 | ||
|
|
493591c286 | ||
|
|
ec48de4970 |
@@ -0,0 +1,390 @@
|
|||||||
|
# ARCX iOS 本地打包机落地计划
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** 在本机(或专用 Mac)建立可重复的 ARCX iOS 出包能力:前端资源导出 → 原生工程签名 → 产出可安装/可上架 ipa。
|
||||||
|
|
||||||
|
**Architecture:** 采用「两阶段」:Phase A 先用 Mac **安心打包**快速打通证书与出包;Phase B 再搭建完整 **离线 SDK + Xcode** 本地打包机,满足证书/代码不出本机、可脚本化/CI 的需求。`arcx-app` 是 vue-cli/vite 项目,本地打包资源**必须经 HBuilderX 导出**,不能仅靠 `npm run build:ios`。
|
||||||
|
|
||||||
|
**Tech Stack:** uni-app (Vue3/Vite) · HBuilderX · DCloud iOS 离线 SDK · Xcode 26+ · CocoaPods · Apple Developer 证书/Profile · Transporter
|
||||||
|
|
||||||
|
**官方文档入口:**
|
||||||
|
- 总览:[uni-app 官网](https://uniapp.dcloud.net.cn/) → 发布 → 原生 App(离线)
|
||||||
|
- 离线 SDK 总入口:[nativesupport AppDocs](https://nativesupport.dcloud.net.cn/AppDocs/README)
|
||||||
|
- iOS 开发环境/工程配置:[usesdk/ios](https://nativesupport.dcloud.net.cn/AppDocs/usesdk/ios)
|
||||||
|
- 导出前端资源:[importfeproject/export](https://nativesupport.dcloud.net.cn/AppDocs/importfeproject/export.html)
|
||||||
|
- 证书/Archive/上传:[package/ios](https://nativesupport.dcloud.net.cn/AppDocs/package/ios.html)
|
||||||
|
- iOS 原生资源(Info.plist 等):[app-nativeresource-ios](https://uniapp.dcloud.net.cn/tutorial/app-nativeresource-ios)
|
||||||
|
- 安心打包(Mac 本地签):[SafePack](https://uniapp.dcloud.net.cn/tutorial/build/SafePack.html)
|
||||||
|
- 模块/Pod 集成(5.13+):[iOSModuleConfig/common](https://nativesupport.dcloud.net.cn/AppDocs/usemodule/iOSModuleConfig/common.html)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 官网结论摘要(先读再落地)
|
||||||
|
|
||||||
|
### 两条「本地」路径不要混
|
||||||
|
|
||||||
|
| 路径 | 本质 | 证书/前端代码 | 限制 | 适合 |
|
||||||
|
|------|------|---------------|------|------|
|
||||||
|
| **安心打包** | 云端生成原生壳,**本机合并代码并签名** | 不上传代码与证书 | 非纯离线;改模块配置会重新拉壳;付费原生插件/原生混淆能力受限场景见文档 | 日常出包、Mac 打包机首选起步 |
|
||||||
|
| **离线打包** | 下载 iOS 离线 SDK,用 **Xcode** 本地编签 | 全程本机 | 无原生混淆;**付费 uni 原生插件不可用**;须申请 `dcloud_appkey`;编译器版本必须与 SDK 一致 | 强合规、深度原生定制、CI Archive |
|
||||||
|
|
||||||
|
### 离线打包标准流水线(官方)
|
||||||
|
|
||||||
|
1. **版本对齐**:HBuilderX / `npm run info` 的编译器版本 ≡ 离线 SDK 版本(不一致会弹窗且功能异常)。
|
||||||
|
2. **申请 Appkey**(SDK ≥ 3.1.10):在 Info.plist 配置 `dcloud_appkey`;Bundle ID + `control.xml` 中 appid 必须与申请一致。
|
||||||
|
3. **导出资源**:HBuilderX → 发行 → 原生 App-本地打包 → **生成本地打包 App 资源**。
|
||||||
|
- **vue-cli 项目不能用纯 CLI 导出**,必须用 HBuilderX 打开项目再导出。
|
||||||
|
- `manifest.json` 的 `appid` 不能手填空值,须由 HBuilderX 获取(本仓库已有 `__UNI__8A880C6`)。
|
||||||
|
4. **配置 Xcode 工程**(`HBuilder-Hello`):
|
||||||
|
- Bundle ID / Display Name / Version / Build
|
||||||
|
- 与导出资源中的 `manifest`、`control.xml` appid 对齐
|
||||||
|
- 图标、LaunchScreen、隐私描述、所需模块(参考 `Feature-iOS.xls`;新版推荐 CocoaPods `uniapp_subspecs`)
|
||||||
|
- 合并项目内 `nativeResources/ios`(离线时需**手动**合并进 Xcode)
|
||||||
|
5. **Signing**:生产证书 + Distribution Profile(与 Bundle ID 绑定)。
|
||||||
|
6. **Product → Archive → 导出 ipa**;上架用 **Transporter** 上传。
|
||||||
|
|
||||||
|
### 与本仓库的映射
|
||||||
|
|
||||||
|
| 项 | 当前值 | 打包机要求 |
|
||||||
|
|----|--------|------------|
|
||||||
|
| 项目形态 | vite + `@dcloudio/uni-app` alpha `4080620251107001` | 离线 SDK 选与编译器对齐的版本;用 HBuilderX 打开本仓库根目录 |
|
||||||
|
| DCloud appid | `__UNI__8A880C6` | 写入 SDK 工程 `control.xml` / 资源目录名 |
|
||||||
|
| iOS Bundle ID | `com.shelingxingqiu.arcx` | Apple App ID + Provisioning 必须一致 |
|
||||||
|
| 版本 | `versionName` 0.1.1 / `versionCode` 101 | Xcode Version / Build 对齐 |
|
||||||
|
| 证书 | `manifest` 中 p12/profile **已注释** | 打包机钥匙串导入正式证书;勿把密码提交 Git |
|
||||||
|
| 原生资源 | `nativeResources/ios/info.plist`(含 ATT 文案) | 云包自动合并;离线需手动合并 |
|
||||||
|
| 本机工具 | macOS 26.1 · Xcode 26.1.1 · CocoaPods 已装 | 已满足官方「Xcode 26.0+」门槛 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文件与目录约定(打包机)
|
||||||
|
|
||||||
|
建议在打包机固定目录(示例,可按团队规范改):
|
||||||
|
|
||||||
|
```text
|
||||||
|
~/Packaging/
|
||||||
|
credentials/ # 不进 Git:.p12、.mobileprovision、密码备忘(加密磁盘)
|
||||||
|
sdk/
|
||||||
|
iOS-SDK-<ver>/ # 解压后的离线 SDK(与编译器同版本)
|
||||||
|
workspace/
|
||||||
|
arcx-app/ # git clone
|
||||||
|
HBuilder-Hello/ # 从 SDK 拷出的长期维护工程(或软链)
|
||||||
|
artifacts/
|
||||||
|
ipa/ # 产出
|
||||||
|
logs/
|
||||||
|
scripts/
|
||||||
|
export-note.md # HBuilderX 导出步骤备忘(GUI)
|
||||||
|
sync-www.sh # 把导出的 www/资源拷进原生工程
|
||||||
|
archive.sh # xcodebuild archive + export
|
||||||
|
```
|
||||||
|
|
||||||
|
项目内相关文件:
|
||||||
|
- `src/manifest.json` — appid、ios.appid、隐私、图标路径
|
||||||
|
- `nativeResources/ios/info.plist` — 额外 Info.plist 合并项
|
||||||
|
- `package.json` — 编译器版本锚点
|
||||||
|
- `docs/superpowers/plans/2026-08-07-ios-local-pack-machine.md` — 本计划
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase A:Mac 安心打包机(1–2 天,先打通)
|
||||||
|
|
||||||
|
> 目标:Mac 上可重复产出已签名 ipa;证书与业务代码不上传云端(首次仍会提交模块配置拉取原生壳)。
|
||||||
|
|
||||||
|
### Task 1: 账号与证书就绪
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- 不改仓库;材料落在 `~/Packaging/credentials/`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Apple Developer**
|
||||||
|
|
||||||
|
准备:
|
||||||
|
- Apple Developer 账号
|
||||||
|
- App ID:`com.shelingxingqiu.arcx`
|
||||||
|
- Distribution Certificate(.p12 + 密码)
|
||||||
|
- App Store / Ad Hoc `mobileprovision`(按发布渠道选)
|
||||||
|
|
||||||
|
- [ ] **Step 2: 导入钥匙串**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 在打包机执行(路径按实际修改)
|
||||||
|
security import ~/Packaging/credentials/ios_dist.p12 -k ~/Library/Keychains/login.keychain-db -P '<P12_PASSWORD>' -T /usr/bin/codesign
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: 钥匙串可见「Apple Distribution: …」证书,信任链完整。
|
||||||
|
|
||||||
|
- [ ] **Step 3: 在 HBuilderX 配置打包证书**
|
||||||
|
|
||||||
|
打开 `src/manifest.json` → App 模块配置 / iOS 打包配置,填入(**勿提交明文密码到 Git**):
|
||||||
|
- Bundle ID:`com.shelingxingqiu.arcx`
|
||||||
|
- 证书路径、密码、profile 路径
|
||||||
|
|
||||||
|
本地可用环境变量或 HBuilderX 图形界面填写;保持仓库内注释状态。
|
||||||
|
|
||||||
|
- [ ] **Step 4: 验收清单**
|
||||||
|
|
||||||
|
- [ ] Bundle ID 与 Profile 一致
|
||||||
|
- [ ] 隐私文案与实际上架用途一致(当前含相机/相册/定位/麦克风/跟踪,无用项建议上架前删减)
|
||||||
|
- [ ] 图标资源 `unpackage/res/icons/` 齐全
|
||||||
|
|
||||||
|
### Task 2: HBuilderX 打开 CLI 项目并首次安心打包
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify locally only: HBuilderX 打开 `/Users/ZhuanZ/Documents/gdfw/arcx-app`
|
||||||
|
- Output: `unpackage/release/ipa/`(或 HBuilderX 提示目录)
|
||||||
|
|
||||||
|
- [ ] **Step 1: 安装与 HBuilderX 编译器对齐的版本**
|
||||||
|
|
||||||
|
对照 `package.json` 中 `@dcloudio/uni-app` 版本号 `3.0.0-alpha-4080620251107001`(对应 HX/编译器 4.08.06 一带),安装匹配的 [HBuilderX](https://www.dcloud.io/hbuilderx.html)。
|
||||||
|
|
||||||
|
- [ ] **Step 2: 用 HBuilderX 打开项目根目录**
|
||||||
|
|
||||||
|
确认能识别 `src/manifest.json`,appid 为 `__UNI__8A880C6`。
|
||||||
|
|
||||||
|
- [ ] **Step 3: 发行 → 原生 App-云端打包,勾选安心打包**
|
||||||
|
|
||||||
|
按 [SafePack](https://uniapp.dcloud.net.cn/tutorial/build/SafePack.html):
|
||||||
|
1. 首次:云端下发原生壳 → 本机合并代码 → 本机签名
|
||||||
|
2. 后续仅改前端且模块配置不变:本地复用壳,速度更快,且不占免费云打包次数
|
||||||
|
|
||||||
|
- [ ] **Step 4: 安装 ipa 到测试机或上传 TestFlight**
|
||||||
|
|
||||||
|
Expected: 应用可启动;若提示 appkey/证书问题,回到 Task 1。
|
||||||
|
|
||||||
|
- [ ] **Step 5: 记录打包机 SOP 一页纸**
|
||||||
|
|
||||||
|
写入 `~/Packaging/scripts/export-note.md`:HBuilderX 版本、证书别名、Profile 名称、成功产物路径、耗时。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase B:完整离线本地打包机(3–5 天)
|
||||||
|
|
||||||
|
> 目标:不依赖云端原生壳;用离线 SDK + Xcode Archive 出包;可脚本化。
|
||||||
|
|
||||||
|
### Task 3: 下载并对齐 iOS 离线 SDK
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `~/Packaging/sdk/iOS-SDK-<ver>/`
|
||||||
|
|
||||||
|
- [ ] **Step 1: 查编译器版本**
|
||||||
|
|
||||||
|
在项目根目录:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /Users/ZhuanZ/Documents/gdfw/arcx-app
|
||||||
|
# 若已配置 uni info 脚本则:
|
||||||
|
npx uni -v
|
||||||
|
# 或在 HBuilderX 中查看关于/编译器版本
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: 记下与导出资源一致的版本号字符串。
|
||||||
|
|
||||||
|
- [ ] **Step 2: 下载同版本 iOS 离线 SDK**
|
||||||
|
|
||||||
|
从 [SDK 下载页](https://nativesupport.dcloud.net.cn/AppDocs/download/ios)(以官网当前入口为准)下载,解压到 `~/Packaging/sdk/`。
|
||||||
|
|
||||||
|
目录应含:
|
||||||
|
- `HBuilder-Hello` — 打包工程
|
||||||
|
- `Feature-iOS.xls` — 模块依赖表
|
||||||
|
- `SDK` — 库与资源
|
||||||
|
- (新版)`uniapp.podspec` / 示例 `Podfile`
|
||||||
|
|
||||||
|
- [ ] **Step 3: 版本一致性门禁**
|
||||||
|
|
||||||
|
建立检查表:`编译器版本` = `SDK 发布版本` = `HBuilderX 导出资源所用版本`。三者任一不一致禁止出生产包。
|
||||||
|
|
||||||
|
### Task 4: 申请并写入 dcloud_appkey
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify (Xcode 工程): `HBuilder-Hello/.../Info.plist`
|
||||||
|
- 参考:[usesdk/ios 配置 Appkey](https://nativesupport.dcloud.net.cn/AppDocs/usesdk/ios)
|
||||||
|
|
||||||
|
- [ ] **Step 1: 在 DCloud 开发者中心申请 Appkey**
|
||||||
|
|
||||||
|
绑定:
|
||||||
|
- DCloud appid:`__UNI__8A880C6`
|
||||||
|
- Bundle ID:`com.shelingxingqiu.arcx`
|
||||||
|
|
||||||
|
- [ ] **Step 2: Info.plist 增加**
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<key>dcloud_appkey</key>
|
||||||
|
<string>YOUR_DCLOUD_APPKEY</string>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: 同步修改 control.xml 中的 appid 与 Bundle ID**
|
||||||
|
|
||||||
|
Expected: 真机运行不再提示 appkey 错误。
|
||||||
|
|
||||||
|
### Task 5: HBuilderX 导出本地打包资源并导入工程
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Output from HX: 通常在项目 `unpackage/resources/` 或发行提示路径下的 www/app 资源
|
||||||
|
- Modify: `HBuilder-Hello` 内 Pandora/apps 资源目录
|
||||||
|
|
||||||
|
- [ ] **Step 1: 导出**
|
||||||
|
|
||||||
|
HBuilderX → 发行 → **原生 App-本地打包** → **生成本地打包 App 资源**
|
||||||
|
(CLI 项目必须这一步,见[官方说明](https://nativesupport.dcloud.net.cn/AppDocs/importfeproject/export.html))
|
||||||
|
|
||||||
|
- [ ] **Step 2: 拷贝资源进 HBuilder-Hello**
|
||||||
|
|
||||||
|
按官方「导入前端资源」文档:将导出的应用资源放到原生工程 `Pandora/apps/<appid>/www`(目录名 = appid)。
|
||||||
|
|
||||||
|
建议脚本骨架 `~/Packaging/scripts/sync-www.sh`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
APPID="__UNI__8A880C6"
|
||||||
|
SRC="${1:?usage: sync-www.sh <exported_www_dir>}"
|
||||||
|
DST="$HOME/Packaging/workspace/HBuilder-Hello/HBuilder/Pandora/apps/${APPID}/www"
|
||||||
|
rm -rf "$DST"
|
||||||
|
mkdir -p "$DST"
|
||||||
|
cp -R "$SRC"/. "$DST"/
|
||||||
|
echo "synced -> $DST"
|
||||||
|
```
|
||||||
|
|
||||||
|
> 实际 `Pandora` 相对路径以当次 SDK 示例工程为准,首次手工核对后再固化脚本。
|
||||||
|
|
||||||
|
- [ ] **Step 3: 合并 `nativeResources/ios`**
|
||||||
|
|
||||||
|
将仓库 `nativeResources/ios/info.plist` 中的键(如 `NSUserTrackingUsageDescription`)合并进 Xcode Info.plist;有 `Resources` 时拷入 Bundle Resources。
|
||||||
|
|
||||||
|
### Task 6: 配置 Xcode Identity、模块与签名
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: Xcode Target `HBuilder` General / Signing / Info.plist /(新版)Podfile
|
||||||
|
|
||||||
|
- [ ] **Step 1: Identity**
|
||||||
|
|
||||||
|
| 字段 | 值 |
|
||||||
|
|------|-----|
|
||||||
|
| Display Name | ARCX |
|
||||||
|
| Bundle Identifier | `com.shelingxingqiu.arcx` |
|
||||||
|
| Version | `0.1.1`(随 `manifest.versionName`) |
|
||||||
|
| Build | `101`(随 `manifest.versionCode`) |
|
||||||
|
|
||||||
|
同时打开工程内 `manifest`,使 name/version 与上表一致。
|
||||||
|
|
||||||
|
- [ ] **Step 2: 模块精简**
|
||||||
|
|
||||||
|
本项目 `manifest.app-plus.modules` 当前为空 → 离线工程只保留基础能力。
|
||||||
|
若后续加支付/推送/登录,按 `Feature-iOS.xls` 或 `Podfile` 的 `uniapp_subspecs` 开启,再 `pod install`。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/Packaging/workspace/HBuilder-Hello
|
||||||
|
pod install
|
||||||
|
open HBuilder-Hello.xcworkspace # 使用 Pods 时必须开 workspace
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Signing & Capabilities**
|
||||||
|
|
||||||
|
- Team + Distribution 证书
|
||||||
|
- Release 使用正确 Provisioning Profile
|
||||||
|
- 需要 ATT 时已有隐私描述;按需加 Associated Domains 等
|
||||||
|
|
||||||
|
- [ ] **Step 4: 真机 Run 冒烟**
|
||||||
|
|
||||||
|
Expected: App 启动到首页 Tab,无「版本不一致 / appkey 错误」弹窗。
|
||||||
|
|
||||||
|
### Task 7: Archive 出包与上传
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `~/Packaging/scripts/archive.sh`
|
||||||
|
- Output: `~/Packaging/artifacts/ipa/`
|
||||||
|
|
||||||
|
- [ ] **Step 1: GUI 首次 Archive**
|
||||||
|
|
||||||
|
Xcode → Product → Archive → Distribute App → 导出 ipa(App Store Connect 或 Ad Hoc)。
|
||||||
|
|
||||||
|
- [ ] **Step 2: 固化命令行(打包机自动化)**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
WS="$HOME/Packaging/workspace/HBuilder-Hello/HBuilder-Hello.xcworkspace"
|
||||||
|
SCHEME="HBuilder"
|
||||||
|
OUT="$HOME/Packaging/artifacts/ipa"
|
||||||
|
ARCHIVE="$OUT/ARCX.xcarchive"
|
||||||
|
EXPORT_PLIST="$HOME/Packaging/scripts/ExportOptions.plist"
|
||||||
|
|
||||||
|
xcodebuild -workspace "$WS" -scheme "$SCHEME" \
|
||||||
|
-configuration Release -archivePath "$ARCHIVE" archive
|
||||||
|
|
||||||
|
xcodebuild -exportArchive -archivePath "$ARCHIVE" \
|
||||||
|
-exportOptionsPlist "$EXPORT_PLIST" -exportPath "$OUT"
|
||||||
|
```
|
||||||
|
|
||||||
|
`ExportOptions.plist` 首次用 Xcode 导出后从 Organizer 旁路生成,或按 Apple 文档手写 `method=app-store-connect`。
|
||||||
|
|
||||||
|
- [ ] **Step 3: 上传**
|
||||||
|
|
||||||
|
使用 **Transporter** 上传 ipa 至 App Store Connect([官方](https://nativesupport.dcloud.net.cn/AppDocs/package/ios.html))。
|
||||||
|
|
||||||
|
- [ ] **Step 4: 打生产包前检查**
|
||||||
|
|
||||||
|
- [ ] `control.xml` **去掉** `debug="true"` / `syncDebug="true"`(自定义基座调试用,留着会导致热更新等问题)
|
||||||
|
- [ ] Version/Build 已递增
|
||||||
|
- [ ] 隐私清单(SDK 4.13+ 示例工程含基础 PrivacyInfo)已按模块补齐
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase C:打包机运维与门禁(持续)
|
||||||
|
|
||||||
|
### Task 8: 目录权限、密钥与备份
|
||||||
|
|
||||||
|
- [ ] **Step 1:** `credentials/` 放加密卷或 1Password;**禁止进 Git**
|
||||||
|
- [ ] **Step 2:** `.gitignore` 确认忽略 `unpackage/release/`、本地 p12、profile
|
||||||
|
- [ ] **Step 3:** 每月检查:Apple 证书过期日、HBuilderX/SDK 升级窗口、Xcode 与 macOS 兼容性
|
||||||
|
|
||||||
|
### Task 9: 版本升级流程(强制)
|
||||||
|
|
||||||
|
每次升级编译器:
|
||||||
|
|
||||||
|
1. 升级 HBuilderX / 项目 `@dcloudio/*` 依赖
|
||||||
|
2. 下载**同版本** iOS 离线 SDK
|
||||||
|
3. 重新导出资源
|
||||||
|
4. Diff `HBuilder-Hello` 与新 SDK 示例工程(Pods、隐私清单、基础库)
|
||||||
|
5. 再 Archive
|
||||||
|
|
||||||
|
### Task 10: 验收标准(Definition of Done)
|
||||||
|
|
||||||
|
- [ ] 安心打包:本机产出可安装 ipa(Phase A)
|
||||||
|
- [ ] 离线打包:Xcode Archive 产出 ipa,安装后无版本/appkey 弹窗(Phase B)
|
||||||
|
- [ ] 同一 commit 资源可重复出包(脚本化 sync + archive)
|
||||||
|
- [ ] 证书与密码不在仓库中
|
||||||
|
- [ ] 有一页 SOP:版本号对齐表 + 出包命令 + 回滚方式
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 推荐落地节奏
|
||||||
|
|
||||||
|
| 周次 | 交付 |
|
||||||
|
|------|------|
|
||||||
|
| Day 1 | 证书/Profile + HBuilderX 打开项目 + 安心打包首包 |
|
||||||
|
| Day 2 | TestFlight/真机验收;写 SOP;决定是否上离线 |
|
||||||
|
| Day 3–4 | 下载对齐 SDK、Appkey、导出资源、Xcode Identity/签名 |
|
||||||
|
| Day 5 | Archive 脚本化 + Transporter;自定义基座(可选) |
|
||||||
|
|
||||||
|
**默认建议:** ARCX 现阶段模块少、无付费原生插件 → **先 Phase A(安心打包)**;若合规要求「完全不碰云原生壳」或要 CI Archive,再上 Phase B。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 风险与明确不做
|
||||||
|
|
||||||
|
| 风险 | 应对 |
|
||||||
|
|------|------|
|
||||||
|
| 编译器与 SDK 版本不一致 | 出包前硬门禁,禁止混用 |
|
||||||
|
| CLI 无法直接导出本地资源 | 打包机必须装 HBuilderX |
|
||||||
|
| 离线无法用付费 uni 原生插件 / 原生混淆 | 评估插件策略;必要时改安心/传统云包 |
|
||||||
|
| manifest 隐私项过多导致审核追问 | 上架前按真实能力删减 |
|
||||||
|
| p12 密码曾出现在历史注释 | 已注释路径勿回填到 Git;轮换证书 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Self-Review
|
||||||
|
|
||||||
|
1. **Spec coverage:** 官网离线流程(版本对齐、Appkey、导出资源、Xcode 配置、证书、Archive、Transporter)与安心打包路径均有 Task;本仓库 Bundle ID/appid/nativeResources 已映射。
|
||||||
|
2. **Placeholder scan:** 脚本中的路径以 SDK 实际目录为准,首次需人工核对后固化(已标明)。
|
||||||
|
3. **一致性:** Bundle ID 全程使用 `com.shelingxingqiu.arcx`;DCloud appid 全程 `__UNI__8A880C6`。
|
||||||
Generated
+1284
-1573
File diff suppressed because it is too large
Load Diff
+8
-5
@@ -12,14 +12,17 @@
|
|||||||
"build:ios": "uni build -p app-ios"
|
"build:ios": "uni build -p app-ios"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dcloudio/uni-app": "^3.0.0-4080420251103001",
|
"@dcloudio/uni-app": "3.0.0-alpha-4080620251107001",
|
||||||
"@dcloudio/uni-app-plus": "^3.0.0-4080420251103001",
|
"@dcloudio/uni-app-plus": "3.0.0-alpha-4080620251107001",
|
||||||
|
"@dcloudio/uni-components": "3.0.0-alpha-4080620251107001",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"vue": "^3.4.27"
|
"vue": "^3.4.27"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@dcloudio/types": "latest",
|
"@dcloudio/types": "^3.4.19",
|
||||||
"@dcloudio/vite-plugin-uni": "3.0.0-4020320240708001",
|
"@dcloudio/uni-cli-shared": "3.0.0-alpha-4080620251107001",
|
||||||
"vite": "^5.2.8"
|
"@dcloudio/vite-plugin-uni": "3.0.0-alpha-4080620251107001",
|
||||||
|
"sass": "^1.77.0",
|
||||||
|
"vite": "5.2.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ const props = defineProps({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="background" :style="{ backgroundColor: bgColor }">
|
<view class="background" :style="{ backgroundColor: bgColor }">
|
||||||
<image class="bg-image" v-if="type === 2" src="../static/app-bg3.png" />
|
<image class="bg-image" v-if="type === 2" src="/static/app-bg3.png" />
|
||||||
<image
|
<image
|
||||||
class="bg-image"
|
class="bg-image"
|
||||||
v-if="type === 4"
|
v-if="type === 4"
|
||||||
src="../static/app-bg5.png"
|
src="/static/app-bg5.png"
|
||||||
mode="widthFix"
|
mode="widthFix"
|
||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const props = defineProps({
|
|||||||
<template>
|
<template>
|
||||||
<view class="avatar" @click="onClick">
|
<view class="avatar" @click="onClick">
|
||||||
<image
|
<image
|
||||||
:src="src || '../static/user-icon.png'"
|
:src="src || '/static/user-icon.png'"
|
||||||
mode="widthFix"
|
mode="widthFix"
|
||||||
:style="{
|
:style="{
|
||||||
width: size + 'px',
|
width: size + 'px',
|
||||||
|
|||||||
@@ -214,13 +214,13 @@ onMounted(async () => {
|
|||||||
@touchstart.stop="confirmAdd"
|
@touchstart.stop="confirmAdd"
|
||||||
:style="{ ...getNewPos() }"
|
:style="{ ...getNewPos() }"
|
||||||
>
|
>
|
||||||
<image src="../static/arrow-edit-save.png" mode="widthFix" />
|
<image src="/static/arrow-edit-save.png" mode="widthFix" />
|
||||||
</view>
|
</view>
|
||||||
<view class="edit-btn delete-btn" @touchstart.stop="deleteArrow">
|
<view class="edit-btn delete-btn" @touchstart.stop="deleteArrow">
|
||||||
<image src="../static/arrow-edit-delete.png" mode="widthFix" />
|
<image src="/static/arrow-edit-delete.png" mode="widthFix" />
|
||||||
</view>
|
</view>
|
||||||
<view class="edit-btn drag-btn" @touchstart.stop="startDrag($event)">
|
<view class="edit-btn drag-btn" @touchstart.stop="startDrag($event)">
|
||||||
<image src="../static/arrow-edit-move.png" mode="widthFix" />
|
<image src="/static/arrow-edit-move.png" mode="widthFix" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</movable-view>
|
</movable-view>
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view
|
<view
|
||||||
class="container"
|
class="edit-option"
|
||||||
:style="{
|
:style="{
|
||||||
maxHeight: expand ? '500px' : '50px',
|
maxHeight: expand ? '500px' : '50px',
|
||||||
marginTop: noArrow ? '0' : '10px',
|
marginTop: noArrow ? '0' : '10px',
|
||||||
@@ -207,14 +207,14 @@ onMounted(async () => {
|
|||||||
}}</text>
|
}}</text>
|
||||||
<text v-if="!expand && itemIndex === 3">{{ formatSetAndAmount }}</text>
|
<text v-if="!expand && itemIndex === 3">{{ formatSetAndAmount }}</text>
|
||||||
</block>
|
</block>
|
||||||
<button hover-class="none">
|
<view>
|
||||||
<image
|
<image
|
||||||
v-if="!noArrow"
|
v-if="!noArrow"
|
||||||
src="../static/arrow-grey.png"
|
src="/static/arrow-grey.png"
|
||||||
mode="widthFix"
|
mode="widthFix"
|
||||||
:style="{ transform: expand ? 'rotateX(180deg)' : 'rotateX(0deg)' }"
|
:style="{ transform: expand ? 'rotateX(180deg)' : 'rotateX(0deg)' }"
|
||||||
/>
|
/>
|
||||||
</button>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="itemIndex === 0" class="bow-items">
|
<view v-if="itemIndex === 0" class="bow-items">
|
||||||
<view
|
<view
|
||||||
@@ -342,7 +342,7 @@ onMounted(async () => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.container {
|
.edit-option {
|
||||||
width: calc(100% - 20px);
|
width: calc(100% - 20px);
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
@@ -352,25 +352,25 @@ onMounted(async () => {
|
|||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
.container > view:first-child {
|
.edit-option > view:first-child {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
}
|
}
|
||||||
.container > view:first-child > view:first-child {
|
.edit-option > view:first-child > view:first-child {
|
||||||
width: 85px;
|
width: 85px;
|
||||||
}
|
}
|
||||||
.container > view:first-child > text:nth-child(2) {
|
.edit-option > view:first-child > text:nth-child(2) {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
.container > view:first-child > button {
|
.edit-option > view:first-child > view {
|
||||||
width: 85px;
|
width: 85px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
.container > view:first-child > button > image {
|
.edit-option > view:first-child > view > image {
|
||||||
transition: all 0.5s ease;
|
transition: all 0.5s ease;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const onClick = () => {
|
|||||||
uni.navigateBack();
|
uni.navigateBack();
|
||||||
} else {
|
} else {
|
||||||
uni.redirectTo({
|
uni.redirectTo({
|
||||||
url: "/pages/index",
|
url: "/pages/index/index",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,7 +42,7 @@ onMounted(() => {
|
|||||||
<template>
|
<template>
|
||||||
<view class="container">
|
<view class="container">
|
||||||
<view class="back-btn" @click="onClick">
|
<view class="back-btn" @click="onClick">
|
||||||
<image src="../static/back-black.png" mode="widthFix" />
|
<image src="/static/back-black.png" mode="widthFix" />
|
||||||
</view>
|
</view>
|
||||||
<view v-if="pointBook" class="point-book-info">
|
<view v-if="pointBook" class="point-book-info">
|
||||||
<text>{{ pointBook.bowType.name }}</text>
|
<text>{{ pointBook.bowType.name }}</text>
|
||||||
|
|||||||
@@ -28,32 +28,27 @@ const hide = ref(true);
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="container" :style="{ width }">
|
<view class="input-row" :style="{ width }">
|
||||||
<input
|
<input
|
||||||
:type="type"
|
:type="type"
|
||||||
@change="onChange"
|
@change="onChange"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
placeholder-style="color: #999;"
|
placeholder-style="color: #999;"
|
||||||
/>
|
/>
|
||||||
<button v-if="btnType === 'code'" hover-class="none" class="get-code">
|
<view v-if="btnType === 'code'" class="get-code">
|
||||||
get verification code
|
get verification code
|
||||||
</button>
|
</view>
|
||||||
<button
|
<view v-if="type === 'password'" class="eye-btn" @click="hide = !hide">
|
||||||
v-if="type === 'password'"
|
|
||||||
hover-class="none"
|
|
||||||
class="eye-btn"
|
|
||||||
@click="hide = !hide"
|
|
||||||
>
|
|
||||||
<image
|
<image
|
||||||
:src="`../static/${hide ? 'eye-close' : 'eye-open'}.png`"
|
:src="`/static/${hide ? 'eye-close' : 'eye-open'}.png`"
|
||||||
mode="widthFix"
|
mode="widthFix"
|
||||||
/>
|
/>
|
||||||
</button>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.container {
|
.input-row {
|
||||||
height: 100rpx;
|
height: 100rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -72,7 +67,8 @@ const hide = ref(true);
|
|||||||
.get-code {
|
.get-code {
|
||||||
color: #287fff;
|
color: #287fff;
|
||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
width: 80%;
|
width: 60%;
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
.eye-btn {
|
.eye-btn {
|
||||||
padding: 20rpx;
|
padding: 20rpx;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const targetOptions = ref({});
|
|||||||
|
|
||||||
const toDetailPage = () => {
|
const toDetailPage = () => {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/detail?id=${props.data.id}`,
|
url: `/pages/detail/index?id=${props.data.id}`,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ onMounted(() => {
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view>
|
<view>
|
||||||
<image src="../static/bow-target.png" mode="widthFix" />
|
<image src="/static/bow-target.png" mode="widthFix" />
|
||||||
<view class="arrow-amount">
|
<view class="arrow-amount">
|
||||||
<text>共</text>
|
<text>共</text>
|
||||||
<text>{{ data.arrows * data.groups }}</text>
|
<text>{{ data.arrows * data.groups }}</text>
|
||||||
|
|||||||
@@ -53,9 +53,8 @@ const onBtnClick = debounce(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button
|
<view
|
||||||
class="sbtn"
|
class="sbtn"
|
||||||
hover-class="none"
|
|
||||||
:style="{
|
:style="{
|
||||||
width: width,
|
width: width,
|
||||||
borderRadius: rounded + 'rpx',
|
borderRadius: rounded + 'rpx',
|
||||||
@@ -69,9 +68,9 @@ const onBtnClick = debounce(async () => {
|
|||||||
<slot />
|
<slot />
|
||||||
</block>
|
</block>
|
||||||
<block v-else>
|
<block v-else>
|
||||||
<image src="../static/btn-loading.png" mode="widthFix" class="loading" />
|
<image src="/static/btn-loading.png" mode="widthFix" class="loading" />
|
||||||
</block>
|
</block>
|
||||||
</button>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ watch(
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view
|
<view
|
||||||
class="container"
|
class="s-modal"
|
||||||
v-if="showContainer"
|
v-if="showContainer"
|
||||||
:style="{ opacity: show ? 1 : 0 }"
|
:style="{ opacity: show ? 1 : 0 }"
|
||||||
@click="onClose"
|
@click="onClose"
|
||||||
@@ -60,7 +60,7 @@ watch(
|
|||||||
mode="widthFix"
|
mode="widthFix"
|
||||||
/>
|
/>
|
||||||
<view class="close-btn" @click="onClose" v-if="!noBg">
|
<view class="close-btn" @click="onClose" v-if="!noBg">
|
||||||
<image src="../static/close-yellow.png" mode="widthFix" />
|
<image src="/static/close-yellow.png" mode="widthFix" />
|
||||||
</view>
|
</view>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</view>
|
</view>
|
||||||
@@ -68,7 +68,7 @@ watch(
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.container {
|
.s-modal {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ const props = defineProps({
|
|||||||
<template>
|
<template>
|
||||||
<view class="container" :style="{ display: show ? 'flex' : 'none' }">
|
<view class="container" :style="{ display: show ? 'flex' : 'none' }">
|
||||||
<view class="scale-in">
|
<view class="scale-in">
|
||||||
<image src="../static/point-book-tip-bg.png" mode="widthFix" />
|
<image src="/static/point-book-tip-bg.png" mode="widthFix" />
|
||||||
<slot />
|
<slot />
|
||||||
</view>
|
</view>
|
||||||
<IconButton
|
<IconButton
|
||||||
v-if="!!onClose"
|
v-if="!!onClose"
|
||||||
src="../static/close-white-outline.png"
|
src="/static/close-white-outline.png"
|
||||||
:width="30"
|
:width="30"
|
||||||
:onClick="onClose"
|
:onClick="onClose"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -19,14 +19,14 @@ const isIOS = computed(() => {
|
|||||||
<view class="tabbar" :style="{ paddingBottom: isIOS ? '30rpx' : '0rpx' }">
|
<view class="tabbar" :style="{ paddingBottom: isIOS ? '30rpx' : '0rpx' }">
|
||||||
<view @click="onClick(0)">
|
<view @click="onClick(0)">
|
||||||
<image
|
<image
|
||||||
:src="`../static/tab-score${selected === 0 ? '-s' : ''}.png`"
|
:src="`/static/tab-score${selected === 0 ? '-s' : ''}.png`"
|
||||||
mode="widthFix"
|
mode="widthFix"
|
||||||
/>
|
/>
|
||||||
<text :style="{ color: selected === 0 ? '#333' : '#999999' }">Score</text>
|
<text :style="{ color: selected === 0 ? '#333' : '#999999' }">Score</text>
|
||||||
</view>
|
</view>
|
||||||
<view @click="onClick(1)">
|
<view @click="onClick(1)">
|
||||||
<image
|
<image
|
||||||
:src="`../static/tab-history${selected === 1 ? '-s' : ''}.png`"
|
:src="`/static/tab-history${selected === 1 ? '-s' : ''}.png`"
|
||||||
mode="widthFix"
|
mode="widthFix"
|
||||||
/>
|
/>
|
||||||
<text :style="{ color: selected === 1 ? '#333' : '#999999' }"
|
<text :style="{ color: selected === 1 ? '#333' : '#999999' }"
|
||||||
@@ -35,7 +35,7 @@ const isIOS = computed(() => {
|
|||||||
</view>
|
</view>
|
||||||
<view @click="onClick(2)">
|
<view @click="onClick(2)">
|
||||||
<image
|
<image
|
||||||
:src="`../static/tab-user${selected === 2 ? '-s' : ''}.png`"
|
:src="`/static/tab-user${selected === 2 ? '-s' : ''}.png`"
|
||||||
mode="widthFix"
|
mode="widthFix"
|
||||||
/>
|
/>
|
||||||
<text :style="{ color: selected === 2 ? '#333' : '#999999' }"
|
<text :style="{ color: selected === 2 ? '#333' : '#999999' }"
|
||||||
|
|||||||
+80
-70
@@ -1,43 +1,48 @@
|
|||||||
{
|
{
|
||||||
"name" : "ARCX",
|
"name": "ARCX",
|
||||||
"appid" : "__UNI__8A880C6",
|
"appid": "__UNI__8A880C6",
|
||||||
"description" : "ARCX for iOS & Android",
|
"description": "ARCX for iOS & Android",
|
||||||
"versionName" : "0.1.1",
|
"versionName": "0.1.1",
|
||||||
"versionCode" : 101,
|
"versionCode": 101,
|
||||||
"transformPx" : false,
|
"transformPx": false,
|
||||||
"app-plus" : {
|
"app-plus": {
|
||||||
"screenOrientation" : [ "portrait-primary" ],
|
"screenOrientation": [
|
||||||
"splashscreen" : {
|
"portrait-primary"
|
||||||
"autoclose" : true,
|
],
|
||||||
"waiting" : true
|
"splashscreen": {
|
||||||
|
"autoclose": true,
|
||||||
|
"waiting": true
|
||||||
},
|
},
|
||||||
"compatible" : {
|
"compatible": {
|
||||||
"ignoreVersion" : true
|
"ignoreVersion": true
|
||||||
},
|
},
|
||||||
"modules" : {},
|
"modules": {},
|
||||||
"distribute" : {
|
"distribute": {
|
||||||
"android" : {
|
"android": {
|
||||||
"packagename" : "com.pointbook.app",
|
"packagename": "com.pointbook.app",
|
||||||
"keystore" : "__ASK__:absolute/path/to/your.keystore",
|
"keystore": "__ASK__:absolute/path/to/your.keystore",
|
||||||
"password" : "__ASK__:keystore_password",
|
"password": "__ASK__:keystore_password",
|
||||||
"aliasname" : "__ASK__:key_alias",
|
"aliasname": "__ASK__:key_alias",
|
||||||
"minSdkVersion" : 21,
|
"minSdkVersion": 21,
|
||||||
"targetSdkVersion" : 34,
|
"targetSdkVersion": 34,
|
||||||
"abiFilters" : [ "armeabi-v7a", "arm64-v8a" ],
|
"abiFilters": [
|
||||||
"custompermissions" : true,
|
"armeabi-v7a",
|
||||||
"permissions" : [],
|
"arm64-v8a"
|
||||||
"permissionExternalStorage" : {
|
],
|
||||||
"request" : "once",
|
"custompermissions": true,
|
||||||
"prompt" : "Storage access is required to save app data"
|
"permissions": [],
|
||||||
|
"permissionExternalStorage": {
|
||||||
|
"request": "once",
|
||||||
|
"prompt": "Storage access is required to save app data"
|
||||||
},
|
},
|
||||||
"permissionPhoneState" : {
|
"permissionPhoneState": {
|
||||||
"request" : "none",
|
"request": "none",
|
||||||
"prompt" : ""
|
"prompt": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ios" : {
|
"ios": {
|
||||||
"appid" : "com.shelingxingqiu.arcx",
|
"appid": "com.shelingxingqiu.arcx",
|
||||||
"devices" : "iphone",
|
"devices": "iphone",
|
||||||
// "deploymentTarget" : "13.0",
|
// "deploymentTarget" : "13.0",
|
||||||
// "mobileprovision" : "/Users/makaihong/Projects/point-book-app/DistributeProfile.mobileprovision",
|
// "mobileprovision" : "/Users/makaihong/Projects/point-book-app/DistributeProfile.mobileprovision",
|
||||||
// "p12" : "/Users/makaihong/Projects/point-book-app/Distribute_pwd_123456.p12",
|
// "p12" : "/Users/makaihong/Projects/point-book-app/Distribute_pwd_123456.p12",
|
||||||
@@ -48,49 +53,54 @@
|
|||||||
// "id" : "com.pointbook.scheme"
|
// "id" : "com.pointbook.scheme"
|
||||||
// }
|
// }
|
||||||
// ],
|
// ],
|
||||||
"privacyDescription" : {
|
"privacyDescription": {
|
||||||
"NSCameraUsageDescription" : "Camera access is required for taking photos and scanning",
|
"NSCameraUsageDescription": "Camera access is required for taking photos and scanning",
|
||||||
"NSMicrophoneUsageDescription" : "Microphone access is required for recording and calls",
|
"NSMicrophoneUsageDescription": "Microphone access is required for recording and calls",
|
||||||
"NSPhotoLibraryUsageDescription" : "Photo library access is required to select images",
|
"NSPhotoLibraryUsageDescription": "Photo library access is required to select images",
|
||||||
"NSPhotoLibraryAddUsageDescription" : "Photo library write permission is required to save images",
|
"NSPhotoLibraryAddUsageDescription": "Photo library write permission is required to save images",
|
||||||
"NSLocationWhenInUseUsageDescription" : "Location access is required to provide location-based features",
|
"NSLocationWhenInUseUsageDescription": "Location access is required to provide location-based features",
|
||||||
"NSUserTrackingUsageDescription" : "We use tracking to analyze in-app clicks and usage to continuously improve the product experience."
|
"NSUserTrackingUsageDescription": "We use tracking to analyze in-app clicks and usage to continuously improve the product experience."
|
||||||
},
|
},
|
||||||
"dSYMs" : false
|
"dSYMs": false,
|
||||||
|
"infoplist": {
|
||||||
|
"UIDeviceFamily": [
|
||||||
|
1
|
||||||
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"icons" : {
|
"icons": {
|
||||||
"android" : {
|
"android": {
|
||||||
"hdpi" : "unpackage/res/icons/72x72.png",
|
"hdpi": "unpackage/res/icons/72x72.png",
|
||||||
"xhdpi" : "unpackage/res/icons/96x96.png",
|
"xhdpi": "unpackage/res/icons/96x96.png",
|
||||||
"xxhdpi" : "unpackage/res/icons/144x144.png",
|
"xxhdpi": "unpackage/res/icons/144x144.png",
|
||||||
"xxxhdpi" : "unpackage/res/icons/192x192.png"
|
"xxxhdpi": "unpackage/res/icons/192x192.png"
|
||||||
},
|
},
|
||||||
"ios" : {
|
"ios": {
|
||||||
"appstore" : "unpackage/res/icons/1024x1024.png",
|
"appstore": "unpackage/res/icons/1024x1024.png",
|
||||||
"ipad" : {
|
"ipad": {
|
||||||
"app" : "unpackage/res/icons/76x76.png",
|
"app": "unpackage/res/icons/76x76.png",
|
||||||
"app@2x" : "unpackage/res/icons/152x152.png",
|
"app@2x": "unpackage/res/icons/152x152.png",
|
||||||
"notification" : "unpackage/res/icons/20x20.png",
|
"notification": "unpackage/res/icons/20x20.png",
|
||||||
"notification@2x" : "unpackage/res/icons/40x40.png",
|
"notification@2x": "unpackage/res/icons/40x40.png",
|
||||||
"proapp@2x" : "unpackage/res/icons/167x167.png",
|
"proapp@2x": "unpackage/res/icons/167x167.png",
|
||||||
"settings" : "unpackage/res/icons/29x29.png",
|
"settings": "unpackage/res/icons/29x29.png",
|
||||||
"settings@2x" : "unpackage/res/icons/58x58.png",
|
"settings@2x": "unpackage/res/icons/58x58.png",
|
||||||
"spotlight" : "unpackage/res/icons/40x40.png",
|
"spotlight": "unpackage/res/icons/40x40.png",
|
||||||
"spotlight@2x" : "unpackage/res/icons/80x80.png"
|
"spotlight@2x": "unpackage/res/icons/80x80.png"
|
||||||
},
|
},
|
||||||
"iphone" : {
|
"iphone": {
|
||||||
"app@2x" : "unpackage/res/icons/120x120.png",
|
"app@2x": "unpackage/res/icons/120x120.png",
|
||||||
"app@3x" : "unpackage/res/icons/180x180.png",
|
"app@3x": "unpackage/res/icons/180x180.png",
|
||||||
"notification@2x" : "unpackage/res/icons/40x40.png",
|
"notification@2x": "unpackage/res/icons/40x40.png",
|
||||||
"notification@3x" : "unpackage/res/icons/60x60.png",
|
"notification@3x": "unpackage/res/icons/60x60.png",
|
||||||
"settings@2x" : "unpackage/res/icons/58x58.png",
|
"settings@2x": "unpackage/res/icons/58x58.png",
|
||||||
"settings@3x" : "unpackage/res/icons/87x87.png",
|
"settings@3x": "unpackage/res/icons/87x87.png",
|
||||||
"spotlight@2x" : "unpackage/res/icons/80x80.png",
|
"spotlight@2x": "unpackage/res/icons/80x80.png",
|
||||||
"spotlight@3x" : "unpackage/res/icons/120x120.png"
|
"spotlight@3x": "unpackage/res/icons/120x120.png"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vueVersion" : "3"
|
"vueVersion": "3"
|
||||||
}
|
}
|
||||||
+16
-9
@@ -1,35 +1,42 @@
|
|||||||
{
|
{
|
||||||
"pages": [
|
"pages": [
|
||||||
{
|
{
|
||||||
"path": "pages/index"
|
"path": "pages/index/index"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/signin"
|
"path": "pages/signup/index"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/signup"
|
"path": "pages/signin/index"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/create"
|
"path": "pages/create/index"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/detail"
|
"path": "pages/detail/index"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/edit"
|
"path": "pages/edit/index"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/reset-pwd"
|
"path": "pages/reset-pwd/index"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/edit-profile"
|
"path": "pages/edit-profile/index"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/list/index"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"globalStyle": {
|
"globalStyle": {
|
||||||
"navigationBarTextStyle": "white",
|
"navigationBarTextStyle": "white",
|
||||||
"navigationBarBackgroundColor": "#2c3e50",
|
"navigationBarBackgroundColor": "#2c3e50",
|
||||||
"backgroundColor": "#ffffff",
|
"backgroundColor": "#ffffff",
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom",
|
||||||
|
"app-plus": {
|
||||||
|
"softinputMode": "adjustResize",
|
||||||
|
"softinputNavBar": "none"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"condition": {
|
"condition": {
|
||||||
"current": 0,
|
"current": 0,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const onExpandChange = (index, expand) => {
|
|||||||
|
|
||||||
const toListPage = () => {
|
const toListPage = () => {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: "/pages/list",
|
url: "/pages/list/index",
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const onSelect = (itemIndex, value) => {
|
const onSelect = (itemIndex, value) => {
|
||||||
@@ -49,7 +49,7 @@ const toEditPage = () => {
|
|||||||
amountGroup: amountGroup.value,
|
amountGroup: amountGroup.value,
|
||||||
});
|
});
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: "/pages/edit",
|
url: "/pages/edit/index",
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
@@ -68,7 +68,7 @@ const goBack = () => {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
uni.redirectTo({
|
uni.redirectTo({
|
||||||
url: "/pages/index",
|
url: "/pages/index/index",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -126,7 +126,7 @@ onLoad(async (options) => {
|
|||||||
>
|
>
|
||||||
<text>Stability</text>
|
<text>Stability</text>
|
||||||
<image
|
<image
|
||||||
src="../static/s-question-mark.png"
|
src="/static/s-question-mark.png"
|
||||||
mode="widthFix"
|
mode="widthFix"
|
||||||
class="question-mark"
|
class="question-mark"
|
||||||
/>
|
/>
|
||||||
@@ -154,7 +154,7 @@ onLoad(async (options) => {
|
|||||||
@click="() => openTip(3)"
|
@click="() => openTip(3)"
|
||||||
v-if="user.id === record.user.id"
|
v-if="user.id === record.user.id"
|
||||||
>
|
>
|
||||||
<image src="../static/edit.png" mode="widthFix" />
|
<image src="/static/edit.png" mode="widthFix" />
|
||||||
<text>Notes</text>
|
<text>Notes</text>
|
||||||
</button>
|
</button>
|
||||||
</view>
|
</view>
|
||||||
@@ -163,7 +163,7 @@ onLoad(async (options) => {
|
|||||||
<text>Distribution</text>
|
<text>Distribution</text>
|
||||||
<!-- <button hover-class="none" @click="() => openTip(2)">
|
<!-- <button hover-class="none" @click="() => openTip(2)">
|
||||||
<image
|
<image
|
||||||
src="../static/s-question-mark.png"
|
src="/static/s-question-mark.png"
|
||||||
mode="widthFix"
|
mode="widthFix"
|
||||||
class="question-mark"
|
class="question-mark"
|
||||||
/>
|
/>
|
||||||
@@ -55,7 +55,7 @@ const onSubmit = async () => {
|
|||||||
);
|
);
|
||||||
if (res.record_id) {
|
if (res.record_id) {
|
||||||
uni.redirectTo({
|
uni.redirectTo({
|
||||||
url: `/pages/detail?id=${res.record_id}`,
|
url: `/pages/detail/index?id=${res.record_id}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,7 +110,7 @@ onMounted(() => {
|
|||||||
<text>Set {{ currentGroup }}</text>
|
<text>Set {{ currentGroup }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view @click="deleteArrow">
|
<view @click="deleteArrow">
|
||||||
<image src="../static/delete.png" />
|
<image src="/static/delete.png" />
|
||||||
<text>Delete</text>
|
<text>Delete</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { ref, computed, onMounted, watch } from "vue";
|
|
||||||
import Score from "@/pages/score.vue";
|
|
||||||
import History from "@/pages/list.vue";
|
|
||||||
import Profile from "@/pages/profile.vue";
|
|
||||||
import Tabbar from "@/components/Tabbar.vue";
|
|
||||||
|
|
||||||
import useStore from "@/store";
|
|
||||||
import { storeToRefs } from "pinia";
|
|
||||||
const { user } = storeToRefs(useStore());
|
|
||||||
|
|
||||||
const isIOS = computed(() => {
|
|
||||||
const systemInfo = uni.getDeviceInfo();
|
|
||||||
return systemInfo.osName === "ios";
|
|
||||||
});
|
|
||||||
|
|
||||||
const tabIndex = ref(0);
|
|
||||||
const onTabChnage = (index) => {
|
|
||||||
tabIndex.value = index;
|
|
||||||
};
|
|
||||||
const editAvatar = ref(false);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<view class="container">
|
|
||||||
<swiper
|
|
||||||
:current="tabIndex"
|
|
||||||
@change="(e) => (tabIndex = e.detail.current)"
|
|
||||||
:style="{
|
|
||||||
height: `calc(100vh - 100rpx - ${isIOS ? '30rpx' : '0rpx'})`,
|
|
||||||
background: '#f5f5f5',
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<swiper-item>
|
|
||||||
<Score />
|
|
||||||
</swiper-item>
|
|
||||||
<swiper-item>
|
|
||||||
<History />
|
|
||||||
</swiper-item>
|
|
||||||
<swiper-item>
|
|
||||||
<Profile :editAvatar="() => (editAvatar = true)" />
|
|
||||||
</swiper-item>
|
|
||||||
</swiper>
|
|
||||||
<view
|
|
||||||
class="edit-avatar"
|
|
||||||
:style="{ height: editAvatar ? '100vh' : '0' }"
|
|
||||||
@click="editAvatar = false"
|
|
||||||
>
|
|
||||||
<image :src="user.avatar" mode="widthFix" />
|
|
||||||
<view>
|
|
||||||
<view>
|
|
||||||
<text>Take a photo</text>
|
|
||||||
<image src="../static/back-grey.png" mode="widthFix" />
|
|
||||||
</view>
|
|
||||||
<view>
|
|
||||||
<text>Choose photo</text>
|
|
||||||
<image src="../static/back-grey.png" mode="widthFix" />
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<Tabbar :selected="tabIndex" :onClick="onTabChnage" />
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.container {
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vw;
|
|
||||||
background: #f5f5f5;
|
|
||||||
}
|
|
||||||
.edit-avatar {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
width: 100vw;
|
|
||||||
background: rgba(0, 0, 0, 0.8);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
overflow: hidden;
|
|
||||||
z-index: 999;
|
|
||||||
}
|
|
||||||
.edit-avatar > image {
|
|
||||||
width: 85vw;
|
|
||||||
height: 85vw;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
.edit-avatar > view {
|
|
||||||
border-radius: 25rpx;
|
|
||||||
margin-top: 100rpx;
|
|
||||||
width: calc(100% - 150rpx);
|
|
||||||
padding: 0 40rpx;
|
|
||||||
background: #404040;
|
|
||||||
}
|
|
||||||
.edit-avatar > view > view {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
font-size: 30rpx;
|
|
||||||
color: #ffffff;
|
|
||||||
padding: 40rpx 0;
|
|
||||||
}
|
|
||||||
.edit-avatar > view > view:not(:last-child) {
|
|
||||||
border-bottom: 1rpx solid #fff3;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
.edit-avatar > view > view > image {
|
|
||||||
width: 28rpx;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, computed, onMounted, watch } from "vue";
|
||||||
|
import Score from "@/pages/score/index.vue";
|
||||||
|
import History from "@/pages/list/index.vue";
|
||||||
|
import Profile from "@/pages/profile/index.vue";
|
||||||
|
import Tabbar from "@/components/Tabbar.vue";
|
||||||
|
import SModal from "@/components/SModal.vue";
|
||||||
|
import EditOption from "@/components/EditOption.vue";
|
||||||
|
import ScreenHint from "@/components/ScreenHint.vue";
|
||||||
|
import { removePointRecord } from "@/api";
|
||||||
|
|
||||||
|
import useStore from "@/store";
|
||||||
|
import { storeToRefs } from "pinia";
|
||||||
|
const { user } = storeToRefs(useStore());
|
||||||
|
|
||||||
|
const isIOS = computed(() => {
|
||||||
|
const systemInfo = uni.getDeviceInfo();
|
||||||
|
return systemInfo.osName === "ios";
|
||||||
|
});
|
||||||
|
|
||||||
|
const tabIndex = ref(0);
|
||||||
|
const editAvatar = ref(false);
|
||||||
|
const selectorIndex = ref(0);
|
||||||
|
const bowType = ref({});
|
||||||
|
const distance = ref(0);
|
||||||
|
const bowtargetType = ref({});
|
||||||
|
const showModal = ref(false);
|
||||||
|
const showTip = ref(false);
|
||||||
|
const removeId = ref("");
|
||||||
|
const dataSeq = ref(0);
|
||||||
|
|
||||||
|
const onTabChnage = (index) => {
|
||||||
|
tabIndex.value = index;
|
||||||
|
};
|
||||||
|
const openSelector = (index) => {
|
||||||
|
selectorIndex.value = index;
|
||||||
|
showModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSelectOption = (itemIndex, value) => {
|
||||||
|
if (itemIndex === 0) {
|
||||||
|
bowType.value = value.name === bowType.value.name ? {} : value;
|
||||||
|
} else if (itemIndex === 1) {
|
||||||
|
distance.value = value === distance.value ? 0 : value;
|
||||||
|
} else if (itemIndex === 2) {
|
||||||
|
bowtargetType.value = value.name === bowtargetType.value.name ? {} : value;
|
||||||
|
}
|
||||||
|
showModal.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onRemoveRecord = (item) => {
|
||||||
|
removeId.value = item.id;
|
||||||
|
showTip.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmRemove = async () => {
|
||||||
|
try {
|
||||||
|
showTip.value = false;
|
||||||
|
await removePointRecord(removeId.value);
|
||||||
|
dataSeq.value += 1;
|
||||||
|
uni.showToast({ title: "Deleted", icon: "none" });
|
||||||
|
} catch (e) {
|
||||||
|
uni.showToast({ title: "Delete failed, please retry", icon: "none" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="container">
|
||||||
|
<swiper
|
||||||
|
:current="tabIndex"
|
||||||
|
@change="(e) => (tabIndex = e.detail.current)"
|
||||||
|
:style="{
|
||||||
|
height: `calc(100vh - 100rpx - ${isIOS ? '30rpx' : '0rpx'})`,
|
||||||
|
background: '#f5f5f5',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<swiper-item>
|
||||||
|
<Score />
|
||||||
|
</swiper-item>
|
||||||
|
<swiper-item>
|
||||||
|
<History
|
||||||
|
:openSelector="openSelector"
|
||||||
|
:onRemoveRecord="onRemoveRecord"
|
||||||
|
:bowType="bowType"
|
||||||
|
:distance="distance"
|
||||||
|
:bowtargetType="bowtargetType"
|
||||||
|
:seq="dataSeq"
|
||||||
|
/>
|
||||||
|
</swiper-item>
|
||||||
|
<swiper-item>
|
||||||
|
<Profile :editAvatar="() => (editAvatar = true)" />
|
||||||
|
</swiper-item>
|
||||||
|
</swiper>
|
||||||
|
<view
|
||||||
|
class="edit-avatar"
|
||||||
|
:style="{ height: editAvatar ? '100vh' : '0' }"
|
||||||
|
@click="editAvatar = false"
|
||||||
|
>
|
||||||
|
<image :src="user.avatar" mode="widthFix" />
|
||||||
|
<view>
|
||||||
|
<view>
|
||||||
|
<text>Take a photo</text>
|
||||||
|
<image src="/static/back-grey.png" mode="widthFix" />
|
||||||
|
</view>
|
||||||
|
<view>
|
||||||
|
<text>Choose photo</text>
|
||||||
|
<image src="/static/back-grey.png" mode="widthFix" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<SModal
|
||||||
|
:show="showModal"
|
||||||
|
:noBg="true"
|
||||||
|
height="auto"
|
||||||
|
:onClose="() => (showModal = false)"
|
||||||
|
>
|
||||||
|
<view class="selector">
|
||||||
|
<view @click="() => (showModal = false)">
|
||||||
|
<image src="/static/close-grey.png" mode="widthFix" />
|
||||||
|
</view>
|
||||||
|
<EditOption
|
||||||
|
v-show="selectorIndex === 0"
|
||||||
|
:itemIndex="0"
|
||||||
|
:expand="true"
|
||||||
|
:noArrow="true"
|
||||||
|
:onSelect="onSelectOption"
|
||||||
|
:value="bowType.name"
|
||||||
|
/>
|
||||||
|
<EditOption
|
||||||
|
v-show="selectorIndex === 1"
|
||||||
|
:itemIndex="1"
|
||||||
|
:expand="true"
|
||||||
|
:noArrow="true"
|
||||||
|
:onSelect="onSelectOption"
|
||||||
|
:value="distance + ''"
|
||||||
|
/>
|
||||||
|
<EditOption
|
||||||
|
v-show="selectorIndex === 2"
|
||||||
|
:itemIndex="2"
|
||||||
|
:expand="true"
|
||||||
|
:noArrow="true"
|
||||||
|
:onSelect="onSelectOption"
|
||||||
|
:value="bowtargetType.name"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</SModal>
|
||||||
|
<ScreenHint :show="showTip">
|
||||||
|
<view class="tip-content">
|
||||||
|
<text>Are you sure to delete this record?</text>
|
||||||
|
<view>
|
||||||
|
<view @click="showTip = false">Cancel</view>
|
||||||
|
<view @click="confirmRemove">Confirm</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</ScreenHint>
|
||||||
|
<Tabbar :selected="tabIndex" :onClick="onTabChnage" />
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.container {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vw;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
.edit-avatar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 100vw;
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
.edit-avatar > image {
|
||||||
|
width: 85vw;
|
||||||
|
height: 85vw;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
.edit-avatar > view {
|
||||||
|
border-radius: 25rpx;
|
||||||
|
margin-top: 100rpx;
|
||||||
|
width: calc(100% - 150rpx);
|
||||||
|
padding: 0 40rpx;
|
||||||
|
background: #404040;
|
||||||
|
}
|
||||||
|
.edit-avatar > view > view {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 40rpx 0;
|
||||||
|
}
|
||||||
|
.edit-avatar > view > view:not(:last-child) {
|
||||||
|
border-bottom: 1rpx solid #fff3;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
.edit-avatar > view > view > image {
|
||||||
|
width: 28rpx;
|
||||||
|
}
|
||||||
|
.selector {
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.selector > view {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.selector > view > image {
|
||||||
|
width: 40px;
|
||||||
|
}
|
||||||
|
.tip-content {
|
||||||
|
width: 100%;
|
||||||
|
padding: 25px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
.tip-content > text {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
.tip-content > view {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.tip-content > view > view {
|
||||||
|
width: 48%;
|
||||||
|
background: linear-gradient(180deg, #fbfbfb 0%, #f5f5f5 100%);
|
||||||
|
border-radius: 22px;
|
||||||
|
border: 1px solid #eeeeee;
|
||||||
|
padding: 12px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
.tip-content > view > view:last-child {
|
||||||
|
background: #fed847;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,262 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { ref, onMounted } from "vue";
|
|
||||||
import SModal from "@/components/SModal.vue";
|
|
||||||
import EditOption from "@/components/EditOption.vue";
|
|
||||||
import PointRecord from "@/components/PointRecord.vue";
|
|
||||||
import ScrollList from "@/components/ScrollList.vue";
|
|
||||||
import ScreenHint from "@/components/ScreenHint.vue";
|
|
||||||
import { getPointBookListAPI, removePointRecord } from "@/api";
|
|
||||||
|
|
||||||
const statusBarHeight = uni.getSystemInfoSync().statusBarHeight;
|
|
||||||
const showTip = ref(false);
|
|
||||||
const bowType = ref({});
|
|
||||||
const distance = ref(0);
|
|
||||||
const bowtargetType = ref({});
|
|
||||||
const showModal = ref(false);
|
|
||||||
const selectorIndex = ref(0);
|
|
||||||
const list = ref([]);
|
|
||||||
const removeId = ref("");
|
|
||||||
|
|
||||||
const onListLoading = async (page) => {
|
|
||||||
const result = await getPointBookListAPI(
|
|
||||||
page,
|
|
||||||
bowType.value.id,
|
|
||||||
distance.value,
|
|
||||||
bowtargetType.value.id
|
|
||||||
);
|
|
||||||
if (page === 1) {
|
|
||||||
list.value = result;
|
|
||||||
} else {
|
|
||||||
list.value = list.value.concat(result);
|
|
||||||
}
|
|
||||||
return result.length;
|
|
||||||
};
|
|
||||||
|
|
||||||
const openSelector = (index) => {
|
|
||||||
selectorIndex.value = index;
|
|
||||||
showModal.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onRemoveRecord = (item) => {
|
|
||||||
removeId.value = item.id;
|
|
||||||
showTip.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const confirmRemove = async () => {
|
|
||||||
try {
|
|
||||||
showTip.value = false;
|
|
||||||
await removePointRecord(removeId.value);
|
|
||||||
list.value = list.value.filter((it) => it.id !== removeId.value);
|
|
||||||
uni.showToast({ title: "Deleted", icon: "none" });
|
|
||||||
} catch (e) {
|
|
||||||
uni.showToast({ title: "Delete failed, please retry", icon: "none" });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSelectOption = (itemIndex, value) => {
|
|
||||||
if (itemIndex === 0) {
|
|
||||||
bowType.value = value.name === bowType.value.name ? {} : value;
|
|
||||||
} else if (itemIndex === 1) {
|
|
||||||
distance.value = value === distance.value ? 0 : value;
|
|
||||||
} else if (itemIndex === 2) {
|
|
||||||
bowtargetType.value = value.name === bowtargetType.value.name ? {} : value;
|
|
||||||
}
|
|
||||||
showModal.value = false;
|
|
||||||
onListLoading(1);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<view
|
|
||||||
class="list"
|
|
||||||
:style="{
|
|
||||||
paddingTop: statusBarHeight + 'px',
|
|
||||||
backgroundSize: `100% ${statusBarHeight + 50}px`,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<view class="list-header">
|
|
||||||
<text>History</text>
|
|
||||||
</view>
|
|
||||||
<view class="selectors">
|
|
||||||
<view @click="() => openSelector(0)">
|
|
||||||
<text :style="{ color: bowType.name ? '#000' : '#999' }">{{
|
|
||||||
bowType.name || "Please select"
|
|
||||||
}}</text>
|
|
||||||
<image src="../static/arrow-grey.png" mode="widthFix" />
|
|
||||||
</view>
|
|
||||||
<view @click="() => openSelector(1)">
|
|
||||||
<text :style="{ color: distance ? '#000' : '#999' }">{{
|
|
||||||
distance ? distance + " m" : "Please select"
|
|
||||||
}}</text>
|
|
||||||
<image src="../static/arrow-grey.png" mode="widthFix" />
|
|
||||||
</view>
|
|
||||||
<view @click="() => openSelector(2)">
|
|
||||||
<text :style="{ color: bowtargetType.name ? '#000' : '#999' }">{{
|
|
||||||
bowtargetType.name || "Please select"
|
|
||||||
}}</text>
|
|
||||||
<image src="../static/arrow-grey.png" mode="widthFix" />
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="point-records">
|
|
||||||
<ScrollList :onLoading="onListLoading">
|
|
||||||
<view v-for="(item, index) in list" :key="item.id">
|
|
||||||
<PointRecord :data="item" :onRemove="onRemoveRecord" />
|
|
||||||
<view
|
|
||||||
v-if="index < list.length - 1"
|
|
||||||
:style="{ height: '25rpx' }"
|
|
||||||
></view>
|
|
||||||
</view>
|
|
||||||
<view class="no-data" v-if="list.length === 0">No data</view>
|
|
||||||
</ScrollList>
|
|
||||||
</view>
|
|
||||||
<SModal
|
|
||||||
:show="showModal"
|
|
||||||
:noBg="true"
|
|
||||||
height="auto"
|
|
||||||
:onClose="() => (showModal = false)"
|
|
||||||
>
|
|
||||||
<view class="selector">
|
|
||||||
<button hover-class="none" @click="() => (showModal = false)">
|
|
||||||
<image src="../static/close-grey.png" mode="widthFix" />
|
|
||||||
</button>
|
|
||||||
<EditOption
|
|
||||||
v-show="selectorIndex === 0"
|
|
||||||
:itemIndex="0"
|
|
||||||
:expand="true"
|
|
||||||
:noArrow="true"
|
|
||||||
:onSelect="onSelectOption"
|
|
||||||
:value="bowType.name"
|
|
||||||
/>
|
|
||||||
<EditOption
|
|
||||||
v-show="selectorIndex === 1"
|
|
||||||
:itemIndex="1"
|
|
||||||
:expand="true"
|
|
||||||
:noArrow="true"
|
|
||||||
:onSelect="onSelectOption"
|
|
||||||
:value="distance + ''"
|
|
||||||
/>
|
|
||||||
<EditOption
|
|
||||||
v-show="selectorIndex === 2"
|
|
||||||
:itemIndex="2"
|
|
||||||
:expand="true"
|
|
||||||
:noArrow="true"
|
|
||||||
:onSelect="onSelectOption"
|
|
||||||
:value="bowtargetType.name"
|
|
||||||
/>
|
|
||||||
</view>
|
|
||||||
</SModal>
|
|
||||||
<ScreenHint :show="showTip">
|
|
||||||
<view class="tip-content">
|
|
||||||
<text>Are you sure to delete this record?</text>
|
|
||||||
<view>
|
|
||||||
<button hover-class="none" @click="showTip = false">Cancel</button>
|
|
||||||
<button hover-class="none" @click="confirmRemove">Confirm</button>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</ScreenHint>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.list {
|
|
||||||
background: url("../static/app-bg3.png") no-repeat top/contain;
|
|
||||||
}
|
|
||||||
.list-header {
|
|
||||||
height: 40px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 30rpx;
|
|
||||||
color: #333333;
|
|
||||||
}
|
|
||||||
.selectors {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 0 15px;
|
|
||||||
margin-top: 40rpx;
|
|
||||||
}
|
|
||||||
.selectors > view {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
background-color: #fff;
|
|
||||||
height: 55px;
|
|
||||||
border-radius: 12px;
|
|
||||||
color: #333;
|
|
||||||
font-size: 13px;
|
|
||||||
width: 26vw;
|
|
||||||
}
|
|
||||||
.selectors > view:last-child {
|
|
||||||
width: 34vw;
|
|
||||||
}
|
|
||||||
.selectors > view > text {
|
|
||||||
width: calc(100% - 11vw);
|
|
||||||
text-align: center;
|
|
||||||
margin-left: 3vw;
|
|
||||||
}
|
|
||||||
.selectors > view > image {
|
|
||||||
width: 5vw;
|
|
||||||
margin-right: 3vw;
|
|
||||||
}
|
|
||||||
.selector {
|
|
||||||
padding: 10px;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 10px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.selector > button {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
.selector > button > image {
|
|
||||||
width: 40px;
|
|
||||||
}
|
|
||||||
.point-records {
|
|
||||||
margin: 0 15px;
|
|
||||||
margin-top: 10px;
|
|
||||||
height: calc(100% - 80px);
|
|
||||||
}
|
|
||||||
.no-data {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
color: #999999;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
.tip-content {
|
|
||||||
width: 100%;
|
|
||||||
padding: 25px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
.tip-content > text {
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 14px;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
.tip-content > view {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
.tip-content > view > button {
|
|
||||||
width: 48%;
|
|
||||||
background: linear-gradient(180deg, #fbfbfb 0%, #f5f5f5 100%);
|
|
||||||
border-radius: 22px;
|
|
||||||
border: 1px solid #eeeeee;
|
|
||||||
padding: 12px 0;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
.tip-content > view > button:last-child {
|
|
||||||
background: #fed847;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, watch } from "vue";
|
||||||
|
import PointRecord from "@/components/PointRecord.vue";
|
||||||
|
import ScrollList from "@/components/ScrollList.vue";
|
||||||
|
import { getPointBookListAPI } from "@/api";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
openSelector: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
onRemoveRecord: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
bowType: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
distance: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
bowtargetType: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
seq: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const statusBarHeight = uni.getSystemInfoSync().statusBarHeight;
|
||||||
|
const list = ref([]);
|
||||||
|
const scrollListRef = ref(null);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [props.bowType, props.distance, props.bowtargetType, props.seq],
|
||||||
|
() => {
|
||||||
|
if (scrollListRef.value) {
|
||||||
|
scrollListRef.value.refresherrefresh();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const onListLoading = async (page) => {
|
||||||
|
const result = await getPointBookListAPI(
|
||||||
|
page,
|
||||||
|
props.bowType.id,
|
||||||
|
props.distance,
|
||||||
|
props.bowtargetType.id
|
||||||
|
);
|
||||||
|
if (page === 1) {
|
||||||
|
list.value = result;
|
||||||
|
} else {
|
||||||
|
list.value = list.value.concat(result);
|
||||||
|
}
|
||||||
|
return result.length;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view
|
||||||
|
class="list"
|
||||||
|
:style="{
|
||||||
|
paddingTop: statusBarHeight + 'px',
|
||||||
|
backgroundSize: `100% ${statusBarHeight + 50}px`,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<view class="list-header">
|
||||||
|
<text>History</text>
|
||||||
|
</view>
|
||||||
|
<view class="selectors">
|
||||||
|
<view @click="() => openSelector(0)">
|
||||||
|
<text :style="{ color: bowType.name ? '#000' : '#999' }">{{
|
||||||
|
bowType.name || "Please select"
|
||||||
|
}}</text>
|
||||||
|
<image src="/static/arrow-grey.png" mode="widthFix" />
|
||||||
|
</view>
|
||||||
|
<view @click="() => openSelector(1)">
|
||||||
|
<text :style="{ color: distance ? '#000' : '#999' }">{{
|
||||||
|
distance ? distance + " m" : "Please select"
|
||||||
|
}}</text>
|
||||||
|
<image src="/static/arrow-grey.png" mode="widthFix" />
|
||||||
|
</view>
|
||||||
|
<view @click="() => openSelector(2)">
|
||||||
|
<text :style="{ color: bowtargetType.name ? '#000' : '#999' }">{{
|
||||||
|
bowtargetType.name || "Please select"
|
||||||
|
}}</text>
|
||||||
|
<image src="/static/arrow-grey.png" mode="widthFix" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="point-records">
|
||||||
|
<ScrollList :onLoading="onListLoading" ref="scrollListRef">
|
||||||
|
<view v-for="(item, index) in list" :key="item.id">
|
||||||
|
<PointRecord :data="item" :onRemove="onRemoveRecord" />
|
||||||
|
<view
|
||||||
|
v-if="index < list.length - 1"
|
||||||
|
:style="{ height: '25rpx' }"
|
||||||
|
></view>
|
||||||
|
</view>
|
||||||
|
<view class="no-data" v-if="list.length === 0">No data</view>
|
||||||
|
</ScrollList>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.list {
|
||||||
|
background: url("/static/app-bg3.png") no-repeat top/contain;
|
||||||
|
}
|
||||||
|
.list-header {
|
||||||
|
height: 40px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
.selectors {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 15px;
|
||||||
|
margin-top: 40rpx;
|
||||||
|
}
|
||||||
|
.selectors > view {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
background-color: #fff;
|
||||||
|
height: 55px;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #333;
|
||||||
|
font-size: 13px;
|
||||||
|
width: 26vw;
|
||||||
|
}
|
||||||
|
.selectors > view:last-child {
|
||||||
|
width: 34vw;
|
||||||
|
}
|
||||||
|
.selectors > view > text {
|
||||||
|
width: calc(100% - 11vw);
|
||||||
|
text-align: center;
|
||||||
|
margin-left: 3vw;
|
||||||
|
}
|
||||||
|
.selectors > view > image {
|
||||||
|
width: 5vw;
|
||||||
|
margin-right: 3vw;
|
||||||
|
}
|
||||||
|
.point-records {
|
||||||
|
margin: 0 15px;
|
||||||
|
margin-top: 10px;
|
||||||
|
height: calc(100% - 80px);
|
||||||
|
}
|
||||||
|
.no-data {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: #999999;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -9,8 +9,8 @@ const { user } = storeToRefs(store);
|
|||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
editAvatar: {
|
editAvatar: {
|
||||||
type: String,
|
type: Function,
|
||||||
default: "",
|
default: () => {},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -18,13 +18,13 @@ const statusBarHeight = uni.getSystemInfoSync().statusBarHeight;
|
|||||||
|
|
||||||
const toEditPage = (type) => {
|
const toEditPage = (type) => {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: "/pages/edit-profile?type=" + type,
|
url: "/pages/edit-profile/index?type=" + type,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const toSignInPage = () => {
|
const toSignInPage = () => {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: "/pages/signin",
|
url: "/pages/signin/index",
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -39,25 +39,25 @@ const toSignInPage = () => {
|
|||||||
<view class="header">
|
<view class="header">
|
||||||
<image :src="user.avatar" mode="widthFix" />
|
<image :src="user.avatar" mode="widthFix" />
|
||||||
<view @click="editAvatar">
|
<view @click="editAvatar">
|
||||||
<image src="../static/pen-yellow.png" mode="widthFix" />
|
<image src="/static/pen-yellow.png" mode="widthFix" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="body">
|
<view class="body">
|
||||||
<view>
|
<view>
|
||||||
<view @click="toEditPage('Name')">
|
<view @click="toEditPage('Name')">
|
||||||
<image src="../static/user-yellow.png" mode="widthFix" />
|
<image src="/static/user-yellow.png" mode="widthFix" />
|
||||||
<text>Name</text>
|
<text>Name</text>
|
||||||
<image src="../static/back-grey.png" mode="widthFix" />
|
<image src="/static/back-grey.png" mode="widthFix" />
|
||||||
</view>
|
</view>
|
||||||
<view @click="toEditPage('Email')">
|
<view @click="toEditPage('Email')">
|
||||||
<image src="../static/email-yellow.png" mode="widthFix" />
|
<image src="/static/email-yellow.png" mode="widthFix" />
|
||||||
<text>Email</text>
|
<text>Email</text>
|
||||||
<image src="../static/back-grey.png" mode="widthFix" />
|
<image src="/static/back-grey.png" mode="widthFix" />
|
||||||
</view>
|
</view>
|
||||||
<view @click="toEditPage('Password')">
|
<view @click="toEditPage('Password')">
|
||||||
<image src="../static/password-yellow.png" mode="widthFix" />
|
<image src="/static/password-yellow.png" mode="widthFix" />
|
||||||
<text>Password</text>
|
<text>Password</text>
|
||||||
<image src="../static/back-grey.png" mode="widthFix" />
|
<image src="/static/back-grey.png" mode="widthFix" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view @click="toSignInPage">Log out</view>
|
<view @click="toSignInPage">Log out</view>
|
||||||
@@ -74,7 +74,7 @@ const toSignInPage = () => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: url("../static/app-bg6.png") no-repeat top/contain;
|
background: url("/static/app-bg6.png") no-repeat top/contain;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
@@ -23,6 +23,10 @@ const toSignInPage = () => {
|
|||||||
<InputRow type="password" placeholder="confirm password" width="80vw" />
|
<InputRow type="password" placeholder="confirm password" width="80vw" />
|
||||||
<view :style="{ height: '20rpx' }"></view>
|
<view :style="{ height: '20rpx' }"></view>
|
||||||
<SButton width="80vw">Submit</SButton>
|
<SButton width="80vw">Submit</SButton>
|
||||||
|
<view class="to-sign-up">
|
||||||
|
<text>have an account? </text>
|
||||||
|
<view hover-class="none" @click.stop="toSignInPage">sign in ></view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -49,4 +53,17 @@ const toSignInPage = () => {
|
|||||||
width: 80vw;
|
width: 80vw;
|
||||||
margin-bottom: 20rpx;
|
margin-bottom: 20rpx;
|
||||||
}
|
}
|
||||||
|
.to-sign-up {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.to-sign-up > view {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #287fff;
|
||||||
|
margin-left: 20rpx;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -32,10 +32,10 @@ const heatMapImageSrc = ref(""); // 存储热力图图片地址
|
|||||||
const startScoring = () => {
|
const startScoring = () => {
|
||||||
if (user.value.id)
|
if (user.value.id)
|
||||||
return uni.navigateTo({
|
return uni.navigateTo({
|
||||||
url: "/pages/create",
|
url: "/pages/create/index",
|
||||||
});
|
});
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: "/pages/signin",
|
url: "/pages/signin/index",
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ watch(
|
|||||||
const signin = () => {
|
const signin = () => {
|
||||||
if (!user.value.id) {
|
if (!user.value.id) {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: "/pages/signin",
|
url: "/pages/signin/index",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -94,21 +94,21 @@ onMounted(async () => {
|
|||||||
borderColor="#333"
|
borderColor="#333"
|
||||||
/>
|
/>
|
||||||
<text class="truncate">{{ user.nickName }}</text>
|
<text class="truncate">{{ user.nickName }}</text>
|
||||||
<image v-if="heat" :src="`../static/hot${heat}.png`" mode="widthFix" />
|
<image v-if="heat" :src="`/static/hot${heat}.png`" mode="widthFix" />
|
||||||
</block>
|
</block>
|
||||||
<block v-else>
|
<block v-else>
|
||||||
<image src="../static/user-icon.png" mode="widthFix" />
|
<image src="/static/user-icon.png" mode="widthFix" />
|
||||||
<text>Hello~</text>
|
<text>Hello~</text>
|
||||||
</block>
|
</block>
|
||||||
</view>
|
</view>
|
||||||
<view class="daily-signin">
|
<view class="daily-signin">
|
||||||
<view>
|
<view>
|
||||||
<image src="../static/week-check.png" />
|
<image src="/static/week-check.png" />
|
||||||
</view>
|
</view>
|
||||||
<view :class="data.weeksCheckIn[0] ? 'checked' : ''">
|
<view :class="data.weeksCheckIn[0] ? 'checked' : ''">
|
||||||
<image
|
<image
|
||||||
v-if="data.weeksCheckIn[0]"
|
v-if="data.weeksCheckIn[0]"
|
||||||
src="../static/checked-green2.png"
|
src="/static/checked-green2.png"
|
||||||
mode="widthFix"
|
mode="widthFix"
|
||||||
/>
|
/>
|
||||||
<view v-else></view>
|
<view v-else></view>
|
||||||
@@ -117,7 +117,7 @@ onMounted(async () => {
|
|||||||
<view :class="data.weeksCheckIn[1] ? 'checked' : ''">
|
<view :class="data.weeksCheckIn[1] ? 'checked' : ''">
|
||||||
<image
|
<image
|
||||||
v-if="data.weeksCheckIn[1]"
|
v-if="data.weeksCheckIn[1]"
|
||||||
src="../static/checked-green2.png"
|
src="/static/checked-green2.png"
|
||||||
mode="widthFix"
|
mode="widthFix"
|
||||||
/>
|
/>
|
||||||
<view v-else></view>
|
<view v-else></view>
|
||||||
@@ -126,7 +126,7 @@ onMounted(async () => {
|
|||||||
<view :class="data.weeksCheckIn[2] ? 'checked' : ''">
|
<view :class="data.weeksCheckIn[2] ? 'checked' : ''">
|
||||||
<image
|
<image
|
||||||
v-if="data.weeksCheckIn[2]"
|
v-if="data.weeksCheckIn[2]"
|
||||||
src="../static/checked-green2.png"
|
src="/static/checked-green2.png"
|
||||||
mode="widthFix"
|
mode="widthFix"
|
||||||
/>
|
/>
|
||||||
<view v-else></view>
|
<view v-else></view>
|
||||||
@@ -135,7 +135,7 @@ onMounted(async () => {
|
|||||||
<view :class="data.weeksCheckIn[3] ? 'checked' : ''">
|
<view :class="data.weeksCheckIn[3] ? 'checked' : ''">
|
||||||
<image
|
<image
|
||||||
v-if="data.weeksCheckIn[3]"
|
v-if="data.weeksCheckIn[3]"
|
||||||
src="../static/checked-green2.png"
|
src="/static/checked-green2.png"
|
||||||
mode="widthFix"
|
mode="widthFix"
|
||||||
/>
|
/>
|
||||||
<view v-else></view>
|
<view v-else></view>
|
||||||
@@ -144,7 +144,7 @@ onMounted(async () => {
|
|||||||
<view :class="data.weeksCheckIn[4] ? 'checked' : ''">
|
<view :class="data.weeksCheckIn[4] ? 'checked' : ''">
|
||||||
<image
|
<image
|
||||||
v-if="data.weeksCheckIn[4]"
|
v-if="data.weeksCheckIn[4]"
|
||||||
src="../static/checked-green2.png"
|
src="/static/checked-green2.png"
|
||||||
mode="widthFix"
|
mode="widthFix"
|
||||||
/>
|
/>
|
||||||
<view v-else></view>
|
<view v-else></view>
|
||||||
@@ -153,7 +153,7 @@ onMounted(async () => {
|
|||||||
<view :class="data.weeksCheckIn[5] ? 'checked' : ''">
|
<view :class="data.weeksCheckIn[5] ? 'checked' : ''">
|
||||||
<image
|
<image
|
||||||
v-if="data.weeksCheckIn[5]"
|
v-if="data.weeksCheckIn[5]"
|
||||||
src="../static/checked-green2.png"
|
src="/static/checked-green2.png"
|
||||||
mode="widthFix"
|
mode="widthFix"
|
||||||
/>
|
/>
|
||||||
<view v-else></view>
|
<view v-else></view>
|
||||||
@@ -162,7 +162,7 @@ onMounted(async () => {
|
|||||||
<view :class="data.weeksCheckIn[6] ? 'checked' : ''">
|
<view :class="data.weeksCheckIn[6] ? 'checked' : ''">
|
||||||
<image
|
<image
|
||||||
v-if="data.weeksCheckIn[6]"
|
v-if="data.weeksCheckIn[6]"
|
||||||
src="../static/checked-green2.png"
|
src="/static/checked-green2.png"
|
||||||
mode="widthFix"
|
mode="widthFix"
|
||||||
/>
|
/>
|
||||||
<view v-else></view>
|
<view v-else></view>
|
||||||
@@ -196,16 +196,16 @@ onMounted(async () => {
|
|||||||
</view>
|
</view>
|
||||||
<view>
|
<view>
|
||||||
<view @click="startScoring">
|
<view @click="startScoring">
|
||||||
<image src="../static/start-scoring.png" mode="widthFix" />
|
<image src="/static/start-scoring.png" mode="widthFix" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="title" :style="{ marginBottom: 0 }">
|
<view class="title" :style="{ marginBottom: 0 }">
|
||||||
<image src="../static/point-book-title1.png" mode="widthFix" />
|
<image src="/static/point-book-title1.png" mode="widthFix" />
|
||||||
</view>
|
</view>
|
||||||
<view class="heat-map">
|
<view class="heat-map">
|
||||||
<image
|
<image
|
||||||
:src="bowTargetSrc || '../static/bow-target.png'"
|
:src="bowTargetSrc || '/static/bow-target.png'"
|
||||||
mode="widthFix"
|
mode="widthFix"
|
||||||
/>
|
/>
|
||||||
<image v-if="heatMapImageSrc" :src="heatMapImageSrc" mode="aspectFill" />
|
<image v-if="heatMapImageSrc" :src="heatMapImageSrc" mode="aspectFill" />
|
||||||
@@ -234,7 +234,7 @@ onMounted(async () => {
|
|||||||
.score {
|
.score {
|
||||||
padding-left: 25rpx;
|
padding-left: 25rpx;
|
||||||
padding-right: 25rpx;
|
padding-right: 25rpx;
|
||||||
background: url("../static/app-bg5.png") no-repeat top/contain;
|
background: url("/static/app-bg5.png") no-repeat top/contain;
|
||||||
}
|
}
|
||||||
.statistics {
|
.statistics {
|
||||||
border-radius: 25rpx;
|
border-radius: 25rpx;
|
||||||
@@ -7,53 +7,51 @@ const checked = ref(false);
|
|||||||
|
|
||||||
const toSignUpPage = () => {
|
const toSignUpPage = () => {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: "/pages/signup",
|
url: "/pages/signup/index",
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const toResetPasswordPage = () => {
|
const toResetPasswordPage = () => {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: "/pages/reset-pwd",
|
url: "/pages/reset-pwd/index",
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const signIn = () => {
|
||||||
|
uni.navigateBack();
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="container">
|
<view class="container">
|
||||||
<image class="app-logo" src="../static/logo.png" mode="widthFix" />
|
<image class="app-logo" src="/static/logo.png" mode="widthFix" />
|
||||||
<text class="app-name">ARCX</text>
|
<text class="app-name">ARCX</text>
|
||||||
<InputRow type="text" placeholder="email" width="80vw" />
|
<InputRow type="text" placeholder="email" width="80vw" />
|
||||||
<InputRow type="password" placeholder="password" width="80vw" />
|
<InputRow type="password" placeholder="password" width="80vw" />
|
||||||
<view class="btn-row">
|
<view class="btn-row">
|
||||||
<button hover-class="none" @click="toResetPasswordPage">
|
<view @click="toResetPasswordPage"> Forgot Password? </view>
|
||||||
Forgot Password?
|
|
||||||
</button>
|
|
||||||
</view>
|
</view>
|
||||||
<SButton width="80vw">login</SButton>
|
<SButton width="80vw" :onClick="signIn">login</SButton>
|
||||||
<button
|
<view @click.stop="checked = !checked" class="agreement">
|
||||||
hover-class="none"
|
<image :src="`/static/${checked ? 'checked' : 'unchecked'}.png`" />
|
||||||
@click.stop="checked = !checked"
|
|
||||||
class="agreement"
|
|
||||||
>
|
|
||||||
<image :src="`../static/${checked ? 'checked' : 'unchecked'}.png`" />
|
|
||||||
<text>i read and accept</text>
|
<text>i read and accept</text>
|
||||||
<button hover-class="none" @click.stop="">user agreement</button>
|
<view @click.stop="">user agreement</view>
|
||||||
<text>and</text>
|
<text>and</text>
|
||||||
<button hover-class="none" @click.stop="">privacy policy</button>
|
<view @click.stop="">privacy policy</view>
|
||||||
</button>
|
</view>
|
||||||
<view class="thrid-signin">
|
<view class="thrid-signin">
|
||||||
<button hover-class="none">
|
<view>
|
||||||
<image src="../static/google-icon.png" mode="widthFix" />
|
<image src="/static/google-icon.png" mode="widthFix" />
|
||||||
<text>login with google</text>
|
<text>login with google</text>
|
||||||
</button>
|
</view>
|
||||||
<button hover-class="none">
|
<view>
|
||||||
<image src="../static/apple-icon.png" mode="widthFix" />
|
<image src="/static/apple-icon.png" mode="widthFix" />
|
||||||
<text>login with apple</text>
|
<text>login with apple</text>
|
||||||
</button>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="to-sign-up">
|
<view class="to-sign-up">
|
||||||
<text>don't have an account? </text>
|
<text>don't have an account? </text>
|
||||||
<button hover-class="none" @click.stop="toSignUpPage">sign up ></button>
|
<view @click.stop="toSignUpPage">sign up ></view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -71,6 +69,7 @@ const toResetPasswordPage = () => {
|
|||||||
width: 176rpx;
|
width: 176rpx;
|
||||||
height: 176rpx;
|
height: 176rpx;
|
||||||
margin-top: 40rpx;
|
margin-top: 40rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
}
|
}
|
||||||
.app-name {
|
.app-name {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -83,7 +82,7 @@ const toResetPasswordPage = () => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
.btn-row > button {
|
.btn-row > view {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: #287fff;
|
color: #287fff;
|
||||||
margin-bottom: 25rpx;
|
margin-bottom: 25rpx;
|
||||||
@@ -103,7 +102,7 @@ const toResetPasswordPage = () => {
|
|||||||
height: 32rpx;
|
height: 32rpx;
|
||||||
margin-right: 10rpx;
|
margin-right: 10rpx;
|
||||||
}
|
}
|
||||||
.agreement > button {
|
.agreement > view {
|
||||||
color: #333;
|
color: #333;
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
margin: 0 10rpx;
|
margin: 0 10rpx;
|
||||||
@@ -114,7 +113,7 @@ const toResetPasswordPage = () => {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin: 60rpx 0;
|
margin: 60rpx 0;
|
||||||
}
|
}
|
||||||
.thrid-signin > button {
|
.thrid-signin > view {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 88rpx;
|
height: 88rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -126,7 +125,7 @@ const toResetPasswordPage = () => {
|
|||||||
color: #333333;
|
color: #333333;
|
||||||
margin: 20rpx 0;
|
margin: 20rpx 0;
|
||||||
}
|
}
|
||||||
.thrid-signin > button > image {
|
.thrid-signin > view > image {
|
||||||
width: 40rpx;
|
width: 40rpx;
|
||||||
margin-right: 20rpx;
|
margin-right: 20rpx;
|
||||||
}
|
}
|
||||||
@@ -137,7 +136,7 @@ const toResetPasswordPage = () => {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.to-sign-up > button {
|
.to-sign-up > view {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: #287fff;
|
color: #287fff;
|
||||||
margin-left: 20rpx;
|
margin-left: 20rpx;
|
||||||
@@ -21,13 +21,13 @@ const toSignInPage = () => {
|
|||||||
<SButton width="80vw">login</SButton>
|
<SButton width="80vw">login</SButton>
|
||||||
<view class="agreement">
|
<view class="agreement">
|
||||||
<text>By clicking “Sign Up”, you agree to our</text>
|
<text>By clicking “Sign Up”, you agree to our</text>
|
||||||
<button hover-class="none" @click.stop="">user agreement</button>
|
<view hover-class="none" @click.stop="">user agreement</view>
|
||||||
<text>and</text>
|
<text>and</text>
|
||||||
<button hover-class="none" @click.stop="">privacy policy</button>
|
<view hover-class="none" @click.stop="">privacy policy</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="to-sign-up">
|
<view class="to-sign-up">
|
||||||
<text>have an account? </text>
|
<text>have an account? </text>
|
||||||
<button hover-class="none" @click.stop="toSignInPage">sign in ></button>
|
<view hover-class="none" @click.stop="toSignInPage">sign in ></view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -70,7 +70,7 @@ const toSignInPage = () => {
|
|||||||
height: 32rpx;
|
height: 32rpx;
|
||||||
margin-right: 10rpx;
|
margin-right: 10rpx;
|
||||||
}
|
}
|
||||||
.agreement > button {
|
.agreement > view {
|
||||||
color: #333;
|
color: #333;
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
margin: 0 10rpx;
|
margin: 0 10rpx;
|
||||||
@@ -83,7 +83,7 @@ const toSignInPage = () => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
margin-top: 100rpx;
|
margin-top: 100rpx;
|
||||||
}
|
}
|
||||||
.to-sign-up > button {
|
.to-sign-up > view {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: #287fff;
|
color: #287fff;
|
||||||
margin-left: 20rpx;
|
margin-left: 20rpx;
|
||||||
+1
-1
@@ -3,7 +3,7 @@ import { defineStore } from "pinia";
|
|||||||
const defaultUser = {
|
const defaultUser = {
|
||||||
id: "",
|
id: "",
|
||||||
nickName: "user",
|
nickName: "user",
|
||||||
avatar: "../static/user-icon.png",
|
avatar: "/static/user-icon.png",
|
||||||
};
|
};
|
||||||
|
|
||||||
// 定义游戏相关的 store
|
// 定义游戏相关的 store
|
||||||
|
|||||||
Reference in New Issue
Block a user