
Bill Skill
- 1 installs
- 27 repo stars
- Updated June 18, 2026
- tencentcloudbase/awesome-miniprogram-skills
WeChat Mini Program skill for a life-payments flow: querying outstanding bills, paying online, and viewing payment history.
About
Adds a bill-payment capability set to a WeChat Mini Program for querying due bills, paying, and reviewing payment history. A developer uses it as a scenario template when building utility-payment features in a Mini Program.
- Handles due-bill query, online payment, and history lookup
- Routes on natural-language queries like checking or paying utility bills
Bill Skill by the numbers
- 1 all-time installs (skills.sh)
- Ranked #1,983 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/tencentcloudbase/awesome-miniprogram-skills --skill bill-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 a life-payments flow: querying outstanding bills, paying online, and viewing payment history.
Files
生活缴费
基于账单系统完成待缴账单查询、在线缴费与历史记录查看的能力集合。
触发场景
用户原话举例(路由命中本技能):
- "帮我查一下这个月的水电费"
- "我的话费该交了吗"
- "帮我交一下电费"
- "查一下欠了多少费"
- "看看我还有哪些账单没交"
- "帮我查查缴费记录"
- "物业费怎么交"
不适用范围
- 手机话费充值、流量包购买等 → 不在本技能范围,由充值技能处理
- 违章罚款、社保缴费等政府缴费 → 不在本技能范围
- 信用卡还款、贷款还款等金融类缴费 → 不在本技能范围
接口链路
getBills:查询当前用户所有待缴账单,含逾期提醒。payBill:基于 billId 完成单笔账单支付。getPaymentHistory:查询历史缴费记录,含金额与支付方式。
使用顺序
- 查询待缴账单是首个入口,用户提出缴费诉求时优先展示账单列表卡片。
- 用户选择账单后调用 payBill 完成支付,支付成功后展示缴费结果卡片。
- 缴费结果卡片提供"查看缴费记录"和"继续缴费"两个后续操作。
- 历史记录查询为独立查询,可在任意时刻调用。
设计风格
- 模拟支付宝/微信支付风格,绿色安全可靠。
- 强调色:#00B578(支付绿),辅助背景 #F0FFF5(淡绿底)。
- 按钮使用绿色渐变
linear-gradient(135deg, #00C987, #00B578)。 - 支持暗黑模式。
// skills/bill-skill/apis/getBills.js
const { isPreviewMode, defaultBillList, successResult, errorResult } = require('../utils/util')
async function getBills(params) {
try {
console.info('[ai-mode] [bill-skill] getBills called')
if (isPreviewMode()) {
const items = defaultBillList()
const totalAmount = items.reduce((sum, b) => sum + b.amount, 0)
const overdueCount = items.filter((b) => b.overdue).length
const msg = items.length > 0
? `查询到 ${items.length} 笔待缴账单,合计 ¥${totalAmount.toFixed(2)}`
: '暂无待缴账单'
return successResult(msg, {
items,
total: items.length,
totalAmount: Math.round(totalAmount * 100) / 100,
overdueCount
})
}
// 正式模式调云函数
const { result } = await wx.cloud.callFunction({ name: 'bill-skill-handler', data: { action: 'getBills' } })
if (result && result.code === 0) {
const d = result.data
const msg = d.items.length > 0
? `查询到 ${d.items.length} 笔待缴账单,合计 ¥${d.totalAmount.toFixed(2)}`
: '暂无待缴账单'
return successResult(msg, d)
}
return errorResult(result?.message || '查询账单失败')
} catch (err) {
console.error('[ai-mode] [bill-skill] getBills error:', err.message)
return errorResult('查询账单失败,请稍后重试')
}
}
module.exports = getBills
// skills/bill-skill/apis/getPaymentHistory.js
const { isPreviewMode, defaultPaymentHistory, successResult, errorResult } = require('../utils/util')
async function getPaymentHistory(params) {
try {
console.info('[ai-mode] [bill-skill] getPaymentHistory called')
if (isPreviewMode()) {
const items = defaultPaymentHistory()
const totalAmount = items.reduce((sum, h) => sum + h.amount, 0)
const msg = items.length > 0
? `查询到 ${items.length} 条缴费记录,共 ¥${totalAmount.toFixed(2)}`
: '暂无缴费记录'
return successResult(msg, {
items,
total: items.length,
totalAmount: Math.round(totalAmount * 100) / 100
})
}
// 正式模式调云函数
const { result } = await wx.cloud.callFunction({
name: 'bill-skill-handler',
data: { action: 'getPaymentHistory' }
})
if (result && result.code === 0) {
const d = result.data
const msg = d.items.length > 0
? `查询到 ${d.items.length} 条缴费记录,共 ¥${d.totalAmount.toFixed(2)}`
: '暂无缴费记录'
return successResult(msg, d)
}
return errorResult(result?.message || '查询缴费记录失败')
} catch (err) {
console.error('[ai-mode] [bill-skill] getPaymentHistory error:', err.message)
return errorResult('查询缴费记录失败,请稍后重试')
}
}
module.exports = getPaymentHistory
// skills/bill-skill/apis/payBill.js
const { isPreviewMode, defaultBillDetail, successResult, errorResult, getOpenid } = require('../utils/util')
async function payBill(params) {
try {
const { billId } = (params && params.arguments) || params || {}
if (!billId) {
return errorResult('请选择要缴费的账单', null, {
suggestion: { action: 'getBills', reason: '缺少 billId,需要先查询待缴账单' }
})
}
console.info('[ai-mode] [bill-skill] payBill called billId=', billId)
if (isPreviewMode()) {
const bill = defaultBillDetail(billId)
if (!bill) {
return errorResult('未找到该账单信息')
}
if (bill.status !== 'unpaid') {
return errorResult('该账单已缴费,无需重复支付')
}
const payTime = new Date().toISOString()
const orderNo = `PAY${Date.now()}${String(Math.random()).slice(2, 8)}`
const msg = `缴费成功!${bill.billTypeText} ¥${bill.amount.toFixed(2)} 已支付完成`
return successResult(msg, {
orderNo,
billId: bill.billId,
billType: bill.billType,
billTypeText: bill.billTypeText,
provider: bill.provider,
accountNo: bill.accountNo,
amount: bill.amount,
payTime,
payMethod: '微信支付',
status: 'success'
})
}
// 正式模式:调共享支付云函数
const bill = defaultBillDetail(billId)
if (!bill) {
return errorResult('未找到该账单信息')
}
if (bill.status !== 'unpaid') {
return errorResult('该账单已缴费,无需重复支付')
}
const { result } = await wx.cloud.callFunction({
name: 'payment-handler',
data: {
action: 'createPayment',
orderId: bill.billId,
totalAmount: bill.amount,
description: bill.billTypeText,
skillName: 'bill-skill'
}
})
if (result && result.code === 0) {
const d = result.data
return successResult(
`请确认支付,${bill.billTypeText} ¥${bill.amount.toFixed(2)}。`,
{ orderId: d.orderId, prepayId: d.prepayId, payParams: d.payParams, totalAmount: d.totalAmount },
{ payParams: d.payParams }
)
}
return errorResult(result?.message || '支付失败')
} catch (err) {
console.error('[ai-mode] [bill-skill] payBill error:', err.message)
return errorResult('支付失败,请稍后重试')
}
}
module.exports = payBill
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
const db = cloud.database()
const _ = db.command
const bills = [
{ billId: 'B001', billName: '水费', billType: 'water', amount: 68.50, dueDate: '2026-06-15', status: 'unpaid', provider: '市水务集团' },
{ billId: 'B002', billName: '电费', billType: 'electricity', amount: 236.80, dueDate: '2026-06-20', status: 'unpaid', provider: '市电力公司' },
{ billId: 'B003', billName: '燃气费', billType: 'gas', amount: 45.20, dueDate: '2026-06-18', status: 'unpaid', provider: '市燃气集团' },
{ billId: 'B004', billName: '话费', billType: 'phone', amount: 99.00, dueDate: '2026-06-25', status: 'unpaid', provider: '中国移动' },
{ billId: 'B005', billName: '物业费', billType: 'property', amount: 320.00, dueDate: '2026-06-30', status: 'unpaid', provider: '万科物业' }
]
const historyBills = [
{ billId: 'H001', billName: '电费', billType: 'electricity', amount: 210.50, payTime: '2026-05-20T10:30:00', status: 'paid', provider: '市电力公司' },
{ billId: 'H002', billName: '水费', billType: 'water', amount: 55.00, payTime: '2026-05-15T14:20:00', status: 'paid', provider: '市水务集团' },
{ billId: 'H003', billName: '燃气费', billType: 'gas', amount: 38.60, payTime: '2026-05-18T09:15:00', status: 'paid', provider: '市燃气集团' }
]
async function handleGetBills() {
return { code: 0, message: 'success', data: { items: bills } }
}
async function handlePayBill({ openid, billId, billName, amount, billType }) {
if (!openid) return { code: -1, message: 'openid 不能为空', data: null }
const bill = bills.find(b => b.billId === billId)
if (!bill) return { code: -1, message: 'bill_not_found', data: null }
const payTime = new Date().toISOString()
const transactionId = `P${Date.now().toString(36).toUpperCase()}`
try {
await db.collection('bill_records').add({
data: { openid, billId, billName, billType, amount, payTime, transactionId, createdAt: db.serverDate() }
})
} catch (e) {
console.error('[bill-skill-handler] save bill_record failed:', e.message)
}
return {
code: 0, message: 'success',
data: { billId, billName, billType, amount, payTime, transactionId, status: 'paid' }
}
}
async function handleGetPaymentHistory(uid) {
if (!uid) return { code: -1, message: 'uid 不能为空', data: null }
try {
const res = await db.collection('bill_records')
.where({ _openid: uid })
.orderBy('payTime', 'desc')
.get()
return { code: 0, message: 'success', data: { items: res.data || [] } }
} catch (e) {
console.error('[bill-skill-handler] getPaymentHistory error:', e.message)
return { code: 0, message: 'success', data: { items: historyBills } }
}
}
exports.main = async (event) => {
const wxContext = cloud.getWXContext()
const uid = wxContext.OPENID || 'anonymous'
const { action } = event
console.log('[bill-skill-handler] action=', action, 'uid=', uid)
switch (action) {
case 'getBills': return handleGetBills()
case 'payBill': return handlePayBill(event, uid)
case 'getPaymentHistory': return handleGetPaymentHistory(uid)
default: return { code: -1, message: `未知 action: ${action}` }
}
}
{
"name": "bill-skill-handler",
"version": "1.0.0",
"description": "bill-skill 云函数",
"main": "index.js",
"dependencies": {
"wx-server-sdk": "latest"
}
}
// skills/bill-skill/components/bill-list-card/index.js
Component({
data: {
items: [],
total: 0,
totalAmount: '0.00',
overdueCount: 0
},
lifetimes: {
created() {
console.info('[ai-mode] bill-list-card created')
const { NotificationType } = wx.modelContext
const modelCtx = wx.modelContext.getContext(this)
modelCtx.on(NotificationType.Result, (data) => {
const sc = (data && data.result && data.result.structuredContent) || {}
console.info('[ai-mode] bill-list-card 收到 Result:', JSON.stringify(sc))
this.setData({
items: sc.items || [],
total: sc.total || 0,
totalAmount: String(sc.totalAmount || '0.00'),
overdueCount: sc.overdueCount || 0
})
})
const viewCtx = wx.modelContext.getViewContext(this)
try {
const { width, minHeight, maxHeight } = viewCtx.getDimensions()
console.info(`[ai-mode] bill-list-card dimensions width=${width} minHeight=${minHeight} maxHeight=${maxHeight}`)
} catch (e) {
console.info('[ai-mode] bill-list-card getDimensions skipped:', e.message)
}
viewCtx.on(NotificationType.Overflow, (data) => {
const overflowed = !!(data && data.overflowHeight > 0)
console.info(`[ai-mode] bill-list-card overflow overflowed=${overflowed} data=${JSON.stringify(data)}`)
})
console.info('[ai-mode] bill-list-card overflow monitor=on')
}
},
methods: {
onTapPay(e) {
const { billId, billTypeText, amount } = e.currentTarget.dataset
console.info(`[ai-mode] bill-list-card send api/call name=payBill args=${JSON.stringify({ billId })}`)
wx.modelContext.getContext(this).sendFollowUpMessage({
content: [
{ type: 'text', text: `缴纳${billTypeText} ¥${amount}` },
{ type: 'api/call', data: { name: 'payBill', arguments: { billId } } }
]
})
}
}
})
{
"component": true,
"usingComponents": {}
}
<view class="bill-list-card">
<!-- 头部汇总 -->
<view class="header">
<view class="header-title">待缴账单</view>
<view class="header-count" wx:if="{{items.length > 0}}">共 {{total}} 笔 · 合计 <text class="header-amount">¥{{totalAmount}}</text></view>
<view class="header-empty" wx:else>暂无待缴账单</view>
</view>
<!-- 逾期提醒 -->
<view class="overdue-banner" wx:if="{{overdueCount > 0}}">
<text class="overdue-icon">⚠️</text>
<text class="overdue-text">{{overdueCount}} 笔账单已逾期,请尽快处理</text>
</view>
<!-- 账单列表 -->
<view class="bill-list">
<view
class="bill-item"
wx:for="{{items}}"
wx:key="billId"
data-bill-id="{{item.billId}}"
data-bill-type="{{item.billType}}"
data-bill-type-text="{{item.billTypeText}}"
data-amount="{{item.amount}}"
data-provider="{{item.provider}}"
data-account-no="{{item.accountNo}}"
bindtap="onTapPay"
>
<!-- 图标 -->
<view class="bill-icon-wrap">
<image class="bill-icon" src="/skills/bill-skill/components/bill-list-card/icons/{{item.billType}}.png" mode="aspectFit" />
</view>
<!-- 信息 -->
<view class="bill-info">
<view class="bill-type">{{item.billTypeText}}</view>
<view class="bill-provider">{{item.provider}}</view>
<view class="bill-meta">
<text class="bill-account">户号 {{item.accountNo}}</text>
<text class="bill-divider">|</text>
<text class="bill-due {{item.overdue ? 'overdue' : ''}}">到期 {{item.dueDate}}</text>
</view>
</view>
<!-- 金额 -->
<view class="bill-amount-wrap">
<view class="bill-amount">¥{{item.amount}}</view>
<view class="bill-amount-label">{{item.overdue ? '逾期' : '待缴'}}</view>
</view>
</view>
</view>
</view>
/* skills/bill-skill/components/bill-list-card/index.wxss */
/* 色源:支付绿 #00B578 / 淡绿底 #F0FFF5 / 深底(暗黑) #1C1C1E */
.bill-list-card {
background: #FFFFFF; /* 亮色卡片底 */
border-radius: 16px; /* 苹果式大圆角 */
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
overflow: hidden;
margin: 8px 0;
}
/* 暗黑模式 */
@media (prefers-color-scheme: dark) {
.bill-list-card {
background: #2C2C2E;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2);
}
}
/* 头部 */
.header {
padding: 16px 16px 12px;
}
.header-title {
font-size: 17px;
font-weight: 600;
color: rgba(0, 0, 0, 0.85); /* 主文色阶 */
margin-bottom: 4px;
}
@media (prefers-color-scheme: dark) {
.header-title {
color: rgba(255, 255, 255, 0.85);
}
}
.header-count {
font-size: 13px;
color: rgba(0, 0, 0, 0.50); /* 次要色阶 */
}
.header-amount {
color: #00B578; /* 支付绿 */
font-weight: 600;
}
.header-empty {
font-size: 15px;
color: rgba(0, 0, 0, 0.30); /* 辅助色阶 */
padding: 24px 0;
text-align: center;
}
@media (prefers-color-scheme: dark) {
.header-count {
color: rgba(255, 255, 255, 0.50);
}
.header-empty {
color: rgba(255, 255, 255, 0.30);
}
}
/* 逾期提醒 */
.overdue-banner {
background: #FFF3E0; /* 浅橙底 */
margin: 0 16px 8px;
padding: 8px 12px;
border-radius: 8px;
display: flex;
align-items: center;
gap: 6px;
}
.overdue-icon {
font-size: 14px;
}
.overdue-text {
font-size: 13px;
color: #E65100;
flex: 1;
}
@media (prefers-color-scheme: dark) {
.overdue-banner {
background: #3E2723;
}
.overdue-text {
color: #FFB74D;
}
}
/* 账单列表 */
.bill-list {
padding: 0 0 4px;
}
.bill-item {
display: flex;
align-items: center;
padding: 12px 16px;
border-top: 1px solid rgba(0, 0, 0, 0.05);
position: relative;
}
.bill-item-active {
background: #F0FFF5; /* 淡绿底触摸反馈 */
}
@media (prefers-color-scheme: dark) {
.bill-item {
border-top-color: rgba(255, 255, 255, 0.08);
}
.bill-item-active {
background: #1A3A2A;
}
}
/* 图标 */
.bill-icon-wrap {
width: 40px;
height: 40px;
border-radius: 10px;
background: #F0FFF5; /* 淡绿底 */
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
flex-shrink: 0;
}
.bill-icon {
width: 22px;
height: 22px;
}
@media (prefers-color-scheme: dark) {
.bill-icon-wrap {
background: #1A3A2A;
}
}
/* 信息区 */
.bill-info {
flex: 1;
min-width: 0;
}
.bill-type {
font-size: 15px;
font-weight: 500;
color: rgba(0, 0, 0, 0.85);
margin-bottom: 2px;
}
.bill-provider {
font-size: 13px;
color: rgba(0, 0, 0, 0.50);
margin-bottom: 2px;
}
.bill-meta {
font-size: 12px;
color: rgba(0, 0, 0, 0.30);
display: flex;
align-items: center;
gap: 4px;
}
.bill-divider {
color: rgba(0, 0, 0, 0.15);
}
.bill-due.overdue {
color: #E65100;
}
@media (prefers-color-scheme: dark) {
.bill-type {
color: rgba(255, 255, 255, 0.85);
}
.bill-provider {
color: rgba(255, 255, 255, 0.50);
}
.bill-meta {
color: rgba(255, 255, 255, 0.30);
}
.bill-divider {
color: rgba(255, 255, 255, 0.15);
}
.bill-due.overdue {
color: #FFB74D;
}
}
/* 金额区 */
.bill-amount-wrap {
text-align: right;
margin-left: 8px;
flex-shrink: 0;
}
.bill-amount {
font-size: 17px;
font-weight: 600;
color: rgba(0, 0, 0, 0.85);
}
.bill-amount-label {
font-size: 12px;
color: rgba(0, 0, 0, 0.30);
margin-top: 2px;
}
@media (prefers-color-scheme: dark) {
.bill-amount {
color: rgba(255, 255, 255, 0.85);
}
.bill-amount-label {
color: rgba(255, 255, 255, 0.30);
}
}
// skills/bill-skill/components/history-card/index.js
Component({
data: {
items: [],
total: 0,
totalAmount: '0.00'
},
lifetimes: {
created() {
console.info('[ai-mode] history-card created')
const { NotificationType } = wx.modelContext
const modelCtx = wx.modelContext.getContext(this)
modelCtx.on(NotificationType.Result, (data) => {
const sc = (data && data.result && data.result.structuredContent) || {}
console.info('[ai-mode] history-card 收到 Result:', JSON.stringify(sc))
this.setData({
items: sc.items || [],
total: sc.total || 0,
totalAmount: String(sc.totalAmount || '0.00')
})
})
const viewCtx = wx.modelContext.getViewContext(this)
try {
const { width, minHeight, maxHeight } = viewCtx.getDimensions()
console.info(`[ai-mode] history-card dimensions width=${width} minHeight=${minHeight} maxHeight=${maxHeight}`)
} catch (e) {
console.info('[ai-mode] history-card getDimensions skipped:', e.message)
}
viewCtx.on(NotificationType.Overflow, (data) => {
const overflowed = !!(data && data.overflowHeight > 0)
console.info(`[ai-mode] history-card overflow overflowed=${overflowed} data=${JSON.stringify(data)}`)
})
console.info('[ai-mode] history-card overflow monitor=on')
}
},
methods: {}
})
{
"component": true,
"usingComponents": {}
}
<view class="history-card">
<!-- 头部 -->
<view class="header">
<view class="header-title">缴费记录</view>
<view class="header-count" wx:if="{{items.length > 0}}">共 {{total}} 笔 · 合计 <text class="header-amount">¥{{totalAmount}}</text></view>
<view class="header-empty" wx:else>暂无缴费记录</view>
</view>
<!-- 记录列表 -->
<view class="history-list">
<view class="history-item" wx:for="{{items}}" wx:key="historyId">
<!-- 图标 -->
<view class="history-icon-wrap">
<image class="history-icon" src="/skills/bill-skill/components/bill-list-card/icons/{{item.billType}}.png" mode="aspectFit" />
</view>
<!-- 信息 -->
<view class="history-info">
<view class="history-type">{{item.billTypeText}}</view>
<view class="history-provider">{{item.provider}}</view>
<view class="history-meta">
<text class="history-account">户号 {{item.accountNo}}</text>
<text class="history-divider">|</text>
<text class="history-time">{{item.payTime}}</text>
</view>
</view>
<!-- 金额与方式 -->
<view class="history-amount-wrap">
<view class="history-amount">-¥{{item.amount}}</view>
<view class="history-paymethod">{{item.payMethod}}</view>
</view>
</view>
</view>
</view>
/* skills/bill-skill/components/history-card/index.wxss */
/* 色源:支付绿 #00B578 / 淡绿底 #F0FFF5 / 深底(暗黑) #1C1C1E */
.history-card {
background: #FFFFFF;
border-radius: 16px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
overflow: hidden;
margin: 8px 0;
}
@media (prefers-color-scheme: dark) {
.history-card {
background: #2C2C2E;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2);
}
}
/* 头部 */
.header {
padding: 16px 16px 12px;
}
.header-title {
font-size: 17px;
font-weight: 600;
color: rgba(0, 0, 0, 0.85);
margin-bottom: 4px;
}
.header-count {
font-size: 13px;
color: rgba(0, 0, 0, 0.50);
}
.header-amount {
color: #00B578;
font-weight: 600;
}
.header-empty {
font-size: 15px;
color: rgba(0, 0, 0, 0.30);
padding: 24px 0;
text-align: center;
}
@media (prefers-color-scheme: dark) {
.header-title {
color: rgba(255, 255, 255, 0.85);
}
.header-count {
color: rgba(255, 255, 255, 0.50);
}
.header-empty {
color: rgba(255, 255, 255, 0.30);
}
}
/* 列表 */
.history-list {
padding: 0 0 4px;
}
.history-item {
display: flex;
align-items: center;
padding: 12px 16px;
border-top: 1px solid rgba(0, 0, 0, 0.05);
}
@media (prefers-color-scheme: dark) {
.history-item {
border-top-color: rgba(255, 255, 255, 0.08);
}
}
/* 图标 */
.history-icon-wrap {
width: 40px;
height: 40px;
border-radius: 10px;
background: #F0FFF5;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
flex-shrink: 0;
}
.history-icon {
width: 22px;
height: 22px;
}
@media (prefers-color-scheme: dark) {
.history-icon-wrap {
background: #1A3A2A;
}
}
/* 信息 */
.history-info {
flex: 1;
min-width: 0;
}
.history-type {
font-size: 15px;
font-weight: 500;
color: rgba(0, 0, 0, 0.85);
margin-bottom: 2px;
}
.history-provider {
font-size: 13px;
color: rgba(0, 0, 0, 0.50);
margin-bottom: 2px;
}
.history-meta {
font-size: 12px;
color: rgba(0, 0, 0, 0.30);
display: flex;
align-items: center;
gap: 4px;
}
.history-divider {
color: rgba(0, 0, 0, 0.15);
}
@media (prefers-color-scheme: dark) {
.history-type {
color: rgba(255, 255, 255, 0.85);
}
.history-provider {
color: rgba(255, 255, 255, 0.50);
}
.history-meta {
color: rgba(255, 255, 255, 0.30);
}
.history-divider {
color: rgba(255, 255, 255, 0.15);
}
}
/* 金额 */
.history-amount-wrap {
text-align: right;
margin-left: 8px;
flex-shrink: 0;
}
.history-amount {
font-size: 15px;
font-weight: 500;
color: #E53935;
}
.history-paymethod {
font-size: 12px;
color: rgba(0, 0, 0, 0.30);
margin-top: 2px;
}
@media (prefers-color-scheme: dark) {
.history-amount {
color: #EF5350;
}
.history-paymethod {
color: rgba(255, 255, 255, 0.30);
}
}
// skills/bill-skill/components/pay-result-card/index.js
Component({
data: {
orderNo: '',
billId: '',
billType: '',
billTypeText: '',
provider: '',
accountNo: '',
amount: '0.00',
payTime: '',
payMethod: '',
status: 'success'
},
lifetimes: {
created() {
console.info('[ai-mode] pay-result-card created')
const { NotificationType } = wx.modelContext
const modelCtx = wx.modelContext.getContext(this)
modelCtx.on(NotificationType.Result, (data) => {
const sc = (data && data.result && data.result.structuredContent) || {}
console.info('[ai-mode] pay-result-card 收到 Result:', JSON.stringify(sc))
this.setData({
orderNo: sc.orderNo || '',
billId: sc.billId || '',
billType: sc.billType || '',
billTypeText: sc.billTypeText || '',
provider: sc.provider || '',
accountNo: sc.accountNo || '',
amount: String(sc.amount || '0.00'),
payTime: sc.payTime || '',
payMethod: sc.payMethod || '',
status: sc.status || 'success'
})
})
const viewCtx = wx.modelContext.getViewContext(this)
try {
const { width, minHeight, maxHeight } = viewCtx.getDimensions()
console.info(`[ai-mode] pay-result-card dimensions width=${width} minHeight=${minHeight} maxHeight=${maxHeight}`)
} catch (e) {
console.info('[ai-mode] pay-result-card getDimensions skipped:', e.message)
}
viewCtx.on(NotificationType.Overflow, (data) => {
const overflowed = !!(data && data.overflowHeight > 0)
console.info(`[ai-mode] pay-result-card overflow overflowed=${overflowed} data=${JSON.stringify(data)}`)
})
console.info('[ai-mode] pay-result-card overflow monitor=on')
}
},
methods: {
onTapViewHistory() {
console.info('[ai-mode] pay-result-card send api/call name=getPaymentHistory')
wx.modelContext.getContext(this).sendFollowUpMessage({
content: [
{ type: 'text', text: '查看缴费记录' },
{ type: 'api/call', data: { name: 'getPaymentHistory', arguments: {} } }
]
})
},
onTapBackList() {
console.info('[ai-mode] pay-result-card send api/call name=getBills')
wx.modelContext.getContext(this).sendFollowUpMessage({
content: [
{ type: 'text', text: '继续缴纳其他账单' },
{ type: 'api/call', data: { name: 'getBills', arguments: {} } }
]
})
}
}
})
{
"component": true,
"usingComponents": {}
}
<view class="pay-result-card">
<!-- 成功状态 -->
<view class="result-status" wx:if="{{status === 'success'}}">
<view class="status-icon success">✓</view>
<view class="status-text">缴费成功</view>
</view>
<!-- 失败状态 -->
<view class="result-status" wx:else>
<view class="status-icon fail">✕</view>
<view class="status-text">缴费失败</view>
</view>
<!-- 金额 -->
<view class="result-amount">¥{{amount}}</view>
<view class="result-amount-label">{{billTypeText}}</view>
<!-- 详情 -->
<view class="result-detail">
<view class="detail-row">
<text class="detail-label">收款方</text>
<text class="detail-value">{{provider}}</text>
</view>
<view class="detail-row">
<text class="detail-label">户号</text>
<text class="detail-value">{{accountNo}}</text>
</view>
<view class="detail-row">
<text class="detail-label">支付方式</text>
<text class="detail-value">{{payMethod}}</text>
</view>
<view class="detail-row">
<text class="detail-label">支付时间</text>
<text class="detail-value">{{payTime}}</text>
</view>
<view class="detail-row" wx:if="{{orderNo}}">
<text class="detail-label">订单编号</text>
<text class="detail-value">{{orderNo}}</text>
</view>
</view>
<!-- 操作按钮 -->
<view class="result-actions">
<button class="btn-primary" bindtap="onTapViewHistory">查看缴费记录</button>
<button class="btn-outline" bindtap="onTapBackList">继续缴费</button>
</view>
</view>
/* skills/bill-skill/components/pay-result-card/index.wxss */
/* 色源:支付绿 #00B578 / 淡绿底 #F0FFF5 / 渐变按钮 linear-gradient(135deg, #00C987, #00B578) */
.pay-result-card {
background: #FFFFFF;
border-radius: 16px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
overflow: hidden;
padding: 32px 24px 20px;
margin: 8px 0;
}
@media (prefers-color-scheme: dark) {
.pay-result-card {
background: #2C2C2E;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2);
}
}
/* 状态 */
.result-status {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 16px;
}
.status-icon {
width: 48px;
height: 48px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
font-weight: 600;
margin-bottom: 8px;
}
.status-icon.success {
background: #F0FFF5; /* 淡绿底 */
color: #00B578; /* 支付绿 */
}
.status-icon.fail {
background: #FFF0F0;
color: #E53935;
}
.status-text {
font-size: 17px;
font-weight: 600;
color: rgba(0, 0, 0, 0.85);
}
@media (prefers-color-scheme: dark) {
.status-icon.success {
background: #1A3A2A;
}
.status-text {
color: rgba(255, 255, 255, 0.85);
}
}
/* 金额 */
.result-amount {
text-align: center;
font-size: 36px;
font-weight: 700;
color: rgba(0, 0, 0, 0.85);
margin-bottom: 4px;
}
.result-amount-label {
text-align: center;
font-size: 15px;
color: rgba(0, 0, 0, 0.50);
margin-bottom: 24px;
}
@media (prefers-color-scheme: dark) {
.result-amount {
color: rgba(255, 255, 255, 0.85);
}
.result-amount-label {
color: rgba(255, 255, 255, 0.50);
}
}
/* 详情 */
.result-detail {
background: #F8F9FA;
border-radius: 12px;
padding: 16px;
margin-bottom: 24px;
}
.detail-row {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
}
.detail-row-last {
border-bottom: none;
}
.detail-label {
font-size: 14px;
color: rgba(0, 0, 0, 0.50);
}
.detail-value {
font-size: 14px;
color: rgba(0, 0, 0, 0.85);
text-align: right;
max-width: 60%;
word-break: break-all;
}
@media (prefers-color-scheme: dark) {
.result-detail {
background: #1C1C1E; /* 暗黑深底 */
}
.detail-row {
border-bottom-color: rgba(255, 255, 255, 0.08);
}
.detail-label {
color: rgba(255, 255, 255, 0.50);
}
.detail-value {
color: rgba(255, 255, 255, 0.85);
}
}
/* 按钮 */
.result-actions {
display: flex;
flex-direction: column;
gap: 12px;
}
.btn-primary {
height: 44px;
line-height: 44px;
border-radius: 12px; /* 按钮圆角 12px */
background: linear-gradient(135deg, #00C987, #00B578); /* 绿色渐变 */
color: #FFFFFF;
font-size: 16px;
font-weight: 500;
text-align: center;
border: none;
}
.btn-primary-hover {
opacity: 0.85;
}
.btn-outline {
height: 44px;
line-height: 44px;
border-radius: 12px;
background: transparent;
color: #00B578; /* 支付绿 */
font-size: 16px;
font-weight: 500;
text-align: center;
border: 1px solid #00B578; /* 支付绿边框 */
}
.btn-outline-hover {
background: #F0FFF5; /* 淡绿底 */
}
@media (prefers-color-scheme: dark) {
.btn-outline {
color: #00C987;
border-color: #00C987;
}
.btn-outline-hover {
background: #1A3A2A;
}
}
const bills = [
{
billId: 'B001',
billType: 'water',
billTypeText: '水费',
provider: '北京市自来水集团',
accountNo: 'WZ20240815001',
amount: 126.50,
dueDate: '2026-06-25',
status: 'unpaid',
overdue: false,
address: '朝阳区望京花园东区3号楼1单元502'
},
{
billId: 'B002',
billType: 'electricity',
billTypeText: '电费',
provider: '国网北京电力',
accountNo: 'ED20240512008',
amount: 328.70,
dueDate: '2026-06-20',
status: 'unpaid',
overdue: true,
address: '朝阳区望京花园东区3号楼1单元502'
},
{
billId: 'B003',
billType: 'gas',
billTypeText: '燃气费',
provider: '北京市燃气集团',
accountNo: 'GQ20231103015',
amount: 85.00,
dueDate: '2026-06-28',
status: 'unpaid',
overdue: false,
address: '朝阳区望京花园东区3号楼1单元502'
},
{
billId: 'B004',
billType: 'phone',
billTypeText: '话费',
provider: '中国移动',
accountNo: '138****5678',
amount: 50.00,
dueDate: '2026-06-30',
status: 'unpaid',
overdue: false,
address: ''
},
{
billId: 'B005',
billType: 'property',
billTypeText: '物业费',
provider: '望京花园物业管理处',
accountNo: 'WY2026Q2',
amount: 560.00,
dueDate: '2026-07-05',
status: 'unpaid',
overdue: false,
address: '朝阳区望京花园东区3号楼1单元502'
}
]
const paymentHistory = [
{
historyId: 'H001',
billType: 'water',
billTypeText: '水费',
provider: '北京市自来水集团',
accountNo: 'WZ20240815001',
amount: 115.30,
payTime: '2026-05-18 14:32:10',
payMethod: '微信支付',
status: 'success'
},
{
historyId: 'H002',
billType: 'electricity',
billTypeText: '电费',
provider: '国网北京电力',
accountNo: 'ED20240512008',
amount: 286.40,
payTime: '2026-05-10 09:15:42',
payMethod: '微信支付',
status: 'success'
},
{
historyId: 'H003',
billType: 'gas',
billTypeText: '燃气费',
provider: '北京市燃气集团',
accountNo: 'GQ20231103015',
amount: 72.00,
payTime: '2026-04-22 19:48:05',
payMethod: '零钱',
status: 'success'
}
]
module.exports = {
bills,
paymentHistory
}
{
"collections": [
{
"name": "bill_records",
"description": "缴费记录",
"indexes": [
{ "name": "idx_openid", "field": "openid" }
]
}
]
}
// skills/bill-skill/index.js
const getBills = require('./apis/getBills.js')
const payBill = require('./apis/payBill.js')
const getPaymentHistory = require('./apis/getPaymentHistory.js')
function registerAPIs() {
const skill = wx.modelContext.createSkill('skills/bill-skill')
skill.use(async (ctx, next) => {
try {
console.info('[ai-mode] [bill-skill] middleware start name=', ctx.name)
await next()
console.info('[ai-mode] [bill-skill] middleware finish name=', ctx.name)
} catch (err) {
console.error('[ai-mode] [bill-skill] middleware error:', err.message)
throw err
}
})
skill.registerAPI('getBills', getBills)
skill.registerAPI('payBill', payBill)
skill.registerAPI('getPaymentHistory', getPaymentHistory)
console.info('[ai-mode] [bill-skill] APIs registered via createSkill')
}
registerAPIs()
{
"apis": [
{
"name": "getBills",
"description": "查询当前用户的所有待缴账单(业务对象:账单列表卡片)。调用前置条件:用户提到缴费、查账单、欠费、水电费、燃气费等场景时。返回待缴账单列表,包含水费、电费、燃气费、话费、物业费等类型,并汇总总金额和逾期数量。【严禁场景】禁止在用户已经完成某笔缴费后立即重新调用本接口展示完整列表,此时应展示缴费结果卡片(payBill 的输出)。",
"_meta": {
"ui": {
"componentPath": "components/bill-list-card/index"
}
},
"inputSchema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {},
"additionalProperties": false
},
"outputSchema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"items": {
"type": "array",
"description": "待缴账单列表",
"items": {
"type": "object",
"properties": {
"billId": { "type": "string", "description": "账单唯一 ID" },
"billType": { "type": "string", "description": "账单类型编码:water/electricity/gas/phone/property" },
"billTypeText": { "type": "string", "description": "账单类型文案:水费/电费/燃气费/话费/物业费" },
"provider": { "type": "string", "description": "供应商名称" },
"accountNo": { "type": "string", "description": "户号/账号" },
"amount": { "type": "number", "description": "账单金额" },
"dueDate": { "type": "string", "description": "到期日期 YYYY-MM-DD" },
"overdue": { "type": "boolean", "description": "是否已逾期" }
},
"required": ["billId", "billType", "billTypeText", "provider", "accountNo", "amount", "dueDate", "overdue"],
"additionalProperties": false
}
},
"total": { "type": "number", "description": "待缴账单总数" },
"totalAmount": { "type": "number", "description": "待缴总金额" },
"overdueCount": { "type": "number", "description": "逾期账单数量" }
},
"required": ["items", "total", "totalAmount", "overdueCount"],
"additionalProperties": false
}
},
{
"name": "payBill",
"description": "为指定账单完成缴费支付(业务对象:缴费结果卡片)。调用前置条件:用户从待缴账单列表中选择了一笔账单,且上下文中已有 billId。返回支付结果,包含订单号、金额、支付方式、支付时间等详情。【严禁场景】禁止在没有 billId 时调用;禁止为已缴费账单重复支付;禁止编造 billId。",
"_meta": {
"ui": {
"componentPath": "components/pay-result-card/index"
}
},
"inputSchema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"billId": {
"type": "string",
"description": "账单唯一标识,必须来自上游 getBills 返回的 items[].billId 原值。【禁止编造】上下文中无 billId 时,应先调 getBills 获取待缴账单列表。"
}
},
"required": ["billId"],
"additionalProperties": false
},
"outputSchema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"orderNo": { "type": "string", "description": "支付订单号" },
"billId": { "type": "string", "description": "账单 ID" },
"billType": { "type": "string", "description": "账单类型编码" },
"billTypeText": { "type": "string", "description": "账单类型文案" },
"provider": { "type": "string", "description": "收款方" },
"accountNo": { "type": "string", "description": "户号" },
"amount": { "type": "number", "description": "支付金额" },
"payTime": { "type": "string", "description": "支付时间 ISO 字符串" },
"payMethod": { "type": "string", "description": "支付方式,如『微信支付』" },
"status": { "type": "string", "description": "支付状态,success/fail" }
},
"required": ["orderNo", "billId", "billType", "billTypeText", "provider", "accountNo", "amount", "payTime", "payMethod", "status"],
"additionalProperties": false
}
},
{
"name": "getPaymentHistory",
"description": "查询历史缴费记录(业务对象:缴费记录卡片)。调用前置条件:用户主动要求查看历史缴费记录,或在缴费成功后查看记录。返回按时间倒序排列的历史缴费记录列表。【严禁场景】禁止在用户明确要查询待缴账单时调用此接口代替 getBills。",
"_meta": {
"ui": {
"componentPath": "components/history-card/index"
}
},
"inputSchema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {},
"additionalProperties": false
},
"outputSchema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"items": {
"type": "array",
"description": "缴费记录列表,按时间倒序",
"items": {
"type": "object",
"properties": {
"historyId": { "type": "string", "description": "记录唯一 ID" },
"billType": { "type": "string", "description": "账单类型编码" },
"billTypeText": { "type": "string", "description": "账单类型文案" },
"provider": { "type": "string", "description": "供应商名称" },
"accountNo": { "type": "string", "description": "户号" },
"amount": { "type": "number", "description": "缴费金额" },
"payTime": { "type": "string", "description": "缴费时间 YYYY-MM-DD HH:mm:ss" },
"payMethod": { "type": "string", "description": "支付方式" },
"status": { "type": "string", "description": "状态,固定为 success" }
},
"required": ["historyId", "billType", "billTypeText", "provider", "accountNo", "amount", "payTime", "payMethod", "status"],
"additionalProperties": false
}
},
"total": { "type": "number", "description": "记录总数" },
"totalAmount": { "type": "number", "description": "累计缴费金额" }
},
"required": ["items", "total", "totalAmount"],
"additionalProperties": false
}
}
],
"components": [
{
"path": "components/bill-list-card/index",
"relatedPage": "/pages/home/home"
},
{
"path": "components/pay-result-card/index",
"relatedPage": "/pages/home/home"
},
{
"path": "components/history-card/index",
"relatedPage": "/pages/home/home"
}
]
}
bill-skill
生活缴费,支持查询待缴账单、完成缴费及查看历史缴费记录。
用户输入示例
- "帮我查一下这个月的水电费"
- "我要缴电费"
- "燃气费欠了多少?"
- "看看我的缴费记录"
- "话费该交了"
- "帮我交一下物业费"
- "最近三个月交了多少水电费"
功能
- 查询当前用户的所有待缴账单(水费/电费/燃气费/话费/物业费)
- 为指定账单完成缴费支付
- 查看历史缴费记录
原子接口
| 接口名 | 说明 |
|---|---|
getBills | 查询当前用户所有待缴账单 |
payBill | 为指定账单完成缴费支付 |
getPaymentHistory | 查询历史缴费记录 |
原子组件
| 组件路径 | 说明 |
|---|---|
components/bill-list-card/index | 待缴账单列表 |
components/pay-result-card/index | 缴费结果展示 |
components/history-card/index | 历史缴费记录列表 |
后端依赖
| 资源 | 名称 |
|---|---|
| 云函数 | bill-skill-handler |
| 数据库集合 | bill_records |
// skills/bill-skill/utils/util.js
const { bills, paymentHistory } = require('../data/seed')
const PREVIEW_MODE_KEY = 'mp_skills_preview_mode'
const CLOUD_ENV_ID = 'cloud1-5g39elugeec5ba0f'
let _cloudInited = false
function isPreviewMode() {
return wx.getStorageSync(PREVIEW_MODE_KEY) !== false
}
function getOpenid() {
const userInfo = wx.getStorageSync('userInfo')
return (userInfo && userInfo.openid) || 'anonymous'
}
function ensureCloudInit() {
if (_cloudInited) return
if (!wx.cloud) throw new Error('当前环境不支持 wx.cloud')
wx.cloud.init({ env: CLOUD_ENV_ID, traceUser: true })
_cloudInited = true
}
function errorResult(msg, structuredContent, meta) {
const result = { isError: true, content: [{ type: 'text', text: msg }] }
if (structuredContent !== undefined) result.structuredContent = structuredContent
if (meta !== undefined) result._meta = meta
return result
}
function successResult(msg, structuredContent, meta) {
const result = { isError: false, content: [{ type: 'text', text: msg }] }
if (structuredContent !== undefined) result.structuredContent = structuredContent
if (meta !== undefined) result._meta = meta
return result
}
function defaultBillList() {
return bills
.filter((b) => b.status === 'unpaid')
.map((b) => ({
billId: b.billId,
billType: b.billType,
billTypeText: b.billTypeText,
provider: b.provider,
accountNo: b.accountNo,
amount: b.amount,
dueDate: b.dueDate,
overdue: b.overdue
}))
}
function defaultBillDetail(billId) {
return bills.find((b) => b.billId === billId) || null
}
function defaultPaymentHistory() {
return paymentHistory.map((h) => ({
historyId: h.historyId,
billType: h.billType,
billTypeText: h.billTypeText,
provider: h.provider,
accountNo: h.accountNo,
amount: h.amount,
payTime: h.payTime,
payMethod: h.payMethod,
status: h.status
}))
}
module.exports = {
CLOUD_ENV_ID,
ensureCloudInit,
errorResult,
successResult,
defaultBillList,
defaultBillDetail,
defaultPaymentHistory,
isPreviewMode,
getOpenid
}
Related skills
Automation & Workflowsintegrations