commit 4149577cae421d0c081639909a5912eed3400447 Author: hailin Date: Wed Mar 25 14:13:36 2026 +0800 feat: initialize WeChat mini program project diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a4e4f95 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.DS_Store +project.private.config.json + +.claude/ +.kiro/ +.qoder/ +.qwen/ +.trae/ +.agents/ + +node_modules/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..e097b0c --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# 云开发 quickstart + +这是云开发的快速启动指引,其中演示了如何上手使用云开发的三大基础能力: + +- 数据库:一个既可在小程序前端操作,也能在云函数中读写的 JSON 文档型数据库 +- 文件存储:在小程序前端直接上传/下载云端文件,在云开发控制台可视化管理 +- 云函数:在云端运行的代码,微信私有协议天然鉴权,开发者只需编写业务逻辑代码 + +## 参考文档 + +- [云开发文档](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/basis/getting-started.html) + diff --git a/cloudfunctions/quickstartFunctions/config.json b/cloudfunctions/quickstartFunctions/config.json new file mode 100644 index 0000000..41a485c --- /dev/null +++ b/cloudfunctions/quickstartFunctions/config.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "openapi": [ + "wxacode.get" + ] + } +} \ No newline at end of file diff --git a/cloudfunctions/quickstartFunctions/index.js b/cloudfunctions/quickstartFunctions/index.js new file mode 100644 index 0000000..1599fb8 --- /dev/null +++ b/cloudfunctions/quickstartFunctions/index.js @@ -0,0 +1,294 @@ +const cloud = require("wx-server-sdk"); +cloud.init({ + env: cloud.DYNAMIC_CURRENT_ENV, +}); + +const db = cloud.database(); + +const DEFAULT_PHOTO_BOOK_HOME = { + photographerName: "陪你拍阿柚", + photographerTagline: "温柔日系私房写真 · 闺蜜情侣生日跟拍", + heroImage: + "https://images.unsplash.com/photo-1524504388940-b1c1722653e1?auto=format&fit=crop&w=1200&q=80", + tags: ["情侣", "闺蜜", "生日", "汉服"], + works: [ + { + id: "w1", + cover: + "https://images.unsplash.com/photo-1521572267360-ee0c2909d518?auto=format&fit=crop&w=900&q=80", + }, + { + id: "w2", + cover: + "https://images.unsplash.com/photo-1524863479829-916d8e77f114?auto=format&fit=crop&w=900&q=80", + }, + { + id: "w3", + cover: + "https://images.unsplash.com/photo-1464863979621-258859e62245?auto=format&fit=crop&w=900&q=80", + }, + ], + packages: [ + { + id: "p1", + badge: "🔥 热门", + title: "2小时轻陪拍", + desc: "含台词一次拍摄片 + 情侣合影", + price: 399, + }, + { + id: "p2", + badge: "", + title: "3小时沉浸拍摄", + desc: "更自由更出片", + price: 699, + }, + ], + ctaText: "去预约一次拍照", +}; + +const safeGetCollection = async (name, query = {}) => { + try { + return await db.collection(name).where(query).get(); + } catch (e) { + return { data: [] }; + } +}; +// 获取openid +const getOpenId = async () => { + // 获取基础信息 + const wxContext = cloud.getWXContext(); + return { + openid: wxContext.OPENID, + appid: wxContext.APPID, + unionid: wxContext.UNIONID, + }; +}; + +// 获取小程序二维码 +const getMiniProgramCode = async () => { + // 获取小程序二维码的buffer + const resp = await cloud.openapi.wxacode.get({ + path: "pages/index/index", + }); + const { buffer } = resp; + // 将图片上传云存储空间 + const upload = await cloud.uploadFile({ + cloudPath: "code.png", + fileContent: buffer, + }); + return upload.fileID; +}; + +// 创建集合 +const createCollection = async () => { + try { + // 创建集合 + await db.createCollection("sales"); + await db.collection("sales").add({ + // data 字段表示需新增的 JSON 数据 + data: { + region: "华东", + city: "上海", + sales: 11, + }, + }); + await db.collection("sales").add({ + // data 字段表示需新增的 JSON 数据 + data: { + region: "华东", + city: "南京", + sales: 11, + }, + }); + await db.collection("sales").add({ + // data 字段表示需新增的 JSON 数据 + data: { + region: "华南", + city: "广州", + sales: 22, + }, + }); + await db.collection("sales").add({ + // data 字段表示需新增的 JSON 数据 + data: { + region: "华南", + city: "深圳", + sales: 22, + }, + }); + return { + success: true, + }; + } catch (e) { + // 这里catch到的是该collection已经存在,从业务逻辑上来说是运行成功的,所以catch返回success给前端,避免工具在前端抛出异常 + return { + success: true, + data: "create collection success", + }; + } +}; + +// 查询数据 +const selectRecord = async () => { + // 返回数据库查询结果 + return await db.collection("sales").get(); +}; + +// 更新数据 +const updateRecord = async (event) => { + try { + // 遍历修改数据库信息 + for (let i = 0; i < event.data.length; i++) { + await db + .collection("sales") + .where({ + _id: event.data[i]._id, + }) + .update({ + data: { + sales: event.data[i].sales, + }, + }); + } + return { + success: true, + data: event.data, + }; + } catch (e) { + return { + success: false, + errMsg: e, + }; + } +}; + +// 新增数据 +const insertRecord = async (event) => { + try { + const insertRecord = event.data; + // 插入数据 + await db.collection("sales").add({ + data: { + region: insertRecord.region, + city: insertRecord.city, + sales: Number(insertRecord.sales), + }, + }); + return { + success: true, + data: event.data, + }; + } catch (e) { + return { + success: false, + errMsg: e, + }; + } +}; + +// 删除数据 +const deleteRecord = async (event) => { + try { + await db + .collection("sales") + .where({ + _id: event.data._id, + }) + .remove(); + return { + success: true, + }; + } catch (e) { + return { + success: false, + errMsg: e, + }; + } +}; + +// 摄影书主页 +const getPhotoBookHome = async () => { + const profileRes = await safeGetCollection("photographer_profiles", { + enabled: true, + }); + const worksRes = await safeGetCollection("photographer_works", { + enabled: true, + }); + const packageRes = await safeGetCollection("photo_packages", { + enabled: true, + }); + + const profile = profileRes.data[0] || {}; + const works = (worksRes.data || []).slice(0, 6); + const packages = (packageRes.data || []).slice(0, 6); + + const fromCloud = Boolean( + profile.photographerName || works.length || packages.length + ); + + if (!fromCloud) { + return { + success: true, + source: "fallback", + data: DEFAULT_PHOTO_BOOK_HOME, + }; + } + + return { + success: true, + source: "cloud", + data: { + photographerName: + profile.photographerName || DEFAULT_PHOTO_BOOK_HOME.photographerName, + photographerTagline: + profile.photographerTagline || + DEFAULT_PHOTO_BOOK_HOME.photographerTagline, + heroImage: profile.heroImage || DEFAULT_PHOTO_BOOK_HOME.heroImage, + tags: profile.tags || DEFAULT_PHOTO_BOOK_HOME.tags, + works: + works.map((item) => ({ + id: item._id, + cover: item.cover, + })) || DEFAULT_PHOTO_BOOK_HOME.works, + packages: + packages.map((item) => ({ + id: item._id, + badge: item.badge || "", + title: item.title, + desc: item.desc, + price: item.price, + })) || DEFAULT_PHOTO_BOOK_HOME.packages, + ctaText: profile.ctaText || DEFAULT_PHOTO_BOOK_HOME.ctaText, + }, + }; +}; + +// const getOpenId = require('./getOpenId/index'); +// const getMiniProgramCode = require('./getMiniProgramCode/index'); +// const createCollection = require('./createCollection/index'); +// const selectRecord = require('./selectRecord/index'); +// const updateRecord = require('./updateRecord/index'); +// const fetchGoodsList = require('./fetchGoodsList/index'); +// const genMpQrcode = require('./genMpQrcode/index'); +// 云函数入口函数 +exports.main = async (event, context) => { + switch (event.type) { + case "getOpenId": + return await getOpenId(); + case "getMiniProgramCode": + return await getMiniProgramCode(); + case "createCollection": + return await createCollection(); + case "selectRecord": + return await selectRecord(); + case "updateRecord": + return await updateRecord(event); + case "insertRecord": + return await insertRecord(event); + case "deleteRecord": + return await deleteRecord(event); + case "getPhotoBookHome": + return await getPhotoBookHome(); + } +}; diff --git a/cloudfunctions/quickstartFunctions/package.json b/cloudfunctions/quickstartFunctions/package.json new file mode 100644 index 0000000..4350dbb --- /dev/null +++ b/cloudfunctions/quickstartFunctions/package.json @@ -0,0 +1,14 @@ +{ + "name": "quickstartFunctions", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "wx-server-sdk": "~2.4.0" + } +} diff --git a/miniprogram/app.js b/miniprogram/app.js new file mode 100644 index 0000000..c861eae --- /dev/null +++ b/miniprogram/app.js @@ -0,0 +1,19 @@ +// app.js +App({ + onLaunch: function () { + this.globalData = { + // env 参数说明: + // env 参数决定接下来小程序发起的云开发调用(wx.cloud.xxx)会请求到哪个云环境的资源 + // 此处请填入环境 ID, 环境 ID 可在微信开发者工具右上顶部工具栏点击云开发按钮打开获取 + env: "", + }; + if (!wx.cloud) { + console.error("请使用 2.2.3 或以上的基础库以使用云能力"); + } else { + wx.cloud.init({ + env: this.globalData.env, + traceUser: true, + }); + } + }, +}); diff --git a/miniprogram/app.json b/miniprogram/app.json new file mode 100644 index 0000000..a79275f --- /dev/null +++ b/miniprogram/app.json @@ -0,0 +1,51 @@ +{ + "pages": [ + "pages/index/index", + "pages/package-detail/index", + "pages/booking/index", + "pages/booking-confirm/index", + "pages/works/index", + "pages/my-bookings/index", + "pages/booking-detail/index", + "pages/example/index" + ], + "window": { + "backgroundColor": "#FFF7F5", + "backgroundTextStyle": "light", + "navigationBarBackgroundColor": "#FFF7F5", + "navigationBarTitleText": "摄影书主页", + "navigationBarTextStyle": "black" + }, + "usingComponents": { + "nav-bar": "/components/nav-bar/index" + }, + "tabBar": { + "color": "#7A7A7A", + "selectedColor": "#FF4D8D", + "backgroundColor": "#FFFDFD", + "borderStyle": "white", + "list": [ + { + "pagePath": "pages/index/index", + "text": "主页", + "iconPath": "images/icons/home.png", + "selectedIconPath": "images/icons/home-active.png" + }, + { + "pagePath": "pages/works/index", + "text": "作品集", + "iconPath": "images/icons/examples.png", + "selectedIconPath": "images/icons/examples-active.png" + }, + { + "pagePath": "pages/my-bookings/index", + "text": "我的预约", + "iconPath": "images/icons/usercenter.png", + "selectedIconPath": "images/icons/usercenter-active.png" + } + ] + }, + "sitemapLocation": "sitemap.json", + "style": "v2", + "lazyCodeLoading": "requiredComponents" +} \ No newline at end of file diff --git a/miniprogram/app.wxss b/miniprogram/app.wxss new file mode 100644 index 0000000..3031e46 --- /dev/null +++ b/miniprogram/app.wxss @@ -0,0 +1,187 @@ +/** app.wxss **/ +page { + --brand-main: #ff4d8d; + --brand-main-press: #e83f7d; + --brand-soft: #fff2f7; + + --bg-base: #fff7f5; + --surface-1: #ffffff; + --surface-2: #fff2f7; + --surface-3: #ffeaf2; + --border-soft: #f3dce6; + + --text-1: #1f1f1f; + --text-2: #5c5c5c; + --text-3: #8a8a8a; + + --state-success: #1fa971; + --state-success-bg: #e8f8f1; + --state-warning: #f59a23; + --state-warning-bg: #fff5e8; + --state-danger: #e5484d; + --state-danger-bg: #ffecee; + --state-pending: #6f6f6f; + --state-pending-bg: #f2f2f2; + + --radius-sm: 12rpx; + --radius-md: 16rpx; + --radius-lg: 20rpx; + --radius-pill: 999rpx; + + --space-1: 8rpx; + --space-2: 12rpx; + --space-3: 16rpx; + --space-4: 20rpx; + --space-5: 24rpx; + --space-6: 32rpx; + + --font-caption: 24rpx; + --font-body: 26rpx; + --font-body-lg: 28rpx; + --font-title-sm: 36rpx; + --font-title-md: 44rpx; + --font-title-lg: 52rpx; + + --btn-h: 88rpx; + + background: var(--bg-base); + color: var(--text-1); + display: flex; + flex-direction: column; +} + +.ui-page { + min-height: 100vh; + box-sizing: border-box; + padding-left: var(--space-4); + padding-right: var(--space-4); + padding-bottom: calc(112rpx + env(safe-area-inset-bottom)); + padding-bottom: calc(112rpx + constant(safe-area-inset-bottom)); +} + +.ui-nav { + height: 88rpx; + display: flex; + align-items: center; + justify-content: space-between; +} + +.ui-nav-side { + min-width: 88rpx; + min-height: 88rpx; + display: flex; + align-items: center; + justify-content: flex-start; + color: var(--text-2); + font-size: var(--font-body); +} + +.ui-nav-title { + font-size: var(--font-title-sm); + font-weight: 700; + color: var(--text-1); +} + +.ui-card { + border-radius: var(--radius-md); + background: var(--surface-1); + border: 1px solid var(--border-soft); + padding: var(--space-4); + box-sizing: border-box; +} + +.ui-btn-primary { + height: var(--btn-h); + border-radius: var(--radius-md); + background: var(--brand-main); + color: #ffffff; + font-size: var(--font-body-lg); + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; +} + +.ui-btn-primary--disabled { + opacity: 0.5; +} + +.ui-btn-secondary { + height: 88rpx; + border-radius: var(--radius-md); + background: #f4f4f4; + color: var(--text-2); + font-size: var(--font-body); + display: flex; + align-items: center; + justify-content: center; +} + +.ui-btn-ghost { + height: 88rpx; + border-radius: var(--radius-md); + background: #ffffff; + border: 1px solid var(--brand-main); + color: var(--brand-main); + font-size: var(--font-body); + display: flex; + align-items: center; + justify-content: center; +} + +.ui-pill { + display: inline-flex; + align-items: center; + border-radius: var(--radius-pill); + min-height: 52rpx; + padding: 0 18rpx; + font-size: var(--font-caption); + font-weight: 600; +} + +.ui-pill-success { + background: var(--state-success-bg); + color: var(--state-success); +} + +.ui-pill-error { + background: var(--state-danger-bg); + color: var(--state-danger); +} + +.ui-pill-pending { + background: var(--state-pending-bg); + color: var(--state-pending); +} + +.ui-pill-warning { + background: var(--state-warning-bg); + color: var(--state-warning); +} + +.ui-state-empty, +.ui-state-error { + margin-top: var(--space-4); + border-radius: var(--radius-md); + padding: var(--space-4); + text-align: center; + font-size: var(--font-body); +} + +.ui-state-empty { + background: #fff; + color: var(--text-3); +} + +.ui-state-error { + background: var(--state-danger-bg); + color: var(--state-danger); +} + +button { + background: initial; +} + +button::after { + border: none; +} diff --git a/miniprogram/components/cloudTipModal/index.js b/miniprogram/components/cloudTipModal/index.js new file mode 100644 index 0000000..8e75cc0 --- /dev/null +++ b/miniprogram/components/cloudTipModal/index.js @@ -0,0 +1,28 @@ +Component({ + + /** + * 页面的初始数据 + */ + data: { + showTip: false, + }, + properties: { + showTipProps: Boolean, + title:String, + content:String + }, + observers: { + showTipProps: function(showTipProps) { + this.setData({ + showTip: showTipProps + }); + } + }, + methods: { + onClose(){ + this.setData({ + showTip: !this.data.showTip + }); + }, + } +}); diff --git a/miniprogram/components/cloudTipModal/index.json b/miniprogram/components/cloudTipModal/index.json new file mode 100644 index 0000000..ee2988f --- /dev/null +++ b/miniprogram/components/cloudTipModal/index.json @@ -0,0 +1,4 @@ +{ + "usingComponents": {}, + "component": true +} diff --git a/miniprogram/components/cloudTipModal/index.wxml b/miniprogram/components/cloudTipModal/index.wxml new file mode 100644 index 0000000..b5b6700 --- /dev/null +++ b/miniprogram/components/cloudTipModal/index.wxml @@ -0,0 +1,10 @@ + + + + + + + {{title}} + {{content}} + + diff --git a/miniprogram/components/cloudTipModal/index.wxss b/miniprogram/components/cloudTipModal/index.wxss new file mode 100644 index 0000000..862f70c --- /dev/null +++ b/miniprogram/components/cloudTipModal/index.wxss @@ -0,0 +1,60 @@ +.install_tip_back { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: rgba(0,0,0,0.4); + z-index: 1; +} +.install_tip_close{ + position:absolute; + right: 10rpx; + top: 10rpx; + width: 32px; + height: 32px; + /* background-color: red; */ +} +.install_tip_detail { + position: fixed; + background-color: white; + right: 0; + bottom: 0; + left: 0; + border-radius: 40rpx 40rpx 0 0; + padding: 50rpx 50rpx 100rpx 50rpx; + z-index: 9; +} + +.install_tip_detail_title { + font-weight: 400; + font-size: 40rpx; + text-align: center; +} + +.install_tip_detail_tip { + font-size: 25rpx; + color: rgba(0,0,0,0.4); + margin-top: 20rpx; + text-align: left; +} +.install_tip_detail_buttons { + padding-top: 50rpx; + display: flex; +} +.install_tip_detail_button { + color: #07C160; + font-weight: 500; + background-color: rgba(0,0,0,0.1); + width: 40%; + text-align: center; + /* height: 90rpx; */ + /* line-height: 90rpx; */ + border-radius: 10rpx; + margin: 0 auto; +} + +.install_tip_detail_button_primary { + background-color: #07C160; + color: #fff; +} \ No newline at end of file diff --git a/miniprogram/components/nav-bar/index.js b/miniprogram/components/nav-bar/index.js new file mode 100644 index 0000000..9c1de05 --- /dev/null +++ b/miniprogram/components/nav-bar/index.js @@ -0,0 +1,75 @@ +Component({ + properties: { + title: { + type: String, + value: "", + }, + showBack: { + type: Boolean, + value: false, + }, + rightText: { + type: String, + value: "", + }, + theme: { + type: String, + value: "blur", + }, + }, + + data: { + statusBarHeight: 20, + navBarHeight: 44, + totalHeight: 64, + sideWidth: 88, + }, + + lifetimes: { + attached() { + this.initMetrics(); + }, + }, + + methods: { + initMetrics() { + const systemInfo = wx.getSystemInfoSync ? wx.getSystemInfoSync() : {}; + const menuRect = wx.getMenuButtonBoundingClientRect + ? wx.getMenuButtonBoundingClientRect() + : null; + + const statusBarHeight = systemInfo.statusBarHeight || 20; + let navBarHeight = 44; + let sideWidth = 88; + + if (menuRect && menuRect.top) { + const gap = menuRect.top - statusBarHeight; + navBarHeight = menuRect.height + gap * 2; + sideWidth = Math.max(menuRect.width, 88); + } + + this.setData({ + statusBarHeight, + navBarHeight, + totalHeight: statusBarHeight + navBarHeight, + sideWidth, + }); + }, + + onTapBack() { + const pages = getCurrentPages(); + if (pages.length > 1) { + wx.navigateBack(); + } else { + wx.switchTab({ + url: "/pages/index/index", + }); + } + this.triggerEvent("back"); + }, + + onTapRight() { + this.triggerEvent("righttap"); + }, + }, +}); diff --git a/miniprogram/components/nav-bar/index.json b/miniprogram/components/nav-bar/index.json new file mode 100644 index 0000000..467ce29 --- /dev/null +++ b/miniprogram/components/nav-bar/index.json @@ -0,0 +1,3 @@ +{ + "component": true +} diff --git a/miniprogram/components/nav-bar/index.wxml b/miniprogram/components/nav-bar/index.wxml new file mode 100644 index 0000000..fb63253 --- /dev/null +++ b/miniprogram/components/nav-bar/index.wxml @@ -0,0 +1,15 @@ + + + + + + 返回 + + + {{title}} + + + {{rightText}} + + + diff --git a/miniprogram/components/nav-bar/index.wxss b/miniprogram/components/nav-bar/index.wxss new file mode 100644 index 0000000..6ae16a5 --- /dev/null +++ b/miniprogram/components/nav-bar/index.wxss @@ -0,0 +1,69 @@ +.nav-placeholder { + width: 100%; +} + +.nav-fixed { + position: fixed; + left: 0; + right: 0; + top: 0; + z-index: 1000; + box-sizing: border-box; +} + +.theme-blur { + background: rgba(255, 255, 255, 0.82); + backdrop-filter: blur(20rpx); + -webkit-backdrop-filter: blur(20rpx); + border-bottom: 1px solid rgba(243, 220, 230, 0.8); +} + +.theme-solid { + background: #fffdfd; + border-bottom: 1px solid rgba(243, 220, 230, 0.9); +} + +.theme-transparent { + background: transparent; +} + +.nav-content { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 20rpx; + box-sizing: border-box; +} + +.side { + min-height: 44px; + display: flex; + align-items: center; +} + +.side.right { + justify-content: flex-end; +} + +.back-btn, +.right-text { + min-height: 44px; + padding: 0 12rpx; + display: flex; + align-items: center; + font-size: 28rpx; + color: #2b2b2b; +} + +.title { + flex: 1; + text-align: center; + font-size: 34rpx; + line-height: 1; + font-weight: 700; + color: #1f1f1f; + padding: 0 12rpx; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/miniprogram/envList.js b/miniprogram/envList.js new file mode 100644 index 0000000..e9a169e --- /dev/null +++ b/miniprogram/envList.js @@ -0,0 +1,6 @@ +const envList = []; +const isMac = false; +module.exports = { + envList, + isMac +}; diff --git a/miniprogram/images/arrow.svg b/miniprogram/images/arrow.svg new file mode 100644 index 0000000..cd32a7d --- /dev/null +++ b/miniprogram/images/arrow.svg @@ -0,0 +1,11 @@ + + + ☀ iOS/☀ 图标/线型/icons_outlined_arrow@3x + + + + + + + + \ No newline at end of file diff --git a/miniprogram/images/avatar.png b/miniprogram/images/avatar.png new file mode 100644 index 0000000..48d5ee2 Binary files /dev/null and b/miniprogram/images/avatar.png differ diff --git a/miniprogram/images/copy.svg b/miniprogram/images/copy.svg new file mode 100644 index 0000000..63759cb --- /dev/null +++ b/miniprogram/images/copy.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/miniprogram/images/icons/avatar.png b/miniprogram/images/icons/avatar.png new file mode 100644 index 0000000..45744b3 Binary files /dev/null and b/miniprogram/images/icons/avatar.png differ diff --git a/miniprogram/images/icons/business-active.png b/miniprogram/images/icons/business-active.png new file mode 100644 index 0000000..690c5b3 Binary files /dev/null and b/miniprogram/images/icons/business-active.png differ diff --git a/miniprogram/images/icons/business.png b/miniprogram/images/icons/business.png new file mode 100644 index 0000000..af0fbcf Binary files /dev/null and b/miniprogram/images/icons/business.png differ diff --git a/miniprogram/images/icons/close.png b/miniprogram/images/icons/close.png new file mode 100644 index 0000000..18f01a2 Binary files /dev/null and b/miniprogram/images/icons/close.png differ diff --git a/miniprogram/images/icons/copy.png b/miniprogram/images/icons/copy.png new file mode 100644 index 0000000..d2d8f61 Binary files /dev/null and b/miniprogram/images/icons/copy.png differ diff --git a/miniprogram/images/icons/customer-service.svg b/miniprogram/images/icons/customer-service.svg new file mode 100644 index 0000000..be7bba4 --- /dev/null +++ b/miniprogram/images/icons/customer-service.svg @@ -0,0 +1,3 @@ + + + diff --git a/miniprogram/images/icons/examples-active.png b/miniprogram/images/icons/examples-active.png new file mode 100644 index 0000000..a5715d7 Binary files /dev/null and b/miniprogram/images/icons/examples-active.png differ diff --git a/miniprogram/images/icons/examples.png b/miniprogram/images/icons/examples.png new file mode 100644 index 0000000..7ce740b Binary files /dev/null and b/miniprogram/images/icons/examples.png differ diff --git a/miniprogram/images/icons/goods-active.png b/miniprogram/images/icons/goods-active.png new file mode 100644 index 0000000..54a4680 Binary files /dev/null and b/miniprogram/images/icons/goods-active.png differ diff --git a/miniprogram/images/icons/goods.png b/miniprogram/images/icons/goods.png new file mode 100644 index 0000000..c1ec7ed Binary files /dev/null and b/miniprogram/images/icons/goods.png differ diff --git a/miniprogram/images/icons/home-active.png b/miniprogram/images/icons/home-active.png new file mode 100644 index 0000000..c1107cb Binary files /dev/null and b/miniprogram/images/icons/home-active.png differ diff --git a/miniprogram/images/icons/home.png b/miniprogram/images/icons/home.png new file mode 100644 index 0000000..e598e18 Binary files /dev/null and b/miniprogram/images/icons/home.png differ diff --git a/miniprogram/images/icons/question.svg b/miniprogram/images/icons/question.svg new file mode 100644 index 0000000..3389b03 --- /dev/null +++ b/miniprogram/images/icons/question.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/miniprogram/images/icons/setting.svg b/miniprogram/images/icons/setting.svg new file mode 100644 index 0000000..1c41dab --- /dev/null +++ b/miniprogram/images/icons/setting.svg @@ -0,0 +1,4 @@ + + + + diff --git a/miniprogram/images/icons/share.svg b/miniprogram/images/icons/share.svg new file mode 100644 index 0000000..297275e --- /dev/null +++ b/miniprogram/images/icons/share.svg @@ -0,0 +1,4 @@ + + + + diff --git a/miniprogram/images/icons/usercenter-active.png b/miniprogram/images/icons/usercenter-active.png new file mode 100644 index 0000000..38a93cf Binary files /dev/null and b/miniprogram/images/icons/usercenter-active.png differ diff --git a/miniprogram/images/icons/usercenter.png b/miniprogram/images/icons/usercenter.png new file mode 100644 index 0000000..e0e0131 Binary files /dev/null and b/miniprogram/images/icons/usercenter.png differ diff --git a/miniprogram/images/images/微信图片_2026-03-25_135106_021.jpg b/miniprogram/images/images/微信图片_2026-03-25_135106_021.jpg new file mode 100644 index 0000000..f20d599 Binary files /dev/null and b/miniprogram/images/images/微信图片_2026-03-25_135106_021.jpg differ diff --git a/miniprogram/images/images/微信图片_2026-03-25_135132_252.jpg b/miniprogram/images/images/微信图片_2026-03-25_135132_252.jpg new file mode 100644 index 0000000..8074477 Binary files /dev/null and b/miniprogram/images/images/微信图片_2026-03-25_135132_252.jpg differ diff --git a/miniprogram/images/images/微信图片_2026-03-25_135136_069.jpg b/miniprogram/images/images/微信图片_2026-03-25_135136_069.jpg new file mode 100644 index 0000000..2ec68ee Binary files /dev/null and b/miniprogram/images/images/微信图片_2026-03-25_135136_069.jpg differ diff --git a/miniprogram/images/images/微信图片_2026-03-25_135139_015.jpg b/miniprogram/images/images/微信图片_2026-03-25_135139_015.jpg new file mode 100644 index 0000000..2bed20e Binary files /dev/null and b/miniprogram/images/images/微信图片_2026-03-25_135139_015.jpg differ diff --git a/miniprogram/images/images/微信图片_2026-03-25_135142_579.jpg b/miniprogram/images/images/微信图片_2026-03-25_135142_579.jpg new file mode 100644 index 0000000..09bd12c Binary files /dev/null and b/miniprogram/images/images/微信图片_2026-03-25_135142_579.jpg differ diff --git a/miniprogram/images/images/微信图片_2026-03-25_135145_488.jpg b/miniprogram/images/images/微信图片_2026-03-25_135145_488.jpg new file mode 100644 index 0000000..bbcad86 Binary files /dev/null and b/miniprogram/images/images/微信图片_2026-03-25_135145_488.jpg differ diff --git a/miniprogram/images/images/微信图片_2026-03-25_135148_396.jpg b/miniprogram/images/images/微信图片_2026-03-25_135148_396.jpg new file mode 100644 index 0000000..6bf4810 Binary files /dev/null and b/miniprogram/images/images/微信图片_2026-03-25_135148_396.jpg differ diff --git a/miniprogram/images/images/微信图片_2026-03-25_135151_646.jpg b/miniprogram/images/images/微信图片_2026-03-25_135151_646.jpg new file mode 100644 index 0000000..f04580c Binary files /dev/null and b/miniprogram/images/images/微信图片_2026-03-25_135151_646.jpg differ diff --git a/miniprogram/images/images/微信图片_2026-03-25_135154_438.jpg b/miniprogram/images/images/微信图片_2026-03-25_135154_438.jpg new file mode 100644 index 0000000..bb8c6b2 Binary files /dev/null and b/miniprogram/images/images/微信图片_2026-03-25_135154_438.jpg differ diff --git a/miniprogram/images/images/微信图片_2026-03-25_135204_816.jpg b/miniprogram/images/images/微信图片_2026-03-25_135204_816.jpg new file mode 100644 index 0000000..5ca813b Binary files /dev/null and b/miniprogram/images/images/微信图片_2026-03-25_135204_816.jpg differ diff --git a/miniprogram/images/images/微信图片_2026-03-25_135209_053.jpg b/miniprogram/images/images/微信图片_2026-03-25_135209_053.jpg new file mode 100644 index 0000000..81f07d5 Binary files /dev/null and b/miniprogram/images/images/微信图片_2026-03-25_135209_053.jpg differ diff --git a/miniprogram/images/images/微信图片_2026-03-25_135212_097.jpg b/miniprogram/images/images/微信图片_2026-03-25_135212_097.jpg new file mode 100644 index 0000000..8685607 Binary files /dev/null and b/miniprogram/images/images/微信图片_2026-03-25_135212_097.jpg differ diff --git a/miniprogram/images/images/微信图片_2026-03-25_135214_854.jpg b/miniprogram/images/images/微信图片_2026-03-25_135214_854.jpg new file mode 100644 index 0000000..2c7d0a3 Binary files /dev/null and b/miniprogram/images/images/微信图片_2026-03-25_135214_854.jpg differ diff --git a/miniprogram/images/images/微信图片_2026-03-25_135217_621.jpg b/miniprogram/images/images/微信图片_2026-03-25_135217_621.jpg new file mode 100644 index 0000000..db0f7a5 Binary files /dev/null and b/miniprogram/images/images/微信图片_2026-03-25_135217_621.jpg differ diff --git a/miniprogram/images/images/微信图片_2026-03-25_135220_802.jpg b/miniprogram/images/images/微信图片_2026-03-25_135220_802.jpg new file mode 100644 index 0000000..6b1c519 Binary files /dev/null and b/miniprogram/images/images/微信图片_2026-03-25_135220_802.jpg differ diff --git a/miniprogram/images/images/微信图片_2026-03-25_135224_237.jpg b/miniprogram/images/images/微信图片_2026-03-25_135224_237.jpg new file mode 100644 index 0000000..5befe71 Binary files /dev/null and b/miniprogram/images/images/微信图片_2026-03-25_135224_237.jpg differ diff --git a/miniprogram/images/images/微信图片_2026-03-25_135227_033.jpg b/miniprogram/images/images/微信图片_2026-03-25_135227_033.jpg new file mode 100644 index 0000000..a27acd6 Binary files /dev/null and b/miniprogram/images/images/微信图片_2026-03-25_135227_033.jpg differ diff --git a/miniprogram/images/images/微信图片_2026-03-25_135229_968.jpg b/miniprogram/images/images/微信图片_2026-03-25_135229_968.jpg new file mode 100644 index 0000000..dbedba6 Binary files /dev/null and b/miniprogram/images/images/微信图片_2026-03-25_135229_968.jpg differ diff --git a/miniprogram/images/images/微信图片_2026-03-25_135239_952.jpg b/miniprogram/images/images/微信图片_2026-03-25_135239_952.jpg new file mode 100644 index 0000000..6b012f3 Binary files /dev/null and b/miniprogram/images/images/微信图片_2026-03-25_135239_952.jpg differ diff --git a/miniprogram/images/images/微信图片_2026-03-25_135243_354.jpg b/miniprogram/images/images/微信图片_2026-03-25_135243_354.jpg new file mode 100644 index 0000000..7f91195 Binary files /dev/null and b/miniprogram/images/images/微信图片_2026-03-25_135243_354.jpg differ diff --git a/miniprogram/images/images/微信图片_2026-03-25_135245_917.jpg b/miniprogram/images/images/微信图片_2026-03-25_135245_917.jpg new file mode 100644 index 0000000..b1454c1 Binary files /dev/null and b/miniprogram/images/images/微信图片_2026-03-25_135245_917.jpg differ diff --git a/miniprogram/images/images/微信图片_2026-03-25_135248_645.jpg b/miniprogram/images/images/微信图片_2026-03-25_135248_645.jpg new file mode 100644 index 0000000..d354dd5 Binary files /dev/null and b/miniprogram/images/images/微信图片_2026-03-25_135248_645.jpg differ diff --git a/miniprogram/images/images/微信图片_2026-03-25_135251_181.jpg b/miniprogram/images/images/微信图片_2026-03-25_135251_181.jpg new file mode 100644 index 0000000..34e4fc1 Binary files /dev/null and b/miniprogram/images/images/微信图片_2026-03-25_135251_181.jpg differ diff --git a/miniprogram/images/images/微信图片_2026-03-25_135253_700.jpg b/miniprogram/images/images/微信图片_2026-03-25_135253_700.jpg new file mode 100644 index 0000000..d3a655a Binary files /dev/null and b/miniprogram/images/images/微信图片_2026-03-25_135253_700.jpg differ diff --git a/miniprogram/images/images/微信图片_2026-03-25_135256_350.jpg b/miniprogram/images/images/微信图片_2026-03-25_135256_350.jpg new file mode 100644 index 0000000..bc78658 Binary files /dev/null and b/miniprogram/images/images/微信图片_2026-03-25_135256_350.jpg differ diff --git a/miniprogram/images/images/微信图片_2026-03-25_135259_199.jpg b/miniprogram/images/images/微信图片_2026-03-25_135259_199.jpg new file mode 100644 index 0000000..2e4123f Binary files /dev/null and b/miniprogram/images/images/微信图片_2026-03-25_135259_199.jpg differ diff --git a/miniprogram/images/images/微信图片_2026-03-25_135301_709.jpg b/miniprogram/images/images/微信图片_2026-03-25_135301_709.jpg new file mode 100644 index 0000000..2d7a3a9 Binary files /dev/null and b/miniprogram/images/images/微信图片_2026-03-25_135301_709.jpg differ diff --git a/miniprogram/mock/assets.js b/miniprogram/mock/assets.js new file mode 100644 index 0000000..733dee0 --- /dev/null +++ b/miniprogram/mock/assets.js @@ -0,0 +1,39 @@ +const customerWorks = [ + "/images/images/微信图片_2026-03-25_135106_021.jpg", + "/images/images/微信图片_2026-03-25_135132_252.jpg", + "/images/images/微信图片_2026-03-25_135136_069.jpg", + "/images/images/微信图片_2026-03-25_135139_015.jpg", + "/images/images/微信图片_2026-03-25_135142_579.jpg", + "/images/images/微信图片_2026-03-25_135145_488.jpg", + "/images/images/微信图片_2026-03-25_135148_396.jpg", + "/images/images/微信图片_2026-03-25_135151_646.jpg", + "/images/images/微信图片_2026-03-25_135154_438.jpg", + "/images/images/微信图片_2026-03-25_135204_816.jpg", + "/images/images/微信图片_2026-03-25_135209_053.jpg", + "/images/images/微信图片_2026-03-25_135212_097.jpg", + "/images/images/微信图片_2026-03-25_135214_854.jpg", + "/images/images/微信图片_2026-03-25_135217_621.jpg", + "/images/images/微信图片_2026-03-25_135220_802.jpg", + "/images/images/微信图片_2026-03-25_135224_237.jpg", + "/images/images/微信图片_2026-03-25_135227_033.jpg", + "/images/images/微信图片_2026-03-25_135229_968.jpg", + "/images/images/微信图片_2026-03-25_135239_952.jpg", + "/images/images/微信图片_2026-03-25_135243_354.jpg", + "/images/images/微信图片_2026-03-25_135245_917.jpg", + "/images/images/微信图片_2026-03-25_135248_645.jpg", + "/images/images/微信图片_2026-03-25_135251_181.jpg", + "/images/images/微信图片_2026-03-25_135253_700.jpg", + "/images/images/微信图片_2026-03-25_135256_350.jpg", + "/images/images/微信图片_2026-03-25_135259_199.jpg", + "/images/images/微信图片_2026-03-25_135301_709.jpg", +]; + +const mockAssets = { + hero: customerWorks[0], + banner: customerWorks[3], + works: customerWorks, +}; + +module.exports = { + mockAssets, +}; diff --git a/miniprogram/mock/photoBook.js b/miniprogram/mock/photoBook.js new file mode 100644 index 0000000..f78b047 --- /dev/null +++ b/miniprogram/mock/photoBook.js @@ -0,0 +1,161 @@ +const { mockAssets } = require("./assets"); + +const categories = ["情侣", "闺蜜", "生日", "汉服"]; +const sceneTags = ["公园", "街拍", "夜景", "室内"]; + +const worksCatalog = mockAssets.works.map((url, idx) => ({ + id: `work_${idx + 1}`, + cover: url, + category: categories[idx % categories.length], + ratio: idx % 3 === 0 ? "wide" : "tall", + title: `${categories[idx % categories.length]}客片 ${idx + 1}`, + scene: sceneTags[idx % sceneTags.length], +})); + +const photoBookHome = { + photographerName: "陪你拍阿柚", + photographerTagline: "你负责开心,我负责把自然瞬间拍好看", + heroImage: mockAssets.hero, + tags: categories, + works: worksCatalog.slice(0, 6), + portfolioCount: worksCatalog.length, + updateText: "最近 7 天新增 12 组客片", + packages: [ + { + id: "pkg_1", + badge: "精选", + title: "2小时轻陪拍", + desc: "适合第一次拍|情侣日常", + price: 399, + originalPrice: 459, + availableSlots: "本周剩余 3 档", + ctaLabel: "立即预约", + }, + { + id: "pkg_2", + badge: "高人气", + title: "3小时沉浸拍摄", + desc: "适合故事感记录|含修图", + price: 569, + originalPrice: 639, + availableSlots: "本周剩余 2 档", + ctaLabel: "立即预约", + }, + ], +}; + +const packageDetail = { + id: "pkg_1", + title: "情侣纪念日 · 2h", + price: 899, + originalPrice: 999, + availableSlots: "本周可约:周六下午 / 周日上午", + cover: mockAssets.banner, + gains: [ + "轻松微步式拍摄,不用担心不会摆姿势", + "72小时内初修件片,7天内全部交付", + "可免费改档1次,天气不好可顺延", + ], + flow: ["选好时间", "表格向我提到路线和穿搭", "一起轻松拍完"], +}; + +const bookingDraft = { + dateList: [ + { day: "周六", date: "28", active: true }, + { day: "周日", date: "29", active: false }, + { day: "周一", date: "30", active: false }, + ], + startTime: "14:00", + endTime: "17:00", + devices: [ + { id: "d1", name: "Pocket3", desc: "清晰便携|氛围感好", active: true }, + { id: "d2", name: "iPhone", desc: "生活感|轻快松弛", active: false }, + { id: "d3", name: "复古相机", desc: "文艺胶片|氛围更强", active: false }, + ], + total: 399, + validation: { + dateValid: true, + timeValid: true, + contactValid: false, + }, + submitState: "idle", +}; + +const bookingConfirm = { + title: "今天开始,慢慢陪你拍。", + tip: "提交后 30 分钟内,我会来和你确认时间、地点和拍照感觉。", + dateText: "2026年03月28日 周六", + timeText: "16:30 - 18:30", + contactText: "林夏 / 138****1029", + notice: "提交即表示你已阅读拍摄须知与改期说明。确认成功后,页面会带你回到「我的预约」。", +}; + +const works = { + tabs: ["全部", ...categories], + activeTab: "全部", + list: worksCatalog, +}; + +const myBookings = [ + { + id: "o1", + status: "待确认", + statusType: "pending", + statusReason: "等待摄影师确认时间", + duration: "2小时", + time: "2026/03/28 14:00-16:00", + location: "深圳 南山区 · 深圳湾公园", + primaryText: "联系摄影师", + secondaryText: "取消预约", + canCancel: true, + canContact: true, + }, + { + id: "o2", + status: "已确认", + statusType: "confirmed", + statusReason: "档期和地点已确认", + duration: "3小时", + time: "2026/04/01 16:30-19:30", + location: "深圳 福田区 · 香蜜公园", + primaryText: "联系摄影师", + secondaryText: "改期申请", + canCancel: false, + canContact: true, + }, + { + id: "o3", + status: "已取消", + statusType: "cancelled", + statusReason: "你已取消该订单", + duration: "2小时", + time: "2026/03/22 10:00-12:00", + location: "深圳 罗湖区 · 东湖公园", + primaryText: "再次预约", + secondaryText: "已结束", + canCancel: false, + canContact: false, + }, +]; + +const bookingDetail = { + status: "已确认", + photographer: "摄影师 安可", + subtitle: "轻松微步式拍摄 | 已与你确认档期", + time: "2026/04/01 16:30-19:30", + duration: "3小时", + device: "Pocket3", + contact: "wx:linxin / 138****1029", + note: "想拍自然互动呀,偏傍晚逆光,穿浅色系。", + total: 899, +}; + +module.exports = { + photoBookHome, + packageDetail, + bookingDraft, + bookingConfirm, + works, + myBookings, + bookingDetail, +}; diff --git a/miniprogram/pages/booking-confirm/index.js b/miniprogram/pages/booking-confirm/index.js new file mode 100644 index 0000000..30b6a77 --- /dev/null +++ b/miniprogram/pages/booking-confirm/index.js @@ -0,0 +1,52 @@ +const { bookingConfirm } = require("../../mock/photoBook"); + +Page({ + data: { + confirm: bookingConfirm, + showSuccessTip: false, + submitState: "idle", + }, + + onLoad(query) { + const next = { ...bookingConfirm }; + if (query.date) { + next.dateText = decodeURIComponent(query.date); + } + if (query.time) { + next.timeText = decodeURIComponent(query.time); + } + if (query.contact) { + next.contactText = decodeURIComponent(query.contact); + } + + this.setData({ confirm: next }); + }, + + onTapBack() { + wx.navigateBack(); + }, + + onTapSubmit() { + if (this.data.submitState === "loading") { + return; + } + + this.setData({ + submitState: "loading", + showSuccessTip: false, + }); + + setTimeout(() => { + this.setData({ + submitState: "idle", + showSuccessTip: true, + }); + + setTimeout(() => { + wx.switchTab({ + url: "/pages/my-bookings/index", + }); + }, 800); + }, 500); + }, +}); diff --git a/miniprogram/pages/booking-confirm/index.json b/miniprogram/pages/booking-confirm/index.json new file mode 100644 index 0000000..8723f6b --- /dev/null +++ b/miniprogram/pages/booking-confirm/index.json @@ -0,0 +1,6 @@ +{ + "navigationStyle": "custom", + "usingComponents": { + "nav-bar": "/components/nav-bar/index" + } +} diff --git a/miniprogram/pages/booking-confirm/index.wxml b/miniprogram/pages/booking-confirm/index.wxml new file mode 100644 index 0000000..aa2c2ee --- /dev/null +++ b/miniprogram/pages/booking-confirm/index.wxml @@ -0,0 +1,30 @@ + + + + + {{confirm.title}} + {{confirm.tip}} + + + + 预约日期 + {{confirm.dateText}} + + + 预约时间 + {{confirm.timeText}} + + + 联系人 + {{confirm.contactText}} + + + {{confirm.notice}} + + 提交后会同步到「我的预约」,可在那边查看和管理。 + + + {{submitState === 'loading' ? '提交中...' : '马上确认预约'}} + 预约提交成功,即将跳转 + + diff --git a/miniprogram/pages/booking-confirm/index.wxss b/miniprogram/pages/booking-confirm/index.wxss new file mode 100644 index 0000000..e80b52b --- /dev/null +++ b/miniprogram/pages/booking-confirm/index.wxss @@ -0,0 +1,79 @@ +page { + background: var(--bg-base); +} + +.page { + padding-top: var(--space-3); + padding-bottom: calc(124rpx + env(safe-area-inset-bottom)); + padding-bottom: calc(124rpx + constant(safe-area-inset-bottom)); +} + +.hero { + background: var(--surface-2); +} + +.hero-title { + font-size: var(--font-title-sm); + font-weight: 700; + color: var(--text-1); +} + +.hero-sub { + margin-top: var(--space-2); + font-size: var(--font-body); + color: var(--text-2); + line-height: 1.6; +} + +.field { + margin-top: var(--space-3); +} + +.k { + font-size: var(--font-caption); + color: var(--text-3); +} + +.v { + margin-top: 8rpx; + font-size: var(--font-body-lg); + font-weight: 700; + color: var(--text-1); +} + +.notice { + margin-top: var(--space-3); + font-size: var(--font-caption); + line-height: 1.7; + color: var(--text-2); +} + +.tip-card { + margin-top: var(--space-3); + border-radius: var(--radius-md); + background: #fff; + border: 1px solid var(--border-soft); + padding: var(--space-4); + font-size: var(--font-caption); + color: var(--text-2); +} + +.bottom-wrap { + position: fixed; + left: var(--space-4); + right: var(--space-4); + bottom: calc(16rpx + env(safe-area-inset-bottom)); + bottom: calc(16rpx + constant(safe-area-inset-bottom)); +} + +.success-tip { + margin-top: var(--space-2); + min-height: 56rpx; + border-radius: var(--radius-md); + background: var(--state-success-bg); + color: var(--state-success); + font-size: var(--font-caption); + display: flex; + align-items: center; + justify-content: center; +} diff --git a/miniprogram/pages/booking-detail/index.js b/miniprogram/pages/booking-detail/index.js new file mode 100644 index 0000000..dd866ff --- /dev/null +++ b/miniprogram/pages/booking-detail/index.js @@ -0,0 +1,51 @@ +const { bookingDetail } = require("../../mock/photoBook"); + +function getStatusClass(status) { + if ((status || "").includes("取消")) { + return "ui-pill-error"; + } + if ((status || "").includes("待")) { + return "ui-pill-pending"; + } + return "ui-pill-success"; +} + +Page({ + data: { + detail: bookingDetail, + statusClass: getStatusClass(bookingDetail.status), + }, + + onTapCancel() { + wx.showModal({ + title: "取消预约", + content: "确认取消后可重新预约,是否继续?", + confirmColor: "#E5484D", + success: (res) => { + if (!res.confirm) { + return; + } + this.setData({ + "detail.status": "已取消", + statusClass: "ui-pill-error", + }); + wx.showToast({ + title: "已发起取消申请", + icon: "none", + }); + }, + }); + }, + + onTapContact() { + wx.setClipboardData({ + data: this.data.detail.contact, + success: () => { + wx.showToast({ + title: "联系方式已复制", + icon: "none", + }); + }, + }); + }, +}); diff --git a/miniprogram/pages/booking-detail/index.json b/miniprogram/pages/booking-detail/index.json new file mode 100644 index 0000000..8723f6b --- /dev/null +++ b/miniprogram/pages/booking-detail/index.json @@ -0,0 +1,6 @@ +{ + "navigationStyle": "custom", + "usingComponents": { + "nav-bar": "/components/nav-bar/index" + } +} diff --git a/miniprogram/pages/booking-detail/index.wxml b/miniprogram/pages/booking-detail/index.wxml new file mode 100644 index 0000000..402b0d9 --- /dev/null +++ b/miniprogram/pages/booking-detail/index.wxml @@ -0,0 +1,38 @@ + + + + + 订单状态 + {{detail.status}} + + + + + + {{detail.photographer}} + {{detail.subtitle}} + + + + + 预约时间{{detail.time}} + 拍摄时长{{detail.duration}} + 设备选择{{detail.device}} + 联系方式{{detail.contact}} + + + + 备注信息 + {{detail.note}} + + + + 费用 + ¥ {{detail.total}} + + + + 取消预约 + 联系摄影师 + + diff --git a/miniprogram/pages/booking-detail/index.wxss b/miniprogram/pages/booking-detail/index.wxss new file mode 100644 index 0000000..7f856ee --- /dev/null +++ b/miniprogram/pages/booking-detail/index.wxss @@ -0,0 +1,111 @@ +page { + background: var(--bg-base); +} + +.page { + padding-top: var(--space-4); + padding-bottom: calc(116rpx + env(safe-area-inset-bottom)); + padding-bottom: calc(116rpx + constant(safe-area-inset-bottom)); +} + +.head { + display: flex; + align-items: center; + justify-content: space-between; +} + +.title { + font-size: var(--font-title-md); + font-weight: 700; + color: var(--text-1); +} + +.card { + margin-top: var(--space-3); +} + +.profile { + display: flex; + align-items: center; + gap: var(--space-3); + background: var(--surface-2); +} + +.avatar { + width: 84rpx; + height: 84rpx; + border-radius: 50%; + background: #ffd8e7; +} + +.name { + font-size: var(--font-body-lg); + font-weight: 700; + color: var(--text-1); +} + +.sub { + margin-top: 4rpx; + font-size: var(--font-caption); + color: var(--text-2); +} + +.row { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 68rpx; +} + +.k { + font-size: var(--font-caption); + color: var(--text-3); +} + +.v { + font-size: var(--font-body); + color: var(--text-1); + font-weight: 600; +} + +.remark { + margin-top: 8rpx; + font-size: var(--font-body); + color: var(--text-1); + line-height: 1.6; +} + +.fee-card { + margin-top: var(--space-3); + border-radius: var(--radius-md); + background: var(--surface-2); + padding: var(--space-4); + display: flex; + align-items: center; + justify-content: space-between; +} + +.fee { + font-size: var(--font-title-md); + font-weight: 700; + color: var(--brand-main); +} + +.bottom { + position: fixed; + left: var(--space-4); + right: var(--space-4); + bottom: calc(16rpx + env(safe-area-inset-bottom)); + bottom: calc(16rpx + constant(safe-area-inset-bottom)); + display: flex; + gap: var(--space-2); +} + +.btn { + flex: 1; +} + +.status.ui-pill-pending { + background: var(--state-warning-bg); + color: var(--state-warning); +} diff --git a/miniprogram/pages/booking/index.js b/miniprogram/pages/booking/index.js new file mode 100644 index 0000000..a5919ad --- /dev/null +++ b/miniprogram/pages/booking/index.js @@ -0,0 +1,146 @@ +const { bookingDraft } = require("../../mock/photoBook"); + +function cloneDraft() { + return JSON.parse(JSON.stringify(bookingDraft)); +} + +function formatHour(hour) { + return `${hour < 10 ? "0" : ""}${hour}:00`; +} + +Page({ + data: { + form: cloneDraft(), + remark: "", + contact: "", + errors: {}, + submitState: "idle", + startHour: 14, + }, + + onLoad() { + this.syncTimeByHour(this.data.startHour); + }, + + syncTimeByHour(startHour) { + const endHour = Math.min(startHour + 3, 23); + this.setData({ + startHour, + "form.startTime": formatHour(startHour), + "form.endTime": formatHour(endHour), + "form.validation.timeValid": true, + "errors.time": "", + }); + }, + + onTapDate(e) { + const picked = e.currentTarget.dataset.date; + const dateList = this.data.form.dateList.map((item) => ({ + ...item, + active: item.date === picked, + })); + + this.setData({ + "form.dateList": dateList, + "form.validation.dateValid": true, + "errors.date": "", + }); + }, + + onChangeTime(e) { + const startHour = Number(e.detail.value || 9); + this.syncTimeByHour(startHour); + }, + + onTapDevice(e) { + const id = e.currentTarget.dataset.id; + const devices = this.data.form.devices.map((item) => ({ + ...item, + active: item.id === id, + })); + this.setData({ + "form.devices": devices, + }); + }, + + onInputRemark(e) { + this.setData({ + remark: e.detail.value, + }); + }, + + onInputContact(e) { + const contact = e.detail.value; + const valid = this.isContactValid(contact); + this.setData({ + contact, + "form.validation.contactValid": valid, + "errors.contact": valid || !contact ? "" : "请填写有效手机号或微信号", + }); + }, + + isContactValid(contact) { + return /(^1\d{10}$)|(^[a-zA-Z0-9_-]{5,}$)/.test((contact || "").trim()); + }, + + getSelectedDate() { + return (this.data.form.dateList || []).find((item) => item.active); + }, + + validateBeforeSubmit() { + const errors = {}; + const selectedDate = this.getSelectedDate(); + const contactValid = this.isContactValid(this.data.contact); + + if (!selectedDate) { + errors.date = "请选择拍摄日期"; + } + + if (!contactValid) { + errors.contact = "请填写有效手机号或微信号"; + } + + this.setData({ + errors, + "form.validation.dateValid": !errors.date, + "form.validation.contactValid": contactValid, + "form.submitState": Object.keys(errors).length ? "error" : "idle", + }); + + return Object.keys(errors).length === 0; + }, + + onTapSubmit() { + if (this.data.submitState === "loading") { + return; + } + + if (!this.validateBeforeSubmit()) { + wx.showToast({ + title: "请先完善预约信息", + icon: "none", + }); + return; + } + + const selectedDate = this.getSelectedDate(); + const dateText = `2026年03月${selectedDate.date}日 ${selectedDate.day}`; + const timeText = `${this.data.form.startTime} - ${this.data.form.endTime}`; + const contactText = this.data.contact; + + this.setData({ + submitState: "loading", + "form.submitState": "loading", + }); + + setTimeout(() => { + this.setData({ + submitState: "idle", + "form.submitState": "idle", + }); + wx.navigateTo({ + url: `/pages/booking-confirm/index?date=${encodeURIComponent(dateText)}&time=${encodeURIComponent(timeText)}&contact=${encodeURIComponent(contactText)}`, + }); + }, 700); + }, +}); diff --git a/miniprogram/pages/booking/index.json b/miniprogram/pages/booking/index.json new file mode 100644 index 0000000..8723f6b --- /dev/null +++ b/miniprogram/pages/booking/index.json @@ -0,0 +1,6 @@ +{ + "navigationStyle": "custom", + "usingComponents": { + "nav-bar": "/components/nav-bar/index" + } +} diff --git a/miniprogram/pages/booking/index.wxml b/miniprogram/pages/booking/index.wxml new file mode 100644 index 0000000..f00b2cd --- /dev/null +++ b/miniprogram/pages/booking/index.wxml @@ -0,0 +1,54 @@ + + + 安排一次轻松拍照 + 先确认时间,再完善联系信息,提交后会快速确认。 + + + 1 选择日期 + + + {{item.day}} + {{item.date}} + + + {{errors.date}} + + + + 2 选择时间 + 已选:{{form.startTime}} - {{form.endTime}}(3小时) + + 建议在日落前 2 小时开始,光线更自然。 + {{errors.time}} + + + + 3 选择设备 + + + {{item.name}} + {{item.desc}} + + + + + + 4 填写联系信息 + + + {{errors.contact}} + + + + 预计费用 + ¥ {{form.total}} + + + {{submitState === 'loading' ? '提交中...' : '马上预约这个时间'}} + diff --git a/miniprogram/pages/booking/index.wxss b/miniprogram/pages/booking/index.wxss new file mode 100644 index 0000000..c9cbbb5 --- /dev/null +++ b/miniprogram/pages/booking/index.wxss @@ -0,0 +1,166 @@ +page { + background: var(--bg-base); +} + +.page { + padding-top: var(--space-4); + padding-bottom: calc(24rpx + env(safe-area-inset-bottom)); + padding-bottom: calc(24rpx + constant(safe-area-inset-bottom)); +} + +.title { + font-size: var(--font-title-md); + font-weight: 700; + color: var(--text-1); +} + +.sub { + margin-top: var(--space-1); + font-size: var(--font-body); + color: var(--text-2); + line-height: 1.6; +} + +.card { + margin-top: var(--space-3); +} + +.step-title { + font-size: var(--font-body-lg); + font-weight: 700; + color: var(--text-1); + display: flex; + align-items: center; + gap: 8rpx; +} + +.step-index { + width: 40rpx; + height: 40rpx; + border-radius: var(--radius-pill); + background: var(--brand-soft); + color: var(--brand-main); + font-size: var(--font-caption); + display: flex; + align-items: center; + justify-content: center; +} + +.date-row { + margin-top: var(--space-3); + display: flex; + gap: var(--space-2); +} + +.date-item { + width: 136rpx; + border-radius: var(--radius-md); + background: #f4f4f4; + border: 1px solid transparent; + padding: 14rpx 0; + text-align: center; +} + +.date-item.active { + background: var(--surface-2); + border-color: var(--brand-main); +} + +.d-day { + font-size: var(--font-caption); + color: var(--text-2); +} + +.d-date { + margin-top: 2rpx; + font-size: var(--font-title-sm); + font-weight: 700; + color: var(--text-1); +} + +.range { + margin-top: var(--space-3); + font-size: var(--font-body); + color: var(--text-2); +} + +.tip { + margin-top: var(--space-1); + font-size: var(--font-caption); + color: var(--text-3); +} + +.device-row { + margin-top: var(--space-3); + display: flex; + gap: var(--space-2); +} + +.device-item { + flex: 1; + border-radius: var(--radius-md); + border: 1px solid var(--border-soft); + padding: 12rpx; + background: #fff; +} + +.device-item.active { + border-color: var(--brand-main); + background: var(--surface-2); +} + +.d-name { + font-size: var(--font-body); + font-weight: 700; + color: var(--text-1); +} + +.d-desc { + margin-top: 4rpx; + font-size: var(--font-caption); + color: var(--text-2); + line-height: 1.5; +} + +.input { + margin-top: var(--space-3); + height: 88rpx; + border-radius: var(--radius-md); + background: #fafafa; + border: 1px solid #ececec; + padding: 0 16rpx; + font-size: var(--font-body); +} + +.error-text { + margin-top: 8rpx; + color: var(--state-danger); + font-size: var(--font-caption); +} + +.fee-card { + margin-top: var(--space-3); + min-height: 88rpx; + border-radius: var(--radius-md); + background: var(--surface-2); + padding: 0 var(--space-4); + display: flex; + align-items: center; + justify-content: space-between; +} + +.fee-label { + color: var(--text-2); + font-size: var(--font-body); +} + +.fee { + font-size: var(--font-title-md); + font-weight: 700; + color: var(--brand-main); +} + +.cta { + margin-top: var(--space-3); + min-height: 88rpx; +} diff --git a/miniprogram/pages/example/index.js b/miniprogram/pages/example/index.js new file mode 100644 index 0000000..400f4e3 --- /dev/null +++ b/miniprogram/pages/example/index.js @@ -0,0 +1,606 @@ +// pages/exampleDetail/index.js +Page({ + data: { + type: "", + envId: "", + showTip: false, + title: "", + content: "", + + haveGetOpenId: false, + openId: "", + + haveGetCodeSrc: false, + codeSrc: "", + + haveGetRecord: false, + record: [], + + haveGetImgSrc: false, + imgSrc: "", + + // ai + modelConfig: { + modelProvider: "deepseek", // 大模型服务厂商 + quickResponseModel: "deepseek-v3", // 快速响应模型 (混元 turbo, gpt4 turbo版,deepseek v3等) + logo: "https://cloudcache.tencent-cloud.com/qcloud/ui/static/static_source_business/2339414f-2c0d-4537-9618-1812bd14f4af.svg", // model 头像 + welcomeMsg: "我是deepseek-v3,很高兴见到你!", // model 欢迎语 + }, + callcbrCode: "", + initEnvCode: "", + callOpenIdCode: "", + callMiniProgramCode: "", + callFunctionCode: "", + callCreateCollectionCode: "", + callUploadFileCode: "", + + showInsertModal: false, + insertRegion: "", + insertCity: "", + insertSales: "", + + haveGetCallContainerRes: false, + callContainerResStr: "", + + ai_page_config: `{ + "usingComponents": { + "agent-ui":"/components/agent-ui/index" + }, +}`, + ai_wxml_config: `<agent-ui agentConfig="{{agentConfig}}" showBotAvatar="{{showBotAvatar}}" chatMode="{{chatMode}}" modelConfig="{{modelConfig}}""></agent-ui>`, + ai_data_config: `data: { + chatMode: "bot", // bot 表示使用agent,model 表示使用大模型 + showBotAvatar: true, // 是否在对话框左侧显示头像 + agentConfig: { + botId: "your agent id", // agent id, + allowWebSearch: true, // 允许客户端选择展示联网搜索按钮 + allowUploadFile: true, // 允许客户端展示上传文件按钮 + allowPullRefresh: true, // 允许客户端展示下拉刷新 + allowUploadImage: true, // 允许客户端展示上传图片按钮 + allowMultiConversation: true, // 允许客户端展示查看会话列表/新建会话按钮 + showToolCallDetail: true, // 是否展示 mcp server toolCall 细节 + allowVoice: true, // 允许客户端展示语音按钮 + showBotName: true, // 允许展示bot名称 + }, + modelConfig: { + modelProvider: "hunyuan-open", // 大模型服务厂商 + quickResponseModel: "hunyuan-lite", // 大模型名称 + logo: "", // model 头像 + welcomeMsg: "欢迎语", // model 欢迎语 + }, +}`, + + // AI 场景示例数据 + aiScenarios: [ + { + title: "💡 智能代码生成与补全", + examples: [ + "帮我创建一个商品列表页面,包含图片、标题、价格和加入购物车按钮", + "帮我完善这个函数,实现商品搜索功能", + ], + }, + { + title: "🔧 代码优化与重构建议", + examples: [ + "优化这段代码的性能,减少不必要的渲染", + "完善云函数调用的错误处理代码", + ], + }, + ], + }, + + onLoad(options) { + if ( + options.type === "cloudbaserunfunction" || + options.type === "cloudbaserun" + ) { + this.getCallcbrCode(); + } + if (options.type === "getOpenId") { + this.getOpenIdCode(); + } + if (options.type === "getMiniProgramCode") { + this.getMiniProgramCode(); + } + + if (options.type === "createCollection") { + this.getCreateCollectionCode(); + } + + if (options.type === "uploadFile") { + this.getUploadFileCode(); + } + this.setData({ type: options?.type, envId: options?.envId }); + }, + + copyUrl() { + wx.setClipboardData({ + data: "https://gitee.com/TencentCloudBase/cloudbase-agent-ui/tree/main/apps/miniprogram-agent-ui/miniprogram/components/agent-ui", + success: function (res) { + wx.showToast({ + title: "复制成功", + icon: "success", + }); + }, + }); + }, + + copyPluginName() { + wx.setClipboardData({ + data: "微信云开发 AI ToolKit", + success: function (res) { + wx.showToast({ + title: "复制成功", + icon: "success", + }); + }, + }); + }, + + copyPrompt(e) { + const prompt = e.currentTarget.dataset.prompt; + wx.setClipboardData({ + data: prompt, + success: function (res) { + wx.showToast({ + title: "复制成功", + icon: "success", + }); + }, + }); + }, + + insertRecord() { + this.setData({ + showInsertModal: true, + insertRegion: "", + insertCity: "", + insertSales: "", + }); + }, + + deleteRecord(e) { + // 调用云函数删除记录 + wx.showLoading({ + title: "删除中...", + }); + wx.cloud + .callFunction({ + name: "quickstartFunctions", + data: { + type: "deleteRecord", + data: { + _id: e.currentTarget.dataset.id, + }, + }, + }) + .then((resp) => { + wx.showToast({ + title: "删除成功", + }); + this.getRecord(); // 刷新列表 + wx.hideLoading(); + }) + .catch((e) => { + wx.showToast({ + title: "删除失败", + icon: "none", + }); + wx.hideLoading(); + }); + }, + + // 输入框事件 + onInsertRegionInput(e) { + this.setData({ insertRegion: e.detail.value }); + }, + onInsertCityInput(e) { + this.setData({ insertCity: e.detail.value }); + }, + onInsertSalesInput(e) { + this.setData({ insertSales: e.detail.value }); + }, + // 取消弹窗 + onInsertCancel() { + this.setData({ showInsertModal: false }); + }, + + // 确认插入 + async onInsertConfirm() { + const { insertRegion, insertCity, insertSales } = this.data; + if (!insertRegion || !insertCity || !insertSales) { + wx.showToast({ title: "请填写完整信息", icon: "none" }); + return; + } + wx.showLoading({ title: "插入中..." }); + try { + await wx.cloud.callFunction({ + name: "quickstartFunctions", + data: { + type: "insertRecord", + data: { + region: insertRegion, + city: insertCity, + sales: Number(insertSales), + }, + }, + }); + wx.showToast({ title: "插入成功" }); + this.setData({ showInsertModal: false }); + this.getRecord(); // 刷新列表 + } catch (e) { + wx.showToast({ title: "插入失败", icon: "none" }); + console.error(e); + } finally { + wx.hideLoading(); + } + }, + + getOpenId() { + wx.showLoading({ + title: "", + }); + wx.cloud + .callFunction({ + name: "quickstartFunctions", + data: { + type: "getOpenId", + }, + }) + .then((resp) => { + this.setData({ + haveGetOpenId: true, + openId: resp.result.openid, + }); + wx.hideLoading(); + }) + .catch((e) => { + wx.hideLoading(); + const { errCode, errMsg } = e; + if (errMsg.includes("Environment not found")) { + this.setData({ + showTip: true, + title: "云开发环境未找到", + content: + "如果已经开通云开发,请检查环境ID与 `miniprogram/app.js` 中的 `env` 参数是否一致。", + }); + return; + } + if (errMsg.includes("FunctionName parameter could not be found")) { + this.setData({ + showTip: true, + title: "请上传云函数", + content: + "在'cloudfunctions/quickstartFunctions'目录右键,选择【上传并部署-云端安装依赖】,等待云函数上传完成后重试。", + }); + return; + } + }); + }, + + clearOpenId() { + this.setData({ + haveGetOpenId: false, + openId: "", + }); + }, + + clearCallContainerRes() { + this.setData({ + haveGetCallContainerRes: false, + callContainerResStr: "", + }); + }, + + getCodeSrc() { + wx.showLoading({ + title: "", + }); + wx.cloud + .callFunction({ + name: "quickstartFunctions", + data: { + type: "getMiniProgramCode", + }, + }) + .then((resp) => { + this.setData({ + haveGetCodeSrc: true, + codeSrc: resp.result, + }); + wx.hideLoading(); + }) + .catch((e) => { + wx.hideLoading(); + console.error(e); + const { errCode, errMsg } = e; + if (errMsg.includes("Environment not found")) { + this.setData({ + showTip: true, + title: "云开发环境未找到", + content: + "如果已经开通云开发,请检查环境ID与 `miniprogram/app.js` 中的 `env` 参数是否一致。", + }); + return; + } + if (errMsg.includes("FunctionName parameter could not be found")) { + this.setData({ + showTip: true, + title: "请上传云函数", + content: + "在'cloudfunctions/quickstartFunctions'目录右键,选择【上传并部署-云端安装依赖】,等待云函数上传完成后重试。", + }); + return; + } + }); + }, + + clearCodeSrc() { + this.setData({ + haveGetCodeSrc: false, + codeSrc: "", + }); + }, + + bindInput(e) { + const index = e.currentTarget.dataset.index; + const record = this.data.record; + record[index].sales = Number(e.detail.value); + this.setData({ + record, + }); + }, + + getRecord() { + wx.showLoading({ + title: "", + }); + wx.cloud + .callFunction({ + name: "quickstartFunctions", + data: { + type: "selectRecord", + }, + }) + .then((resp) => { + this.setData({ + haveGetRecord: true, + record: resp.result.data, + }); + wx.hideLoading(); + }) + .catch((e) => { + this.setData({ + showTip: true, + }); + wx.hideLoading(); + console.error(e); + }); + }, + + clearRecord() { + this.setData({ + haveGetRecord: false, + record: [], + }); + }, + updateRecord() { + wx.showLoading({ + title: "", + }); + wx.cloud + .callFunction({ + name: "quickstartFunctions", + data: { + type: "updateRecord", + data: this.data.record, + }, + }) + .then((resp) => { + wx.showToast({ + title: "更新成功", + }); + wx.hideLoading(); + }) + .catch((e) => { + console.log(e); + this.setData({ + showUploadTip: true, + }); + wx.hideLoading(); + }); + }, + + uploadImg() { + wx.showLoading({ + title: "", + }); + // 让用户选择一张图片 + wx.chooseMedia({ + count: 1, + success: (chooseResult) => { + // 将图片上传至云存储空间 + wx.cloud + .uploadFile({ + // 指定上传到的云路径 + cloudPath: `my-photo-${new Date().getTime()}.png`, + // 指定要上传的文件的小程序临时文件路径 + filePath: chooseResult.tempFiles[0].tempFilePath, + }) + .then((res) => { + this.setData({ + haveGetImgSrc: true, + imgSrc: res.fileID, + }); + }) + .catch((e) => { + console.log("e", e); + }); + }, + complete: () => { + wx.hideLoading(); + }, + }); + }, + + clearImgSrc() { + this.setData({ + haveGetImgSrc: false, + imgSrc: "", + }); + }, + + goOfficialWebsite() { + const url = "https://docs.cloudbase.net/toolbox/quick-start"; + wx.navigateTo({ + url: `../web/index?url=${url}`, + }); + }, + runCallContainer: async function () { + const app = getApp(); + console.log("globalData", app.globalData); + const c1 = new wx.cloud.Cloud({ + resourceEnv: app.globalData.env, + }); + await c1.init(); + const r = await c1.callContainer({ + path: "/api/users", // 填入业务自定义路径 + header: { + "X-WX-SERVICE": "express-test", // 填入服务名称 + }, + // 其余参数同 wx.request + method: "GET", + }); + console.log(r); + this.setData({ + haveGetCallContainerRes: true, + callContainerResStr: `${JSON.stringify(r.data.items, null, 2)}`, + }); + }, + getCallcbrCode: function () { + const app = getApp(); + this.setData({ + callcbrCode: `const c1 = new wx.cloud.Cloud({ + resourceEnv: ${app.globalData.env} +}) +await c1.init() +const r = await c1.callContainer({ + path: '/api/users', // 此处填入业务自定义路径, /api/users 为示例路径 + header: { + 'X-WX-SERVICE': 'express-test', // 填入业务服务名称,express-test 为示例服务 + }, + // 其余参数同 wx.request + method: 'GET', +})`, + }); + }, + getInitEnvCode: function () { + const app = getApp(); + this.setData({ + initEnvCode: `wx.cloud.init({ + env: ${app.globalData.env}, + traceUser: true, +});`, + }); + }, + getCreateCollectionCode: function () { + this.setData({ + callCreateCollectionCode: `const cloud = require('wx-server-sdk'); +cloud.init({ + env: cloud.DYNAMIC_CURRENT_ENV +}); +const db = cloud.database(); +// 创建集合云函数入口函数 +exports.main = async (event, context) => { + try { + // 创建集合 + await db.createCollection('sales'); + return { + success: true + }; + } catch (e) { + return { + success: true, + data: 'create collection success' + }; + } +};`, + }); + }, + getOpenIdCode: function () { + this.setData({ + callOpenIdCode: `const cloud = require('wx-server-sdk'); +cloud.init({ + env: cloud.DYNAMIC_CURRENT_ENV +}); +// 获取openId云函数入口函数 +exports.main = async (event, context) => { + // 获取基础信息 + const wxContext = cloud.getWXContext(); + return { + openid: wxContext.OPENID, + appid: wxContext.APPID, + unionid: wxContext.UNIONID, + }; +};`, + callFunctionCode: `wx.cloud.callFunction({ + name: 'quickstartFunctions', + data: { + type: 'getOpenId' + } +}).then((resp) => console.log(resp))`, + }); + }, + getMiniProgramCode: function () { + this.setData({ + callMiniProgramCode: `const cloud = require('wx-server-sdk'); +cloud.init({ + env: cloud.DYNAMIC_CURRENT_ENV +}); +// 获取小程序二维码云函数入口函数 +exports.main = async (event, context) => { + // 获取小程序二维码的buffer + const resp = await cloud.openapi.wxacode.get({ + path: 'pages/index/index' + }); + const { buffer } = resp; + // 将图片上传云存储空间 + const upload = await cloud.uploadFile({ + cloudPath: 'code.png', + fileContent: buffer + }); + return upload.fileID; +}; +`, + callFunctionCode: `wx.cloud.callFunction({ + name: 'quickstartFunctions', + data: { + type: 'getMiniProgramCode' + } +}).then((resp) => console.log(resp))`, + }); + }, + getUploadFileCode: function () { + this.setData({ + callUploadFileCode: `wx.chooseMedia({ +count: 1, +success: (chooseResult) => { + // 将图片上传至云存储空间 + wx.cloud + .uploadFile({ + // 指定上传到的云路径 + cloudPath: "my-photo.png", + // 指定要上传的文件的小程序临时文件路径 + filePath: chooseResult.tempFiles[0].tempFilePath, + }) + .then((res) => { + console.log(res) + }) + .catch((e) => { + console.log('e', e) + }); +} +});`, + }); + }, +}); diff --git a/miniprogram/pages/example/index.json b/miniprogram/pages/example/index.json new file mode 100644 index 0000000..3ea1434 --- /dev/null +++ b/miniprogram/pages/example/index.json @@ -0,0 +1,5 @@ +{ + "usingComponents": { + "cloud-tip-modal": "/components/cloudTipModal/index" + } +} \ No newline at end of file diff --git a/miniprogram/pages/example/index.wxml b/miniprogram/pages/example/index.wxml new file mode 100644 index 0000000..0e98204 --- /dev/null +++ b/miniprogram/pages/example/index.wxml @@ -0,0 +1,294 @@ + + + + 功能介绍 + 云函数无需维护鉴权机制及登录票据,仅一行代码即可获得。 + 云函数获取OpenId示例 + + + step 1 quickStartFunctions 云函数代码 + + + + step 2 小程序代码段 + + + + + + 运行示例 + {{ openId ? openId : 'OpenID将展示在这里' }} + + 清空 + + + + + + 功能介绍 + 可通过云函数免接口调用凭证,直接生成小程序码。 + 云函数获取小程序码示例 + + step 1 quickStartFunctions 云函数代码 + + + + step 2 小程序代码段 + + + + + + 运行示例 + 小程序码将展示在这里 + + + + 清空 + + + + + + + + 功能介绍 + 集合为常用数据库中表的概念。云开发数据库支持自动备份、无损回档,并且QPS高达3千+。 + 如何体验 + 已自动创建名为“sales”的体验合集,可打开“云开发控制台>数据库>记录列表”中找到该集合。 + + 云函数代码示例 + + + + + + + + + 功能介绍 + 体验查询记录能力,查询数据表中的销量数据。 + 销量数据将展示在这里 + 数据库操作示例 + 参考云函数 quickstartFunctions 示例代码 + + 地区销量统计 + + 地域 + 城市 + 销量 + 操作 + + + + {{item.region}} + {{item.city}} + + + + + + + + 查询记录 + 更新记录 + 新增记录 + + + + + + 新增销量记录 + + + + + + + + + + + + + + 功能介绍 + 多存储类型,仅需一个云函数即可完成上传。 + 文件上传示例 + + 小程序代码段 + + + + + 运行示例 + 上传的图片将展示在这里 + + + + + 清空 + + + + + + + 功能介绍 + 腾讯云开发提供 AI 对话能力,支持 Agent,大模型流式对话,可通过 Agent-UI 组件快速集成 AI 能力 + 集成 Agent-UI 组件指引 + + step 1 拷贝组件源码包 + 点击复制查看组件仓库地址 + step 2 将组件拷贝至小程序目录中 + + step 3 在页面 .json 配置文件中注册组件 + + + + step 4 在页面 .wxml 文件中引用组件 + + + + step 4 在页面 .js 文件中编写配置 + + + + + + + + + + 功能介绍 + 云托管 支持托管用任意语言和框架编写的容器化应用,为开发者提供高可用、自动弹性扩缩的云服务。 + 小程序调用云托管示例 + + step 1 前往云开发平台开通云托管 + step 2 新建容器型托管服务,等待部署完成 + + 此处可使用 Express 示例模板进行安装,此处示例命名为 express-test + + + + + step 3小程序端调用 + + + + 运行示例 + {{ callContainerResStr ? callContainerResStr : '云托管调用结果将展示在这里' }} + 清空 + + + + + + + AI 智能开发小程序 + 连接 AI 开发工具与 MCP 开发小程序 + + + + step + 1 + 打开扩展面板 + + + 在微信开发者工具中使用快捷键打开扩展面板: + + + + 注意:使用快捷键前需先点击编辑区,将焦点从预览区切换到编辑区。 + + + + + Windows/Linux: + Ctrl + Shift + X + + + macOS: + Shift + Command + X + + + + + step + 2 + 搜索并安装插件 + + + 在扩展商店中搜索: + + + 微信云开发 AI ToolKit + + + 复制 + + + + 找到插件后点击安装并启用 + + + + step + 3 + 打开 AI 开发功能 + + + 安装完成后,选中任意文件,点击左上角打开 AI 开发功能即可使用 + + + + + + + ✨ 场景示例 + + + 💡 智能代码生成与补全 + + + · 帮我创建一个商品列表页面,包含图片、标题、价格和加入购物车按钮 + + + + + + · 帮我完善这个函数,实现商品搜索功能 + + + + + + + + + 🔧 代码优化与重构建议 + + + · 优化这个页面的间隔,提升信息密度 + + + + + + · 完善云函数调用的错误处理代码 + + + + + + + + + + + + + diff --git a/miniprogram/pages/example/index.wxss b/miniprogram/pages/example/index.wxss new file mode 100644 index 0000000..d086e0b --- /dev/null +++ b/miniprogram/pages/example/index.wxss @@ -0,0 +1,393 @@ +page { + background-color: white; + padding-bottom: 50px; + font-family: PingFang SC; +} + +.page-container { + padding: 20rpx 40rpx; +} + +.tip { + font-size: 23rpx; + color: rgba(0, 0, 0, 0.5); + width: 90%; + text-align: center; + margin: 30rpx auto 0 auto; +} + +.top_tip { + font-size: 28rpx; + color: rgba(0, 0, 0, 0.5); + width: 90%; + text-align: left; + margin-top: 30rpx; + /* margin-left: 20rpx; */ +} + +.box_text { + background-color: white; + text-align: center; + padding: 300rpx 0; + margin-top: 30rpx; + color: rgba(0, 0, 0, 0.5); +} + +.button { + width: 300rpx; + text-align: center; + margin: 250rpx auto 0 auto; + height: 80rpx; + color: white; + border-radius: 5px; + line-height: 80rpx; + background-color: #07c160; +} + +.button_clear { + width: 300rpx; + text-align: center; + margin: 0 auto 0 auto; + height: 80rpx; + color: #07c160; + border-radius: 5px; + line-height: 80rpx; + background-color: rgba(0, 0, 0, 0.03); +} + +.line { + height: 1rpx; + width: 100%; + background-color: rgba(0, 0, 0, 0.1); +} + +.code_box { + text-align: center; + background-color: white; + margin-top: 30rpx; + padding: 17rpx; +} + +.code_box_title { + color: rgba(0, 0, 0, 0.5); + font-size: 26rpx; + margin-bottom: 20rpx; + text-align: left; +} + +.code_box_record { + display: flex; +} + +.code_box_record_title { + width: 33%; + font-size: 26rpx; + color: rgba(0, 0, 0, 0.5); + padding: 20rpx 0; +} + +.code_box_record_detail { + width: 25%; + font-size: 26rpx; + padding: 20rpx 0; + display: flex; + align-items: center; + justify-content: center; +} + +.title { + margin-top: 16px; + margin-bottom: 8px; + font-size: 36rpx; + font-weight: 500; + color: #000; +} + +.info { + margin-top: 12px; + font-size: 28rpx; + font-weight: 400; + color: rgba(0, 0, 0, 0.6); + line-height: 52rpx; +} + +.img { + /* margin-top: 16px; */ + width: 100%; +} + +.step-img { + margin-top: 20rpx; + border-radius: 12rpx; + box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08); +} + +.code_img { + width: 360rpx; + height: 360rpx; +} + +.block { + font-size: 16px; + line-height: 40px; +} + +.step-left { + color: #FFF; + background-color: #1aad19; + border: #1aad19 solid 1px; + padding: 0px 6px; +} + +.step-right { + color: #1aad19; + background-color: #FFF; + border: #1aad19 solid 1px; + padding: 0px 6px; + margin-right: 10px; +} + +.code_zone { + background-color: #0E190E; + color: rgba(255, 255, 255, 0.7); + border-radius: 12rpx; + padding: 16rpx 32rpx; + box-shadow: 0 6px 8px rgba(0, 0, 0, 0.15); + position: relative; + margin-bottom: 24rpx; +} + +.btn-full { + height: 40px; + border-radius: 4px; + line-height: 40px; + color: #fff; + background-color: #1aad19; + text-align: center; + margin-bottom: 5px; +} + +.step-title { + line-height: 37px; + font-size: 16px; +} + +.step-text{ + font-size: 14px; + line-height: 24px; + padding: 10px 0px; + text-align: justify; +} + +.modal-mask { + position: fixed; left: 0; top: 0; right: 0; bottom: 0; + background: rgba(0,0,0,0.3); z-index: 1000; display: flex; align-items: center; justify-content: center; +} +.modal-content { + background: #fff; padding: 24px; border-radius: 8px; width: 80%; +} +.modal-title { font-size: 18px; margin-bottom: 16px; } +.modal-input { margin-bottom: 12px; border: 1px solid #eee; padding: 8px; border-radius: 4px; width: 100%; } +.modal-actions { display: flex; justify-content: flex-end; gap: 12px; } + +/* AI助手相关样式 */ +.notice-box { + background: linear-gradient(135deg, #fff8e6 0%, #fff3d9 100%); + border-left: 4rpx solid #ff9800; + border-radius: 8rpx; + padding: 20rpx 24rpx; + margin: 20rpx 0; + box-shadow: 0 2rpx 8rpx rgba(255, 152, 0, 0.1); +} + +.notice-text { + font-size: 26rpx; + color: rgba(0, 0, 0, 0.75); + line-height: 40rpx; +} + +.notice-highlight { + color: #ff6f00; + font-weight: 600; +} + +.shortcut-box { + background-color: rgba(0, 0, 0, 0.03); + border-radius: 8rpx; + padding: 24rpx; + margin: 20rpx 0; +} + +.shortcut-item { + display: flex; + align-items: center; + margin-bottom: 16rpx; +} + +.shortcut-item:last-child { + margin-bottom: 0; +} + +.shortcut-label { + font-size: 28rpx; + color: rgba(0, 0, 0, 0.7); + width: 240rpx; + flex-shrink: 0; +} + +.shortcut-key { + font-size: 28rpx; + color: #1aad19; + font-weight: 500; + background-color: rgba(26, 173, 25, 0.1); + padding: 4rpx 12rpx; + border-radius: 4rpx; + white-space: nowrap; +} + +.plugin-name-box { + background-color: rgba(26, 173, 25, 0.05); + border-left: 4rpx solid #1aad19; + padding: 20rpx; + margin: 20rpx 0; + display: flex; + align-items: center; + justify-content: space-between; +} + +.plugin-name { + font-size: 32rpx; + color: #1aad19; + font-weight: 500; + flex: 1; +} + +.copy-btn { + display: flex; + align-items: center; + padding: 6rpx 16rpx; + background-color: rgba(26, 173, 25, 0.1); + border: 1rpx solid rgba(26, 173, 25, 0.3); + border-radius: 8rpx; + margin-left: 20rpx; + transition: all 0.2s; +} + +.copy-btn:active { + background-color: rgba(26, 173, 25, 0.2); +} + +.copy-icon { + width: 24rpx; + height: 24rpx; + margin-right: 6rpx; + filter: invert(52%) sepia(73%) saturate(2034%) hue-rotate(90deg) brightness(92%) contrast(84%); +} + +.copy-text { + font-size: 24rpx; + color: #1aad19; + font-weight: 500; +} + +.divider { + height: 1rpx; + width: 100%; + background-color: rgba(0, 0, 0, 0.1); + margin: 40rpx 0; +} + +.feature-section { + margin-top: 20rpx; +} + +.feature-title { + font-size: 32rpx; + font-weight: 500; + color: rgba(0, 0, 0, 0.85); + margin-bottom: 20rpx; +} + +.feature-list { + padding-left: 0; +} + +.feature-item { + font-size: 28rpx; + color: rgba(0, 0, 0, 0.65); + line-height: 48rpx; + margin-bottom: 12rpx; +} + +/* 带示例的功能项 */ +.feature-item-with-examples { + margin-bottom: 20rpx; + padding: 20rpx; + background-color: rgba(0, 0, 0, 0.02); + border-radius: 12rpx; +} + +.feature-item-with-examples:last-child { + margin-bottom: 0; +} + +.feature-main { + font-size: 30rpx; + font-weight: 500; + color: rgba(0, 0, 0, 0.85); + margin-bottom: 12rpx; + padding-bottom: 8rpx; + border-bottom: 1rpx solid rgba(0, 0, 0, 0.08); +} + +.feature-examples { + padding-left: 0; +} + +.feature-example { + font-size: 26rpx; + color: rgba(0, 0, 0, 0.55); + line-height: 44rpx; + margin-bottom: 8rpx; +} + +.feature-example-wrapper { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 4rpx; + padding: 8rpx 12rpx; +} + +.feature-example-text { + flex: 1; + font-size: 26rpx; + color: rgba(0, 0, 0, 0.55); + line-height: 40rpx; +} + +.feature-copy-btn { + padding: 12rpx; + border-radius: 8rpx; + display: flex; + align-items: center; + justify-content: center; + margin-left: 12rpx; + flex-shrink: 0; + transition: all 0.2s; +} + +.feature-copy-btn:active { + background-color: rgba(0, 0, 0, 0.08); +} + +.feature-copy-icon { + width: 32rpx; + height: 32rpx; + opacity: 0.5; + transition: opacity 0.2s; +} + +.feature-copy-btn:active .feature-copy-icon { + opacity: 0.8; +} \ No newline at end of file diff --git a/miniprogram/pages/index/index.js b/miniprogram/pages/index/index.js new file mode 100644 index 0000000..ff9069f --- /dev/null +++ b/miniprogram/pages/index/index.js @@ -0,0 +1,151 @@ +const { photoBookHome } = require("../../mock/photoBook"); + +Page({ + data: { + home: photoBookHome, + errorText: "", + }, + + onLoad() { + this.fetchHomeData(); + }, + + async fetchHomeData() { + const app = getApp(); + if (!app.globalData.env) { + this.setData({ + home: photoBookHome, + errorText: "未配置云环境,当前展示 mock 数据", + }); + return; + } + + try { + const res = await wx.cloud.callFunction({ + name: "quickstartFunctions", + data: { + type: "getPhotoBookHome", + }, + }); + + const payload = res && res.result && res.result.data; + if (!payload) { + throw new Error("empty payload"); + } + + this.setData({ + home: this.normalizeHome(payload), + errorText: "", + }); + } catch (err) { + console.error("getPhotoBookHome failed", err); + this.setData({ + home: photoBookHome, + errorText: "云端数据拉取失败,已展示 mock 数据", + }); + } + }, + + normalizeHome(home) { + const next = { ...photoBookHome, ...(home || {}) }; + const works = Array.isArray(next.works) ? next.works : []; + const packages = Array.isArray(next.packages) ? next.packages : []; + + if (works.length < 3) { + next.works = photoBookHome.works; + } else { + next.works = works.map((item, idx) => { + if (typeof item === "string") { + return { + id: `w_${idx}`, + cover: item, + category: "全部", + ratio: idx === 0 ? "wide" : "tall", + title: `客片 ${idx + 1}`, + scene: "街拍", + }; + } + return { + id: item.id || `w_${idx}`, + cover: item.cover || photoBookHome.works[0].cover, + category: item.category || "全部", + ratio: item.ratio || (idx === 0 ? "wide" : "tall"), + title: item.title || `客片 ${idx + 1}`, + scene: item.scene || "街拍", + }; + }); + } + + if (!packages.length) { + next.packages = photoBookHome.packages; + } + + if (!Array.isArray(next.tags) || !next.tags.length) { + next.tags = photoBookHome.tags; + } + + if (!next.heroImage) { + next.heroImage = photoBookHome.heroImage; + } + if (!next.portfolioCount) { + next.portfolioCount = photoBookHome.portfolioCount || next.works.length; + } + if (!next.updateText) { + next.updateText = photoBookHome.updateText || "最近更新客片"; + } + + return next; + }, + + onTapHomeWorkPreview(e) { + const index = Number(e.currentTarget.dataset.index || 0); + const works = this.data.home.works || []; + const urls = works.map((item) => item.cover).filter(Boolean); + if (!urls.length) { + return; + } + wx.previewImage({ + current: urls[index] || urls[0], + urls, + }); + }, + + onTapLookWorks() { + wx.switchTab({ + url: "/pages/works/index", + }); + }, + + onTapViewAllWorks() { + wx.switchTab({ + url: "/pages/works/index", + }); + }, + + onTapPackage(e) { + const packageId = e.currentTarget.dataset.id || ""; + wx.navigateTo({ + url: `/pages/package-detail/index?id=${packageId}`, + }); + }, + + onTapPackageDetail(e) { + const packageId = e.currentTarget.dataset.id || ""; + wx.navigateTo({ + url: `/pages/package-detail/index?id=${packageId}`, + }); + }, + + onTapPackageBook(e) { + const packageId = e.currentTarget.dataset.id || ""; + wx.navigateTo({ + url: `/pages/booking/index?packageId=${packageId}`, + }); + }, + + onTapBookNow() { + wx.navigateTo({ + url: "/pages/booking/index", + }); + }, +}); diff --git a/miniprogram/pages/index/index.json b/miniprogram/pages/index/index.json new file mode 100644 index 0000000..8723f6b --- /dev/null +++ b/miniprogram/pages/index/index.json @@ -0,0 +1,6 @@ +{ + "navigationStyle": "custom", + "usingComponents": { + "nav-bar": "/components/nav-bar/index" + } +} diff --git a/miniprogram/pages/index/index.wxml b/miniprogram/pages/index/index.wxml new file mode 100644 index 0000000..bb9cc23 --- /dev/null +++ b/miniprogram/pages/index/index.wxml @@ -0,0 +1,73 @@ + + + + 城市纪实摄影 + {{home.photographerName}} + {{home.photographerTagline}} + 先看作品 + + + + + {{home.portfolioCount}} + 组真实客片 + + {{home.updateText}} + + + + + 精选作品 + 查看全部 + + + + + + {{home.works[0].title || '客片精选'}} + {{home.works[0].category}} · {{home.works[0].scene}} + + + + + + + {{item.category}} + + + + + + + + + {{item}} + + + + + + 预约套餐 + + + + {{item.badge}} + {{item.availableSlots}} + + {{item.title}} + {{item.desc}} + + ¥{{item.price}} + ¥{{item.originalPrice}} + + + 查看详情 + {{item.ctaLabel}} + + + + + {{errorText}} + + 立即预约拍摄 + diff --git a/miniprogram/pages/index/index.wxss b/miniprogram/pages/index/index.wxss new file mode 100644 index 0000000..3976780 --- /dev/null +++ b/miniprogram/pages/index/index.wxss @@ -0,0 +1,297 @@ +page { + background: var(--bg-base); +} + +.page { + padding-top: var(--space-4); +} + +.hero { + position: relative; + width: 100%; + height: 410rpx; + border-radius: var(--radius-lg); + overflow: hidden; + background-size: cover; + background-position: center; + padding: var(--space-5); + display: flex; + flex-direction: column; + justify-content: flex-end; + box-sizing: border-box; +} + +.hero-tag { + width: fit-content; + min-height: 48rpx; + border-radius: var(--radius-pill); + padding: 0 16rpx; + background: rgba(255, 255, 255, 0.86); + color: var(--brand-main); + font-size: var(--font-caption); + display: flex; + align-items: center; +} + +.hero-name { + margin-top: 12rpx; + font-size: var(--font-title-md); + font-weight: 700; + color: #fff; +} + +.hero-subtitle { + margin-top: 8rpx; + font-size: var(--font-body); + color: rgba(255, 255, 255, 0.9); + line-height: 1.6; +} + +.hero-cta { + margin-top: 18rpx; + width: 220rpx; + height: 88rpx; + border-radius: var(--radius-md); + background: #fff; + color: var(--brand-main); + font-size: var(--font-body-lg); + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; +} + +.stats-card { + margin-top: var(--space-3); + border-radius: var(--radius-md); + border: 1px solid var(--border-soft); + background: #fff; + padding: var(--space-4); +} + +.stats-main { + display: flex; + align-items: baseline; + gap: 8rpx; +} + +.stats-count { + font-size: var(--font-title-md); + color: var(--brand-main); + font-weight: 700; + line-height: 1; +} + +.stats-unit { + font-size: var(--font-body); + color: var(--text-2); +} + +.stats-sub { + margin-top: 8rpx; + font-size: var(--font-caption); + color: var(--text-3); +} + +.section { + margin-top: var(--space-5); +} + +.section-head { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--space-3); +} + +.section-title { + font-size: var(--font-title-sm); + font-weight: 700; + color: var(--text-1); +} + +.section-link { + min-height: 60rpx; + padding: 0 8rpx; + color: var(--text-2); + font-size: var(--font-body); + display: flex; + align-items: center; +} + +.works-grid { + display: flex; + gap: var(--space-2); +} + +.work-large-wrap, +.work-small-wrap { + position: relative; + overflow: hidden; + border-radius: var(--radius-md); +} + +.work-large { + width: 430rpx; + height: 236rpx; + border-radius: var(--radius-md); +} + +.work-right { + flex: 1; + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +.work-small { + width: 100%; + height: 112rpx; + border-radius: var(--radius-md); +} + +.work-mask { + position: absolute; + left: 0; + right: 0; + bottom: 0; + padding: 14rpx 12rpx; + background: linear-gradient(to top, rgba(0, 0, 0, 0.56), rgba(0, 0, 0, 0)); +} + +.work-mask.small { + padding: 10rpx; +} + +.work-name { + font-size: var(--font-body); + font-weight: 700; + color: #fff; +} + +.work-meta { + margin-top: 2rpx; + font-size: var(--font-caption); + color: rgba(255, 255, 255, 0.9); +} + +.tags-scroll { + margin-top: var(--space-4); + width: 100%; + white-space: nowrap; +} + +.tags-row { + display: inline-flex; + gap: var(--space-2); +} + +.tag-item { + display: inline-flex; + align-items: center; + min-height: 56rpx; + border-radius: var(--radius-pill); + background: #fff; + border: 1px solid var(--border-soft); + padding: 0 18rpx; + color: var(--text-2); + font-size: var(--font-caption); +} + +.pkg-card { + margin-bottom: var(--space-3); + background: var(--surface-1); + border: 1px solid var(--border-soft); + border-radius: var(--radius-md); + padding: var(--space-4); +} + +.pkg-top { + display: flex; + align-items: center; + justify-content: space-between; +} + +.pkg-badge { + min-height: 48rpx; + border-radius: var(--radius-pill); + background: var(--surface-2); + color: var(--brand-main); + font-size: var(--font-caption); + padding: 0 14rpx; + display: flex; + align-items: center; +} + +.pkg-slots { + color: var(--text-3); + font-size: var(--font-caption); +} + +.pkg-title { + margin-top: var(--space-2); + font-size: var(--font-title-sm); + font-weight: 700; + color: var(--text-1); +} + +.pkg-desc { + margin-top: 8rpx; + font-size: var(--font-body); + color: var(--text-2); +} + +.pkg-price-row { + margin-top: var(--space-2); + display: flex; + align-items: baseline; + gap: 12rpx; +} + +.pkg-price { + font-size: var(--font-title-md); + line-height: 1; + font-weight: 700; + color: var(--brand-main); +} + +.pkg-origin { + color: var(--text-3); + font-size: var(--font-body); + text-decoration: line-through; +} + +.pkg-actions { + margin-top: var(--space-3); + display: flex; + gap: var(--space-2); +} + +.pkg-action { + flex: 1; + min-height: 88rpx; + border-radius: var(--radius-md); + font-size: var(--font-body); + display: flex; + align-items: center; + justify-content: center; +} + +.pkg-action.ghost { + background: #f4f4f4; + color: var(--text-2); +} + +.pkg-action.solid { + background: var(--brand-main); + color: #fff; + font-weight: 700; +} + +.sticky-cta { + position: fixed; + left: var(--space-4); + right: var(--space-4); + bottom: calc(16rpx + env(safe-area-inset-bottom)); + bottom: calc(16rpx + constant(safe-area-inset-bottom)); +} diff --git a/miniprogram/pages/my-bookings/index.js b/miniprogram/pages/my-bookings/index.js new file mode 100644 index 0000000..6f6811f --- /dev/null +++ b/miniprogram/pages/my-bookings/index.js @@ -0,0 +1,89 @@ +const { myBookings } = require("../../mock/photoBook"); + +Page({ + data: { + list: myBookings, + }, + + findOrder(id) { + return (this.data.list || []).find((item) => item.id === id); + }, + + onTapOrder(e) { + const id = e.currentTarget.dataset.id; + wx.navigateTo({ + url: `/pages/booking-detail/index?id=${id}`, + }); + }, + + onTapPrimary(e) { + const id = e.currentTarget.dataset.id; + const order = this.findOrder(id); + if (!order) { + return; + } + + if (order.primaryText === "再次预约") { + wx.navigateTo({ + url: "/pages/booking/index", + }); + return; + } + + if (!order.canContact) { + return; + } + + wx.showToast({ + title: "已复制摄影师联系方式", + icon: "none", + }); + }, + + onTapSecondary(e) { + const id = e.currentTarget.dataset.id; + const list = this.data.list || []; + const index = list.findIndex((item) => item.id === id); + if (index === -1) { + return; + } + + const order = list[index]; + if (order.secondaryText === "取消预约" && order.canCancel) { + wx.showModal({ + title: "确认取消", + content: "取消后可重新预约,你确认要取消本次拍摄吗?", + confirmColor: "#E5484D", + success: (res) => { + if (!res.confirm) { + return; + } + + const next = [...list]; + next[index] = { + ...order, + status: "已取消", + statusType: "cancelled", + statusReason: "你已取消该订单", + canCancel: false, + secondaryText: "已结束", + }; + + this.setData({ list: next }); + wx.showToast({ + title: "已取消预约", + icon: "none", + }); + }, + }); + return; + } + + if (order.secondaryText === "改期申请") { + wx.showToast({ + title: "改期申请已发起", + icon: "none", + }); + } + }, +}); diff --git a/miniprogram/pages/my-bookings/index.json b/miniprogram/pages/my-bookings/index.json new file mode 100644 index 0000000..8723f6b --- /dev/null +++ b/miniprogram/pages/my-bookings/index.json @@ -0,0 +1,6 @@ +{ + "navigationStyle": "custom", + "usingComponents": { + "nav-bar": "/components/nav-bar/index" + } +} diff --git a/miniprogram/pages/my-bookings/index.wxml b/miniprogram/pages/my-bookings/index.wxml new file mode 100644 index 0000000..c681a06 --- /dev/null +++ b/miniprogram/pages/my-bookings/index.wxml @@ -0,0 +1,22 @@ + + + 共 {{list.length}} 个预约 + + + + + {{item.status}} + {{item.duration}} + + {{item.time}} + {{item.location}} + {{item.statusReason}} + + {{item.primaryText}} + {{item.secondaryText}} + + + + + 还没有预约记录,先去看看作品吧 + diff --git a/miniprogram/pages/my-bookings/index.wxss b/miniprogram/pages/my-bookings/index.wxss new file mode 100644 index 0000000..fbb55b2 --- /dev/null +++ b/miniprogram/pages/my-bookings/index.wxss @@ -0,0 +1,107 @@ +page { + background: var(--bg-base); +} + +.page { + padding-top: var(--space-4); +} + +.title { + font-size: var(--font-title-md); + font-weight: 700; + color: var(--text-1); +} + +.sub { + margin-top: var(--space-1); + color: var(--text-2); + font-size: var(--font-body); +} + +.order-card { + margin-top: var(--space-3); + border-radius: var(--radius-md); + background: var(--surface-1); + border: 1px solid var(--border-soft); + padding: var(--space-4); +} + +.row { + display: flex; + align-items: center; + justify-content: space-between; +} + +.status { + border-radius: var(--radius-pill); + min-height: 52rpx; + padding: 0 16rpx; + font-size: var(--font-caption); + font-weight: 700; + display: flex; + align-items: center; +} + +.status.pending { + color: var(--state-warning); + background: var(--state-warning-bg); +} + +.status.confirmed { + color: var(--state-success); + background: var(--state-success-bg); +} + +.status.cancelled { + color: var(--state-danger); + background: var(--state-danger-bg); +} + +.duration { + color: var(--text-3); + font-size: var(--font-caption); +} + +.time { + margin-top: var(--space-2); + font-size: var(--font-body-lg); + font-weight: 700; + color: var(--text-1); +} + +.location, +.reason { + margin-top: 8rpx; + font-size: var(--font-caption); + color: var(--text-2); +} + +.actions { + margin-top: var(--space-3); + display: flex; + gap: var(--space-2); +} + +.btn { + flex: 1; + min-height: 88rpx; + border-radius: var(--radius-md); + display: flex; + align-items: center; + justify-content: center; + font-size: var(--font-body); +} + +.btn.ghost { + background: #f4f4f4; + color: var(--text-2); +} + +.btn.warn { + background: var(--surface-2); + color: var(--brand-main); +} + +.btn.disabled { + opacity: 0.5; +} diff --git a/miniprogram/pages/package-detail/index.js b/miniprogram/pages/package-detail/index.js new file mode 100644 index 0000000..727602c --- /dev/null +++ b/miniprogram/pages/package-detail/index.js @@ -0,0 +1,17 @@ +const { packageDetail } = require("../../mock/photoBook"); + +Page({ + data: { + detail: packageDetail, + }, + + onTapBack() { + wx.navigateBack(); + }, + + onTapBook() { + wx.navigateTo({ + url: `/pages/booking/index?packageId=${this.data.detail.id}`, + }); + }, +}); diff --git a/miniprogram/pages/package-detail/index.json b/miniprogram/pages/package-detail/index.json new file mode 100644 index 0000000..8723f6b --- /dev/null +++ b/miniprogram/pages/package-detail/index.json @@ -0,0 +1,6 @@ +{ + "navigationStyle": "custom", + "usingComponents": { + "nav-bar": "/components/nav-bar/index" + } +} diff --git a/miniprogram/pages/package-detail/index.wxml b/miniprogram/pages/package-detail/index.wxml new file mode 100644 index 0000000..796007a --- /dev/null +++ b/miniprogram/pages/package-detail/index.wxml @@ -0,0 +1,26 @@ + + + + + + 你会收获什么 + + {{item}} + + + 拍摄怎么进行 + + {{index + 1}}. {{item}} + + + + 马上预约这个套餐 + + diff --git a/miniprogram/pages/package-detail/index.wxss b/miniprogram/pages/package-detail/index.wxss new file mode 100644 index 0000000..88abfef --- /dev/null +++ b/miniprogram/pages/package-detail/index.wxss @@ -0,0 +1,88 @@ +page { + background: var(--bg-base); +} + +.page { + padding-top: var(--space-2); +} + +.banner { + margin-top: var(--space-2); + height: 340rpx; + border-radius: var(--radius-lg); + background-size: cover; + background-position: center; + padding: var(--space-4); + box-sizing: border-box; + display: flex; + flex-direction: column; + justify-content: flex-end; +} + +.banner-title { + font-size: var(--font-title-md); + color: #fff; + font-weight: 700; +} + +.price-row { + margin-top: 8rpx; + display: flex; + gap: 14rpx; + align-items: baseline; +} + +.banner-price { + font-size: var(--font-title-lg); + color: #fff; + font-weight: 700; + line-height: 1; +} + +.banner-origin { + color: rgba(255, 255, 255, 0.8); + font-size: var(--font-body); + text-decoration: line-through; +} + +.slots { + margin-top: 10rpx; + width: fit-content; + min-height: 48rpx; + border-radius: var(--radius-pill); + padding: 0 14rpx; + display: flex; + align-items: center; + background: rgba(255, 255, 255, 0.86); + color: var(--brand-main); + font-size: var(--font-caption); +} + +.sec-title { + margin-top: var(--space-5); + font-size: var(--font-title-sm); + font-weight: 700; + color: var(--text-1); +} + +.card { + margin-top: var(--space-3); +} + +.line { + font-size: var(--font-body); + color: var(--text-2); + line-height: 1.8; +} + +.bottom { + position: fixed; + left: var(--space-4); + right: var(--space-4); + bottom: calc(16rpx + env(safe-area-inset-bottom)); + bottom: calc(16rpx + constant(safe-area-inset-bottom)); +} + +.book-btn { + min-height: 88rpx; +} diff --git a/miniprogram/pages/works/index.js b/miniprogram/pages/works/index.js new file mode 100644 index 0000000..ead8c75 --- /dev/null +++ b/miniprogram/pages/works/index.js @@ -0,0 +1,67 @@ +const { works } = require("../../mock/photoBook"); + +Page({ + data: { + tabs: works.tabs, + activeTab: works.activeTab, + list: works.list, + leftList: [], + rightList: [], + currentList: [], + currentCount: works.list.length, + }, + + onLoad() { + this.applyFilter(this.data.activeTab); + }, + + splitList(list) { + const leftList = []; + const rightList = []; + list.forEach((item, index) => { + if (index % 2 === 0) { + leftList.push(item); + } else { + rightList.push(item); + } + }); + this.setData({ + leftList, + rightList, + currentList: list, + currentCount: list.length, + }); + }, + + applyFilter(tab) { + const source = this.data.list || []; + const filtered = tab === "全部" ? source : source.filter((item) => item.category === tab); + this.splitList(filtered); + }, + + onTapTabFilter(e) { + const tab = e.currentTarget.dataset.tab; + this.setData({ activeTab: tab }); + this.applyFilter(tab); + }, + + onTapBook() { + wx.navigateTo({ + url: "/pages/booking/index", + }); + }, + + onTapPreview(e) { + const id = e.currentTarget.dataset.id; + const list = this.data.currentList || []; + const urls = list.map((item) => item.cover).filter(Boolean); + if (!urls.length) { + return; + } + const current = (list.find((item) => item.id === id) || list[0]).cover; + wx.previewImage({ + current, + urls, + }); + }, +}); diff --git a/miniprogram/pages/works/index.json b/miniprogram/pages/works/index.json new file mode 100644 index 0000000..8723f6b --- /dev/null +++ b/miniprogram/pages/works/index.json @@ -0,0 +1,6 @@ +{ + "navigationStyle": "custom", + "usingComponents": { + "nav-bar": "/components/nav-bar/index" + } +} diff --git a/miniprogram/pages/works/index.wxml b/miniprogram/pages/works/index.wxml new file mode 100644 index 0000000..dd36d67 --- /dev/null +++ b/miniprogram/pages/works/index.wxml @@ -0,0 +1,35 @@ + + + + + {{item}} + + + + 当前分类:{{activeTab}} · {{currentCount}} 组客片 + + + + + + + {{item.title}} + {{item.category}} · {{item.scene}} + + + + + + + + {{item.title}} + {{item.category}} · {{item.scene}} + + + + + + 当前分类暂无作品 + + 预约同款风格拍摄 + diff --git a/miniprogram/pages/works/index.wxss b/miniprogram/pages/works/index.wxss new file mode 100644 index 0000000..f8c38b4 --- /dev/null +++ b/miniprogram/pages/works/index.wxss @@ -0,0 +1,98 @@ +page { + background: var(--bg-base); +} + +.page { + padding-top: var(--space-4); +} + +.tabs { + width: 100%; +} + +.tabs-row { + display: inline-flex; + gap: var(--space-2); +} + +.summary { + margin-top: var(--space-3); + font-size: var(--font-caption); + color: var(--text-2); +} + +.tab { + min-height: 64rpx; + padding: 0 24rpx; + border-radius: var(--radius-pill); + background: #fff; + border: 1px solid var(--border-soft); + color: var(--text-2); + font-size: var(--font-body); + display: flex; + align-items: center; + justify-content: center; +} + +.tab.active { + background: var(--brand-main); + border-color: var(--brand-main); + color: #fff; + font-weight: 700; +} + +.waterfall { + margin-top: var(--space-4); + display: flex; + gap: var(--space-2); + align-items: flex-start; +} + +.column { + flex: 1; + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +.photo-wrap { + position: relative; + border-radius: var(--radius-md); + overflow: hidden; +} + +.photo { + width: 100%; + border-radius: var(--radius-md); + overflow: hidden; + background: #f8f8f8; +} + +.photo-mask { + position: absolute; + left: 0; + right: 0; + bottom: 0; + padding: 12rpx; + background: linear-gradient(to top, rgba(0, 0, 0, 0.62), rgba(0, 0, 0, 0)); +} + +.photo-title { + font-size: var(--font-caption); + color: #fff; + font-weight: 700; +} + +.photo-meta { + margin-top: 2rpx; + font-size: 22rpx; + color: rgba(255, 255, 255, 0.9); +} + +.floating-cta { + position: fixed; + left: var(--space-4); + right: var(--space-4); + bottom: calc(16rpx + env(safe-area-inset-bottom)); + bottom: calc(16rpx + constant(safe-area-inset-bottom)); +} diff --git a/miniprogram/sitemap.json b/miniprogram/sitemap.json new file mode 100644 index 0000000..27b2b26 --- /dev/null +++ b/miniprogram/sitemap.json @@ -0,0 +1,7 @@ +{ + "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", + "rules": [{ + "action": "allow", + "page": "*" + }] +} \ No newline at end of file diff --git a/project.config.json b/project.config.json new file mode 100644 index 0000000..a937300 --- /dev/null +++ b/project.config.json @@ -0,0 +1,85 @@ +{ + "miniprogramRoot": "miniprogram/", + "cloudfunctionRoot": "cloudfunctions/", + "setting": { + "urlCheck": true, + "es6": true, + "enhance": true, + "postcss": true, + "preloadBackgroundData": false, + "minified": true, + "newFeature": true, + "coverView": true, + "nodeModules": false, + "autoAudits": false, + "showShadowRootInWxmlPanel": true, + "scopeDataCheck": false, + "uglifyFileName": false, + "checkInvalidKey": true, + "checkSiteMap": true, + "uploadWithSourceMap": true, + "compileHotReLoad": false, + "useMultiFrameRuntime": true, + "useApiHook": true, + "useApiHostProcess": true, + "babelSetting": { + "ignore": [], + "disablePlugins": [], + "outputPath": "" + }, + "enableEngineNative": false, + "useIsolateContext": true, + "useCompilerModule": true, + "userConfirmedUseCompilerModuleSwitch": false, + "userConfirmedBundleSwitch": false, + "packNpmManually": false, + "packNpmRelationList": [], + "minifyWXSS": true, + "compileWorklet": false, + "minifyWXML": true, + "localPlugins": false, + "disableUseStrict": false, + "useCompilerPlugins": false, + "condition": false, + "swc": false, + "disableSWC": true + }, + "appid": "wx47f48cd3355ce2ee", + "projectname": "quickstart-wx-cloud", + "libVersion": "2.20.1", + "cloudfunctionTemplateRoot": "cloudfunctionTemplate/", + "condition": { + "search": { + "list": [] + }, + "conversation": { + "list": [] + }, + "plugin": { + "list": [] + }, + "game": { + "list": [] + }, + "miniprogram": { + "list": [ + { + "id": -1, + "name": "db guide", + "pathName": "pages/databaseGuide/databaseGuide" + } + ] + } + }, + "compileType": "miniprogram", + "srcMiniprogramRoot": "miniprogram/", + "packOptions": { + "ignore": [], + "include": [] + }, + "editorSetting": { + "tabIndent": "insertSpaces", + "tabSize": 2 + }, + "simulatorPluginLibVersion": {} +} \ No newline at end of file diff --git a/skills-lock.json b/skills-lock.json new file mode 100644 index 0000000..364d61e --- /dev/null +++ b/skills-lock.json @@ -0,0 +1,10 @@ +{ + "version": 1, + "skills": { + "better-icons": { + "source": "better-auth/better-icons", + "sourceType": "github", + "computedHash": "ec859a92bcb6fef5ae1fe8ab1d4b10c1d76fae7a809101c9bce1230944af1b0e" + } + } +} diff --git a/uploadCloudFunction.sh b/uploadCloudFunction.sh new file mode 100644 index 0000000..df311b3 --- /dev/null +++ b/uploadCloudFunction.sh @@ -0,0 +1 @@ +${installPath} cloud functions deploy --e ${envId} --n quickstartFunctions --r --project ${projectPath} \ No newline at end of file