
Todolist Skill
- 1 installs
- 27 repo stars
- Updated June 18, 2026
- tencentcloudbase/awesome-miniprogram-skills
A teaching example WeChat Mini Program skill for a simple to-do list using wx.cloud.database directly for add, complete, and delete.
About
Adds a basic to-do CRUD flow to a WeChat Mini Program by calling wx.cloud.database directly. A developer uses it as a beginner teaching example for cloud-database-backed task management.
- Directly uses wx.cloud.database for CRUD
- Covers query, add, complete-toggle, and delete of to-do items
Todolist 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 todolist-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
A teaching example WeChat Mini Program skill for a simple to-do list using wx.cloud.database directly for add, complete, and delete.
Files
简单待办清单
基于云开发数据库直接完成待办事项查询、添加、完成切换与删除的简单教学示例。
触发场景
用户原话举例(路由命中本技能):
- "帮我看下待办列表"
- "新增一个待办:明天交周报"
- "把『明天交周报』标记完成"
- "删掉那个买咖啡豆的待办"
- "我的 todo 还有哪些没做"
不适用范围
- 复杂项目管理、多人协作、日历提醒、文件附件等诉求 → 不在本技能范围
- 门店排队、下单支付、导航等诉求 → 不在本技能范围
使用顺序
- 查看待办列表无需前置条件,可直接查询。
- 修改某条待办状态前需先拿到真实 todoId;没有 todoId 时先展示列表,再从列表中选择具体待办。
- 新增、切换完成状态、删除后都重新展示最新待办列表。
const { isPreviewMode, ensureCloudInit, successResult, errorResult, addTodoLocal } = require('../utils/util')
async function addTodo(params = {}) {
console.info('[ai-mode] addTodo 入口, params=', JSON.stringify(params))
const title = String((params && params.title) || '').trim()
if (!title) {
return errorResult('缺少待办标题。禁止直接新增空待办,请先让用户明确要记录什么事项。')
}
if (isPreviewMode()) {
const data = addTodoLocal(title)
return successResult(
`已新增待办「${title}」。请展示最新待办列表卡片,并用一句简短话术告诉用户新增成功。`,
data,
{ mode: 'list' }
)
}
ensureCloudInit()
const { result } = await wx.cloud.callFunction({
name: 'todolist-skill-handler',
data: { action: 'addTodo', title }
})
if (result && result.code === 0 && result.data) {
return successResult(
`已新增待办「${title}」。请展示最新待办列表卡片,并用一句简短话术告诉用户新增成功。`,
result.data,
{ mode: 'list' }
)
}
return errorResult('新增待办失败,请稍后重试。')
}
module.exports = addTodo
const { isPreviewMode, ensureCloudInit, successResult, errorResult, deleteTodoLocal } = require('../utils/util')
async function deleteTodo(params = {}) {
console.info('[ai-mode] deleteTodo 入口, params=', JSON.stringify(params))
const todoId = params && params.todoId
if (!todoId) {
return errorResult('缺少 todoId。禁止直接删除待办,请先让用户从列表中选择具体待办。')
}
if (isPreviewMode()) {
const local = deleteTodoLocal(todoId)
if (!local) {
return errorResult('未找到该待办。禁止继续删除不存在的待办,请先重新查看列表。')
}
return successResult(
`已删除待办「${local.deleted.title}」。请展示最新待办列表卡片。`,
local.data,
{ mode: 'list' }
)
}
ensureCloudInit()
const { result } = await wx.cloud.callFunction({
name: 'todolist-skill-handler',
data: { action: 'deleteTodo', todoId }
})
if (result && result.code === 0 && result.data) {
return successResult(
`已删除待办。请展示最新待办列表卡片。`,
result.data,
{ mode: 'list' }
)
}
return errorResult('删除待办失败,请稍后重试。')
}
module.exports = deleteTodo
const { isPreviewMode, ensureCloudInit, successResult, errorResult, queryTodosLocal } = require('../utils/util')
async function getTodoList() {
console.info('[ai-mode] getTodoList 入口')
if (isPreviewMode()) {
const data = queryTodosLocal()
return successResult(
`已查询到 ${data.total} 条待办。请展示待办列表卡片,并允许用户在卡片中切换完成状态或删除。禁止以纯文本逐条展开列表。`,
data,
{ mode: 'list' }
)
}
ensureCloudInit()
const { result } = await wx.cloud.callFunction({
name: 'todolist-skill-handler',
data: { action: 'getTodoList' }
})
if (result && result.code === 0 && result.data) {
return successResult(
`已查询到 ${result.data.total} 条待办。请展示待办列表卡片,并允许用户在卡片中切换完成状态或删除。禁止以纯文本逐条展开列表。`,
result.data,
{ mode: 'list' }
)
}
return errorResult('查询待办失败,请稍后重试。')
}
module.exports = getTodoList
const { isPreviewMode, ensureCloudInit, successResult, errorResult, toggleTodoLocal } = require('../utils/util')
async function toggleTodo(params = {}) {
console.info('[ai-mode] toggleTodo 入口, params=', JSON.stringify(params))
const todoId = params && params.todoId
if (!todoId) {
return errorResult('缺少 todoId。禁止直接切换待办状态,请先让用户从列表中选择具体待办。')
}
if (isPreviewMode()) {
const local = toggleTodoLocal(todoId)
if (!local) {
return errorResult('未找到该待办。禁止继续修改不存在的待办,请先重新查看列表。')
}
return successResult(
`已更新待办「${local.todo.title}」的完成状态。请展示最新待办列表卡片。`,
local.data,
{ mode: 'list' }
)
}
ensureCloudInit()
const { result } = await wx.cloud.callFunction({
name: 'todolist-skill-handler',
data: { action: 'toggleTodo', todoId }
})
if (result && result.code === 0 && result.data) {
return successResult(
`已更新待办完成状态。请展示最新待办列表卡片。`,
result.data,
{ mode: 'list' }
)
}
return errorResult('更新待办失败,请稍后重试。')
}
module.exports = toggleTodo
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
const db = cloud.database()
const _ = db.command
async function handleGetTodoList({ openid }) {
if (!openid) {
return { code: -1, message: 'openid 不能为空', data: null }
}
try {
const res = await db.collection('todo_items')
.where({ ownerOpenid: openid })
.orderBy('createTime', 'desc')
.get()
return {
code: 0,
message: 'success',
data: {
items: res.data || []
}
}
} catch (err) {
console.error('[todolist-skill-handler] getTodoList error:', err.message)
return { code: -1, message: err.message, data: null }
}
}
async function handleAddTodo({ openid, title }) {
if (!openid) {
return { code: -1, message: 'openid 不能为空', data: null }
}
if (!title || !String(title).trim()) {
return { code: -1, message: 'title 不能为空', data: null }
}
try {
const todoId = `TD${Date.now().toString(36).toUpperCase()}${Math.random().toString(36).slice(2, 6).toUpperCase()}`
const now = new Date()
const data = {
todoId,
title: String(title).trim(),
done: false,
ownerOpenid: openid,
createTime: now.toISOString(),
updatedAt: db.serverDate()
}
await db.collection('todo_items').add({ data })
return {
code: 0,
message: 'success',
data
}
} catch (err) {
console.error('[todolist-skill-handler] addTodo error:', err.message)
return { code: -1, message: err.message, data: null }
}
}
async function handleToggleTodo({ openid, todoId }) {
if (!openid) {
return { code: -1, message: 'openid 不能为空', data: null }
}
if (!todoId) {
return { code: -1, message: 'todoId 不能为空', data: null }
}
try {
const res = await db.collection('todo_items')
.where({ todoId, ownerOpenid: openid })
.limit(1)
.get()
if (!res.data || !res.data.length) {
return { code: -1, message: 'todo_not_found', data: null }
}
const todo = res.data[0]
const newDone = !todo.done
await db.collection('todo_items')
.where({ todoId, ownerOpenid: openid })
.update({
data: {
done: newDone,
updatedAt: db.serverDate()
}
})
return {
code: 0,
message: 'success',
data: {
todoId,
done: newDone
}
}
} catch (err) {
console.error('[todolist-skill-handler] toggleTodo error:', err.message)
return { code: -1, message: err.message, data: null }
}
}
async function handleDeleteTodo({ openid, todoId }) {
if (!openid) {
return { code: -1, message: 'openid 不能为空', data: null }
}
if (!todoId) {
return { code: -1, message: 'todoId 不能为空', data: null }
}
try {
const res = await db.collection('todo_items')
.where({ todoId, ownerOpenid: openid })
.limit(1)
.get()
if (!res.data || !res.data.length) {
return { code: -1, message: 'todo_not_found', data: null }
}
await db.collection('todo_items')
.where({ todoId, ownerOpenid: openid })
.remove()
return {
code: 0,
message: 'success',
data: { todoId }
}
} catch (err) {
console.error('[todolist-skill-handler] deleteTodo error:', err.message)
return { code: -1, message: err.message, data: null }
}
}
exports.main = async (event) => {
const { action } = event
console.log('[todolist-skill-handler] action=', action, 'event=', JSON.stringify(event))
switch (action) {
case 'getTodoList':
return handleGetTodoList(event)
case 'addTodo':
return handleAddTodo(event)
case 'toggleTodo':
return handleToggleTodo(event)
case 'deleteTodo':
return handleDeleteTodo(event)
default:
return {
code: -1,
message: `未知 action: ${action}`,
data: null
}
}
}
{
"name": "todolist-skill-handler",
"version": "1.0.0",
"description": "todolist-skill 云函数",
"main": "index.js",
"dependencies": {
"wx-server-sdk": "latest"
}
}
Component({
data: {
items: []
},
lifetimes: {
created() {
console.info('[ai-mode] todo-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] todo-list-card 收到 Result:', JSON.stringify(sc))
const items = (sc.items || []).map((item = {}) => {
const displayTitle = String(item.title || '').trim() || '未命名待办'
const displayUpdatedText = String(item.updatedText || '').trim() || (item.done ? '已完成' : '待处理')
return {
...item,
displayTitle,
displayUpdatedText
}
})
this.setData({ items })
})
const viewCtx = wx.modelContext.getViewContext(this)
try {
const { width, minHeight, maxHeight } = viewCtx.getDimensions()
console.info(`[ai-mode] todo-list-card dimensions width=${width} minHeight=${minHeight} maxHeight=${maxHeight}`)
} catch (e) {
console.info('[ai-mode] todo-list-card getDimensions skipped:', e.message)
}
viewCtx.on(NotificationType.Overflow, (data) => {
const overflowed = !!(data && data.overflowHeight > 0)
console.info(`[ai-mode] todo-list-card overflow overflowed=${overflowed} data=${JSON.stringify(data)}`)
})
console.info('[ai-mode] todo-list-card overflow monitor=on')
}
},
methods: {
onTapAdd() {
console.info('[ai-mode] todo-list-card send follow up for add todo')
wx.modelContext.getContext(this).sendFollowUpMessage({
content: [
{ type: 'text', text: '我想新增一个待办,请先问我要记录什么事项。' }
]
})
},
onTapToggle(e) {
const { todoId, title } = e.currentTarget.dataset
console.info(`[ai-mode] todo-list-card send api/call name=toggleTodo args=${JSON.stringify({ todoId })}`)
wx.modelContext.getContext(this).sendFollowUpMessage({
content: [
{ type: 'text', text: `切换${title}` },
{ type: 'api/call', data: { name: 'toggleTodo', arguments: { todoId } } }
]
})
},
onTapDelete(e) {
const { todoId, title } = e.currentTarget.dataset
console.info(`[ai-mode] todo-list-card send api/call name=deleteTodo args=${JSON.stringify({ todoId })}`)
wx.modelContext.getContext(this).sendFollowUpMessage({
content: [
{ type: 'text', text: `删除${title}` },
{ type: 'api/call', data: { name: 'deleteTodo', arguments: { todoId } } }
]
})
}
}
})
{
"component": true,
"usingComponents": {}
}
<view class="td-card">
<view class="td-head">
<view class="td-head-main">
<view class="td-title">我的待办</view>
<view class="td-count">{{items.length}} 项</view>
</view>
<view class="td-add" hover-class="td-add-hover" bind:tap="onTapAdd">+</view>
</view>
<view wx:if="{{!items.length}}" class="td-empty">
<view class="td-empty-title">当前还没有待办</view>
<view class="td-empty-desc">点右上角 + 新增,或直接对 AI 说“新增一个待办:明天交周报”</view>
</view>
<block wx:for="{{items}}" wx:key="todoId">
<view class="td-item {{item.done ? 'is-done' : ''}}">
<view
class="td-check {{item.done ? 'is-done' : ''}}"
hover-class="td-check-hover"
bind:tap="onTapToggle"
data-todo-id="{{item.todoId}}"
data-title="{{item.displayTitle}}"
>
<view wx:if="{{item.done}}" class="td-check-inner">✓</view>
</view>
<view class="td-main">
<view class="td-item-title {{item.done ? 'is-done' : ''}}">{{item.displayTitle}}</view>
<view class="td-item-meta">{{item.displayUpdatedText}}</view>
</view>
<view
class="td-delete"
hover-class="td-delete-hover"
bind:tap="onTapDelete"
data-todo-id="{{item.todoId}}"
data-title="{{item.displayTitle}}"
>×</view>
</view>
</block>
</view>
/* ratio=1:1;现代蓝紫中性色系 */
.td-card {
background: #FFFFFF;
border: 1px solid #E2E8F0;
border-radius: 1.6vw;
padding: 3.2vw;
box-sizing: border-box;
overflow: hidden;
box-shadow: 0 10rpx 36rpx rgba(15, 23, 42, 0.06);
}
.td-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 2.13vw;
}
.td-head-main {
display: flex;
align-items: baseline;
gap: 1.6vw;
min-width: 0;
}
.td-title {
font-size: 4.53vw;
font-weight: 600;
color: #0F172A;
}
.td-count {
font-size: 3.2vw;
color: #64748B;
}
.td-add {
flex-shrink: 0;
width: 8.8vw;
height: 8.8vw;
border-radius: 999rpx;
background: #EEF2FF;
color: #4338CA;
font-size: 5.33vw;
font-weight: 500;
line-height: 8.4vw;
text-align: center;
}
.td-add-hover {
opacity: 0.88;
}
.td-empty {
margin-top: 3.2vw;
padding: 3.2vw;
border: 1px solid #CBD5E1;
border-radius: 1.6vw;
background: #F8FAFC;
}
.td-empty-title {
font-size: 4vw;
color: #0F172A;
}
.td-empty-desc {
margin-top: 1.33vw;
font-size: 3.2vw;
color: #64748B;
line-height: 1.5;
}
.td-item {
margin-top: 3.2vw;
display: flex;
align-items: center;
gap: 2.4vw;
padding: 3.2vw;
background: linear-gradient(180deg, #F8FAFC 0%, #FFFFFF 100%);
border: 1px solid #E2E8F0;
border-radius: 1.6vw;
}
.td-item.is-done {
opacity: 0.72;
}
.td-check {
flex-shrink: 0;
width: 5.87vw;
height: 5.87vw;
border: 1px solid #C7D2FE;
border-radius: 999rpx;
background: #FFFFFF;
display: flex;
align-items: center;
justify-content: center;
}
.td-check.is-done {
background: #4F46E5;
border-color: #4F46E5;
}
.td-check-inner {
color: #FFFFFF;
font-size: 3.2vw;
line-height: 1;
}
.td-check-hover,
.td-delete-hover {
opacity: 0.88;
}
.td-main {
flex: 1;
min-width: 0;
}
.td-item-title {
font-size: 4vw;
font-weight: 600;
color: #0F172A;
line-height: 1.4;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.td-item-title.is-done {
color: #64748B;
opacity: 0.6;
}
.td-item-meta {
margin-top: 1.33vw;
font-size: 3.2vw;
color: #64748B;
}
.td-delete {
flex-shrink: 0;
width: 8vw;
height: 8vw;
border-radius: 999rpx;
background: #FEE2E2;
color: #DC2626;
text-align: center;
line-height: 7.6vw;
font-size: 5.33vw;
font-weight: 400;
}
@media (prefers-color-scheme: dark) {
.td-card {
background: #0F172A;
border-color: #1E293B;
box-shadow: none;
}
.td-title, .td-item-title { color: #F8FAFC; }
.td-count, .td-empty-desc, .td-item-meta, .td-item-title.is-done { color: #94A3B8; }
.td-empty, .td-item {
background: #111827;
border-color: #334155;
}
.td-add {
color: #C7D2FE;
background: #1E293B;
}
.td-check {
background: #0F172A;
border-color: #475569;
}
.td-check.is-done {
background: #6366F1;
border-color: #6366F1;
}
.td-delete {
color: #FDA4AF;
background: #3B1D24;
}
}
{
"collections": [
{
"name": "todo_items",
"description": "待办事项集合,存储用户待办记录",
"indexes": [
{ "name": "idx_ownerOpenid", "field": "ownerOpenid" }
]
}
]
}
const getTodoList = require('./apis/getTodoList.js')
const addTodo = require('./apis/addTodo.js')
const toggleTodo = require('./apis/toggleTodo.js')
const deleteTodo = require('./apis/deleteTodo.js')
function registerAPIs() {
const skill = wx.modelContext.createSkill('skills/todolist-skill')
skill.use(async (ctx, next) => {
try {
console.info('[ai-mode] [todolist-skill] middleware start name=', ctx.name)
await next()
console.info('[ai-mode] [todolist-skill] middleware finish name=', ctx.name)
} catch (err) {
console.error('[ai-mode] [todolist-skill] middleware error:', err.message)
throw err
}
})
skill.registerAPI('getTodoList', getTodoList)
skill.registerAPI('addTodo', addTodo)
skill.registerAPI('toggleTodo', toggleTodo)
skill.registerAPI('deleteTodo', deleteTodo)
console.info('[ai-mode] [todolist-skill] APIs registered via createSkill')
}
registerAPIs()
{
"apis": [
{
"name": "getTodoList",
"description": "查询当前用户的待办列表(业务对象:待办列表卡片)。调用前置条件:用户要查看全部待办、未完成待办或刚完成一次新增/修改操作后需要看到最新列表时。直接使用 wx.cloud.database 查询 todo_items 集合,不经过云函数。",
"_meta": {
"ui": {
"componentPath": "components/todo-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",
"items": {
"type": "object",
"properties": {
"todoId": { "type": "string" },
"title": { "type": "string" },
"done": { "type": "boolean" },
"updatedText": { "type": "string" }
},
"required": ["todoId", "title", "done", "updatedText"],
"additionalProperties": false
}
},
"total": { "type": "number" }
},
"required": ["items", "total"],
"additionalProperties": false
}
},
{
"name": "addTodo",
"description": "新增一条待办事项(业务对象:待办列表卡片)。调用前置条件:用户明确给出了待办标题,例如『新增一个待办:明天交周报』。直接使用 wx.cloud.database 向 todo_items 集合写入一条记录,成功后返回最新列表。",
"_meta": {
"ui": {
"componentPath": "components/todo-list-card/index"
}
},
"inputSchema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "待办标题。取值来源:用户原话中的待办内容。【禁止编造】用户未明确说出待办内容时,禁止填写本字段,应先反问用户要新增什么。"
}
},
"required": ["title"],
"additionalProperties": false
},
"outputSchema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"todoId": { "type": "string" },
"title": { "type": "string" },
"done": { "type": "boolean" },
"updatedText": { "type": "string" }
},
"required": ["todoId", "title", "done", "updatedText"],
"additionalProperties": false
}
},
"total": { "type": "number" }
},
"required": ["items", "total"],
"additionalProperties": false
}
},
{
"name": "toggleTodo",
"description": "切换某条待办的完成状态(业务对象:待办列表卡片)。调用前置条件:已从待办列表中拿到具体 todoId。直接使用 wx.cloud.database 更新 todo_items 集合,成功后返回最新列表。【严禁场景】禁止在无 todoId 时调用,禁止从用户自然语言推断 todoId。",
"_meta": {
"ui": {
"componentPath": "components/todo-list-card/index"
}
},
"inputSchema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"todoId": {
"type": "string",
"description": "待办唯一标识,必须来自上游 getTodoList / addTodo 返回的 items[].todoId 原值。【禁止编造】上下文中无 todoId 时,应先展示待办列表。"
}
},
"required": ["todoId"],
"additionalProperties": false
},
"outputSchema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"todoId": { "type": "string" },
"title": { "type": "string" },
"done": { "type": "boolean" },
"updatedText": { "type": "string" }
},
"required": ["todoId", "title", "done", "updatedText"],
"additionalProperties": false
}
},
"total": { "type": "number" }
},
"required": ["items", "total"],
"additionalProperties": false
}
},
{
"name": "deleteTodo",
"description": "删除一条待办事项(业务对象:待办列表卡片)。调用前置条件:已从待办列表中拿到具体 todoId。直接使用 wx.cloud.database 删除 todo_items 集合中的对应记录,成功后返回最新列表。【严禁场景】禁止在无 todoId 时调用。",
"_meta": {
"ui": {
"componentPath": "components/todo-list-card/index"
}
},
"inputSchema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"todoId": {
"type": "string",
"description": "待办唯一标识,必须来自上游待办列表的 todoId 原值。【禁止编造】上下文中无 todoId 时,应先展示待办列表。"
}
},
"required": ["todoId"],
"additionalProperties": false
},
"outputSchema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"todoId": { "type": "string" },
"title": { "type": "string" },
"done": { "type": "boolean" },
"updatedText": { "type": "string" }
},
"required": ["todoId", "title", "done", "updatedText"],
"additionalProperties": false
}
},
"total": { "type": "number" }
},
"required": ["items", "total"],
"additionalProperties": false
}
}
],
"components": [
{
"path": "components/todo-list-card/index",
"relatedPage": "/pages/home/home"
}
]
}
todolist-skill
简单待办,支持查看待办列表、新增、切换完成状态及删除待办事项。
功能
- 查看当前用户待办列表
- 新增一条待办事项
- 切换待办完成状态
- 删除待办事项
用户输入示例
- "看看我的待办"
- "新增一个待办"
- "明天交周报"
- "完成这个任务"
- "删除这条待办"
- "还有哪些没做完"
原子接口
| 接口名 | 说明 |
|---|---|
getTodoList | 查询当前用户的待办列表 |
addTodo | 新增一条待办事项 |
toggleTodo | 切换某条待办完成状态 |
deleteTodo | 删除一条待办事项 |
原子组件
| 组件路径 | 说明 |
|---|---|
components/todo-list-card/index | 待办列表展示(含新增/切换/删除交互) |
后端依赖
| 资源 | 名称 |
|---|---|
| 云函数 | todolist-skill-handler |
| 数据库集合 | todo_items |
const PREVIEW_MODE_KEY = 'mp_skills_preview_mode'
const TODOS_STORAGE_KEY = 'mp_skills_todos'
function isPreviewMode() {
return wx.getStorageSync(PREVIEW_MODE_KEY) !== false
}
const CLOUD_ENV_ID = 'cloud1-5g39elugeec5ba0f'
const COLLECTION = 'todo_items'
let _cloudInited = false
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 getCurrentOpenid() {
const userInfo = wx.getStorageSync('userInfo') || {}
return userInfo.openid || 'demo_user'
}
function mapTodo(doc) {
return {
todoId: doc.todoId || doc._id,
title: doc.title,
done: !!doc.done,
updatedText: formatDate(doc.updatedAt || doc.createTime || new Date())
}
}
function formatDate(value) {
const date = value instanceof Date ? value : new Date(value)
if (Number.isNaN(date.getTime())) return '刚刚更新'
const pad = (n) => String(n).padStart(2, '0')
return `${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`
}
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
}
// --- 预览模式:local storage mock ---
function getLocalTodos() {
try {
return wx.getStorageSync(TODOS_STORAGE_KEY) || []
} catch (_) {
return []
}
}
function saveLocalTodos(todos) {
wx.setStorageSync(TODOS_STORAGE_KEY, todos)
}
function queryTodosLocal() {
const items = getLocalTodos().map(mapTodo)
return { items, total: items.length }
}
function addTodoLocal(title) {
const todos = getLocalTodos()
const newTodo = {
_id: `todo_${Date.now()}`,
todoId: `todo_${Date.now()}`,
title,
done: false,
createTime: new Date().toISOString(),
updatedAt: new Date().toISOString()
}
todos.unshift(newTodo)
saveLocalTodos(todos)
return queryTodosLocal()
}
function toggleTodoLocal(todoId) {
const todos = getLocalTodos()
const todo = todos.find(t => (t.todoId || t._id) === todoId)
if (!todo) return null
todo.done = !todo.done
todo.updatedAt = new Date().toISOString()
saveLocalTodos(todos)
return { todo, data: queryTodosLocal() }
}
function deleteTodoLocal(todoId) {
const todos = getLocalTodos()
const idx = todos.findIndex(t => (t.todoId || t._id) === todoId)
if (idx === -1) return null
const deleted = todos[idx]
todos.splice(idx, 1)
saveLocalTodos(todos)
return { deleted, data: queryTodosLocal() }
}
module.exports = {
PREVIEW_MODE_KEY,
isPreviewMode,
CLOUD_ENV_ID,
COLLECTION,
TODOS_STORAGE_KEY,
ensureCloudInit,
getCurrentOpenid,
mapTodo,
errorResult,
successResult,
getLocalTodos,
saveLocalTodos,
queryTodosLocal,
addTodoLocal,
toggleTodoLocal,
deleteTodoLocal
}