feat: initialize WeChat mini program project
|
|
@ -0,0 +1,11 @@
|
|||
.DS_Store
|
||||
project.private.config.json
|
||||
|
||||
.claude/
|
||||
.kiro/
|
||||
.qoder/
|
||||
.qwen/
|
||||
.trae/
|
||||
.agents/
|
||||
|
||||
node_modules/
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
# 云开发 quickstart
|
||||
|
||||
这是云开发的快速启动指引,其中演示了如何上手使用云开发的三大基础能力:
|
||||
|
||||
- 数据库:一个既可在小程序前端操作,也能在云函数中读写的 JSON 文档型数据库
|
||||
- 文件存储:在小程序前端直接上传/下载云端文件,在云开发控制台可视化管理
|
||||
- 云函数:在云端运行的代码,微信私有协议天然鉴权,开发者只需编写业务逻辑代码
|
||||
|
||||
## 参考文档
|
||||
|
||||
- [云开发文档](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/basis/getting-started.html)
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"permissions": {
|
||||
"openapi": [
|
||||
"wxacode.get"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
});
|
||||
},
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"usingComponents": {},
|
||||
"component": true
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<!--miniprogram/components/cloudTipModal/index.wxml-->
|
||||
<!-- wx:if="{{showUploadTip}}" -->
|
||||
<view class="install_tip" wx:if="{{showTip}}">
|
||||
<view class="install_tip_back"></view>
|
||||
<view class="install_tip_detail">
|
||||
<image class="install_tip_close" bind:tap="onClose" src="../../images/icons/close.png"/>
|
||||
<view class="install_tip_detail_title">{{title}}</view>
|
||||
<view class="install_tip_detail_tip">{{content}}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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");
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"component": true
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<view class="nav-placeholder" style="height: {{totalHeight}}px;"></view>
|
||||
|
||||
<view class="nav-fixed theme-{{theme}}" style="padding-top: {{statusBarHeight}}px;">
|
||||
<view class="nav-content" style="height: {{navBarHeight}}px;">
|
||||
<view class="side left" style="width: {{sideWidth}}px;">
|
||||
<view wx:if="{{showBack}}" class="back-btn" bindtap="onTapBack">返回</view>
|
||||
</view>
|
||||
|
||||
<view class="title">{{title}}</view>
|
||||
|
||||
<view class="side right" style="width: {{sideWidth}}px;">
|
||||
<view wx:if="{{rightText}}" class="right-text" bindtap="onTapRight">{{rightText}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
const envList = [];
|
||||
const isMac = false;
|
||||
module.exports = {
|
||||
envList,
|
||||
isMac
|
||||
};
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="8px" height="14px" viewBox="0 0 8 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>☀ iOS/☀ 图标/线型/icons_outlined_arrow@3x</title>
|
||||
<g id="控件" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" fill-opacity="0.3">
|
||||
<g id="4.列表/z.覆盖层/右边/箭头" transform="translate(-334.000000, -21.000000)" fill="#000000">
|
||||
<g id="☀-iOS/☀-图标/线型/icons_outlined_arrow" transform="translate(332.000000, 16.000000)">
|
||||
<path d="M2.45405845,6.58064919 L3.51471863,5.51998901 L9.29361566,11.298886 C9.68374096,11.6890113 9.6872014,12.318069 9.29361566,12.7116547 L3.51471863,18.4905518 L2.45405845,17.4298916 L7.87867966,12.0052704 L2.45405845,6.58064919 Z" id="Combined-Shape"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 906 B |
|
After Width: | Height: | Size: 4.9 KiB |
|
|
@ -0,0 +1,2 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M2 2H15V7.5H13V4H4V13H7.5V15H2V2ZM9 9H22V22H9V9ZM11 11V20H20V11H11Z" fill="#8b8b8b" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 197 B |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 875 B |
|
After Width: | Height: | Size: 888 B |
|
After Width: | Height: | Size: 578 B |
|
After Width: | Height: | Size: 666 B |
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.95693 15.75H19.5V6H4.5V17.7974L6.95693 15.75ZM3.73808 20.3849C3.44499 20.6292 3 20.4208 3 20.0392V6C3 5.17157 3.67157 4.5 4.5 4.5H19.5C20.3284 4.5 21 5.17157 21 6V15.75C21 16.5784 20.3284 17.25 19.5 17.25H7.5L3.73808 20.3849Z" fill="black" fill-opacity="0.9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 376 B |
|
After Width: | Height: | Size: 779 B |
|
After Width: | Height: | Size: 789 B |
|
After Width: | Height: | Size: 917 B |
|
After Width: | Height: | Size: 888 B |
|
After Width: | Height: | Size: 873 B |
|
After Width: | Height: | Size: 879 B |
|
|
@ -0,0 +1,5 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.0911 17.4C11.0911 16.9029 11.494 16.5 11.9911 16.5C12.4881 16.5 12.8911 16.9029 12.8911 17.4C12.8911 17.8971 12.4881 18.3 11.9911 18.3C11.494 18.3 11.0911 17.8971 11.0911 17.4Z" fill="black" fill-opacity="0.9"/>
|
||||
<path d="M11.9911 6.00915C9.98467 6.00915 8.35363 7.64019 8.35363 9.64665H9.85363C9.85363 8.46862 10.8131 7.50915 11.9911 7.50915C13.1692 7.50915 14.1286 8.46862 14.1286 9.64665C14.1286 10.4533 13.4619 11.2621 12.5917 11.6156L12.5878 11.6172C11.7935 11.945 11.2412 12.7262 11.2412 13.6387V15H12.7412V13.6387C12.7412 13.3474 12.9142 13.106 13.1585 13.0044C14.3995 12.4993 15.6286 11.248 15.6286 9.64665C15.6286 7.64019 13.9976 6.00915 11.9911 6.00915Z" fill="black" fill-opacity="0.9"/>
|
||||
<path d="M22.4912 12C22.4912 6.20101 17.7902 1.5 11.9912 1.5C6.19222 1.5 1.49121 6.20101 1.49121 12C1.49121 17.799 6.19222 22.5 11.9912 22.5C17.7902 22.5 22.4912 17.799 22.4912 12ZM20.9912 12C20.9912 16.9706 16.9618 21 11.9912 21C7.02065 21 2.99121 16.9706 2.99121 12C2.99121 7.02944 7.02065 3 11.9912 3C16.9618 3 20.9912 7.02944 20.9912 12Z" fill="black" fill-opacity="0.9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
|
|
@ -0,0 +1,4 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.5002 12C16.5002 14.4853 14.4855 16.5 12.0002 16.5C9.51497 16.5 7.50025 14.4853 7.50025 12C7.50025 9.51472 9.51497 7.5 12.0002 7.5C14.4855 7.5 16.5002 9.51472 16.5002 12ZM15.0002 12C15.0002 10.3431 13.6571 9 12.0002 9C10.3434 9 9.00025 10.3431 9.00025 12C9.00025 13.6569 10.3434 15 12.0002 15C13.6571 15 15.0002 13.6569 15.0002 12Z" fill="black" fill-opacity="0.9"/>
|
||||
<path d="M12.0002 1.875L21.0935 6.9375V17.0625L12.0002 22.125L2.90698 17.0625V6.9375L12.0002 1.875ZM4.40698 7.8192V16.1808L12.0002 20.4082L19.5935 16.1808V7.8192L12.0002 3.59179L4.40698 7.8192Z" fill="black" fill-opacity="0.9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 711 B |
|
|
@ -0,0 +1,4 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.43993 20.5601C3.72112 20.8413 4.10234 20.9995 4.5 21H19.5C19.8977 20.9995 20.2789 20.8413 20.5601 20.5601C20.8413 20.2789 20.9995 19.8977 21 19.5V12.75H19.5V19.5H4.5V4.5H11.25V3H4.5C4.10234 3.00054 3.72112 3.15874 3.43993 3.43993C3.15874 3.72112 3.00054 4.10234 3 4.5V19.5C3.00054 19.8977 3.15874 20.2789 3.43993 20.5601Z" fill="black" fill-opacity="0.9"/>
|
||||
<path d="M13.5 4.5V3H20.25C20.6642 3 21 3.33579 21 3.75V10.5H19.5V5.5605L13.0605 12L12 10.9395L18.4395 4.5H13.5Z" fill="black" fill-opacity="0.9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 620 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 326 KiB |
|
After Width: | Height: | Size: 177 KiB |
|
After Width: | Height: | Size: 317 KiB |
|
After Width: | Height: | Size: 301 KiB |
|
After Width: | Height: | Size: 244 KiB |
|
After Width: | Height: | Size: 216 KiB |
|
After Width: | Height: | Size: 325 KiB |
|
After Width: | Height: | Size: 264 KiB |
|
After Width: | Height: | Size: 280 KiB |
|
After Width: | Height: | Size: 326 KiB |
|
After Width: | Height: | Size: 334 KiB |
|
After Width: | Height: | Size: 319 KiB |
|
After Width: | Height: | Size: 331 KiB |
|
After Width: | Height: | Size: 501 KiB |
|
After Width: | Height: | Size: 634 KiB |
|
After Width: | Height: | Size: 372 KiB |
|
After Width: | Height: | Size: 384 KiB |
|
After Width: | Height: | Size: 438 KiB |
|
After Width: | Height: | Size: 151 KiB |
|
After Width: | Height: | Size: 304 KiB |
|
After Width: | Height: | Size: 195 KiB |
|
After Width: | Height: | Size: 316 KiB |
|
After Width: | Height: | Size: 306 KiB |
|
After Width: | Height: | Size: 391 KiB |
|
After Width: | Height: | Size: 250 KiB |
|
After Width: | Height: | Size: 143 KiB |
|
After Width: | Height: | Size: 457 KiB |
|
|
@ -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,
|
||||
};
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
|
@ -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);
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {
|
||||
"nav-bar": "/components/nav-bar/index"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<view class="page ui-page">
|
||||
<nav-bar title="确认预约" showBack theme="blur" />
|
||||
|
||||
<view class="hero ui-card">
|
||||
<view class="hero-title">{{confirm.title}}</view>
|
||||
<view class="hero-sub">{{confirm.tip}}</view>
|
||||
</view>
|
||||
|
||||
<view class="field ui-card">
|
||||
<view class="k">预约日期</view>
|
||||
<view class="v">{{confirm.dateText}}</view>
|
||||
</view>
|
||||
<view class="field ui-card">
|
||||
<view class="k">预约时间</view>
|
||||
<view class="v">{{confirm.timeText}}</view>
|
||||
</view>
|
||||
<view class="field ui-card">
|
||||
<view class="k">联系人</view>
|
||||
<view class="v">{{confirm.contactText}}</view>
|
||||
</view>
|
||||
|
||||
<view class="notice">{{confirm.notice}}</view>
|
||||
|
||||
<view class="tip-card">提交后会同步到「我的预约」,可在那边查看和管理。</view>
|
||||
|
||||
<view class="bottom-wrap">
|
||||
<view class="cta ui-btn-primary {{submitState === 'loading' ? 'ui-btn-primary--disabled' : ''}}" bindtap="onTapSubmit">{{submitState === 'loading' ? '提交中...' : '马上确认预约'}}</view>
|
||||
<view wx:if="{{showSuccessTip}}" class="success-tip">预约提交成功,即将跳转</view>
|
||||
</view>
|
||||
</view>
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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",
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {
|
||||
"nav-bar": "/components/nav-bar/index"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<view class="page ui-page">
|
||||
<nav-bar title="预约详情" showBack theme="blur" />
|
||||
|
||||
<view class="head">
|
||||
<view class="title">订单状态</view>
|
||||
<view class="status ui-pill {{statusClass}}">{{detail.status}}</view>
|
||||
</view>
|
||||
|
||||
<view class="card ui-card profile">
|
||||
<view class="avatar"></view>
|
||||
<view class="info">
|
||||
<view class="name">{{detail.photographer}}</view>
|
||||
<view class="sub">{{detail.subtitle}}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card ui-card">
|
||||
<view class="row"><text class="k">预约时间</text><text class="v">{{detail.time}}</text></view>
|
||||
<view class="row"><text class="k">拍摄时长</text><text class="v">{{detail.duration}}</text></view>
|
||||
<view class="row"><text class="k">设备选择</text><text class="v">{{detail.device}}</text></view>
|
||||
<view class="row"><text class="k">联系方式</text><text class="v">{{detail.contact}}</text></view>
|
||||
</view>
|
||||
|
||||
<view class="card ui-card">
|
||||
<view class="k">备注信息</view>
|
||||
<view class="remark">{{detail.note}}</view>
|
||||
</view>
|
||||
|
||||
<view class="fee-card">
|
||||
<text class="k">费用</text>
|
||||
<text class="fee">¥ {{detail.total}}</text>
|
||||
</view>
|
||||
|
||||
<view class="bottom">
|
||||
<view class="btn ghost ui-btn-ghost" bindtap="onTapCancel">取消预约</view>
|
||||
<view class="btn solid ui-btn-primary" bindtap="onTapContact">联系摄影师</view>
|
||||
</view>
|
||||
</view>
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {
|
||||
"nav-bar": "/components/nav-bar/index"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<view class="page ui-page">
|
||||
<nav-bar title="预约拍摄" showBack theme="blur" />
|
||||
<view class="title">安排一次轻松拍照</view>
|
||||
<view class="sub">先确认时间,再完善联系信息,提交后会快速确认。</view>
|
||||
|
||||
<view class="card ui-card">
|
||||
<view class="step-title"><text class="step-index">1</text> 选择日期</view>
|
||||
<view class="date-row">
|
||||
<view
|
||||
wx:for="{{form.dateList}}"
|
||||
wx:key="date"
|
||||
class="date-item {{item.active ? 'active' : ''}}"
|
||||
data-date="{{item.date}}"
|
||||
bindtap="onTapDate"
|
||||
>
|
||||
<view class="d-day">{{item.day}}</view>
|
||||
<view class="d-date">{{item.date}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view wx:if="{{errors.date}}" class="error-text">{{errors.date}}</view>
|
||||
</view>
|
||||
|
||||
<view class="card ui-card">
|
||||
<view class="step-title"><text class="step-index">2</text> 选择时间</view>
|
||||
<view class="range">已选:{{form.startTime}} - {{form.endTime}}(3小时)</view>
|
||||
<slider min="9" max="18" value="{{startHour}}" activeColor="#ff4d8d" backgroundColor="#efd9e2" block-color="#ffffff" block-size="22" bindchange="onChangeTime" />
|
||||
<view class="tip">建议在日落前 2 小时开始,光线更自然。</view>
|
||||
<view wx:if="{{errors.time}}" class="error-text">{{errors.time}}</view>
|
||||
</view>
|
||||
|
||||
<view class="card ui-card">
|
||||
<view class="step-title"><text class="step-index">3</text> 选择设备</view>
|
||||
<view class="device-row">
|
||||
<view wx:for="{{form.devices}}" wx:key="id" class="device-item {{item.active ? 'active' : ''}}" data-id="{{item.id}}" bindtap="onTapDevice">
|
||||
<view class="d-name">{{item.name}}</view>
|
||||
<view class="d-desc">{{item.desc}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card ui-card">
|
||||
<view class="step-title"><text class="step-index">4</text> 填写联系信息</view>
|
||||
<input class="input" placeholder="微信号或手机号(必填)" value="{{contact}}" bindinput="onInputContact" />
|
||||
<input class="input" placeholder="拍摄偏好(可选)" value="{{remark}}" bindinput="onInputRemark" />
|
||||
<view wx:if="{{errors.contact}}" class="error-text">{{errors.contact}}</view>
|
||||
</view>
|
||||
|
||||
<view class="fee-card">
|
||||
<text class="fee-label">预计费用</text>
|
||||
<text class="fee">¥ {{form.total}}</text>
|
||||
</view>
|
||||
|
||||
<view class="cta ui-btn-primary {{submitState === 'loading' ? 'ui-btn-primary--disabled' : ''}}" bindtap="onTapSubmit">{{submitState === 'loading' ? '提交中...' : '马上预约这个时间'}}</view>
|
||||
</view>
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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)
|
||||
});
|
||||
}
|
||||
});`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"usingComponents": {
|
||||
"cloud-tip-modal": "/components/cloudTipModal/index"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,294 @@
|
|||
<!--pages/exampleDetail/index.wxml-->
|
||||
<block wx:if="{{ type === 'getOpenId' }}">
|
||||
<view class="page-container">
|
||||
<view class="title">功能介绍</view>
|
||||
<view class="info">云函数无需维护鉴权机制及登录票据,仅一行代码即可获得。</view>
|
||||
<view class="title">云函数获取OpenId示例</view>
|
||||
<!-- <view class="info">云函数无需维护鉴权机制及登录票据,仅一行代码即可获得。</view> -->
|
||||
<view class="block">
|
||||
<view class="step-title"><text class="step-left">step</text> <text class="step-right">1 </text>quickStartFunctions 云函数代码</view>
|
||||
<view class="code_zone">
|
||||
<rich-text nodes="<pre style='overflow: scroll;'>{{callOpenIdCode}}</pre>" />
|
||||
</view>
|
||||
<view class="step-title"><text class="step-left">step</text> <text class="step-right">2 </text>小程序代码段</view>
|
||||
<view class="code_zone">
|
||||
<rich-text nodes="<pre style='overflow: scroll;'>{{callFunctionCode}}</pre>" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="block">
|
||||
<view class="btn-full" bind:tap="getOpenId" wx:if="{{!haveGetOpenId}}">运行示例</view>
|
||||
<view class="box_text">{{ openId ? openId : 'OpenID将展示在这里' }}</view>
|
||||
<cloud-tip-modal showTipProps="{{showTip}}" title="{{title}}" content="{{content}}"></cloud-tip-modal>
|
||||
<view class="button_clear" bindtap="clearOpenId" wx:if="{{haveGetOpenId}}">清空</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
<block wx:if="{{ type === 'getMiniProgramCode' }}">
|
||||
<view class="page-container">
|
||||
<view class="title">功能介绍</view>
|
||||
<view class="info">可通过云函数免接口调用凭证,直接生成小程序码。</view>
|
||||
<view class="title">云函数获取小程序码示例</view>
|
||||
<view class="block">
|
||||
<view class="step-title"><text class="step-left">step</text> <text class="step-right">1 </text>quickStartFunctions 云函数代码</view>
|
||||
<view class="code_zone">
|
||||
<rich-text nodes="<pre style='overflow: scroll;'>{{callMiniProgramCode}}</pre>" />
|
||||
</view>
|
||||
<view class="step-title"><text class="step-left">step</text> <text class="step-right">2 </text>小程序代码段</view>
|
||||
<view class="code_zone">
|
||||
<rich-text nodes="<pre style='overflow: scroll;'>{{callFunctionCode}}</pre>" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="block">
|
||||
<view class="btn-full" bind:tap="getCodeSrc" wx:if="{{!haveGetCodeSrc}}">运行示例</view>
|
||||
<view class="box_text" wx:if="{{!codeSrc}}">小程序码将展示在这里</view>
|
||||
<view wx:if="{{codeSrc}}" class="code_box">
|
||||
<image class="code_img" src="{{codeSrc}}"></image>
|
||||
</view>
|
||||
<view class="button_clear" bindtap="clearCodeSrc" wx:if="{{haveGetCodeSrc}}">清空</view>
|
||||
<cloud-tip-modal showTipProps="{{showTip}}" title="{{title}}" content="{{content}}"></cloud-tip-modal>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<block wx:if="{{ type === 'createCollection' }}">
|
||||
<view class="page-container">
|
||||
<view class="title">功能介绍</view>
|
||||
<view class="info">集合为常用数据库中表的概念。云开发数据库支持自动备份、无损回档,并且QPS高达3千+。</view>
|
||||
<view class="title">如何体验</view>
|
||||
<view class="info">已自动创建名为“sales”的体验合集,可打开“云开发控制台>数据库>记录列表”中找到该集合。</view>
|
||||
<image class="img" src="../../images/database.png"></image>
|
||||
<view class="title">云函数代码示例</view>
|
||||
<view class="code_zone">
|
||||
<rich-text nodes="<pre style='overflow: scroll;'>{{callCreateCollectionCode}}</pre>" />
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<block wx:if="{{ type === 'selectRecord' }}">
|
||||
<view class="page-container">
|
||||
<view class="title">功能介绍</view>
|
||||
<view class="top_tip">体验查询记录能力,查询数据表中的销量数据。</view>
|
||||
<view class="box_text" wx:if="{{!record}}">销量数据将展示在这里</view>
|
||||
<view class="title">数据库操作示例</view>
|
||||
<view class="top_tip">参考云函数 quickstartFunctions 示例代码</view>
|
||||
<view wx:if="{{record}}" class="code_box">
|
||||
<view class="code_box_title">地区销量统计</view>
|
||||
<view class="code_box_record">
|
||||
<view class="code_box_record_title">地域</view>
|
||||
<view class="code_box_record_title">城市</view>
|
||||
<view class="code_box_record_title">销量</view>
|
||||
<view class="code_box_record_title">操作</view>
|
||||
</view>
|
||||
<view class="line"></view>
|
||||
<view class="code_box_record" wx:for="{{record}}" wx:key="_id">
|
||||
<view class="code_box_record_detail">{{item.region}}</view>
|
||||
<view class="code_box_record_detail">{{item.city}}</view>
|
||||
<!-- <view class="code_box_record_detail">{{item.sales}}</view> -->
|
||||
<input style="background-color: rgba(0, 0, 0, 0.03)" class="code_box_record_detail" bindinput="bindInput" data-index="{{index}}" value="{{item.sales}}" type="number"></input>
|
||||
<view class="code_box_record_detail">
|
||||
<button style="font-size: 12px" bind:tap="deleteRecord" data-id="{{item._id}}">删除</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="btn-full" bind:tap="getRecord" >查询记录</view>
|
||||
<view class="btn-full" bind:tap="updateRecord" >更新记录</view>
|
||||
<view class="btn-full" bind:tap="insertRecord" >新增记录</view>
|
||||
<!-- <view class="button_clear" bindtap="clearRecord" wx:if="{{haveGetRecord}}">清空</view> -->
|
||||
<cloud-tip-modal showTipProps="{{showTip}}"></cloud-tip-modal>
|
||||
</view>
|
||||
<view wx:if="{{showInsertModal}}" class="modal-mask">
|
||||
<view class="modal-content">
|
||||
<view class="modal-title">新增销量记录</view>
|
||||
<input class="modal-input" placeholder="地域" value="{{insertRegion}}" bindinput="onInsertRegionInput"/>
|
||||
<input class="modal-input" placeholder="城市" value="{{insertCity}}" bindinput="onInsertCityInput"/>
|
||||
<input class="modal-input" placeholder="销量" value="{{insertSales}}" bindinput="onInsertSalesInput" type="number"/>
|
||||
<view class="modal-actions">
|
||||
<button bindtap="onInsertCancel">取消</button>
|
||||
<button bindtap="onInsertConfirm" type="primary">确认</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<block wx:if="{{ type === 'uploadFile' }}">
|
||||
<view class="page-container">
|
||||
<view class="title">功能介绍</view>
|
||||
<view class="top_tip">多存储类型,仅需一个云函数即可完成上传。</view>
|
||||
<view class="title">文件上传示例</view>
|
||||
<view class="block">
|
||||
<view class="step-title">小程序代码段</view>
|
||||
<view class="code_zone">
|
||||
<rich-text nodes="<pre style='overflow: scroll;'>{{callUploadFileCode}}</pre>" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="btn-full" bind:tap="uploadImg" wx:if="{{!haveGetImgSrc}}">运行示例</view>
|
||||
<view class="box_text" wx:if="{{!imgSrc}}">上传的图片将展示在这里</view>
|
||||
<view wx:if="{{imgSrc}}" class="code_box">
|
||||
<image class="code_img" src="{{imgSrc}}"></image>
|
||||
<!-- <view class="img_info">
|
||||
<view class="img_info_title">文件路径</view>
|
||||
<view class="img_info_detail">{{imgSrc}}</view>
|
||||
</view> -->
|
||||
</view>
|
||||
<view class="button_clear" bindtap="clearImgSrc" wx:if="{{haveGetImgSrc}}">清空</view>
|
||||
<cloud-tip-modal showTipProps="{{showTip}}"></cloud-tip-modal>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<block wx:if="{{ type === 'model-guide'}}">
|
||||
<view class="page-container">
|
||||
<view class="title">功能介绍</view>
|
||||
<view class="top_tip">腾讯云开发提供 AI 对话能力,支持 Agent,大模型流式对话,可通过 Agent-UI 组件快速集成 AI 能力</view>
|
||||
<view class="title">集成 Agent-UI 组件指引</view>
|
||||
<view class="block">
|
||||
<view class="step-title"><text class="step-left">step</text> <text class="step-right">1 </text>拷贝组件源码包</view>
|
||||
<view style="display: flex;align-items: center;">点击复制查看组件仓库地址 <image mode="widthFix" style="width: 20px;height: 20px" bind:tap="copyUrl" src='../../images/copy.svg'/></view>
|
||||
<view class="step-title"><text class="step-left">step</text> <text class="step-right">2 </text>将组件拷贝至小程序目录中</view>
|
||||
<image class="img" mode="widthFix" src="../../images/ai_example2.png"></image>
|
||||
<view class="step-title"><text class="step-left">step</text> <text class="step-right">3 </text>在页面 .json 配置文件中注册组件</view>
|
||||
<view class="code_zone">
|
||||
<rich-text nodes="<pre style='overflow: scroll;'>{{ai_page_config}}</pre>" />
|
||||
</view>
|
||||
<view class="step-title"><text class="step-left">step</text> <text class="step-right">4 </text>在页面 .wxml 文件中引用组件</view>
|
||||
<view class="code_zone">
|
||||
<rich-text nodes="<pre style='overflow: scroll;'>{{ai_wxml_config}}</pre>" />
|
||||
</view>
|
||||
<view class="step-title"><text class="step-left">step</text> <text class="step-right">4 </text>在页面 .js 文件中编写配置</view>
|
||||
<view class="code_zone">
|
||||
<rich-text nodes="<pre style='overflow: scroll;'>{{ai_data_config}}</pre>" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<block wx:if="{{ type === 'cloudbaserun' }}">
|
||||
<view class="page-container">
|
||||
<view class="title">功能介绍</view>
|
||||
<view class="info">云托管 支持托管用任意语言和框架编写的容器化应用,为开发者提供高可用、自动弹性扩缩的云服务。</view>
|
||||
<view class="title">小程序调用云托管示例</view>
|
||||
<view class="block">
|
||||
<view class="step-title"><text class="step-left">step</text> <text class="step-right">1 </text>前往云开发平台开通云托管</view>
|
||||
<view class="step-title"><text class="step-left">step</text> <text class="step-right">2 </text>新建容器型托管服务,等待部署完成</view>
|
||||
<view class="step-text">
|
||||
此处可使用 Express 示例模板进行安装,此处示例命名为 express-test
|
||||
</view>
|
||||
<view>
|
||||
<image class="img" src="../../images/create_cbr.png" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view class="step-title"><text class="step-left">step</text> <text class="step-right">3</text>小程序端调用</view>
|
||||
<view class="code_zone">
|
||||
<rich-text nodes="<pre style='overflow: scroll;'>{{callcbrCode}}</pre>" />
|
||||
</view>
|
||||
<view class="btn-full" bind:tap="runCallContainer" wx:if="{{!haveGetCallContainerRes}}">运行示例</view>
|
||||
<view class="box_text">{{ callContainerResStr ? callContainerResStr : '云托管调用结果将展示在这里' }}</view>
|
||||
<view class="button_clear" bindtap="clearCallContainerRes" wx:if="{{haveGetCallContainerRes}}">清空</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<block wx:if="{{ type === 'ai-assistant' }}">
|
||||
<view class="page-container">
|
||||
<view class="title">AI 智能开发小程序</view>
|
||||
<view class="info">连接 AI 开发工具与 MCP 开发小程序</view>
|
||||
|
||||
<view class="block">
|
||||
<view class="step-title">
|
||||
<text class="step-left">step</text>
|
||||
<text class="step-right">1 </text>
|
||||
打开扩展面板
|
||||
</view>
|
||||
<view class="step-text">
|
||||
在微信开发者工具中使用快捷键打开扩展面板:
|
||||
</view>
|
||||
<view class="notice-box">
|
||||
<view class="notice-text">
|
||||
<text class="notice-highlight">注意:</text>使用快捷键前需先点击编辑区,将焦点从预览区切换到编辑区。
|
||||
</view>
|
||||
</view>
|
||||
<view class="shortcut-box">
|
||||
<view class="shortcut-item">
|
||||
<view class="shortcut-label">Windows/Linux:</view>
|
||||
<view class="shortcut-key">Ctrl + Shift + X</view>
|
||||
</view>
|
||||
<view class="shortcut-item">
|
||||
<view class="shortcut-label">macOS:</view>
|
||||
<view class="shortcut-key">Shift + Command + X</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="step-title">
|
||||
<text class="step-left">step</text>
|
||||
<text class="step-right">2 </text>
|
||||
搜索并安装插件
|
||||
</view>
|
||||
<view class="step-text">
|
||||
在扩展商店中搜索:
|
||||
</view>
|
||||
<view class="plugin-name-box">
|
||||
<text class="plugin-name">微信云开发 AI ToolKit</text>
|
||||
<view class="copy-btn" bindtap="copyPluginName">
|
||||
<image class="copy-icon" src="../../images/copy.svg" mode="widthFix"></image>
|
||||
<text class="copy-text">复制</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="step-text">
|
||||
找到插件后点击安装并启用
|
||||
</view>
|
||||
|
||||
<view class="step-title">
|
||||
<text class="step-left">step</text>
|
||||
<text class="step-right">3 </text>
|
||||
打开 AI 开发功能
|
||||
</view>
|
||||
<view class="step-text">
|
||||
安装完成后,选中任意文件,点击左上角打开 AI 开发功能即可使用
|
||||
</view>
|
||||
<image class="img step-img" src="https://tcb-advanced-a656fc-1257967285.ap-shanghai.app.tcloudbase.com/imgs/wechat-ide-extension/ide-extension.png" mode="widthFix"></image>
|
||||
|
||||
<view class="divider"></view>
|
||||
|
||||
<view class="feature-section">
|
||||
<view class="feature-title">✨ 场景示例</view>
|
||||
<view class="feature-list">
|
||||
<view class="feature-item-with-examples">
|
||||
<view class="feature-main">💡 智能代码生成与补全</view>
|
||||
<view class="feature-examples">
|
||||
<view class="feature-example-wrapper">
|
||||
<text class="feature-example-text">· 帮我创建一个商品列表页面,包含图片、标题、价格和加入购物车按钮</text>
|
||||
<view class="feature-copy-btn" bindtap="copyPrompt" data-prompt="帮我创建一个商品列表页面,包含图片、标题、价格和加入购物车按钮">
|
||||
<image class="feature-copy-icon" src="../../images/copy.svg" mode="widthFix"></image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="feature-example-wrapper">
|
||||
<text class="feature-example-text">· 帮我完善这个函数,实现商品搜索功能</text>
|
||||
<view class="feature-copy-btn" bindtap="copyPrompt" data-prompt="帮我完善这个函数,实现商品搜索功能">
|
||||
<image class="feature-copy-icon" src="../../images/copy.svg" mode="widthFix"></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="feature-item-with-examples">
|
||||
<view class="feature-main">🔧 代码优化与重构建议</view>
|
||||
<view class="feature-examples">
|
||||
<view class="feature-example-wrapper">
|
||||
<text class="feature-example-text">· 优化这个页面的间隔,提升信息密度</text>
|
||||
<view class="feature-copy-btn" bindtap="copyPrompt" data-prompt="优化这个页面的间隔,提升信息密度">
|
||||
<image class="feature-copy-icon" src="../../images/copy.svg" mode="widthFix"></image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="feature-example-wrapper">
|
||||
<text class="feature-example-text">· 完善云函数调用的错误处理代码</text>
|
||||
<view class="feature-copy-btn" bindtap="copyPrompt" data-prompt="完善云函数调用的错误处理代码">
|
||||
<image class="feature-copy-icon" src="../../images/copy.svg" mode="widthFix"></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {
|
||||
"nav-bar": "/components/nav-bar/index"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
<view class="page ui-page">
|
||||
<nav-bar title="主页" theme="blur" />
|
||||
<view class="hero" style="background-image: linear-gradient(rgba(0, 0, 0, 0.28), rgba(0, 0, 0, 0.28)), url('{{home.heroImage}}');">
|
||||
<view class="hero-tag">城市纪实摄影</view>
|
||||
<view class="hero-name">{{home.photographerName}}</view>
|
||||
<view class="hero-subtitle">{{home.photographerTagline}}</view>
|
||||
<view class="hero-cta" bindtap="onTapLookWorks">先看作品</view>
|
||||
</view>
|
||||
|
||||
<view class="stats-card">
|
||||
<view class="stats-main">
|
||||
<text class="stats-count">{{home.portfolioCount}}</text>
|
||||
<text class="stats-unit">组真实客片</text>
|
||||
</view>
|
||||
<view class="stats-sub">{{home.updateText}}</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-head">
|
||||
<view class="section-title">精选作品</view>
|
||||
<view class="section-link" bindtap="onTapViewAllWorks">查看全部</view>
|
||||
</view>
|
||||
<view class="works-grid">
|
||||
<view class="work-large-wrap" bindtap="onTapHomeWorkPreview" data-index="0">
|
||||
<image class="work-large" src="{{home.works[0].cover}}" mode="aspectFill"></image>
|
||||
<view class="work-mask">
|
||||
<view class="work-name">{{home.works[0].title || '客片精选'}}</view>
|
||||
<view class="work-meta">{{home.works[0].category}} · {{home.works[0].scene}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="work-right">
|
||||
<view wx:for="{{home.works}}" wx:key="id" wx:if="{{index > 0 && index < 3}}" class="work-small-wrap" bindtap="onTapHomeWorkPreview" data-index="{{index}}">
|
||||
<image class="work-small" src="{{item.cover}}" mode="aspectFill"></image>
|
||||
<view class="work-mask small">
|
||||
<view class="work-meta">{{item.category}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view class="tags-scroll" scroll-x="true" enhanced="true" show-scrollbar="false">
|
||||
<view class="tags-row">
|
||||
<view wx:for="{{home.tags}}" wx:key="*this" class="tag-item">{{item}}</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-head">
|
||||
<view class="section-title">预约套餐</view>
|
||||
</view>
|
||||
<view wx:for="{{home.packages}}" wx:key="id" class="pkg-card" data-id="{{item.id}}" bindtap="onTapPackage">
|
||||
<view class="pkg-top">
|
||||
<view wx:if="{{item.badge}}" class="pkg-badge">{{item.badge}}</view>
|
||||
<view class="pkg-slots">{{item.availableSlots}}</view>
|
||||
</view>
|
||||
<view class="pkg-title">{{item.title}}</view>
|
||||
<view class="pkg-desc">{{item.desc}}</view>
|
||||
<view class="pkg-price-row">
|
||||
<view class="pkg-price">¥{{item.price}}</view>
|
||||
<view class="pkg-origin">¥{{item.originalPrice}}</view>
|
||||
</view>
|
||||
<view class="pkg-actions">
|
||||
<view class="pkg-action ghost" data-id="{{item.id}}" catchtap="onTapPackageDetail">查看详情</view>
|
||||
<view class="pkg-action solid" data-id="{{item.id}}" catchtap="onTapPackageBook">{{item.ctaLabel}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{errorText}}" class="ui-state-error">{{errorText}}</view>
|
||||
|
||||
<view class="sticky-cta ui-btn-primary" bindtap="onTapBookNow">立即预约拍摄</view>
|
||||
</view>
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
@ -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",
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {
|
||||
"nav-bar": "/components/nav-bar/index"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<view class="page ui-page">
|
||||
<nav-bar title="我的预约" theme="blur" />
|
||||
<view class="sub">共 {{list.length}} 个预约</view>
|
||||
|
||||
<block wx:if="{{list.length}}">
|
||||
<view wx:for="{{list}}" wx:key="id" class="order-card" data-id="{{item.id}}" bindtap="onTapOrder">
|
||||
<view class="row top">
|
||||
<view class="status {{item.statusType}}">{{item.status}}</view>
|
||||
<view class="duration">{{item.duration}}</view>
|
||||
</view>
|
||||
<view class="time">{{item.time}}</view>
|
||||
<view class="location">{{item.location}}</view>
|
||||
<view class="reason">{{item.statusReason}}</view>
|
||||
<view class="actions">
|
||||
<view class="btn ghost {{!item.canContact && item.primaryText !== '再次预约' ? 'disabled' : ''}}" data-id="{{item.id}}" catchtap="onTapPrimary">{{item.primaryText}}</view>
|
||||
<view class="btn warn {{!item.canCancel && item.secondaryText === '取消预约' ? 'disabled' : ''}}" data-id="{{item.id}}" catchtap="onTapSecondary">{{item.secondaryText}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<view wx:else class="ui-state-empty">还没有预约记录,先去看看作品吧</view>
|
||||
</view>
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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}`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {
|
||||
"nav-bar": "/components/nav-bar/index"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<view class="page ui-page">
|
||||
<nav-bar title="套餐详情" showBack theme="blur" />
|
||||
|
||||
<view class="banner" style="background-image: linear-gradient(rgba(0, 0, 0, 0.34), rgba(0, 0, 0, 0.34)), url('{{detail.cover}}');">
|
||||
<view class="banner-title">{{detail.title}}</view>
|
||||
<view class="price-row">
|
||||
<view class="banner-price">¥ {{detail.price}}</view>
|
||||
<view class="banner-origin">¥ {{detail.originalPrice}}</view>
|
||||
</view>
|
||||
<view class="slots">{{detail.availableSlots}}</view>
|
||||
</view>
|
||||
|
||||
<view class="sec-title">你会收获什么</view>
|
||||
<view class="card ui-card">
|
||||
<view wx:for="{{detail.gains}}" wx:key="*this" class="line">{{item}}</view>
|
||||
</view>
|
||||
|
||||
<view class="sec-title">拍摄怎么进行</view>
|
||||
<view class="card ui-card">
|
||||
<view wx:for="{{detail.flow}}" wx:key="*this" class="line">{{index + 1}}. {{item}}</view>
|
||||
</view>
|
||||
|
||||
<view class="bottom">
|
||||
<view class="book-btn ui-btn-primary" bindtap="onTapBook">马上预约这个套餐</view>
|
||||
</view>
|
||||
</view>
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {
|
||||
"nav-bar": "/components/nav-bar/index"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<view class="page ui-page">
|
||||
<nav-bar title="作品集" theme="blur" />
|
||||
<scroll-view class="tabs" scroll-x="true" enhanced="true" show-scrollbar="false">
|
||||
<view class="tabs-row">
|
||||
<view wx:for="{{tabs}}" wx:key="*this" class="tab {{activeTab === item ? 'active' : ''}}" data-tab="{{item}}" bindtap="onTapTabFilter">{{item}}</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="summary">当前分类:{{activeTab}} · {{currentCount}} 组客片</view>
|
||||
|
||||
<view wx:if="{{leftList.length || rightList.length}}" class="waterfall">
|
||||
<view class="column">
|
||||
<view wx:for="{{leftList}}" wx:key="id" class="photo-wrap" bindtap="onTapPreview" data-id="{{item.id}}">
|
||||
<image class="photo {{item.ratio === 'tall' ? 'tall' : 'wide'}}" src="{{item.cover}}" mode="widthFix" lazy-load="true"></image>
|
||||
<view class="photo-mask">
|
||||
<view class="photo-title">{{item.title}}</view>
|
||||
<view class="photo-meta">{{item.category}} · {{item.scene}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="column">
|
||||
<view wx:for="{{rightList}}" wx:key="id" class="photo-wrap" bindtap="onTapPreview" data-id="{{item.id}}">
|
||||
<image class="photo {{item.ratio === 'tall' ? 'tall' : 'wide'}}" src="{{item.cover}}" mode="widthFix" lazy-load="true"></image>
|
||||
<view class="photo-mask">
|
||||
<view class="photo-title">{{item.title}}</view>
|
||||
<view class="photo-meta">{{item.category}} · {{item.scene}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:else class="ui-state-empty">当前分类暂无作品</view>
|
||||
|
||||
<view class="floating-cta ui-btn-primary" bindtap="onTapBook">预约同款风格拍摄</view>
|
||||
</view>
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
|
||||
"rules": [{
|
||||
"action": "allow",
|
||||
"page": "*"
|
||||
}]
|
||||
}
|
||||
|
|
@ -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": {}
|
||||
}
|
||||