
Shopping Skill
- 1 installs
- 27 repo stars
- Updated June 18, 2026
- tencentcloudbase/awesome-miniprogram-skills
WeChat Mini Program skill for designer-toy shopping: searching products, viewing details, checking store inventory, and placing orders.
About
Adds a trendy-toy shopping flow to a WeChat Mini Program covering product search, detail views, store inventory, and ordering. A developer uses it as a scenario template when building a retail shopping feature.
- Routes fuzzy and keyword intent into product search
- Covers product details, store inventory, and ordering
Shopping Skill by the numbers
- 1 all-time installs (skills.sh)
- Ranked #1,981 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Jul 27, 2026 (Skillselion catalog sync)
npx skills add https://github.com/tencentcloudbase/awesome-miniprogram-skills --skill shopping-skillAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 27 |
| Last updated | June 18, 2026 |
| Repository | tencentcloudbase/awesome-miniprogram-skills ↗ |
What it does
WeChat Mini Program skill for designer-toy shopping: searching products, viewing details, checking store inventory, and placing orders.
Files
shopping-skill 潮玩购物场景
业务流程图
用户意图
│
├─ 模糊意图("想逛逛/看看有什么潮玩")─→ searchProducts(keyword='') → 推荐列表卡片
│ │
├─ 明确关键词("Molly/SP/盲盒/手办")─→ searchProducts(keyword) → 搜索结果卡片 ─┤
│ │
│ 用户点击卡片选择某款商品 │
│ ↓ │
│ getProductDetail → 商品详情卡片 │
│ │ │
│ ┌───────────────┴───────────────┐ │
│ ↓ ↓ │
│ 用户点击"查看门店库存" 用户点击"立即购买" │
│ ↓ ↓ │
│ checkStoreStock → 库存卡片 placeOrder → 下单成功卡片 │
│ │
└─ 查询门店库存("XX在哪有货")──→ checkStoreStock → 门店库存卡片Agent 不能跳过 getProductDetail 直接调 placeOrder——必须先有 getProductDetail 返回的有效 productId。
Agent 不能编造 productId 或 storeId——必须来自上游接口返回的原值。
placeOrder 未返回成功前,禁止向用户宣布"已下单成功"。
原子接口依赖关系
| 接口 | 作用 | 组件 | 前置条件 |
|---|---|---|---|
| searchProducts | 搜索/推荐潮玩商品 | product-list-card | — |
| getProductDetail | 查看商品详情 | product-detail-card | 已有 productId(来自 searchProducts) |
| checkStoreStock | 查询门店库存 | stock-check-card | 已有 productId(来自 getProductDetail) |
| placeOrder | 下单购买 | order-success-card | 已有 productId + storeId(来自 getProductDetail/checkStoreStock) |
业务约束(跨接口铁律)
1. 输出形态
- 所有成功返回的接口(isError=false)且绑定了组件的,必须展示卡片,禁止以纯文本列出卡片中的详情数据。
- Agent 回复时可附加一句简短引导话术(如"为你推荐了这些潮玩,点击卡片查看详情"),但禁止把商品名、价格等以 markdown 列表形式展开。
2. 执行顺序
placeOrder必须在调用成功(isError=false)后才能向用户宣布"下单成功"。placeOrder必须在getProductDetail成功后调用。- 禁止并发调用
placeOrder;须等上一笔结束后再发起下一笔。
3. 数据来源
productId必须来自searchProducts/getProductDetail返回的items[].productId或productId原值,禁止编造。storeId必须来自checkStoreStock/getProductDetail返回的stores[].storeId原值,禁止编造。
4. 库存查询
checkStoreStock在没有指定 storeId 时返回所有门店的库存情况。- 库存数据为模拟数据,实际库存以门店为准。
用户意图分流
直接意图(触发本 SKILL)
- "想逛逛潮玩"
- "有什么盲盒推荐"
- "看看手办"
- "Molly 有什么新款"
- "SP 限量款"
- "XX 在哪有货"
- "帮我下单这个"
- "买一个"
- "推荐潮玩"
- "最近有什么新品"
意图分流规则
- 用户只说"想逛/推荐"等模糊表达 →
searchProducts(keyword='') - 用户说出具体品名/品类/品牌 →
searchProducts(keyword='用户说的关键词') - 用户从卡片点击选中某商品 →
getProductDetail(productId 由卡片 sendFollowUpMessage 传入) - 用户问门店/库存 →
checkStoreStock - 用户要购买 →
placeOrder(需先有 productId 和 storeId) - 用户表达歧义短语(如"那个")→ 先反问澄清,禁止猜测
// 查询门店库存
// 规范(最佳实践):
// - content:「事实陈述 + 业务动作」两段式
// - structuredContent:供 Agent 理解(精简)
// - _meta:组件渲染用(含门店地址),Agent 不可见
const { findProduct, getStores } = require('../utils/storage.js')
async function checkStoreStock({ productId, storeId } = {}) {
try {
if (!productId) {
return {
isError: true,
content: [{ type: 'text', text: '缺少 productId。禁止编造,应先调用 getProductDetail 获取有效 productId。' }]
}
}
const product = findProduct(productId)
if (!product) {
return {
isError: true,
content: [{ type: 'text', text: `未找到 productId=${productId} 的商品。禁止编造 ID。` }]
}
}
const allStores = getStores()
let stocks = (product.storeStocks || []).map(s => {
const store = allStores.find(st => st.id === s.storeId)
return {
storeId: s.storeId,
storeName: store ? store.name : `门店${s.storeId}`,
address: store ? store.address : '',
stock: s.stock
}
})
if (storeId) {
stocks = stocks.filter(s => s.storeId === Number(storeId))
}
return {
isError: false,
content: [{
type: 'text',
text: `已查到「${product.name}」${storeId ? '指定门店' : '各门店'}的库存情况。接下来为用户展示门店库存卡片,用简短话术引导用户查看,禁止以纯文本列出库存详情。`
}],
structuredContent: {
productId: product.id,
productName: product.name,
stores: stocks.map(s => ({
storeId: s.storeId,
storeName: s.storeName,
stock: s.stock
}))
},
_meta: {
imageUrl: product.imageUrl,
stores: stocks
}
}
} catch (err) {
console.error('[shopping-skill][checkStoreStock] error', err)
return {
isError: true,
content: [{ type: 'text', text: `查询库存失败:${err.message || '未知错误'}。` }]
}
}
}
module.exports = checkStoreStock
// 查看商品详情
// 规范(最佳实践):
// - content:「事实陈述 + 业务动作」两段式
// - structuredContent:供 Agent 理解(不含 imageUrl 等纯渲染字段)
// - _meta:组件渲染用(含 imageUrl、tags、storeStocks),Agent 不可见
const { findProduct, getStores } = require('../utils/storage.js')
async function getProductDetail({ productId } = {}) {
try {
if (!productId) {
return {
isError: true,
content: [{ type: 'text', text: '缺少 productId。禁止编造,应先调用 searchProducts 获取有效 productId。' }]
}
}
const product = findProduct(productId)
if (!product) {
return {
isError: true,
content: [{ type: 'text', text: `未找到 productId=${productId} 的商品。禁止编造 ID 再次调用。正确出口:引导用户重新搜索商品。` }]
}
}
const allStores = getStores()
const storeInfo = (product.storeStocks || []).map(s => {
const store = allStores.find(st => st.id === s.storeId)
return {
storeId: s.storeId,
storeName: store ? store.name : `门店${s.storeId}`,
stock: s.stock
}
})
return {
isError: false,
content: [{
type: 'text',
text: `已查到「${product.name}」的详细信息(¥${product.price})。接下来为用户展示商品详情卡片,卡片上可查看门店库存或直接购买,禁止以纯文本列出商品详情。`
}],
structuredContent: {
productId: product.id,
name: product.name,
price: product.price,
description: product.description,
categoryName: product.categoryName,
tags: product.tags || [],
stores: storeInfo
},
_meta: {
imageUrl: product.imageUrl,
tags: product.tags || []
}
}
} catch (err) {
console.error('[shopping-skill][getProductDetail] error', err)
return {
isError: true,
content: [{ type: 'text', text: `查询失败:${err.message || '未知错误'}。` }]
}
}
}
module.exports = getProductDetail
// 下单购买
// 规范(最佳实践):
// - 使用模拟下单(环境不支持真实支付)
// - content:「事实陈述 + 业务动作」两段式
// - structuredContent:供 Agent 理解(不含 imageUrl)
// - _meta:组件渲染用(含 imageUrl),Agent 不可见
const { isPreviewMode, findProduct, findStore, saveOrder } = require('../utils/storage.js')
const { genOrderId } = require('../utils/id.js')
async function placeOrder({ productId, storeId } = {}) {
try {
if (!productId || !storeId) {
return {
isError: true,
content: [{ type: 'text', text: '缺少 productId 或 storeId。禁止编造,应先调用 getProductDetail 获取有效 ID。' }]
}
}
const product = findProduct(productId)
if (!product) {
return {
isError: true,
content: [{ type: 'text', text: `未找到 productId=${productId} 的商品。禁止编造 ID 再次调用。正确出口:引导用户重新搜索商品。` }]
}
}
const store = findStore(storeId)
if (!store) {
return {
isError: true,
content: [{ type: 'text', text: `未找到 storeId=${storeId} 的门店。禁止编造 ID。` }]
}
}
// 检查库存
const storeStock = (product.storeStocks || []).find(s => s.storeId === Number(storeId))
if (storeStock && storeStock.stock <= 0) {
return {
isError: true,
content: [{ type: 'text', text: `「${product.name}」在 ${store.name} 已售罄。请引导用户选择其他门店或商品。` }]
}
}
// 预览模式:走本地 storage
if (isPreviewMode()) {
const orderId = genOrderId()
const order = {
orderId,
productId: product.id,
productName: product.name,
totalPrice: product.price,
storeId: store.id,
storeName: store.name,
orderTime: new Date().toISOString(),
status: 'paid'
}
saveOrder(order)
return {
isError: false,
content: [{
type: 'text',
text: `下单成功!订单 ${orderId},${product.name} 在 ${store.name} 已购买成功(¥${product.price})。接下来为用户展示下单成功卡片,并简短告知"已下单成功,可前往门店取货"。禁止以纯文本重复订单详情。`
}],
structuredContent: {
orderId: order.orderId,
productName: order.productName,
totalPrice: order.totalPrice,
storeName: order.storeName,
orderTime: order.orderTime,
status: 'paid'
},
_meta: {
imageUrl: product.imageUrl,
address: store.address
}
}
}
// 正式模式:调云函数
const { result } = await wx.cloud.callFunction({
name: 'shopping-skill-handler',
data: {
action: 'placeOrder',
productId: product.id,
productName: product.name,
totalPrice: product.price,
storeId: store.id,
storeName: store.name
}
})
if (result && result.code === 0) {
const order = result.data
return {
isError: false,
content: [{
type: 'text',
text: `下单成功!订单 ${order.orderId},${order.productName} 在 ${order.storeName} 已购买成功(¥${order.totalPrice})。接下来为用户展示下单成功卡片,并简短告知"已下单成功,可前往门店取货"。禁止以纯文本重复订单详情。`
}],
structuredContent: {
orderId: order.orderId,
productName: order.productName,
totalPrice: order.totalPrice,
storeName: order.storeName,
orderTime: order.orderTime,
status: 'paid'
},
_meta: {
imageUrl: product.imageUrl,
address: store.address
}
}
}
return {
isError: true,
content: [{ type: 'text', text: result?.message || '下单失败' }]
}
} catch (err) {
console.error('[shopping-skill][placeOrder] error', err)
return {
isError: true,
content: [{ type: 'text', text: `下单失败:${err.message || '未知错误'}。` }]
}
}
}
module.exports = placeOrder
// 搜索/推荐潮玩商品
// 规范(最佳实践):
// - content:「事实陈述 + 业务动作」两段式 + 禁止纯文本列详情
// - structuredContent:供 Agent 理解(精简)
// - _meta:组件渲染用(含 imageUrl),Agent 不可见
// - 失败分支:堵死错误退路 + 给出正确出口
const { getProducts } = require('../utils/storage.js')
async function searchProducts({ keyword } = {}) {
try {
const kw = (keyword || '').trim().toLowerCase()
const catalog = getProducts()
let matched = catalog
if (kw) {
matched = catalog.filter(p =>
p.name.toLowerCase().includes(kw) ||
p.categoryName.toLowerCase().includes(kw) ||
(p.tags || []).some(t => t.toLowerCase().includes(kw)) ||
(p.description || '').toLowerCase().includes(kw)
)
}
if (!matched.length) {
// 失败分支:事实陈述 + 禁止错误路径 + 给出正确出口
return {
isError: true,
content: [{
type: 'text',
text: `未在商品库中匹配到包含「${keyword}」的潮玩商品。禁止编造商品名再次调用本接口,禁止使用空关键词兜底搜索。正确出口:引导用户换个关键词(如 Molly、盲盒、手办),或直接展示推荐商品。`
}]
}
}
const picked = matched.slice(0, 3)
// structuredContent:Agent 理解(精简,不含图片)
const items = picked.map(p => ({
productId: p.id,
name: p.name,
price: p.price,
categoryName: p.categoryName,
description: p.description
}))
// _meta:组件渲染(含 imageUrl)
const viewItems = picked.map(p => ({
productId: p.id,
name: p.name,
price: p.price,
categoryName: p.categoryName,
description: p.description,
imageUrl: p.imageUrl
}))
const title = kw ? `"${keyword}" 搜索结果` : '精选推荐'
return {
isError: false,
// content:事实陈述 + 业务动作 + 禁止纯文本
content: [{
type: 'text',
text: `已${kw ? `搜索到 ${matched.length} 款匹配「${keyword}」的潮玩` : '为你精选潮玩好物'}。接下来为用户展示商品列表卡片,用简短话术引导用户从卡片中选择,禁止以纯文本列出商品详情。`
}],
structuredContent: {
items,
total: matched.length,
hasMore: matched.length > picked.length,
keyword: kw
},
_meta: {
viewItems,
title
}
}
} catch (err) {
console.error('[shopping-skill][searchProducts] error', err)
return {
isError: true,
content: [{ type: 'text', text: `搜索失败:${err.message || '未知错误'}。请引导用户稍后重试。` }]
}
}
}
module.exports = searchProducts
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
const db = cloud.database()
const COLLECTION = 'shopping_orders'
exports.main = async (event, context) => {
const { action, openid, ...params } = event
try {
switch (action) {
case 'placeOrder':
return await placeOrder(openid, params)
case 'getOrders':
return await getOrders(openid)
default:
return { success: false, errMsg: `未知 action: ${action}` }
}
} catch (err) {
console.error('[shopping-skill-handler] error:', err)
return { success: false, errMsg: err.message }
}
}
async function placeOrder(openid, params) {
const { productId, productName, quantity, totalAmount, storeId, address } = params
if (!openid || !productId || !productName) {
return { success: false, errMsg: '缺少必填参数: openid, productId, productName' }
}
const order = {
openid,
productId,
productName,
quantity: quantity || 1,
totalAmount: totalAmount || 0,
storeId: storeId || '',
address: address || '',
status: 'paid',
createdAt: db.serverDate()
}
const result = await db.collection(COLLECTION).add({ data: order })
return {
success: true,
data: {
orderId: result._id,
...order,
createdAt: new Date().toISOString()
}
}
}
async function getOrders(openid) {
if (!openid) {
return { success: false, errMsg: '缺少 openid' }
}
const result = await db.collection(COLLECTION)
.where({ openid })
.orderBy('createdAt', 'desc')
.get()
return {
success: true,
data: result.data
}
}
{
"name": "shopping-skill-handler",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"wx-server-sdk": "latest"
}
}
// 下单成功卡片组件
// 规范:
// - 基础数据从 structuredContent 获取
// - imageUrl 和门店地址从 _meta 补充
Component({
data: {
orderId: '',
productName: '',
totalPrice: 0,
storeName: '',
orderTime: '',
status: '',
imageUrl: '',
address: ''
},
lifetimes: {
created() {
this._modelCtx = wx.modelContext.getContext(this)
this._viewCtx = wx.modelContext.getViewContext(this)
const { NotificationType } = wx.modelContext
this._modelCtx.on(NotificationType.Result, (data) => {
const result = data && data.result ? data.result : {}
const sc = result.structuredContent || {}
const meta = result._meta || {}
this.setData({
orderId: sc.orderId || '',
productName: sc.productName || '',
totalPrice: sc.totalPrice || 0,
storeName: sc.storeName || '',
orderTime: sc.orderTime || '',
status: sc.status || '',
imageUrl: meta.imageUrl || '',
address: meta.address || ''
})
})
}
},
methods: {
onTapBack() {
// 返回首页浏览更多
this._modelCtx.sendFollowUpMessage({
content: [
{ type: 'text', text: '再看看其他潮玩' },
{ type: 'api/call', data: { name: 'searchProducts', arguments: { keyword: '' } } }
]
})
}
}
})
{
"component": true
}
<view class="os-card">
<view class="os-icon">✓</view>
<view class="os-status">下单成功</view>
<view class="os-divider"></view>
<view class="os-product">
<image wx:if="{{imageUrl}}" class="os-img" src="{{imageUrl}}" mode="aspectFill"></image>
<view class="os-product-info">
<view class="os-product-name">{{productName}}</view>
<view class="os-product-price">¥{{totalPrice}}</view>
</view>
</view>
<view class="os-details">
<view class="os-detail-row">
<text class="os-detail-label">订单编号</text>
<text class="os-detail-value">{{orderId}}</text>
</view>
<view class="os-detail-row">
<text class="os-detail-label">购买门店</text>
<text class="os-detail-value">{{storeName}}</text>
</view>
<view wx:if="{{address}}" class="os-detail-row">
<text class="os-detail-label">门店地址</text>
<text class="os-detail-value">{{address}}</text>
</view>
<view class="os-detail-row">
<text class="os-detail-label">下单时间</text>
<text class="os-detail-value">{{orderTime}}</text>
</view>
</view>
<view class="os-tip">请凭订单编号前往门店取货</view>
<view class="os-btn" hover-class="os-btn-hover" bind:tap="onTapBack">
继续逛逛
</view>
</view>
/* 下单成功卡片 */
/* 强调色:#FF2D78(潮玩粉) 色源 */
/* 渐变按钮:linear-gradient(135deg, #FF2D78, #7928CA) 色源 */
/* 暗黑模式:#121212(深紫黑底) 色源 */
.os-card {
background: #FFFFFF;
border-radius: 32rpx;
padding: 32rpx 24rpx;
box-shadow: 0 6rpx 24rpx rgba(255, 45, 120, 0.08);
width: 100%;
box-sizing: border-box;
text-align: center;
}
.os-icon {
width: 100rpx;
height: 100rpx;
line-height: 100rpx;
background: linear-gradient(135deg, #FF2D78, #7928CA);
color: #FFFFFF;
font-size: 48rpx;
border-radius: 50%;
margin: 0 auto 16rpx;
text-align: center;
}
.os-status {
font-size: 34rpx;
font-weight: 600;
color: rgba(0, 0, 0, 0.85);
}
.os-divider {
height: 1rpx;
background: #FFF0F5;
margin: 20rpx 0;
}
.os-product {
display: flex;
align-items: center;
padding: 16rpx;
background: #FFF0F5;
border-radius: 20rpx;
margin-bottom: 16rpx;
text-align: left;
}
.os-img {
width: 100rpx;
height: 100rpx;
border-radius: 16rpx;
margin-right: 16rpx;
background-color: #FFE4EC;
}
.os-product-info {
flex: 1;
min-width: 0;
}
.os-product-name {
font-size: 28rpx;
font-weight: 600;
color: rgba(0, 0, 0, 0.85);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.os-product-price {
font-size: 30rpx;
font-weight: 700;
color: #FF2D78;
margin-top: 4rpx;
}
.os-details {
text-align: left;
margin-bottom: 16rpx;
}
.os-detail-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8rpx 0;
font-size: 24rpx;
}
.os-detail-label {
color: rgba(0, 0, 0, 0.50);
flex-shrink: 0;
margin-right: 16rpx;
}
.os-detail-value {
color: rgba(0, 0, 0, 0.85);
text-align: right;
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.os-tip {
font-size: 22rpx;
color: rgba(0, 0, 0, 0.30);
margin-bottom: 20rpx;
padding: 12rpx;
background: #FFF0F5;
border-radius: 12rpx;
}
.os-btn {
text-align: center;
padding: 20rpx;
border-radius: 24rpx;
background: linear-gradient(135deg, #FF2D78, #7928CA);
color: #FFFFFF;
font-size: 28rpx;
font-weight: 600;
}
.os-btn-hover {
opacity: 0.85;
transform: scale(0.98);
}
/* ========== 暗黑模式 ========== */
@media (prefers-color-scheme: dark) {
.os-card {
background: #1E1E2E;
box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.3);
}
.os-status {
color: #FFFFFF;
}
.os-divider {
background: #2A1A2E;
}
.os-product {
background: #2A1A2E;
}
.os-product-name {
color: #FFFFFF;
}
.os-detail-label {
color: rgba(255, 255, 255, 0.50);
}
.os-detail-value {
color: #FFFFFF;
}
.os-tip {
color: rgba(255, 255, 255, 0.30);
background: #2A1A2E;
}
}
// 商品详情卡片组件
// 规范:
// - 基础数据从 structuredContent 获取(Agent 语义筛选后下发)
// - imageUrl 从 _meta 补充(Agent 不可见的纯渲染数据)
Component({
data: {
productId: 0,
name: '',
price: 0,
description: '',
categoryName: '',
imageUrl: '',
tags: [],
stores: []
},
lifetimes: {
created() {
this._modelCtx = wx.modelContext.getContext(this)
this._viewCtx = wx.modelContext.getViewContext(this)
const { NotificationType } = wx.modelContext
this._modelCtx.on(NotificationType.Result, (data) => {
const result = data && data.result ? data.result : {}
const sc = result.structuredContent || {}
const meta = result._meta || {}
this.setData({
productId: sc.productId,
name: sc.name || '',
price: sc.price || 0,
description: sc.description || '',
categoryName: sc.categoryName || '',
imageUrl: meta.imageUrl || '',
tags: meta.tags || [],
stores: sc.stores || []
})
})
}
},
methods: {
onTapBuy() {
if (!this.data.productId || !this.data.stores.length) return
const storeId = this.data.stores[0].storeId
this._modelCtx.sendFollowUpMessage({
content: [
{ type: 'text', text: `下单购买${this.data.name}` },
{ type: 'api/call', data: { name: 'placeOrder', arguments: { productId: this.data.productId, storeId } } }
]
})
},
onTapCheckStock() {
if (!this.data.productId) return
this._modelCtx.sendFollowUpMessage({
content: [
{ type: 'text', text: `查看${this.data.name}的门店库存` },
{ type: 'api/call', data: { name: 'checkStoreStock', arguments: { productId: this.data.productId } } }
]
})
}
}
})
{
"component": true
}
<view class="pd-card">
<image class="pd-img" src="{{imageUrl}}" mode="aspectFill"></image>
<view class="pd-body">
<view class="pd-name">{{name}}</view>
<view class="pd-price">¥{{price}}</view>
<view class="pd-cat">{{categoryName}}</view>
<view wx:if="{{description}}" class="pd-desc">{{description}}</view>
<view wx:if="{{tags.length > 0}}" class="pd-tags">
<view wx:for="{{tags}}" wx:key="*this" class="pd-tag">{{item}}</view>
</view>
<view wx:if="{{stores.length > 0}}" class="pd-stores">
<view class="pd-section-title">有货门店</view>
<view wx:for="{{stores}}" wx:key="storeId" class="pd-store-item">
<text class="pd-store-name">{{item.storeName}}</text>
<text wx:if="{{item.stock > 0}}" class="pd-store-stock">库存 {{item.stock}} 件</text>
<text wx:else class="pd-store-stock pd-store-soldout">已售罄</text>
</view>
</view>
<view class="pd-actions">
<view class="pd-btn pd-btn-secondary" hover-class="pd-btn-hover" bind:tap="onTapCheckStock">
查看门店库存
</view>
<view class="pd-btn pd-btn-primary" hover-class="pd-btn-hover" bind:tap="onTapBuy">
立即购买
</view>
</view>
</view>
</view>
/* 商品详情卡片 */
/* 强调色:#FF2D78(潮玩粉) 色源 */
/* 渐变按钮:linear-gradient(135deg, #FF2D78, #7928CA) 色源 */
/* 暗黑模式:#121212(深紫黑底) 色源 */
.pd-card {
background: #FFFFFF;
border-radius: 32rpx;
overflow: hidden;
box-shadow: 0 6rpx 24rpx rgba(255, 45, 120, 0.08);
width: 100%;
}
.pd-img {
width: 100%;
height: 360rpx;
background-color: #FFE4EC;
}
.pd-body {
padding: 20rpx 24rpx 24rpx;
}
.pd-name {
font-size: 34rpx;
font-weight: 600;
color: rgba(0, 0, 0, 0.85);
}
.pd-price {
font-size: 36rpx;
font-weight: 700;
color: #FF2D78;
margin-top: 8rpx;
}
.pd-cat {
font-size: 22rpx;
color: rgba(0, 0, 0, 0.30);
background: #FFF0F5;
display: inline-block;
padding: 4rpx 16rpx;
border-radius: 8rpx;
margin-top: 10rpx;
}
.pd-desc {
font-size: 26rpx;
color: rgba(0, 0, 0, 0.50);
line-height: 1.5;
margin-top: 14rpx;
}
.pd-tags {
display: flex;
flex-wrap: wrap;
gap: 8rpx;
margin-top: 14rpx;
}
.pd-tag {
font-size: 22rpx;
color: #FF2D78;
background: #FFF0F5;
padding: 4rpx 14rpx;
border-radius: 20rpx;
border: 1rpx solid #FF2D78;
}
.pd-stores {
margin-top: 18rpx;
padding: 16rpx;
background: #FFF0F5;
border-radius: 20rpx;
}
.pd-section-title {
font-size: 26rpx;
font-weight: 600;
color: rgba(0, 0, 0, 0.85);
margin-bottom: 10rpx;
}
.pd-store-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8rpx 0;
font-size: 24rpx;
}
.pd-store-name {
color: rgba(0, 0, 0, 0.85);
flex: 1;
}
.pd-store-stock {
color: #FF2D78;
font-weight: 500;
}
.pd-store-soldout {
color: rgba(0, 0, 0, 0.30);
}
.pd-actions {
display: flex;
gap: 16rpx;
margin-top: 24rpx;
}
.pd-btn {
flex: 1;
text-align: center;
padding: 20rpx;
border-radius: 24rpx;
font-size: 28rpx;
font-weight: 600;
}
.pd-btn-hover {
opacity: 0.85;
transform: scale(0.98);
}
.pd-btn-primary {
background: linear-gradient(135deg, #FF2D78, #7928CA);
color: #FFFFFF;
}
.pd-btn-secondary {
background: #FFF0F5;
color: #FF2D78;
border: 1rpx solid #FF2D78;
}
/* ========== 暗黑模式 ========== */
@media (prefers-color-scheme: dark) {
.pd-card {
background: #1E1E2E;
box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.3);
}
.pd-img {
background-color: #2A2A3E;
}
.pd-name {
color: #FFFFFF;
}
.pd-cat {
color: rgba(255, 255, 255, 0.50);
background: #2A1A2E;
}
.pd-desc {
color: rgba(255, 255, 255, 0.50);
}
.pd-tag {
color: #FF2D78;
background: #2A1A2E;
border-color: #FF2D78;
}
.pd-stores {
background: #2A1A2E;
}
.pd-section-title {
color: #FFFFFF;
}
.pd-store-name {
color: #FFFFFF;
}
.pd-store-soldout {
color: rgba(255, 255, 255, 0.30);
}
.pd-btn-secondary {
background: #2A1A2E;
color: #FF2D78;
border-color: #FF2D78;
}
}
// 潮玩商品列表组件
// 规范:
// - 组件渲染基于 structuredContent(Agent 语义筛选后下发)
// - imageUrl 等纯渲染字段从 _meta 补充(Agent 不可见,不参与语义筛选)
Component({
data: {
title: '精选推荐',
items: [],
total: 0,
hasMore: false
},
lifetimes: {
created() {
this._modelCtx = wx.modelContext.getContext(this)
this._viewCtx = wx.modelContext.getViewContext(this)
const { NotificationType } = wx.modelContext
this._modelCtx.on(NotificationType.Result, (data) => {
const result = data && data.result ? data.result : {}
const sc = result.structuredContent || {}
const meta = result._meta || {}
const viewItems = meta.viewItems || sc.items || []
this.setData({
items: viewItems.slice(0, 3),
total: sc.total || viewItems.length,
hasMore: sc.hasMore || (sc.total && sc.total > 3),
title: meta.title || (sc.keyword ? `"${sc.keyword}" 搜索结果` : '精选推荐')
})
})
}
},
methods: {
onTapItem(e) {
const item = e.currentTarget.dataset.item
if (!item) return
this._modelCtx.sendFollowUpMessage({
content: [
{ type: 'text', text: `查看${item.name}详情` },
{ type: 'api/call', data: { name: 'getProductDetail', arguments: { productId: item.productId } } }
]
})
}
}
})
{
"component": true
}
<view class="pl-card">
<view class="pl-header">
<view class="pl-title">{{title}}</view>
<view wx:if="{{total > 0}}" class="pl-sub">{{total}} 款</view>
</view>
<block wx:if="{{items.length > 0}}">
<view class="pl-list">
<view
wx:for="{{items}}"
wx:key="productId"
class="pl-item"
hover-class="pl-item-hover"
bind:tap="onTapItem"
data-item="{{item}}"
>
<image class="pl-img" src="{{item.imageUrl}}" mode="aspectFill"></image>
<view class="pl-info">
<view class="pl-name">{{item.name}}</view>
<view wx:if="{{item.description}}" class="pl-desc">{{item.description}}</view>
<view class="pl-foot">
<view class="pl-cat">{{item.categoryName}}</view>
<view class="pl-price">¥{{item.price}}</view>
</view>
</view>
</view>
</view>
</block>
<block wx:else>
<view class="pl-empty">
<view class="pl-empty-title">没有匹配的商品</view>
<view class="pl-empty-sub">换个关键词试试,或浏览全部推荐</view>
</view>
</block>
</view>
/* 潮玩商品列表卡片 */
/* 强调色:#FF2D78(潮玩粉) 色源 */
/* 背景:#FFF0F5(淡粉底) 色源 */
/* 渐变按钮:linear-gradient(135deg, #FF2D78, #7928CA) 色源 */
/* 暗黑模式:#121212(深紫黑底) 色源 */
.pl-card {
background: #FFFFFF;
border-radius: 32rpx;
padding: 24rpx;
box-shadow: 0 6rpx 24rpx rgba(255, 45, 120, 0.08);
width: 100%;
box-sizing: border-box;
}
.pl-header {
display: flex;
align-items: baseline;
justify-content: space-between;
margin-bottom: 18rpx;
}
.pl-title {
font-size: 34rpx;
font-weight: 600;
color: rgba(0, 0, 0, 0.85);
}
.pl-sub {
font-size: 26rpx;
color: rgba(0, 0, 0, 0.30);
}
/* 纵向列表 */
.pl-list {
display: flex;
flex-direction: column;
}
.pl-item {
display: flex;
flex-direction: row;
align-items: center;
background: #FFF0F5;
border-radius: 24rpx;
padding: 16rpx;
box-sizing: border-box;
margin-bottom: 14rpx;
}
.pl-item-hover {
opacity: 0.88;
transform: scale(0.99);
}
.pl-img {
width: 120rpx;
height: 120rpx;
border-radius: 20rpx;
background-color: #FFE4EC;
flex-shrink: 0;
}
.pl-info {
flex: 1;
min-width: 0;
margin-left: 18rpx;
display: flex;
flex-direction: column;
justify-content: center;
}
.pl-name {
font-size: 30rpx;
font-weight: 600;
color: rgba(0, 0, 0, 0.85);
line-height: 1.3;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.pl-desc {
font-size: 26rpx;
color: rgba(0, 0, 0, 0.50);
margin-top: 4rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.pl-foot {
display: flex;
align-items: baseline;
justify-content: space-between;
margin-top: 8rpx;
}
.pl-cat {
font-size: 22rpx;
color: rgba(0, 0, 0, 0.30);
background: #FFFFFF;
padding: 2rpx 12rpx;
border-radius: 8rpx;
}
.pl-price {
font-size: 30rpx;
font-weight: 700;
color: #FF2D78;
}
.pl-empty {
padding: 30rpx 16rpx;
text-align: center;
}
.pl-empty-title {
font-size: 28rpx;
color: #FF2D78;
font-weight: 600;
}
.pl-empty-sub {
font-size: 24rpx;
color: rgba(0, 0, 0, 0.30);
margin-top: 8rpx;
}
/* ========== 暗黑模式 ========== */
@media (prefers-color-scheme: dark) {
.pl-card {
background: #1E1E2E;
box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.3);
}
.pl-title {
color: #FFFFFF;
}
.pl-sub {
color: rgba(255, 255, 255, 0.30);
}
.pl-item {
background: #2A1A2E;
}
.pl-img {
background-color: #2A2A3E;
}
.pl-name {
color: #FFFFFF;
}
.pl-desc {
color: rgba(255, 255, 255, 0.50);
}
.pl-cat {
color: rgba(255, 255, 255, 0.30);
background: #1E1E2E;
}
.pl-price {
color: #FF2D78;
}
.pl-empty-title {
color: #FF2D78;
}
.pl-empty-sub {
color: rgba(255, 255, 255, 0.30);
}
}
// 门店库存卡片组件
// 规范:
// - 基础数据从 structuredContent 获取(Agent 语义筛选后下发)
// - 门店完整信息(含地址)从 _meta 补充
Component({
data: {
productName: '',
productId: 0,
stores: [],
imageUrl: ''
},
lifetimes: {
created() {
this._modelCtx = wx.modelContext.getContext(this)
this._viewCtx = wx.modelContext.getViewContext(this)
const { NotificationType } = wx.modelContext
this._modelCtx.on(NotificationType.Result, (data) => {
const result = data && data.result ? data.result : {}
const sc = result.structuredContent || {}
const meta = result._meta || {}
// _meta.stores 含完整门店信息(含地址),sc.stores 为精简版
const viewStores = meta.stores || sc.stores || []
this.setData({
productName: sc.productName || '',
productId: sc.productId,
stores: viewStores,
imageUrl: meta.imageUrl || ''
})
})
}
},
methods: {
onTapOrder(e) {
const store = e.currentTarget.dataset.store
if (!store || store.stock <= 0) return
if (!this.data.productId) return
this._modelCtx.sendFollowUpMessage({
content: [
{ type: 'text', text: `在${store.storeName}购买` },
{ type: 'api/call', data: { name: 'placeOrder', arguments: { productId: this.data.productId, storeId: store.storeId } } }
]
})
}
}
})
{
"component": true
}
<view class="sc-card">
<view class="sc-header">
<image wx:if="{{imageUrl}}" class="sc-img" src="{{imageUrl}}" mode="aspectFill"></image>
<view class="sc-title">{{productName}} - 门店库存</view>
</view>
<block wx:if="{{stores.length > 0}}">
<view class="sc-list">
<view
wx:for="{{stores}}"
wx:key="storeId"
class="sc-item"
hover-class="sc-item-hover"
bind:tap="onTapOrder"
data-store="{{item}}"
>
<view class="sc-item-left">
<view class="sc-store-name">{{item.storeName}}</view>
<view wx:if="{{item.address}}" class="sc-store-addr">{{item.address}}</view>
</view>
<view class="sc-item-right">
<view wx:if="{{item.stock > 0}}" class="sc-stock sc-stock-avail">库存 {{item.stock}} 件</view>
<view wx:else class="sc-stock sc-stock-soldout">已售罄</view>
</view>
</view>
</view>
</block>
<block wx:else>
<view class="sc-empty">
<view class="sc-empty-text">暂无门店库存信息</view>
</view>
</block>
</view>
/* 门店库存卡片 */
/* 强调色:#FF2D78(潮玩粉) 色源 */
/* 背景:#FFF0F5(淡粉底) 色源 */
/* 暗黑模式:#121212(深紫黑底) 色源 */
.sc-card {
background: #FFFFFF;
border-radius: 32rpx;
padding: 24rpx;
box-shadow: 0 6rpx 24rpx rgba(255, 45, 120, 0.08);
width: 100%;
box-sizing: border-box;
}
.sc-header {
display: flex;
align-items: center;
margin-bottom: 18rpx;
}
.sc-img {
width: 80rpx;
height: 80rpx;
border-radius: 16rpx;
margin-right: 16rpx;
background-color: #FFE4EC;
}
.sc-title {
font-size: 30rpx;
font-weight: 600;
color: rgba(0, 0, 0, 0.85);
flex: 1;
}
.sc-list {
display: flex;
flex-direction: column;
}
.sc-item {
display: flex;
justify-content: space-between;
align-items: center;
background: #FFF0F5;
border-radius: 20rpx;
padding: 18rpx;
margin-bottom: 12rpx;
}
.sc-item-hover {
opacity: 0.88;
}
.sc-item-left {
flex: 1;
min-width: 0;
}
.sc-store-name {
font-size: 28rpx;
font-weight: 600;
color: rgba(0, 0, 0, 0.85);
}
.sc-store-addr {
font-size: 22rpx;
color: rgba(0, 0, 0, 0.50);
margin-top: 4rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.sc-item-right {
margin-left: 16rpx;
flex-shrink: 0;
}
.sc-stock {
font-size: 24rpx;
font-weight: 600;
padding: 6rpx 16rpx;
border-radius: 20rpx;
}
.sc-stock-avail {
color: #FF2D78;
background: #FFE4EC;
}
.sc-stock-soldout {
color: rgba(0, 0, 0, 0.30);
background: #F5F5F5;
}
.sc-empty {
padding: 30rpx 16rpx;
text-align: center;
}
.sc-empty-text {
font-size: 26rpx;
color: rgba(0, 0, 0, 0.30);
}
/* ========== 暗黑模式 ========== */
@media (prefers-color-scheme: dark) {
.sc-card {
background: #1E1E2E;
box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.3);
}
.sc-img {
background-color: #2A2A3E;
}
.sc-title {
color: #FFFFFF;
}
.sc-item {
background: #2A1A2E;
}
.sc-store-name {
color: #FFFFFF;
}
.sc-store-addr {
color: rgba(255, 255, 255, 0.50);
}
.sc-stock-avail {
color: #FF2D78;
background: #3A1A2E;
}
.sc-stock-soldout {
color: rgba(255, 255, 255, 0.30);
background: #2A2A3E;
}
.sc-empty-text {
color: rgba(255, 255, 255, 0.30);
}
}
// 潮玩商品数据 seed(潮玩购物场景)
// 模拟泡泡玛特/得物风格的潮玩商品数据
const PRODUCTS = [
// ---- 盲盒 ----
{
id: 1001,
name: 'Molly 校园系列 盲盒',
price: 59,
categoryName: '盲盒',
description: 'Molly 校园主题盲盒,含 12 款常规款 + 1 款隐藏款',
imageUrl: 'https://via.placeholder.com/400x400/FF2D78/FFFFFF?text=Molly',
tags: ['Molly', '校园系列', '盲盒', '热门'],
storeStocks: [
{ storeId: 1, stock: 23 },
{ storeId: 2, stock: 8 },
{ storeId: 3, stock: 15 }
]
},
{
id: 1002,
name: 'SKULLPANDA 漫相集 盲盒',
price: 69,
categoryName: '盲盒',
description: 'SKULLPANDA 艺术主题盲盒,每款都有独特艺术风格',
imageUrl: 'https://via.placeholder.com/400x400/7928CA/FFFFFF?text=SP',
tags: ['SKULLPANDA', '漫相集', '盲盒', '艺术'],
storeStocks: [
{ storeId: 1, stock: 12 },
{ storeId: 2, stock: 0 },
{ storeId: 3, stock: 20 }
]
},
{
id: 1003,
name: 'DIMOO 水族馆系列 盲盒',
price: 59,
categoryName: '盲盒',
description: 'DIMOO 水族馆主题,探索海底世界的奇妙生物',
imageUrl: 'https://via.placeholder.com/400x400/00D4FF/FFFFFF?text=DIMOO',
tags: ['DIMOO', '水族馆', '盲盒', '海洋'],
storeStocks: [
{ storeId: 1, stock: 30 },
{ storeId: 2, stock: 18 },
{ storeId: 3, stock: 5 }
]
},
{
id: 1004,
name: 'LABUBU 精灵森林 盲盒',
price: 79,
categoryName: '盲盒',
description: 'LABUBU 精灵主题盲盒,神秘森林中的小精灵',
imageUrl: 'https://via.placeholder.com/400x400/FF6B35/FFFFFF?text=LABUBU',
tags: ['LABUBU', '精灵森林', '盲盒', '限定'],
storeStocks: [
{ storeId: 1, stock: 6 },
{ storeId: 2, stock: 14 },
{ storeId: 3, stock: 0 }
]
},
// ---- 手办 ----
{
id: 2001,
name: 'Molly 珍藏版 花精灵 手办',
price: 199,
categoryName: '手办',
description: 'Molly 花精灵限定手办,高约 15cm,精美涂装',
imageUrl: 'https://via.placeholder.com/400x400/FF69B4/FFFFFF?text=Molly+手办',
tags: ['Molly', '花精灵', '手办', '限定', '珍藏'],
storeStocks: [
{ storeId: 1, stock: 3 },
{ storeId: 2, stock: 0 },
{ storeId: 3, stock: 7 }
]
},
{
id: 2002,
name: 'SKULLPANDA 夜之城 手办',
price: 259,
categoryName: '手办',
description: 'SKULLPANDA 夜之城系列手办,赛博朋克风格',
imageUrl: 'https://via.placeholder.com/400x400/1A1A2E/FFFFFF?text=SP+手办',
tags: ['SKULLPANDA', '夜之城', '手办', '赛博朋克'],
storeStocks: [
{ storeId: 1, stock: 0 },
{ storeId: 2, stock: 5 },
{ storeId: 3, stock: 2 }
]
},
// ---- 周边 ----
{
id: 3001,
name: 'Molly 帆布包 托特包',
price: 89,
categoryName: '周边',
description: 'Molly 限定印花帆布包,大容量日常百搭',
imageUrl: 'https://via.placeholder.com/400x400/FFF0F5/FF2D78?text=帆布包',
tags: ['Molly', '周边', '帆布包', '限定'],
storeStocks: [
{ storeId: 1, stock: 45 },
{ storeId: 2, stock: 30 },
{ storeId: 3, stock: 50 }
]
},
{
id: 3002,
name: 'DIMOO 钥匙扣 盲盒挂件',
price: 39,
categoryName: '周边',
description: 'DIMOO 可爱造型钥匙扣,随机款式',
imageUrl: 'https://via.placeholder.com/400x400/00BFA5/FFFFFF?text=钥匙扣',
tags: ['DIMOO', '周边', '钥匙扣', '挂件'],
storeStocks: [
{ storeId: 1, stock: 60 },
{ storeId: 2, stock: 42 },
{ storeId: 3, stock: 35 }
]
}
]
const STORES = [
{ id: 1, name: '潮玩星球 万象城店', address: '深圳市南山区万象城 B1-12' },
{ id: 2, name: '潮玩星球 海岸城店', address: '深圳市南山区海岸城 3F-08' },
{ id: 3, name: '潮玩星球 壹方城店', address: '深圳市宝安区壹方城 L2-15' }
]
const ORDERS = [
{
orderId: 'ORD_20260101_001',
productId: 1001,
productName: 'Molly 校园系列 盲盒',
totalPrice: 59,
storeName: '潮玩星球 万象城店',
storeId: 1,
orderTime: '2026-01-01T10:30:00.000Z',
status: 'paid'
}
]
module.exports = {
PRODUCTS,
STORES,
ORDERS
}
{
"collections": [
{
"name": "shopping_orders",
"description": "潮玩购物订单",
"indexes": [
{ "name": "idx_openid", "field": "openid" },
{ "name": "idx_status", "field": "status" }
]
}
]
}
// 注册所有原子接口
const searchProducts = require('./apis/searchProducts.js')
const getProductDetail = require('./apis/getProductDetail.js')
const checkStoreStock = require('./apis/checkStoreStock.js')
const placeOrder = require('./apis/placeOrder.js')
function registerAPIs() {
// 创建 skill 实例,path 需与 app.json 中 agent.skills[].path 一致
const skill = wx.modelContext.createSkill('skills/shopping-skill')
// 注册原子接口,name 需与 mcp.json 中声明的一致
skill.registerAPI('searchProducts', searchProducts)
skill.registerAPI('getProductDetail', getProductDetail)
skill.registerAPI('checkStoreStock', checkStoreStock)
skill.registerAPI('placeOrder', placeOrder)
console.log('[shopping-skill] APIs registered via createSkill')
}
registerAPIs()
{
"apis": [
{
"name": "searchProducts",
"description": "搜索或推荐潮玩商品(业务对象:潮玩商品列表卡片)。\n调用前置条件:用户表达想逛潮玩或搜索特定商品时(如「有什么盲盒」「Molly」「看看手办」「推荐一下」)。\n当 keyword 为空时返回精选推荐商品;当 keyword 有值时按名称/分类/标签搜索。\n【严禁场景】禁止在无有效 keyword 时编造 keyword 调用。",
"inputSchema": {
"type": "object",
"properties": {
"keyword": {
"type": "string",
"description": "搜索关键词(商品名/品牌/分类/标签)。用户未指定具体商品时传空字符串 '' 走精选推荐。【禁止编造】用户未说出任何关键词时传空字符串。"
}
}
},
"outputSchema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"description": "商品列表",
"items": {
"type": "object",
"properties": {
"productId": { "type": "number", "description": "商品唯一 ID" },
"name": { "type": "string", "description": "商品名称" },
"price": { "type": "number", "description": "价格(元)" },
"categoryName": { "type": "string", "description": "分类名(盲盒/手办/周边)" },
"description": { "type": "string", "description": "商品简介" }
}
}
},
"total": { "type": "number", "description": "匹配总数" },
"hasMore": { "type": "boolean", "description": "是否有更多结果" },
"keyword": { "type": "string", "description": "搜索关键词(推荐场景为空字符串)" }
}
},
"_meta": { "ui": { "componentPath": "components/product-list-card/index" } }
},
{
"name": "getProductDetail",
"description": "查看某款潮玩商品的完整详情(业务对象:商品详情卡片)。\n调用前置条件:已从 searchProducts 返回的 items 中获取到具体的 productId。\n【严禁场景】禁止在未获得有效 productId 的情况下调用本接口,禁止从用户自然语言推断 productId。\n【后续动作】展示商品详情卡片后,等待用户在卡片上点击操作,禁止 Agent 跳过卡片直接调用 placeOrder。",
"inputSchema": {
"type": "object",
"properties": {
"productId": {
"type": "number",
"description": "商品唯一标识,必须来自 searchProducts 返回的 items[].productId 字段原值。【禁止编造】禁止从用户自然语言推断或截取。"
}
},
"required": ["productId"]
},
"outputSchema": {
"type": "object",
"properties": {
"productId": { "type": "number" },
"name": { "type": "string" },
"price": { "type": "number", "description": "价格(元)" },
"description": { "type": "string", "description": "详细描述" },
"categoryName": { "type": "string", "description": "分类" },
"tags": { "type": "array", "description": "标签", "items": { "type": "string" } },
"stores": { "type": "array", "description": "有货门店列表", "items": { "type": "object", "properties": { "storeId": { "type": "number" }, "storeName": { "type": "string" }, "stock": { "type": "number" } } } }
}
},
"_meta": { "ui": { "componentPath": "components/product-detail-card/index" } }
},
{
"name": "checkStoreStock",
"description": "查询某款商品在各门店的库存情况(业务对象:门店库存卡片)。\n调用前置条件:已有有效的 productId(来自 getProductDetail)。\n不传 storeId 时返回所有门店的库存;传 storeId 时返回指定门店的库存。\n【严禁场景】禁止在无有效 productId 时调用。",
"inputSchema": {
"type": "object",
"properties": {
"productId": {
"type": "number",
"description": "商品唯一标识,必须来自 getProductDetail 返回的 productId 字段原值。【禁止编造】"
},
"storeId": {
"type": "number",
"description": "可选,指定门店 id。用户未指定具体门店时留空,返回所有门店库存。"
}
},
"required": ["productId"]
},
"outputSchema": {
"type": "object",
"properties": {
"productId": { "type": "number" },
"productName": { "type": "string" },
"storeId": { "type": "number", "description": "门店 ID(仅查询指定门店时返回)" },
"storeName": { "type": "string", "description": "门店名称(仅查询指定门店时返回)" },
"stock": { "type": "number", "description": "库存数量(仅查询指定门店时返回)" },
"stores": {
"type": "array",
"description": "门店库存列表",
"items": {
"type": "object",
"properties": {
"storeId": { "type": "number", "description": "门店 ID" },
"storeName": { "type": "string", "description": "门店名称" },
"address": { "type": "string", "description": "门店地址" },
"stock": { "type": "number", "description": "库存数量" }
}
}
}
}
},
"_meta": { "ui": { "componentPath": "components/stock-check-card/index" } }
},
{
"name": "placeOrder",
"description": "下单购买潮玩商品(业务对象:下单成功卡片)。\n调用前置条件:已有有效的 productId 和 storeId(来自 getProductDetail 或 checkStoreStock)。\n【严禁场景】禁止在无有效 productId/storeId 时调用;禁止在 placeOrder 未返回成功前向用户宣布「已下单成功」。",
"inputSchema": {
"type": "object",
"properties": {
"productId": {
"type": "number",
"description": "商品唯一标识,必须来自 getProductDetail 返回的 productId 字段原值。【禁止编造】"
},
"storeId": {
"type": "number",
"description": "门店 ID,用户指定购买门店。必须来自 getProductDetail 或 checkStoreStock 返回的 stores[].storeId 原值。【禁止编造】"
}
},
"required": ["productId", "storeId"]
},
"outputSchema": {
"type": "object",
"properties": {
"orderId": { "type": "string", "description": "订单 ID" },
"productName": { "type": "string", "description": "商品名称" },
"totalPrice": { "type": "number", "description": "实付金额(元)" },
"storeName": { "type": "string", "description": "购买门店" },
"orderTime": { "type": "string", "description": "下单时间 ISO 格式" },
"status": { "type": "string", "description": "订单状态:paid" }
}
},
"_meta": { "ui": { "componentPath": "components/order-success-card/index" } }
}
],
"components": [
{ "path": "components/product-list-card/index", "relatedPage": "/pages/home/home" },
{ "path": "components/product-detail-card/index", "relatedPage": "/pages/home/home" },
{ "path": "components/stock-check-card/index", "relatedPage": "/pages/home/home" },
{ "path": "components/order-success-card/index", "relatedPage": "/pages/home/home" }
]
}
shopping-skill
潮玩购物,支持搜索商品、查看详情、查询门店库存及下单购买。
功能
- 搜索或推荐潮玩商品(盲盒/手办/周边)
- 查看商品完整详情与门店库存
- 查询各门店库存情况
- 下单购买指定门店商品
用户输入示例
- "看看有什么潮玩"
- "最近有什么新品"
- "这个盲盒有货吗"
- "我要买这个"
- "下单"
原子接口
| 接口名 | 说明 |
|---|---|
searchProducts | 搜索或推荐潮玩商品 |
getProductDetail | 查看某款潮玩商品完整详情 |
checkStoreStock | 查询某款商品在各门店的库存 |
placeOrder | 下单购买潮玩商品 |
原子组件
| 组件路径 | 说明 |
|---|---|
components/product-list-card/index | 商品列表展示 |
components/product-detail-card/index | 商品详情展示 |
components/stock-check-card/index | 门店库存查询 |
components/order-success-card/index | 下单成功结果展示 |
后端依赖
| 资源 | 名称 |
|---|---|
| 云函数 | shopping-skill-handler |
| 数据库集合 | shopping_orders |
// 订单 ID 生成工具
function genOrderId() {
const now = new Date()
const dateStr = now.toISOString().slice(0, 10).replace(/-/g, '')
const rand = Math.random().toString(36).substring(2, 8).toUpperCase()
return `ORD_${dateStr}_${rand}`
}
module.exports = { genOrderId }
// storage 工具:本地存储管理 + seed 数据注入
const { PRODUCTS, STORES, ORDERS } = require('../data/seed.js')
const PREVIEW_MODE_KEY = 'mp_skills_preview_mode'
function isPreviewMode() {
return wx.getStorageSync(PREVIEW_MODE_KEY) !== false
}
const PRODUCTS_KEY = 'shopping_products'
const STORES_KEY = 'shopping_stores'
const ORDERS_KEY = 'shopping_orders'
const VERSION_KEY = 'shopping_version'
const SEED_VERSION = 1
function ensureSeeded() {
const version = wx.getStorageSync(VERSION_KEY)
if (version !== SEED_VERSION) {
wx.setStorageSync(PRODUCTS_KEY, PRODUCTS)
wx.setStorageSync(STORES_KEY, STORES)
wx.setStorageSync(ORDERS_KEY, ORDERS)
wx.setStorageSync(VERSION_KEY, SEED_VERSION)
console.log('[shopping-skill][storage] seed data injected v' + SEED_VERSION)
}
}
function getProducts() {
ensureSeeded()
return wx.getStorageSync(PRODUCTS_KEY) || PRODUCTS
}
function getStores() {
ensureSeeded()
return wx.getStorageSync(STORES_KEY) || STORES
}
function findProduct(productId) {
const products = getProducts()
return products.find(p => p.id === Number(productId)) || null
}
function findStore(storeId) {
const stores = getStores()
return stores.find(s => s.id === Number(storeId)) || null
}
function getOrders() {
ensureSeeded()
return wx.getStorageSync(ORDERS_KEY) || ORDERS
}
function saveOrder(order) {
const orders = getOrders()
orders.push(order)
wx.setStorageSync(ORDERS_KEY, orders)
}
function getOpenid() {
const userInfo = wx.getStorageSync('userInfo')
return (userInfo && userInfo.openid) || 'anonymous'
}
module.exports = {
isPreviewMode,
getProducts,
getStores,
findProduct,
findStore,
getOrders,
saveOrder,
getOpenid
}
Related skills
Automation & Workflowsecommerce