chore: upload app_client
46
.trae/documents/基于 Swagger 接口实现 Uni-App Vue3 微信登录页.md
Normal file
@ -0,0 +1,46 @@
|
||||
## 接口梳理(App 用户相关)
|
||||
- 登录与绑定:
|
||||
- `POST /api/app/users/weixin/login`,请求含 `code`、`invite_code`;响应含 `token`、`user_id`、`avatar`、`nickname`、`invite_code`(.trae/documents/基于 Swagger 的 App 用户 API 汇总与 Uni-App 微信登录页面实现方案.md:5-10)
|
||||
- `POST /api/app/users/{user_id}/phone/bind`,请求含微信手机号 `code`;响应为标准成功(.trae/documents/基于 Swagger 的 App 用户 API 汇总与 Uni-App 微信登录页面实现方案.md:11-16)
|
||||
- 用户资料与地址:
|
||||
- `PUT /api/app/users/{user_id}`(修改头像/昵称,`avatar`、`nickname`)(:19-22)
|
||||
- 地址列表/新增/删除/设默认(`:23-27`),新增请求含基本地址字段(`:25-27`)
|
||||
- 积分与统计:
|
||||
- `GET /api/app/users/{user_id}/points/balance` 响应 `balance`(`:31-34`)
|
||||
- `GET /api/app/users/{user_id}/stats` 响应 `coupon_count`、`item_card_count`、`points_balance`(`:35-37`)
|
||||
- 订单与卡券/道具:
|
||||
- 订单列表、优惠券、邀请、道具卡与使用记录(`:40-48`)
|
||||
|
||||
## 技术方案
|
||||
- 网络层:
|
||||
- 方案A(推荐)复用 `alova` 客户端与生成器,统一 `Authorization` 与错误处理(`:51`)
|
||||
- 方案B 以 `uni.request` 封装最小所需接口(登录/绑定/统计),在请求头注入 `Bearer` token(`:71-72`)
|
||||
- 平台与配置:
|
||||
- `baseURL` 指向后端 `http://127.0.0.1:9991`;在微信小程序后台配置合法域名与 HTTPS 证书(`:75`)
|
||||
- 状态与路由:
|
||||
- `pages.json` 添加 `pages/login/index`;登录成功后 `uni.reLaunch` 到首页;用 `pinia` 管理 `isLogin`、`userInfo`、`points`(`:79`)
|
||||
- 错误处理:
|
||||
- 按既有分类提示与重试策略,覆盖网络错误、超时、404/500、参数错误(`:83`)
|
||||
|
||||
## 页面实现(Uni-App Vue3)
|
||||
- 结构:Logo/说明、`「微信登录」`按钮、`open-type="getPhoneNumber"` 授权按钮、加载与错误提示(`:57-58`)
|
||||
- 流程:
|
||||
- 触发 `uni.login({ provider: 'weixin' })` 获取 `code` → 调用 `POST /api/app/users/weixin/login` → 持久化 `token`、`user_id`(`:61-62`)
|
||||
- 可选手机号绑定:`onGetPhoneNumber` 取 `code` → `POST /api/app/users/{user_id}/phone/bind`(`:63-64`)
|
||||
- 登录后拉取统计与积分余额更新首页(`:65-66`)
|
||||
|
||||
## 交付内容
|
||||
- 新增 `pages/login/index.vue`(Composition API)含完整登录/绑定流程与错误提示(`:91-94`)
|
||||
- 接入并配置网络层(复用 `alova` 或最小 `uni.request` 封装)
|
||||
- 路由与 `pinia` 状态的最小接入
|
||||
|
||||
## 验证方法
|
||||
- 在开发者工具/真机验证:`code` 获取、接口返回、`token/user_id` 存储、后续接口成功(`:87-88`)
|
||||
- 输出必要的调试日志(不含敏感信息),观察错误分支与重试入口
|
||||
|
||||
## 执行步骤
|
||||
1. 接入 Swagger JSON(`http://127.0.0.1:9991/swagger/v1/swagger.json`)同步生成或确认接口(`:99-101`)
|
||||
2. 选定网络层方案并落地调用
|
||||
3. 新增登录页面与交互逻辑
|
||||
4. 调整路由与状态管理
|
||||
5. 自测与联调,完成交付
|
||||
@ -0,0 +1,102 @@
|
||||
## API 文档汇总(App 用户相关)
|
||||
|
||||
* 登录与绑定:
|
||||
|
||||
* `POST /api/app/users/weixin/login`(miniapp/src/api/apis/apiDefinitions.js:106)
|
||||
|
||||
* 请求: `App_weixin_login_request`(code、invite\_code,可选)
|
||||
|
||||
* 响应: `App_weixin_login_response`(token、user\_id、avatar、nickname、invite\_code)(miniapp/src/api/apis/globals.d.ts:760)
|
||||
|
||||
* `POST /api/app/users/{user_id}/phone/bind`(apiDefinitions.js:123)
|
||||
|
||||
* 请求: `App_bind_phone_request`(code,来源于微信手机号授权)(globals.d.ts:376)
|
||||
|
||||
* 响应: 成功布尔或标准成功结构(项目统一在 `responded` 钩子返回 `response.data` 或 `response.data.data`,miniapp/src/api/apis/index.js:59)
|
||||
|
||||
* 用户资料与地址:
|
||||
|
||||
* `PUT /api/app/users/{user_id}` 修改头像/昵称(apiDefinitions.js:107)
|
||||
|
||||
* 请求: `App_modify_user_request`(avatar、nickname,可选)(globals.d.ts:363)
|
||||
|
||||
* `GET /api/app/users/{user_id}/addresses` 列表(apiDefinitions.js:108)/ `POST` 新增(apiDefinitions.js:109)/ `DELETE` 删除(apiDefinitions.js:110)/ `PUT .../default` 设默认(apiDefinitions.js:114)
|
||||
|
||||
* 新增请求: `App_add_address_request`(姓名、手机号、省市区、详细地址、是否默认)(globals.d.ts:367)
|
||||
|
||||
* 响应: 列表返回数组,新增/删除/设默认返回标准成功结构(项目统一 `responded` 处理)
|
||||
|
||||
* 积分与统计:
|
||||
|
||||
* `GET /api/app/users/{user_id}/points`(apiDefinitions.js:124)/ `GET .../points/balance`(apiDefinitions.js:125)
|
||||
|
||||
* 响应: `App_points_balance_response`(balance)(globals.d.ts:773)
|
||||
|
||||
* `GET /api/app/users/{user_id}/stats`(apiDefinitions.js:126)
|
||||
|
||||
* 响应: `App_user_stats_response`(coupon\_count、item\_card\_count、points\_balance)(globals.d.ts:776)
|
||||
|
||||
* 订单与卡券/道具:
|
||||
|
||||
* `GET /api/app/users/{user_id}/orders`(apiDefinitions.js:122)→ 订单列表(类型包含 `Model_order_items` 等)
|
||||
|
||||
* `GET /api/app/users/{user_id}/coupons`(apiDefinitions.js:118)/ `GET .../invites`(apiDefinitions.js:119)
|
||||
|
||||
* `GET /api/app/users/{user_id}/item_cards`(apiDefinitions.js:120)/ `GET .../item_cards/uses`(apiDefinitions.js:121)
|
||||
|
||||
* 响应: `User_item_card_with_template[]`(globals.d.ts:978)
|
||||
|
||||
## 现有代码要点(可复用)
|
||||
|
||||
* API 客户端:`alova` + 生成器(miniapp/src/api/apis/index.js:35、112;miniapp/alova.config.js:6),已封装 `Authorization`、401 刷新登录(index.js:9-33)。
|
||||
|
||||
* 登录页(Taro版):`miniapp/src/pages/login/index.vue`,逻辑封装在 `Apis.login.WechatAppLogin`(index.js:122-421)。
|
||||
|
||||
## 登录页面实现(Uni-App Vue3)
|
||||
|
||||
* 页面结构:Logo/说明文案、按钮`「微信登录」`与`open-type="getPhoneNumber"`的手机号授权按钮,加载与错误提示。
|
||||
|
||||
* 流程:
|
||||
|
||||
* `uni.login({ provider: 'weixin' })` 获取 `code` → 调用 `POST /api/app/users/weixin/login` → 存储 `token`、`user_id` 到 `uni.setStorageSync`。
|
||||
|
||||
* 可选:用户点击手机号授权后触发 `onGetPhoneNumber`,拿到 `code` 调用 `POST /api/app/users/{user_id}/phone/bind` 绑定手机号。
|
||||
|
||||
* 登录完成后拉取 `GET /api/app/users/{user_id}/stats` 与 `.../points/balance` 更新首页状态。
|
||||
|
||||
* 网络层:
|
||||
|
||||
* 方案A(推荐,复用现有):在 Uni-App 中引入与复用 `alova` 生成的 `Apis`(保持统一的拦截器与基址、响应处理)。
|
||||
|
||||
* 方案B(轻量):使用 `uni.request` 封装最小调用(登录/绑定/统计),按现有 `Authorization: Bearer <token>` 规则注入。
|
||||
|
||||
* 配置与安全:
|
||||
|
||||
* `baseURL` 指向后端地址(如 `http://127.0.0.1:9991`),并在微信小程序后台配置合法域名/证书;避免在日志中输出明文 token/手机号等敏感信息。
|
||||
|
||||
* 路由与状态:
|
||||
|
||||
* 在 `pages.json` 新增 `pages/login/index`,登录成功后 `uni.reLaunch` 到首页;使用 `pinia` 存储 `isLogin`、`userInfo`、`points` 等(参考 miniapp/src/store/index.js)。
|
||||
|
||||
* 错误处理:
|
||||
|
||||
* 按当前项目的分类提示(连接被拒绝、超时、域名未配置、SSL 错误、404、500、参数错误)进行用户级文案与重试入口(参考 index.js:221-253)。
|
||||
|
||||
## 验证与交付
|
||||
|
||||
* 验证:真机或开发者工具下,观察 `code` 获取、接口返回、`token/user_id` 存储与后续接口成功;埋点或日志控制台输出关键步骤。
|
||||
|
||||
* 交付:
|
||||
|
||||
* 新增 `pages/login/index.vue`(Uni-App Vue3 Composition API 实现)。
|
||||
|
||||
* 复用或新增 API 封装(A/B 二选一)。
|
||||
|
||||
* 配置/路由调整与最小 `pinia` 状态接入。
|
||||
|
||||
## 后续执行步骤
|
||||
|
||||
* 接入 Swagger 源:将生成器输入指向 `http://127.0.0.1:9991/swagger/v1/swagger.json`(或项目后端的 Swagger JSON),生成/更新 `Apis` 并对齐 `baseURL`。
|
||||
|
||||
* 按上述方案完成页面与调用接入,并保持与现有 `alova` 响应处理一致性。
|
||||
|
||||
35
.trae/documents/完善首页 UI:通知滚动与轮播图占位.md
Normal file
@ -0,0 +1,35 @@
|
||||
## 目标
|
||||
- 首页 UI 始终可见:轮播图在无数据时也显示占位滑块;通知始终滚动显示(Marquee)。
|
||||
- 接口路径统一为 `/api/app/*`,兼容返回 `{list: [...]}` 与字段 `snake_case`(如 `image_url`)。
|
||||
|
||||
## 变更范围
|
||||
- 文件:`pages/index/index.vue`
|
||||
- 保留现有登录弹窗,但不阻断首页数据加载;完善数据清洗与空态展示。
|
||||
|
||||
## 轮播图(无数据也展示)
|
||||
- 始终渲染 `swiper.banner-swiper`,不再用 `v-if` 隐藏容器。
|
||||
- 数据存在:按 `banners` 渲染;字段映射 `id`、`image_url|imageUrl|image`、`link_url|linkUrl|link|url`。
|
||||
- 数据为空:渲染 3 个占位滑块(`swiper-item` 内用 `<view>` 纯色/渐变背景 + 文案“敬请期待”),避免依赖静态图片资源。
|
||||
- URL 清洗:移除反引号与空格,保证 `image_url` 可用。
|
||||
|
||||
## 通知滚动(Marquee)
|
||||
- 始终渲染通知条。
|
||||
- 有数据:横向无缝滚动(CSS `@keyframes` + `transform: translateX`),将所有通知拼接为一条长文本,重复一份以实现循环滚动。
|
||||
- 无数据:使用默认文案(如“欢迎光临”“最新活动敬请期待”)参与滚动,保证始终有动效。
|
||||
|
||||
## 活动区
|
||||
- 保持当前两列栅格布局;无数据时显示“暂无活动”占位文案;点击仅在 `link` 为内部路径时跳转。
|
||||
|
||||
## 数据与接口
|
||||
- 请求入口统一:`/api/app/notices`、`/api/app/banners`、`/api/app/activities`。
|
||||
- 解包:支持 `list|items|data`。
|
||||
- 映射:通知 `content|text|title`;轮播图 `image_url|imageUrl|image|img|pic`、`link_url|linkUrl|link|url`;活动 `cover_url|coverUrl|image|img|pic`、`title|name`、`sub_title|subTitle|subtitle|desc|description`。
|
||||
|
||||
## 交互与空态
|
||||
- 未登录/未绑定:弹窗提醒,但首页照常加载并显示占位内容。
|
||||
- 点击跳转:内部路径以 `/` 开头才触发 `navigateTo`;避免空链接导致错误。
|
||||
|
||||
## 验证
|
||||
- 模拟后端返回 `{list:[...]}` 含反引号的 `image_url`,确认轮播图正常显示。
|
||||
- 清空 `banners` 与 `notices`,确认占位滑块与默认滚动文案显示。
|
||||
- 在微信小程序/浏览器预览,验证滚动流畅度与样式适配。
|
||||
17
App.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<script>
|
||||
export default {
|
||||
onLaunch: function() {
|
||||
console.log('App Launch')
|
||||
},
|
||||
onShow: function() {
|
||||
console.log('App Show')
|
||||
},
|
||||
onHide: function() {
|
||||
console.log('App Hide')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/*每个页面公共css */
|
||||
</style>
|
||||
60
api/appUser.js
Normal file
@ -0,0 +1,60 @@
|
||||
import { request, authRequest } from '../utils/request'
|
||||
|
||||
export function wechatLogin(code, invite_code) {
|
||||
const data = invite_code ? { code, invite_code } : { code }
|
||||
return request({ url: '/api/app/users/weixin/login', method: 'POST', data })
|
||||
}
|
||||
|
||||
export function bindPhone(user_id, code, extraHeader = {}) {
|
||||
return authRequest({ url: `/api/app/users/${user_id}/phone/bind`, method: 'POST', data: { code }, header: extraHeader })
|
||||
}
|
||||
|
||||
export function getUserStats(user_id) {
|
||||
return authRequest({ url: `/api/app/users/${user_id}/stats`, method: 'GET' })
|
||||
}
|
||||
|
||||
export function getPointsBalance(user_id) {
|
||||
return authRequest({ url: `/api/app/users/${user_id}/points/balance`, method: 'GET' })
|
||||
}
|
||||
|
||||
export function getPointsRecords(user_id, page = 1, page_size = 20) {
|
||||
return authRequest({ url: `/api/app/users/${user_id}/points`, method: 'GET', data: { page, page_size } })
|
||||
}
|
||||
|
||||
export function getOrders(user_id, status, page = 1, page_size = 20) {
|
||||
const data = { page, page_size }
|
||||
if (status) data.status = status
|
||||
return authRequest({ url: `/api/app/users/${user_id}/orders`, method: 'GET', data })
|
||||
}
|
||||
|
||||
export function listAddresses(user_id) {
|
||||
return authRequest({ url: `/api/app/users/${user_id}/addresses`, method: 'GET' })
|
||||
}
|
||||
|
||||
export function addAddress(user_id, payload) {
|
||||
return authRequest({ url: `/api/app/users/${user_id}/addresses`, method: 'POST', data: payload })
|
||||
}
|
||||
|
||||
export function updateAddress(user_id, address_id, payload) {
|
||||
return authRequest({ url: `/api/app/users/${user_id}/addresses/${address_id}`, method: 'PUT', data: payload })
|
||||
}
|
||||
|
||||
export function deleteAddress(user_id, address_id) {
|
||||
return authRequest({ url: `/api/app/users/${user_id}/addresses/${address_id}`, method: 'DELETE' })
|
||||
}
|
||||
|
||||
export function setDefaultAddress(user_id, address_id) {
|
||||
return authRequest({ url: `/api/app/users/${user_id}/addresses/${address_id}/default`, method: 'PUT' })
|
||||
}
|
||||
|
||||
export function getActivityDetail(activity_id) {
|
||||
return authRequest({ url: `/api/app/activities/${activity_id}`, method: 'GET' })
|
||||
}
|
||||
|
||||
export function getActivityIssues(activity_id) {
|
||||
return authRequest({ url: `/api/app/activities/${activity_id}/issues`, method: 'GET' })
|
||||
}
|
||||
|
||||
export function getActivityIssueRewards(activity_id, issue_id) {
|
||||
return authRequest({ url: `/api/app/activities/${activity_id}/issues/${issue_id}/rewards`, method: 'GET' })
|
||||
}
|
||||
146
console-1763271273297.log
Normal file
@ -0,0 +1,146 @@
|
||||
uni.api.esm.js:502 App Launch
|
||||
uni.api.esm.js:502 App Show
|
||||
VM75:363 Error: not node js file system!path:/saaa_config.json; go __invokeHandler__ readFile worker? false
|
||||
h @ VM75:363
|
||||
a.WeixinJSCore.invokeHandler @ VM75:363
|
||||
b @ WAServiceMainContext.js:1
|
||||
invoke @ WAServiceMainContext.js:1
|
||||
invoke @ WAServiceMainContext.js:1
|
||||
u @ WAServiceMainContext.js:1
|
||||
(anonymous) @ WAServiceMainContext.js:1
|
||||
ae @ WAServiceMainContext.js:1
|
||||
o @ WAServiceMainContext.js:1
|
||||
(anonymous) @ WAServiceMainContext.js:1
|
||||
QX @ WAServiceMainContext.js:1
|
||||
(anonymous) @ WAServiceMainContext.js:1
|
||||
c.emit @ WAServiceMainContext.js:1
|
||||
emitInternal @ WAServiceMainContext.js:1
|
||||
e @ WAServiceMainContext.js:1
|
||||
c.emit @ WAServiceMainContext.js:1
|
||||
emitInternal @ WAServiceMainContext.js:1
|
||||
p @ WAServiceMainContext.js:1
|
||||
_ @ WAServiceMainContext.js:1
|
||||
B @ WAServiceMainContext.js:1
|
||||
(anonymous) @ WAServiceMainContext.js:1
|
||||
a @ WAServiceMainContext.js:1
|
||||
s @ WAServiceMainContext.js:1
|
||||
(anonymous) @ WAServiceMainContext.js:1
|
||||
(anonymous) @ WAServiceMainContext.js:1
|
||||
(anonymous) @ WAServiceMainContext.js:1
|
||||
(anonymous) @ WAServiceMainContext.js:1
|
||||
c.emit @ WAServiceMainContext.js:1
|
||||
emit @ WAServiceMainContext.js:1
|
||||
(anonymous) @ WAServiceMainContext.js:1
|
||||
(anonymous) @ WAServiceMainContext.js:1
|
||||
(anonymous) @ WAServiceMainContext.js:1
|
||||
(anonymous) @ WAServiceMainContext.js:1
|
||||
_emit @ WAServiceMainContext.js:1
|
||||
emit @ WAServiceMainContext.js:1
|
||||
emit @ WAServiceMainContext.js:1
|
||||
subscribeHandler @ WAServiceMainContext.js:1
|
||||
ret.subscribeHandler
|
||||
Show 8 more frames
|
||||
uni.api.esm.js:502 mine onShow token: isLogin: false phoneBound: false
|
||||
uni.api.esm.js:502 mine login modal confirm: true
|
||||
WAServiceMainContext.js:1 [wxapplib]] backgroundfetch privacy fail {"errno":101,"errMsg":"private_getBackgroundFetchData:fail private_getBackgroundFetchData:fail:jsapi invalid request data"}
|
||||
(anonymous) @ WAServiceMainContext.js:1
|
||||
i @ WAServiceMainContext.js:1
|
||||
s @ WAServiceMainContext.js:1
|
||||
fail @ WAServiceMainContext.js:1
|
||||
p @ WAServiceMainContext.js:1
|
||||
(anonymous) @ WAServiceMainContext.js:1
|
||||
u @ WAServiceMainContext.js:1
|
||||
fail @ WAServiceMainContext.js:1
|
||||
p @ WAServiceMainContext.js:1
|
||||
(anonymous) @ WAServiceMainContext.js:1
|
||||
(anonymous) @ WAServiceMainContext.js:1
|
||||
G @ WAServiceMainContext.js:1
|
||||
(anonymous) @ WAServiceMainContext.js:1
|
||||
r @ WAServiceMainContext.js:1
|
||||
s @ WAServiceMainContext.js:1
|
||||
callAndRemove @ WAServiceMainContext.js:1
|
||||
invokeCallbackHandler @ WAServiceMainContext.js:1
|
||||
ret.invokeCallbackHandler
|
||||
uni.api.esm.js:502 login_flow start getPhoneNumber, codeExists: true
|
||||
uni.api.esm.js:502 login_flow uni.login success, loginCode exists: true
|
||||
uni.api.esm.js:502 HTTP request POST /api/app/users/weixin/login data {code: "0f1tQZFa1a5TFK0U6UGa11gIyJ0tQZF6"} headers {Accept: "application/json", content-type: "application/json", X-Requested-With: "XMLHttpRequest", Accept-Language: "zh_CN", X-App-Client: "uni-app", …}
|
||||
uni.api.esm.js:502 HTTP response POST /api/app/users/weixin/login status 200 body {user_id: 813, nickname: "善良的巴乔", avatar: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAA…BNCqAnHlGBRgAAQAA//8HSv0f1XCh/AAAAABJRU5ErkJggg==", invite_code: "AZCHW75Z", token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ODEzL…MzNH0.ZUItPpkDEbAhTy8g80PGRxLxQ_JXqS4jlu_vz7IrMAU"}avatar: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAQMAAAD58POIAAAABlBMVEUAAABmAADpPQ5dAAAAAXRSTlMAQObYZgAAAD9JREFUeJxiAIH//xHkSBdAEofwRrjAKEAFsMQCTzQjXgCRSMDsES4AoWAALj6iBRABNCqAnHlGBRgAAQAA//8HSv0f1XCh/AAAAABJRU5ErkJggg=="invite_code: "AZCHW75Z"nickname: "善良的巴乔"token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ODEzLCJ1c2VybmFtZSI6IuWWhOiJr-eahOW3tOS5lCIsIm5pY2tuYW1lIjoi5ZaE6Imv55qE5be05LmUIiwiaXNfc3VwZXIiOjAsInBsYXRmb3JtIjoiQVBQIiwiZXhwIjoxNzY1ODc3MzM0LCJuYmYiOjE3NjMyODUzMzQsImlhdCI6MTc2MzI4NTMzNH0.ZUItPpkDEbAhTy8g80PGRxLxQ_JXqS4jlu_vz7IrMAU"user_id: 813[[Prototype]]: Object
|
||||
uni.api.esm.js:502 login_flow wechatLogin response user_id: 813
|
||||
uni.api.esm.js:502 login_flow token stored
|
||||
uni.api.esm.js:502 login_flow user_id stored: 813
|
||||
uni.api.esm.js:502 login_flow bindPhone start
|
||||
uni.api.esm.js:502 HTTP request POST /api/app/users/813/phone/bind data {code: "c568695e007ec9d5dca1ecaf4d5452dd378fd42542d4a90fd9ad7b942b5d4248"} headers {Accept: "application/json", content-type: "application/json", X-Requested-With: "XMLHttpRequest", Accept-Language: "zh_CN", X-App-Client: "uni-app", …}
|
||||
uni.api.esm.js:502 HTTP response POST /api/app/users/813/phone/bind status 401 body {code: 10103, message: "您的账号登录过期,请重新登录。"}
|
||||
uni.api.esm.js:502 login_flow bindPhone 401, try re-login and retry
|
||||
__f__ @ uni.api.esm.js:502
|
||||
Ku.wu.__f__ @ mp.esm.js:523
|
||||
(anonymous) @ index.vue:59
|
||||
s @ app-service.js:1219
|
||||
(anonymous) @ app-service.js:1219
|
||||
(anonymous) @ app-service.js:1219
|
||||
asyncGeneratorStep @ app-service.js:1174
|
||||
i @ app-service.js:1174
|
||||
Promise.then (async)
|
||||
asyncGeneratorStep @ app-service.js:1174
|
||||
c @ app-service.js:1174
|
||||
Promise.then (async)
|
||||
asyncGeneratorStep @ app-service.js:1174
|
||||
c @ app-service.js:1174
|
||||
(anonymous) @ app-service.js:1174
|
||||
(anonymous) @ app-service.js:1174
|
||||
(anonymous) @ index.vue:95
|
||||
I.forEach.v.<computed> @ WAServiceMainContext.js:1
|
||||
p @ WAServiceMainContext.js:1
|
||||
(anonymous) @ WAServiceMainContext.js:1
|
||||
i @ WAServiceMainContext.js:1
|
||||
(anonymous) @ WAServiceMainContext.js:1
|
||||
G @ WAServiceMainContext.js:1
|
||||
(anonymous) @ WAServiceMainContext.js:1
|
||||
r @ WAServiceMainContext.js:1
|
||||
s @ WAServiceMainContext.js:1
|
||||
callAndRemove @ WAServiceMainContext.js:1
|
||||
invokeCallbackHandler @ WAServiceMainContext.js:1
|
||||
ret.invokeCallbackHandler
|
||||
uni.api.esm.js:502 HTTP request POST /api/app/users/weixin/login data {code: "0e15cAll2FVxFg4MxDnl2v9KUj15cAlO"} headers {Accept: "application/json", content-type: "application/json", X-Requested-With: "XMLHttpRequest", Accept-Language: "zh_CN", X-App-Client: "uni-app", …}
|
||||
uni.api.esm.js:502 HTTP response POST /api/app/users/weixin/login status 200 body {user_id: 813, nickname: "善良的巴乔", avatar: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAA…BNCqAnHlGBRgAAQAA//8HSv0f1XCh/AAAAABJRU5ErkJggg==", invite_code: "AZCHW75Z", token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ODEzL…MzNH0.ZUItPpkDEbAhTy8g80PGRxLxQ_JXqS4jlu_vz7IrMAU"}
|
||||
uni.api.esm.js:502 HTTP request POST /api/app/users/813/phone/bind data {code: "c568695e007ec9d5dca1ecaf4d5452dd378fd42542d4a90fd9ad7b942b5d4248"} headers {Accept: "application/json", content-type: "application/json", X-Requested-With: "XMLHttpRequest", Accept-Language: "zh_CN", X-App-Client: "uni-app", …}
|
||||
uni.api.esm.js:502 HTTP response POST /api/app/users/813/phone/bind status 401 body {code: 10103, message: "您的账号登录过期,请重新登录。"}
|
||||
uni.api.esm.js:502 login_flow error: 您的账号登录过期,请重新登录。 status: 401
|
||||
__f__ @ uni.api.esm.js:502
|
||||
Ku.wu.__f__ @ mp.esm.js:523
|
||||
(anonymous) @ index.vue:90
|
||||
s @ app-service.js:1219
|
||||
(anonymous) @ app-service.js:1219
|
||||
(anonymous) @ app-service.js:1219
|
||||
asyncGeneratorStep @ app-service.js:1174
|
||||
i @ app-service.js:1174
|
||||
Promise.then (async)
|
||||
asyncGeneratorStep @ app-service.js:1174
|
||||
c @ app-service.js:1174
|
||||
Promise.then (async)
|
||||
asyncGeneratorStep @ app-service.js:1174
|
||||
c @ app-service.js:1174
|
||||
Promise.then (async)
|
||||
asyncGeneratorStep @ app-service.js:1174
|
||||
i @ app-service.js:1174
|
||||
Promise.then (async)
|
||||
asyncGeneratorStep @ app-service.js:1174
|
||||
c @ app-service.js:1174
|
||||
Promise.then (async)
|
||||
asyncGeneratorStep @ app-service.js:1174
|
||||
c @ app-service.js:1174
|
||||
(anonymous) @ app-service.js:1174
|
||||
(anonymous) @ app-service.js:1174
|
||||
(anonymous) @ index.vue:95
|
||||
I.forEach.v.<computed> @ WAServiceMainContext.js:1
|
||||
p @ WAServiceMainContext.js:1
|
||||
(anonymous) @ WAServiceMainContext.js:1
|
||||
i @ WAServiceMainContext.js:1
|
||||
(anonymous) @ WAServiceMainContext.js:1
|
||||
G @ WAServiceMainContext.js:1
|
||||
(anonymous) @ WAServiceMainContext.js:1
|
||||
r @ WAServiceMainContext.js:1
|
||||
s @ WAServiceMainContext.js:1
|
||||
callAndRemove @ WAServiceMainContext.js:1
|
||||
invokeCallbackHandler @ WAServiceMainContext.js:1
|
||||
ret.invokeCallbackHandler
|
||||
20
index.html
Normal file
@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<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>
|
||||
<title></title>
|
||||
<!--preload-links-->
|
||||
<!--app-context-->
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"><!--app-html--></div>
|
||||
<script type="module" src="/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
22
main.js
Normal file
@ -0,0 +1,22 @@
|
||||
import App from './App'
|
||||
|
||||
// #ifndef VUE3
|
||||
import Vue from 'vue'
|
||||
import './uni.promisify.adaptor'
|
||||
Vue.config.productionTip = false
|
||||
App.mpType = 'app'
|
||||
const app = new Vue({
|
||||
...App
|
||||
})
|
||||
app.$mount()
|
||||
// #endif
|
||||
|
||||
// #ifdef VUE3
|
||||
import { createSSRApp } from 'vue'
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App)
|
||||
return {
|
||||
app
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
75
manifest.json
Normal file
@ -0,0 +1,75 @@
|
||||
{
|
||||
"name" : "app_client",
|
||||
"appid" : "",
|
||||
"description" : "",
|
||||
"versionName" : "1.0.0",
|
||||
"versionCode" : "100",
|
||||
"transformPx" : false,
|
||||
/* 5+App特有相关 */
|
||||
"app-plus" : {
|
||||
"usingComponents" : true,
|
||||
"nvueStyleCompiler" : "uni-app",
|
||||
"compilerVersion" : 3,
|
||||
"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.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.ACCESS_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||
"<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.FLASHLIGHT\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||
]
|
||||
},
|
||||
/* ios打包配置 */
|
||||
"ios" : {},
|
||||
/* SDK配置 */
|
||||
"sdkConfigs" : {}
|
||||
}
|
||||
},
|
||||
/* 快应用特有相关 */
|
||||
"quickapp" : {},
|
||||
/* 小程序特有相关 */
|
||||
"mp-weixin" : {
|
||||
"appid" : "wx26ad074017e1e63f",
|
||||
"setting" : {
|
||||
"urlCheck" : false,
|
||||
"minified" : true,
|
||||
"es6" : true,
|
||||
"postcss" : true
|
||||
},
|
||||
"usingComponents" : true
|
||||
},
|
||||
"mp-alipay" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"mp-baidu" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"mp-toutiao" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"uniStatistics" : {
|
||||
"enable" : false
|
||||
},
|
||||
"vueVersion" : "3"
|
||||
}
|
||||
121
pages.json
Normal file
@ -0,0 +1,121 @@
|
||||
{
|
||||
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
|
||||
{
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "uni-app"
|
||||
}
|
||||
}
|
||||
,
|
||||
{
|
||||
"path": "pages/login/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "登录"
|
||||
}
|
||||
}
|
||||
,
|
||||
{
|
||||
"path": "pages/shop/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商城"
|
||||
}
|
||||
}
|
||||
,
|
||||
{
|
||||
"path": "pages/cabinet/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "货柜"
|
||||
}
|
||||
}
|
||||
,
|
||||
{
|
||||
"path": "pages/mine/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的"
|
||||
}
|
||||
}
|
||||
,
|
||||
{
|
||||
"path": "pages/points/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "积分记录"
|
||||
}
|
||||
}
|
||||
,
|
||||
{
|
||||
"path": "pages/orders/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的订单"
|
||||
}
|
||||
}
|
||||
,
|
||||
{
|
||||
"path": "pages/address/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "地址管理"
|
||||
}
|
||||
}
|
||||
,
|
||||
{
|
||||
"path": "pages/address/edit",
|
||||
"style": {
|
||||
"navigationBarTitleText": "编辑地址"
|
||||
}
|
||||
}
|
||||
,
|
||||
{
|
||||
"path": "pages/help/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "使用帮助"
|
||||
}
|
||||
}
|
||||
,
|
||||
{
|
||||
"path": "pages/agreement/user",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用户协议"
|
||||
}
|
||||
}
|
||||
,
|
||||
{
|
||||
"path": "pages/agreement/purchase",
|
||||
"style": {
|
||||
"navigationBarTitleText": "购买协议"
|
||||
}
|
||||
}
|
||||
,
|
||||
{
|
||||
"path": "pages/activity/yifanshang/index",
|
||||
"style": { "navigationBarTitleText": "一番赏" }
|
||||
}
|
||||
,
|
||||
{
|
||||
"path": "pages/activity/wuxianshang/index",
|
||||
"style": { "navigationBarTitleText": "无限赏" }
|
||||
}
|
||||
,
|
||||
{
|
||||
"path": "pages/activity/duiduipeng/index",
|
||||
"style": { "navigationBarTitleText": "对对碰" }
|
||||
}
|
||||
],
|
||||
"tabBar": {
|
||||
"color": "#7A7E83",
|
||||
"selectedColor": "#007AFF",
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"borderStyle": "black",
|
||||
"list": [
|
||||
{ "pagePath": "pages/index/index", "text": "首页", "iconPath": "static/tab/home.png", "selectedIconPath": "static/tab/home_active.png" },
|
||||
{ "pagePath": "pages/shop/index", "text": "商城", "iconPath": "static/tab/shop.png", "selectedIconPath": "static/tab/shop_active.png" },
|
||||
{ "pagePath": "pages/cabinet/index", "text": "货柜", "iconPath": "static/tab/box.png", "selectedIconPath": "static/tab/box_active.png" },
|
||||
{ "pagePath": "pages/mine/index", "text": "我的", "iconPath": "static/tab/profile.png", "selectedIconPath": "static/tab/profile_active.png" }
|
||||
]
|
||||
},
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "uni-app",
|
||||
"navigationBarBackgroundColor": "#F8F8F8",
|
||||
"backgroundColor": "#F8F8F8"
|
||||
},
|
||||
"uniIdRouter": {}
|
||||
}
|
||||
65
pages/activity/duiduipeng.vue
Normal file
@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<scroll-view class="page" scroll-y>
|
||||
<view class="banner" v-if="detail.banner">
|
||||
<image class="banner-img" :src="detail.banner" mode="widthFix" />
|
||||
</view>
|
||||
<view class="header">
|
||||
<view class="title">{{ detail.name || detail.title || '-' }}</view>
|
||||
<view class="meta">分类:{{ detail.category_name || '对对碰' }}</view>
|
||||
<view class="meta" v-if="detail.price_draw !== undefined">参与价:¥{{ detail.price_draw }}</view>
|
||||
<view class="meta" v-if="detail.status !== undefined">状态:{{ statusText }}</view>
|
||||
</view>
|
||||
<view class="actions">
|
||||
<button class="btn" @click="onPreviewBanner">查看图片</button>
|
||||
<button class="btn primary" @click="onParticipate">立即参与</button>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { getActivityDetail } from '../../api/appUser'
|
||||
|
||||
const detail = ref({})
|
||||
const statusText = ref('')
|
||||
|
||||
function statusToText(s) {
|
||||
if (s === 1) return '进行中'
|
||||
if (s === 0) return '未开始'
|
||||
if (s === 2) return '已结束'
|
||||
return String(s || '')
|
||||
}
|
||||
|
||||
async function fetchDetail(id) {
|
||||
const data = await getActivityDetail(id)
|
||||
detail.value = data || {}
|
||||
statusText.value = statusToText(detail.value.status)
|
||||
}
|
||||
|
||||
function onPreviewBanner() {
|
||||
const url = detail.value.banner || ''
|
||||
if (url) uni.previewImage({ urls: [url], current: url })
|
||||
}
|
||||
|
||||
function onParticipate() {
|
||||
uni.showToast({ title: '功能待接入', icon: 'none' })
|
||||
}
|
||||
|
||||
onLoad((opts) => {
|
||||
const id = (opts && opts.id) || ''
|
||||
if (id) fetchDetail(id)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page { height: 100vh }
|
||||
.banner { padding: 24rpx }
|
||||
.banner-img { width: 100% }
|
||||
.header { padding: 0 24rpx }
|
||||
.title { font-size: 36rpx; font-weight: 700 }
|
||||
.meta { margin-top: 8rpx; font-size: 26rpx; color: #666 }
|
||||
.actions { display: flex; padding: 24rpx; gap: 16rpx }
|
||||
.btn { flex: 1 }
|
||||
.primary { background-color: #007AFF; color: #fff }
|
||||
</style>
|
||||
153
pages/activity/duiduipeng/index.vue
Normal file
@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<scroll-view class="page" scroll-y>
|
||||
<view class="banner" v-if="detail.banner">
|
||||
<image class="banner-img" :src="detail.banner" mode="widthFix" />
|
||||
</view>
|
||||
<view class="header">
|
||||
<view class="title">{{ detail.name || detail.title || '-' }}</view>
|
||||
<view class="meta">分类:{{ detail.category_name || '对对碰' }}</view>
|
||||
<view class="meta" v-if="detail.price_draw !== undefined">参与价:¥{{ detail.price_draw }}</view>
|
||||
<view class="meta" v-if="detail.status !== undefined">状态:{{ statusText }}</view>
|
||||
</view>
|
||||
<view class="actions">
|
||||
<button class="btn" @click="onPreviewBanner">查看图片</button>
|
||||
<button class="btn primary" @click="onParticipate">立即参与</button>
|
||||
</view>
|
||||
<view class="issues">
|
||||
<view class="issues-title">期数</view>
|
||||
<view v-if="issues.length" class="issues-list">
|
||||
<view class="issue-item" v-for="it in issues" :key="it.id">
|
||||
<text class="issue-title">{{ it.title || ('第' + (it.no || it.index || it.issue_no || '-') + '期') }}</text>
|
||||
<text class="issue-status" v-if="it.status_text">{{ it.status_text }}</text>
|
||||
<view class="rewards" v-if="rewardsMap[it.id] && rewardsMap[it.id].length">
|
||||
<view class="reward" v-for="rw in rewardsMap[it.id]" :key="rw.id">
|
||||
<image v-if="rw.image" class="reward-img" :src="rw.image" mode="aspectFill" />
|
||||
<view class="reward-texts">
|
||||
<text class="reward-title">{{ rw.title }}</text>
|
||||
<text class="reward-meta" v-if="rw.rarity || rw.odds">{{ [rw.rarity, rw.odds].filter(Boolean).join(' · ') }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rewards-empty" v-else>暂无奖励配置</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="issues-empty">暂无期数</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { getActivityDetail, getActivityIssues, getActivityIssueRewards } from '../../../api/appUser'
|
||||
|
||||
const detail = ref({})
|
||||
const statusText = ref('')
|
||||
const issues = ref([])
|
||||
const rewardsMap = ref({})
|
||||
|
||||
function statusToText(s) {
|
||||
if (s === 1) return '进行中'
|
||||
if (s === 0) return '未开始'
|
||||
if (s === 2) return '已结束'
|
||||
return String(s || '')
|
||||
}
|
||||
|
||||
async function fetchDetail(id) {
|
||||
const data = await getActivityDetail(id)
|
||||
detail.value = data || {}
|
||||
statusText.value = statusToText(detail.value.status)
|
||||
}
|
||||
|
||||
function unwrap(list) {
|
||||
if (Array.isArray(list)) return list
|
||||
const obj = list || {}
|
||||
const data = obj.data || {}
|
||||
const arr = obj.list || obj.items || data.list || data.items || data
|
||||
return Array.isArray(arr) ? arr : []
|
||||
}
|
||||
|
||||
function normalizeIssues(list) {
|
||||
const arr = unwrap(list)
|
||||
return arr.map((i, idx) => ({
|
||||
id: i.id ?? String(idx),
|
||||
title: i.title ?? i.name ?? '',
|
||||
no: i.no ?? i.index ?? i.issue_no ?? i.issue_number ?? null,
|
||||
status_text: i.status_text ?? (i.status === 1 ? '进行中' : i.status === 0 ? '未开始' : i.status === 2 ? '已结束' : '')
|
||||
}))
|
||||
}
|
||||
function normalizeRewards(list) {
|
||||
const arr = unwrap(list)
|
||||
return arr.map((i, idx) => ({
|
||||
id: i.id ?? String(idx),
|
||||
title: i.title ?? i.name ?? '',
|
||||
image: i.image ?? i.img ?? i.pic ?? i.banner ?? '',
|
||||
odds: i.odds ?? i.rate ?? i.probability ?? i.prob ?? '',
|
||||
rarity: i.rarity ?? i.rarity_name ?? ''
|
||||
}))
|
||||
}
|
||||
async function fetchRewardsForIssues(activityId) {
|
||||
const list = issues.value || []
|
||||
const promises = list.map(it => getActivityIssueRewards(activityId, it.id))
|
||||
const results = await Promise.allSettled(promises)
|
||||
results.forEach((res, i) => {
|
||||
const issueId = list[i] && list[i].id
|
||||
if (!issueId) return
|
||||
const value = res.status === 'fulfilled' ? normalizeRewards(res.value) : []
|
||||
rewardsMap.value = { ...(rewardsMap.value || {}), [issueId]: value }
|
||||
})
|
||||
}
|
||||
|
||||
async function fetchIssues(id) {
|
||||
const data = await getActivityIssues(id)
|
||||
issues.value = normalizeIssues(data)
|
||||
await fetchRewardsForIssues(id)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function onPreviewBanner() {
|
||||
const url = detail.value.banner || ''
|
||||
if (url) uni.previewImage({ urls: [url], current: url })
|
||||
}
|
||||
|
||||
function onParticipate() {
|
||||
uni.showToast({ title: '功能待接入', icon: 'none' })
|
||||
}
|
||||
|
||||
onLoad((opts) => {
|
||||
const id = (opts && opts.id) || ''
|
||||
if (id) {
|
||||
fetchDetail(id)
|
||||
fetchIssues(id)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page { height: 100vh }
|
||||
.banner { padding: 24rpx }
|
||||
.banner-img { width: 100% }
|
||||
.header { padding: 0 24rpx }
|
||||
.title { font-size: 36rpx; font-weight: 700 }
|
||||
.meta { margin-top: 8rpx; font-size: 26rpx; color: #666 }
|
||||
.actions { display: flex; padding: 24rpx; gap: 16rpx }
|
||||
.btn { flex: 1 }
|
||||
.primary { background-color: #007AFF; color: #fff }
|
||||
.issues { background: #fff; border-radius: 12rpx; margin: 0 24rpx 24rpx; padding: 16rpx }
|
||||
.issues-title { font-size: 30rpx; font-weight: 600; margin-bottom: 12rpx }
|
||||
.issues-list { }
|
||||
.issue-item { display: flex; justify-content: space-between; padding: 12rpx 0; border-bottom: 1rpx solid #f0f0f0 }
|
||||
.issue-item:last-child { border-bottom: 0 }
|
||||
.issue-title { font-size: 26rpx }
|
||||
.issue-status { font-size: 24rpx; color: #666 }
|
||||
.rewards { width: 100%; margin-top: 12rpx }
|
||||
.reward { display: flex; align-items: center; margin-bottom: 8rpx }
|
||||
.reward-img { width: 80rpx; height: 80rpx; border-radius: 8rpx; margin-right: 12rpx; background: #f5f5f5 }
|
||||
.reward-texts { display: flex; flex-direction: column }
|
||||
.reward-title { font-size: 26rpx }
|
||||
.reward-meta { font-size: 22rpx; color: #888 }
|
||||
.rewards-empty { font-size: 24rpx; color: #999 }
|
||||
.issues-empty { font-size: 24rpx; color: #999 }
|
||||
</style>
|
||||
65
pages/activity/wuxianshang.vue
Normal file
@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<scroll-view class="page" scroll-y>
|
||||
<view class="banner" v-if="detail.banner">
|
||||
<image class="banner-img" :src="detail.banner" mode="widthFix" />
|
||||
</view>
|
||||
<view class="header">
|
||||
<view class="title">{{ detail.name || detail.title || '-' }}</view>
|
||||
<view class="meta">分类:{{ detail.category_name || '无限赏' }}</view>
|
||||
<view class="meta" v-if="detail.price_draw !== undefined">单次抽选:¥{{ detail.price_draw }}</view>
|
||||
<view class="meta" v-if="detail.status !== undefined">状态:{{ statusText }}</view>
|
||||
</view>
|
||||
<view class="actions">
|
||||
<button class="btn" @click="onPreviewBanner">查看图片</button>
|
||||
<button class="btn primary" @click="onParticipate">立即参与</button>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { getActivityDetail } from '../../api/appUser'
|
||||
|
||||
const detail = ref({})
|
||||
const statusText = ref('')
|
||||
|
||||
function statusToText(s) {
|
||||
if (s === 1) return '进行中'
|
||||
if (s === 0) return '未开始'
|
||||
if (s === 2) return '已结束'
|
||||
return String(s || '')
|
||||
}
|
||||
|
||||
async function fetchDetail(id) {
|
||||
const data = await getActivityDetail(id)
|
||||
detail.value = data || {}
|
||||
statusText.value = statusToText(detail.value.status)
|
||||
}
|
||||
|
||||
function onPreviewBanner() {
|
||||
const url = detail.value.banner || ''
|
||||
if (url) uni.previewImage({ urls: [url], current: url })
|
||||
}
|
||||
|
||||
function onParticipate() {
|
||||
uni.showToast({ title: '功能待接入', icon: 'none' })
|
||||
}
|
||||
|
||||
onLoad((opts) => {
|
||||
const id = (opts && opts.id) || ''
|
||||
if (id) fetchDetail(id)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page { height: 100vh }
|
||||
.banner { padding: 24rpx }
|
||||
.banner-img { width: 100% }
|
||||
.header { padding: 0 24rpx }
|
||||
.title { font-size: 36rpx; font-weight: 700 }
|
||||
.meta { margin-top: 8rpx; font-size: 26rpx; color: #666 }
|
||||
.actions { display: flex; padding: 24rpx; gap: 16rpx }
|
||||
.btn { flex: 1 }
|
||||
.primary { background-color: #007AFF; color: #fff }
|
||||
</style>
|
||||
151
pages/activity/wuxianshang/index.vue
Normal file
@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<scroll-view class="page" scroll-y>
|
||||
<view class="banner" v-if="detail.banner">
|
||||
<image class="banner-img" :src="detail.banner" mode="widthFix" />
|
||||
</view>
|
||||
<view class="header">
|
||||
<view class="title">{{ detail.name || detail.title || '-' }}</view>
|
||||
<view class="meta">分类:{{ detail.category_name || '无限赏' }}</view>
|
||||
<view class="meta" v-if="detail.price_draw !== undefined">单次抽选:¥{{ detail.price_draw }}</view>
|
||||
<view class="meta" v-if="detail.status !== undefined">状态:{{ statusText }}</view>
|
||||
</view>
|
||||
<view class="actions">
|
||||
<button class="btn" @click="onPreviewBanner">查看图片</button>
|
||||
<button class="btn primary" @click="onParticipate">立即参与</button>
|
||||
</view>
|
||||
<view class="issues">
|
||||
<view class="issues-title">期数</view>
|
||||
<view v-if="issues.length" class="issues-list">
|
||||
<view class="issue-item" v-for="it in issues" :key="it.id">
|
||||
<text class="issue-title">{{ it.title || ('第' + (it.no || it.index || it.issue_no || '-') + '期') }}</text>
|
||||
<text class="issue-status" v-if="it.status_text">{{ it.status_text }}</text>
|
||||
<view class="rewards" v-if="rewardsMap[it.id] && rewardsMap[it.id].length">
|
||||
<view class="reward" v-for="rw in rewardsMap[it.id]" :key="rw.id">
|
||||
<image v-if="rw.image" class="reward-img" :src="rw.image" mode="aspectFill" />
|
||||
<view class="reward-texts">
|
||||
<text class="reward-title">{{ rw.title }}</text>
|
||||
<text class="reward-meta" v-if="rw.rarity || rw.odds">{{ [rw.rarity, rw.odds].filter(Boolean).join(' · ') }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rewards-empty" v-else>暂无奖励配置</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="issues-empty">暂无期数</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { getActivityDetail, getActivityIssues, getActivityIssueRewards } from '../../../api/appUser'
|
||||
|
||||
const detail = ref({})
|
||||
const statusText = ref('')
|
||||
const issues = ref([])
|
||||
const rewardsMap = ref({})
|
||||
|
||||
function statusToText(s) {
|
||||
if (s === 1) return '进行中'
|
||||
if (s === 0) return '未开始'
|
||||
if (s === 2) return '已结束'
|
||||
return String(s || '')
|
||||
}
|
||||
|
||||
async function fetchDetail(id) {
|
||||
const data = await getActivityDetail(id)
|
||||
detail.value = data || {}
|
||||
statusText.value = statusToText(detail.value.status)
|
||||
}
|
||||
|
||||
function unwrap(list) {
|
||||
if (Array.isArray(list)) return list
|
||||
const obj = list || {}
|
||||
const data = obj.data || {}
|
||||
const arr = obj.list || obj.items || data.list || data.items || data
|
||||
return Array.isArray(arr) ? arr : []
|
||||
}
|
||||
|
||||
function normalizeIssues(list) {
|
||||
const arr = unwrap(list)
|
||||
return arr.map((i, idx) => ({
|
||||
id: i.id ?? String(idx),
|
||||
title: i.title ?? i.name ?? '',
|
||||
no: i.no ?? i.index ?? i.issue_no ?? i.issue_number ?? null,
|
||||
status_text: i.status_text ?? (i.status === 1 ? '进行中' : i.status === 0 ? '未开始' : i.status === 2 ? '已结束' : '')
|
||||
}))
|
||||
}
|
||||
function normalizeRewards(list) {
|
||||
const arr = unwrap(list)
|
||||
return arr.map((i, idx) => ({
|
||||
id: i.id ?? String(idx),
|
||||
title: i.title ?? i.name ?? '',
|
||||
image: i.image ?? i.img ?? i.pic ?? i.banner ?? '',
|
||||
odds: i.odds ?? i.rate ?? i.probability ?? i.prob ?? '',
|
||||
rarity: i.rarity ?? i.rarity_name ?? ''
|
||||
}))
|
||||
}
|
||||
async function fetchRewardsForIssues(activityId) {
|
||||
const list = issues.value || []
|
||||
const promises = list.map(it => getActivityIssueRewards(activityId, it.id))
|
||||
const results = await Promise.allSettled(promises)
|
||||
results.forEach((res, i) => {
|
||||
const issueId = list[i] && list[i].id
|
||||
if (!issueId) return
|
||||
const value = res.status === 'fulfilled' ? normalizeRewards(res.value) : []
|
||||
rewardsMap.value = { ...(rewardsMap.value || {}), [issueId]: value }
|
||||
})
|
||||
}
|
||||
|
||||
async function fetchIssues(id) {
|
||||
const data = await getActivityIssues(id)
|
||||
issues.value = normalizeIssues(data)
|
||||
await fetchRewardsForIssues(id)
|
||||
}
|
||||
|
||||
|
||||
function onPreviewBanner() {
|
||||
const url = detail.value.banner || ''
|
||||
if (url) uni.previewImage({ urls: [url], current: url })
|
||||
}
|
||||
|
||||
function onParticipate() {
|
||||
uni.showToast({ title: '功能待接入', icon: 'none' })
|
||||
}
|
||||
|
||||
onLoad((opts) => {
|
||||
const id = (opts && opts.id) || ''
|
||||
if (id) {
|
||||
fetchDetail(id)
|
||||
fetchIssues(id)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page { height: 100vh }
|
||||
.banner { padding: 24rpx }
|
||||
.banner-img { width: 100% }
|
||||
.header { padding: 0 24rpx }
|
||||
.title { font-size: 36rpx; font-weight: 700 }
|
||||
.meta { margin-top: 8rpx; font-size: 26rpx; color: #666 }
|
||||
.actions { display: flex; padding: 24rpx; gap: 16rpx }
|
||||
.btn { flex: 1 }
|
||||
.primary { background-color: #007AFF; color: #fff }
|
||||
.issues { background: #fff; border-radius: 12rpx; margin: 0 24rpx 24rpx; padding: 16rpx }
|
||||
.issues-title { font-size: 30rpx; font-weight: 600; margin-bottom: 12rpx }
|
||||
.issues-list { }
|
||||
.issue-item { display: flex; justify-content: space-between; padding: 12rpx 0; border-bottom: 1rpx solid #f0f0f0 }
|
||||
.issue-item:last-child { border-bottom: 0 }
|
||||
.issue-title { font-size: 26rpx }
|
||||
.issue-status { font-size: 24rpx; color: #666 }
|
||||
.rewards { width: 100%; margin-top: 12rpx }
|
||||
.reward { display: flex; align-items: center; margin-bottom: 8rpx }
|
||||
.reward-img { width: 80rpx; height: 80rpx; border-radius: 8rpx; margin-right: 12rpx; background: #f5f5f5 }
|
||||
.reward-texts { display: flex; flex-direction: column }
|
||||
.reward-title { font-size: 26rpx }
|
||||
.reward-meta { font-size: 22rpx; color: #888 }
|
||||
.rewards-empty { font-size: 24rpx; color: #999 }
|
||||
.issues-empty { font-size: 24rpx; color: #999 }
|
||||
</style>
|
||||
66
pages/activity/yifanshang.vue
Normal file
@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<scroll-view class="page" scroll-y>
|
||||
<view class="banner" v-if="detail.banner">
|
||||
<image class="banner-img" :src="detail.banner" mode="widthFix" />
|
||||
</view>
|
||||
<view class="header">
|
||||
<view class="title">{{ detail.name || detail.title || '-' }}</view>
|
||||
<view class="meta">分类:{{ detail.category_name || '一番赏' }}</view>
|
||||
<view class="meta" v-if="detail.price_draw !== undefined">抽选价:¥{{ detail.price_draw }}</view>
|
||||
<view class="meta" v-if="detail.status !== undefined">状态:{{ statusText }}</view>
|
||||
</view>
|
||||
<view class="actions">
|
||||
<button class="btn" @click="onPreviewBanner">查看图片</button>
|
||||
<button class="btn primary" @click="onParticipate">立即参与</button>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { getActivityDetail } from '../../api/appUser'
|
||||
|
||||
const detail = ref({})
|
||||
|
||||
function statusToText(s) {
|
||||
if (s === 1) return '进行中'
|
||||
if (s === 0) return '未开始'
|
||||
if (s === 2) return '已结束'
|
||||
return String(s || '')
|
||||
}
|
||||
|
||||
const statusText = ref('')
|
||||
|
||||
async function fetchDetail(id) {
|
||||
const data = await getActivityDetail(id)
|
||||
detail.value = data || {}
|
||||
statusText.value = statusToText(detail.value.status)
|
||||
}
|
||||
|
||||
function onPreviewBanner() {
|
||||
const url = detail.value.banner || ''
|
||||
if (url) uni.previewImage({ urls: [url], current: url })
|
||||
}
|
||||
|
||||
function onParticipate() {
|
||||
uni.showToast({ title: '功能待接入', icon: 'none' })
|
||||
}
|
||||
|
||||
onLoad((opts) => {
|
||||
const id = (opts && opts.id) || ''
|
||||
if (id) fetchDetail(id)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page { height: 100vh }
|
||||
.banner { padding: 24rpx }
|
||||
.banner-img { width: 100% }
|
||||
.header { padding: 0 24rpx }
|
||||
.title { font-size: 36rpx; font-weight: 700 }
|
||||
.meta { margin-top: 8rpx; font-size: 26rpx; color: #666 }
|
||||
.actions { display: flex; padding: 24rpx; gap: 16rpx }
|
||||
.btn { flex: 1 }
|
||||
.primary { background-color: #007AFF; color: #fff }
|
||||
</style>
|
||||
151
pages/activity/yifanshang/index.vue
Normal file
@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<scroll-view class="page" scroll-y>
|
||||
<view class="banner" v-if="detail.banner">
|
||||
<image class="banner-img" :src="detail.banner" mode="widthFix" />
|
||||
</view>
|
||||
<view class="header">
|
||||
<view class="title">{{ detail.name || detail.title || '-' }}</view>
|
||||
<view class="meta">分类:{{ detail.category_name || '一番赏' }}</view>
|
||||
<view class="meta" v-if="detail.price_draw !== undefined">抽选价:¥{{ detail.price_draw }}</view>
|
||||
<view class="meta" v-if="detail.status !== undefined">状态:{{ statusText }}</view>
|
||||
</view>
|
||||
<view class="actions">
|
||||
<button class="btn" @click="onPreviewBanner">查看图片</button>
|
||||
<button class="btn primary" @click="onParticipate">立即参与</button>
|
||||
</view>
|
||||
<view class="issues">
|
||||
<view class="issues-title">期数</view>
|
||||
<view v-if="issues.length" class="issues-list">
|
||||
<view class="issue-item" v-for="it in issues" :key="it.id">
|
||||
<text class="issue-title">{{ it.title || ('第' + (it.no || it.index || it.issue_no || '-') + '期') }}</text>
|
||||
<text class="issue-status" v-if="it.status_text">{{ it.status_text }}</text>
|
||||
<view class="rewards" v-if="rewardsMap[it.id] && rewardsMap[it.id].length">
|
||||
<view class="reward" v-for="rw in rewardsMap[it.id]" :key="rw.id">
|
||||
<image v-if="rw.image" class="reward-img" :src="rw.image" mode="aspectFill" />
|
||||
<view class="reward-texts">
|
||||
<text class="reward-title">{{ rw.title }}</text>
|
||||
<text class="reward-meta" v-if="rw.rarity || rw.odds">{{ [rw.rarity, rw.odds].filter(Boolean).join(' · ') }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rewards-empty" v-else>暂无奖励配置</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="issues-empty">暂无期数</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { getActivityDetail, getActivityIssues, getActivityIssueRewards } from '../../../api/appUser'
|
||||
|
||||
const detail = ref({})
|
||||
const issues = ref([])
|
||||
const rewardsMap = ref({})
|
||||
|
||||
function statusToText(s) {
|
||||
if (s === 1) return '进行中'
|
||||
if (s === 0) return '未开始'
|
||||
if (s === 2) return '已结束'
|
||||
return String(s || '')
|
||||
}
|
||||
|
||||
const statusText = ref('')
|
||||
|
||||
async function fetchDetail(id) {
|
||||
const data = await getActivityDetail(id)
|
||||
detail.value = data || {}
|
||||
statusText.value = statusToText(detail.value.status)
|
||||
}
|
||||
|
||||
function unwrap(list) {
|
||||
if (Array.isArray(list)) return list
|
||||
const obj = list || {}
|
||||
const data = obj.data || {}
|
||||
const arr = obj.list || obj.items || data.list || data.items || data
|
||||
return Array.isArray(arr) ? arr : []
|
||||
}
|
||||
|
||||
function normalizeIssues(list) {
|
||||
const arr = unwrap(list)
|
||||
return arr.map((i, idx) => ({
|
||||
id: i.id ?? String(idx),
|
||||
title: i.title ?? i.name ?? '',
|
||||
no: i.no ?? i.index ?? i.issue_no ?? i.issue_number ?? null,
|
||||
status_text: i.status_text ?? (i.status === 1 ? '进行中' : i.status === 0 ? '未开始' : i.status === 2 ? '已结束' : '')
|
||||
}))
|
||||
}
|
||||
function normalizeRewards(list) {
|
||||
const arr = unwrap(list)
|
||||
return arr.map((i, idx) => ({
|
||||
id: i.id ?? String(idx),
|
||||
title: i.title ?? i.name ?? '',
|
||||
image: i.image ?? i.img ?? i.pic ?? i.banner ?? '',
|
||||
odds: i.odds ?? i.rate ?? i.probability ?? i.prob ?? '',
|
||||
rarity: i.rarity ?? i.rarity_name ?? ''
|
||||
}))
|
||||
}
|
||||
async function fetchRewardsForIssues(activityId) {
|
||||
const list = issues.value || []
|
||||
const promises = list.map(it => getActivityIssueRewards(activityId, it.id))
|
||||
const results = await Promise.allSettled(promises)
|
||||
results.forEach((res, i) => {
|
||||
const issueId = list[i] && list[i].id
|
||||
if (!issueId) return
|
||||
const value = res.status === 'fulfilled' ? normalizeRewards(res.value) : []
|
||||
rewardsMap.value = { ...(rewardsMap.value || {}), [issueId]: value }
|
||||
})
|
||||
}
|
||||
|
||||
async function fetchIssues(id) {
|
||||
const data = await getActivityIssues(id)
|
||||
issues.value = normalizeIssues(data)
|
||||
await fetchRewardsForIssues(id)
|
||||
}
|
||||
|
||||
function onPreviewBanner() {
|
||||
const url = detail.value.banner || ''
|
||||
if (url) uni.previewImage({ urls: [url], current: url })
|
||||
}
|
||||
|
||||
function onParticipate() {
|
||||
uni.showToast({ title: '功能待接入', icon: 'none' })
|
||||
}
|
||||
|
||||
onLoad((opts) => {
|
||||
const id = (opts && opts.id) || ''
|
||||
if (id) {
|
||||
fetchDetail(id)
|
||||
fetchIssues(id)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page { height: 100vh }
|
||||
.banner { padding: 24rpx }
|
||||
.banner-img { width: 100% }
|
||||
.header { padding: 0 24rpx }
|
||||
.title { font-size: 36rpx; font-weight: 700 }
|
||||
.meta { margin-top: 8rpx; font-size: 26rpx; color: #666 }
|
||||
.actions { display: flex; padding: 24rpx; gap: 16rpx }
|
||||
.btn { flex: 1 }
|
||||
.primary { background-color: #007AFF; color: #fff }
|
||||
.issues { background: #fff; border-radius: 12rpx; margin: 0 24rpx 24rpx; padding: 16rpx }
|
||||
.issues-title { font-size: 30rpx; font-weight: 600; margin-bottom: 12rpx }
|
||||
.issues-list { }
|
||||
.issue-item { display: flex; justify-content: space-between; padding: 12rpx 0; border-bottom: 1rpx solid #f0f0f0 }
|
||||
.issue-item:last-child { border-bottom: 0 }
|
||||
.issue-title { font-size: 26rpx }
|
||||
.issue-status { font-size: 24rpx; color: #666 }
|
||||
.rewards { width: 100%; margin-top: 12rpx }
|
||||
.reward { display: flex; align-items: center; margin-bottom: 8rpx }
|
||||
.reward-img { width: 80rpx; height: 80rpx; border-radius: 8rpx; margin-right: 12rpx; background: #f5f5f5 }
|
||||
.reward-texts { display: flex; flex-direction: column }
|
||||
.reward-title { font-size: 26rpx }
|
||||
.reward-meta { font-size: 22rpx; color: #888 }
|
||||
.rewards-empty { font-size: 24rpx; color: #999 }
|
||||
.issues-empty { font-size: 24rpx; color: #999 }
|
||||
</style>
|
||||
158
pages/address/edit.vue
Normal file
@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<view class="wrap">
|
||||
<view class="form-item">
|
||||
<text class="label">姓名</text>
|
||||
<input class="input" v-model="name" placeholder="请输入姓名" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">手机号</text>
|
||||
<input class="input" v-model="mobile" placeholder="请输入手机号" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">省份</text>
|
||||
<input class="input" v-model="province" placeholder="请输入省份" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">城市</text>
|
||||
<input class="input" v-model="city" placeholder="请输入城市" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">区县</text>
|
||||
<input class="input" v-model="district" placeholder="请输入区县" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">详细地址</text>
|
||||
<input class="input" v-model="detail" placeholder="请输入详细地址" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">设为默认</text>
|
||||
<switch :checked="isDefault" @change="e => isDefault = e.detail.value" />
|
||||
</view>
|
||||
<button class="submit" :disabled="loading" @click="onSubmit">保存</button>
|
||||
<view v-if="error" class="error">{{ error }}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { addAddress, updateAddress, listAddresses, setDefaultAddress } from '../../api/appUser'
|
||||
|
||||
const id = ref('')
|
||||
const name = ref('')
|
||||
const mobile = ref('')
|
||||
const province = ref('')
|
||||
const city = ref('')
|
||||
const district = ref('')
|
||||
const detail = ref('')
|
||||
let isDefault = false
|
||||
const loading = ref(false)
|
||||
const error = ref('')
|
||||
|
||||
function fill(data) {
|
||||
name.value = data.name || data.realname || ''
|
||||
mobile.value = data.mobile || data.phone || ''
|
||||
province.value = data.province || ''
|
||||
city.value = data.city || ''
|
||||
district.value = data.district || ''
|
||||
detail.value = data.address || data.detail || ''
|
||||
isDefault = !!data.is_default
|
||||
}
|
||||
|
||||
async function init(idParam) {
|
||||
if (!idParam) {
|
||||
const data = uni.getStorageSync('edit_address') || {}
|
||||
if (data && data.id) fill(data)
|
||||
return
|
||||
}
|
||||
const user_id = uni.getStorageSync('user_id')
|
||||
try {
|
||||
const list = await listAddresses(user_id)
|
||||
const arr = Array.isArray(list) ? list : (list && (list.list || list.items)) || []
|
||||
const found = arr.find(a => String(a.id) === String(idParam))
|
||||
if (found) fill(found)
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
const user_id = uni.getStorageSync('user_id')
|
||||
if (!name.value || !mobile.value || !province.value || !city.value || !district.value || !detail.value) {
|
||||
uni.showToast({ title: '请完善必填信息', icon: 'none' })
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
const payload = {
|
||||
name: name.value,
|
||||
mobile: mobile.value,
|
||||
province: province.value,
|
||||
city: city.value,
|
||||
district: district.value,
|
||||
address: detail.value,
|
||||
is_default: isDefault
|
||||
}
|
||||
try {
|
||||
let savedId = id.value
|
||||
if (id.value) {
|
||||
await updateAddress(user_id, id.value, payload)
|
||||
savedId = id.value
|
||||
} else {
|
||||
try {
|
||||
const res = await addAddress(user_id, payload)
|
||||
savedId = (res && (res.id || res.address_id)) || ''
|
||||
} catch (eAdd) {
|
||||
const sc = eAdd && eAdd.statusCode
|
||||
const bc = (eAdd && eAdd.data && (eAdd.data.code || eAdd.code)) || undefined
|
||||
const msg = eAdd && (eAdd.message || eAdd.errMsg || '')
|
||||
const isUniqueErr = sc === 400 && (bc === 10011 || (msg && msg.toLowerCase().includes('unique')))
|
||||
if (isUniqueErr) {
|
||||
try {
|
||||
const list = await listAddresses(user_id)
|
||||
const arr = Array.isArray(list) ? list : (list && (list.list || list.items)) || []
|
||||
const found = arr.find(a => (a.mobile === mobile.value || a.phone === mobile.value) && (a.address === detail.value || a.detail === detail.value) && (a.city === city.value) && (a.district === district.value) && (a.province === province.value))
|
||||
if (found) {
|
||||
savedId = found.id
|
||||
await updateAddress(user_id, savedId, payload)
|
||||
}
|
||||
} catch (_) {}
|
||||
} else {
|
||||
throw eAdd
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isDefault) {
|
||||
if (!savedId) {
|
||||
try {
|
||||
const list = await listAddresses(user_id)
|
||||
const arr = Array.isArray(list) ? list : (list && (list.list || list.items)) || []
|
||||
const found = arr.find(a => (a.mobile === mobile.value || a.phone === mobile.value) && (a.address === detail.value || a.detail === detail.value))
|
||||
if (found) savedId = found.id
|
||||
} catch (_) {}
|
||||
}
|
||||
if (savedId) {
|
||||
await setDefaultAddress(user_id, savedId)
|
||||
}
|
||||
}
|
||||
uni.showToast({ title: '保存成功', icon: 'success' })
|
||||
uni.navigateBack()
|
||||
} catch (e) {
|
||||
error.value = e && (e.message || e.errMsg) || '保存失败'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onLoad((opts) => {
|
||||
id.value = (opts && opts.id) || ''
|
||||
init(id.value)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.wrap { padding: 24rpx }
|
||||
.form-item { display: flex; align-items: center; background: #fff; border-radius: 12rpx; padding: 16rpx; margin-bottom: 12rpx }
|
||||
.label { width: 160rpx; font-size: 28rpx; color: #666 }
|
||||
.input { flex: 1; font-size: 28rpx }
|
||||
.submit { width: 100%; margin-top: 20rpx }
|
||||
.error { color: #e43; margin-top: 12rpx }
|
||||
</style>
|
||||
130
pages/address/index.vue
Normal file
@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<view class="wrap">
|
||||
<view class="header">
|
||||
<button class="add" @click="toAdd">新增地址</button>
|
||||
</view>
|
||||
<view v-if="error" class="error">{{ error }}</view>
|
||||
<view v-if="list.length === 0 && !loading" class="empty">暂无地址</view>
|
||||
<view v-for="item in list" :key="item.id" class="addr">
|
||||
<view class="addr-main">
|
||||
<view class="addr-row">
|
||||
<text class="name">姓名:{{ item.name || item.realname }}</text>
|
||||
</view>
|
||||
<view class="addr-row">
|
||||
<text class="phone">手机号:{{ item.phone || item.mobile }}</text>
|
||||
</view>
|
||||
<view class="addr-row" v-if="item.is_default">
|
||||
<text class="default">默认</text>
|
||||
</view>
|
||||
<view class="addr-row">
|
||||
<text class="region">省市区:{{ item.province }}{{ item.city }}{{ item.district }}</text>
|
||||
</view>
|
||||
<view class="addr-row">
|
||||
<text class="detail">详细地址:{{ item.address || item.detail }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="addr-actions">
|
||||
<button size="mini" @click="toEdit(item)">编辑</button>
|
||||
<button size="mini" type="warn" @click="onDelete(item)">删除</button>
|
||||
<button size="mini" :disabled="item.is_default" @click="onSetDefault(item)">设为默认</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { listAddresses, deleteAddress, setDefaultAddress } from '../../api/appUser'
|
||||
|
||||
const list = ref([])
|
||||
const loading = ref(false)
|
||||
const error = ref('')
|
||||
|
||||
async function fetchList() {
|
||||
const user_id = uni.getStorageSync('user_id')
|
||||
const token = uni.getStorageSync('token')
|
||||
const phoneBound = !!uni.getStorageSync('phone_bound')
|
||||
if (!user_id || !token || !phoneBound) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '请先登录并绑定手机号',
|
||||
confirmText: '去登录',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.navigateTo({ url: '/pages/login/index' })
|
||||
}
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
try {
|
||||
const data = await listAddresses(user_id)
|
||||
list.value = Array.isArray(data) ? data : (data && (data.list || data.items)) || []
|
||||
} catch (e) {
|
||||
error.value = e && (e.message || e.errMsg) || '获取地址失败'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function toAdd() {
|
||||
uni.removeStorageSync('edit_address')
|
||||
uni.navigateTo({ url: '/pages/address/edit' })
|
||||
}
|
||||
|
||||
function toEdit(item) {
|
||||
uni.setStorageSync('edit_address', item)
|
||||
uni.navigateTo({ url: `/pages/address/edit?id=${item.id}` })
|
||||
}
|
||||
|
||||
function onDelete(item) {
|
||||
const user_id = uni.getStorageSync('user_id')
|
||||
uni.showModal({
|
||||
title: '确认删除',
|
||||
content: '确定删除该地址吗?',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
await deleteAddress(user_id, item.id)
|
||||
fetchList()
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '删除失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function onSetDefault(item) {
|
||||
try {
|
||||
const user_id = uni.getStorageSync('user_id')
|
||||
await setDefaultAddress(user_id, item.id)
|
||||
fetchList()
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '设置失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
fetchList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.wrap { padding: 24rpx }
|
||||
.header { display: flex; justify-content: flex-end; margin-bottom: 12rpx }
|
||||
.add { font-size: 28rpx }
|
||||
.addr { background: #fff; border-radius: 12rpx; padding: 20rpx; margin-bottom: 16rpx; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04) }
|
||||
.addr-row { display: flex; align-items: center; margin-bottom: 8rpx }
|
||||
.name { font-size: 30rpx; margin-right: 12rpx }
|
||||
.phone { font-size: 28rpx; color: #666 }
|
||||
.default { font-size: 24rpx; color: #007AFF; margin-left: 10rpx }
|
||||
.region { font-size: 26rpx; color: #666 }
|
||||
.detail { font-size: 26rpx; color: #333 }
|
||||
.addr-actions { display: flex; justify-content: flex-end; gap: 12rpx; margin-top: 12rpx }
|
||||
.empty { text-align: center; color: #999; margin-top: 40rpx }
|
||||
.error { color: #e43; margin-bottom: 12rpx }
|
||||
</style>
|
||||
69
pages/agreement/purchase.vue
Normal file
@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<scroll-view class="page" scroll-y>
|
||||
<view class="h1">购买协议</view>
|
||||
<view class="meta">生效日期:2025年11月18日</view>
|
||||
<view class="meta">运营方:【公司全称】</view>
|
||||
<view class="p">本《购买协议》适用于您在【您的小程序名称】(以下简称“本平台”)购买盲盒商品的行为。当您点击“立即购买”并完成支付时,即视为您已阅读、理解并同意本协议全部内容。</view>
|
||||
<view class="h2">一、商品说明</view>
|
||||
<view class="ol">
|
||||
<view class="li">盲盒特性:本平台所售盲盒为系列化商品,包装外观一致,内部款式随机,具体款式无法提前指定或预知。</view>
|
||||
<view class="li">概率公示:各款式(含隐藏款、特殊款)的抽取概率已在商品详情页明确公示,确保透明公正。</view>
|
||||
<view class="li">商品展示:页面图片、描述仅供参考,实际商品以实物为准。</view>
|
||||
</view>
|
||||
<view class="h2">二、购买规则</view>
|
||||
<view class="ol">
|
||||
<view class="li">购买资格:仅限已注册并通过身份验证的用户购买。</view>
|
||||
<view class="li">支付方式:支持微信支付等平台认可的支付方式,支付成功即视为订单成立。</view>
|
||||
</view>
|
||||
<view class="h2">三、发货与物流</view>
|
||||
<view class="ol">
|
||||
<view class="li">发货时效:发货订单提交成功后,本平台将在3-15个工作日内安排发货(预售商品按页面说明执行)。</view>
|
||||
<view class="li">物流信息:您可在“我的订单”中查看物流状态。因地址错误、联系不畅导致的配送失败,责任由您承担。</view>
|
||||
</view>
|
||||
<view class="h2">四、售后服务</view>
|
||||
<view class="ol">
|
||||
<view class="li">质量问题(如商品破损、漏发、错发、非盲盒系列商品):请在签收后2到4小时内联系客服并提供凭证(如开箱视频、照片);经核实后,平台将为您补发、换货或退款。</view>
|
||||
<view class="li">非质量问题(如抽中重复款式、不喜欢款式、未抽中隐藏款等):不支持无理由退换货。</view>
|
||||
<view class="li">拆封后商品:出于卫生与二次销售考虑,已拆封盲盒恕不退换(质量问题除外)。</view>
|
||||
</view>
|
||||
<view class="h2">五、价格与促销</view>
|
||||
<view class="ol">
|
||||
<view class="li">商品价格以页面实时显示为准,平台有权根据市场情况调整价格。</view>
|
||||
<view class="li">优惠券、折扣活动需遵守对应规则,不可叠加或兑现。</view>
|
||||
</view>
|
||||
<view class="h2">六、特别提示</view>
|
||||
<view class="ol">
|
||||
<view class="li">盲盒不具备投资属性,本平台不承诺保值、升值或回购。</view>
|
||||
<view class="li">隐藏款抽取属小概率事件,请勿抱有赌博心态,理性消费。</view>
|
||||
</view>
|
||||
<view class="h2">七、免责情形</view>
|
||||
<view class="ul">
|
||||
<view class="li">因不可抗力导致无法发货或延迟;</view>
|
||||
<view class="li">用户提供错误收货信息;</view>
|
||||
<view class="li">因第三方物流原因造成的商品损毁或丢失(平台将协助索赔)。</view>
|
||||
</view>
|
||||
<view class="h2">八、协议效力</view>
|
||||
<view class="p">本购买协议为《用户协议》的补充,两者冲突时,以本协议中关于交易的条款为准。未尽事宜,依照《消费者权益保护法》《电子商务法》等法律法规执行。</view>
|
||||
<view class="h2">九、联系我们</view>
|
||||
<view class="p">售后专线:service@yourdomain.com</view>
|
||||
<view class="p">工作时间:工作日 9:00–18:00</view>
|
||||
<view class="p">运营主体:【公司全称】</view>
|
||||
<view class="p">统一社会信用代码:【XXXXXXXXXXXXXX】</view>
|
||||
<view class="tip">理性消费提醒:盲盒是一种娱乐消费形式,请根据自身经济能力合理购买,切勿沉迷或过度投入。</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page { height: 100vh }
|
||||
.h1 { font-size: 36rpx; font-weight: 700; padding: 24rpx }
|
||||
.meta { padding: 0 24rpx; font-size: 24rpx; color: #666 }
|
||||
.h2 { font-size: 30rpx; font-weight: 600; padding: 20rpx 24rpx 8rpx }
|
||||
.p { padding: 0 24rpx; font-size: 26rpx; line-height: 1.6 }
|
||||
.ul, .ol { padding: 0 24rpx }
|
||||
.ul .li, .ol .li { font-size: 26rpx; line-height: 1.6; margin: 8rpx 0 }
|
||||
.tip { padding: 16rpx 24rpx; font-size: 24rpx; color: #999 }
|
||||
</style>
|
||||
68
pages/agreement/user.vue
Normal file
@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<scroll-view class="page" scroll-y>
|
||||
<view class="h1">用户协议</view>
|
||||
<view class="meta">生效日期:2025年11月18日</view>
|
||||
<view class="meta">运营方:【公司全称】</view>
|
||||
<view class="p">欢迎您使用【您的小程序名称】(以下简称“本平台”)提供的服务。请您在注册、登录或使用本平台前,认真阅读并充分理解本《用户协议》(以下简称“本协议”)。一旦您完成注册、登录或以任何方式使用本平台服务,即视为您已完全接受本协议全部条款。如您不同意,请勿使用本平台。</view>
|
||||
<view class="h2">一、协议范围</view>
|
||||
<view class="p">本协议规范您作为用户在本平台注册、浏览、互动、参与活动等行为,是您与本平台之间的基本权利义务约定。</view>
|
||||
<view class="h2">二、用户资格</view>
|
||||
<view class="ol">
|
||||
<view class="li">您须为年满18周岁的自然人,具备完全民事行为能力。</view>
|
||||
<view class="li">未成年人如需使用本平台,须在法定监护人知情并同意下进行,监护人应承担相应责任。</view>
|
||||
</view>
|
||||
<view class="h2">三、账号与安全</view>
|
||||
<view class="ol">
|
||||
<view class="li">您应提供真实、准确、完整的注册信息,并对账号下的一切行为负责。</view>
|
||||
<view class="li">请妥善保管账号密码,因泄露、出借或被盗用造成的损失由您自行承担。</view>
|
||||
</view>
|
||||
<view class="h2">四、用户行为规范</view>
|
||||
<view class="p">您承诺不从事以下行为:</view>
|
||||
<view class="ul">
|
||||
<view class="li">发布违法、侵权、色情、暴力或虚假信息;</view>
|
||||
<view class="li">利用技术手段干扰系统正常运行(如刷量、外挂、爬虫);</view>
|
||||
<view class="li">恶意投诉、敲诈勒索或损害平台商誉;</view>
|
||||
<view class="li">转售账号或用于商业牟利(除非平台明确授权)。</view>
|
||||
</view>
|
||||
<view class="h2">五、隐私保护</view>
|
||||
<view class="p">我们尊重并保护您的个人信息。关于数据收集、使用及保护的具体规则,请参见《隐私政策》(链接)。未经您同意,我们不会向第三方共享您的个人信息,法律法规另有规定除外。</view>
|
||||
<view class="h2">六、知识产权</view>
|
||||
<view class="ol">
|
||||
<view class="li">本平台所有内容(包括但不限于界面设计、文字、图片、盲盒形象、LOGO)的知识产权归本平台或其许可方所有。</view>
|
||||
<view class="li">未经书面授权,您不得复制、传播、修改或用于商业用途。</view>
|
||||
</view>
|
||||
<view class="h2">七、免责条款</view>
|
||||
<view class="ol">
|
||||
<view class="li">因不可抗力(如地震、网络攻击、政府行为)导致服务中断,本平台不承担责任。</view>
|
||||
<view class="li">您因违反本协议造成自身或第三方损失的,由您自行承担。</view>
|
||||
</view>
|
||||
<view class="h2">八、协议变更与终止</view>
|
||||
<view class="ol">
|
||||
<view class="li">本平台有权根据业务或法律变化修订本协议,修订后将通过公告或推送通知您。继续使用即视为接受新条款。</view>
|
||||
<view class="li">您可随时注销账号终止使用;平台有权对违规账号采取限制或封禁措施。</view>
|
||||
</view>
|
||||
<view class="h2">九、法律适用与争议解决</view>
|
||||
<view class="p">本协议适用中华人民共和国法律。因本协议引起的争议,双方应协商解决;协商不成的,提交本平台运营方所在地有管辖权的人民法院诉讼解决。</view>
|
||||
<view class="h2">十、联系我们</view>
|
||||
<view class="p">客服邮箱:service@yourdomain.com</view>
|
||||
<view class="p">客服电话:400-XXX-XXXX(工作日 9:00–18:00)</view>
|
||||
<view class="p">运营主体:【公司全称】</view>
|
||||
<view class="p">地址:【公司注册地址】</view>
|
||||
<view class="tip">温馨提示:盲盒具有随机性和娱乐性,请理性参与,避免沉迷。未成年人禁止参与购买。</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page { height: 100vh }
|
||||
.h1 { font-size: 36rpx; font-weight: 700; padding: 24rpx }
|
||||
.meta { padding: 0 24rpx; font-size: 24rpx; color: #666 }
|
||||
.h2 { font-size: 30rpx; font-weight: 600; padding: 20rpx 24rpx 8rpx }
|
||||
.p { padding: 0 24rpx; font-size: 26rpx; line-height: 1.6 }
|
||||
.ul, .ol { padding: 0 24rpx }
|
||||
.ul .li, .ol .li { font-size: 26rpx; line-height: 1.6; margin: 8rpx 0 }
|
||||
.tip { padding: 16rpx 24rpx; font-size: 24rpx; color: #999 }
|
||||
</style>
|
||||
28
pages/cabinet/index.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<view class="wrap">货柜</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
onShow(() => {
|
||||
const token = uni.getStorageSync('token')
|
||||
const phoneBound = !!uni.getStorageSync('phone_bound')
|
||||
console.log('cabinet onShow token:', token, 'isLogin:', !!token, 'phoneBound:', phoneBound)
|
||||
if (!token || !phoneBound) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '请先登录并绑定手机号',
|
||||
confirmText: '去登录',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.navigateTo({ url: '/pages/login/index' })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.wrap { padding: 40rpx }
|
||||
</style>
|
||||
28
pages/help/index.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<view class="wrap">
|
||||
<view class="card" @click="toUser">
|
||||
<view class="title">用户协议</view>
|
||||
<view class="desc">查看平台使用条款与隐私说明</view>
|
||||
</view>
|
||||
<view class="card" @click="toPurchase">
|
||||
<view class="title">购买协议</view>
|
||||
<view class="desc">查看盲盒购买规则与售后政策</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
toUser() { uni.navigateTo({ url: '/pages/agreement/user' }) },
|
||||
toPurchase() { uni.navigateTo({ url: '/pages/agreement/purchase' }) }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.wrap { padding: 24rpx }
|
||||
.card { background: #fff; border-radius: 12rpx; padding: 24rpx; margin-bottom: 16rpx; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04) }
|
||||
.title { font-size: 30rpx; font-weight: 600 }
|
||||
.desc { margin-top: 8rpx; font-size: 24rpx; color: #666 }
|
||||
</style>
|
||||
224
pages/index/index.vue
Normal file
@ -0,0 +1,224 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="notice-bar">
|
||||
<swiper class="notice-swiper" vertical circular autoplay interval="3000" duration="300">
|
||||
<swiper-item v-for="n in displayNotices" :key="n.id">
|
||||
<view class="notice-item">{{ n.text }}</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
|
||||
<view class="banner-box">
|
||||
<swiper class="banner-swiper" circular autoplay interval="4000" duration="500">
|
||||
<swiper-item v-for="b in displayBanners" :key="b.id">
|
||||
<image v-if="b.image" class="banner-image" :src="b.image" mode="aspectFill" @tap="onBannerTap(b)" />
|
||||
<view v-else class="banner-fallback">
|
||||
<text class="banner-fallback-text">{{ b.title || '敬请期待' }}</text>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
|
||||
<view class="activity-section">
|
||||
<view class="section-title">活动</view>
|
||||
<view v-if="activities.length" class="activity-grid">
|
||||
<view class="activity-item" v-for="a in activities" :key="a.id" @tap="onActivityTap(a)">
|
||||
<image v-if="a.image" class="activity-thumb" :src="a.image" mode="aspectFill" />
|
||||
<view v-else class="banner-fallback">
|
||||
<text class="banner-fallback-text">{{ a.title || '活动敬请期待' }}</text>
|
||||
</view>
|
||||
<text class="activity-name">{{ a.title }}</text>
|
||||
<text class="activity-desc" v-if="a.subtitle">{{ a.subtitle }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="activity-empty">暂无活动</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { authRequest, request } from '../../utils/request.js'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
notices: [],
|
||||
banners: [],
|
||||
activities: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
displayNotices() {
|
||||
if (Array.isArray(this.notices) && this.notices.length) return this.notices
|
||||
return [
|
||||
{ id: 'n1', text: '欢迎光临' },
|
||||
{ id: 'n2', text: '最新活动敬请期待' }
|
||||
]
|
||||
},
|
||||
displayBanners() {
|
||||
if (Array.isArray(this.banners) && this.banners.length) return this.banners
|
||||
return [
|
||||
{ id: 'ph-1', title: '精彩内容即将上线', image: '' },
|
||||
{ id: 'ph-2', title: '敬请期待', image: '' },
|
||||
{ id: 'ph-3', title: '更多活动请关注', image: '' }
|
||||
]
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
const token = uni.getStorageSync('token')
|
||||
const phoneBound = !!uni.getStorageSync('phone_bound')
|
||||
if (!token || !phoneBound) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '请先登录并绑定手机号',
|
||||
confirmText: '去登录',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.navigateTo({ url: '/pages/login/index' })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
try { console.log('home onShow', { token: !!token, phoneBound }) } catch (_) {}
|
||||
this.loadHomeData()
|
||||
},
|
||||
methods: {
|
||||
toArray(x) { return Array.isArray(x) ? x : [] },
|
||||
unwrap(list) {
|
||||
if (Array.isArray(list)) return list
|
||||
const obj = list || {}
|
||||
const arr = obj.list || obj.items || obj.data || []
|
||||
return Array.isArray(arr) ? arr : []
|
||||
},
|
||||
cleanUrl(u) {
|
||||
const s = String(u || '').trim()
|
||||
const m = s.match(/https?:\/\/[^\s'"`]+/)
|
||||
if (m && m[0]) return m[0]
|
||||
return s.replace(/[`'\"]/g, '').trim()
|
||||
},
|
||||
apiGet(url) {
|
||||
const token = uni.getStorageSync('token')
|
||||
const fn = token ? authRequest : request
|
||||
return fn({ url })
|
||||
},
|
||||
normalizeNotices(list) {
|
||||
const arr = this.unwrap(list)
|
||||
return arr.map((i, idx) => ({
|
||||
id: i.id ?? String(idx),
|
||||
text: i.content ?? i.text ?? i.title ?? ''
|
||||
})).filter(i => i.text)
|
||||
},
|
||||
normalizeBanners(list) {
|
||||
const arr = this.unwrap(list)
|
||||
console.log('normalizeBanners input', list, 'unwrapped', arr)
|
||||
const mapped = arr.map((i, idx) => ({
|
||||
id: i.id ?? String(idx),
|
||||
title: i.title ?? '',
|
||||
image: this.cleanUrl(i.imageUrl ?? i.image_url ?? i.image ?? i.img ?? i.pic ?? ''),
|
||||
link: this.cleanUrl(i.linkUrl ?? i.link_url ?? i.link ?? i.url ?? ''),
|
||||
sort: typeof i.sort === 'number' ? i.sort : 0
|
||||
})).filter(i => i.image)
|
||||
mapped.sort((a, b) => a.sort - b.sort)
|
||||
console.log('normalizeBanners mapped', mapped)
|
||||
return mapped
|
||||
},
|
||||
normalizeActivities(list) {
|
||||
const arr = this.unwrap(list)
|
||||
console.log('normalizeActivities input', list, 'unwrapped', arr)
|
||||
const mapped = arr.map((i, idx) => ({
|
||||
id: i.id ?? String(idx),
|
||||
image: this.cleanUrl(i.banner ?? i.coverUrl ?? i.cover_url ?? i.image ?? i.img ?? i.pic ?? ''),
|
||||
title: i.title ?? i.name ?? '',
|
||||
subtitle: this.buildActivitySubtitle(i),
|
||||
link: this.cleanUrl(i.linkUrl ?? i.link_url ?? i.link ?? i.url ?? ''),
|
||||
category_name: (i.category_name ?? i.categoryName ?? '').trim(),
|
||||
category_id: i.activity_category_id ?? i.category_id ?? i.categoryId ?? null
|
||||
})).filter(i => i.image || i.title)
|
||||
console.log('normalizeActivities mapped', mapped)
|
||||
return mapped
|
||||
},
|
||||
buildActivitySubtitle(i) {
|
||||
const base = i.subTitle ?? i.sub_title ?? i.subtitle ?? i.desc ?? i.description ?? ''
|
||||
if (base) return base
|
||||
const cat = i.category_name ?? i.categoryName ?? ''
|
||||
const price = (i.price_draw !== undefined && i.price_draw !== null) ? `¥${i.price_draw}` : ''
|
||||
const parts = [cat, price].filter(Boolean)
|
||||
return parts.join(' · ')
|
||||
},
|
||||
async loadHomeData() {
|
||||
const results = await Promise.allSettled([
|
||||
this.apiGet('/api/app/notices'),
|
||||
this.apiGet('/api/app/banners'),
|
||||
this.apiGet('/api/app/activities')
|
||||
])
|
||||
const [nRes, bRes, acRes] = results
|
||||
if (nRes.status === 'fulfilled') {
|
||||
console.log('notices ok', nRes.value)
|
||||
this.notices = this.normalizeNotices(nRes.value)
|
||||
} else {
|
||||
console.error('notices error', nRes.reason)
|
||||
this.notices = []
|
||||
}
|
||||
if (bRes.status === 'fulfilled') {
|
||||
console.log('banners ok', bRes.value)
|
||||
this.banners = this.normalizeBanners(bRes.value)
|
||||
} else {
|
||||
console.error('banners error', bRes.reason)
|
||||
this.banners = []
|
||||
}
|
||||
if (acRes.status === 'fulfilled') {
|
||||
console.log('activities ok', acRes.value)
|
||||
this.activities = this.normalizeActivities(acRes.value)
|
||||
} else {
|
||||
console.error('activities error', acRes.reason)
|
||||
this.activities = []
|
||||
}
|
||||
console.log('home normalized', { notices: this.notices, banners: this.banners, activities: this.activities })
|
||||
},
|
||||
onBannerTap(b) {
|
||||
const imgs = (Array.isArray(this.banners) ? this.banners : []).map(x => x.image).filter(Boolean)
|
||||
const current = b && b.image
|
||||
if (current) {
|
||||
uni.previewImage({ urls: imgs.length ? imgs : [current], current })
|
||||
return
|
||||
}
|
||||
if (b.link && /^\/.+/.test(b.link)) {
|
||||
uni.navigateTo({ url: b.link })
|
||||
}
|
||||
},
|
||||
onActivityTap(a) {
|
||||
const name = (a.category_name || a.categoryName || '').trim()
|
||||
const id = a.id
|
||||
let path = ''
|
||||
if (name === '一番赏') path = '/pages/activity/yifanshang/index'
|
||||
else if (name === '无限赏') path = '/pages/activity/wuxianshang/index'
|
||||
else if (name === '对对碰') path = '/pages/activity/duiduipeng/index'
|
||||
if (path && id) {
|
||||
uni.navigateTo({ url: `${path}?id=${id}` })
|
||||
return
|
||||
}
|
||||
if (a.link && /^\/.+/.test(a.link)) {
|
||||
uni.navigateTo({ url: a.link })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.page { padding: 24rpx }
|
||||
.notice-bar { height: 64rpx; background: #fff4e6; border-radius: 8rpx; overflow: hidden; margin-bottom: 24rpx; }
|
||||
.notice-swiper { height: 64rpx }
|
||||
.notice-item { height: 64rpx; line-height: 64rpx; padding: 0 24rpx; color: #a15c00; font-size: 26rpx }
|
||||
.banner-box { margin-bottom: 24rpx }
|
||||
.banner-swiper { width: 100%; height: 320rpx; border-radius: 12rpx; overflow: hidden }
|
||||
.banner-image { width: 100%; height: 320rpx }
|
||||
.banner-fallback { width: 100%; height: 320rpx; display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #f5f5f5, #eaeaea) }
|
||||
.banner-fallback-text { color: #666; font-size: 28rpx }
|
||||
.activity-section { background: #ffffff; border-radius: 12rpx; padding: 24rpx }
|
||||
.section-title { font-size: 30rpx; font-weight: 600; margin-bottom: 16rpx }
|
||||
.activity-grid { display: flex; flex-wrap: wrap; margin: -12rpx }
|
||||
.activity-item { width: 50%; padding: 12rpx }
|
||||
.activity-thumb { width: 100%; height: 200rpx; border-radius: 8rpx }
|
||||
.activity-name { display: block; margin-top: 8rpx; font-size: 26rpx; color: #222 }
|
||||
.activity-desc { display: block; margin-top: 4rpx; font-size: 22rpx; color: #888 }
|
||||
</style>
|
||||
145
pages/login/index.vue
Normal file
@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<image class="logo" src="/static/logo.png" mode="widthFix"></image>
|
||||
<view class="title">微信登录</view>
|
||||
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<button class="btn" open-type="getPhoneNumber" :disabled="loading" @getphonenumber="onGetPhoneNumber">授权手机号快速登录</button>
|
||||
<!-- #endif -->
|
||||
<view class="agreements">
|
||||
<text>注册或登录即表示您已阅读并同意</text>
|
||||
<text class="link" @tap="toUserAgreement">《用户协议》</text>
|
||||
<text>和</text>
|
||||
<text class="link" @tap="toPurchaseAgreement">《购买协议》</text>
|
||||
</view>
|
||||
<view v-if="needBindPhone" class="tip">登录成功,请绑定手机号以完成登录</view>
|
||||
<view v-if="error" class="error">{{ error }}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { wechatLogin, bindPhone, getUserStats, getPointsBalance } from '../../api/appUser'
|
||||
|
||||
const loading = ref(false)
|
||||
const error = ref('')
|
||||
const needBindPhone = ref(false)
|
||||
|
||||
const loggedIn = computed(() => !!uni.getStorageSync('token'))
|
||||
|
||||
function onLogin() {}
|
||||
|
||||
function toUserAgreement() { uni.navigateTo({ url: '/pages/agreement/user' }) }
|
||||
function toPurchaseAgreement() { uni.navigateTo({ url: '/pages/agreement/purchase' }) }
|
||||
|
||||
function onGetPhoneNumber(e) {
|
||||
const phoneCode = e.detail.code
|
||||
console.log('login_flow start getPhoneNumber, codeExists:', !!phoneCode)
|
||||
if (!phoneCode) {
|
||||
uni.showToast({ title: '未授权手机号', icon: 'none' })
|
||||
console.error('login_flow error: missing phoneCode')
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
uni.login({
|
||||
provider: 'weixin',
|
||||
success: async (res) => {
|
||||
try {
|
||||
const loginCode = res.code
|
||||
console.log('login_flow uni.login success, loginCode exists:', !!loginCode)
|
||||
const data = await wechatLogin(loginCode)
|
||||
console.log('login_flow wechatLogin response user_id:', data && data.user_id)
|
||||
const token = data && data.token
|
||||
const user_id = data && data.user_id
|
||||
const avatar = data && data.avatar
|
||||
const nickname = data && data.nickname
|
||||
const invite_code = data && data.invite_code
|
||||
uni.setStorageSync('user_info', data || {})
|
||||
if (token) {
|
||||
uni.setStorageSync('token', token)
|
||||
console.log('login_flow token stored')
|
||||
}
|
||||
if (user_id) {
|
||||
uni.setStorageSync('user_id', user_id)
|
||||
console.log('login_flow user_id stored:', user_id)
|
||||
}
|
||||
if (avatar) {
|
||||
uni.setStorageSync('avatar', avatar)
|
||||
}
|
||||
if (nickname) {
|
||||
uni.setStorageSync('nickname', nickname)
|
||||
}
|
||||
if (invite_code) {
|
||||
uni.setStorageSync('invite_code', invite_code)
|
||||
}
|
||||
console.log('login_flow bindPhone start')
|
||||
try {
|
||||
// 首次绑定前短暂延迟,确保服务端 token 生效
|
||||
await new Promise(r => setTimeout(r, 600))
|
||||
const bindRes = await bindPhone(user_id, phoneCode, { 'X-Suppress-Auth-Modal': true })
|
||||
const phoneNumber = (bindRes && (bindRes.phone || bindRes.phone_number || bindRes.mobile)) || ''
|
||||
if (phoneNumber) uni.setStorageSync('phone_number', phoneNumber)
|
||||
} catch (bindErr) {
|
||||
if (bindErr && bindErr.statusCode === 401) {
|
||||
console.warn('login_flow bindPhone 401, try re-login and retry')
|
||||
// 重新获取登录 code
|
||||
const relogin = await new Promise((resolve, reject) => {
|
||||
uni.login({ provider: 'weixin', success: resolve, fail: reject })
|
||||
})
|
||||
const data2 = await wechatLogin(relogin.code)
|
||||
const token2 = data2 && data2.token
|
||||
const user2 = data2 && data2.user_id
|
||||
if (token2) uni.setStorageSync('token', token2)
|
||||
if (user2) uni.setStorageSync('user_id', user2)
|
||||
// 再次延迟后重试绑定
|
||||
await new Promise(r => setTimeout(r, 600))
|
||||
const bindRes2 = await bindPhone(user2 || user_id, phoneCode, { 'X-Suppress-Auth-Modal': true })
|
||||
const phoneNumber2 = (bindRes2 && (bindRes2.phone || bindRes2.phone_number || bindRes2.mobile)) || ''
|
||||
if (phoneNumber2) uni.setStorageSync('phone_number', phoneNumber2)
|
||||
} else {
|
||||
throw bindErr
|
||||
}
|
||||
}
|
||||
uni.setStorageSync('phone_bound', true)
|
||||
console.log('login_flow bindPhone success, phone_bound stored')
|
||||
try {
|
||||
const stats = await getUserStats(user_id)
|
||||
console.log('login_flow getUserStats success')
|
||||
const balance = await getPointsBalance(user_id)
|
||||
console.log('login_flow getPointsBalance success')
|
||||
uni.setStorageSync('user_stats', stats)
|
||||
const b = balance && balance.balance !== undefined ? balance.balance : balance
|
||||
uni.setStorageSync('points_balance', b)
|
||||
} catch (e) {
|
||||
console.error('login_flow fetch stats/points error:', e && (e.message || e.errMsg))
|
||||
}
|
||||
uni.showToast({ title: '登录并绑定成功', icon: 'success' })
|
||||
console.log('login_flow navigate to index')
|
||||
uni.reLaunch({ url: '/pages/index/index' })
|
||||
} catch (err) {
|
||||
console.error('login_flow error:', err && (err.message || err.errMsg), 'status:', err && err.statusCode)
|
||||
error.value = err.message || '登录或绑定失败'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
},
|
||||
fail: (e) => {
|
||||
console.error('login_flow uni.login fail:', e && e.errMsg)
|
||||
error.value = '微信登录失败'
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container { padding: 40rpx; display: flex; flex-direction: column; align-items: center }
|
||||
.logo { width: 200rpx; margin-top: 100rpx; margin-bottom: 40rpx }
|
||||
.title { font-size: 36rpx; margin-bottom: 20rpx }
|
||||
.btn { width: 80%; margin-top: 20rpx }
|
||||
.agreements { margin-top: 16rpx; font-size: 24rpx; color: #666; display: flex; flex-wrap: wrap; justify-content: center }
|
||||
.link { color: #007AFF; margin: 0 6rpx }
|
||||
.tip { color: #666; margin-top: 12rpx }
|
||||
.error { color: #e43; margin-top: 20rpx }
|
||||
</style>
|
||||
176
pages/mine/index.vue
Normal file
@ -0,0 +1,176 @@
|
||||
<template>
|
||||
<view class="wrap">
|
||||
<view class="header">
|
||||
<image class="avatar" :src="avatar || '/static/logo.png'" mode="aspectFill"></image>
|
||||
<view class="profile">
|
||||
<view class="nickname">{{ nickname || '未登录' }}</view>
|
||||
<view class="userid">ID:{{ userId || '-' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info">
|
||||
<view class="info-item">
|
||||
<text class="info-label">手机号</text>
|
||||
<text class="info-value">{{ phoneNumber || '未绑定' }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="info-label">邀请码</text>
|
||||
<text class="info-value">{{ inviteCode || '-' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="stats">
|
||||
<view class="stat">
|
||||
<view class="stat-label" @click="toPoints">积分</view>
|
||||
<view class="stat-value">{{ pointsBalance }}</view>
|
||||
</view>
|
||||
<view class="stat">
|
||||
<view class="stat-label">卡券</view>
|
||||
<view class="stat-value">{{ stats.coupon_count || 0 }}</view>
|
||||
</view>
|
||||
<view class="stat">
|
||||
<view class="stat-label">道具卡</view>
|
||||
<view class="stat-value">{{ stats.item_card_count || 0 }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<button class="refresh" @click="refresh" :disabled="loading">刷新数据</button>
|
||||
<view v-if="error" class="error">{{ error }}</view>
|
||||
</view>
|
||||
<view class="orders">
|
||||
<view class="orders-title">我的订单</view>
|
||||
<view class="orders-cats">
|
||||
<view class="orders-cat" @click="toOrders('pending')">
|
||||
<view class="orders-cat-title">待付款</view>
|
||||
</view>
|
||||
<view class="orders-cat" @click="toOrders('completed')">
|
||||
<view class="orders-cat-title">已完成</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="addresses">
|
||||
<view class="addresses-title">地址管理</view>
|
||||
<view class="addresses-entry" @click="toAddresses">
|
||||
<text class="addresses-text">管理收货地址</text>
|
||||
<text class="addresses-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="addresses">
|
||||
<view class="addresses-title">使用帮助</view>
|
||||
<view class="addresses-entry" @click="toHelp">
|
||||
<text class="addresses-text">查看协议与说明</text>
|
||||
<text class="addresses-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onShow, onLoad } from '@dcloudio/uni-app'
|
||||
import { getUserStats, getPointsBalance } from '../../api/appUser'
|
||||
|
||||
const avatar = ref(uni.getStorageSync('avatar') || '')
|
||||
const nickname = ref(uni.getStorageSync('nickname') || '')
|
||||
const userId = ref(uni.getStorageSync('user_id') || '')
|
||||
const phoneNumber = ref(uni.getStorageSync('phone_number') || '')
|
||||
const inviteCode = ref(uni.getStorageSync('invite_code') || (uni.getStorageSync('user_info') || {}).invite_code || '')
|
||||
const pointsBalance = ref(uni.getStorageSync('points_balance') || 0)
|
||||
const stats = ref(uni.getStorageSync('user_stats') || {})
|
||||
const loading = ref(false)
|
||||
const error = ref('')
|
||||
|
||||
async function refresh() {
|
||||
const token = uni.getStorageSync('token')
|
||||
const phoneBound = !!uni.getStorageSync('phone_bound')
|
||||
if (!token || !phoneBound) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '请先登录并绑定手机号',
|
||||
confirmText: '去登录',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.navigateTo({ url: '/pages/login/index' })
|
||||
}
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
const user_id = uni.getStorageSync('user_id')
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
try {
|
||||
const s = await getUserStats(user_id)
|
||||
stats.value = s || {}
|
||||
uni.setStorageSync('user_stats', stats.value)
|
||||
const b = await getPointsBalance(user_id)
|
||||
const balance = b && b.balance !== undefined ? b.balance : b
|
||||
pointsBalance.value = balance || 0
|
||||
uni.setStorageSync('points_balance', pointsBalance.value)
|
||||
} catch (e) {
|
||||
error.value = e && (e.message || e.errMsg) || '数据获取失败'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function toPoints() {
|
||||
uni.navigateTo({ url: '/pages/points/index' })
|
||||
}
|
||||
|
||||
function toOrders(status) {
|
||||
const s = status === 'completed' ? 'completed' : 'pending'
|
||||
uni.navigateTo({ url: `/pages/orders/index?status=${s}` })
|
||||
}
|
||||
|
||||
function toAddresses() {
|
||||
uni.navigateTo({ url: '/pages/address/index' })
|
||||
}
|
||||
|
||||
function toHelp() {
|
||||
uni.navigateTo({ url: '/pages/help/index' })
|
||||
}
|
||||
|
||||
onShow(() => {
|
||||
avatar.value = uni.getStorageSync('avatar') || avatar.value
|
||||
nickname.value = uni.getStorageSync('nickname') || nickname.value
|
||||
userId.value = uni.getStorageSync('user_id') || userId.value
|
||||
phoneNumber.value = uni.getStorageSync('phone_number') || phoneNumber.value
|
||||
const ui = uni.getStorageSync('user_info') || {}
|
||||
inviteCode.value = uni.getStorageSync('invite_code') || ui.invite_code || inviteCode.value
|
||||
pointsBalance.value = uni.getStorageSync('points_balance') || pointsBalance.value
|
||||
const s = uni.getStorageSync('user_stats')
|
||||
if (s) stats.value = s
|
||||
refresh()
|
||||
})
|
||||
|
||||
onLoad(() => {
|
||||
refresh()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.wrap { padding: 40rpx }
|
||||
.header { display: flex; align-items: center; margin-bottom: 24rpx }
|
||||
.avatar { width: 120rpx; height: 120rpx; border-radius: 60rpx; background-color: #f5f5f5 }
|
||||
.profile { margin-left: 20rpx; display: flex; flex-direction: column }
|
||||
.nickname { font-size: 32rpx }
|
||||
.userid { margin-top: 6rpx; font-size: 24rpx; color: #999 }
|
||||
.info { display: flex; flex-direction: column; background: #fff; border-radius: 12rpx; padding: 20rpx; margin-bottom: 20rpx; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04) }
|
||||
.info-item { display: flex; justify-content: space-between; margin-bottom: 12rpx }
|
||||
.info-label { color: #666; font-size: 24rpx }
|
||||
.info-value { font-size: 28rpx }
|
||||
.stats { display: flex; background: #fafafa; border-radius: 12rpx; padding: 20rpx; justify-content: space-between; margin-bottom: 20rpx }
|
||||
.stat { flex: 1; align-items: center }
|
||||
.stat-label { color: #666; font-size: 24rpx }
|
||||
.stat-value { font-size: 36rpx; margin-top: 8rpx }
|
||||
.orders { background: #fff; border-radius: 12rpx; padding: 20rpx; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04); margin-bottom: 20rpx }
|
||||
.orders-title { font-size: 30rpx; margin-bottom: 12rpx }
|
||||
.orders-cats { display: flex; justify-content: space-between }
|
||||
.orders-cat { flex: 1; background: #f7f7f7; border-radius: 12rpx; padding: 20rpx; margin-right: 12rpx }
|
||||
.orders-cat:last-child { margin-right: 0 }
|
||||
.orders-cat-title { font-size: 28rpx; text-align: center }
|
||||
.addresses { background: #fff; border-radius: 12rpx; padding: 20rpx; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04); margin-bottom: 20rpx }
|
||||
.addresses-title { font-size: 30rpx; margin-bottom: 12rpx }
|
||||
.addresses-entry { display: flex; justify-content: space-between; align-items: center; background: #f7f7f7; border-radius: 12rpx; padding: 20rpx }
|
||||
.addresses-text { font-size: 28rpx }
|
||||
.addresses-arrow { font-size: 28rpx; color: #999 }
|
||||
.refresh { width: 100%; margin-top: 12rpx }
|
||||
.error { color: #e43; margin-top: 20rpx }
|
||||
</style>
|
||||
149
pages/orders/index.vue
Normal file
@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<view class="wrap">
|
||||
<view class="tabs">
|
||||
<view class="tab" :class="{ active: currentTab === 'pending' }" @click="switchTab('pending')">待付款</view>
|
||||
<view class="tab" :class="{ active: currentTab === 'completed' }" @click="switchTab('completed')">已完成</view>
|
||||
</view>
|
||||
<view v-if="error" class="error">{{ error }}</view>
|
||||
<view v-if="orders.length === 0 && !loading" class="empty">暂无订单</view>
|
||||
<view v-for="item in orders" :key="item.id || item.order_no" class="order">
|
||||
<view class="order-main">
|
||||
<view class="order-title">{{ item.title || item.subject || '订单' }}</view>
|
||||
<view class="order-sub">{{ formatTime(item.created_at || item.time) }}</view>
|
||||
</view>
|
||||
<view class="order-right">
|
||||
<view class="order-amount">{{ formatAmount(item.total_amount || item.amount || item.price) }}</view>
|
||||
<view class="order-status">{{ statusText(item.status || item.pay_status || item.state) }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="loadingMore" class="loading">加载中...</view>
|
||||
<view v-else-if="!hasMore && orders.length > 0" class="end">没有更多了</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad, onReachBottom } from '@dcloudio/uni-app'
|
||||
import { getOrders } from '../../api/appUser'
|
||||
|
||||
const currentTab = ref('pending')
|
||||
const orders = ref([])
|
||||
const loading = ref(false)
|
||||
const loadingMore = ref(false)
|
||||
const error = ref('')
|
||||
const page = ref(1)
|
||||
const pageSize = ref(20)
|
||||
const hasMore = ref(true)
|
||||
|
||||
function formatTime(t) {
|
||||
if (!t) return ''
|
||||
const d = typeof t === 'string' ? new Date(t) : new Date(t)
|
||||
const y = d.getFullYear()
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
const hh = String(d.getHours()).padStart(2, '0')
|
||||
const mm = String(d.getMinutes()).padStart(2, '0')
|
||||
return `${y}-${m}-${day} ${hh}:${mm}`
|
||||
}
|
||||
|
||||
function formatAmount(a) {
|
||||
if (a === undefined || a === null) return ''
|
||||
const n = Number(a)
|
||||
if (Number.isNaN(n)) return String(a)
|
||||
return `¥${n.toFixed(2)}`
|
||||
}
|
||||
|
||||
function statusText(s) {
|
||||
const v = String(s || '').toLowerCase()
|
||||
if (v.includes('pend')) return '待付款'
|
||||
if (v.includes('paid') || v.includes('complete') || v.includes('done')) return '已完成'
|
||||
return s || ''
|
||||
}
|
||||
|
||||
function switchTab(tab) {
|
||||
if (currentTab.value === tab) return
|
||||
currentTab.value = tab
|
||||
fetchOrders(false)
|
||||
}
|
||||
|
||||
function apiStatus() {
|
||||
return currentTab.value === 'pending' ? 'pending' : 'completed'
|
||||
}
|
||||
|
||||
async function fetchOrders(append) {
|
||||
const user_id = uni.getStorageSync('user_id')
|
||||
const token = uni.getStorageSync('token')
|
||||
const phoneBound = !!uni.getStorageSync('phone_bound')
|
||||
if (!user_id || !token || !phoneBound) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '请先登录并绑定手机号',
|
||||
confirmText: '去登录',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.navigateTo({ url: '/pages/login/index' })
|
||||
}
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
if (append) {
|
||||
if (!hasMore.value || loadingMore.value) return
|
||||
loadingMore.value = true
|
||||
page.value = page.value + 1
|
||||
} else {
|
||||
loading.value = true
|
||||
page.value = 1
|
||||
hasMore.value = true
|
||||
orders.value = []
|
||||
}
|
||||
error.value = ''
|
||||
try {
|
||||
const list = await getOrders(user_id, apiStatus(), page.value, pageSize.value)
|
||||
const items = Array.isArray(list) ? list : (list && list.items) || []
|
||||
const total = (list && list.total) || 0
|
||||
orders.value = append ? orders.value.concat(items) : items
|
||||
if (total) {
|
||||
hasMore.value = orders.value.length < total
|
||||
} else {
|
||||
hasMore.value = items.length === pageSize.value
|
||||
}
|
||||
} catch (e) {
|
||||
error.value = e && (e.message || e.errMsg) || '获取订单失败'
|
||||
} finally {
|
||||
if (append) {
|
||||
loadingMore.value = false
|
||||
} else {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onLoad((opts) => {
|
||||
const s = (opts && opts.status) || ''
|
||||
if (s === 'completed' || s === 'pending') currentTab.value = s
|
||||
fetchOrders(false)
|
||||
})
|
||||
|
||||
onReachBottom(() => {
|
||||
fetchOrders(true)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.wrap { padding: 24rpx }
|
||||
.tabs { display: flex; background: #fff; border-radius: 12rpx; padding: 8rpx; margin-bottom: 16rpx; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04) }
|
||||
.tab { flex: 1; text-align: center; padding: 16rpx 0; font-size: 28rpx; color: #666 }
|
||||
.tab.active { color: #007AFF; font-weight: 600 }
|
||||
.order { display: flex; justify-content: space-between; align-items: center; background: #fff; border-radius: 12rpx; padding: 20rpx; margin-bottom: 16rpx; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04) }
|
||||
.order-main { display: flex; flex-direction: column }
|
||||
.order-title { font-size: 28rpx; color: #333 }
|
||||
.order-sub { font-size: 24rpx; color: #999; margin-top: 6rpx }
|
||||
.order-right { display: flex; flex-direction: column; align-items: flex-end }
|
||||
.order-amount { font-size: 28rpx; color: #333 }
|
||||
.order-status { font-size: 24rpx; color: #666; margin-top: 6rpx }
|
||||
.empty { text-align: center; color: #999; margin-top: 40rpx }
|
||||
.error { color: #e43; margin-bottom: 12rpx }
|
||||
.loading { text-align: center; color: #666; margin: 20rpx 0 }
|
||||
.end { text-align: center; color: #999; margin: 20rpx 0 }
|
||||
</style>
|
||||
117
pages/points/index.vue
Normal file
@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<view class="wrap">
|
||||
<view v-if="error" class="error">{{ error }}</view>
|
||||
<view v-if="records.length === 0 && !loading" class="empty">暂无积分记录</view>
|
||||
<view v-for="item in records" :key="item.id || item.time || item.created_at" class="record">
|
||||
<view class="record-main">
|
||||
<view class="record-title">{{ item.title || item.reason || '变更' }}</view>
|
||||
<view class="record-time">{{ formatTime(item.time || item.created_at) }}</view>
|
||||
</view>
|
||||
<view class="record-amount" :class="{ inc: (item.change || item.amount || 0) > 0, dec: (item.change || item.amount || 0) < 0 }">
|
||||
{{ (item.change ?? item.amount ?? 0) > 0 ? '+' : '' }}{{ item.change ?? item.amount ?? 0 }}
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="loadingMore" class="loading">加载中...</view>
|
||||
<view v-else-if="!hasMore && records.length > 0" class="end">没有更多了</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad, onReachBottom } from '@dcloudio/uni-app'
|
||||
import { getPointsRecords } from '../../api/appUser'
|
||||
|
||||
const records = ref([])
|
||||
const loading = ref(false)
|
||||
const loadingMore = ref(false)
|
||||
const error = ref('')
|
||||
const page = ref(1)
|
||||
const pageSize = ref(20)
|
||||
const hasMore = ref(true)
|
||||
|
||||
function formatTime(t) {
|
||||
if (!t) return ''
|
||||
const d = typeof t === 'string' ? new Date(t) : new Date(t)
|
||||
const y = d.getFullYear()
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
const hh = String(d.getHours()).padStart(2, '0')
|
||||
const mm = String(d.getMinutes()).padStart(2, '0')
|
||||
return `${y}-${m}-${day} ${hh}:${mm}`
|
||||
}
|
||||
|
||||
async function fetchRecords(append = false) {
|
||||
const user_id = uni.getStorageSync('user_id')
|
||||
const token = uni.getStorageSync('token')
|
||||
const phoneBound = !!uni.getStorageSync('phone_bound')
|
||||
if (!user_id || !token || !phoneBound) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '请先登录并绑定手机号',
|
||||
confirmText: '去登录',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.navigateTo({ url: '/pages/login/index' })
|
||||
}
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
if (append) {
|
||||
if (!hasMore.value || loadingMore.value) return
|
||||
loadingMore.value = true
|
||||
page.value = page.value + 1
|
||||
} else {
|
||||
loading.value = true
|
||||
page.value = 1
|
||||
hasMore.value = true
|
||||
}
|
||||
error.value = ''
|
||||
try {
|
||||
const list = await getPointsRecords(user_id, page.value, pageSize.value)
|
||||
const items = Array.isArray(list) ? list : (list && list.items) || []
|
||||
const total = (list && list.total) || 0
|
||||
if (append) {
|
||||
records.value = records.value.concat(items)
|
||||
} else {
|
||||
records.value = items
|
||||
}
|
||||
if (total) {
|
||||
hasMore.value = records.value.length < total
|
||||
} else {
|
||||
hasMore.value = items.length === pageSize.value
|
||||
}
|
||||
} catch (e) {
|
||||
error.value = e && (e.message || e.errMsg) || '获取积分记录失败'
|
||||
} finally {
|
||||
if (append) {
|
||||
loadingMore.value = false
|
||||
} else {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
fetchRecords(false)
|
||||
})
|
||||
|
||||
onReachBottom(() => {
|
||||
fetchRecords(true)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.wrap { padding: 24rpx }
|
||||
.record { display: flex; justify-content: space-between; align-items: center; background: #fff; border-radius: 12rpx; padding: 20rpx; margin-bottom: 16rpx; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04) }
|
||||
.record-main { display: flex; flex-direction: column }
|
||||
.record-title { font-size: 28rpx; color: #333 }
|
||||
.record-time { font-size: 24rpx; color: #999; margin-top: 6rpx }
|
||||
.record-amount { font-size: 32rpx }
|
||||
.record-amount.inc { color: #18a058 }
|
||||
.record-amount.dec { color: #d03050 }
|
||||
.empty { text-align: center; color: #999; margin-top: 40rpx }
|
||||
.error { color: #e43; margin-bottom: 12rpx }
|
||||
.loading { text-align: center; color: #666; margin: 20rpx 0 }
|
||||
.end { text-align: center; color: #999; margin: 20rpx 0 }
|
||||
</style>
|
||||
289
pages/shop/index.vue
Normal file
@ -0,0 +1,289 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view v-if="loading" class="loading-wrap"><view class="spinner"></view></view>
|
||||
<view class="products-section" v-else>
|
||||
<view class="section-title">商品</view>
|
||||
<view class="toolbar">
|
||||
<input class="search" v-model="keyword" placeholder="搜索商品" confirm-type="search" @confirm="onSearchConfirm" />
|
||||
<view class="filters">
|
||||
<input class="price" type="number" v-model="minPrice" placeholder="最低价" />
|
||||
<text class="dash">-</text>
|
||||
<input class="price" type="number" v-model="maxPrice" placeholder="最高价" />
|
||||
<button class="apply-btn" size="mini" @tap="onApplyFilters">筛选</button>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="displayCount" class="products-columns">
|
||||
<view class="column" v-for="(col, ci) in columns" :key="ci">
|
||||
<view class="product-item" v-for="p in col" :key="p.id" @tap="onProductTap(p)">
|
||||
<view class="product-card">
|
||||
<view class="thumb-wrap">
|
||||
<image class="product-thumb" :class="{ visible: isLoaded(p) }" :src="p.image" mode="widthFix" lazy-load="true" @load="onImageLoad(p)" @error="onImageError(p)" />
|
||||
<view v-if="!isLoaded(p)" class="skeleton"></view>
|
||||
<view class="badge">
|
||||
<text class="badge-price" v-if="p.price !== null">¥{{ p.price }}</text>
|
||||
<text class="badge-points" v-if="p.points !== null">{{ p.points }}积分</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="product-title">{{ p.title }}</text>
|
||||
<view class="product-extra" v-if="p.stock !== null">
|
||||
<text class="stock">库存 {{ p.stock }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="empty">暂无商品</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { ref, computed } from 'vue'
|
||||
import { request, authRequest } from '../../utils/request.js'
|
||||
|
||||
const products = ref([])
|
||||
const columns = ref([[], []])
|
||||
const colHeights = ref([0, 0])
|
||||
const CACHE_KEY = 'products_cache_v1'
|
||||
const TTL_MS = 10 * 60 * 1000
|
||||
const loading = ref(false)
|
||||
const keyword = ref('')
|
||||
const minPrice = ref('')
|
||||
const maxPrice = ref('')
|
||||
const displayCount = computed(() => (columns.value[0].length + columns.value[1].length))
|
||||
const loadedMap = ref({})
|
||||
function getKey(p) { return String((p && p.id) ?? '') + '|' + String((p && p.image) ?? '') }
|
||||
function unwrap(list) {
|
||||
if (Array.isArray(list)) return list
|
||||
const obj = list || {}
|
||||
const data = obj.data || {}
|
||||
const arr = obj.list || obj.items || data.list || data.items || data
|
||||
return Array.isArray(arr) ? arr : []
|
||||
}
|
||||
function cleanUrl(u) {
|
||||
const s = String(u || '').trim()
|
||||
const m = s.match(/https?:\/\/[^\s'"`]+/)
|
||||
if (m && m[0]) return m[0]
|
||||
return s.replace(/[`'\"]/g, '').trim()
|
||||
}
|
||||
function apiGet(url, data = {}) {
|
||||
const token = uni.getStorageSync('token')
|
||||
const fn = token ? authRequest : request
|
||||
return fn({ url, method: 'GET', data })
|
||||
}
|
||||
|
||||
function getCachedProducts() {
|
||||
try {
|
||||
const obj = uni.getStorageSync(CACHE_KEY)
|
||||
if (obj && Array.isArray(obj.data) && typeof obj.ts === 'number') {
|
||||
const fresh = Date.now() - obj.ts < TTL_MS
|
||||
if (fresh) return obj.data
|
||||
}
|
||||
} catch (_) {}
|
||||
return null
|
||||
}
|
||||
|
||||
function setCachedProducts(list) {
|
||||
try {
|
||||
uni.setStorageSync(CACHE_KEY, { data: Array.isArray(list) ? list : [], ts: Date.now() })
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
function estimateHeight(p) {
|
||||
const base = 220
|
||||
const len = String(p.title || '').length
|
||||
const lines = Math.min(2, Math.ceil(len / 12))
|
||||
const titleH = lines * 36
|
||||
const stockH = p.stock !== null && p.stock !== undefined ? 34 : 0
|
||||
const padding = 28
|
||||
return base + titleH + stockH + padding
|
||||
}
|
||||
|
||||
function distributeToColumns(list) {
|
||||
const arr = Array.isArray(list) ? list : []
|
||||
const cols = Array.from({ length: 2 }, () => [])
|
||||
const hs = [0, 0]
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
const h = estimateHeight(arr[i])
|
||||
const idx = hs[0] <= hs[1] ? 0 : 1
|
||||
cols[idx].push(arr[i])
|
||||
hs[idx] += h
|
||||
}
|
||||
columns.value = cols
|
||||
colHeights.value = hs
|
||||
const presentKeys = new Set(arr.map(getKey))
|
||||
const next = {}
|
||||
const prev = loadedMap.value || {}
|
||||
for (const k in prev) { if (presentKeys.has(k)) next[k] = prev[k] }
|
||||
loadedMap.value = next
|
||||
}
|
||||
|
||||
function extractListAndTotal(payload) {
|
||||
if (Array.isArray(payload)) return { list: payload, total: payload.length }
|
||||
const obj = payload || {}
|
||||
const data = obj.data || {}
|
||||
const list = obj.list || obj.items || data.list || data.items || []
|
||||
const totalRaw = obj.total ?? data.total
|
||||
const total = typeof totalRaw === 'number' ? totalRaw : (Array.isArray(list) ? list.length : 0)
|
||||
return { list: Array.isArray(list) ? list : [], total }
|
||||
}
|
||||
|
||||
function normalizeProducts(list) {
|
||||
const arr = unwrap(list)
|
||||
return arr.map((i, idx) => ({
|
||||
id: i.id ?? i.productId ?? i._id ?? i.sku_id ?? String(idx),
|
||||
image: cleanUrl(i.main_image ?? i.imageUrl ?? i.image_url ?? i.image ?? i.img ?? i.pic ?? ''),
|
||||
title: i.title ?? i.name ?? i.product_name ?? i.sku_name ?? '',
|
||||
price: i.price_sale ?? i.price ?? i.price_min ?? i.amount ?? null,
|
||||
points: i.points_required ?? i.points ?? i.integral ?? null,
|
||||
stock: i.stock ?? i.inventory ?? i.quantity ?? null,
|
||||
link: cleanUrl(i.linkUrl ?? i.link_url ?? i.link ?? i.url ?? '')
|
||||
})).filter(i => i.image || i.title)
|
||||
}
|
||||
|
||||
function onProductTap(p) {
|
||||
const imgs = (Array.isArray(products.value) ? products.value : []).map(x => x.image).filter(Boolean)
|
||||
const current = p && p.image
|
||||
if (current) {
|
||||
uni.previewImage({ urls: imgs.length ? imgs : [current], current })
|
||||
return
|
||||
}
|
||||
if (p.link && /^\/.+/.test(p.link)) {
|
||||
uni.navigateTo({ url: p.link })
|
||||
}
|
||||
}
|
||||
|
||||
function applyFilters() {
|
||||
const k = String(keyword.value || '').trim().toLowerCase()
|
||||
const min = Number(minPrice.value)
|
||||
const max = Number(maxPrice.value)
|
||||
const hasMin = !isNaN(min) && String(minPrice.value).trim() !== ''
|
||||
const hasMax = !isNaN(max) && String(maxPrice.value).trim() !== ''
|
||||
const list = Array.isArray(products.value) ? products.value : []
|
||||
const filtered = list.filter(p => {
|
||||
const title = String(p.title || '').toLowerCase()
|
||||
if (k && !title.includes(k)) return false
|
||||
const priceNum = typeof p.price === 'number' ? p.price : Number(p.price)
|
||||
if (hasMin) {
|
||||
if (isNaN(priceNum)) return false
|
||||
if (priceNum < min) return false
|
||||
}
|
||||
if (hasMax) {
|
||||
if (isNaN(priceNum)) return false
|
||||
if (priceNum > max) return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
distributeToColumns(filtered)
|
||||
}
|
||||
|
||||
function onSearchConfirm() { applyFilters() }
|
||||
function onApplyFilters() { applyFilters() }
|
||||
|
||||
async function loadProducts() {
|
||||
try {
|
||||
const cached = getCachedProducts()
|
||||
if (cached) {
|
||||
products.value = cached
|
||||
distributeToColumns(cached)
|
||||
return
|
||||
}
|
||||
const first = await apiGet('/api/app/products', { page: 1 })
|
||||
const { list: firstList, total } = extractListAndTotal(first)
|
||||
const pageSize = 20
|
||||
const totalPages = Math.max(1, Math.ceil(((typeof total === 'number' ? total : 0)) / pageSize))
|
||||
if (totalPages <= 1) {
|
||||
const normalized = normalizeProducts(firstList)
|
||||
products.value = normalized
|
||||
distributeToColumns(normalized)
|
||||
setCachedProducts(normalized)
|
||||
return
|
||||
}
|
||||
const tasks = []
|
||||
for (let p = 2; p <= totalPages; p++) {
|
||||
tasks.push(apiGet('/api/app/products', { page: p }))
|
||||
}
|
||||
const results = await Promise.allSettled(tasks)
|
||||
const restLists = results.map(r => {
|
||||
if (r.status === 'fulfilled') {
|
||||
const { list } = extractListAndTotal(r.value)
|
||||
return Array.isArray(list) ? list : []
|
||||
}
|
||||
return []
|
||||
})
|
||||
const merged = [firstList, ...restLists].flat()
|
||||
const normalized = normalizeProducts(merged)
|
||||
products.value = normalized
|
||||
distributeToColumns(normalized)
|
||||
setCachedProducts(normalized)
|
||||
} catch (e) {
|
||||
products.value = []
|
||||
columns.value = [[], []]
|
||||
colHeights.value = [0, 0]
|
||||
const presentKeys = new Set([])
|
||||
const next = {}
|
||||
const prev = loadedMap.value || {}
|
||||
for (const k in prev) { if (presentKeys.has(k)) next[k] = prev[k] }
|
||||
loadedMap.value = next
|
||||
}
|
||||
}
|
||||
|
||||
function isLoaded(p) { return !!(loadedMap.value && loadedMap.value[getKey(p)]) }
|
||||
function onImageLoad(p) { const k = getKey(p); if (!k) return; loadedMap.value = { ...(loadedMap.value || {}), [k]: true } }
|
||||
function onImageError(p) { const k = getKey(p); if (!k) return; const prev = { ...(loadedMap.value || {}) }; delete prev[k]; loadedMap.value = prev }
|
||||
|
||||
onShow(async () => {
|
||||
const token = uni.getStorageSync('token')
|
||||
const phoneBound = !!uni.getStorageSync('phone_bound')
|
||||
if (!token || !phoneBound) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '请先登录并绑定手机号',
|
||||
confirmText: '去登录',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.navigateTo({ url: '/pages/login/index' })
|
||||
}
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
await loadProducts()
|
||||
loading.value = false
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page { padding: 24rpx }
|
||||
.section-title { font-size: 30rpx; font-weight: 600; margin-bottom: 16rpx }
|
||||
.products-section { background: #ffffff; border-radius: 12rpx; padding: 24rpx; margin-top: 24rpx }
|
||||
.loading-wrap { min-height: 60vh; display: flex; align-items: center; justify-content: center }
|
||||
.spinner { width: 56rpx; height: 56rpx; border: 6rpx solid rgba(0,122,255,0.15); border-top-color: #007AFF; border-radius: 50%; animation: spin 1s linear infinite }
|
||||
@keyframes spin { from { transform: rotate(0) } to { transform: rotate(360deg) } }
|
||||
.toolbar { display: flex; flex-direction: column; gap: 12rpx; margin-bottom: 16rpx }
|
||||
.search { background: #f6f8ff; border: 1rpx solid rgba(0,122,255,0.25); border-radius: 999rpx; padding: 14rpx 20rpx; font-size: 26rpx }
|
||||
.filters { display: flex; align-items: center; gap: 12rpx }
|
||||
.price { flex: 1; background: #f6f8ff; border: 1rpx solid rgba(0,122,255,0.25); border-radius: 999rpx; padding: 12rpx 16rpx; font-size: 26rpx }
|
||||
.dash { color: #888; font-size: 26rpx }
|
||||
.apply-btn { background: #007AFF; color: #fff; border-radius: 999rpx; padding: 0 20rpx }
|
||||
.products-columns { display: flex; gap: 12rpx }
|
||||
.column { flex: 1 }
|
||||
.product-item { margin-bottom: 12rpx }
|
||||
.empty { padding: 40rpx; color: #888; text-align: center }
|
||||
.product-card { background: #fff; border-radius: 16rpx; overflow: hidden; box-shadow: 0 6rpx 16rpx rgba(0,122,255,0.08); transition: transform .15s ease }
|
||||
.product-item:active .product-card { transform: scale(0.98) }
|
||||
.thumb-wrap { position: relative }
|
||||
.product-thumb { width: 100%; height: auto; display: block; opacity: 0; transition: opacity .25s ease; z-index: 0 }
|
||||
.product-thumb.visible { opacity: 1 }
|
||||
.skeleton { position: absolute; left: 0; top: 0; right: 0; bottom: 0; background: linear-gradient(90deg, #eef2ff 25%, #f6f8ff 37%, #eef2ff 63%); background-size: 400% 100%; animation: shimmer 1.2s ease infinite; z-index: 1 }
|
||||
@keyframes shimmer { 0% { background-position: 100% 0 } 100% { background-position: 0 0 } }
|
||||
.thumb-wrap { background: #f6f8ff; min-height: 220rpx }
|
||||
.badge { position: absolute; left: 12rpx; bottom: 12rpx; display: flex; gap: 8rpx }
|
||||
.badge-price { background: #007AFF; color: #fff; font-size: 22rpx; padding: 6rpx 12rpx; border-radius: 999rpx; box-shadow: 0 2rpx 8rpx rgba(0,122,255,0.25) }
|
||||
.badge-points { background: rgba(0,122,255,0.85); color: #fff; font-size: 22rpx; padding: 6rpx 12rpx; border-radius: 999rpx }
|
||||
.product-title { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; margin: 12rpx; font-size: 26rpx; color: #222 }
|
||||
.product-extra { display: flex; justify-content: flex-end; align-items: center; margin: 0 12rpx 12rpx }
|
||||
.stock { font-size: 22rpx; color: #888 }
|
||||
</style>
|
||||
BIN
static/logo.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
static/tab/box.png
Normal file
|
After Width: | Height: | Size: 373 B |
BIN
static/tab/box_active.png
Normal file
|
After Width: | Height: | Size: 651 B |
BIN
static/tab/home.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
static/tab/home_active.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
static/tab/profile.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
static/tab/profile_active.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
static/tab/shop.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
static/tab/shop_active.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
13
uni.promisify.adaptor.js
Normal file
@ -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) => {
|
||||
res.then((res) => {
|
||||
if (!res) return resolve(res)
|
||||
return res[0] ? reject(res[0]) : resolve(res[1])
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
76
uni.scss
Normal file
@ -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:#ffffff;
|
||||
$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: #555555; // 二级标题颜色
|
||||
$uni-font-size-subtitle:26px;
|
||||
$uni-color-paragraph: #3F536E; // 文章段落颜色
|
||||
$uni-font-size-paragraph:15px;
|
||||
1
unpackage/dist/dev/.sourcemap/mp-weixin/api/appUser.js.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"appUser.js","sources":["api/appUser.js"],"sourcesContent":["import { request, authRequest } from '../utils/request'\n\nexport function wechatLogin(code, invite_code) {\n const data = invite_code ? { code, invite_code } : { code }\n return request({ url: '/api/app/users/weixin/login', method: 'POST', data })\n}\n\nexport function bindPhone(user_id, code, extraHeader = {}) {\n return authRequest({ url: `/api/app/users/${user_id}/phone/bind`, method: 'POST', data: { code }, header: extraHeader })\n}\n\nexport function getUserStats(user_id) {\n return authRequest({ url: `/api/app/users/${user_id}/stats`, method: 'GET' })\n}\n\nexport function getPointsBalance(user_id) {\n return authRequest({ url: `/api/app/users/${user_id}/points/balance`, method: 'GET' })\n}\n\nexport function getPointsRecords(user_id, page = 1, page_size = 20) {\n return authRequest({ url: `/api/app/users/${user_id}/points`, method: 'GET', data: { page, page_size } })\n}\n\nexport function getOrders(user_id, status, page = 1, page_size = 20) {\n const data = { page, page_size }\n if (status) data.status = status\n return authRequest({ url: `/api/app/users/${user_id}/orders`, method: 'GET', data })\n}\n\nexport function listAddresses(user_id) {\n return authRequest({ url: `/api/app/users/${user_id}/addresses`, method: 'GET' })\n}\n\nexport function addAddress(user_id, payload) {\n return authRequest({ url: `/api/app/users/${user_id}/addresses`, method: 'POST', data: payload })\n}\n\nexport function updateAddress(user_id, address_id, payload) {\n return authRequest({ url: `/api/app/users/${user_id}/addresses/${address_id}`, method: 'PUT', data: payload })\n}\n\nexport function deleteAddress(user_id, address_id) {\n return authRequest({ url: `/api/app/users/${user_id}/addresses/${address_id}`, method: 'DELETE' })\n}\n\nexport function setDefaultAddress(user_id, address_id) {\n return authRequest({ url: `/api/app/users/${user_id}/addresses/${address_id}/default`, method: 'PUT' })\n}\n\nexport function getActivityDetail(activity_id) {\n return authRequest({ url: `/api/app/activities/${activity_id}`, method: 'GET' })\n}\n\nexport function getActivityIssues(activity_id) {\n return authRequest({ url: `/api/app/activities/${activity_id}/issues`, method: 'GET' })\n}\n\nexport function getActivityIssueRewards(activity_id, issue_id) {\n return authRequest({ url: `/api/app/activities/${activity_id}/issues/${issue_id}/rewards`, method: 'GET' })\n}"],"names":["request","authRequest"],"mappings":";;AAEO,SAAS,YAAY,MAAM,aAAa;AAC7C,QAAM,OAAO,cAAc,EAAE,MAAM,YAAW,IAAK,EAAE,KAAM;AAC3D,SAAOA,cAAAA,QAAQ,EAAE,KAAK,+BAA+B,QAAQ,QAAQ,MAAM;AAC7E;AAEO,SAAS,UAAU,SAAS,MAAM,cAAc,CAAA,GAAI;AACzD,SAAOC,cAAAA,YAAY,EAAE,KAAK,kBAAkB,OAAO,eAAe,QAAQ,QAAQ,MAAM,EAAE,KAAM,GAAE,QAAQ,YAAW,CAAE;AACzH;AAEO,SAAS,aAAa,SAAS;AACpC,SAAOA,cAAW,YAAC,EAAE,KAAK,kBAAkB,OAAO,UAAU,QAAQ,OAAO;AAC9E;AAEO,SAAS,iBAAiB,SAAS;AACxC,SAAOA,cAAW,YAAC,EAAE,KAAK,kBAAkB,OAAO,mBAAmB,QAAQ,OAAO;AACvF;AAEO,SAAS,iBAAiB,SAAS,OAAO,GAAG,YAAY,IAAI;AAClE,SAAOA,cAAW,YAAC,EAAE,KAAK,kBAAkB,OAAO,WAAW,QAAQ,OAAO,MAAM,EAAE,MAAM,UAAW,EAAA,CAAE;AAC1G;AAEO,SAAS,UAAU,SAAS,QAAQ,OAAO,GAAG,YAAY,IAAI;AACnE,QAAM,OAAO,EAAE,MAAM,UAAW;AAChC,MAAI;AAAQ,SAAK,SAAS;AAC1B,SAAOA,0BAAY,EAAE,KAAK,kBAAkB,OAAO,WAAW,QAAQ,OAAO,MAAM;AACrF;AAEO,SAAS,cAAc,SAAS;AACrC,SAAOA,cAAW,YAAC,EAAE,KAAK,kBAAkB,OAAO,cAAc,QAAQ,OAAO;AAClF;AAEO,SAAS,WAAW,SAAS,SAAS;AAC3C,SAAOA,0BAAY,EAAE,KAAK,kBAAkB,OAAO,cAAc,QAAQ,QAAQ,MAAM,QAAO,CAAE;AAClG;AAEO,SAAS,cAAc,SAAS,YAAY,SAAS;AAC1D,SAAOA,cAAW,YAAC,EAAE,KAAK,kBAAkB,OAAO,cAAc,UAAU,IAAI,QAAQ,OAAO,MAAM,QAAO,CAAE;AAC/G;AAEO,SAAS,cAAc,SAAS,YAAY;AACjD,SAAOA,0BAAY,EAAE,KAAK,kBAAkB,OAAO,cAAc,UAAU,IAAI,QAAQ,SAAQ,CAAE;AACnG;AAEO,SAAS,kBAAkB,SAAS,YAAY;AACrD,SAAOA,0BAAY,EAAE,KAAK,kBAAkB,OAAO,cAAc,UAAU,YAAY,QAAQ,MAAK,CAAE;AACxG;AAEO,SAAS,kBAAkB,aAAa;AAC7C,SAAOA,cAAW,YAAC,EAAE,KAAK,uBAAuB,WAAW,IAAI,QAAQ,OAAO;AACjF;AAEO,SAAS,kBAAkB,aAAa;AAC7C,SAAOA,cAAW,YAAC,EAAE,KAAK,uBAAuB,WAAW,WAAW,QAAQ,OAAO;AACxF;AAEO,SAAS,wBAAwB,aAAa,UAAU;AAC7D,SAAOA,0BAAY,EAAE,KAAK,uBAAuB,WAAW,WAAW,QAAQ,YAAY,QAAQ,MAAK,CAAE;AAC5G;;;;;;;;;;;;;;;"}
|
||||
1
unpackage/dist/dev/.sourcemap/mp-weixin/app.js.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"app.js","sources":["App.vue","main.js"],"sourcesContent":["<script>\r\n\texport default {\r\n\t\tonLaunch: function() {\r\n\t\t\tconsole.log('App Launch')\r\n\t\t},\r\n\t\tonShow: function() {\r\n\t\t\tconsole.log('App Show')\r\n\t\t},\r\n\t\tonHide: function() {\r\n\t\t\tconsole.log('App Hide')\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style>\r\n\t/*每个页面公共css */\r\n</style>\n","import App from './App'\n\n// #ifndef VUE3\nimport Vue from 'vue'\nimport './uni.promisify.adaptor'\nVue.config.productionTip = false\nApp.mpType = 'app'\nconst app = new Vue({\n ...App\n})\napp.$mount()\n// #endif\n\n// #ifdef VUE3\nimport { createSSRApp } from 'vue'\nexport function createApp() {\n const app = createSSRApp(App)\n return {\n app\n }\n}\n// #endif"],"names":["uni","createSSRApp","App"],"mappings":";;;;;;;;;;;;;;;;;;;;AACC,MAAK,YAAU;AAAA,EACd,UAAU,WAAW;AACpBA,kBAAAA,MAAA,MAAA,OAAA,gBAAY,YAAY;AAAA,EACxB;AAAA,EACD,QAAQ,WAAW;AAClBA,kBAAAA,MAAY,MAAA,OAAA,gBAAA,UAAU;AAAA,EACtB;AAAA,EACD,QAAQ,WAAW;AAClBA,kBAAAA,MAAY,MAAA,OAAA,iBAAA,UAAU;AAAA,EACvB;AACD;ACIM,SAAS,YAAY;AAC1B,QAAM,MAAMC,cAAY,aAACC,SAAG;AAC5B,SAAO;AAAA,IACL;AAAA,EACD;AACH;;;"}
|
||||
1
unpackage/dist/dev/.sourcemap/mp-weixin/common/assets.js.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"assets.js","sources":["static/logo.png"],"sourcesContent":["export default \"__VITE_ASSET__46719607__\""],"names":[],"mappings":";AAAA,MAAe,aAAA;;"}
|
||||
1
unpackage/dist/dev/.sourcemap/mp-weixin/common/vendor.js.map
vendored
Normal file
1
unpackage/dist/dev/.sourcemap/mp-weixin/pages/activity/duiduipeng/index.js.map
vendored
Normal file
1
unpackage/dist/dev/.sourcemap/mp-weixin/pages/activity/wuxianshang/index.js.map
vendored
Normal file
1
unpackage/dist/dev/.sourcemap/mp-weixin/pages/activity/yifanshang/index.js.map
vendored
Normal file
1
unpackage/dist/dev/.sourcemap/mp-weixin/pages/address/edit.js.map
vendored
Normal file
1
unpackage/dist/dev/.sourcemap/mp-weixin/pages/address/index.js.map
vendored
Normal file
1
unpackage/dist/dev/.sourcemap/mp-weixin/pages/agreement/purchase.js.map
vendored
Normal file
1
unpackage/dist/dev/.sourcemap/mp-weixin/pages/agreement/user.js.map
vendored
Normal file
1
unpackage/dist/dev/.sourcemap/mp-weixin/pages/cabinet/index.js.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sources":["pages/cabinet/index.vue","../../迅雷下载/HBuilderX.4.57.2025032507/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvY2FiaW5ldC9pbmRleC52dWU"],"sourcesContent":["<template>\n <view class=\"wrap\">货柜</view>\n</template>\n\n<script setup>\nimport { onShow } from '@dcloudio/uni-app'\nonShow(() => {\n const token = uni.getStorageSync('token')\n const phoneBound = !!uni.getStorageSync('phone_bound')\n console.log('cabinet onShow token:', token, 'isLogin:', !!token, 'phoneBound:', phoneBound)\n if (!token || !phoneBound) {\n uni.showModal({\n title: '提示',\n content: '请先登录并绑定手机号',\n confirmText: '去登录',\n success: (res) => {\n if (res.confirm) {\n uni.navigateTo({ url: '/pages/login/index' })\n }\n }\n })\n }\n})\n</script>\n\n<style scoped>\n.wrap { padding: 40rpx }\n</style>","import MiniProgramPage from 'D:/project/app_client/pages/cabinet/index.vue'\nwx.createPage(MiniProgramPage)"],"names":["onShow","uni"],"mappings":";;;;;AAMAA,kBAAAA,OAAO,MAAM;AACX,YAAM,QAAQC,cAAAA,MAAI,eAAe,OAAO;AACxC,YAAM,aAAa,CAAC,CAACA,oBAAI,eAAe,aAAa;AACrDA,oBAAAA,MAAY,MAAA,OAAA,iCAAA,yBAAyB,OAAO,YAAY,CAAC,CAAC,OAAO,eAAe,UAAU;AAC1F,UAAI,CAAC,SAAS,CAAC,YAAY;AACzBA,sBAAAA,MAAI,UAAU;AAAA,UACZ,OAAO;AAAA,UACP,SAAS;AAAA,UACT,aAAa;AAAA,UACb,SAAS,CAAC,QAAQ;AAChB,gBAAI,IAAI,SAAS;AACfA,4BAAAA,MAAI,WAAW,EAAE,KAAK,qBAAoB,CAAE;AAAA,YAC7C;AAAA,UACF;AAAA,QACP,CAAK;AAAA,MACF;AAAA,IACH,CAAC;;;;;;;ACrBD,GAAG,WAAW,eAAe;"}
|
||||
1
unpackage/dist/dev/.sourcemap/mp-weixin/pages/help/index.js.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sources":["pages/help/index.vue","../../迅雷下载/HBuilderX.4.57.2025032507/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvaGVscC9pbmRleC52dWU"],"sourcesContent":["<template>\n <view class=\"wrap\">\n <view class=\"card\" @click=\"toUser\">\n <view class=\"title\">用户协议</view>\n <view class=\"desc\">查看平台使用条款与隐私说明</view>\n </view>\n <view class=\"card\" @click=\"toPurchase\">\n <view class=\"title\">购买协议</view>\n <view class=\"desc\">查看盲盒购买规则与售后政策</view>\n </view>\n </view>\n</template>\n\n<script>\nexport default {\n methods: {\n toUser() { uni.navigateTo({ url: '/pages/agreement/user' }) },\n toPurchase() { uni.navigateTo({ url: '/pages/agreement/purchase' }) }\n }\n}\n</script>\n\n<style scoped>\n.wrap { padding: 24rpx }\n.card { background: #fff; border-radius: 12rpx; padding: 24rpx; margin-bottom: 16rpx; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04) }\n.title { font-size: 30rpx; font-weight: 600 }\n.desc { margin-top: 8rpx; font-size: 24rpx; color: #666 }\n</style>","import MiniProgramPage from 'D:/project/app_client/pages/help/index.vue'\nwx.createPage(MiniProgramPage)"],"names":["uni"],"mappings":";;AAcA,MAAK,YAAU;AAAA,EACb,SAAS;AAAA,IACP,SAAS;AAAEA,oBAAAA,MAAI,WAAW,EAAE,KAAK,wBAAsB,CAAG;AAAA,IAAG;AAAA,IAC7D,aAAa;AAAEA,oBAAAA,MAAI,WAAW,EAAE,KAAK,4BAA4B,CAAC;AAAA,IAAE;AAAA,EACtE;AACF;;;;;;;;AClBA,GAAG,WAAW,eAAe;"}
|
||||
1
unpackage/dist/dev/.sourcemap/mp-weixin/pages/index/index.js.map
vendored
Normal file
1
unpackage/dist/dev/.sourcemap/mp-weixin/pages/login/index.js.map
vendored
Normal file
1
unpackage/dist/dev/.sourcemap/mp-weixin/pages/mine/index.js.map
vendored
Normal file
1
unpackage/dist/dev/.sourcemap/mp-weixin/pages/orders/index.js.map
vendored
Normal file
1
unpackage/dist/dev/.sourcemap/mp-weixin/pages/points/index.js.map
vendored
Normal file
1
unpackage/dist/dev/.sourcemap/mp-weixin/pages/shop/index.js.map
vendored
Normal file
1
unpackage/dist/dev/.sourcemap/mp-weixin/utils/request.js.map
vendored
Normal file
63
unpackage/dist/dev/mp-weixin/api/appUser.js
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
"use strict";
|
||||
const utils_request = require("../utils/request.js");
|
||||
function wechatLogin(code, invite_code) {
|
||||
const data = invite_code ? { code, invite_code } : { code };
|
||||
return utils_request.request({ url: "/api/app/users/weixin/login", method: "POST", data });
|
||||
}
|
||||
function bindPhone(user_id, code, extraHeader = {}) {
|
||||
return utils_request.authRequest({ url: `/api/app/users/${user_id}/phone/bind`, method: "POST", data: { code }, header: extraHeader });
|
||||
}
|
||||
function getUserStats(user_id) {
|
||||
return utils_request.authRequest({ url: `/api/app/users/${user_id}/stats`, method: "GET" });
|
||||
}
|
||||
function getPointsBalance(user_id) {
|
||||
return utils_request.authRequest({ url: `/api/app/users/${user_id}/points/balance`, method: "GET" });
|
||||
}
|
||||
function getPointsRecords(user_id, page = 1, page_size = 20) {
|
||||
return utils_request.authRequest({ url: `/api/app/users/${user_id}/points`, method: "GET", data: { page, page_size } });
|
||||
}
|
||||
function getOrders(user_id, status, page = 1, page_size = 20) {
|
||||
const data = { page, page_size };
|
||||
if (status)
|
||||
data.status = status;
|
||||
return utils_request.authRequest({ url: `/api/app/users/${user_id}/orders`, method: "GET", data });
|
||||
}
|
||||
function listAddresses(user_id) {
|
||||
return utils_request.authRequest({ url: `/api/app/users/${user_id}/addresses`, method: "GET" });
|
||||
}
|
||||
function addAddress(user_id, payload) {
|
||||
return utils_request.authRequest({ url: `/api/app/users/${user_id}/addresses`, method: "POST", data: payload });
|
||||
}
|
||||
function updateAddress(user_id, address_id, payload) {
|
||||
return utils_request.authRequest({ url: `/api/app/users/${user_id}/addresses/${address_id}`, method: "PUT", data: payload });
|
||||
}
|
||||
function deleteAddress(user_id, address_id) {
|
||||
return utils_request.authRequest({ url: `/api/app/users/${user_id}/addresses/${address_id}`, method: "DELETE" });
|
||||
}
|
||||
function setDefaultAddress(user_id, address_id) {
|
||||
return utils_request.authRequest({ url: `/api/app/users/${user_id}/addresses/${address_id}/default`, method: "PUT" });
|
||||
}
|
||||
function getActivityDetail(activity_id) {
|
||||
return utils_request.authRequest({ url: `/api/app/activities/${activity_id}`, method: "GET" });
|
||||
}
|
||||
function getActivityIssues(activity_id) {
|
||||
return utils_request.authRequest({ url: `/api/app/activities/${activity_id}/issues`, method: "GET" });
|
||||
}
|
||||
function getActivityIssueRewards(activity_id, issue_id) {
|
||||
return utils_request.authRequest({ url: `/api/app/activities/${activity_id}/issues/${issue_id}/rewards`, method: "GET" });
|
||||
}
|
||||
exports.addAddress = addAddress;
|
||||
exports.bindPhone = bindPhone;
|
||||
exports.deleteAddress = deleteAddress;
|
||||
exports.getActivityDetail = getActivityDetail;
|
||||
exports.getActivityIssueRewards = getActivityIssueRewards;
|
||||
exports.getActivityIssues = getActivityIssues;
|
||||
exports.getOrders = getOrders;
|
||||
exports.getPointsBalance = getPointsBalance;
|
||||
exports.getPointsRecords = getPointsRecords;
|
||||
exports.getUserStats = getUserStats;
|
||||
exports.listAddresses = listAddresses;
|
||||
exports.setDefaultAddress = setDefaultAddress;
|
||||
exports.updateAddress = updateAddress;
|
||||
exports.wechatLogin = wechatLogin;
|
||||
//# sourceMappingURL=../../.sourcemap/mp-weixin/api/appUser.js.map
|
||||
40
unpackage/dist/dev/mp-weixin/app.js
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
||||
const common_vendor = require("./common/vendor.js");
|
||||
if (!Math) {
|
||||
"./pages/index/index.js";
|
||||
"./pages/login/index.js";
|
||||
"./pages/shop/index.js";
|
||||
"./pages/cabinet/index.js";
|
||||
"./pages/mine/index.js";
|
||||
"./pages/points/index.js";
|
||||
"./pages/orders/index.js";
|
||||
"./pages/address/index.js";
|
||||
"./pages/address/edit.js";
|
||||
"./pages/help/index.js";
|
||||
"./pages/agreement/user.js";
|
||||
"./pages/agreement/purchase.js";
|
||||
"./pages/activity/yifanshang/index.js";
|
||||
"./pages/activity/wuxianshang/index.js";
|
||||
"./pages/activity/duiduipeng/index.js";
|
||||
}
|
||||
const _sfc_main = {
|
||||
onLaunch: function() {
|
||||
common_vendor.index.__f__("log", "at App.vue:4", "App Launch");
|
||||
},
|
||||
onShow: function() {
|
||||
common_vendor.index.__f__("log", "at App.vue:7", "App Show");
|
||||
},
|
||||
onHide: function() {
|
||||
common_vendor.index.__f__("log", "at App.vue:10", "App Hide");
|
||||
}
|
||||
};
|
||||
function createApp() {
|
||||
const app = common_vendor.createSSRApp(_sfc_main);
|
||||
return {
|
||||
app
|
||||
};
|
||||
}
|
||||
createApp().app.mount("#app");
|
||||
exports.createApp = createApp;
|
||||
//# sourceMappingURL=../.sourcemap/mp-weixin/app.js.map
|
||||
58
unpackage/dist/dev/mp-weixin/app.json
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
{
|
||||
"pages": [
|
||||
"pages/index/index",
|
||||
"pages/login/index",
|
||||
"pages/shop/index",
|
||||
"pages/cabinet/index",
|
||||
"pages/mine/index",
|
||||
"pages/points/index",
|
||||
"pages/orders/index",
|
||||
"pages/address/index",
|
||||
"pages/address/edit",
|
||||
"pages/help/index",
|
||||
"pages/agreement/user",
|
||||
"pages/agreement/purchase",
|
||||
"pages/activity/yifanshang/index",
|
||||
"pages/activity/wuxianshang/index",
|
||||
"pages/activity/duiduipeng/index"
|
||||
],
|
||||
"window": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "uni-app",
|
||||
"navigationBarBackgroundColor": "#F8F8F8",
|
||||
"backgroundColor": "#F8F8F8"
|
||||
},
|
||||
"tabBar": {
|
||||
"color": "#7A7E83",
|
||||
"selectedColor": "#007AFF",
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"borderStyle": "black",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/index/index",
|
||||
"text": "首页",
|
||||
"iconPath": "static/tab/home.png",
|
||||
"selectedIconPath": "static/tab/home_active.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/shop/index",
|
||||
"text": "商城",
|
||||
"iconPath": "static/tab/shop.png",
|
||||
"selectedIconPath": "static/tab/shop_active.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/cabinet/index",
|
||||
"text": "货柜",
|
||||
"iconPath": "static/tab/box.png",
|
||||
"selectedIconPath": "static/tab/box_active.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/mine/index",
|
||||
"text": "我的",
|
||||
"iconPath": "static/tab/profile.png",
|
||||
"selectedIconPath": "static/tab/profile_active.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
"usingComponents": {}
|
||||
}
|
||||
3
unpackage/dist/dev/mp-weixin/app.wxss
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
/*每个页面公共css */
|
||||
page{--status-bar-height:25px;--top-window-height:0px;--window-top:0px;--window-bottom:0px;--window-left:0px;--window-right:0px;--window-magin:0px}[data-c-h="true"]{display: none !important;}
|
||||
4
unpackage/dist/dev/mp-weixin/common/assets.js
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
"use strict";
|
||||
const _imports_0 = "/static/logo.png";
|
||||
exports._imports_0 = _imports_0;
|
||||
//# sourceMappingURL=../../.sourcemap/mp-weixin/common/assets.js.map
|
||||
7905
unpackage/dist/dev/mp-weixin/common/vendor.js
vendored
Normal file
137
unpackage/dist/dev/mp-weixin/pages/activity/duiduipeng/index.js
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
"use strict";
|
||||
const common_vendor = require("../../../common/vendor.js");
|
||||
const api_appUser = require("../../../api/appUser.js");
|
||||
const _sfc_main = {
|
||||
__name: "index",
|
||||
setup(__props) {
|
||||
const detail = common_vendor.ref({});
|
||||
const statusText = common_vendor.ref("");
|
||||
const issues = common_vendor.ref([]);
|
||||
const rewardsMap = common_vendor.ref({});
|
||||
function statusToText(s) {
|
||||
if (s === 1)
|
||||
return "进行中";
|
||||
if (s === 0)
|
||||
return "未开始";
|
||||
if (s === 2)
|
||||
return "已结束";
|
||||
return String(s || "");
|
||||
}
|
||||
async function fetchDetail(id) {
|
||||
const data = await api_appUser.getActivityDetail(id);
|
||||
detail.value = data || {};
|
||||
statusText.value = statusToText(detail.value.status);
|
||||
}
|
||||
function unwrap(list) {
|
||||
if (Array.isArray(list))
|
||||
return list;
|
||||
const obj = list || {};
|
||||
const data = obj.data || {};
|
||||
const arr = obj.list || obj.items || data.list || data.items || data;
|
||||
return Array.isArray(arr) ? arr : [];
|
||||
}
|
||||
function normalizeIssues(list) {
|
||||
const arr = unwrap(list);
|
||||
return arr.map((i, idx) => ({
|
||||
id: i.id ?? String(idx),
|
||||
title: i.title ?? i.name ?? "",
|
||||
no: i.no ?? i.index ?? i.issue_no ?? i.issue_number ?? null,
|
||||
status_text: i.status_text ?? (i.status === 1 ? "进行中" : i.status === 0 ? "未开始" : i.status === 2 ? "已结束" : "")
|
||||
}));
|
||||
}
|
||||
function normalizeRewards(list) {
|
||||
const arr = unwrap(list);
|
||||
return arr.map((i, idx) => ({
|
||||
id: i.id ?? String(idx),
|
||||
title: i.title ?? i.name ?? "",
|
||||
image: i.image ?? i.img ?? i.pic ?? i.banner ?? "",
|
||||
odds: i.odds ?? i.rate ?? i.probability ?? i.prob ?? "",
|
||||
rarity: i.rarity ?? i.rarity_name ?? ""
|
||||
}));
|
||||
}
|
||||
async function fetchRewardsForIssues(activityId) {
|
||||
const list = issues.value || [];
|
||||
const promises = list.map((it) => api_appUser.getActivityIssueRewards(activityId, it.id));
|
||||
const results = await Promise.allSettled(promises);
|
||||
results.forEach((res, i) => {
|
||||
const issueId = list[i] && list[i].id;
|
||||
if (!issueId)
|
||||
return;
|
||||
const value = res.status === "fulfilled" ? normalizeRewards(res.value) : [];
|
||||
rewardsMap.value = { ...rewardsMap.value || {}, [issueId]: value };
|
||||
});
|
||||
}
|
||||
async function fetchIssues(id) {
|
||||
const data = await api_appUser.getActivityIssues(id);
|
||||
issues.value = normalizeIssues(data);
|
||||
await fetchRewardsForIssues(id);
|
||||
}
|
||||
function onPreviewBanner() {
|
||||
const url = detail.value.banner || "";
|
||||
if (url)
|
||||
common_vendor.index.previewImage({ urls: [url], current: url });
|
||||
}
|
||||
function onParticipate() {
|
||||
common_vendor.index.showToast({ title: "功能待接入", icon: "none" });
|
||||
}
|
||||
common_vendor.onLoad((opts) => {
|
||||
const id = opts && opts.id || "";
|
||||
if (id) {
|
||||
fetchDetail(id);
|
||||
fetchIssues(id);
|
||||
}
|
||||
});
|
||||
return (_ctx, _cache) => {
|
||||
return common_vendor.e({
|
||||
a: detail.value.banner
|
||||
}, detail.value.banner ? {
|
||||
b: detail.value.banner
|
||||
} : {}, {
|
||||
c: common_vendor.t(detail.value.name || detail.value.title || "-"),
|
||||
d: common_vendor.t(detail.value.category_name || "对对碰"),
|
||||
e: detail.value.price_draw !== void 0
|
||||
}, detail.value.price_draw !== void 0 ? {
|
||||
f: common_vendor.t(detail.value.price_draw)
|
||||
} : {}, {
|
||||
g: detail.value.status !== void 0
|
||||
}, detail.value.status !== void 0 ? {
|
||||
h: common_vendor.t(statusText.value)
|
||||
} : {}, {
|
||||
i: common_vendor.o(onPreviewBanner),
|
||||
j: common_vendor.o(onParticipate),
|
||||
k: issues.value.length
|
||||
}, issues.value.length ? {
|
||||
l: common_vendor.f(issues.value, (it, k0, i0) => {
|
||||
return common_vendor.e({
|
||||
a: common_vendor.t(it.title || "第" + (it.no || it.index || it.issue_no || "-") + "期"),
|
||||
b: it.status_text
|
||||
}, it.status_text ? {
|
||||
c: common_vendor.t(it.status_text)
|
||||
} : {}, {
|
||||
d: rewardsMap.value[it.id] && rewardsMap.value[it.id].length
|
||||
}, rewardsMap.value[it.id] && rewardsMap.value[it.id].length ? {
|
||||
e: common_vendor.f(rewardsMap.value[it.id], (rw, k1, i1) => {
|
||||
return common_vendor.e({
|
||||
a: rw.image
|
||||
}, rw.image ? {
|
||||
b: rw.image
|
||||
} : {}, {
|
||||
c: common_vendor.t(rw.title),
|
||||
d: rw.rarity || rw.odds
|
||||
}, rw.rarity || rw.odds ? {
|
||||
e: common_vendor.t([rw.rarity, rw.odds].filter(Boolean).join(" · "))
|
||||
} : {}, {
|
||||
f: rw.id
|
||||
});
|
||||
})
|
||||
} : {}, {
|
||||
f: it.id
|
||||
});
|
||||
})
|
||||
} : {});
|
||||
};
|
||||
}
|
||||
};
|
||||
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-e6d1f6ed"]]);
|
||||
wx.createPage(MiniProgramPage);
|
||||
//# sourceMappingURL=../../../../.sourcemap/mp-weixin/pages/activity/duiduipeng/index.js.map
|
||||
4
unpackage/dist/dev/mp-weixin/pages/activity/duiduipeng/index.json
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "对对碰",
|
||||
"usingComponents": {}
|
||||
}
|
||||
1
unpackage/dist/dev/mp-weixin/pages/activity/duiduipeng/index.wxml
vendored
Normal file
@ -0,0 +1 @@
|
||||
<scroll-view class="page data-v-e6d1f6ed" scroll-y><view wx:if="{{a}}" class="banner data-v-e6d1f6ed"><image class="banner-img data-v-e6d1f6ed" src="{{b}}" mode="widthFix"/></view><view class="header data-v-e6d1f6ed"><view class="title data-v-e6d1f6ed">{{c}}</view><view class="meta data-v-e6d1f6ed">分类:{{d}}</view><view wx:if="{{e}}" class="meta data-v-e6d1f6ed">参与价:¥{{f}}</view><view wx:if="{{g}}" class="meta data-v-e6d1f6ed">状态:{{h}}</view></view><view class="actions data-v-e6d1f6ed"><button class="btn data-v-e6d1f6ed" bindtap="{{i}}">查看图片</button><button class="btn primary data-v-e6d1f6ed" bindtap="{{j}}">立即参与</button></view><view class="issues data-v-e6d1f6ed"><view class="issues-title data-v-e6d1f6ed">期数</view><view wx:if="{{k}}" class="issues-list data-v-e6d1f6ed"><view wx:for="{{l}}" wx:for-item="it" wx:key="f" class="issue-item data-v-e6d1f6ed"><text class="issue-title data-v-e6d1f6ed">{{it.a}}</text><text wx:if="{{it.b}}" class="issue-status data-v-e6d1f6ed">{{it.c}}</text><view wx:if="{{it.d}}" class="rewards data-v-e6d1f6ed"><view wx:for="{{it.e}}" wx:for-item="rw" wx:key="f" class="reward data-v-e6d1f6ed"><image wx:if="{{rw.a}}" class="reward-img data-v-e6d1f6ed" src="{{rw.b}}" mode="aspectFill"/><view class="reward-texts data-v-e6d1f6ed"><text class="reward-title data-v-e6d1f6ed">{{rw.c}}</text><text wx:if="{{rw.d}}" class="reward-meta data-v-e6d1f6ed">{{rw.e}}</text></view></view></view><view wx:else class="rewards-empty data-v-e6d1f6ed">暂无奖励配置</view></view></view><view wx:else class="issues-empty data-v-e6d1f6ed">暂无期数</view></view></scroll-view>
|
||||
49
unpackage/dist/dev/mp-weixin/pages/activity/duiduipeng/index.wxss
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
|
||||
.page.data-v-e6d1f6ed { height: 100vh
|
||||
}
|
||||
.banner.data-v-e6d1f6ed { padding: 24rpx
|
||||
}
|
||||
.banner-img.data-v-e6d1f6ed { width: 100%
|
||||
}
|
||||
.header.data-v-e6d1f6ed { padding: 0 24rpx
|
||||
}
|
||||
.title.data-v-e6d1f6ed { font-size: 36rpx; font-weight: 700
|
||||
}
|
||||
.meta.data-v-e6d1f6ed { margin-top: 8rpx; font-size: 26rpx; color: #666
|
||||
}
|
||||
.actions.data-v-e6d1f6ed { display: flex; padding: 24rpx; gap: 16rpx
|
||||
}
|
||||
.btn.data-v-e6d1f6ed { flex: 1
|
||||
}
|
||||
.primary.data-v-e6d1f6ed { background-color: #007AFF; color: #fff
|
||||
}
|
||||
.issues.data-v-e6d1f6ed { background: #fff; border-radius: 12rpx; margin: 0 24rpx 24rpx; padding: 16rpx
|
||||
}
|
||||
.issues-title.data-v-e6d1f6ed { font-size: 30rpx; font-weight: 600; margin-bottom: 12rpx
|
||||
}
|
||||
.issues-list.data-v-e6d1f6ed {
|
||||
}
|
||||
.issue-item.data-v-e6d1f6ed { display: flex; justify-content: space-between; padding: 12rpx 0; border-bottom: 1rpx solid #f0f0f0
|
||||
}
|
||||
.issue-item.data-v-e6d1f6ed:last-child { border-bottom: 0
|
||||
}
|
||||
.issue-title.data-v-e6d1f6ed { font-size: 26rpx
|
||||
}
|
||||
.issue-status.data-v-e6d1f6ed { font-size: 24rpx; color: #666
|
||||
}
|
||||
.rewards.data-v-e6d1f6ed { width: 100%; margin-top: 12rpx
|
||||
}
|
||||
.reward.data-v-e6d1f6ed { display: flex; align-items: center; margin-bottom: 8rpx
|
||||
}
|
||||
.reward-img.data-v-e6d1f6ed { width: 80rpx; height: 80rpx; border-radius: 8rpx; margin-right: 12rpx; background: #f5f5f5
|
||||
}
|
||||
.reward-texts.data-v-e6d1f6ed { display: flex; flex-direction: column
|
||||
}
|
||||
.reward-title.data-v-e6d1f6ed { font-size: 26rpx
|
||||
}
|
||||
.reward-meta.data-v-e6d1f6ed { font-size: 22rpx; color: #888
|
||||
}
|
||||
.rewards-empty.data-v-e6d1f6ed { font-size: 24rpx; color: #999
|
||||
}
|
||||
.issues-empty.data-v-e6d1f6ed { font-size: 24rpx; color: #999
|
||||
}
|
||||
137
unpackage/dist/dev/mp-weixin/pages/activity/wuxianshang/index.js
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
"use strict";
|
||||
const common_vendor = require("../../../common/vendor.js");
|
||||
const api_appUser = require("../../../api/appUser.js");
|
||||
const _sfc_main = {
|
||||
__name: "index",
|
||||
setup(__props) {
|
||||
const detail = common_vendor.ref({});
|
||||
const statusText = common_vendor.ref("");
|
||||
const issues = common_vendor.ref([]);
|
||||
const rewardsMap = common_vendor.ref({});
|
||||
function statusToText(s) {
|
||||
if (s === 1)
|
||||
return "进行中";
|
||||
if (s === 0)
|
||||
return "未开始";
|
||||
if (s === 2)
|
||||
return "已结束";
|
||||
return String(s || "");
|
||||
}
|
||||
async function fetchDetail(id) {
|
||||
const data = await api_appUser.getActivityDetail(id);
|
||||
detail.value = data || {};
|
||||
statusText.value = statusToText(detail.value.status);
|
||||
}
|
||||
function unwrap(list) {
|
||||
if (Array.isArray(list))
|
||||
return list;
|
||||
const obj = list || {};
|
||||
const data = obj.data || {};
|
||||
const arr = obj.list || obj.items || data.list || data.items || data;
|
||||
return Array.isArray(arr) ? arr : [];
|
||||
}
|
||||
function normalizeIssues(list) {
|
||||
const arr = unwrap(list);
|
||||
return arr.map((i, idx) => ({
|
||||
id: i.id ?? String(idx),
|
||||
title: i.title ?? i.name ?? "",
|
||||
no: i.no ?? i.index ?? i.issue_no ?? i.issue_number ?? null,
|
||||
status_text: i.status_text ?? (i.status === 1 ? "进行中" : i.status === 0 ? "未开始" : i.status === 2 ? "已结束" : "")
|
||||
}));
|
||||
}
|
||||
function normalizeRewards(list) {
|
||||
const arr = unwrap(list);
|
||||
return arr.map((i, idx) => ({
|
||||
id: i.id ?? String(idx),
|
||||
title: i.title ?? i.name ?? "",
|
||||
image: i.image ?? i.img ?? i.pic ?? i.banner ?? "",
|
||||
odds: i.odds ?? i.rate ?? i.probability ?? i.prob ?? "",
|
||||
rarity: i.rarity ?? i.rarity_name ?? ""
|
||||
}));
|
||||
}
|
||||
async function fetchRewardsForIssues(activityId) {
|
||||
const list = issues.value || [];
|
||||
const promises = list.map((it) => api_appUser.getActivityIssueRewards(activityId, it.id));
|
||||
const results = await Promise.allSettled(promises);
|
||||
results.forEach((res, i) => {
|
||||
const issueId = list[i] && list[i].id;
|
||||
if (!issueId)
|
||||
return;
|
||||
const value = res.status === "fulfilled" ? normalizeRewards(res.value) : [];
|
||||
rewardsMap.value = { ...rewardsMap.value || {}, [issueId]: value };
|
||||
});
|
||||
}
|
||||
async function fetchIssues(id) {
|
||||
const data = await api_appUser.getActivityIssues(id);
|
||||
issues.value = normalizeIssues(data);
|
||||
await fetchRewardsForIssues(id);
|
||||
}
|
||||
function onPreviewBanner() {
|
||||
const url = detail.value.banner || "";
|
||||
if (url)
|
||||
common_vendor.index.previewImage({ urls: [url], current: url });
|
||||
}
|
||||
function onParticipate() {
|
||||
common_vendor.index.showToast({ title: "功能待接入", icon: "none" });
|
||||
}
|
||||
common_vendor.onLoad((opts) => {
|
||||
const id = opts && opts.id || "";
|
||||
if (id) {
|
||||
fetchDetail(id);
|
||||
fetchIssues(id);
|
||||
}
|
||||
});
|
||||
return (_ctx, _cache) => {
|
||||
return common_vendor.e({
|
||||
a: detail.value.banner
|
||||
}, detail.value.banner ? {
|
||||
b: detail.value.banner
|
||||
} : {}, {
|
||||
c: common_vendor.t(detail.value.name || detail.value.title || "-"),
|
||||
d: common_vendor.t(detail.value.category_name || "无限赏"),
|
||||
e: detail.value.price_draw !== void 0
|
||||
}, detail.value.price_draw !== void 0 ? {
|
||||
f: common_vendor.t(detail.value.price_draw)
|
||||
} : {}, {
|
||||
g: detail.value.status !== void 0
|
||||
}, detail.value.status !== void 0 ? {
|
||||
h: common_vendor.t(statusText.value)
|
||||
} : {}, {
|
||||
i: common_vendor.o(onPreviewBanner),
|
||||
j: common_vendor.o(onParticipate),
|
||||
k: issues.value.length
|
||||
}, issues.value.length ? {
|
||||
l: common_vendor.f(issues.value, (it, k0, i0) => {
|
||||
return common_vendor.e({
|
||||
a: common_vendor.t(it.title || "第" + (it.no || it.index || it.issue_no || "-") + "期"),
|
||||
b: it.status_text
|
||||
}, it.status_text ? {
|
||||
c: common_vendor.t(it.status_text)
|
||||
} : {}, {
|
||||
d: rewardsMap.value[it.id] && rewardsMap.value[it.id].length
|
||||
}, rewardsMap.value[it.id] && rewardsMap.value[it.id].length ? {
|
||||
e: common_vendor.f(rewardsMap.value[it.id], (rw, k1, i1) => {
|
||||
return common_vendor.e({
|
||||
a: rw.image
|
||||
}, rw.image ? {
|
||||
b: rw.image
|
||||
} : {}, {
|
||||
c: common_vendor.t(rw.title),
|
||||
d: rw.rarity || rw.odds
|
||||
}, rw.rarity || rw.odds ? {
|
||||
e: common_vendor.t([rw.rarity, rw.odds].filter(Boolean).join(" · "))
|
||||
} : {}, {
|
||||
f: rw.id
|
||||
});
|
||||
})
|
||||
} : {}, {
|
||||
f: it.id
|
||||
});
|
||||
})
|
||||
} : {});
|
||||
};
|
||||
}
|
||||
};
|
||||
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-b5fdbf1f"]]);
|
||||
wx.createPage(MiniProgramPage);
|
||||
//# sourceMappingURL=../../../../.sourcemap/mp-weixin/pages/activity/wuxianshang/index.js.map
|
||||
4
unpackage/dist/dev/mp-weixin/pages/activity/wuxianshang/index.json
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "无限赏",
|
||||
"usingComponents": {}
|
||||
}
|
||||
1
unpackage/dist/dev/mp-weixin/pages/activity/wuxianshang/index.wxml
vendored
Normal file
@ -0,0 +1 @@
|
||||
<scroll-view class="page data-v-b5fdbf1f" scroll-y><view wx:if="{{a}}" class="banner data-v-b5fdbf1f"><image class="banner-img data-v-b5fdbf1f" src="{{b}}" mode="widthFix"/></view><view class="header data-v-b5fdbf1f"><view class="title data-v-b5fdbf1f">{{c}}</view><view class="meta data-v-b5fdbf1f">分类:{{d}}</view><view wx:if="{{e}}" class="meta data-v-b5fdbf1f">单次抽选:¥{{f}}</view><view wx:if="{{g}}" class="meta data-v-b5fdbf1f">状态:{{h}}</view></view><view class="actions data-v-b5fdbf1f"><button class="btn data-v-b5fdbf1f" bindtap="{{i}}">查看图片</button><button class="btn primary data-v-b5fdbf1f" bindtap="{{j}}">立即参与</button></view><view class="issues data-v-b5fdbf1f"><view class="issues-title data-v-b5fdbf1f">期数</view><view wx:if="{{k}}" class="issues-list data-v-b5fdbf1f"><view wx:for="{{l}}" wx:for-item="it" wx:key="f" class="issue-item data-v-b5fdbf1f"><text class="issue-title data-v-b5fdbf1f">{{it.a}}</text><text wx:if="{{it.b}}" class="issue-status data-v-b5fdbf1f">{{it.c}}</text><view wx:if="{{it.d}}" class="rewards data-v-b5fdbf1f"><view wx:for="{{it.e}}" wx:for-item="rw" wx:key="f" class="reward data-v-b5fdbf1f"><image wx:if="{{rw.a}}" class="reward-img data-v-b5fdbf1f" src="{{rw.b}}" mode="aspectFill"/><view class="reward-texts data-v-b5fdbf1f"><text class="reward-title data-v-b5fdbf1f">{{rw.c}}</text><text wx:if="{{rw.d}}" class="reward-meta data-v-b5fdbf1f">{{rw.e}}</text></view></view></view><view wx:else class="rewards-empty data-v-b5fdbf1f">暂无奖励配置</view></view></view><view wx:else class="issues-empty data-v-b5fdbf1f">暂无期数</view></view></scroll-view>
|
||||
49
unpackage/dist/dev/mp-weixin/pages/activity/wuxianshang/index.wxss
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
|
||||
.page.data-v-b5fdbf1f { height: 100vh
|
||||
}
|
||||
.banner.data-v-b5fdbf1f { padding: 24rpx
|
||||
}
|
||||
.banner-img.data-v-b5fdbf1f { width: 100%
|
||||
}
|
||||
.header.data-v-b5fdbf1f { padding: 0 24rpx
|
||||
}
|
||||
.title.data-v-b5fdbf1f { font-size: 36rpx; font-weight: 700
|
||||
}
|
||||
.meta.data-v-b5fdbf1f { margin-top: 8rpx; font-size: 26rpx; color: #666
|
||||
}
|
||||
.actions.data-v-b5fdbf1f { display: flex; padding: 24rpx; gap: 16rpx
|
||||
}
|
||||
.btn.data-v-b5fdbf1f { flex: 1
|
||||
}
|
||||
.primary.data-v-b5fdbf1f { background-color: #007AFF; color: #fff
|
||||
}
|
||||
.issues.data-v-b5fdbf1f { background: #fff; border-radius: 12rpx; margin: 0 24rpx 24rpx; padding: 16rpx
|
||||
}
|
||||
.issues-title.data-v-b5fdbf1f { font-size: 30rpx; font-weight: 600; margin-bottom: 12rpx
|
||||
}
|
||||
.issues-list.data-v-b5fdbf1f {
|
||||
}
|
||||
.issue-item.data-v-b5fdbf1f { display: flex; justify-content: space-between; padding: 12rpx 0; border-bottom: 1rpx solid #f0f0f0
|
||||
}
|
||||
.issue-item.data-v-b5fdbf1f:last-child { border-bottom: 0
|
||||
}
|
||||
.issue-title.data-v-b5fdbf1f { font-size: 26rpx
|
||||
}
|
||||
.issue-status.data-v-b5fdbf1f { font-size: 24rpx; color: #666
|
||||
}
|
||||
.rewards.data-v-b5fdbf1f { width: 100%; margin-top: 12rpx
|
||||
}
|
||||
.reward.data-v-b5fdbf1f { display: flex; align-items: center; margin-bottom: 8rpx
|
||||
}
|
||||
.reward-img.data-v-b5fdbf1f { width: 80rpx; height: 80rpx; border-radius: 8rpx; margin-right: 12rpx; background: #f5f5f5
|
||||
}
|
||||
.reward-texts.data-v-b5fdbf1f { display: flex; flex-direction: column
|
||||
}
|
||||
.reward-title.data-v-b5fdbf1f { font-size: 26rpx
|
||||
}
|
||||
.reward-meta.data-v-b5fdbf1f { font-size: 22rpx; color: #888
|
||||
}
|
||||
.rewards-empty.data-v-b5fdbf1f { font-size: 24rpx; color: #999
|
||||
}
|
||||
.issues-empty.data-v-b5fdbf1f { font-size: 24rpx; color: #999
|
||||
}
|
||||
137
unpackage/dist/dev/mp-weixin/pages/activity/yifanshang/index.js
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
"use strict";
|
||||
const common_vendor = require("../../../common/vendor.js");
|
||||
const api_appUser = require("../../../api/appUser.js");
|
||||
const _sfc_main = {
|
||||
__name: "index",
|
||||
setup(__props) {
|
||||
const detail = common_vendor.ref({});
|
||||
const issues = common_vendor.ref([]);
|
||||
const rewardsMap = common_vendor.ref({});
|
||||
function statusToText(s) {
|
||||
if (s === 1)
|
||||
return "进行中";
|
||||
if (s === 0)
|
||||
return "未开始";
|
||||
if (s === 2)
|
||||
return "已结束";
|
||||
return String(s || "");
|
||||
}
|
||||
const statusText = common_vendor.ref("");
|
||||
async function fetchDetail(id) {
|
||||
const data = await api_appUser.getActivityDetail(id);
|
||||
detail.value = data || {};
|
||||
statusText.value = statusToText(detail.value.status);
|
||||
}
|
||||
function unwrap(list) {
|
||||
if (Array.isArray(list))
|
||||
return list;
|
||||
const obj = list || {};
|
||||
const data = obj.data || {};
|
||||
const arr = obj.list || obj.items || data.list || data.items || data;
|
||||
return Array.isArray(arr) ? arr : [];
|
||||
}
|
||||
function normalizeIssues(list) {
|
||||
const arr = unwrap(list);
|
||||
return arr.map((i, idx) => ({
|
||||
id: i.id ?? String(idx),
|
||||
title: i.title ?? i.name ?? "",
|
||||
no: i.no ?? i.index ?? i.issue_no ?? i.issue_number ?? null,
|
||||
status_text: i.status_text ?? (i.status === 1 ? "进行中" : i.status === 0 ? "未开始" : i.status === 2 ? "已结束" : "")
|
||||
}));
|
||||
}
|
||||
function normalizeRewards(list) {
|
||||
const arr = unwrap(list);
|
||||
return arr.map((i, idx) => ({
|
||||
id: i.id ?? String(idx),
|
||||
title: i.title ?? i.name ?? "",
|
||||
image: i.image ?? i.img ?? i.pic ?? i.banner ?? "",
|
||||
odds: i.odds ?? i.rate ?? i.probability ?? i.prob ?? "",
|
||||
rarity: i.rarity ?? i.rarity_name ?? ""
|
||||
}));
|
||||
}
|
||||
async function fetchRewardsForIssues(activityId) {
|
||||
const list = issues.value || [];
|
||||
const promises = list.map((it) => api_appUser.getActivityIssueRewards(activityId, it.id));
|
||||
const results = await Promise.allSettled(promises);
|
||||
results.forEach((res, i) => {
|
||||
const issueId = list[i] && list[i].id;
|
||||
if (!issueId)
|
||||
return;
|
||||
const value = res.status === "fulfilled" ? normalizeRewards(res.value) : [];
|
||||
rewardsMap.value = { ...rewardsMap.value || {}, [issueId]: value };
|
||||
});
|
||||
}
|
||||
async function fetchIssues(id) {
|
||||
const data = await api_appUser.getActivityIssues(id);
|
||||
issues.value = normalizeIssues(data);
|
||||
await fetchRewardsForIssues(id);
|
||||
}
|
||||
function onPreviewBanner() {
|
||||
const url = detail.value.banner || "";
|
||||
if (url)
|
||||
common_vendor.index.previewImage({ urls: [url], current: url });
|
||||
}
|
||||
function onParticipate() {
|
||||
common_vendor.index.showToast({ title: "功能待接入", icon: "none" });
|
||||
}
|
||||
common_vendor.onLoad((opts) => {
|
||||
const id = opts && opts.id || "";
|
||||
if (id) {
|
||||
fetchDetail(id);
|
||||
fetchIssues(id);
|
||||
}
|
||||
});
|
||||
return (_ctx, _cache) => {
|
||||
return common_vendor.e({
|
||||
a: detail.value.banner
|
||||
}, detail.value.banner ? {
|
||||
b: detail.value.banner
|
||||
} : {}, {
|
||||
c: common_vendor.t(detail.value.name || detail.value.title || "-"),
|
||||
d: common_vendor.t(detail.value.category_name || "一番赏"),
|
||||
e: detail.value.price_draw !== void 0
|
||||
}, detail.value.price_draw !== void 0 ? {
|
||||
f: common_vendor.t(detail.value.price_draw)
|
||||
} : {}, {
|
||||
g: detail.value.status !== void 0
|
||||
}, detail.value.status !== void 0 ? {
|
||||
h: common_vendor.t(statusText.value)
|
||||
} : {}, {
|
||||
i: common_vendor.o(onPreviewBanner),
|
||||
j: common_vendor.o(onParticipate),
|
||||
k: issues.value.length
|
||||
}, issues.value.length ? {
|
||||
l: common_vendor.f(issues.value, (it, k0, i0) => {
|
||||
return common_vendor.e({
|
||||
a: common_vendor.t(it.title || "第" + (it.no || it.index || it.issue_no || "-") + "期"),
|
||||
b: it.status_text
|
||||
}, it.status_text ? {
|
||||
c: common_vendor.t(it.status_text)
|
||||
} : {}, {
|
||||
d: rewardsMap.value[it.id] && rewardsMap.value[it.id].length
|
||||
}, rewardsMap.value[it.id] && rewardsMap.value[it.id].length ? {
|
||||
e: common_vendor.f(rewardsMap.value[it.id], (rw, k1, i1) => {
|
||||
return common_vendor.e({
|
||||
a: rw.image
|
||||
}, rw.image ? {
|
||||
b: rw.image
|
||||
} : {}, {
|
||||
c: common_vendor.t(rw.title),
|
||||
d: rw.rarity || rw.odds
|
||||
}, rw.rarity || rw.odds ? {
|
||||
e: common_vendor.t([rw.rarity, rw.odds].filter(Boolean).join(" · "))
|
||||
} : {}, {
|
||||
f: rw.id
|
||||
});
|
||||
})
|
||||
} : {}, {
|
||||
f: it.id
|
||||
});
|
||||
})
|
||||
} : {});
|
||||
};
|
||||
}
|
||||
};
|
||||
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-3c32883e"]]);
|
||||
wx.createPage(MiniProgramPage);
|
||||
//# sourceMappingURL=../../../../.sourcemap/mp-weixin/pages/activity/yifanshang/index.js.map
|
||||
4
unpackage/dist/dev/mp-weixin/pages/activity/yifanshang/index.json
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "一番赏",
|
||||
"usingComponents": {}
|
||||
}
|
||||
1
unpackage/dist/dev/mp-weixin/pages/activity/yifanshang/index.wxml
vendored
Normal file
@ -0,0 +1 @@
|
||||
<scroll-view class="page data-v-3c32883e" scroll-y><view wx:if="{{a}}" class="banner data-v-3c32883e"><image class="banner-img data-v-3c32883e" src="{{b}}" mode="widthFix"/></view><view class="header data-v-3c32883e"><view class="title data-v-3c32883e">{{c}}</view><view class="meta data-v-3c32883e">分类:{{d}}</view><view wx:if="{{e}}" class="meta data-v-3c32883e">抽选价:¥{{f}}</view><view wx:if="{{g}}" class="meta data-v-3c32883e">状态:{{h}}</view></view><view class="actions data-v-3c32883e"><button class="btn data-v-3c32883e" bindtap="{{i}}">查看图片</button><button class="btn primary data-v-3c32883e" bindtap="{{j}}">立即参与</button></view><view class="issues data-v-3c32883e"><view class="issues-title data-v-3c32883e">期数</view><view wx:if="{{k}}" class="issues-list data-v-3c32883e"><view wx:for="{{l}}" wx:for-item="it" wx:key="f" class="issue-item data-v-3c32883e"><text class="issue-title data-v-3c32883e">{{it.a}}</text><text wx:if="{{it.b}}" class="issue-status data-v-3c32883e">{{it.c}}</text><view wx:if="{{it.d}}" class="rewards data-v-3c32883e"><view wx:for="{{it.e}}" wx:for-item="rw" wx:key="f" class="reward data-v-3c32883e"><image wx:if="{{rw.a}}" class="reward-img data-v-3c32883e" src="{{rw.b}}" mode="aspectFill"/><view class="reward-texts data-v-3c32883e"><text class="reward-title data-v-3c32883e">{{rw.c}}</text><text wx:if="{{rw.d}}" class="reward-meta data-v-3c32883e">{{rw.e}}</text></view></view></view><view wx:else class="rewards-empty data-v-3c32883e">暂无奖励配置</view></view></view><view wx:else class="issues-empty data-v-3c32883e">暂无期数</view></view></scroll-view>
|
||||
49
unpackage/dist/dev/mp-weixin/pages/activity/yifanshang/index.wxss
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
|
||||
.page.data-v-3c32883e { height: 100vh
|
||||
}
|
||||
.banner.data-v-3c32883e { padding: 24rpx
|
||||
}
|
||||
.banner-img.data-v-3c32883e { width: 100%
|
||||
}
|
||||
.header.data-v-3c32883e { padding: 0 24rpx
|
||||
}
|
||||
.title.data-v-3c32883e { font-size: 36rpx; font-weight: 700
|
||||
}
|
||||
.meta.data-v-3c32883e { margin-top: 8rpx; font-size: 26rpx; color: #666
|
||||
}
|
||||
.actions.data-v-3c32883e { display: flex; padding: 24rpx; gap: 16rpx
|
||||
}
|
||||
.btn.data-v-3c32883e { flex: 1
|
||||
}
|
||||
.primary.data-v-3c32883e { background-color: #007AFF; color: #fff
|
||||
}
|
||||
.issues.data-v-3c32883e { background: #fff; border-radius: 12rpx; margin: 0 24rpx 24rpx; padding: 16rpx
|
||||
}
|
||||
.issues-title.data-v-3c32883e { font-size: 30rpx; font-weight: 600; margin-bottom: 12rpx
|
||||
}
|
||||
.issues-list.data-v-3c32883e {
|
||||
}
|
||||
.issue-item.data-v-3c32883e { display: flex; justify-content: space-between; padding: 12rpx 0; border-bottom: 1rpx solid #f0f0f0
|
||||
}
|
||||
.issue-item.data-v-3c32883e:last-child { border-bottom: 0
|
||||
}
|
||||
.issue-title.data-v-3c32883e { font-size: 26rpx
|
||||
}
|
||||
.issue-status.data-v-3c32883e { font-size: 24rpx; color: #666
|
||||
}
|
||||
.rewards.data-v-3c32883e { width: 100%; margin-top: 12rpx
|
||||
}
|
||||
.reward.data-v-3c32883e { display: flex; align-items: center; margin-bottom: 8rpx
|
||||
}
|
||||
.reward-img.data-v-3c32883e { width: 80rpx; height: 80rpx; border-radius: 8rpx; margin-right: 12rpx; background: #f5f5f5
|
||||
}
|
||||
.reward-texts.data-v-3c32883e { display: flex; flex-direction: column
|
||||
}
|
||||
.reward-title.data-v-3c32883e { font-size: 26rpx
|
||||
}
|
||||
.reward-meta.data-v-3c32883e { font-size: 22rpx; color: #888
|
||||
}
|
||||
.rewards-empty.data-v-3c32883e { font-size: 24rpx; color: #999
|
||||
}
|
||||
.issues-empty.data-v-3c32883e { font-size: 24rpx; color: #999
|
||||
}
|
||||
144
unpackage/dist/dev/mp-weixin/pages/address/edit.js
vendored
Normal file
@ -0,0 +1,144 @@
|
||||
"use strict";
|
||||
const common_vendor = require("../../common/vendor.js");
|
||||
const api_appUser = require("../../api/appUser.js");
|
||||
const _sfc_main = {
|
||||
__name: "edit",
|
||||
setup(__props) {
|
||||
const id = common_vendor.ref("");
|
||||
const name = common_vendor.ref("");
|
||||
const mobile = common_vendor.ref("");
|
||||
const province = common_vendor.ref("");
|
||||
const city = common_vendor.ref("");
|
||||
const district = common_vendor.ref("");
|
||||
const detail = common_vendor.ref("");
|
||||
let isDefault = false;
|
||||
const loading = common_vendor.ref(false);
|
||||
const error = common_vendor.ref("");
|
||||
function fill(data) {
|
||||
name.value = data.name || data.realname || "";
|
||||
mobile.value = data.mobile || data.phone || "";
|
||||
province.value = data.province || "";
|
||||
city.value = data.city || "";
|
||||
district.value = data.district || "";
|
||||
detail.value = data.address || data.detail || "";
|
||||
isDefault = !!data.is_default;
|
||||
}
|
||||
async function init(idParam) {
|
||||
if (!idParam) {
|
||||
const data = common_vendor.index.getStorageSync("edit_address") || {};
|
||||
if (data && data.id)
|
||||
fill(data);
|
||||
return;
|
||||
}
|
||||
const user_id = common_vendor.index.getStorageSync("user_id");
|
||||
try {
|
||||
const list = await api_appUser.listAddresses(user_id);
|
||||
const arr = Array.isArray(list) ? list : list && (list.list || list.items) || [];
|
||||
const found = arr.find((a) => String(a.id) === String(idParam));
|
||||
if (found)
|
||||
fill(found);
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
async function onSubmit() {
|
||||
const user_id = common_vendor.index.getStorageSync("user_id");
|
||||
if (!name.value || !mobile.value || !province.value || !city.value || !district.value || !detail.value) {
|
||||
common_vendor.index.showToast({ title: "请完善必填信息", icon: "none" });
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
error.value = "";
|
||||
const payload = {
|
||||
name: name.value,
|
||||
mobile: mobile.value,
|
||||
province: province.value,
|
||||
city: city.value,
|
||||
district: district.value,
|
||||
address: detail.value,
|
||||
is_default: isDefault
|
||||
};
|
||||
try {
|
||||
let savedId = id.value;
|
||||
if (id.value) {
|
||||
await api_appUser.updateAddress(user_id, id.value, payload);
|
||||
savedId = id.value;
|
||||
} else {
|
||||
try {
|
||||
const res = await api_appUser.addAddress(user_id, payload);
|
||||
savedId = res && (res.id || res.address_id) || "";
|
||||
} catch (eAdd) {
|
||||
const sc = eAdd && eAdd.statusCode;
|
||||
const bc = eAdd && eAdd.data && (eAdd.data.code || eAdd.code) || void 0;
|
||||
const msg = eAdd && (eAdd.message || eAdd.errMsg || "");
|
||||
const isUniqueErr = sc === 400 && (bc === 10011 || msg && msg.toLowerCase().includes("unique"));
|
||||
if (isUniqueErr) {
|
||||
try {
|
||||
const list = await api_appUser.listAddresses(user_id);
|
||||
const arr = Array.isArray(list) ? list : list && (list.list || list.items) || [];
|
||||
const found = arr.find((a) => (a.mobile === mobile.value || a.phone === mobile.value) && (a.address === detail.value || a.detail === detail.value) && a.city === city.value && a.district === district.value && a.province === province.value);
|
||||
if (found) {
|
||||
savedId = found.id;
|
||||
await api_appUser.updateAddress(user_id, savedId, payload);
|
||||
}
|
||||
} catch (_) {
|
||||
}
|
||||
} else {
|
||||
throw eAdd;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isDefault) {
|
||||
if (!savedId) {
|
||||
try {
|
||||
const list = await api_appUser.listAddresses(user_id);
|
||||
const arr = Array.isArray(list) ? list : list && (list.list || list.items) || [];
|
||||
const found = arr.find((a) => (a.mobile === mobile.value || a.phone === mobile.value) && (a.address === detail.value || a.detail === detail.value));
|
||||
if (found)
|
||||
savedId = found.id;
|
||||
} catch (_) {
|
||||
}
|
||||
}
|
||||
if (savedId) {
|
||||
await api_appUser.setDefaultAddress(user_id, savedId);
|
||||
}
|
||||
}
|
||||
common_vendor.index.showToast({ title: "保存成功", icon: "success" });
|
||||
common_vendor.index.navigateBack();
|
||||
} catch (e) {
|
||||
error.value = e && (e.message || e.errMsg) || "保存失败";
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
common_vendor.onLoad((opts) => {
|
||||
id.value = opts && opts.id || "";
|
||||
init(id.value);
|
||||
});
|
||||
return (_ctx, _cache) => {
|
||||
return common_vendor.e({
|
||||
a: name.value,
|
||||
b: common_vendor.o(($event) => name.value = $event.detail.value),
|
||||
c: mobile.value,
|
||||
d: common_vendor.o(($event) => mobile.value = $event.detail.value),
|
||||
e: province.value,
|
||||
f: common_vendor.o(($event) => province.value = $event.detail.value),
|
||||
g: city.value,
|
||||
h: common_vendor.o(($event) => city.value = $event.detail.value),
|
||||
i: district.value,
|
||||
j: common_vendor.o(($event) => district.value = $event.detail.value),
|
||||
k: detail.value,
|
||||
l: common_vendor.o(($event) => detail.value = $event.detail.value),
|
||||
m: common_vendor.unref(isDefault),
|
||||
n: common_vendor.o((e) => common_vendor.isRef(isDefault) ? isDefault.value = e.detail.value : isDefault = e.detail.value),
|
||||
o: loading.value,
|
||||
p: common_vendor.o(onSubmit),
|
||||
q: error.value
|
||||
}, error.value ? {
|
||||
r: common_vendor.t(error.value)
|
||||
} : {});
|
||||
};
|
||||
}
|
||||
};
|
||||
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-dcb1f0d8"]]);
|
||||
wx.createPage(MiniProgramPage);
|
||||
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/address/edit.js.map
|
||||
4
unpackage/dist/dev/mp-weixin/pages/address/edit.json
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "编辑地址",
|
||||
"usingComponents": {}
|
||||
}
|
||||
1
unpackage/dist/dev/mp-weixin/pages/address/edit.wxml
vendored
Normal file
@ -0,0 +1 @@
|
||||
<view class="wrap data-v-dcb1f0d8"><view class="form-item data-v-dcb1f0d8"><text class="label data-v-dcb1f0d8">姓名</text><input class="input data-v-dcb1f0d8" placeholder="请输入姓名" value="{{a}}" bindinput="{{b}}"/></view><view class="form-item data-v-dcb1f0d8"><text class="label data-v-dcb1f0d8">手机号</text><input class="input data-v-dcb1f0d8" placeholder="请输入手机号" value="{{c}}" bindinput="{{d}}"/></view><view class="form-item data-v-dcb1f0d8"><text class="label data-v-dcb1f0d8">省份</text><input class="input data-v-dcb1f0d8" placeholder="请输入省份" value="{{e}}" bindinput="{{f}}"/></view><view class="form-item data-v-dcb1f0d8"><text class="label data-v-dcb1f0d8">城市</text><input class="input data-v-dcb1f0d8" placeholder="请输入城市" value="{{g}}" bindinput="{{h}}"/></view><view class="form-item data-v-dcb1f0d8"><text class="label data-v-dcb1f0d8">区县</text><input class="input data-v-dcb1f0d8" placeholder="请输入区县" value="{{i}}" bindinput="{{j}}"/></view><view class="form-item data-v-dcb1f0d8"><text class="label data-v-dcb1f0d8">详细地址</text><input class="input data-v-dcb1f0d8" placeholder="请输入详细地址" value="{{k}}" bindinput="{{l}}"/></view><view class="form-item data-v-dcb1f0d8"><text class="label data-v-dcb1f0d8">设为默认</text><switch class="data-v-dcb1f0d8" checked="{{m}}" bindchange="{{n}}"/></view><button class="submit data-v-dcb1f0d8" disabled="{{o}}" bindtap="{{p}}">保存</button><view wx:if="{{q}}" class="error data-v-dcb1f0d8">{{r}}</view></view>
|
||||
13
unpackage/dist/dev/mp-weixin/pages/address/edit.wxss
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
.wrap.data-v-dcb1f0d8 { padding: 24rpx
|
||||
}
|
||||
.form-item.data-v-dcb1f0d8 { display: flex; align-items: center; background: #fff; border-radius: 12rpx; padding: 16rpx; margin-bottom: 12rpx
|
||||
}
|
||||
.label.data-v-dcb1f0d8 { width: 160rpx; font-size: 28rpx; color: #666
|
||||
}
|
||||
.input.data-v-dcb1f0d8 { flex: 1; font-size: 28rpx
|
||||
}
|
||||
.submit.data-v-dcb1f0d8 { width: 100%; margin-top: 20rpx
|
||||
}
|
||||
.error.data-v-dcb1f0d8 { color: #e43; margin-top: 12rpx
|
||||
}
|
||||
107
unpackage/dist/dev/mp-weixin/pages/address/index.js
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
"use strict";
|
||||
const common_vendor = require("../../common/vendor.js");
|
||||
const api_appUser = require("../../api/appUser.js");
|
||||
const _sfc_main = {
|
||||
__name: "index",
|
||||
setup(__props) {
|
||||
const list = common_vendor.ref([]);
|
||||
const loading = common_vendor.ref(false);
|
||||
const error = common_vendor.ref("");
|
||||
async function fetchList() {
|
||||
const user_id = common_vendor.index.getStorageSync("user_id");
|
||||
const token = common_vendor.index.getStorageSync("token");
|
||||
const phoneBound = !!common_vendor.index.getStorageSync("phone_bound");
|
||||
if (!user_id || !token || !phoneBound) {
|
||||
common_vendor.index.showModal({
|
||||
title: "提示",
|
||||
content: "请先登录并绑定手机号",
|
||||
confirmText: "去登录",
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
common_vendor.index.navigateTo({ url: "/pages/login/index" });
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
error.value = "";
|
||||
try {
|
||||
const data = await api_appUser.listAddresses(user_id);
|
||||
list.value = Array.isArray(data) ? data : data && (data.list || data.items) || [];
|
||||
} catch (e) {
|
||||
error.value = e && (e.message || e.errMsg) || "获取地址失败";
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
function toAdd() {
|
||||
common_vendor.index.removeStorageSync("edit_address");
|
||||
common_vendor.index.navigateTo({ url: "/pages/address/edit" });
|
||||
}
|
||||
function toEdit(item) {
|
||||
common_vendor.index.setStorageSync("edit_address", item);
|
||||
common_vendor.index.navigateTo({ url: `/pages/address/edit?id=${item.id}` });
|
||||
}
|
||||
function onDelete(item) {
|
||||
const user_id = common_vendor.index.getStorageSync("user_id");
|
||||
common_vendor.index.showModal({
|
||||
title: "确认删除",
|
||||
content: "确定删除该地址吗?",
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
await api_appUser.deleteAddress(user_id, item.id);
|
||||
fetchList();
|
||||
} catch (e) {
|
||||
common_vendor.index.showToast({ title: "删除失败", icon: "none" });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
async function onSetDefault(item) {
|
||||
try {
|
||||
const user_id = common_vendor.index.getStorageSync("user_id");
|
||||
await api_appUser.setDefaultAddress(user_id, item.id);
|
||||
fetchList();
|
||||
} catch (e) {
|
||||
common_vendor.index.showToast({ title: "设置失败", icon: "none" });
|
||||
}
|
||||
}
|
||||
common_vendor.onLoad(() => {
|
||||
fetchList();
|
||||
});
|
||||
return (_ctx, _cache) => {
|
||||
return common_vendor.e({
|
||||
a: common_vendor.o(toAdd),
|
||||
b: error.value
|
||||
}, error.value ? {
|
||||
c: common_vendor.t(error.value)
|
||||
} : {}, {
|
||||
d: list.value.length === 0 && !loading.value
|
||||
}, list.value.length === 0 && !loading.value ? {} : {}, {
|
||||
e: common_vendor.f(list.value, (item, k0, i0) => {
|
||||
return common_vendor.e({
|
||||
a: common_vendor.t(item.name || item.realname),
|
||||
b: common_vendor.t(item.phone || item.mobile),
|
||||
c: item.is_default
|
||||
}, item.is_default ? {} : {}, {
|
||||
d: common_vendor.t(item.province),
|
||||
e: common_vendor.t(item.city),
|
||||
f: common_vendor.t(item.district),
|
||||
g: common_vendor.t(item.address || item.detail),
|
||||
h: common_vendor.o(($event) => toEdit(item), item.id),
|
||||
i: common_vendor.o(($event) => onDelete(item), item.id),
|
||||
j: item.is_default,
|
||||
k: common_vendor.o(($event) => onSetDefault(item), item.id),
|
||||
l: item.id
|
||||
});
|
||||
})
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-c47feaaa"]]);
|
||||
wx.createPage(MiniProgramPage);
|
||||
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/address/index.js.map
|
||||
4
unpackage/dist/dev/mp-weixin/pages/address/index.json
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "地址管理",
|
||||
"usingComponents": {}
|
||||
}
|
||||
1
unpackage/dist/dev/mp-weixin/pages/address/index.wxml
vendored
Normal file
@ -0,0 +1 @@
|
||||
<view class="wrap data-v-c47feaaa"><view class="header data-v-c47feaaa"><button class="add data-v-c47feaaa" bindtap="{{a}}">新增地址</button></view><view wx:if="{{b}}" class="error data-v-c47feaaa">{{c}}</view><view wx:if="{{d}}" class="empty data-v-c47feaaa">暂无地址</view><view wx:for="{{e}}" wx:for-item="item" wx:key="l" class="addr data-v-c47feaaa"><view class="addr-main data-v-c47feaaa"><view class="addr-row data-v-c47feaaa"><text class="name data-v-c47feaaa">姓名:{{item.a}}</text></view><view class="addr-row data-v-c47feaaa"><text class="phone data-v-c47feaaa">手机号:{{item.b}}</text></view><view wx:if="{{item.c}}" class="addr-row data-v-c47feaaa"><text class="default data-v-c47feaaa">默认</text></view><view class="addr-row data-v-c47feaaa"><text class="region data-v-c47feaaa">省市区:{{item.d}}{{item.e}}{{item.f}}</text></view><view class="addr-row data-v-c47feaaa"><text class="detail data-v-c47feaaa">详细地址:{{item.g}}</text></view></view><view class="addr-actions data-v-c47feaaa"><button class="data-v-c47feaaa" size="mini" bindtap="{{item.h}}">编辑</button><button class="data-v-c47feaaa" size="mini" type="warn" bindtap="{{item.i}}">删除</button><button class="data-v-c47feaaa" size="mini" disabled="{{item.j}}" bindtap="{{item.k}}">设为默认</button></view></view></view>
|
||||
27
unpackage/dist/dev/mp-weixin/pages/address/index.wxss
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
|
||||
.wrap.data-v-c47feaaa { padding: 24rpx
|
||||
}
|
||||
.header.data-v-c47feaaa { display: flex; justify-content: flex-end; margin-bottom: 12rpx
|
||||
}
|
||||
.add.data-v-c47feaaa { font-size: 28rpx
|
||||
}
|
||||
.addr.data-v-c47feaaa { background: #fff; border-radius: 12rpx; padding: 20rpx; margin-bottom: 16rpx; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04)
|
||||
}
|
||||
.addr-row.data-v-c47feaaa { display: flex; align-items: center; margin-bottom: 8rpx
|
||||
}
|
||||
.name.data-v-c47feaaa { font-size: 30rpx; margin-right: 12rpx
|
||||
}
|
||||
.phone.data-v-c47feaaa { font-size: 28rpx; color: #666
|
||||
}
|
||||
.default.data-v-c47feaaa { font-size: 24rpx; color: #007AFF; margin-left: 10rpx
|
||||
}
|
||||
.region.data-v-c47feaaa { font-size: 26rpx; color: #666
|
||||
}
|
||||
.detail.data-v-c47feaaa { font-size: 26rpx; color: #333
|
||||
}
|
||||
.addr-actions.data-v-c47feaaa { display: flex; justify-content: flex-end; gap: 12rpx; margin-top: 12rpx
|
||||
}
|
||||
.empty.data-v-c47feaaa { text-align: center; color: #999; margin-top: 40rpx
|
||||
}
|
||||
.error.data-v-c47feaaa { color: #e43; margin-bottom: 12rpx
|
||||
}
|
||||
9
unpackage/dist/dev/mp-weixin/pages/agreement/purchase.js
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
"use strict";
|
||||
const common_vendor = require("../../common/vendor.js");
|
||||
const _sfc_main = {};
|
||||
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
|
||||
return {};
|
||||
}
|
||||
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-5512f5e4"]]);
|
||||
wx.createPage(MiniProgramPage);
|
||||
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/agreement/purchase.js.map
|
||||
4
unpackage/dist/dev/mp-weixin/pages/agreement/purchase.json
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "购买协议",
|
||||
"usingComponents": {}
|
||||
}
|
||||
1
unpackage/dist/dev/mp-weixin/pages/agreement/purchase.wxml
vendored
Normal file
@ -0,0 +1 @@
|
||||
<scroll-view class="page data-v-5512f5e4" scroll-y><view class="h1 data-v-5512f5e4">购买协议</view><view class="meta data-v-5512f5e4">生效日期:2025年11月18日</view><view class="meta data-v-5512f5e4">运营方:【公司全称】</view><view class="p data-v-5512f5e4">本《购买协议》适用于您在【您的小程序名称】(以下简称“本平台”)购买盲盒商品的行为。当您点击“立即购买”并完成支付时,即视为您已阅读、理解并同意本协议全部内容。</view><view class="h2 data-v-5512f5e4">一、商品说明</view><view class="ol data-v-5512f5e4"><view class="li data-v-5512f5e4">盲盒特性:本平台所售盲盒为系列化商品,包装外观一致,内部款式随机,具体款式无法提前指定或预知。</view><view class="li data-v-5512f5e4">概率公示:各款式(含隐藏款、特殊款)的抽取概率已在商品详情页明确公示,确保透明公正。</view><view class="li data-v-5512f5e4">商品展示:页面图片、描述仅供参考,实际商品以实物为准。</view></view><view class="h2 data-v-5512f5e4">二、购买规则</view><view class="ol data-v-5512f5e4"><view class="li data-v-5512f5e4">购买资格:仅限已注册并通过身份验证的用户购买。</view><view class="li data-v-5512f5e4">支付方式:支持微信支付等平台认可的支付方式,支付成功即视为订单成立。</view></view><view class="h2 data-v-5512f5e4">三、发货与物流</view><view class="ol data-v-5512f5e4"><view class="li data-v-5512f5e4">发货时效:发货订单提交成功后,本平台将在3-15个工作日内安排发货(预售商品按页面说明执行)。</view><view class="li data-v-5512f5e4">物流信息:您可在“我的订单”中查看物流状态。因地址错误、联系不畅导致的配送失败,责任由您承担。</view></view><view class="h2 data-v-5512f5e4">四、售后服务</view><view class="ol data-v-5512f5e4"><view class="li data-v-5512f5e4">质量问题(如商品破损、漏发、错发、非盲盒系列商品):请在签收后2到4小时内联系客服并提供凭证(如开箱视频、照片);经核实后,平台将为您补发、换货或退款。</view><view class="li data-v-5512f5e4">非质量问题(如抽中重复款式、不喜欢款式、未抽中隐藏款等):不支持无理由退换货。</view><view class="li data-v-5512f5e4">拆封后商品:出于卫生与二次销售考虑,已拆封盲盒恕不退换(质量问题除外)。</view></view><view class="h2 data-v-5512f5e4">五、价格与促销</view><view class="ol data-v-5512f5e4"><view class="li data-v-5512f5e4">商品价格以页面实时显示为准,平台有权根据市场情况调整价格。</view><view class="li data-v-5512f5e4">优惠券、折扣活动需遵守对应规则,不可叠加或兑现。</view></view><view class="h2 data-v-5512f5e4">六、特别提示</view><view class="ol data-v-5512f5e4"><view class="li data-v-5512f5e4">盲盒不具备投资属性,本平台不承诺保值、升值或回购。</view><view class="li data-v-5512f5e4">隐藏款抽取属小概率事件,请勿抱有赌博心态,理性消费。</view></view><view class="h2 data-v-5512f5e4">七、免责情形</view><view class="ul data-v-5512f5e4"><view class="li data-v-5512f5e4">因不可抗力导致无法发货或延迟;</view><view class="li data-v-5512f5e4">用户提供错误收货信息;</view><view class="li data-v-5512f5e4">因第三方物流原因造成的商品损毁或丢失(平台将协助索赔)。</view></view><view class="h2 data-v-5512f5e4">八、协议效力</view><view class="p data-v-5512f5e4">本购买协议为《用户协议》的补充,两者冲突时,以本协议中关于交易的条款为准。未尽事宜,依照《消费者权益保护法》《电子商务法》等法律法规执行。</view><view class="h2 data-v-5512f5e4">九、联系我们</view><view class="p data-v-5512f5e4">售后专线:service@yourdomain.com</view><view class="p data-v-5512f5e4">工作时间:工作日 9:00–18:00</view><view class="p data-v-5512f5e4">运营主体:【公司全称】</view><view class="p data-v-5512f5e4">统一社会信用代码:【XXXXXXXXXXXXXX】</view><view class="tip data-v-5512f5e4">理性消费提醒:盲盒是一种娱乐消费形式,请根据自身经济能力合理购买,切勿沉迷或过度投入。</view></scroll-view>
|
||||
17
unpackage/dist/dev/mp-weixin/pages/agreement/purchase.wxss
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
|
||||
.page.data-v-5512f5e4 { height: 100vh
|
||||
}
|
||||
.h1.data-v-5512f5e4 { font-size: 36rpx; font-weight: 700; padding: 24rpx
|
||||
}
|
||||
.meta.data-v-5512f5e4 { padding: 0 24rpx; font-size: 24rpx; color: #666
|
||||
}
|
||||
.h2.data-v-5512f5e4 { font-size: 30rpx; font-weight: 600; padding: 20rpx 24rpx 8rpx
|
||||
}
|
||||
.p.data-v-5512f5e4 { padding: 0 24rpx; font-size: 26rpx; line-height: 1.6
|
||||
}
|
||||
.ul.data-v-5512f5e4, .ol.data-v-5512f5e4 { padding: 0 24rpx
|
||||
}
|
||||
.ul .li.data-v-5512f5e4, .ol .li.data-v-5512f5e4 { font-size: 26rpx; line-height: 1.6; margin: 8rpx 0
|
||||
}
|
||||
.tip.data-v-5512f5e4 { padding: 16rpx 24rpx; font-size: 24rpx; color: #999
|
||||
}
|
||||
9
unpackage/dist/dev/mp-weixin/pages/agreement/user.js
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
"use strict";
|
||||
const common_vendor = require("../../common/vendor.js");
|
||||
const _sfc_main = {};
|
||||
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
|
||||
return {};
|
||||
}
|
||||
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-ff1e87b4"]]);
|
||||
wx.createPage(MiniProgramPage);
|
||||
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/agreement/user.js.map
|
||||
4
unpackage/dist/dev/mp-weixin/pages/agreement/user.json
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "用户协议",
|
||||
"usingComponents": {}
|
||||
}
|
||||
1
unpackage/dist/dev/mp-weixin/pages/agreement/user.wxml
vendored
Normal file
@ -0,0 +1 @@
|
||||
<scroll-view class="page data-v-ff1e87b4" scroll-y><view class="h1 data-v-ff1e87b4">用户协议</view><view class="meta data-v-ff1e87b4">生效日期:2025年11月18日</view><view class="meta data-v-ff1e87b4">运营方:【公司全称】</view><view class="p data-v-ff1e87b4">欢迎您使用【您的小程序名称】(以下简称“本平台”)提供的服务。请您在注册、登录或使用本平台前,认真阅读并充分理解本《用户协议》(以下简称“本协议”)。一旦您完成注册、登录或以任何方式使用本平台服务,即视为您已完全接受本协议全部条款。如您不同意,请勿使用本平台。</view><view class="h2 data-v-ff1e87b4">一、协议范围</view><view class="p data-v-ff1e87b4">本协议规范您作为用户在本平台注册、浏览、互动、参与活动等行为,是您与本平台之间的基本权利义务约定。</view><view class="h2 data-v-ff1e87b4">二、用户资格</view><view class="ol data-v-ff1e87b4"><view class="li data-v-ff1e87b4">您须为年满18周岁的自然人,具备完全民事行为能力。</view><view class="li data-v-ff1e87b4">未成年人如需使用本平台,须在法定监护人知情并同意下进行,监护人应承担相应责任。</view></view><view class="h2 data-v-ff1e87b4">三、账号与安全</view><view class="ol data-v-ff1e87b4"><view class="li data-v-ff1e87b4">您应提供真实、准确、完整的注册信息,并对账号下的一切行为负责。</view><view class="li data-v-ff1e87b4">请妥善保管账号密码,因泄露、出借或被盗用造成的损失由您自行承担。</view></view><view class="h2 data-v-ff1e87b4">四、用户行为规范</view><view class="p data-v-ff1e87b4">您承诺不从事以下行为:</view><view class="ul data-v-ff1e87b4"><view class="li data-v-ff1e87b4">发布违法、侵权、色情、暴力或虚假信息;</view><view class="li data-v-ff1e87b4">利用技术手段干扰系统正常运行(如刷量、外挂、爬虫);</view><view class="li data-v-ff1e87b4">恶意投诉、敲诈勒索或损害平台商誉;</view><view class="li data-v-ff1e87b4">转售账号或用于商业牟利(除非平台明确授权)。</view></view><view class="h2 data-v-ff1e87b4">五、隐私保护</view><view class="p data-v-ff1e87b4">我们尊重并保护您的个人信息。关于数据收集、使用及保护的具体规则,请参见《隐私政策》(链接)。未经您同意,我们不会向第三方共享您的个人信息,法律法规另有规定除外。</view><view class="h2 data-v-ff1e87b4">六、知识产权</view><view class="ol data-v-ff1e87b4"><view class="li data-v-ff1e87b4">本平台所有内容(包括但不限于界面设计、文字、图片、盲盒形象、LOGO)的知识产权归本平台或其许可方所有。</view><view class="li data-v-ff1e87b4">未经书面授权,您不得复制、传播、修改或用于商业用途。</view></view><view class="h2 data-v-ff1e87b4">七、免责条款</view><view class="ol data-v-ff1e87b4"><view class="li data-v-ff1e87b4">因不可抗力(如地震、网络攻击、政府行为)导致服务中断,本平台不承担责任。</view><view class="li data-v-ff1e87b4">您因违反本协议造成自身或第三方损失的,由您自行承担。</view></view><view class="h2 data-v-ff1e87b4">八、协议变更与终止</view><view class="ol data-v-ff1e87b4"><view class="li data-v-ff1e87b4">本平台有权根据业务或法律变化修订本协议,修订后将通过公告或推送通知您。继续使用即视为接受新条款。</view><view class="li data-v-ff1e87b4">您可随时注销账号终止使用;平台有权对违规账号采取限制或封禁措施。</view></view><view class="h2 data-v-ff1e87b4">九、法律适用与争议解决</view><view class="p data-v-ff1e87b4">本协议适用中华人民共和国法律。因本协议引起的争议,双方应协商解决;协商不成的,提交本平台运营方所在地有管辖权的人民法院诉讼解决。</view><view class="h2 data-v-ff1e87b4">十、联系我们</view><view class="p data-v-ff1e87b4">客服邮箱:service@yourdomain.com</view><view class="p data-v-ff1e87b4">客服电话:400-XXX-XXXX(工作日 9:00–18:00)</view><view class="p data-v-ff1e87b4">运营主体:【公司全称】</view><view class="p data-v-ff1e87b4">地址:【公司注册地址】</view><view class="tip data-v-ff1e87b4">温馨提示:盲盒具有随机性和娱乐性,请理性参与,避免沉迷。未成年人禁止参与购买。</view></scroll-view>
|
||||
17
unpackage/dist/dev/mp-weixin/pages/agreement/user.wxss
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
|
||||
.page.data-v-ff1e87b4 { height: 100vh
|
||||
}
|
||||
.h1.data-v-ff1e87b4 { font-size: 36rpx; font-weight: 700; padding: 24rpx
|
||||
}
|
||||
.meta.data-v-ff1e87b4 { padding: 0 24rpx; font-size: 24rpx; color: #666
|
||||
}
|
||||
.h2.data-v-ff1e87b4 { font-size: 30rpx; font-weight: 600; padding: 20rpx 24rpx 8rpx
|
||||
}
|
||||
.p.data-v-ff1e87b4 { padding: 0 24rpx; font-size: 26rpx; line-height: 1.6
|
||||
}
|
||||
.ul.data-v-ff1e87b4, .ol.data-v-ff1e87b4 { padding: 0 24rpx
|
||||
}
|
||||
.ul .li.data-v-ff1e87b4, .ol .li.data-v-ff1e87b4 { font-size: 26rpx; line-height: 1.6; margin: 8rpx 0
|
||||
}
|
||||
.tip.data-v-ff1e87b4 { padding: 16rpx 24rpx; font-size: 24rpx; color: #999
|
||||
}
|
||||
30
unpackage/dist/dev/mp-weixin/pages/cabinet/index.js
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
"use strict";
|
||||
const common_vendor = require("../../common/vendor.js");
|
||||
const _sfc_main = {
|
||||
__name: "index",
|
||||
setup(__props) {
|
||||
common_vendor.onShow(() => {
|
||||
const token = common_vendor.index.getStorageSync("token");
|
||||
const phoneBound = !!common_vendor.index.getStorageSync("phone_bound");
|
||||
common_vendor.index.__f__("log", "at pages/cabinet/index.vue:10", "cabinet onShow token:", token, "isLogin:", !!token, "phoneBound:", phoneBound);
|
||||
if (!token || !phoneBound) {
|
||||
common_vendor.index.showModal({
|
||||
title: "提示",
|
||||
content: "请先登录并绑定手机号",
|
||||
confirmText: "去登录",
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
common_vendor.index.navigateTo({ url: "/pages/login/index" });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return (_ctx, _cache) => {
|
||||
return {};
|
||||
};
|
||||
}
|
||||
};
|
||||
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-814d09c7"]]);
|
||||
wx.createPage(MiniProgramPage);
|
||||
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/cabinet/index.js.map
|
||||
4
unpackage/dist/dev/mp-weixin/pages/cabinet/index.json
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "货柜",
|
||||
"usingComponents": {}
|
||||
}
|
||||
1
unpackage/dist/dev/mp-weixin/pages/cabinet/index.wxml
vendored
Normal file
@ -0,0 +1 @@
|
||||
<view class="wrap data-v-814d09c7">货柜</view>
|
||||
3
unpackage/dist/dev/mp-weixin/pages/cabinet/index.wxss
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
.wrap.data-v-814d09c7 { padding: 40rpx
|
||||
}
|
||||
21
unpackage/dist/dev/mp-weixin/pages/help/index.js
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
"use strict";
|
||||
const common_vendor = require("../../common/vendor.js");
|
||||
const _sfc_main = {
|
||||
methods: {
|
||||
toUser() {
|
||||
common_vendor.index.navigateTo({ url: "/pages/agreement/user" });
|
||||
},
|
||||
toPurchase() {
|
||||
common_vendor.index.navigateTo({ url: "/pages/agreement/purchase" });
|
||||
}
|
||||
}
|
||||
};
|
||||
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
|
||||
return {
|
||||
a: common_vendor.o((...args) => $options.toUser && $options.toUser(...args)),
|
||||
b: common_vendor.o((...args) => $options.toPurchase && $options.toPurchase(...args))
|
||||
};
|
||||
}
|
||||
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-e6af2099"]]);
|
||||
wx.createPage(MiniProgramPage);
|
||||
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/help/index.js.map
|
||||
4
unpackage/dist/dev/mp-weixin/pages/help/index.json
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "使用帮助",
|
||||
"usingComponents": {}
|
||||
}
|
||||
1
unpackage/dist/dev/mp-weixin/pages/help/index.wxml
vendored
Normal file
@ -0,0 +1 @@
|
||||
<view class="wrap data-v-e6af2099"><view class="card data-v-e6af2099" bindtap="{{a}}"><view class="title data-v-e6af2099">用户协议</view><view class="desc data-v-e6af2099">查看平台使用条款与隐私说明</view></view><view class="card data-v-e6af2099" bindtap="{{b}}"><view class="title data-v-e6af2099">购买协议</view><view class="desc data-v-e6af2099">查看盲盒购买规则与售后政策</view></view></view>
|
||||