From 0f4e04f4ad1911c667bcece5759627c334c48d88 Mon Sep 17 00:00:00 2001 From: gqt <3217233537@qq.com> Date: Mon, 8 Jun 2026 21:00:32 +0800 Subject: [PATCH] feat: migrate ice venue module --- miniprogram/src/components/ice/StatePanel.vue | 210 +++ miniprogram/src/modules/ice/api/booking.js | 63 + miniprogram/src/modules/ice/api/card.js | 61 + miniprogram/src/modules/ice/api/content.js | 17 + miniprogram/src/modules/ice/api/course.js | 68 + miniprogram/src/modules/ice/api/home.js | 52 + miniprogram/src/modules/ice/api/member.js | 151 +++ miniprogram/src/modules/ice/api/request.js | 134 ++ miniprogram/src/modules/ice/api/session.js | 108 ++ miniprogram/src/pages.json | 65 + .../src/pages/ice/activities/detail.vue | 157 +++ miniprogram/src/pages/ice/bookings/detail.vue | 511 +++++++ miniprogram/src/pages/ice/bookings/index.vue | 544 ++++++++ miniprogram/src/pages/ice/courses/detail.vue | 258 ++++ miniprogram/src/pages/ice/courses/index.vue | 419 ++++++ miniprogram/src/pages/ice/home/index.vue | 474 +++++++ .../src/pages/ice/profile/card-history.vue | 516 ++++++++ miniprogram/src/pages/ice/profile/cards.vue | 430 ++++++ miniprogram/src/pages/ice/profile/detail.vue | 271 ++++ miniprogram/src/pages/ice/profile/index.vue | 1175 +++++++++++++++++ .../src/static/ice/tab/booking-active.png | Bin 0 -> 433 bytes miniprogram/src/static/ice/tab/booking.png | Bin 0 -> 429 bytes .../src/static/ice/tab/course-active.png | Bin 0 -> 350 bytes miniprogram/src/static/ice/tab/course.png | Bin 0 -> 347 bytes .../src/static/ice/tab/home-active.png | Bin 0 -> 496 bytes miniprogram/src/static/ice/tab/home.png | Bin 0 -> 493 bytes .../src/static/ice/tab/mine-active.png | Bin 0 -> 469 bytes miniprogram/src/static/ice/tab/mine.png | Bin 0 -> 466 bytes 28 files changed, 5684 insertions(+) create mode 100644 miniprogram/src/components/ice/StatePanel.vue create mode 100644 miniprogram/src/modules/ice/api/booking.js create mode 100644 miniprogram/src/modules/ice/api/card.js create mode 100644 miniprogram/src/modules/ice/api/content.js create mode 100644 miniprogram/src/modules/ice/api/course.js create mode 100644 miniprogram/src/modules/ice/api/home.js create mode 100644 miniprogram/src/modules/ice/api/member.js create mode 100644 miniprogram/src/modules/ice/api/request.js create mode 100644 miniprogram/src/modules/ice/api/session.js create mode 100644 miniprogram/src/pages/ice/activities/detail.vue create mode 100644 miniprogram/src/pages/ice/bookings/detail.vue create mode 100644 miniprogram/src/pages/ice/bookings/index.vue create mode 100644 miniprogram/src/pages/ice/courses/detail.vue create mode 100644 miniprogram/src/pages/ice/courses/index.vue create mode 100644 miniprogram/src/pages/ice/home/index.vue create mode 100644 miniprogram/src/pages/ice/profile/card-history.vue create mode 100644 miniprogram/src/pages/ice/profile/cards.vue create mode 100644 miniprogram/src/pages/ice/profile/detail.vue create mode 100644 miniprogram/src/pages/ice/profile/index.vue create mode 100644 miniprogram/src/static/ice/tab/booking-active.png create mode 100644 miniprogram/src/static/ice/tab/booking.png create mode 100644 miniprogram/src/static/ice/tab/course-active.png create mode 100644 miniprogram/src/static/ice/tab/course.png create mode 100644 miniprogram/src/static/ice/tab/home-active.png create mode 100644 miniprogram/src/static/ice/tab/home.png create mode 100644 miniprogram/src/static/ice/tab/mine-active.png create mode 100644 miniprogram/src/static/ice/tab/mine.png diff --git a/miniprogram/src/components/ice/StatePanel.vue b/miniprogram/src/components/ice/StatePanel.vue new file mode 100644 index 0000000..45c517e --- /dev/null +++ b/miniprogram/src/components/ice/StatePanel.vue @@ -0,0 +1,210 @@ + + + + + diff --git a/miniprogram/src/modules/ice/api/booking.js b/miniprogram/src/modules/ice/api/booking.js new file mode 100644 index 0000000..6f6a3d3 --- /dev/null +++ b/miniprogram/src/modules/ice/api/booking.js @@ -0,0 +1,63 @@ +import { request } from './request' + +const toNumber = (value) => { + const numberValue = Number(value) + return Number.isFinite(numberValue) ? numberValue : 0 +} + +const toBookingCourse = (backendCourse = {}) => ({ + id: backendCourse.id ?? null, + title: backendCourse.title || '', + start_time: backendCourse.start_time || '', + end_time: backendCourse.end_time || '', + coach_name: backendCourse.coach_name || '', + capacity: toNumber(backendCourse.capacity), + booked_count: toNumber(backendCourse.booked_count) +}) + +const toBookingCard = (backendCard) => { + if (!backendCard) return null + return { + id: backendCard.id ?? null, + card_name: backendCard.card_name || '', + remained_detail: backendCard.remained_detail || '' + } +} + +const toBookingViewModel = (backendBooking = {}) => ({ + id: backendBooking.id, + status: backendBooking.status || 'booked', + checked_in_at: backendBooking.checked_in_at || null, + cancelled_at: backendBooking.cancelled_at || null, + created_at: backendBooking.created_at || '', + course: toBookingCourse(backendBooking.course), + card: toBookingCard(backendBooking.card) +}) + +const toPaginatedBookings = (backendData = {}) => ({ + items: Array.isArray(backendData.items) ? backendData.items.map(toBookingViewModel) : [], + total: toNumber(backendData.total), + page: toNumber(backendData.page) || 1, + page_size: toNumber(backendData.page_size) || 20 +}) + +const toQuery = (params = {}) => ({ + status: params.status || 'all', + page: params.page || 1, + page_size: params.pageSize || 20 +}) + +export const bookingApi = { + getBookings(params = {}) { + return request({ + url: '/api/bookings', + data: toQuery(params) + }).then(toPaginatedBookings) + }, + + getBookingDetail(id) { + return request({ + url: `/api/bookings/${id}` + }).then(toBookingViewModel) + } +} diff --git a/miniprogram/src/modules/ice/api/card.js b/miniprogram/src/modules/ice/api/card.js new file mode 100644 index 0000000..ed02889 --- /dev/null +++ b/miniprogram/src/modules/ice/api/card.js @@ -0,0 +1,61 @@ +import { request } from './request' + +const toNumber = (value) => { + const numberValue = Number(value) + return Number.isFinite(numberValue) ? numberValue : 0 +} + +const toMemberCard = (backendCard = {}) => ({ + id: backendCard.id, + card_no: backendCard.card_no || '', + card_name: backendCard.card_name || '', + card_type: backendCard.card_type || 'times', + backend_card_type: backendCard.backend_card_type || '', + status: backendCard.status || 'active', + total_times: toNumber(backendCard.total_times), + remained_times: toNumber(backendCard.remained_times), + original_balance: backendCard.original_balance || '0.00', + gift_balance: backendCard.gift_balance || '0.00', + balance: backendCard.balance || '0.00', + start_date: backendCard.start_date || '', + expire_date: backendCard.expire_date || '', + remained_detail: backendCard.remained_detail || '' +}) + +const toCardChange = (backendChange = {}) => ({ + id: backendChange.id, + change_type: backendChange.change_type || 'adjustment', + backend_change_type: backendChange.backend_change_type || '', + delta: backendChange.delta || '0', + post_times: toNumber(backendChange.post_times), + post_balance: backendChange.post_balance || '0.00', + memo: backendChange.memo || '', + created_at: backendChange.created_at || '' +}) + +const toCardHistory = (backendData = {}) => ({ + card: toMemberCard(backendData.card), + items: Array.isArray(backendData.items) ? backendData.items.map(toCardChange) : [], + total: toNumber(backendData.total), + page: toNumber(backendData.page) || 1, + page_size: toNumber(backendData.page_size) || 20 +}) + +export const cardApi = { + getCards(status = 'all') { + return request({ + url: '/api/members/me/cards', + data: { status } + }).then((data = {}) => (Array.isArray(data.items) ? data.items.map(toMemberCard) : [])) + }, + + getCardChanges(cardId, params = {}) { + return request({ + url: `/api/members/me/cards/${cardId}/changes`, + data: { + page: params.page || 1, + page_size: params.pageSize || 20 + } + }).then(toCardHistory) + } +} diff --git a/miniprogram/src/modules/ice/api/content.js b/miniprogram/src/modules/ice/api/content.js new file mode 100644 index 0000000..ff8fc7a --- /dev/null +++ b/miniprogram/src/modules/ice/api/content.js @@ -0,0 +1,17 @@ +import { request } from './request' + +const toActivityDetail = (backendActivity = {}) => ({ + id: backendActivity.id, + title: backendActivity.title || '', + cover: backendActivity.cover || '', + created_at: backendActivity.created_at || '', + content_html: backendActivity.content_html || '' +}) + +export const contentApi = { + getActivityDetail(id) { + return request({ + url: `/api/contents/activities/${id}` + }).then(toActivityDetail) + } +} diff --git a/miniprogram/src/modules/ice/api/course.js b/miniprogram/src/modules/ice/api/course.js new file mode 100644 index 0000000..45238ef --- /dev/null +++ b/miniprogram/src/modules/ice/api/course.js @@ -0,0 +1,68 @@ +import { request } from './request' + +const toNumber = (value) => { + const numberValue = Number(value) + return Number.isFinite(numberValue) ? numberValue : 0 +} + +const toCoach = (backendCoach = {}) => ({ + id: backendCoach.id ?? null, + name: backendCoach.name || '' +}) + +export const toCourseViewModel = (backendCourse = {}) => { + const coach = toCoach(backendCourse.coach) + return { + id: backendCourse.id, + title: backendCourse.title || '', + start_time: backendCourse.start_time || '', + end_time: backendCourse.end_time || '', + coach, + coach_name: backendCourse.coach_name || coach.name, + capacity: toNumber(backendCourse.capacity), + booked_count: toNumber(backendCourse.booked_count), + available_count: toNumber(backendCourse.available_count), + is_booked: backendCourse.is_booked === true, + my_booking_id: backendCourse.my_booking_id ?? null, + cover: backendCourse.cover || '', + detail_html: backendCourse.detail_html || '', + } +} + +const toPaginatedCourses = (backendData = {}) => ({ + items: Array.isArray(backendData.items) ? backendData.items.map(toCourseViewModel) : [], + total: toNumber(backendData.total), + page: toNumber(backendData.page) || 1, + page_size: toNumber(backendData.page_size) || 20 +}) + +const toQuery = (params = {}) => { + const data = {} + if (params.date) data.date = params.date + if (params.coachId) data.coach_id = params.coachId + if (params.page) data.page = params.page + if (params.pageSize) data.page_size = params.pageSize + return data +} + +export const courseApi = { + getCoaches(status = 'active') { + return request({ + url: '/api/coaches', + data: { status } + }).then((data = {}) => (Array.isArray(data.items) ? data.items.map(toCoach) : [])) + }, + + getCourses(params = {}) { + return request({ + url: '/api/courses', + data: toQuery(params) + }).then(toPaginatedCourses) + }, + + getCourseDetail(id) { + return request({ + url: `/api/courses/${id}` + }).then(toCourseViewModel) + } +} diff --git a/miniprogram/src/modules/ice/api/home.js b/miniprogram/src/modules/ice/api/home.js new file mode 100644 index 0000000..84b4f8b --- /dev/null +++ b/miniprogram/src/modules/ice/api/home.js @@ -0,0 +1,52 @@ +import { request } from './request' +import { toCourseViewModel } from './course' + +const toNumber = (value) => { + const numberValue = Number(value) + return Number.isFinite(numberValue) ? numberValue : 0 +} + +const toBanner = (backendBanner = {}) => ({ + id: backendBanner.id, + title: backendBanner.title || '', + image: backendBanner.image || '', + link_type: backendBanner.link_type || 'none', + link_value: backendBanner.link_value || '', + sort_order: toNumber(backendBanner.sort_order) +}) + +const toActivitySummary = (backendActivity = {}) => ({ + id: backendActivity.id, + title: backendActivity.title || '', + cover: backendActivity.cover || '', + created_at: backendActivity.created_at || '' +}) + +const toHomeViewModel = (backendData = {}) => ({ + banners: Array.isArray(backendData.banners) ? backendData.banners.map(toBanner) : [], + upcomingCourses: Array.isArray(backendData.upcoming_courses) + ? backendData.upcoming_courses.map(toCourseViewModel) + : [], + activities: Array.isArray(backendData.activities) ? backendData.activities.map(toActivitySummary) : [], + venue: { + name: backendData.venue?.name || '奥林匹克滑冰中心', + } +}) + +export const homeApi = { + getHome(options = {}) { + const data = { + course_limit: options.courseLimit || 2, + banner_limit: options.bannerLimit || 5 + } + if (options.activityLimit !== undefined) { + data.activity_limit = options.activityLimit + } + if (options.courseDate) data.course_date = options.courseDate + + return request({ + url: '/api/contents/home', + data + }).then(toHomeViewModel) + } +} diff --git a/miniprogram/src/modules/ice/api/member.js b/miniprogram/src/modules/ice/api/member.js new file mode 100644 index 0000000..b73d7cb --- /dev/null +++ b/miniprogram/src/modules/ice/api/member.js @@ -0,0 +1,151 @@ +import { authStorage, request } from './request' + +// 防腐层:小程序端只在本文件感知后端字段名,页面层统一使用下方 mapper 输出的前端模型。 +export const memberBackendContract = Object.freeze({ + wechatLoginResponse: [ + 'access_token', + 'token_type', + 'expires_in', + 'member_bound', + 'member' + ], + registerOrBindRequest: [ + 'phone_code', + 'phone', + 'name', + 'gender', + 'birthday' + ], + registerOrBindResponse: [ + 'created', + 'bound', + 'member' + ], + memberSummary: [ + 'id', + 'name', + 'phone', + 'gender', + 'birthday', + 'joined_at' + ], + memberProfile: [ + 'id', + 'name', + 'phone', + 'gender', + 'gender_label', + 'birthday', + 'joined_at', + 'sales.id', + 'sales.name', + 'card_summary.total', + 'card_summary.active', + 'card_summary.disabled', + 'card_summary.expired' + ] +}) + +const GENDER_LABELS = Object.freeze({ + male: '男', + female: '女', + unknown: '未设置' +}) + +const emptyCardSummary = () => ({ + total: 0, + active: 0, + disabled: 0, + expired: 0 +}) + +const toNumber = (value) => { + const numberValue = Number(value) + return Number.isFinite(numberValue) ? numberValue : 0 +} + +const toMemberCardSummary = (backendCardSummary = {}) => ({ + total: toNumber(backendCardSummary.total), + active: toNumber(backendCardSummary.active), + disabled: toNumber(backendCardSummary.disabled), + expired: toNumber(backendCardSummary.expired) +}) + +const toMemberViewModel = (backendMember, options = {}) => { + if (!backendMember || !backendMember.id) return null + + const cardSummary = options.withProfileFields + ? toMemberCardSummary(backendMember.card_summary) + : emptyCardSummary() + + return { + id: backendMember.id, + name: backendMember.name || '', + phone: backendMember.phone || '', + gender: backendMember.gender || 'unknown', + genderLabel: backendMember.gender_label || GENDER_LABELS[backendMember.gender] || GENDER_LABELS.unknown, + birthday: backendMember.birthday || '', + joinDate: backendMember.joined_at || '', + salesId: backendMember.sales?.id || null, + salesName: backendMember.sales?.name || '无', + cardSummary, + cardCount: cardSummary.total + } +} + +const toWechatLoginViewModel = (backendData = {}) => ({ + accessToken: backendData.access_token || '', + tokenType: backendData.token_type || 'Bearer', + expiresIn: toNumber(backendData.expires_in), + memberBound: backendData.member_bound === true, + member: toMemberViewModel(backendData.member) +}) + +const toRegisterOrBindPayload = (formData = {}) => ({ + phone_code: formData.phoneCode || null, + phone: formData.phone || null, + name: formData.name || null, + gender: formData.gender || 'unknown', + birthday: formData.birthday || null +}) + +const toRegisterOrBindViewModel = (backendData = {}) => ({ + created: backendData.created === true, + bound: backendData.bound === true, + member: toMemberViewModel(backendData.member) +}) + +const toMemberProfileViewModel = (backendData = {}) => { + return toMemberViewModel(backendData, { withProfileFields: true }) +} + +// 登录注册与绑定 API 接口组 +export const api = { + // 1. 微信小程序登录 (用 code 换 token) + wechatLogin(code) { + return request({ + url: '/api/auth/wechat-login', + method: 'POST', + data: { code } + }).then(toWechatLoginViewModel) + }, + + // 2. 绑定或注册会员 + registerOrBind(data) { + return request({ + url: '/api/auth/register-or-bind', + method: 'POST', + data: toRegisterOrBindPayload(data) + }).then(toRegisterOrBindViewModel) + }, + + // 3. 获取当前登录会员信息 + getMemberMe() { + return request({ + url: '/api/members/me', + method: 'GET' + }).then(toMemberProfileViewModel) + } +} + +export { authStorage, request } diff --git a/miniprogram/src/modules/ice/api/request.js b/miniprogram/src/modules/ice/api/request.js new file mode 100644 index 0000000..a79c13d --- /dev/null +++ b/miniprogram/src/modules/ice/api/request.js @@ -0,0 +1,134 @@ +const KEY_TOKEN = 'ICE_MEMBER_TOKEN' +const KEY_TOKEN_EXPIRES_AT = 'ICE_MEMBER_TOKEN_EXPIRES_AT' + +const API_BASE_URL = import.meta.env.VITE_ICE_API_BASE_URL || 'http://127.0.0.1:8000' + +export const authStorage = { + setToken(token, expiresIn) { + uni.setStorageSync(KEY_TOKEN, token) + if (expiresIn) { + uni.setStorageSync(KEY_TOKEN_EXPIRES_AT, Date.now() + Number(expiresIn) * 1000) + } + }, + getToken() { + return uni.getStorageSync(KEY_TOKEN) + }, + isTokenExpired() { + const expiresAt = Number(uni.getStorageSync(KEY_TOKEN_EXPIRES_AT) || 0) + return expiresAt > 0 && Date.now() >= expiresAt + }, + clear() { + uni.removeStorageSync(KEY_TOKEN) + uni.removeStorageSync(KEY_TOKEN_EXPIRES_AT) + } +} + +export const normalizeApiError = (rawError = {}) => { + const statusCode = rawError.statusCode + const body = rawError.data || rawError + const code = body?.code + const backendMessage = body?.message || '' + + if (statusCode === 401 || code === 40001) { + return { + type: 'auth_expired', + code, + statusCode, + message: '登录状态已过期,请重新登录', + raw: rawError + } + } + + if (code === 40002) { + return { + type: 'profile_required', + code, + statusCode, + message: '请先完善会员资料', + raw: rawError + } + } + + if (code === 40003) { + return { + type: 'bind_conflict', + code, + statusCode, + message: backendMessage || '该手机号已绑定其他微信账号,请联系场馆前台处理', + raw: rawError + } + } + + if (!statusCode) { + return { + type: 'network_error', + code, + statusCode, + message: '网络连接失败,请检查后重试', + raw: rawError + } + } + + if (statusCode === 404 || code === 40400) { + return { + type: 'not_found', + code, + statusCode, + message: backendMessage || '未找到对应信息或无访问权限', + raw: rawError + } + } + + return { + type: 'api_error', + code, + statusCode, + message: backendMessage || '服务暂时不可用,请稍后重试', + raw: rawError + } +} + +export function request(options = {}) { + const token = authStorage.getToken() + const header = { + 'Content-Type': 'application/json', + ...(options.header || {}) + } + + if (token) { + header.Authorization = `Bearer ${token}` + } + + return new Promise((resolve, reject) => { + uni.request({ + url: options.url.startsWith('http') ? options.url : `${API_BASE_URL}${options.url}`, + method: options.method || 'GET', + data: options.data, + header, + success: (res) => { + const body = res.data + + if (res.statusCode >= 200 && res.statusCode < 300) { + if (body && body.code === 0) { + resolve(body.data) + return + } + + reject(normalizeApiError({ + statusCode: res.statusCode, + data: body + })) + return + } + + reject(normalizeApiError({ + statusCode: res.statusCode, + data: body + })) + }, + fail: (err) => { + reject(normalizeApiError(err)) + } + }) + }) +} diff --git a/miniprogram/src/modules/ice/api/session.js b/miniprogram/src/modules/ice/api/session.js new file mode 100644 index 0000000..6dbc9d9 --- /dev/null +++ b/miniprogram/src/modules/ice/api/session.js @@ -0,0 +1,108 @@ +import { api, authStorage } from './member' + +export const SESSION_STATUS = Object.freeze({ + CHECKING: 'checking', + GUEST: 'guest', + EXPIRED: 'expired', + PROFILE_REQUIRED: 'profile_required', + MEMBER: 'member', + NETWORK_ERROR: 'network_error' +}) + +const createLoginCode = () => new Promise((resolve, reject) => { + uni.login({ + provider: 'weixin', + success: (loginRes) => { + if (loginRes.code) { + resolve(loginRes.code) + return + } + reject({ + type: 'login_error', + message: '微信登录没有返回有效凭证,请重新尝试' + }) + }, + fail: () => { + reject({ + type: 'login_error', + message: '微信登录失败,请稍后重试' + }) + } + }) +}) + +export const loginAndStoreToken = async () => { + const code = await createLoginCode() + const data = await api.wechatLogin(code) + if (!data?.accessToken) { + throw { + type: 'login_error', + message: '登录失败,请稍后重试' + } + } + authStorage.setToken(data.accessToken, data.expiresIn) + return data +} + +export const getMemberSession = async () => { + const token = authStorage.getToken() + if (!token) { + return { status: SESSION_STATUS.GUEST } + } + + if (authStorage.isTokenExpired()) { + authStorage.clear() + return { + status: SESSION_STATUS.EXPIRED, + message: '登录状态已过期,请重新登录' + } + } + + try { + const member = await api.getMemberMe() + return { + status: SESSION_STATUS.MEMBER, + member + } + } catch (err) { + if (err?.type === 'profile_required' || err?.code === 40002) { + return { + status: SESSION_STATUS.PROFILE_REQUIRED, + message: '请先完善会员资料' + } + } + + if (err?.type === 'auth_expired' || err?.code === 40001) { + authStorage.clear() + return { + status: SESSION_STATUS.EXPIRED, + message: '登录状态已过期,请重新登录' + } + } + + return { + status: SESSION_STATUS.NETWORK_ERROR, + message: err?.message || '暂时无法连接服务,请稍后重试', + error: err + } + } +} + +export const isSessionBlockingError = (err) => { + return ['auth_expired', 'profile_required'].includes(err?.type) || [40001, 40002].includes(err?.code) +} + +export const sessionStatusFromError = (err) => { + if (err?.type === 'auth_expired' || err?.code === 40001) { + authStorage.clear() + return SESSION_STATUS.EXPIRED + } + if (err?.type === 'profile_required' || err?.code === 40002) { + return SESSION_STATUS.PROFILE_REQUIRED + } + return '' +} + +export const errorMessage = (err, fallback = '操作失败,请稍后重试') => { + return err?.message || fallback +} diff --git a/miniprogram/src/pages.json b/miniprogram/src/pages.json index 80a18e6..12c4bd6 100644 --- a/miniprogram/src/pages.json +++ b/miniprogram/src/pages.json @@ -136,6 +136,71 @@ "style": { "navigationBarTitleText": "个人信息" } + }, + { + "path": "pages/ice/home/index", + "style": { + "navigationBarTitleText": "首页", + "enablePullDownRefresh": true + } + }, + { + "path": "pages/ice/courses/index", + "style": { + "navigationBarTitleText": "近期课程", + "enablePullDownRefresh": true + } + }, + { + "path": "pages/ice/bookings/index", + "style": { + "navigationBarTitleText": "我的预约", + "enablePullDownRefresh": true + } + }, + { + "path": "pages/ice/profile/index", + "style": { + "navigationBarTitleText": "会员中心" + } + }, + { + "path": "pages/ice/bookings/detail", + "style": { + "navigationBarTitleText": "预约详情" + } + }, + { + "path": "pages/ice/courses/detail", + "style": { + "navigationBarTitleText": "课程详情" + } + }, + { + "path": "pages/ice/profile/cards", + "style": { + "navigationBarTitleText": "我的会员卡", + "enablePullDownRefresh": true + } + }, + { + "path": "pages/ice/profile/card-history", + "style": { + "navigationBarTitleText": "会员卡使用流水", + "enablePullDownRefresh": true + } + }, + { + "path": "pages/ice/profile/detail", + "style": { + "navigationBarTitleText": "个人信息" + } + }, + { + "path": "pages/ice/activities/detail", + "style": { + "navigationBarTitleText": "活动详情" + } } ], "globalStyle": { diff --git a/miniprogram/src/pages/ice/activities/detail.vue b/miniprogram/src/pages/ice/activities/detail.vue new file mode 100644 index 0000000..130cb2e --- /dev/null +++ b/miniprogram/src/pages/ice/activities/detail.vue @@ -0,0 +1,157 @@ + + + + + diff --git a/miniprogram/src/pages/ice/bookings/detail.vue b/miniprogram/src/pages/ice/bookings/detail.vue new file mode 100644 index 0000000..807198f --- /dev/null +++ b/miniprogram/src/pages/ice/bookings/detail.vue @@ -0,0 +1,511 @@ + + + + + diff --git a/miniprogram/src/pages/ice/bookings/index.vue b/miniprogram/src/pages/ice/bookings/index.vue new file mode 100644 index 0000000..8fd1a6e --- /dev/null +++ b/miniprogram/src/pages/ice/bookings/index.vue @@ -0,0 +1,544 @@ + + + + + diff --git a/miniprogram/src/pages/ice/courses/detail.vue b/miniprogram/src/pages/ice/courses/detail.vue new file mode 100644 index 0000000..9674a42 --- /dev/null +++ b/miniprogram/src/pages/ice/courses/detail.vue @@ -0,0 +1,258 @@ + + + + + diff --git a/miniprogram/src/pages/ice/courses/index.vue b/miniprogram/src/pages/ice/courses/index.vue new file mode 100644 index 0000000..8f1b757 --- /dev/null +++ b/miniprogram/src/pages/ice/courses/index.vue @@ -0,0 +1,419 @@ + + + + + diff --git a/miniprogram/src/pages/ice/home/index.vue b/miniprogram/src/pages/ice/home/index.vue new file mode 100644 index 0000000..eb56f28 --- /dev/null +++ b/miniprogram/src/pages/ice/home/index.vue @@ -0,0 +1,474 @@ + + + + + diff --git a/miniprogram/src/pages/ice/profile/card-history.vue b/miniprogram/src/pages/ice/profile/card-history.vue new file mode 100644 index 0000000..d5b082a --- /dev/null +++ b/miniprogram/src/pages/ice/profile/card-history.vue @@ -0,0 +1,516 @@ + + + + + diff --git a/miniprogram/src/pages/ice/profile/cards.vue b/miniprogram/src/pages/ice/profile/cards.vue new file mode 100644 index 0000000..fc99cb6 --- /dev/null +++ b/miniprogram/src/pages/ice/profile/cards.vue @@ -0,0 +1,430 @@ + + + + + diff --git a/miniprogram/src/pages/ice/profile/detail.vue b/miniprogram/src/pages/ice/profile/detail.vue new file mode 100644 index 0000000..c15241e --- /dev/null +++ b/miniprogram/src/pages/ice/profile/detail.vue @@ -0,0 +1,271 @@ + + + + + diff --git a/miniprogram/src/pages/ice/profile/index.vue b/miniprogram/src/pages/ice/profile/index.vue new file mode 100644 index 0000000..6d51470 --- /dev/null +++ b/miniprogram/src/pages/ice/profile/index.vue @@ -0,0 +1,1175 @@ + + + + + diff --git a/miniprogram/src/static/ice/tab/booking-active.png b/miniprogram/src/static/ice/tab/booking-active.png new file mode 100644 index 0000000000000000000000000000000000000000..eacef651076a2984dbffe9fbf38008adb3f47ea8 GIT binary patch literal 433 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7Ro>V65o$NZuCFd`k^8cs1pbh(zZW2czyl*`PJ*Ep6|c? z;`r^yx`hG%I23#s5}7!J9cC~bViCBu=lk7PN2@P>vUc3e+#B%2MZkC30>SS4ulB5F zH^}-JJ-7ZUgPVR`_v;I+t)bir({}Iw_H~c`!FNe-3#@m2?M>$7>ORwO#d(5I)r(C> zdBCQw`ypu%-TjNp7{wy7yFITbp54*Ck)@RrEV1Jg^GxNrC$#5YzWLbKmid~s_0sd( z+SB{ZOBr4_FOt!{1r(M&uxqo#%J07N2SOp5I9{tYY}%~vzc>FE!#9z~85_^Z0a@F+ zKIH&e?}efTpFVl?CFqS}+JURc{`_N8drk5}@%y`%zeY42TE%8?T1P{4{o-Fd61FWt vKmW|uaa`OMAT?)u{h3)W+880h7xC{mtK^gRmj<6Yyg(wJu6{1-oD!M<&rH0( literal 0 HcmV?d00001 diff --git a/miniprogram/src/static/ice/tab/booking.png b/miniprogram/src/static/ice/tab/booking.png new file mode 100644 index 0000000000000000000000000000000000000000..51593c630d29c43cf48207cd338ffc37ba524f62 GIT binary patch literal 429 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7Ro>U@Y-;aSW-L^Y)J8q$UFa)_^&) zUKcS>zo)=nHZ6Gb1V+`Lo)!74MHkq2=5B})18M_;88>w+KYaaHv9D^a&AaUv`?ntt zf3)C_ki!gyLo5Pn4F-%YoC<3y-|xQK{e98X@}@L)sf8ap6=vx;DvRyES{crtaOKf< ztN*JQd*c3xU0=w}wOZ_ePI>*?*FWQ$?jF7Mu&m^@^l>2(v6+l3+Z9y5TuAMf1e+T3 z(Kun7*jM2+6pM6rNnTf;T_~Q+$t4VyD0s?l*XNR!+%vCb2v>!=cQ(VRbNg(*?t966Be-YrQLE}bK$h02-5{2C zj_Z`k-Iu*Lw$ES;SB7ZtVMt`+`26`xWV*xoz2*Mbf%dPKI-s+8LCBuISH=ln1iYSp t&fmn+E9I0~aPOaC_9AIepaVg|&)4h>F6}EjoK%EBd{0+Dmvv4FO#oY0uFn7f literal 0 HcmV?d00001 diff --git a/miniprogram/src/static/ice/tab/course-active.png b/miniprogram/src/static/ice/tab/course-active.png new file mode 100644 index 0000000000000000000000000000000000000000..1f57376e69b5ba2448ed34849c3a1fbeebe6771d GIT binary patch literal 350 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7Ro>U{v*VaSW-L^Y+fcz#|3%435qp zi*s0qah<)<+igDRllXY4vLBhD}WTu zVAz;@?84J*4c71g5D%<$-mmNzRtZ~5nc&*g9CBtGmdF|y*)<7&IHmr;?vK5Y`<#Z$ z>!*Ev@O5`nk}{*8Eo*kd-DLF#TR5^a_AU;;pecCmfz@C0F9*a~ApU*uYrg$;Dd!i{ S_?Lu(L_A&nT-G@yGywpeNQi9! literal 0 HcmV?d00001 diff --git a/miniprogram/src/static/ice/tab/course.png b/miniprogram/src/static/ice/tab/course.png new file mode 100644 index 0000000000000000000000000000000000000000..21f7bda4fa2dc9f651c791a03912e97fedc884d2 GIT binary patch literal 347 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7Ro>U{vySaSW-L^Y+fc+`|R}434TE zyI&pfFF(*US?tqC-OTeQ3QgKV?6xJbKrJA!XZ8A52fsi5S)H3^EC2Yn;lBg2=KFX) z*78~~ARbuhyL4>+$#oo8+d O67h8Pb6Mw<&;$Ux=7^dA literal 0 HcmV?d00001 diff --git a/miniprogram/src/static/ice/tab/home-active.png b/miniprogram/src/static/ice/tab/home-active.png new file mode 100644 index 0000000000000000000000000000000000000000..118a46045a3fbacffaffb7e7e01e08ce02e54450 GIT binary patch literal 496 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7Ro>VBGHM;uumf=j~0$yh8>8t%>Td z^3Gj5{6$4(S^DQ$tGc~JxgJQUzvumDzj8NMTcwZNH!^De z{_^==d2Ld_oyf0}%N1i8J}x?rgyGDk}ZQgzpwM`y%euo-SNpI(P5SSH+2i8Fd`_i)~ALVk{1;FIZ|Z z{n`C}|L@(pJeP6%(e{IfH`s1nYRMc`XHb~+FU;1~{>P14-oM(no^B{z^Z!G&!?kDo z&${yT*G@SZ7CU3EtxtmlV}~Fr5gB(RZl_Ob^_PPz=u%*nC-Zvq?0Hw^U4&>pu!eDm z?bbDms+I@`_&Nn#Q@r1_%7W$VZ)RAaGcYt1^~gUIer*xOw4e$k>gnp|vd$@?2>=}G B)g}M{ literal 0 HcmV?d00001 diff --git a/miniprogram/src/static/ice/tab/home.png b/miniprogram/src/static/ice/tab/home.png new file mode 100644 index 0000000000000000000000000000000000000000..711ddfd16bf6df2903e09c4485805ba8796871d5 GIT binary patch literal 493 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7Ro>VBF&A;uumf=k3kFyh8>8Es5%{ zO24z&6fw*1=xFy6<$55YZruIf|5Y^W>Avg77K#8hLcxJ)J7VhJ*Q;(^sv;`gyZ*ZO zuJfCxwLChx>bwQ-OQsHU-*aM4vnKZT9ZS10Dagm6WZf&C%L(!hH8)(o3#?)8a6DVg zcd)F#r)}Ba`;Wi>+s}QPv!gFTr9_v4?$Mnl z|3Ciyer?`6o(dg19=W!+D|OFGUij498T@(stCv3y9)FkpBL1RY``YBc?f02Cnf=e! zOR9Lg+-KXXLvP+BG9BS?=wU!1&U{k(b~07#_F{fDbSbb(pR(+d-lR-%qiSqlzo;%fkX4NKZ+=!?0)5s~uGhM?QjtJzf1=);T3K0RUCT B+YA5z literal 0 HcmV?d00001 diff --git a/miniprogram/src/static/ice/tab/mine-active.png b/miniprogram/src/static/ice/tab/mine-active.png new file mode 100644 index 0000000000000000000000000000000000000000..5a19691c95474d1d04a0d5d32840a44fdb280783 GIT binary patch literal 469 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7Ro>V4UOW;uumf=j|=Wyk-Ldw?v1# zXKz-`n75?;<;^{LM;8T&cXD-o+++Cuwj1Z+Kh4PkrsX)=JtGh%*=EvVZZy=>>9 z&;X@|nUW4h%pPeBC%sq%gzfu|v0Pw#AE9Bb;={0a`OZUMrgJ^;30`ed-@b$A|EEk1 zhZu{4Q?~AZB4nbR!ti#H=2BmKvj**1t~vksB16vqXUTe8zp%SodXIREwier)hn|TE zT%9em)tv-hva&o_XKLK>_TAa@>ymd@f8P8$QhRQW^~R7M3%8RD=ggLsW_TPkoX99} z?=Fu6ujYToDZ=^e_4QZk4^5b|QvWpD0=*-LcTC&DuYRW^_h#_5NwYuP|F}M`D`zopr E05n3*^8f$< literal 0 HcmV?d00001 diff --git a/miniprogram/src/static/ice/tab/mine.png b/miniprogram/src/static/ice/tab/mine.png new file mode 100644 index 0000000000000000000000000000000000000000..4f3c12568894de14c61eda79aa61f08dba0bd663 GIT binary patch literal 466 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7Ro>V4Ugc;uumf=j|=Wyk-Ldw?v1% z@9rp^zH@**@93f+@m{8`k9!Q?-*)3X{HHlt;IxwGBo!!{b#<4BNdNQitIyy19QbO^ zHOX0VmOZ&MxBQ%6ldtln#pO6#(0aBvq5^Z5Pp>jnVD9Li<;ue-d0_5?Yey|Cmi}M+ za(<0D_p%9#d2KH3iQ8}Nv{H42Y>w2@ox!XZ{LHp`am-*y`oPz^N%;^S9=ovmqX8}IJ@d9yU9Jio5YHhbyhT=i*Pg2y!^58OPqg^%~7g_;q=q203? zS13NUZzy{7_QCu9U;Yx4jJ~GcWVq5=A#r`uw}&m#8+EHsMHLC(ll}i{sm52Xj0Vm# z>mFxcIe9T}j~1^-c(uS41E#3yupotp?WAef>lg}W@BLnx%T@%8D+W(jKbLh*2~7a- C|IAVV literal 0 HcmV?d00001