diff --git a/pages/garden/add/index.js b/pages/garden/add/index.js index 8862779..6206f5d 100644 --- a/pages/garden/add/index.js +++ b/pages/garden/add/index.js @@ -1,6 +1,7 @@ // pages/garden/add/index.js import { MOCK_PLANTS, CARE_TASK_ICONS } from '../../../utils/mockData'; import request from '../../../utils/request'; +import { requestSubscription, checkSubscriptionSettings } from '../../../utils/subscribe'; Page({ data: { @@ -236,7 +237,6 @@ Page({ placement: newPlantLocation || '', ossIds: [uploadedImageId], carePlans: carePlans, - // Default fields as not in UI yet potMaterial: '', potSize: '', sunlight: '', @@ -245,18 +245,40 @@ Page({ // Submit wx.showLoading({ title: 'Creating...' }); - request.post('/plant/add', payload).then(res => { + request.post('/plant/add', payload).then(async () => { wx.hideLoading(); wx.showToast({ title: '添加成功', icon: 'success' }); - // Refresh previous page (e.g. garden list) if needed - // const pages = getCurrentPages(); - // const prevPage = pages[pages.length - 2]; - // if (prevPage && prevPage.onRefresh) prevPage.onRefresh(); + // Smart Subscription Check + const subStatus = await checkSubscriptionSettings(); + + if (subStatus === 'accept') { + // Already authorized 'Always', just call API to consume quota + requestSubscription().finally(() => { + wx.navigateBack(); + }); + } else if (subStatus === 'reject' || subStatus === 'ban') { + // User rejected 'Always', do not disturb + setTimeout(() => wx.navigateBack(), 500); + } else { + // Not set, ask user + wx.showModal({ + title: '添加成功', + content: '是否订阅植物养护提醒?', + confirmText: '订阅', + cancelText: '暂不', + success: (res) => { + if (res.confirm) { + requestSubscription().finally(() => { + wx.navigateBack(); + }); + } else { + wx.navigateBack(); + } + } + }); + } - setTimeout(() => { - wx.navigateBack(); - }, 1000); }).catch(err => { wx.hideLoading(); console.error('Add plant failed', err); diff --git a/pages/tasks/index.js b/pages/tasks/index.js index 7bf61ff..ff2e6d5 100644 --- a/pages/tasks/index.js +++ b/pages/tasks/index.js @@ -203,6 +203,7 @@ Page({ // Sync with backend this.fetchTodayTasks(); + }).catch(err => { wx.hideLoading(); console.error('Complete task failed', err); diff --git a/pages/wiki/detail/index.js b/pages/wiki/detail/index.js index 5e2a157..06131ad 100644 --- a/pages/wiki/detail/index.js +++ b/pages/wiki/detail/index.js @@ -1,5 +1,5 @@ // pages/wiki/detail/index.js -import { MOCK_WIKI } from '../../../utils/mockData'; +import request from '../../../utils/request'; Page({ data: { @@ -15,38 +15,105 @@ Page({ }, loadPlantDetail(id) { - // Find plant in MOCK_WIKI - const plant = MOCK_WIKI.find(p => p.id === id); - if (plant) { + // Fetch detail via wiki/page with specific ID + // Since there's no /wiki/detail endpoint, we use /wiki/page to get it + request.post('/wiki/page', { + current: 1, + pageSize: 1, + id: id + }).then(res => { + const data = res || {}; + const list = data.list || []; + const item = list.length > 0 ? list[0] : null; + + if (!item) { + wx.showToast({ title: '未找到该植物', icon: 'none' }); + return; + } + // Set Page Title - wx.setNavigationBarTitle({ - title: plant.name - }); + wx.setNavigationBarTitle({ title: item.name }); - // Prepare swiper list - const swiperList = (plant.images || []).map(img => { - return (img.indexOf('http') === 0 || img.indexOf('wxfile') === 0) ? img : `/assets/${img}`; - }); + // Prepare swiper list from imgList + const swiperList = (item.imgList || []).map(img => img.url); - // Ensure some default values if missing - const enrichedPlant = { - ...plant, - light: plant.light || { level: '中等', description: '适合明亮的散射光' }, - water: plant.water || { frequency: '每周1-2次', description: '保持土壤微润' }, - temperature: plant.temperature || '15-28℃', - humidity: plant.humidity || '50%-70%', - soil: plant.soil || '疏松透气的营养土', - fertilizer: plant.fertilizer || '生长季每月施一次薄肥', - toxicity: plant.toxicity || '无毒', - commonPests: plant.commonPests || ['红蜘蛛'], - careTips: plant.careTips || ['注意通风', '定期清洁叶面'] + // Parse pest/disease list + const commonPests = item.pestsDiseases + ? item.pestsDiseases.split(',').map(s => s.trim()).filter(Boolean) + : []; + + // Parse aliases + const aliasesList = item.aliases + ? item.aliases.split(/[,,、]/).map(s => s.trim()).filter(Boolean) + : []; + + // Parse reproduction methods + const reproductionList = item.reproductionMethod + ? item.reproductionMethod.split(/[,,、]/).map(s => s.trim()).filter(Boolean) + : []; + + // Difficulty label + const diffLabels = { 1: '简单', 2: '中等', 3: '较难', 4: '困难', 5: '专家' }; + + // Map API data to display model + const plant = { + id: item.id, + name: item.name, + latinName: item.latinName || '', + aliases: item.aliases || '', + aliasesList: aliasesList, + genus: item.genus || '', + distributionArea: item.distributionArea || '', + difficulty: item.difficulty || 0, + difficultyLabel: diffLabels[item.difficulty] || '未知', + isHot: item.isHot === 1, + lifeCycle: item.lifeCycle || '', + growthHabit: item.growthHabit || '', + reproductionMethod: item.reproductionMethod || '', + reproductionList: reproductionList, + + // Light + lightIntensity: item.lightIntensity || '', + lightType: item.lightType || '', + + // Temperature + optimalTempPeriod: item.optimalTempPeriod || '', + + // Morphology + stem: item.stem || '', + foliageType: item.foliageType || '', + foliageColor: item.foliageColor || '', + foliageShape: item.foliageShape || '', + height: item.height || 0, + + // Flowering + floweringPeriod: item.floweringPeriod || '', + floweringColor: item.floweringColor || '', + floweringShape: item.floweringShape || '', + flowerDiameter: item.flowerDiameter || 0, + + // Fruit + fruit: item.fruit || '', + + // Pests + pestsDiseases: item.pestsDiseases || '', + commonPests: commonPests, + + // Classes + classes: (item.classes || []).map(c => c.name), + + // Images + imgList: item.imgList || [] }; this.setData({ - plant: enrichedPlant, + plant: plant, swiperList: swiperList }); - } + }).catch(err => { + console.error('Load plant detail failed', err); + wx.showToast({ title: '加载失败', icon: 'none' }); + }); }, onSwiperChange(e) { diff --git a/pages/wiki/detail/index.wxml b/pages/wiki/detail/index.wxml index 20b2848..d34a608 100644 --- a/pages/wiki/detail/index.wxml +++ b/pages/wiki/detail/index.wxml @@ -1,5 +1,5 @@ - + @@ -11,7 +11,6 @@ list="{{swiperList}}" navigation="{{ { type: '' } }}" /> - @@ -28,12 +27,12 @@ {{plant.name}} - {{plant.scientificName}} + {{plant.latinName}} - {{plant.family}} - {{plant.category}} - 难度: {{plant.difficulty || 'Easy'}} + {{plant.genus}} + {{item}} + 难度: {{plant.difficultyLabel}} @@ -41,10 +40,11 @@ - -
+ + +
- {{plant.description}} + {{plant.growthHabit}}
@@ -56,13 +56,21 @@ - - 原产地 - {{plant.origin}} + + 分布区域 + {{plant.distributionArea}} - - 毒性 - {{plant.toxicity}} + + 别名 + {{plant.aliases}} + + + 生命周期 + {{plant.lifeCycle === 'perennial' ? '多年生' : (plant.lifeCycle === 'annual' ? '一年生' : plant.lifeCycle)}} + + + 株高 + 约 {{plant.height}} cm @@ -71,79 +79,121 @@
- + 养护指南 - + - 光照 ({{plant.light.level}}) - {{plant.light.description}} + 光照 + {{plant.lightIntensity}} - - + + - + - 水分 ({{plant.water.frequency}}) - {{plant.water.description}} + 适宜温度 + {{plant.optimalTempPeriod}} - - + + - + - 环境 - 温度: {{plant.temperature}} - 湿度: {{plant.humidity}} - - - - - - - - - - 土壤与肥料 - 土: {{plant.soil}} - 肥: {{plant.fertilizer}} + 繁殖方式 + {{plant.reproductionMethod}}
- -
+ +
- - 常见问题 + + 形态特征 - - 易发病虫害 - - {{item}} + + + + {{plant.stem}} + + + 叶形 + {{plant.foliageShape}} + + + 叶色 + {{plant.foliageColor}} + + + 叶质 + {{plant.foliageType}} - - 专家提示 - - - - {{item}} - + +
+ + +
+ + + 开花信息 + + + + + 花期 + {{plant.floweringPeriod}} + + 花色 + {{plant.floweringColor}} + + + 花型 + {{plant.floweringShape}} + + + 花径 + 约 {{plant.flowerDiameter}} mm + + + +
+ + +
+ + + 果实 + + + {{plant.fruit}} + +
+ + +
+ + + 常见病虫害 + + + + {{item}}
@@ -153,3 +203,8 @@ + + + + + diff --git a/pages/wiki/detail/index.wxss b/pages/wiki/detail/index.wxss index 7689e71..d6caf80 100644 --- a/pages/wiki/detail/index.wxss +++ b/pages/wiki/detail/index.wxss @@ -313,3 +313,11 @@ t-swiper { color: #4B5563; line-height: 1.5; } + +.wiki-detail-loading { + height: 100vh; + display: flex; + align-items: center; + justify-content: center; + background: #F9FAFB; +} diff --git a/pages/wiki/index.js b/pages/wiki/index.js index 60f065a..3293fe1 100644 --- a/pages/wiki/index.js +++ b/pages/wiki/index.js @@ -1,21 +1,21 @@ // pages/wiki/index.js -import { MOCK_WIKI } from '../../utils/mockData'; +import request from '../../utils/request'; Page({ data: { - // Data Source (effectively the backend result) - filteredSourceList: [], + // Categories + categories: [], + activeCategory: 'all', // 'all' or category id - // Display Data (rendered list) + // Display Data displayedList: [], // Filter State searchQuery: '', - activeCategory: '全部', // Pagination State - page: 1, - pageSize: 5, + current: 1, + pageSize: 10, isLoading: false, hasMore: true, @@ -24,8 +24,8 @@ Page({ }, onLoad() { - // Initial Load - this.filterList(); + this.fetchCategories(); + this.fetchWikiList(true); }, onShow() { @@ -35,98 +35,116 @@ Page({ } }, - // Search Input Handler - onSearchInput(e) { - this.setData({ searchQuery: e.detail.value }, () => { - this.filterList(); + // Fetch categories from API + fetchCategories() { + request.get('/wiki-class/list').then(res => { + const list = (res && res.list) || (Array.isArray(res) ? res : []); + this.setData({ categories: list }); + }).catch(err => { + console.error('Fetch categories failed', err); }); }, + // Fetch wiki list from API + fetchWikiList(reset = false) { + if (this.data.isLoading) return; + if (!reset && !this.data.hasMore) return; + + const current = reset ? 1 : this.data.current; + + this.setData({ isLoading: true }); + + // Build params + const params = { + current: current, + pageSize: this.data.pageSize + }; + + // Search query + if (this.data.searchQuery) { + params.name = this.data.searchQuery; + } + + // Category filter + if (this.data.activeCategory !== 'all') { + params.classId = [this.data.activeCategory]; + } + + request.post('/wiki/page', params).then(res => { + const data = res || {}; + const list = data.list || []; + const total = data.total || 0; + + // Map API data to display model + const mappedList = list.map(item => ({ + id: item.id, + name: item.name, + latinName: item.latinName || '', + aliases: item.aliases || '', + genus: item.genus || '', + difficulty: item.difficulty || 0, + isHot: item.isHot === 1, + image: (item.imgList && item.imgList.length > 0) ? item.imgList[0].url : '', + classes: (item.classes || []).map(c => c.name), + // Pass the full item for detail navigation + raw: item + })); + + if (reset) { + this.setData({ + displayedList: mappedList, + current: 2, + hasMore: mappedList.length < total, + isLoading: false + }); + } else { + // Append using data path for performance + const updateData = {}; + const currentLen = this.data.displayedList.length; + mappedList.forEach((item, index) => { + updateData[`displayedList[${currentLen + index}]`] = item; + }); + updateData['current'] = current + 1; + updateData['hasMore'] = (currentLen + mappedList.length) < total; + updateData['isLoading'] = false; + this.setData(updateData); + } + }).catch(err => { + console.error('Fetch wiki list failed', err); + this.setData({ isLoading: false }); + }); + }, + + // Search Input Handler (debounced) + onSearchInput(e) { + const value = e.detail.value; + this.setData({ searchQuery: value }); + + // Debounce search + if (this._searchTimer) clearTimeout(this._searchTimer); + this._searchTimer = setTimeout(() => { + this.fetchWikiList(true); + }, 500); + }, + // Category Filter Handler setCategory(e) { - this.setData({ activeCategory: e.currentTarget.dataset.cat }, () => { - this.filterList(); + const catId = e.currentTarget.dataset.cat; + this.setData({ activeCategory: catId }, () => { + this.fetchWikiList(true); }); }, - /** - * Simulates "Backend" Search & Filtering - * Resets pagination and prepares the filtered data source. - */ - filterList() { - const { searchQuery, activeCategory } = this.data; - let result = MOCK_WIKI; - - // Filter by Search Query - if (searchQuery) { - const q = searchQuery.toLowerCase(); - result = result.filter(item => - item.name.toLowerCase().includes(q) || - item.scientificName.toLowerCase().includes(q) - ); - } - - // Filter by Category - if (activeCategory !== '全部') { - result = result.filter(item => item.category.includes(activeCategory)); - } - - this.setData({ - filteredSourceList: result, - displayedList: [], - page: 1, - hasMore: true - }, () => { - this.loadMoreData(); - }); - }, - - /** - * Simulates "Backend" Pagination - * Appends the next page of data from filteredSourceList to displayedList. - * key-value data path update is used for performance optimization. - */ - loadMoreData() { - const { isLoading, hasMore, page, pageSize, filteredSourceList, displayedList } = this.data; - - if (isLoading || !hasMore) return; - - this.setData({ isLoading: true }); - - // Simulate Network Delay - setTimeout(() => { - const startIndex = (page - 1) * pageSize; - const endIndex = startIndex + pageSize; - const newItems = filteredSourceList.slice(startIndex, endIndex); - - const isLastPage = endIndex >= filteredSourceList.length; - - if (newItems.length > 0) { - // Performance Optimization: Use data path to append items - // Instead of setData({ displayedList: [...old, ...new] }) - const updateData = {}; - const currentLen = displayedList.length; - newItems.forEach((item, index) => { - updateData[`displayedList[${currentLen + index}]`] = item; - }); - - updateData['page'] = page + 1; - updateData['hasMore'] = !isLastPage; - updateData['isLoading'] = false; - - this.setData(updateData); - } else { - this.setData({ - hasMore: false, - isLoading: false - }); - } - }, 500); - }, - // Infinite Scroll Handler onReachBottom() { - this.loadMoreData(); + this.fetchWikiList(false); + }, + + // Pull down refresh + onPullDownRefresh() { + this.fetchCategories(); + this.fetchWikiList(true); + wx.stopPullDownRefresh(); }, goToDetail(e) { @@ -136,6 +154,12 @@ Page({ }); }, + // Difficulty label helper + getDifficultyLabel(level) { + const labels = { 1: '简单', 2: '中等', 3: '较难', 4: '困难', 5: '专家' }; + return labels[level] || '未知'; + }, + openIdentifyModal() { this.setData({ showIdentifyModal: true }); }, onPopupVisibleChange(e) { diff --git a/pages/wiki/index.wxml b/pages/wiki/index.wxml index ed437f1..65a660c 100644 --- a/pages/wiki/index.wxml +++ b/pages/wiki/index.wxml @@ -1,4 +1,13 @@ + + var diffLabels = { '1': '简单', '2': '中等', '3': '较难', '4': '困难', '5': '专家' }; + module.exports = { + getDifficulty: function(level) { + return diffLabels['' + level] || '未知'; + } + }; + + + - {{item}} + 全部 + + + {{item.name}} - + + + + {{item.name}} - {{item.scientificName}} + {{item.latinName}} - {{tag}} + {{cls}} + + + {{tools.getDifficulty(item.difficulty)}} + + + 热门 diff --git a/pages/wiki/index.wxss b/pages/wiki/index.wxss index cebe3cb..aa74680 100644 --- a/pages/wiki/index.wxss +++ b/pages/wiki/index.wxss @@ -60,7 +60,14 @@ flex-shrink: 0; background: #f0f0f0; } -.wiki-image t-image { width: 100%; height: 100%; display: block; } +.wiki-image image { width: 100%; height: 100%; display: block; } +.wiki-image-placeholder { + width: 100%; height: 100%; + display: flex; + align-items: center; + justify-content: center; + background: #f5f5f5; +} .wiki-info { flex: 1; min-width: 0; } diff --git a/project.config.json b/project.config.json index c7de474..42ca7fd 100644 --- a/project.config.json +++ b/project.config.json @@ -53,7 +53,7 @@ }, "compileType": "miniprogram", "libVersion": "3.7.1", - "appid": "wxb463820bf36dd5d6", + "appid": "wx52dfc635739a9c19", "projectname": "plant-mp", "isGameTourist": false, "condition": { diff --git a/utils/request.js b/utils/request.js index 2f6ea9c..1af19ea 100644 --- a/utils/request.js +++ b/utils/request.js @@ -177,7 +177,8 @@ class WxRequest { // Initialize with default instance const request = new WxRequest({ - baseUrl: 'http://192.168.0.184:8888', + baseUrl: 'http://192.168.0.184:8889', + //baseUrl: 'https://prod.sundynix.cn/plant', header: { 'Content-Type': 'application/json' } diff --git a/utils/subscribe.js b/utils/subscribe.js new file mode 100644 index 0000000..0fda9e9 --- /dev/null +++ b/utils/subscribe.js @@ -0,0 +1,53 @@ +/** + * Request WeChat Mini Program Subscription Message + * Template ID: R7fh3NDpuV8DYqI83HpEQvC8mLJy5xMWFl1qeGN9JIo + */ +const TEMPLATE_ID = 'R7fh3NDpuV8DYqI83HpEQvC8mLJy5xMWFl1qeGN9JIo'; + +export const requestSubscription = () => { + return new Promise((resolve) => { + // Check if subscription capability is available (basic check) + if (!wx.requestSubscribeMessage) { + console.warn('Current version does not support subscribe message'); + resolve({ success: false, errMsg: 'Not supported' }); + return; + } + + wx.requestSubscribeMessage({ + tmplIds: [TEMPLATE_ID], + success(res) { + if (res[TEMPLATE_ID] === 'accept') { + console.log('Subscription accepted'); + resolve({ success: true, status: 'accept' }); + } else { + console.log('Subscription rejected or other status', res[TEMPLATE_ID]); + resolve({ success: false, status: res[TEMPLATE_ID] }); + } + }, + fail(err) { + console.error('Subscription failed', err); + resolve({ success: false, errMsg: err.errMsg }); + } + }); + }); +}; + +export const checkSubscriptionSettings = () => { + return new Promise((resolve) => { + if (!wx.getSetting) { + resolve(undefined); + return; + } + + wx.getSetting({ + withSubscriptions: true, + success(res) { + const itemSettings = (res.subscriptionsSetting && res.subscriptionsSetting.itemSettings) || {}; + resolve(itemSettings[TEMPLATE_ID]); + }, + fail() { + resolve(undefined); + } + }); + }); +};