feat: login rest

This commit is contained in:
Blizzard
2026-02-12 09:26:39 +08:00
parent e97fd30fa3
commit 5553e2711a
115 changed files with 4090 additions and 3499 deletions
+48 -15
View File
@@ -2,39 +2,72 @@ import request from './utils/request';
App({ App({
onLaunch() { onLaunch() {
// Login // Initialize login process immediately
this.loginPromise = new Promise((resolve, reject) => {
this._resolveLogin = resolve;
this._rejectLogin = reject;
});
this.doLogin();
},
// Perform actual login
doLogin() {
wx.login({ wx.login({
success: res => { success: res => {
// Send res.code to backend to swap for openId, sessionKey, unionId
if (res.code) { if (res.code) {
request.get('/auth/miniLogin', { code: res.code }).then(data => { request.get('/auth/miniLogin', { code: res.code }).then(async (data) => {
// Response structure based on user input: { user: {...}, token: "...", expiresAt: ... }
// Note: request.js might return data.user directly if it unwraps 'data'
// But looking at previous request.js usage, it seems to return the 'data' field of the response.
// Let's handle both cases safely.
const token = data.token; const token = data.token;
const user = data.user;
if (token && typeof token === 'string') { if (token && typeof token === 'string') {
wx.setStorageSync('token', token); wx.setStorageSync('token', token);
if (user) { console.log('Login successful');
wx.setStorageSync('userInfo', user); if (this._resolveLogin) this._resolveLogin(token);
this.globalData.userInfo = user;
// Background Profile Update
request.get('/profile/detail').then(userDetail => {
if (userDetail) {
wx.setStorageSync('userInfo', userDetail);
this.globalData.userInfo = userDetail;
} }
console.log('Login successful, user info stored'); }).catch(e => {
console.error('Fetch profile detail failed on launch', e);
// Fallback
if (data.user) {
wx.setStorageSync('userInfo', data.user);
this.globalData.userInfo = data.user;
}
});
} else { } else {
console.warn('Login response did not contain a valid token', data); console.warn('Login response invalid', data);
if (this._rejectLogin) this._rejectLogin('No token');
} }
}).catch(err => { }).catch(err => {
console.error('Login failed', err); console.error('Login API failed', err);
if (this._rejectLogin) this._rejectLogin(err);
}); });
} else { } else {
console.error('wx.login failed: ' + res.errMsg); console.error('wx.login failed: ' + res.errMsg);
if (this._rejectLogin) this._rejectLogin(res.errMsg);
} }
},
fail: err => {
if (this._rejectLogin) this._rejectLogin(err);
} }
}); });
}, },
// Method for other pages/utils to wait for login
ensureLogin() {
// If token exists, resolve immediately
const token = wx.getStorageSync('token');
if (token) return Promise.resolve(token);
// Return existing promise or create new if failed previously?
// For simplicity, return the launch promise.
// In robust apps, handle token expiration and re-login here.
return this.loginPromise;
},
globalData: { globalData: {
userInfo: null userInfo: null
} }
+4 -1
View File
@@ -9,9 +9,12 @@
"pages/profile/index", "pages/profile/index",
"pages/plant-detail/edit/index", "pages/plant-detail/edit/index",
"pages/plant-detail/index", "pages/plant-detail/index",
"pages/plant-detail/growth-record/index",
"pages/wiki/detail/index", "pages/wiki/detail/index",
"pages/wiki/identify/index", "pages/wiki/identify/index",
"pages/profile/identify-history/index" "pages/profile/identify-history/index",
"pages/profile/badges/index",
"pages/profile/badges/level-detail/index"
], ],
"window": { "window": {
"backgroundTextStyle": "light", "backgroundTextStyle": "light",
+9
View File
@@ -1,4 +1,13 @@
/** app.wxss **/ /** app.wxss **/
@font-face {
font-family: 't';
src: url('https://tdesign.gtimg.com/icon/0.3.1/fonts/t.woff') format('woff'),
url('https://tdesign.gtimg.com/icon/0.3.1/fonts/t.ttf') format('truetype');
font-weight: normal;
font-style: normal;
font-display: swap;
}
page { page {
--primary: #558B2F; --primary: #558B2F;
--primary-light: #9CCC65; --primary-light: #9CCC65;
-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#333" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m12 19-7-7 7-7"/><path d="M19 12H5"/></svg>

Before

Width:  |  Height:  |  Size: 226 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#666" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>

Before

Width:  |  Height:  |  Size: 286 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#666" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="4" rx="2" ry="2"/><line x1="16" x2="16" y1="2" y2="6"/><line x1="8" x2="8" y1="2" y2="6"/><line x1="3" x2="21" y1="10" y2="10"/></svg>

Before

Width:  |  Height:  |  Size: 346 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#333" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>

Before

Width:  |  Height:  |  Size: 304 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ccc" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>

Before

Width:  |  Height:  |  Size: 215 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ccc" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>

Before

Width:  |  Height:  |  Size: 205 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#90A4AE" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>

Before

Width:  |  Height:  |  Size: 340 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#558B2F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>

Before

Width:  |  Height:  |  Size: 340 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#FFD700" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m2 4 3 12h14l3-12-6 7-4-7-4 7-6-7zm3 16h14"/></svg>

Before

Width:  |  Height:  |  Size: 237 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#29B6F6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 16.3c2.2 0 4-1.83 4-4.05 0-1.16-.57-2.26-1.71-3.19S7.29 6.75 7 5.3c-.29 1.45-1.14 2.8-2.29 3.76S3 11.1 3 12.25c0 2.22 1.8 4.05 4 4.05z"/><path d="M12.56 6.6A10.97 10.97 0 0 0 14 3.02c.5 2.5 2 4.9 4 6.5s3 3.5 3 5.5a6.98 6.98 0 0 1-11.91 4.97"/></svg>

Before

Width:  |  Height:  |  Size: 438 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#2196F3" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/><line x1="16" x2="8" y1="13" y2="13"/><line x1="16" x2="8" y1="17" y2="17"/><line x1="10" x2="8" y1="9" y2="9"/></svg>

Before

Width:  |  Height:  |  Size: 411 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#90A4AE" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>

Before

Width:  |  Height:  |  Size: 283 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#558B2F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>

Before

Width:  |  Height:  |  Size: 283 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#666" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/></svg>

Before

Width:  |  Height:  |  Size: 328 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#666" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/></svg>

Before

Width:  |  Height:  |  Size: 282 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#333" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>

Before

Width:  |  Height:  |  Size: 308 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#BDBDBD" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>

Before

Width:  |  Height:  |  Size: 276 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9C27B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8" r="7"/><polyline points="8.21 13.89 7 23 12 20 17 23 15.79 13.88"/></svg>

Before

Width:  |  Height:  |  Size: 273 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#90A4AE" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>

Before

Width:  |  Height:  |  Size: 266 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#558B2F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>

Before

Width:  |  Height:  |  Size: 266 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#4CAF50" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/><rect width="10" height="6" x="7" y="9" rx="2"/><path d="M7 12h10"/></svg>

Before

Width:  |  Height:  |  Size: 393 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#8D6E63" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><line x1="20" x2="8.12" y1="4" y2="15.88"/><line x1="14.47" x2="20" y1="14.48" y2="20"/><line x1="8.12" x2="12" y1="8.12" y2="12"/></svg>

Before

Width:  |  Height:  |  Size: 373 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#333" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" x2="16.65" y1="21" y2="16.65"/></svg>

Before

Width:  |  Height:  |  Size: 256 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#666" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.47a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.39a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>

Before

Width:  |  Height:  |  Size: 789 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9C27B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>

Before

Width:  |  Height:  |  Size: 238 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#78909C" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 22v-5l5-5 5 5-5 5z"/><path d="M9.5 14.5 16 8"/><path d="m17 2 5 5-.5.5a3.53 3.53 0 0 1-5 0s0 0 0 0a3.53 3.53 0 0 1 0-5L17 2z"/></svg>

Before

Width:  |  Height:  |  Size: 322 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#FF9800" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>

Before

Width:  |  Height:  |  Size: 297 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#90A4AE" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><path d="M12 11h4"/><path d="M12 16h4"/><path d="M8 11h.01"/><path d="M8 16h.01"/></svg>

Before

Width:  |  Height:  |  Size: 403 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#558B2F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><path d="M12 11h4"/><path d="M12 16h4"/><path d="M8 11h.01"/><path d="M8 16h.01"/></svg>

Before

Width:  |  Height:  |  Size: 403 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#FFD700" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9H4.5a2.5 2.5 0 0 1 0-5H6"/><path d="M18 9h1.5a2.5 2.5 0 0 0 0-5H18"/><path d="M4 22h16"/><path d="M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22"/><path d="M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22"/><path d="M18 2H6v7a6 6 0 0 0 12 0V2Z"/></svg>

Before

Width:  |  Height:  |  Size: 465 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#90A4AE" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>

Before

Width:  |  Height:  |  Size: 289 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#558B2F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>

Before

Width:  |  Height:  |  Size: 289 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#999" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>

Before

Width:  |  Height:  |  Size: 224 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#2196F3" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>

Before

Width:  |  Height:  |  Size: 241 B

+3 -3
View File
@@ -5,9 +5,9 @@
"requires": true, "requires": true,
"packages": { "packages": {
"node_modules/tdesign-miniprogram": { "node_modules/tdesign-miniprogram": {
"version": "1.12.2", "version": "1.12.3",
"resolved": "https://registry.npmjs.org/tdesign-miniprogram/-/tdesign-miniprogram-1.12.2.tgz", "resolved": "https://registry.npmjs.org/tdesign-miniprogram/-/tdesign-miniprogram-1.12.3.tgz",
"integrity": "sha512-ZpOdwonT26RRCK/FWbg9tR2lAJ54Hb4PAdyTWu8URWkbKOmSQhn0JCwCtWWRofKbyWCPsCn5NqljobaGh5VCMg==", "integrity": "sha512-F4nMv/ph3yyq9bO4RrJuB9x9VWyKIN6lV1HqFaV4AsR0cpDoBYtGYLPOFejvj0MYDSntSHLMVe1nm0fqsXUaUQ==",
"license": "MIT" "license": "MIT"
} }
} }
+15
View File
@@ -5,6 +5,21 @@ toc: false
docClass: timeline docClass: timeline
--- ---
## 🌈 1.12.3 `2026-02-03`
### 🚀 Features
- `ActionSheet`: 为 `items` 子项的 `icon` 字段新增 `object` 类型,支持透传到 `TIcon` 组件 @anlyyao ([#4251](https://github.com/Tencent/tdesign-miniprogram/pull/4251))
- `Button`: 新增 `activity-type``entrance-path``need-show-entrance` 属性 @anlyyao ([#4220](https://github.com/Tencent/tdesign-miniprogram/pull/4220))
- `ChatActionbar`: 支持长按展示 @mimaoxiao ([#4071](https://github.com/Tencent/tdesign-miniprogram/pull/4071))
- `Icon`: 新增 217 个与人工智能、文档、徽标和文件相关的图标 @uyarn ([#4207](https://github.com/Tencent/tdesign-miniprogram/pull/4207))
- `Search`: 为 `change` 事件新增 `trigger` 参数,表示触发源 @anlyyao ([#4223](https://github.com/Tencent/tdesign-miniprogram/pull/4223))
### 🐞 Bug Fixes
- `ChatContent`: 修复英文单词在换行时被截断的问题 @mimaoxiao ([#4226](https://github.com/Tencent/tdesign-miniprogram/pull/4226))
- `Popup`: 修复 `duration` 参数无效的问题 @novlan1 ([#4201](https://github.com/Tencent/tdesign-miniprogram/pull/4201))
## 🌈 1.12.2 `2026-01-21` ## 🌈 1.12.2 `2026-01-21`
### 🚀 Features ### 🚀 Features
@@ -1,22 +1,7 @@
/// <reference types="miniprogram-api-typings" />
/// <reference types="miniprogram-api-typings" />
import { SuperComponent } from '../common/src/index'; import { SuperComponent } from '../common/src/index';
export default class ActionSheet extends SuperComponent { export default class ActionSheet extends SuperComponent {
static show: (options: import("./show").ActionSheetShowOption) => WechatMiniprogram.Component.TrivialInstance; static show: (options: import("./show").ActionSheetShowOption) => WechatMiniprogram.Component.TrivialInstance;
behaviors: WechatMiniprogram.Behavior.BehaviorIdentifier<{ behaviors: string[];
distanceTop: number;
}, {
usingCustomNavbar: {
type: BooleanConstructor;
value: false;
};
customNavbarHeight: {
type: NumberConstructor;
value: number;
};
}, {
calculateCustomNavbarDistanceTop(): void;
}, WechatMiniprogram.Component.BehaviorOption>[];
externalClasses: string[]; externalClasses: string[];
properties: { properties: {
align?: { align?: {
@@ -1 +1 @@
<wxs src="./action-sheet.wxs" module="_this"/><wxs src="../common/utils.wxs" module="_"/><import src="./template/list.wxml"/><import src="./template/grid.wxml"/><view id="{{classPrefix}}" style="{{_._style([style, customStyle])}}" class="{{_.cls(classPrefix, [align, theme, ['no-description', !description]])}} class {{prefix}}-class"><t-popup visible="{{visible}}" placement="bottom" usingCustomNavbar="{{usingCustomNavbar}}" bind:visible-change="onPopupVisibleChange" show-overlay="{{showOverlay}}" z-index="{{ popupProps.zIndex || defaultPopUpzIndex }}" overlay-props="{{ popupProps.overlayProps || defaultPopUpProps }}"><view class="{{classPrefix}}__content {{prefix}}-class-content" tabindex="0"><view wx:if="{{description}}" tabindex="0" class="{{classPrefix}}__description">{{description}}</view><block wx:if="{{gridThemeItems.length}}"><template is="grid" data="{{classPrefix, prefix, gridThemeItems, count, currentSwiperIndex}}"/></block><view wx:elif="{{items && items.length}}" class="{{classPrefix}}__list"><block wx:for="{{ items }}" wx:key="index"><template is="list" data="{{index, classPrefix, listThemeItemClass: _.cls(classPrefix + '__list-item', [['disabled', item.disabled]]), item}}"/></block></view></view><slot/><view wx:if="{{showCancel}}" class="{{classPrefix}}__footer"><view class="{{classPrefix}}__gap-{{theme}}"/><view class="{{classPrefix}}__cancel {{prefix}}-class-cancel" hover-class="{{classPrefix}}__cancel--hover" hover-stay-time="70" bind:tap="onCancel" aria-role="button">{{ cancelText || '取消' }}</view></view></t-popup></view> <wxs src="./action-sheet.wxs" module="_this"/><wxs src="../common/utils.wxs" module="_"/><import src="./template/grid.wxml"/><import src="../common/template/icon.wxml"/><view id="{{classPrefix}}" style="{{_._style([style, customStyle])}}" class="{{_.cls(classPrefix, [align, theme, ['no-description', !description]])}} class {{prefix}}-class"><t-popup visible="{{visible}}" placement="bottom" usingCustomNavbar="{{usingCustomNavbar}}" bind:visible-change="onPopupVisibleChange" show-overlay="{{showOverlay}}" z-index="{{ popupProps.zIndex || defaultPopUpzIndex }}" overlay-props="{{ popupProps.overlayProps || defaultPopUpProps }}"><view class="{{classPrefix}}__content {{prefix}}-class-content" tabindex="0"><view wx:if="{{description}}" tabindex="0" class="{{classPrefix}}__description">{{description}}</view><block wx:if="{{gridThemeItems.length}}"><template is="grid" data="{{classPrefix, prefix, gridThemeItems, count, currentSwiperIndex}}"/></block><view wx:elif="{{items && items.length}}" class="{{classPrefix}}__list"><block wx:for="{{ items }}" wx:key="index"><view data-index="{{index}}" style="{{ item.color ? 'color: ' + item.color : '' }}" class="{{_.cls(classPrefix + '__list-item', [['disabled', item.disabled]])}}" bind:tap="onSelect" aria-role="{{ariaRole || 'button'}}" aria-label="{{item.label || item}}" tabindex="0"><view class="{{classPrefix}}__list-item-content"><template wx:if="{{_this.getIconData(item.icon)}}" is="icon" data="{{tClass: classPrefix + '__list-item-icon', ..._this.getIconData(item.icon)}}"/><view class="{{classPrefix}}__list-item-text">{{item.label || item}}</view><template wx:if="{{_this.getIconData(item.suffixIcon)}}" is="icon" data="{{tClass: classPrefix + '__list-item-icon--suffix', ..._this.getIconData(item.suffixIcon)}}"/></view><view wx:if="{{item.description}}" class="{{classPrefix}}__list-item-desc">{{item.description}}</view></view></block></view></view><slot/><view wx:if="{{showCancel}}" class="{{classPrefix}}__footer"><view class="{{classPrefix}}__gap-{{theme}}"/><view class="{{classPrefix}}__cancel {{prefix}}-class-cancel" hover-class="{{classPrefix}}__cancel--hover" hover-stay-time="70" bind:tap="onCancel" aria-role="button">{{ cancelText || '取消' }}</view></view></t-popup></view>
@@ -1,3 +1,5 @@
var utils = require('../common/utils.wxs');
var getListThemeItemClass = function (props) { var getListThemeItemClass = function (props) {
var classPrefix = props.classPrefix; var classPrefix = props.classPrefix;
var item = props.item; var item = props.item;
@@ -9,11 +11,24 @@ var getListThemeItemClass = function (props) {
return classList.join(' '); return classList.join(' ');
}; };
var getIconData = function (icon) {
if (utils.isString(icon)) {
return { name: icon };
}
if (utils.isNoEmptyObj(icon)) {
return icon;
}
return null;
};
var isImage = function (name) { var isImage = function (name) {
return name.indexOf('/') !== -1; return name.indexOf('/') !== -1;
}; };
module.exports = { module.exports = {
getListThemeItemClass: getListThemeItemClass, getListThemeItemClass: getListThemeItemClass,
getIconData: getIconData,
isImage: isImage, isImage: isImage,
}; };
@@ -1 +1 @@
<template name="grid"><block wx:if="{{gridThemeItems.length === 1}}"><t-grid align="center" t-class="{{classPrefix}}__grid" column="{{count / 2}}" class="{{classPrefix}}__single-wrap"><t-grid-item t-class="{{classPrefix}}__grid-item" class="{{classPrefix}}__square" wx:for="{{gridThemeItems[0]}}" wx:key="index" bind:tap="onSelect" data-index="{{index}}" icon="{{ { name: item.icon, color: item.color } }}" text="{{item.label || ''}}" description="{{item.description || ''}}" image="{{item.image || ''}}" style="--td-grid-item-text-color: {{item.color}}"></t-grid-item></t-grid></block><block wx:elif="{{gridThemeItems.length > 1}}"><view class="{{classPrefix}}__swiper-wrap"><swiper style="height: 456rpx" autoplay="{{false}}" current="{{currentSwiperIndex}}" bindchange="onSwiperChange"><swiper-item wx:for="{{gridThemeItems}}" wx:key="index"><t-grid align="center" t-class="{{classPrefix}}__grid {{classPrefix}}__grid--swiper" column="{{count / 2}}"><t-grid-item t-class="{{classPrefix}}__grid-item" class="{{classPrefix}}__square" wx:for="{{item}}" wx:key="index" data-index="{{index}}" bind:tap="onSelect" icon="{{ { name: item.icon, color: item.color } }}" text="{{item.label || ''}}" description="{{item.description || ''}}" image="{{item.image || ''}}" style="--td-grid-item-text-color: {{item.color}}"></t-grid-item></t-grid></swiper-item></swiper><view class="{{classPrefix}}__nav"><view class="{{classPrefix}}__dots"><view wx:for="{{gridThemeItems.length}}" wx:key="index" class="{{classPrefix}}__dots-item {{index === currentSwiperIndex ? prefix + '-is-active' : ''}}"/></view></view></view></block></template> <wxs src="../action-sheet.wxs" module="_this"/><template name="grid"><block wx:if="{{gridThemeItems.length === 1}}"><t-grid align="center" t-class="{{classPrefix}}__grid" column="{{count / 2}}" class="{{classPrefix}}__single-wrap"><t-grid-item t-class="{{classPrefix}}__grid-item" class="{{classPrefix}}__square" wx:for="{{gridThemeItems[0]}}" wx:key="index" bind:tap="onSelect" data-index="{{index}}" icon="{{ { color: item.color, ..._this.getIconData(item.icon) } }}" text="{{item.label || ''}}" description="{{item.description || ''}}" image="{{item.image || ''}}" style="--td-grid-item-text-color: {{item.color}}"></t-grid-item></t-grid></block><block wx:elif="{{gridThemeItems.length > 1}}"><view class="{{classPrefix}}__swiper-wrap"><swiper style="height: 456rpx" autoplay="{{false}}" current="{{currentSwiperIndex}}" bindchange="onSwiperChange"><swiper-item wx:for="{{gridThemeItems}}" wx:key="index"><t-grid align="center" t-class="{{classPrefix}}__grid {{classPrefix}}__grid--swiper" column="{{count / 2}}"><t-grid-item t-class="{{classPrefix}}__grid-item" class="{{classPrefix}}__square" wx:for="{{item}}" wx:key="index" data-index="{{index}}" bind:tap="onSelect" icon="{{ { color: item.color, ..._this.getIconData(item.icon) } }}" text="{{item.label || ''}}" description="{{item.description || ''}}" image="{{item.image || ''}}" style="--td-grid-item-text-color: {{item.color}}"></t-grid-item></t-grid></swiper-item></swiper><view class="{{classPrefix}}__nav"><view class="{{classPrefix}}__dots"><view wx:for="{{gridThemeItems.length}}" wx:key="index" class="{{classPrefix}}__dots-item {{index === currentSwiperIndex ? prefix + '-is-active' : ''}}"/></view></view></view></block></template>
@@ -1 +0,0 @@
<template name="list"><view data-index="{{index}}" style="{{ item.color ? 'color: ' + item.color : '' }}" class="{{listThemeItemClass}}" bind:tap="onSelect" aria-role="{{ariaRole || 'button'}}" aria-label="{{item.label || item}}" tabindex="0"><view class="{{classPrefix}}__list-item-content"><t-icon wx:if="{{item.icon}}" name="{{item.icon}}" class="{{classPrefix}}__list-item-icon"/><view class="{{classPrefix}}__list-item-text">{{item.label || item}}</view><t-icon wx:if="{{item.suffixIcon}}" name="{{item.suffixIcon}}" class="{{classPrefix}}__list-item-icon {{classPrefix}}__list-item-icon--suffix"/></view><view wx:if="{{item.description}}" class="{{classPrefix}}__list-item-desc">{{item.description}}</view></view></template>
+2 -2
View File
@@ -55,6 +55,6 @@ export interface ActionSheetItem {
description?: string; description?: string;
color?: string; color?: string;
disabled?: boolean; disabled?: boolean;
icon?: string; icon?: string | object;
suffixIcon?: string; suffixIcon?: string | object;
} }
+1 -1
View File
@@ -1 +1 @@
<import src="../common/template/icon.wxml"/><wxs src="../common/utils.wxs" module="_"/><button id="{{tId}}" style="{{_._style([style, customStyle])}}" data-custom="{{ customDataset }}" class="class {{className}}" form-type="{{disabled || loading ? '' : type}}" open-type="{{disabled || loading ? '' : openType}}" hover-stop-propagation="{{hoverStopPropagation}}" hover-start-time="{{hoverStartTime}}" hover-stay-time="{{hoverStayTime}}" lang="{{lang}}" session-from="{{sessionFrom}}" hover-class="{{disabled || loading ? '' : (hoverClass || classPrefix + '--hover')}}" send-message-title="{{sendMessageTitle}}" send-message-path="{{sendMessagePath}}" send-message-img="{{sendMessageImg}}" app-parameter="{{appParameter}}" show-message-card="{{showMessageCard}}" catch:tap="handleTap" bind:getuserinfo="getuserinfo" bind:contact="contact" bind:createliveactivity="createliveactivity" bind:getphonenumber="getphonenumber" bind:getrealtimephonenumber="getrealtimephonenumber" bind:error="error" bind:opensetting="opensetting" bind:launchapp="launchapp" bind:chooseavatar="chooseavatar" bind:agreeprivacyauthorization="agreeprivacyauthorization" aria-label="{{ariaLabel}}"><template wx:if="{{_icon}}" is="icon" data="{{tClass: classPrefix + '__icon ' + prefix + '-class-icon', ariaHidden: true, name: iconName, ..._icon}}"/><t-loading wx:if="{{loading}}" delay="{{loadingProps.delay || 0}}" duration="{{loadingProps.duration || 800}}" indicator="{{loadingProps.indicator || true}}" inheritColor="{{loadingProps.inheritColor || true}}" layout="{{loadingProps.layout || 'horizontal'}}" pause="{{loadingProps.pause || false}}" progress="{{loadingProps.progress || 0}}" reverse="{{loadingProps.reverse || false}}" size="{{loadingProps.size || '40rpx'}}" text="{{loadingProps.text || '' }}" theme="{{loadingProps.theme || 'circular'}}" loading t-class="{{classPrefix}}__loading {{classPrefix}}__loading--wrapper" t-class-indicator="{{classPrefix}}__loading--indicator {{prefix}}-class-loading"/><view class="{{classPrefix}}__content"><slot name="content"/><block wx:if="{{content}}">{{content}}</block><slot/></view><slot name="suffix"/></button> <import src="../common/template/icon.wxml"/><wxs src="../common/utils.wxs" module="_"/><button id="{{tId}}" style="{{_._style([style, customStyle])}}" data-custom="{{customDataset}}" class="class {{className}}" activity-type="{{activityType ? activityType : ''}}" entrance-path="{{entrancePath}}" form-type="{{disabled || loading ? '' : type}}" open-type="{{disabled || loading ? '' : openType}}" hover-stop-propagation="{{hoverStopPropagation}}" hover-start-time="{{hoverStartTime}}" hover-stay-time="{{hoverStayTime}}" lang="{{lang}}" need-show-entrance="{{needShowEntrance}}" session-from="{{sessionFrom}}" hover-class="{{disabled || loading ? '' : (hoverClass || classPrefix + '--hover')}}" send-message-title="{{sendMessageTitle}}" send-message-path="{{sendMessagePath}}" send-message-img="{{sendMessageImg}}" app-parameter="{{appParameter}}" show-message-card="{{showMessageCard}}" catch:tap="handleTap" bind:getuserinfo="getuserinfo" bind:contact="contact" bind:createliveactivity="createliveactivity" bind:getphonenumber="getphonenumber" bind:getrealtimephonenumber="getrealtimephonenumber" bind:error="error" bind:opensetting="opensetting" bind:launchapp="launchapp" bind:chooseavatar="chooseavatar" bind:agreeprivacyauthorization="agreeprivacyauthorization" aria-label="{{ariaLabel}}"><template wx:if="{{_icon}}" is="icon" data="{{tClass: classPrefix + '__icon ' + prefix + '-class-icon', ariaHidden: true, name: iconName, ..._icon}}"/><t-loading wx:if="{{loading}}" delay="{{loadingProps.delay || 0}}" duration="{{loadingProps.duration || 800}}" indicator="{{loadingProps.indicator || true}}" inheritColor="{{loadingProps.inheritColor || true}}" layout="{{loadingProps.layout || 'horizontal'}}" pause="{{loadingProps.pause || false}}" progress="{{loadingProps.progress || 0}}" reverse="{{loadingProps.reverse || false}}" size="{{loadingProps.size || '40rpx'}}" text="{{loadingProps.text || '' }}" theme="{{loadingProps.theme || 'circular'}}" loading t-class="{{classPrefix}}__loading {{classPrefix}}__loading--wrapper" t-class-indicator="{{classPrefix}}__loading--indicator {{prefix}}-class-loading"/><view class="{{classPrefix}}__content"><slot name="content"/><block wx:if="{{content}}">{{content}}</block><slot/></view><slot name="suffix"/></button>
+1 -1
View File
@@ -1 +1 @@
const props={appParameter:{type:String,value:""},block:{type:Boolean,value:!1},content:{type:String},customDataset:{type:null},disabled:{type:null,value:void 0},ghost:{type:Boolean,value:!1},hoverClass:{type:String,value:""},hoverStartTime:{type:Number,value:20},hoverStayTime:{type:Number,value:70},hoverStopPropagation:{type:Boolean,value:!1},icon:{type:null},lang:{type:String},loading:{type:Boolean,value:!1},loadingProps:{type:Object},openType:{type:String},phoneNumberNoQuotaToast:{type:Boolean,value:!0},sendMessageImg:{type:String,value:"截图"},sendMessagePath:{type:String,value:"当前分享路径"},sendMessageTitle:{type:String,value:"当前标题"},sessionFrom:{type:String,value:""},shape:{type:String,value:"rectangle"},showMessageCard:{type:Boolean,value:!1},size:{type:String,value:"medium"},tId:{type:String,value:""},theme:{type:String,value:"default"},type:{type:String},variant:{type:String,value:"base"}};export default props; const props={activityType:{type:Number},appParameter:{type:String,value:""},block:{type:Boolean,value:!1},content:{type:String},customDataset:{type:null},disabled:{type:null,value:void 0},entrancePath:{type:String,value:""},ghost:{type:Boolean,value:!1},hoverClass:{type:String,value:""},hoverStartTime:{type:Number,value:20},hoverStayTime:{type:Number,value:70},hoverStopPropagation:{type:Boolean,value:!1},icon:{type:null},lang:{type:String},loading:{type:Boolean,value:!1},loadingProps:{type:Object},needShowEntrance:{type:Boolean,value:!0},openType:{type:String},phoneNumberNoQuotaToast:{type:Boolean,value:!0},sendMessageImg:{type:String,value:"截图"},sendMessagePath:{type:String,value:"当前分享路径"},sendMessageTitle:{type:String,value:"当前标题"},sessionFrom:{type:String,value:""},shape:{type:String,value:"rectangle"},showMessageCard:{type:Boolean,value:!1},size:{type:String,value:"medium"},tId:{type:String,value:""},theme:{type:String,value:"default"},type:{type:String},variant:{type:String,value:"base"}};export default props;
+12
View File
@@ -1,5 +1,9 @@
import { LoadingProps } from '../loading/index'; import { LoadingProps } from '../loading/index';
export interface TdButtonProps { export interface TdButtonProps {
activityType?: {
type: NumberConstructor;
value?: number;
};
appParameter?: { appParameter?: {
type: StringConstructor; type: StringConstructor;
value?: string; value?: string;
@@ -20,6 +24,10 @@ export interface TdButtonProps {
type: BooleanConstructor; type: BooleanConstructor;
value?: boolean; value?: boolean;
}; };
entrancePath?: {
type: StringConstructor;
value?: string;
};
ghost?: { ghost?: {
type: BooleanConstructor; type: BooleanConstructor;
value?: boolean; value?: boolean;
@@ -56,6 +64,10 @@ export interface TdButtonProps {
type: ObjectConstructor; type: ObjectConstructor;
value?: LoadingProps; value?: LoadingProps;
}; };
needShowEntrance?: {
type: BooleanConstructor;
value?: boolean;
};
openType?: { openType?: {
type: StringConstructor; type: StringConstructor;
value?: 'contact' | 'share' | 'getPhoneNumber' | 'getUserInfo' | 'launchApp' | 'openSetting' | 'feedback' | 'chooseAvatar' | 'agreePrivacyAuthorization'; value?: 'contact' | 'share' | 'getPhoneNumber' | 'getUserInfo' | 'launchApp' | 'openSetting' | 'feedback' | 'chooseAvatar' | 'agreePrivacyAuthorization';
+1 -15
View File
@@ -1,24 +1,10 @@
/// <reference types="miniprogram-api-typings" /> /// <reference types="miniprogram-api-typings" />
/// <reference types="miniprogram-api-typings" />
import { SuperComponent } from '../common/src/index'; import { SuperComponent } from '../common/src/index';
import { TdCalendarProps } from './type'; import { TdCalendarProps } from './type';
export interface CalendarProps extends TdCalendarProps { export interface CalendarProps extends TdCalendarProps {
} }
export default class Calendar extends SuperComponent { export default class Calendar extends SuperComponent {
behaviors: WechatMiniprogram.Behavior.BehaviorIdentifier<{ behaviors: string[];
distanceTop: number;
}, {
usingCustomNavbar: {
type: BooleanConstructor;
value: false;
};
customNavbarHeight: {
type: NumberConstructor;
value: number;
};
}, {
calculateCustomNavbarDistanceTop(): void;
}, WechatMiniprogram.Component.BehaviorOption>[];
externalClasses: string[]; externalClasses: string[];
options: WechatMiniprogram.Component.ComponentOptions; options: WechatMiniprogram.Component.ComponentOptions;
properties: TdCalendarProps; properties: TdCalendarProps;
@@ -12,21 +12,31 @@ export default class ChatActionbar extends SuperComponent {
replay: string; replay: string;
copy: string; copy: string;
share: string; share: string;
quote: string;
}; };
iconActiveMap: { iconActiveMap: {
good: string; good: string;
bad: string; bad: string;
}; };
widthStyle: string;
popoverStyle: string;
popoverPosition: string;
longpressVisible: boolean;
}; };
observers: { observers: {
comment(newVal: any): void; comment(newVal: any): void;
'actionBar, pComment'(): void; 'actionBar, pComment, placement'(): void;
longPressPosition(newVal: any): void;
}; };
methods: { methods: {
filterSpecialChars(content: string): string; filterSpecialChars(content: string): string;
handleActionClick(e: any): void; handleActionClick(e: any): void;
handleCopy(): void; handleCopy(): void;
setActions(): void; setActions(): void;
setPComment(newVal: any): void;
showPopover(pos: any): void;
hidePopover(): void;
onVisibleChange(e: any): void;
}; };
lifetimes: { lifetimes: {
created(): void; created(): void;
@@ -1 +1 @@
import{__decorate}from"tslib";import{SuperComponent,wxComponent}from"../common/src/index";import config from"../common/config";import props from"./props";const{prefix:prefix}=config,name=`${prefix}-chat-actionbar`;let ChatActionbar=class extends SuperComponent{constructor(){super(...arguments),this.options={multipleSlots:!0},this.properties=props,this.data={actions:[],classPrefix:name,pComment:"",iconMap:{good:"thumb-up",bad:"thumb-down",replay:"refresh",copy:"copy",share:"share-1"},iconActiveMap:{good:"thumb-up-filled",bad:"thumb-down-filled"}},this.observers={comment(t){this.setData({pComment:t||""})},"actionBar, pComment"(){this.setActions()}},this.methods={filterSpecialChars(t){let e=t;const a=[];e=e.replace(/^(\s*\|.*\|.*\n\s*\|[-: ]+\|.*\n(\s*\|.*\|.*\n)*)/gm,t=>{const e=t.replace(/\[\d+(?:,\d+)*\]\(@ref\)/g,"").replace(/(\*\*|__)(.*?)\1|(\*|_)(.*?)\3/g,"$2$4").replace(/<br\s*\/?>/gi,"\n");return a.push(e),`%%TABLE${a.length-1}%%`}),e=e.replace(/^(\s*)#{1,6}\s+/gm,"$1"),e=e.replace(/(\*\*|__)(.*?)\1|(\*|_)(.*?)\3/g,"$2$4"),e=e.replace(/!\[.*?\]\(.*?\)/g,""),e=e.replace(/\[\d+(?:,\d+)*\]\(@ref\)/g,"");return e=e.replace(/(\\|`|\{|\}|\[|\]|\(|\)|\|||@ref|\([@#]\w+\))/g,""),e=e.replace(/\[\d+\]/g,""),e=e.replace(/<br\s*\/?>/gi,"\n"),e=e.replace(/%%TABLE(\d+)%%/g,(t,e)=>a[parseInt(e,10)]||""),e.replace(/\n{3,}/g,"\n\n").trim()},handleActionClick(t){const{name:e}=t.currentTarget.dataset;if("copy"===e&&this.data.content)this.data.handleCopy();else if("good"===e){const t="good"===this.data.pComment;this.setData({pComment:t?void 0:"good"}),this.triggerEvent("actions",{name:e,active:!t})}else if("bad"===e){const t="bad"===this.data.pComment;this.setData({pComment:t?void 0:"bad"}),this.triggerEvent("actions",{name:e,active:!t})}else this.triggerEvent("actions",{name:e})},handleCopy(){if(!this.data.content)return;const t="markdown"===this.data.copyMode?this.data.content:this.data.filterSpecialChars(this.data.content);this.triggerEvent("actions",{name:"copy",data:t})},setActions(){const t=[];Array.isArray(this.properties.actionBar)&&this.properties.actionBar.forEach(e=>{"good"===e||"bad"===e?t.push({name:e,isActive:this.data.pComment===e}):t.push({name:e,isActive:!1})}),this.setData({actions:t})}},this.lifetimes={created(){this.data.filterSpecialChars=this.filterSpecialChars.bind(this),this.data.handleActionClick=this.handleActionClick.bind(this),this.data.handleCopy=this.handleCopy.bind(this)},attached(){this.setData({pComment:this.properties.comment||""}),this.setActions()},detached(){}}}};ChatActionbar=__decorate([wxComponent()],ChatActionbar);export default ChatActionbar; import{__decorate}from"tslib";import{SuperComponent,wxComponent}from"../common/src/index";import config from"../common/config";import props from"./props";const{prefix:prefix}=config,name=`${prefix}-chat-actionbar`;let ChatActionbar=class extends SuperComponent{constructor(){super(...arguments),this.options={multipleSlots:!0,styleIsolation:"shared"},this.properties=props,this.data={actions:[],classPrefix:name,pComment:"",iconMap:{good:"thumb-up",bad:"thumb-down",replay:"refresh",copy:"copy",share:"share-1",quote:"enter"},iconActiveMap:{good:"thumb-up-filled",bad:"thumb-down-filled"},widthStyle:"",popoverStyle:"transition: none;position: fixed;",popoverPosition:"",longpressVisible:!1},this.observers={comment(t){this.setPComment(t)},"actionBar, pComment, placement"(){this.setActions()},longPressPosition(t){"longpress"===this.properties.placement&&(t?this.showPopover(t):this.hidePopover())}},this.methods={filterSpecialChars(t){let e=t;const i=[];e=e.replace(/^(\s*\|.*\|.*\n\s*\|[-: ]+\|.*\n(\s*\|.*\|.*\n)*)/gm,t=>{const e=t.replace(/\[\d+(?:,\d+)*\]\(@ref\)/g,"").replace(/(\*\*|__)(.*?)\1|(\*|_)(.*?)\3/g,"$2$4").replace(/<br\s*\/?>/gi,"\n");return i.push(e),`%%TABLE${i.length-1}%%`}),e=e.replace(/^(\s*)#{1,6}\s+/gm,"$1"),e=e.replace(/(\*\*|__)(.*?)\1|(\*|_)(.*?)\3/g,"$2$4"),e=e.replace(/!\[.*?\]\(.*?\)/g,""),e=e.replace(/\[\d+(?:,\d+)*\]\(@ref\)/g,"");return e=e.replace(/(\\|`|\{|\}|\[|\]|\(|\)|\|||@ref|\([@#]\w+\))/g,""),e=e.replace(/\[\d+\]/g,""),e=e.replace(/<br\s*\/?>/gi,"\n"),e=e.replace(/%%TABLE(\d+)%%/g,(t,e)=>i[parseInt(e,10)]||""),e.replace(/\n{3,}/g,"\n\n").trim()},handleActionClick(t){const{name:e}=t.currentTarget.dataset;if("copy"===e&&this.data.content)this.data.handleCopy();else if("good"===e){const t="good"===this.data.pComment;this.setData({pComment:t?void 0:"good"}),this.triggerEvent("actions",{name:e,active:!t,chatId:this.properties.chatId})}else if("bad"===e){const t="bad"===this.data.pComment;this.setData({pComment:t?void 0:"bad"}),this.triggerEvent("actions",{name:e,active:!t,chatId:this.properties.chatId})}else this.triggerEvent("actions",{name:e,chatId:this.properties.chatId});this.onVisibleChange({detail:{visible:!1}})},handleCopy(){if(!this.data.content)return;const t="markdown"===this.data.copyMode?this.data.content:this.data.filterSpecialChars(this.data.content);this.triggerEvent("actions",{name:"copy",data:t})},setActions(){const t={replay:"刷新",copy:"复制",good:"点赞",bad:"点踩",share:"分享",quote:"引用"},e=[];let i=[];"longpress"===this.properties.placement?i=["quote","copy","share"]:Array.isArray(this.properties.actionBar)&&(i=this.properties.actionBar),i.forEach(i=>{"good"===i||"bad"===i?e.push({name:i,isActive:this.data.pComment===i,text:t[i]||i}):e.push({name:i,isActive:!1,text:t[i]||i})}),this.setData({actions:e})},setPComment(t){this.setData({pComment:t||""})},showPopover(t){this.setData({widthStyle:`width: ${128*this.data.actions.length+8*(this.data.actions.length-1)}rpx`,popoverPosition:`top:${t.y}px;left:${t.x}px`,longpressVisible:!0}),setTimeout(()=>{const t=this.selectComponent(".popover"),e=this.createSelectorQuery().in(t);e.select(".t-popover").boundingClientRect(),e.exec(t=>{const[e]=t,{screenWidth:i}=wx.getWindowInfo();e.left+e.width>i?this.setData({popoverStyle:"transition: none;position:fixed; left: unset !important; right: 16rpx !important;"}):e.left<=0&&this.setData({popoverStyle:"transition: none;position:fixed; left: 16rpx !important;"})})},200)},hidePopover(){this.onVisibleChange({detail:{visible:!1}})},onVisibleChange(t){const{visible:e}=t.detail;this.setData({longpressVisible:e}),e||setTimeout(()=>{this.setData({popoverPosition:"",popoverStyle:"transition: none;position: fixed;"})},200)}},this.lifetimes={created(){this.data.filterSpecialChars=this.filterSpecialChars.bind(this),this.data.handleActionClick=this.handleActionClick.bind(this),this.data.handleCopy=this.handleCopy.bind(this),this.data.showPopover=this.showPopover.bind(this),this.data.hidePopover=this.hidePopover.bind(this),this.data.setPComment=this.setPComment.bind(this)},attached(){this.setData({pComment:this.properties.comment||""}),this.setActions()},detached(){}}}};ChatActionbar=__decorate([wxComponent()],ChatActionbar);export default ChatActionbar;
@@ -1 +1 @@
{"component":true,"styleIsolation":"apply-shared","usingComponents":{"t-icon":"../icon/icon"}} {"component":true,"styleIsolation":"apply-shared","usingComponents":{"t-icon":"../icon/icon","t-popover":"../popover/popover"}}
@@ -1 +1 @@
<wxs src="../common/utils.wxs" module="_"/><view class="class {{[classPrefix, placement==='longpress' ? classPrefix+'__inner--popover' : '', computedPlacement]}}" style="{{_._style([style, customStyle])}}"><view class="{{classPrefix}}__inner {{classPrefix}}__inner--column"><view class="{{classPrefix}}__item {{classPrefix}}__left"><slot name="prefix"/></view><block wx:for="{{actions}}" wx:for-item="item" wx:for-index="index" wx:key="index"><button wx:if="{{item.name === 'share'}}" data-name="{{item.name}}" class="{{_.cls(classPrefix + '__item', [['active', item.isActive]])}}" open-type="{{content ? 'share' : 'none'}}" data-chat-id="{{chatId}}" bindtap="handleActionClick"><t-icon name="{{item.isActive ? iconActiveMap[item.name] : iconMap[item.name]}}" size="40rpx"/></button><view wx:else data-name="{{item.name}}" class="{{_.cls(classPrefix + '__item', [['active', item.isActive]])}}" bindtap="handleActionClick"><t-icon name="{{item.isActive ? iconActiveMap[item.name] : iconMap[item.name]}}" size="40rpx"/></view></block></view></view> <wxs src="../common/utils.wxs" module="_"/><view wx:if="{{placement !== 'longpress'}}" class="{{[classPrefix, placement]}}" style="{{_._style([style, customStyle])}}"><view class="{{classPrefix}}__inner {{classPrefix}}__inner--column"><view class="{{classPrefix}}__left {{classPrefix+'__item'}}"><slot name="prefix"/></view><block wx:for="{{actions}}" wx:for-item="item" wx:for-index="index" wx:key="index"><button wx:if="{{item.name === 'share'}}" data-name="{{item.name}}" class="{{_.cls(classPrefix+'__item', [['active', item.isActive]])}}" open-type="{{content ? 'share' : 'none'}}" data-chat-id="{{chatId}}" bindtap="handleActionClick"><t-icon name="{{item.isActive ? iconActiveMap[item.name] : iconMap[item.name]}}" size="40rpx"/></button><view wx:else data-name="{{item.name}}" class="{{_.cls(classPrefix+'__item', [['active', item.isActive]])}}" bindtap="handleActionClick"><t-icon name="{{item.isActive ? iconActiveMap[item.name] : iconMap[item.name]}}" size="40rpx"/></view></block></view></view><view wx:else class="{{[classPrefix, classPrefix+'__popover-skeleton']}}" style="{{popoverPosition}}"><t-popover class="popover" placement="bottom" theme="dark" visible="{{longpressVisible}}" customStyle="{{popoverStyle}}" bind:visible-change="onVisibleChange"><view class="{{[classPrefix, classPrefix+'__popover-skeleton__inner']}}"></view><view slot="content" class="{{[classPrefix, classPrefix+'--popover', 'popover-visible']}}" style="{{_._style([style, customStyle, widthStyle])}}"><view class="{{classPrefix}}__inner {{classPrefix}}__inner--column"><view class="{{classPrefix}}__left {{classPrefix+'__item--popover'}}"><slot name="prefix"/></view><block wx:for="{{actions}}" wx:for-item="item" wx:for-index="index" wx:key="index"><button wx:if="{{item.name === 'share'}}" data-name="{{item.name}}" class="{{_.cls(classPrefix+'__item--popover', [['active', item.isActive]])}}" open-type="{{content ? 'share' : 'none'}}" data-chat-id="{{chatId}}" bindtap="handleActionClick"><t-icon name="{{iconMap[item.name]}}" size="40rpx"/><view class="{{classPrefix}}__item__text">{{item.text}}</view></button><view wx:else data-name="{{item.name}}" class="{{_.cls(classPrefix+'__item--popover', [['active', item.isActive]])}}" bindtap="handleActionClick"><t-icon name="{{iconMap[item.name]}}" size="40rpx" customStyle="{{item.name === 'quote' ? 'transform: scaleX(-1)' : ''}}"/><view class="{{classPrefix}}__item__text">{{item.text}}</view></view></block></view></view></t-popover></view>
@@ -1,11 +1,16 @@
@import '../common/style/index.wxss';.t-chat-actionbar{display:flex;padding:var(--chat-actionbar-padding,0);} @import '../common/style/index.wxss';.t-chat-actionbar{display:flex;padding:var(--chat-actionbar-padding,0);}
.t-chat-actionbar.start{justify-content:flex-start;} .t-chat-actionbar.start{justify-content:flex-start;}
.t-chat-actionbar.end{justify-content:flex-end;} .t-chat-actionbar.end{justify-content:flex-end;}
.t-chat-actionbar--popover{color:var(--td-font-white-1,#fff);border-radius:6rpx;}
.t-chat-actionbar--popover .t-chat-actionbar__inner{background-color:unset;border:none;display:flex;flex-wrap:wrap;gap:4rpx;}
.t-chat-actionbar--popover .t-chat-actionbar__inner--column{gap:8rpx;}
.t-chat-actionbar--popover .t-chat-actionbar__item--popover{color:#fff;background-color:unset;padding:0;margin:0;font-size:28rpx;line-height:42rpx;width:128rpx;height:156rpx;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8rpx;}
.t-chat-actionbar__inner{background-color:var(--td-bg-color-secondarycontainer,var(--td-gray-color-1,#f3f3f3));border:2rpx solid var(--td-component-border,var(--td-gray-color-4,#dcdcdc));box-sizing:border-box;border-radius:var(--td-radius-default,12rpx);display:inline-flex;flex-direction:row;flex-wrap:nowrap;align-items:center;} .t-chat-actionbar__inner{background-color:var(--td-bg-color-secondarycontainer,var(--td-gray-color-1,#f3f3f3));border:2rpx solid var(--td-component-border,var(--td-gray-color-4,#dcdcdc));box-sizing:border-box;border-radius:var(--td-radius-default,12rpx);display:inline-flex;flex-direction:row;flex-wrap:nowrap;align-items:center;}
.t-chat-actionbar__inner--column{display:flex;align-items:center;justify-content:space-between;} .t-chat-actionbar__inner--column{display:flex;align-items:center;justify-content:space-between;}
.t-chat-actionbar__inner--popover{padding:45rpx;background-color:var(--td-mask-active,rgba(0,0,0,.6));border-radius:32rpx;color:var(--td-font-white-1,#fff);}
.t-chat-actionbar__left:empty{display:none;} .t-chat-actionbar__left:empty{display:none;}
.t-chat-actionbar__item{color:var(--td-text-color-primary,var(--td-font-gray-1,rgba(0,0,0,.9)));margin:12rpx 0;padding:4rpx 28rpx;border-right:2rpx solid var(--td-component-stroke,var(--td-gray-color-3,#e7e7e7));background-color:unset;outline:0;} .t-chat-actionbar__item{color:var(--td-text-color-primary,var(--td-font-gray-1,rgba(0,0,0,.9)));padding:var(--chat-actionbar-item-padding,16rpx 28rpx);border-right:2rpx solid var(--td-component-stroke,var(--td-gray-color-3,#e7e7e7));background-color:unset;outline:0;}
.t-chat-actionbar__item:after{display:none;} .t-chat-actionbar__item:after{display:none;}
.t-chat-actionbar__item:last-child{border-right:none;} .t-chat-actionbar__item:last-child{border-right:none;}
.t-chat-actionbar__item--active{color:var(--td-brand-color,var(--td-primary-color-7,#0052d9));} .t-chat-actionbar__item--active{color:var(--td-brand-color,var(--td-primary-color-7,#0052d9));}
.t-chat-actionbar__popover-skeleton{position:fixed;--td-popover-padding:8rpx 16rpx;}
.t-chat-actionbar__popover-skeleton__inner{width:20rpx;height:20rpx;}
+1 -1
View File
@@ -1 +1 @@
const props={actionBar:{type:Array,value:["replay","copy","good","bad","share"]},chatId:{type:String,value:""},comment:{type:String,value:""},content:{type:String,value:""},copyMode:{type:String,value:"markdown"},disabled:{type:Boolean,value:!1},placement:{type:String,value:"start"}};export default props; const props={actionBar:{type:Array,value:["replay","copy","good","bad","share"]},chatId:{type:String,value:""},comment:{type:String,value:""},content:{type:String,value:""},copyMode:{type:String,value:"markdown"},disabled:{type:Boolean,value:!1},placement:{type:String,value:"start"},longPressPosition:{type:Object,value:null}};export default props;
+12 -1
View File
@@ -25,6 +25,17 @@ export interface TdChatActionbarProps {
}; };
placement?: { placement?: {
type: StringConstructor; type: StringConstructor;
value?: 'start' | 'end' | 'space-around' | 'space-between'; value?: 'start' | 'end' | 'space-around' | 'space-between' | 'longpress';
};
longPressPosition?: {
type: ObjectConstructor;
value?: {
pageX: number;
pageY: number;
clientX: number;
clientY: number;
x: number;
y: number;
};
}; };
} }
@@ -1,4 +1,4 @@
@import '../common/style/index.wxss';.t-chat-content{font:var(--td-font-body-large,32rpx / 48rpx var(--td-font-family,PingFang SC,Microsoft YaHei,Arial Regular));word-break:break-all;word-wrap:break-word;overflow-wrap:break-word;box-sizing:border-box;width:fit-content;} @import '../common/style/index.wxss';.t-chat-content{font:var(--td-font-body-large,32rpx / 48rpx var(--td-font-family,PingFang SC,Microsoft YaHei,Arial Regular));word-break:break-word;word-wrap:break-word;overflow-wrap:break-word;box-sizing:border-box;width:fit-content;}
.t-chat-content__system,.t-chat-content__user{color:var(--td-chat-content-user-text-color,var(--td-text-color-primary,var(--td-font-gray-1,rgba(0,0,0,.9))));} .t-chat-content__system,.t-chat-content__user{color:var(--td-chat-content-user-text-color,var(--td-text-color-primary,var(--td-font-gray-1,rgba(0,0,0,.9))));}
.t-chat-content__system ._pre,.t-chat-content__user ._pre{margin:0;white-space:pre-wrap;} .t-chat-content__system ._pre,.t-chat-content__user ._pre{margin:0;white-space:pre-wrap;}
.t-chat-content__assistant{color:var(--td-chat-content-assistant-text-color,var(--td-text-color-primary,var(--td-font-gray-1,rgba(0,0,0,.9))));} .t-chat-content__assistant{color:var(--td-chat-content-assistant-text-color,var(--td-text-color-primary,var(--td-font-gray-1,rgba(0,0,0,.9))));}
@@ -1 +1 @@
import{__decorate}from"tslib";import{SuperComponent,wxComponent}from"../common/src/index";import props from"./props";import config from"../common/config";const{prefix:prefix}=config,name=`${prefix}-chat-message`;let ChatMessage=class extends SuperComponent{constructor(){super(...arguments),this.options={multipleSlots:!0},this.properties=props,this.data={classPrefix:name,article:"",showAvatar:null,showName:null,showDateTime:null,contentClasses:[],chatItemClass:[]},this.observers={avatar(){this.setShowAvatar()},name(){this.setShowName()},datetime(){this.setShowDateTime()},classPrefix(){this.setContentClasses()},"classPrefix, variant, placement, showDateTime"(){this.setChatItemClass()}},this.methods={handleLongPress(t){this.triggerEvent("longpress",{e:t,id:this.data.chatId})},setShowAvatar(){var t;this.setData({showAvatar:(null===(t=this.properties)||void 0===t?void 0:t.avatar)||""})},setShowName(){var t;this.setData({showName:(null===(t=this.properties)||void 0===t?void 0:t.name)||""})},setShowDateTime(){var t;this.setData({showDateTime:(null===(t=this.properties)||void 0===t?void 0:t.datetime)||""})},setContentClasses(){this.setData({contentClasses:[`${this.data.classPrefix}__content`]})},setChatItemClass(){const{classPrefix:t,showDateTime:e}=this.data,{variant:s,role:a,placement:i}=this.properties,o=[`${t}`,`${t}--${s}`,a,i];e&&o.push(`${t}__header`),this.setData({chatItemClass:o})}},this.lifetimes={created(){this.data.handleLongPress=this.handleLongPress.bind(this)},attached(){this.setShowAvatar(),this.setShowName(),this.setShowDateTime(),this.setContentClasses(),this.setChatItemClass()},detached(){}}}};ChatMessage=__decorate([wxComponent()],ChatMessage);export default ChatMessage; import{__decorate}from"tslib";import{SuperComponent,wxComponent}from"../common/src/index";import props from"./props";import config from"../common/config";const{prefix:prefix}=config,name=`${prefix}-chat-message`;let ChatMessage=class extends SuperComponent{constructor(){super(...arguments),this.options={multipleSlots:!0},this.properties=props,this.data={classPrefix:name,article:"",showAvatar:null,showName:null,showDateTime:null,contentClasses:[],chatItemClass:[]},this.observers={avatar(){this.setShowAvatar()},name(){this.setShowName()},datetime(){this.setShowDateTime()},classPrefix(){this.setContentClasses()},"classPrefix, variant, placement, showDateTime"(){this.setChatItemClass()}},this.methods={handleLongPress(t){this.triggerEvent("message-longpress",{e:t,id:this.data.chatId,longPressPosition:{x:t.detail.x,y:t.detail.y}})},setShowAvatar(){var t;this.setData({showAvatar:(null===(t=this.properties)||void 0===t?void 0:t.avatar)||""})},setShowName(){var t;this.setData({showName:(null===(t=this.properties)||void 0===t?void 0:t.name)||""})},setShowDateTime(){var t;this.setData({showDateTime:(null===(t=this.properties)||void 0===t?void 0:t.datetime)||""})},setContentClasses(){this.setData({contentClasses:[`${this.data.classPrefix}__content`]})},setChatItemClass(){const{classPrefix:t,showDateTime:e}=this.data,{variant:s,role:a,placement:i}=this.properties,o=[`${t}`,`${t}--${s}`,a,i];e&&o.push(`${t}__header`),this.setData({chatItemClass:o})}},this.lifetimes={created(){this.data.handleLongPress=this.handleLongPress.bind(this)},attached(){this.setShowAvatar(),this.setShowName(),this.setShowDateTime(),this.setContentClasses(),this.setChatItemClass()},detached(){}}}};ChatMessage=__decorate([wxComponent()],ChatMessage);export default ChatMessage;
@@ -8,7 +8,6 @@
.t-chat-message__header .t-chat-message__avatar{padding-top:calc(44rpx + var(--td-spacer,16rpx));} .t-chat-message__header .t-chat-message__avatar{padding-top:calc(44rpx + var(--td-spacer,16rpx));}
.t-chat-message__header .t-chat-message__avatar:empty{padding-top:0;} .t-chat-message__header .t-chat-message__avatar:empty{padding-top:0;}
.t-chat-message.user .t-chat-message__base{padding-right:var(--td-spacer-2,32rpx);} .t-chat-message.user .t-chat-message__base{padding-right:var(--td-spacer-2,32rpx);}
.t-chat-message.user .t-chat-content{max-width:90%;}
.t-chat-message.assistant .t-chat-message__base{padding-left:var(--td-spacer-2,32rpx);} .t-chat-message.assistant .t-chat-message__base{padding-left:var(--td-spacer-2,32rpx);}
.t-chat-message.error .t-chat-message__base{padding-left:var(--td-spacer-2,32rpx);} .t-chat-message.error .t-chat-message__base{padding-left:var(--td-spacer-2,32rpx);}
.t-chat-message.error .t-chat-message__text--error{color:var(--td-error-color-6,#d54941);} .t-chat-message.error .t-chat-message__text--error{color:var(--td-error-color-6,#d54941);}
+1 -16
View File
@@ -1,21 +1,6 @@
/// <reference types="miniprogram-api-typings" />
/// <reference types="miniprogram-api-typings" />
import { SuperComponent } from '../common/src/index'; import { SuperComponent } from '../common/src/index';
export default class Dialog extends SuperComponent { export default class Dialog extends SuperComponent {
behaviors: WechatMiniprogram.Behavior.BehaviorIdentifier<{ behaviors: string[];
distanceTop: number;
}, {
usingCustomNavbar: {
type: BooleanConstructor;
value: false;
};
customNavbarHeight: {
type: NumberConstructor;
value: number;
};
}, {
calculateCustomNavbarDistanceTop(): void;
}, WechatMiniprogram.Component.BehaviorOption>[];
options: { options: {
multipleSlots: boolean; multipleSlots: boolean;
}; };
+1 -16
View File
@@ -1,21 +1,6 @@
/// <reference types="miniprogram-api-typings" />
/// <reference types="miniprogram-api-typings" />
import { ComponentsOptionsType, SuperComponent } from '../common/src/index'; import { ComponentsOptionsType, SuperComponent } from '../common/src/index';
export default class Drawer extends SuperComponent { export default class Drawer extends SuperComponent {
behaviors: WechatMiniprogram.Behavior.BehaviorIdentifier<{ behaviors: string[];
distanceTop: number;
}, {
usingCustomNavbar: {
type: BooleanConstructor;
value: false;
};
customNavbarHeight: {
type: NumberConstructor;
value: number;
};
}, {
calculateCustomNavbarDistanceTop(): void;
}, WechatMiniprogram.Component.BehaviorOption>[];
externalClasses: any[]; externalClasses: any[];
options: ComponentsOptionsType; options: ComponentsOptionsType;
properties: import("./type").TdDrawerProps; properties: import("./type").TdDrawerProps;
+1 -16
View File
@@ -1,21 +1,6 @@
/// <reference types="miniprogram-api-typings" />
/// <reference types="miniprogram-api-typings" />
import { SuperComponent } from '../common/src/index'; import { SuperComponent } from '../common/src/index';
export default class Fab extends SuperComponent { export default class Fab extends SuperComponent {
behaviors: WechatMiniprogram.Behavior.BehaviorIdentifier<{ behaviors: string[];
distanceTop: number;
}, {
usingCustomNavbar: {
type: BooleanConstructor;
value: false;
};
customNavbarHeight: {
type: NumberConstructor;
value: number;
};
}, {
calculateCustomNavbarDistanceTop(): void;
}, WechatMiniprogram.Component.BehaviorOption>[];
properties: import("./type").TdFabProps; properties: import("./type").TdFabProps;
externalClasses: string[]; externalClasses: string[];
data: { data: {
+1 -1
View File
@@ -1 +1 @@
import{__awaiter,__decorate}from"tslib";import{SuperComponent,wxComponent}from"../common/src/index";import config from"../common/config";import props from"./props";import{styles,addUnit,getRect}from"../common/utils";const{prefix:prefix}=config,name=`${prefix}-icon`;let Icon=class extends SuperComponent{constructor(){super(...arguments),this.externalClasses=[`${prefix}-class`],this.properties=props,this.data={componentPrefix:prefix,classPrefix:name,isImage:!1,iconStyle:void 0},this.observers={"name, color, size, style"(){this.setIconStyle()}},this.methods={onTap(t){this.triggerEvent("click",t.detail)},setIconStyle(){const{name:t,color:e,size:o,classPrefix:i}=this.data,s=-1!==t.indexOf("/"),n=addUnit(o),r=e?{color:e}:{},c=o?{"font-size":n}:{},a=Object.assign(Object.assign({},r),c);this.setData({isImage:s},()=>__awaiter(this,void 0,void 0,function*(){if(s){let t=n;t||(yield getRect(this,`.${i}`).then(e=>{t=addUnit(null==e?void 0:e.height)}).catch(()=>{})),a.width=t,a.height=t}this.setData({iconStyle:`${styles(a)}`})}))}}}};Icon=__decorate([wxComponent()],Icon);export default Icon; import{__awaiter,__decorate}from"tslib";import{SuperComponent,wxComponent}from"../common/src/index";import config from"../common/config";import props from"./props";import{styles,addUnit,getRect}from"../common/utils";const{prefix:prefix}=config,name=`${prefix}-icon`;let Icon=class extends SuperComponent{constructor(){super(...arguments),this.externalClasses=[`${prefix}-class`],this.properties=props,this.data={componentPrefix:prefix,classPrefix:name,isImage:!1,iconStyle:void 0},this.observers={"name, color, size, style"(){this.setIconStyle()}},this.methods={onTap(t){this.triggerEvent("click",t.detail)},setIconStyle(){const{name:t,color:e,size:o,classPrefix:i}=this.data,s=-1!==t.indexOf("/"),n=null!==o&&""!==o?addUnit(o):void 0,r=e?{color:e}:{},c=o?{"font-size":n}:{},a=Object.assign(Object.assign({},r),c);this.setData({isImage:s},()=>__awaiter(this,void 0,void 0,function*(){if(s){let t=n;t||(yield getRect(this,`.${i}`).then(e=>{t=addUnit(null==e?void 0:e.height)}).catch(()=>{})),a.width=t,a.height=t}this.setData({iconStyle:`${styles(a)}`})}))}}}};Icon=__decorate([wxComponent()],Icon);export default Icon;
File diff suppressed because it is too large Load Diff
+1 -3
View File
@@ -1,5 +1,3 @@
/// <reference types="miniprogram-api-typings" />
/// <reference types="miniprogram-api-typings" />
import { RelationsOptions, SuperComponent } from '../common/src/index'; import { RelationsOptions, SuperComponent } from '../common/src/index';
export default class Indexes extends SuperComponent { export default class Indexes extends SuperComponent {
externalClasses: string[]; externalClasses: string[];
@@ -18,7 +16,7 @@ export default class Indexes extends SuperComponent {
showTips: boolean; showTips: boolean;
}; };
relations: RelationsOptions; relations: RelationsOptions;
behaviors: WechatMiniprogram.Behavior.BehaviorIdentifier<WechatMiniprogram.Component.DataOption, WechatMiniprogram.Component.PropertyOption, WechatMiniprogram.Component.MethodOption, WechatMiniprogram.Component.BehaviorOption>[]; behaviors: string[];
timer: any; timer: any;
groupTop: any[]; groupTop: any[];
sidebar: any; sidebar: any;
+1 -1
View File
@@ -1,2 +1,2 @@
declare const _default: (funcName?: string) => WechatMiniprogram.Behavior.BehaviorIdentifier<WechatMiniprogram.Component.DataOption, WechatMiniprogram.Component.PropertyOption, WechatMiniprogram.Component.MethodOption, WechatMiniprogram.Component.BehaviorOption>; declare const _default: (funcName?: string) => string;
export default _default; export default _default;
@@ -1,8 +1,2 @@
/// <reference types="miniprogram-api-typings" /> declare const themeChangeBehavior: string;
/// <reference types="miniprogram-api-typings" />
declare const themeChangeBehavior: WechatMiniprogram.Behavior.BehaviorIdentifier<{
theme: string;
}, WechatMiniprogram.Component.PropertyOption, {
_initTheme(): void;
}, WechatMiniprogram.Component.BehaviorOption>;
export default themeChangeBehavior; export default themeChangeBehavior;
+1 -7
View File
@@ -1,8 +1,2 @@
/// <reference types="miniprogram-api-typings" /> declare const _default: string;
/// <reference types="miniprogram-api-typings" />
declare const _default: WechatMiniprogram.Behavior.BehaviorIdentifier<WechatMiniprogram.Component.DataOption, WechatMiniprogram.Component.PropertyOption, {
resetTouchStatus(): void;
touchStart(event: any): void;
touchMove(event: any): void;
}, WechatMiniprogram.Component.BehaviorOption>;
export default _default; export default _default;
+1 -31
View File
@@ -1,31 +1 @@
/// <reference types="miniprogram-api-typings" /> export default function transition(): string;
/// <reference types="miniprogram-api-typings" />
export default function transition(): WechatMiniprogram.Behavior.BehaviorIdentifier<{
transitionClass: string;
transitionDurations: number;
className: string;
realVisible: boolean;
}, {
visible: {
type: BooleanConstructor;
value: any;
observer: string;
};
appear: BooleanConstructor;
name: {
type: StringConstructor;
value: string;
};
durations: {
type: NumberConstructor;
optionalTypes: ArrayConstructor[];
};
}, {
watchVisible(curr: any, prev: any): void;
getDurations(): number[];
enter(): void;
entered(): void;
leave(): void;
leaved(): void;
onTransitionEnd(): void;
}, WechatMiniprogram.Component.BehaviorOption>;
@@ -1,17 +1,2 @@
/// <reference types="miniprogram-api-typings" /> declare const useCustomNavbarBehavior: string;
/// <reference types="miniprogram-api-typings" />
declare const useCustomNavbarBehavior: WechatMiniprogram.Behavior.BehaviorIdentifier<{
distanceTop: number;
}, {
usingCustomNavbar: {
type: BooleanConstructor;
value: false;
};
customNavbarHeight: {
type: NumberConstructor;
value: number;
};
}, {
calculateCustomNavbarDistanceTop(): void;
}, WechatMiniprogram.Component.BehaviorOption>;
export default useCustomNavbarBehavior; export default useCustomNavbarBehavior;
+1 -44
View File
@@ -1,53 +1,10 @@
/// <reference types="miniprogram-api-typings" />
/// <reference types="miniprogram-api-typings" />
import { SuperComponent } from '../common/src/index'; import { SuperComponent } from '../common/src/index';
import { TdOverlayProps } from './type'; import { TdOverlayProps } from './type';
export interface OverlayProps extends TdOverlayProps { export interface OverlayProps extends TdOverlayProps {
} }
export default class Overlay extends SuperComponent { export default class Overlay extends SuperComponent {
properties: TdOverlayProps; properties: TdOverlayProps;
behaviors: (WechatMiniprogram.Behavior.BehaviorIdentifier<{ behaviors: string[];
transitionClass: string;
transitionDurations: number;
className: string;
realVisible: boolean;
}, {
visible: {
type: BooleanConstructor;
value: any;
observer: string;
};
appear: BooleanConstructor;
name: {
type: StringConstructor;
value: string;
};
durations: {
type: NumberConstructor;
optionalTypes: ArrayConstructor[];
};
}, {
watchVisible(curr: any, prev: any): void;
getDurations(): number[];
enter(): void;
entered(): void;
leave(): void;
leaved(): void;
onTransitionEnd(): void;
}, WechatMiniprogram.Component.BehaviorOption> | WechatMiniprogram.Behavior.BehaviorIdentifier<{
distanceTop: number;
}, {
usingCustomNavbar: {
type: BooleanConstructor;
value: false;
};
customNavbarHeight: {
type: NumberConstructor;
value: number;
};
}, {
calculateCustomNavbarDistanceTop(): void;
}, WechatMiniprogram.Component.BehaviorOption>)[];
data: { data: {
prefix: string; prefix: string;
classPrefix: string; classPrefix: string;
+1 -16
View File
@@ -1,21 +1,6 @@
/// <reference types="miniprogram-api-typings" />
/// <reference types="miniprogram-api-typings" />
import { SuperComponent, RelationsOptions } from '../common/src/index'; import { SuperComponent, RelationsOptions } from '../common/src/index';
export default class Picker extends SuperComponent { export default class Picker extends SuperComponent {
behaviors: WechatMiniprogram.Behavior.BehaviorIdentifier<{ behaviors: string[];
distanceTop: number;
}, {
usingCustomNavbar: {
type: BooleanConstructor;
value: false;
};
customNavbarHeight: {
type: NumberConstructor;
value: number;
};
}, {
calculateCustomNavbarDistanceTop(): void;
}, WechatMiniprogram.Component.BehaviorOption>[];
properties: import("./type").TdPickerProps; properties: import("./type").TdPickerProps;
externalClasses: string[]; externalClasses: string[];
options: { options: {
+1 -3
View File
@@ -1,11 +1,9 @@
/// <reference types="miniprogram-api-typings" />
/// <reference types="miniprogram-api-typings" />
import { TdPopoverProps } from './type'; import { TdPopoverProps } from './type';
import { SuperComponent } from '../common/src/index'; import { SuperComponent } from '../common/src/index';
export interface PopoverProps extends TdPopoverProps { export interface PopoverProps extends TdPopoverProps {
} }
export default class Popover extends SuperComponent { export default class Popover extends SuperComponent {
behaviors: WechatMiniprogram.Behavior.BehaviorIdentifier<WechatMiniprogram.Component.DataOption, WechatMiniprogram.Component.PropertyOption, WechatMiniprogram.Component.MethodOption, WechatMiniprogram.Component.BehaviorOption>[]; behaviors: string[];
externalClasses: string[]; externalClasses: string[];
options: { options: {
multipleSlots: boolean; multipleSlots: boolean;
+1 -44
View File
@@ -1,52 +1,9 @@
/// <reference types="miniprogram-api-typings" />
/// <reference types="miniprogram-api-typings" />
import { TdPopupProps } from './type'; import { TdPopupProps } from './type';
import { SuperComponent } from '../common/src/index'; import { SuperComponent } from '../common/src/index';
export declare type PopupProps = TdPopupProps; export declare type PopupProps = TdPopupProps;
export default class Popup extends SuperComponent { export default class Popup extends SuperComponent {
externalClasses: string[]; externalClasses: string[];
behaviors: (WechatMiniprogram.Behavior.BehaviorIdentifier<{ behaviors: string[];
transitionClass: string;
transitionDurations: number;
className: string;
realVisible: boolean;
}, {
visible: {
type: BooleanConstructor;
value: any;
observer: string;
};
appear: BooleanConstructor;
name: {
type: StringConstructor;
value: string;
};
durations: {
type: NumberConstructor;
optionalTypes: ArrayConstructor[];
};
}, {
watchVisible(curr: any, prev: any): void;
getDurations(): number[];
enter(): void;
entered(): void;
leave(): void;
leaved(): void;
onTransitionEnd(): void;
}, WechatMiniprogram.Component.BehaviorOption> | WechatMiniprogram.Behavior.BehaviorIdentifier<{
distanceTop: number;
}, {
usingCustomNavbar: {
type: BooleanConstructor;
value: false;
};
customNavbarHeight: {
type: NumberConstructor;
value: number;
};
}, {
calculateCustomNavbarDistanceTop(): void;
}, WechatMiniprogram.Component.BehaviorOption>)[];
options: { options: {
multipleSlots: boolean; multipleSlots: boolean;
}; };
+1 -1
View File
@@ -1 +1 @@
<wxs src="./popup.wxs" module="popup"/><wxs src="../common/utils.wxs" module="_"/><view wx:if="{{realVisible}}" style="{{_._style([popup.getPopupStyles(zIndex, distanceTop, placement), style, customStyle])}}" class="{{_.cls(classPrefix, [placement])}} {{transitionClass}} class {{prefix}}-class" bind:transitionend="onTransitionEnd"><view data-prevention="{{preventScrollThrough || (overlayProps ? !!overlayProps.preventScrollThrough : false)}}" bind:touchmove="{{popup.onContentTouchMove}}" class="{{classPrefix}}__content {{prefix}}-class-content"><slot name="content"/><slot/><view class="{{classPrefix}}__close" bind:tap="handleClose"><t-icon name="close" wx:if="{{closeBtn}}" size="64rpx"/><slot name="close-btn" class="{{classPrefix}}-slot"/></view></view></view><t-overlay id="popup-overlay" wx:if="{{showOverlay}}" visible="{{visible}}" usingCustomNavbar="{{usingCustomNavbar}}" z-index="{{overlayProps && overlayProps.zIndex || 11000}}" duration="{{overlayProps && overlayProps.duration || 300}}" background-color="{{overlayProps && overlayProps.backgroundColor || ''}}" prevent-scroll-through="{{preventScrollThrough || (overlayProps ? !!overlayProps.preventScrollThrough : false)}}" bind:tap="handleOverlayClick" custom-style="{{overlayProps && overlayProps.style || ''}}"/> <wxs src="./popup.wxs" module="popup"/><wxs src="../common/utils.wxs" module="_"/><view wx:if="{{realVisible}}" style="{{_._style([popup.getPopupStyles(zIndex, distanceTop, placement, duration), style, customStyle])}}" class="{{_.cls(classPrefix, [placement])}} {{transitionClass}} class {{prefix}}-class" bind:transitionend="onTransitionEnd"><view data-prevention="{{preventScrollThrough || (overlayProps ? !!overlayProps.preventScrollThrough : false)}}" bind:touchmove="{{popup.onContentTouchMove}}" class="{{classPrefix}}__content {{prefix}}-class-content"><slot name="content"/><slot/><view class="{{classPrefix}}__close" bind:tap="handleClose"><t-icon name="close" wx:if="{{closeBtn}}" size="64rpx"/><slot name="close-btn" class="{{classPrefix}}-slot"/></view></view></view><t-overlay id="popup-overlay" wx:if="{{showOverlay}}" visible="{{visible}}" usingCustomNavbar="{{usingCustomNavbar}}" z-index="{{overlayProps && overlayProps.zIndex || 11000}}" duration="{{overlayProps && overlayProps.duration || duration || 300}}" background-color="{{overlayProps && overlayProps.backgroundColor || ''}}" prevent-scroll-through="{{preventScrollThrough || (overlayProps ? !!overlayProps.preventScrollThrough : false)}}" bind:tap="handleOverlayClick" custom-style="{{overlayProps && overlayProps.style || ''}}"/>
+4 -1
View File
@@ -1,8 +1,11 @@
function getPopupStyles(zIndex, distanceTop, placement) { function getPopupStyles(zIndex, distanceTop, placement, duration) {
var zIndexStyle = zIndex ? 'z-index:' + zIndex + ';' : ''; var zIndexStyle = zIndex ? 'z-index:' + zIndex + ';' : '';
if ((placement === 'top' || placement === 'left' || placement === 'right') && distanceTop) { if ((placement === 'top' || placement === 'left' || placement === 'right') && distanceTop) {
zIndexStyle = zIndexStyle + 'top:' + distanceTop + 'px;' + '--td-popup-distance-top:' + distanceTop + 'px;'; zIndexStyle = zIndexStyle + 'top:' + distanceTop + 'px;' + '--td-popup-distance-top:' + distanceTop + 'px;';
} }
if (duration) {
zIndexStyle = zIndexStyle + '--td-popup-transition:all ' + duration + 'ms ease;';
}
return zIndexStyle; return zIndexStyle;
} }
+1 -1
View File
@@ -23,5 +23,5 @@ export default class Search extends SuperComponent {
handleClear(): void; handleClear(): void;
onConfirm(e: any): void; onConfirm(e: any): void;
onActionClick(): void; onActionClick(): void;
onSelectResultItem(e: any): void; onSelectOption(e: any): void;
} }
+1 -1
View File
@@ -1 +1 @@
import{__decorate}from"tslib";import{SuperComponent,wxComponent}from"../common/src/index";import config from"../common/config";import props from"./props";import{getCharacterLength}from"../common/utils";const{prefix:prefix}=config,name=`${prefix}-search`;let Search=class extends SuperComponent{constructor(){super(...arguments),this.externalClasses=[`${prefix}-class`,`${prefix}-class-input-container`,`${prefix}-class-input`,`${prefix}-class-action`,`${prefix}-class-left`,`${prefix}-class-clear`],this.options={multipleSlots:!0},this.properties=props,this.observers={resultList(e){const{isSelected:t}=this.data;e.length?t?this.setData({isShowResultList:!1,isSelected:!1}):this.setData({isShowResultList:!0}):this.setData({isShowResultList:!1})},"clearTrigger, clearable, disabled, readonly"(){this.updateClearIconVisible()}},this.data={classPrefix:name,prefix:prefix,isShowResultList:!1,isSelected:!1,showClearIcon:!0}}updateClearIconVisible(e=!1){const{clearTrigger:t,disabled:s,readonly:i}=this.properties;s||i?this.setData({showClearIcon:!1}):this.setData({showClearIcon:e||"always"===String(t)})}onInput(e){let{value:t}=e.detail;const{maxcharacter:s}=this.properties;if(s&&"number"==typeof s&&s>0){const{characters:e}=getCharacterLength("maxcharacter",t,s);t=e}this.setData({value:t}),this.triggerEvent("change",{value:t})}onFocus(e){const{value:t}=e.detail;this.updateClearIconVisible(!0),this.triggerEvent("focus",{value:t})}onBlur(e){const{value:t}=e.detail;this.updateClearIconVisible(),this.triggerEvent("blur",{value:t})}handleClear(){this.setData({value:""}),this.triggerEvent("clear",{value:""}),this.triggerEvent("change",{value:""})}onConfirm(e){const{value:t}=e.detail;this.triggerEvent("submit",{value:t})}onActionClick(){this.triggerEvent("action-click")}onSelectResultItem(e){const{index:t}=e.currentTarget.dataset,s=this.properties.resultList[t];this.setData({value:s,isSelected:!0}),this.triggerEvent("change",{value:s}),this.triggerEvent("selectresult",{index:t,item:s})}};Search=__decorate([wxComponent()],Search);export default Search; import{__decorate}from"tslib";import{SuperComponent,wxComponent}from"../common/src/index";import config from"../common/config";import props from"./props";import{getCharacterLength}from"../common/utils";const{prefix:prefix}=config,name=`${prefix}-search`;let Search=class extends SuperComponent{constructor(){super(...arguments),this.externalClasses=[`${prefix}-class`,`${prefix}-class-input-container`,`${prefix}-class-input`,`${prefix}-class-action`,`${prefix}-class-left`,`${prefix}-class-clear`],this.options={multipleSlots:!0},this.properties=props,this.observers={resultList(e){const{isSelected:t}=this.data;e.length?t?this.setData({isShowResultList:!1,isSelected:!1}):this.setData({isShowResultList:!0}):this.setData({isShowResultList:!1})},"clearTrigger, clearable, disabled, readonly"(){this.updateClearIconVisible()}},this.data={classPrefix:name,prefix:prefix,isShowResultList:!1,isSelected:!1,showClearIcon:!0}}updateClearIconVisible(e=!1){const{clearTrigger:t,disabled:s,readonly:i}=this.properties;s||i?this.setData({showClearIcon:!1}):this.setData({showClearIcon:e||"always"===String(t)})}onInput(e){let{value:t}=e.detail;const{maxcharacter:s}=this.properties;if(s&&"number"==typeof s&&s>0){const{characters:e}=getCharacterLength("maxcharacter",t,s);t=e}this.setData({value:t}),this.triggerEvent("change",{value:t,trigger:"input-change"})}onFocus(e){const{value:t}=e.detail;this.updateClearIconVisible(!0),this.triggerEvent("focus",{value:t})}onBlur(e){const{value:t}=e.detail;this.updateClearIconVisible(),this.triggerEvent("blur",{value:t})}handleClear(){this.setData({value:""}),this.triggerEvent("clear",{value:""}),this.triggerEvent("change",{value:"",trigger:"clear"})}onConfirm(e){const{value:t}=e.detail;this.triggerEvent("submit",{value:t})}onActionClick(){this.triggerEvent("action-click")}onSelectOption(e){const{index:t}=e.currentTarget.dataset,s=this.properties.resultList[t];this.setData({value:s,isSelected:!0}),this.triggerEvent("change",{value:s,trigger:"option-click"})}};Search=__decorate([wxComponent()],Search);export default Search;
+1 -1
View File
@@ -1 +1 @@
<wxs src="../common/utils.wxs" module="_"/><wxs src="./search.wxs" module="_this"></wxs><view style="{{_._style([style, customStyle])}}" class="class {{classPrefix}} {{prefix}}-class"><view class="{{classPrefix}}__input-box {{prefix}}-{{focus ? 'is-focused' : 'not-focused'}} {{classPrefix}}__input-box--{{center ? 'center' : ''}} {{classPrefix}}__input-box--{{shape}} {{prefix}}-class-input-container"><t-icon wx:if="{{leftIcon}}" name="{{leftIcon}}" class="{{prefix}}-icon {{prefix}}-class-left" aria-hidden="{{true}}"/><slot wx:else name="left-icon"/><input type="{{type}}" name="input" maxlength="{{maxlength}}" disabled="{{disabled || readonly}}" class="{{prefix}}-input__keyword {{prefix}}-class-input {{ disabled ? prefix + '-input--disabled' : ''}}" focus="{{focus}}" value="{{value}}" confirm-type="{{confirmType}}" confirm-hold="{{confirmHold}}" cursor="{{cursor}}" adjust-position="{{adjustPosition}}" always-embed="{{alwaysEmbed}}" selection-start="{{selectionStart}}" selection-end="{{selectionEnd}}" hold-keyboard="{{holdKeyboard}}" cursor-spacing="{{cursorSpacing}}" cursor-color="{{cursorColor}}" placeholder="{{placeholder}}" placeholder-style="{{placeholderStyle}}" placeholder-class="{{placeholderClass}} {{classPrefix}}__placeholder {{classPrefix}}__placeholder--{{center ? 'center': 'normal'}}" bind:input="onInput" bind:focus="onFocus" bind:blur="onBlur" bind:confirm="onConfirm"/><view wx:if="{{value !=='' && clearable && showClearIcon}}" class="{{classPrefix}}__clear hotspot-expanded {{prefix}}-class-clear" catch:touchstart="handleClear" aria-role="button" aria-label="清除"><t-icon name="close-circle-filled" size="inherit" color="inherit"/></view></view><view wx:if="{{action}}" class="{{classPrefix}}__search-action {{prefix}}-class-action" catch:tap="onActionClick" aria-role="button">{{action}}</view><slot wx:else name="action"/></view><view wx:if="{{isShowResultList && !isSelected}}" class="{{classPrefix}}__result-list" aria-role="listbox"><t-cell wx:for="{{resultList}}" wx:key="index" data-index="{{index}}" class="{{classPrefix}}__result-item" hover bind:tap="onSelectResultItem" aria-role="option"><rich-text slot="title" nodes="{{_this.highLight(item, value)}}"></rich-text></t-cell></view> <wxs src="../common/utils.wxs" module="_"/><wxs src="./search.wxs" module="_this"></wxs><view style="{{_._style([style, customStyle])}}" class="class {{classPrefix}} {{prefix}}-class"><view class="{{classPrefix}}__input-box {{prefix}}-{{focus ? 'is-focused' : 'not-focused'}} {{classPrefix}}__input-box--{{center ? 'center' : ''}} {{classPrefix}}__input-box--{{shape}} {{prefix}}-class-input-container"><t-icon wx:if="{{leftIcon}}" name="{{leftIcon}}" class="{{prefix}}-icon {{prefix}}-class-left" aria-hidden="{{true}}"/><slot wx:else name="left-icon"/><input type="{{type}}" name="input" maxlength="{{maxlength}}" disabled="{{disabled || readonly}}" class="{{prefix}}-input__keyword {{prefix}}-class-input {{ disabled ? prefix + '-input--disabled' : ''}}" focus="{{focus}}" value="{{value}}" confirm-type="{{confirmType}}" confirm-hold="{{confirmHold}}" cursor="{{cursor}}" adjust-position="{{adjustPosition}}" always-embed="{{alwaysEmbed}}" selection-start="{{selectionStart}}" selection-end="{{selectionEnd}}" hold-keyboard="{{holdKeyboard}}" cursor-spacing="{{cursorSpacing}}" cursor-color="{{cursorColor}}" placeholder="{{placeholder}}" placeholder-style="{{placeholderStyle}}" placeholder-class="{{placeholderClass}} {{classPrefix}}__placeholder {{classPrefix}}__placeholder--{{center ? 'center': 'normal'}}" bind:input="onInput" bind:focus="onFocus" bind:blur="onBlur" bind:confirm="onConfirm"/><view wx:if="{{value !=='' && clearable && showClearIcon}}" class="{{classPrefix}}__clear hotspot-expanded {{prefix}}-class-clear" catch:touchstart="handleClear" aria-role="button" aria-label="清除"><t-icon name="close-circle-filled" size="inherit" color="inherit"/></view></view><view wx:if="{{action}}" class="{{classPrefix}}__search-action {{prefix}}-class-action" catch:tap="onActionClick" aria-role="button">{{action}}</view><slot wx:else name="action"/></view><view wx:if="{{isShowResultList && !isSelected}}" class="{{classPrefix}}__result-list" aria-role="listbox"><t-cell wx:for="{{resultList}}" wx:key="index" data-index="{{index}}" class="{{classPrefix}}__result-item" hover bind:tap="onSelectOption" aria-role="option"><rich-text slot="title" nodes="{{_this.highLight(item, value)}}"></rich-text></t-cell></view>
+1 -3
View File
@@ -1,5 +1,3 @@
/// <reference types="miniprogram-api-typings" />
/// <reference types="miniprogram-api-typings" />
import { SuperComponent } from '../common/src/index'; import { SuperComponent } from '../common/src/index';
import type { TdStickyProps } from './type'; import type { TdStickyProps } from './type';
export interface StickyProps extends TdStickyProps { export interface StickyProps extends TdStickyProps {
@@ -7,7 +5,7 @@ export interface StickyProps extends TdStickyProps {
export default class Sticky extends SuperComponent { export default class Sticky extends SuperComponent {
externalClasses: string[]; externalClasses: string[];
properties: TdStickyProps; properties: TdStickyProps;
behaviors: WechatMiniprogram.Behavior.BehaviorIdentifier<WechatMiniprogram.Component.DataOption, WechatMiniprogram.Component.PropertyOption, WechatMiniprogram.Component.MethodOption, WechatMiniprogram.Component.BehaviorOption>[]; behaviors: string[];
observers: { observers: {
'offsetTop, disabled, container'(): void; 'offsetTop, disabled, container'(): void;
}; };
+1 -7
View File
@@ -1,5 +1,3 @@
/// <reference types="miniprogram-api-typings" />
/// <reference types="miniprogram-api-typings" />
import { RelationsOptions, SuperComponent } from '../common/src/index'; import { RelationsOptions, SuperComponent } from '../common/src/index';
import { TdTabsProps } from './type'; import { TdTabsProps } from './type';
export interface TabsProps extends TdTabsProps { export interface TabsProps extends TdTabsProps {
@@ -8,11 +6,7 @@ export default class Tabs extends SuperComponent {
options: { options: {
pureDataPattern: RegExp; pureDataPattern: RegExp;
}; };
behaviors: WechatMiniprogram.Behavior.BehaviorIdentifier<WechatMiniprogram.Component.DataOption, WechatMiniprogram.Component.PropertyOption, { behaviors: string[];
resetTouchStatus(): void;
touchStart(event: any): void;
touchMove(event: any): void;
}, WechatMiniprogram.Component.BehaviorOption>[];
externalClasses: string[]; externalClasses: string[];
relations: RelationsOptions; relations: RelationsOptions;
properties: TdTabsProps; properties: TdTabsProps;
+1 -44
View File
@@ -1,6 +1,4 @@
/// <reference types="node" /> /// <reference types="node" />
/// <reference types="miniprogram-api-typings" />
/// <reference types="miniprogram-api-typings" />
import { SuperComponent } from '../common/src/index'; import { SuperComponent } from '../common/src/index';
import { ToastOptionsType } from './index'; import { ToastOptionsType } from './index';
declare type Timer = NodeJS.Timeout | null; declare type Timer = NodeJS.Timeout | null;
@@ -9,48 +7,7 @@ export default class Toast extends SuperComponent {
options: { options: {
multipleSlots: boolean; multipleSlots: boolean;
}; };
behaviors: (WechatMiniprogram.Behavior.BehaviorIdentifier<{ behaviors: string[];
transitionClass: string;
transitionDurations: number;
className: string;
realVisible: boolean;
}, {
visible: {
type: BooleanConstructor;
value: any;
observer: string;
};
appear: BooleanConstructor;
name: {
type: StringConstructor;
value: string;
};
durations: {
type: NumberConstructor;
optionalTypes: ArrayConstructor[];
};
}, {
watchVisible(curr: any, prev: any): void;
getDurations(): number[];
enter(): void;
entered(): void;
leave(): void;
leaved(): void;
onTransitionEnd(): void;
}, WechatMiniprogram.Component.BehaviorOption> | WechatMiniprogram.Behavior.BehaviorIdentifier<{
distanceTop: number;
}, {
usingCustomNavbar: {
type: BooleanConstructor;
value: false;
};
customNavbarHeight: {
type: NumberConstructor;
value: number;
};
}, {
calculateCustomNavbarDistanceTop(): void;
}, WechatMiniprogram.Component.BehaviorOption>)[];
hideTimer: Timer; hideTimer: Timer;
data: { data: {
prefix: string; prefix: string;
@@ -1,37 +1,7 @@
/// <reference types="miniprogram-api-typings" />
/// <reference types="miniprogram-api-typings" />
import { SuperComponent } from '../common/src/index'; import { SuperComponent } from '../common/src/index';
export default class Transition extends SuperComponent { export default class Transition extends SuperComponent {
externalClasses: string[]; externalClasses: string[];
behaviors: WechatMiniprogram.Behavior.BehaviorIdentifier<{ behaviors: string[];
transitionClass: string;
transitionDurations: number;
className: string;
realVisible: boolean;
}, {
visible: {
type: BooleanConstructor;
value: any;
observer: string;
};
appear: BooleanConstructor;
name: {
type: StringConstructor;
value: string;
};
durations: {
type: NumberConstructor;
optionalTypes: ArrayConstructor[];
};
}, {
watchVisible(curr: any, prev: any): void;
getDurations(): number[];
enter(): void;
entered(): void;
leave(): void;
leaved(): void;
onTransitionEnd(): void;
}, WechatMiniprogram.Component.BehaviorOption>[];
data: { data: {
classPrefix: string; classPrefix: string;
}; };
+3 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "tdesign-miniprogram", "name": "tdesign-miniprogram",
"version": "1.12.2", "version": "1.12.3",
"title": "tdesign-miniprogram", "title": "tdesign-miniprogram",
"description": "TDesign Component for miniprogram", "description": "TDesign Component for miniprogram",
"main": "miniprogram_dist/index.js", "main": "miniprogram_dist/index.js",
@@ -18,7 +18,8 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/Tencent/tdesign-miniprogram" "url": "https://github.com/Tencent/tdesign-miniprogram",
"directory": "packages/tdesign-miniprogram"
}, },
"homepage": "https://tdesign.tencent.com/miniprogram", "homepage": "https://tdesign.tencent.com/miniprogram",
"bugs": { "bugs": {
+3 -3
View File
@@ -13,9 +13,9 @@
} }
}, },
"node_modules/tdesign-miniprogram": { "node_modules/tdesign-miniprogram": {
"version": "1.12.2", "version": "1.12.3",
"resolved": "https://registry.npmjs.org/tdesign-miniprogram/-/tdesign-miniprogram-1.12.2.tgz", "resolved": "https://registry.npmjs.org/tdesign-miniprogram/-/tdesign-miniprogram-1.12.3.tgz",
"integrity": "sha512-ZpOdwonT26RRCK/FWbg9tR2lAJ54Hb4PAdyTWu8URWkbKOmSQhn0JCwCtWWRofKbyWCPsCn5NqljobaGh5VCMg==", "integrity": "sha512-F4nMv/ph3yyq9bO4RrJuB9x9VWyKIN6lV1HqFaV4AsR0cpDoBYtGYLPOFejvj0MYDSntSHLMVe1nm0fqsXUaUQ==",
"license": "MIT" "license": "MIT"
} }
} }
+10 -2
View File
@@ -12,7 +12,8 @@ Page({
isLoading: false, isLoading: false,
current: 1, current: 1,
pageSize: 10, pageSize: 10,
hasMore: true hasMore: true,
userInfo: null
}, },
onLoad() { onLoad() {
@@ -23,6 +24,13 @@ Page({
if (typeof this.getTabBar === 'function' && this.getTabBar()) { if (typeof this.getTabBar === 'function' && this.getTabBar()) {
this.getTabBar().setData({ selected: 2 }); this.getTabBar().setData({ selected: 2 });
} }
// Update user info for header
const app = getApp();
const info = app.globalData.userInfo || wx.getStorageSync('userInfo');
if (info) {
this.setData({ userInfo: info });
}
}, },
// Called by create post page // Called by create post page
@@ -67,7 +75,7 @@ Page({
return { return {
id: item.id, id: item.id,
user: publisher.nickName || publisher.name || '花友', user: publisher.nickName || publisher.name || '花友',
avatar: avatarObj.url || '/assets/default_avatar.png', avatar: avatarObj.url,
content: item.content, content: item.content,
images: (item.imgList || []).map(img => img.url), images: (item.imgList || []).map(img => img.url),
time: item.createdAtStr || '刚刚', time: item.createdAtStr || '刚刚',
+3 -5
View File
@@ -3,10 +3,8 @@
<!-- Header with User Info --> <!-- Header with User Info -->
<view class="community-header"> <view class="community-header">
<view class="user-info"> <view class="user-info">
<view class="user-avatar"> <image class="header-avatar" src="{{userInfo.avatar && userInfo.avatar.url ? userInfo.avatar.url : userInfo.avatar}}" mode="aspectFill" />
<text>我</text> <text class="user-name">{{userInfo.nickname || userInfo.name || '花友'}}</text>
</view>
<text class="user-name">我的花园</text>
</view> </view>
</view> </view>
@@ -31,7 +29,7 @@
<text class="post-text">{{item.content}}</text> <text class="post-text">{{item.content}}</text>
<!-- Image Grid --> <!-- Image Grid -->
<view wx:if="{{item.images.length > 0}}" class="post-images-grid grid-{{item.images.length === 1 ? '1' : (item.images.length <= 4 ? '2' : '3')}}"> <view wx:if="{{item.images && item.images.length > 0}}" class="post-images-grid grid-{{item.images.length === 1 ? '1' : (item.images.length === 2 || item.images.length === 4 ? '2' : '3')}}">
<view wx:for="{{item.images}}" wx:for-item="img" wx:key="*this" class="post-image-item" catchtap="previewImage" data-url="{{img}}" data-urls="{{item.images}}"> <view wx:for="{{item.images}}" wx:for-item="img" wx:key="*this" class="post-image-item" catchtap="previewImage" data-url="{{img}}" data-urls="{{item.images}}">
<image <image
src="{{img}}" src="{{img}}"
+14 -15
View File
@@ -29,17 +29,12 @@ page {
gap: 20rpx; gap: 20rpx;
} }
.community-header .user-avatar { .community-header .header-avatar {
width: 80rpx; width: 80rpx;
height: 80rpx; height: 80rpx;
background: linear-gradient(135deg, #E8F5E9, #C8E6C9);
color: #558B2F;
border-radius: 16rpx; border-radius: 16rpx;
display: flex; background: #f5f5f5;
align-items: center; object-fit: cover;
justify-content: center;
font-weight: 700;
font-size: 32rpx;
} }
.community-header .user-name { .community-header .user-name {
@@ -135,31 +130,35 @@ page {
/* Single Image */ /* Single Image */
.grid-1 { .grid-1 {
display: flex; display: flex;
width: 70%; width: auto;
max-width: 70%;
} }
.grid-1 .post-image-item { .grid-1 .post-image-item {
width: 100%; width: 100%;
border-radius: 16rpx; aspect-ratio: 16 / 9;
border-radius: 8rpx;
overflow: hidden;
} }
.grid-1 .post-image-item t-image { .grid-1 .post-image-item image {
border-radius: 16rpx; border-radius: 8rpx;
} }
/* 2-4 Images (2 columns) */ /* 2 or 4 Images (2 columns) */
.grid-2 { .grid-2 {
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
width: 75%; width: 500rpx; /* Constrain width for 2 columns to look like partial grid */
} }
.grid-2 .post-image-item { .grid-2 .post-image-item {
aspect-ratio: 1; aspect-ratio: 1;
} }
/* 5+ Images (3 columns) */ /* 3, 5+ Images (3 columns) */
.grid-3 { .grid-3 {
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
width: auto; /* Full available width */
} }
.grid-3 .post-image-item { .grid-3 .post-image-item {
+1 -1
View File
@@ -1,5 +1,5 @@
// pages/garden/add/index.js // pages/garden/add/index.js
import { MOCK_PLANTS, CARE_TASK_ICONS } from '../../../utils/mockData'; import { CARE_TASK_ICONS } from '../../../utils/mockData';
import request from '../../../utils/request'; import request from '../../../utils/request';
import { requestSubscription, checkSubscriptionSettings } from '../../../utils/subscribe'; import { requestSubscription, checkSubscriptionSettings } from '../../../utils/subscribe';
+13
View File
@@ -126,5 +126,18 @@ Page({
if (!this.data.isLastPage && !this.data.isLoading) { if (!this.data.isLastPage && !this.data.isLoading) {
this.loadPlants(false); this.loadPlants(false);
} }
},
onShareAppMessage() {
return {
title: '我的植物花园 - Sundynix Plant',
path: '/pages/garden/index'
};
},
onShareTimeline() {
return {
title: '我的植物花园 - Sundynix Plant'
};
} }
}) })
+6
View File
@@ -8,6 +8,12 @@
</view> </view>
<text class="subtitle">今天也要好好照顾它们哦</text> <text class="subtitle">今天也要好好照顾它们哦</text>
</view> </view>
<view class="share-btn-wrap">
<button class="share-capsule" open-type="share">
<t-icon name="share" size="32rpx" color="#558B2F" />
<text>推荐给好友</text>
</button>
</view>
</view> </view>
<view class="banner-container"> <view class="banner-container">
+33
View File
@@ -40,6 +40,39 @@
font-weight: 500; font-weight: 500;
} }
.page-header {
position: relative;
}
.share-btn-wrap {
position: absolute;
top: 40rpx;
right: 40rpx;
z-index: 20;
}
.share-capsule {
display: flex !important;
align-items: center;
gap: 8rpx;
background: #F1F8E9 !important; /* Lighter background */
border-radius: 32rpx;
padding: 12rpx 24rpx !important;
margin: 0 !important;
border: 1px solid rgba(85, 139, 47, 0.2) !important;
box-shadow: 0 4rpx 12rpx rgba(85, 139, 47, 0.08); /* subtle shadow */
font-size: 24rpx;
font-weight: 600;
color: #558B2F;
line-height: normal !important;
width: auto !important;
min-height: 0 !important;
}
.share-capsule::after {
border: none !important;
}
.banner-container { .banner-container {
margin: 0 40rpx 24rpx; margin: 0 40rpx 24rpx;
height: 220rpx; height: 220rpx;
+103
View File
@@ -0,0 +1,103 @@
import request from '../../../utils/request';
Page({
data: {
plantId: '',
recordType: 'growth',
content: '',
image: ''
},
onLoad(options) {
if (options.plantId) {
this.setData({ plantId: options.plantId });
}
},
setRecordType(e) {
const type = e.currentTarget.dataset.type;
this.setData({ recordType: type });
},
onContentInput(e) {
this.setData({ content: e.detail.value });
},
handleChooseImage() {
wx.chooseMedia({
count: 1,
mediaType: ['image'],
sourceType: ['album', 'camera'],
success: (res) => {
this.setData({
image: res.tempFiles[0].tempFilePath
});
}
});
},
handleRemoveImage() {
this.setData({ image: '' });
},
async handleAddRecord() {
if (!this.data.content.trim()) {
wx.showToast({ title: '请填写备注', icon: 'none' });
return;
}
if (!this.data.plantId) {
wx.showToast({ title: '缺少植物ID', icon: 'none' });
return;
}
wx.showLoading({ title: '保存中...', mask: true });
try {
let ossIds = [];
// 1. Upload Image if exists
if (this.data.image) {
const uploadRes = await request.upload(this.data.image);
// Correctly extract ID from nested 'file' object based on API response
if (uploadRes && uploadRes.file && uploadRes.file.id) {
ossIds.push(uploadRes.file.id);
} else if (uploadRes && uploadRes.id) {
// Fallback just in case
ossIds.push(uploadRes.id);
} else {
console.warn('Upload response structure mismatch:', uploadRes);
}
}
// 2. Prepare payload
const mapTitle = { growth: '生长记录', repot: '换盆记录', pest: '病虫害记录', other: '日常记录' };
const title = mapTitle[this.data.recordType] || '日常记录';
const payload = {
plantId: this.data.plantId,
ossIds: ossIds,
name: title,
tag: this.data.recordType,
desc: this.data.content.substring(0, 100),
content: this.data.content
};
// 3. Call Add API
await request.post('/plant/growth/add', payload);
wx.hideLoading();
wx.showToast({ title: '保存成功', icon: 'success' });
// 4. Navigate back, detail page will refresh in onShow
setTimeout(() => {
wx.navigateBack();
}, 1000);
} catch (err) {
wx.hideLoading();
console.error('Add record failed', err);
}
}
});
@@ -0,0 +1,11 @@
{
"navigationBarTitleText": "添加成长记录",
"navigationBarBackgroundColor": "#FFFFFF",
"navigationBarTextStyle": "black",
"backgroundColor": "#F4F6F0",
"usingComponents": {
"t-icon": "tdesign-miniprogram/icon/icon",
"t-image": "tdesign-miniprogram/image/image",
"t-button": "tdesign-miniprogram/button/button"
}
}
@@ -0,0 +1,61 @@
<view class="growth-record-page">
<view class="form-container">
<!-- Record Type -->
<view class="form-group">
<text class="form-label">记录类型</text>
<view class="chip-group">
<view class="chip {{recordType === 'growth' ? 'active' : ''}}" bindtap="setRecordType" data-type="growth">
<t-icon name="thumb-up" size="32rpx" />
<text>生长</text>
</view>
<view class="chip {{recordType === 'repot' ? 'active' : ''}}" bindtap="setRecordType" data-type="repot">
<t-icon name="swap" size="32rpx" />
<text>换盆</text>
</view>
<view class="chip {{recordType === 'pest' ? 'active' : ''}}" bindtap="setRecordType" data-type="pest">
<t-icon name="error-circle" size="32rpx" />
<text>病虫害</text>
</view>
<view class="chip {{recordType === 'other' ? 'active' : ''}}" bindtap="setRecordType" data-type="other">
<t-icon name="file" size="32rpx" />
<text>其他</text>
</view>
</view>
</view>
<!-- Content -->
<view class="form-group">
<text class="form-label">备注</text>
<textarea
class="form-textarea"
placeholder="记录这一刻的变化..."
value="{{content}}"
bindinput="onContentInput"
maxlength="500"
auto-height
/>
</view>
<!-- Image Upload -->
<view class="form-group">
<text class="form-label">添加照片</text>
<view class="record-image-upload">
<view wx:if="{{image}}" class="uploaded-image-box">
<t-image src="{{image}}" mode="aspectFill" width="200rpx" height="200rpx" shape="round" />
<view class="remove-img-btn" bindtap="handleRemoveImage">
<t-icon name="close" size="32rpx" color="#FFF" />
</view>
</view>
<view wx:else class="upload-add-btn" bindtap="handleChooseImage">
<t-icon name="add" size="64rpx" color="#999" />
</view>
</view>
</view>
</view>
<view class="footer-action">
<view class="submit-btn" bindtap="handleAddRecord">
<text>保存记录</text>
</view>
</view>
</view>
+159
View File
@@ -0,0 +1,159 @@
/* pages/plant-detail/growth-record/index.wxss */
page {
background: #F4F6F0;
}
.growth-record-page {
padding: 32rpx;
padding-bottom: 160rpx;
}
.form-container {
background: white;
border-radius: 32rpx;
padding: 32rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.03);
}
.form-group {
margin-bottom: 48rpx;
}
.form-group:last-child {
margin-bottom: 0;
}
.form-label {
display: block;
font-size: 30rpx;
font-weight: 600;
color: #333;
margin-bottom: 24rpx;
}
/* Chip Styles */
.chip-group {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
}
.chip {
padding: 20rpx 32rpx;
background: #F5F5F5;
border-radius: 40rpx;
font-size: 28rpx;
color: #666;
display: flex;
align-items: center;
gap: 12rpx;
border: 2rpx solid transparent;
transition: all 0.2s;
}
.chip.active {
background: #E8F5E9;
color: #558B2F;
border-color: #558B2F;
font-weight: 600;
}
.chip:active {
transform: scale(0.96);
}
/* Textarea */
.form-textarea {
width: 100%;
min-height: 200rpx;
padding: 24rpx;
border: 2rpx solid #e0e0e0;
border-radius: 24rpx;
font-size: 30rpx;
box-sizing: border-box;
background: #FAFAFA;
line-height: 1.6;
}
.form-textarea:focus {
background: white;
border-color: #558B2F;
box-shadow: 0 0 0 6rpx rgba(85, 139, 47, 0.1);
}
/* Image Upload */
.record-image-upload {
display: flex;
flex-wrap: wrap;
gap: 24rpx;
}
.upload-add-btn {
width: 200rpx;
height: 200rpx;
background: #FAFAFA;
border: 2rpx dashed #d9d9d9;
border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
}
.upload-add-btn:active {
background: #F0F0F0;
border-color: #558B2F;
}
.uploaded-image-box {
position: relative;
width: 200rpx;
height: 200rpx;
}
.remove-img-btn {
position: absolute;
top: -16rpx;
right: -16rpx;
width: 44rpx;
height: 44rpx;
background: rgba(0, 0, 0, 0.5);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
backdrop-filter: blur(4rpx);
}
/* Footer Action */
.footer-action {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 32rpx;
background: white;
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.05);
z-index: 100;
padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
}
.submit-btn {
width: 100%;
height: 96rpx;
background: linear-gradient(135deg, #689F38, #558B2F);
border-radius: 48rpx;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
font-weight: 600;
box-shadow: 0 8rpx 24rpx rgba(85, 139, 47, 0.3);
}
.submit-btn:active {
transform: scale(0.98);
filter: brightness(0.95);
}
+39 -72
View File
@@ -5,7 +5,7 @@ Page({
data: { data: {
currentPlant: null, currentPlant: null,
activeImageIndex: 0, activeImageIndex: 0,
activeTab: 'care', activeTab: 'info',
careLogs: [], careLogs: [],
displayCareLogs: [], displayCareLogs: [],
displayCareLimit: 5, displayCareLimit: 5,
@@ -51,22 +51,46 @@ Page({
return { ...cp, taskIcon: iconObj }; return { ...cp, taskIcon: iconObj };
}); });
// Calculate days planted and format date
let adoptionDate = '未知';
let daysPlanted = 0;
if (plant.plantTime) {
const start = new Date(plant.plantTime);
const now = new Date();
const diffTime = now - start;
if (diffTime > 0) {
daysPlanted = Math.floor(diffTime / (1000 * 60 * 60 * 24));
}
adoptionDate = plant.plantTime.split('T')[0];
}
this.setData({ this.setData({
currentPlant: { currentPlant: {
...plant, ...plant,
location: plant.placement || '',
adoptionDate: adoptionDate,
daysPlanted: daysPlanted,
careSchedule: carePlans careSchedule: carePlans
}, },
swiperImages: swiperImages, swiperImages: swiperImages,
// Map logs and records directly from plant detail response // Map logs and records directly from plant detail response
careLogs: this.processLogs(plant.careRecords || []), careLogs: this.processLogs(plant.careRecords || []),
records: (plant.growthRecords || plant.recordList || []).map(item => ({ records: (plant.growthRecords || plant.recordList || []).map(item => {
// Extract image URL safely
let imageUrl = '';
if (item.imgList && item.imgList.length > 0) {
imageUrl = item.imgList[0].url;
}
return {
id: item.id, id: item.id,
date: item.createdAtStr ? item.createdAtStr.split(' ')[0] : '', date: item.createdAtStr ? item.createdAtStr.split(' ')[0] : '',
type: item.recordType || 'growth', type: item.tag || 'growth',
title: item.title, title: item.name || '成长记录',
content: item.content, content: item.content || item.desc || '',
image: (item.imgList && item.imgList.length > 0) ? item.imgList[0].url : '' image: imageUrl
})) };
})
}); });
this.updateDisplayLogs(); this.updateDisplayLogs();
@@ -203,78 +227,21 @@ Page({
}, },
// Growth Record Logic // Growth Record Logic
openGrowthModal() {
this.setData({
showGrowthModal: true,
newRecordContent: '',
newRecordType: 'growth',
newRecordImage: ''
});
},
onGrowthPopupVisibleChange(e) { this.setData({ showGrowthModal: e.detail.visible }); },
closeGrowthModal() { this.setData({ showGrowthModal: false }); },
setRecordType(e) {
const type = e.currentTarget.dataset.type;
if (e.detail.checked) {
this.setData({ newRecordType: type });
}
},
setRecordTypeByTap(e) {
const type = e.currentTarget.dataset.type;
this.setData({ newRecordType: type });
},
onRecordContentInput(e) { this.setData({ newRecordContent: e.detail.value }); },
handleChooseRecordImage() {
wx.chooseMedia({
count: 1,
mediaType: ['image'],
sourceType: ['album', 'camera'],
success: (res) => {
this.setData({
newRecordImage: res.tempFiles[0].tempFilePath
});
}
});
},
handleRemoveRecordImage() {
this.setData({ newRecordImage: '' });
},
handlePreviewRecordImage(e) { handlePreviewRecordImage(e) {
const src = e.currentTarget.dataset.src; const src = e.currentTarget.dataset.src;
const fullPath = (src.indexOf('http') === 0 || src.indexOf('wxfile') === 0) ? src : `/assets/${src}`; if (!src) return;
wx.previewImage({ wx.previewImage({
current: fullPath, current: src,
urls: [fullPath] urls: [src]
}); });
}, },
handleAddRecord() { openGrowthModal() {
if (!this.data.newRecordContent.trim()) return; if (this.data.currentPlant && this.data.currentPlant.id) {
wx.navigateTo({
const mapTitle = { growth: '生长记录', repot: '换盆记录', pest: '病虫害记录', other: '日常记录' }; url: `/pages/plant-detail/growth-record/index?plantId=${this.data.currentPlant.id}`
const now = new Date();
const dateStr = `${now.getFullYear()}-${(now.getMonth() + 1).toString().padStart(2, '0')}-${now.getDate().toString().padStart(2, '0')}`;
const record = {
id: Date.now().toString(),
date: dateStr,
type: this.data.newRecordType,
title: mapTitle[this.data.newRecordType],
content: this.data.newRecordContent,
image: this.data.newRecordImage
};
this.setData({
records: [record, ...this.data.records],
showGrowthModal: false
}); });
this.updateDisplayRecords();
wx.showToast({ title: '记录成功', icon: 'success' });
} }
},
}) })
+62 -119
View File
@@ -32,14 +32,69 @@
<view class="detail-content"> <view class="detail-content">
<!-- Custom Tabs --> <!-- Custom Tabs -->
<view class="pd-tabs"> <view class="pd-tabs">
<view class="pd-tab-btn {{activeTab === 'info' ? 'active' : ''}}" bindtap="switchTab" data-tab="info">
基础档案
</view>
<view class="pd-tab-btn {{activeTab === 'care' ? 'active' : ''}}" bindtap="switchTab" data-tab="care"> <view class="pd-tab-btn {{activeTab === 'care' ? 'active' : ''}}" bindtap="switchTab" data-tab="care">
养护记录 养护记录
</view> </view>
<view class="pd-tab-btn {{activeTab === 'archive' ? 'active' : ''}}" bindtap="switchTab" data-tab="archive"> <view class="pd-tab-btn {{activeTab === 'growth' ? 'active' : ''}}" bindtap="switchTab" data-tab="growth">
植物档案 成长档案
</view> </view>
</view> </view>
<!-- Info Tab Scroll View -->
<scroll-view
scroll-y="{{!showGrowthModal}}"
class="pd-content"
enhanced="{{true}}"
show-scrollbar="{{false}}"
hidden="{{activeTab !== 'info'}}"
>
<view class="archive-view fadeIn">
<view class="archive-identity-card">
<view class="aic-header">
<view class="aic-badge">🏆 元老级植物</view>
<view class="aic-location">
<t-icon name="location" size="28rpx" color="#388E3C" />
<text>{{currentPlant.location || '位置未定'}}</text>
</view>
</view>
<view class="aic-stats">
<view class="aic-stat-item">
<text class="label">入家时间</text>
<text class="value">{{currentPlant.adoptionDate || '未知'}}</text>
</view>
<view class="aic-stat-item">
<text class="label">入住天数</text>
<text class="value">{{currentPlant.daysPlanted}} 天</text>
</view>
</view>
<!-- New detail fields -->
<view class="aic-extra-info">
<view wx:if="{{currentPlant.potMaterial}}" class="aic-info-row">
<text class="label">花园材质:</text>
<text class="value">{{currentPlant.potMaterial}}</text>
</view>
<view wx:if="{{currentPlant.potSize}}" class="aic-info-row">
<text class="label">花园大小:</text>
<text class="value">{{currentPlant.potSize}}</text>
</view>
<view wx:if="{{currentPlant.sunlight}}" class="aic-info-row">
<text class="label">光照条件:</text>
<text class="value">{{currentPlant.sunlight}}</text>
</view>
<view wx:if="{{currentPlant.plantingMaterial}}" class="aic-info-row">
<text class="label">植料土壤:</text>
<text class="value">{{currentPlant.plantingMaterial}}</text>
</view>
</view>
</view>
</view>
</scroll-view>
<!-- Care Tab Scroll View --> <!-- Care Tab Scroll View -->
<scroll-view <scroll-view
scroll-y="{{!showGrowthModal}}" scroll-y="{{!showGrowthModal}}"
@@ -91,58 +146,15 @@
</view> </view>
</scroll-view> </scroll-view>
<!-- Archive Tab Scroll View --> <!-- Growth Tab Scroll View -->
<scroll-view <scroll-view
scroll-y="{{!showGrowthModal}}" scroll-y="{{!showGrowthModal}}"
class="pd-content" class="pd-content"
enhanced="{{true}}" enhanced="{{true}}"
show-scrollbar="{{false}}" show-scrollbar="{{false}}"
hidden="{{activeTab !== 'archive'}}" hidden="{{activeTab !== 'growth'}}"
> >
<view class="archive-view fadeIn"> <view class="archive-view fadeIn">
<view class="archive-identity-card">
<view class="aic-header">
<view class="aic-badge">🏆 元老级植物</view>
<view class="aic-location">
<t-icon name="location" size="28rpx" color="#388E3C" />
<text>{{currentPlant.location || '位置未定'}}</text>
</view>
</view>
<view class="aic-stats">
<view class="aic-stat-item">
<text class="label">入家时间</text>
<text class="value">{{currentPlant.adoptionDate || '未知'}}</text>
</view>
<view class="aic-stat-item">
<text class="label">入住天数</text>
<text class="value">{{currentPlant.daysPlanted}} 天</text>
</view>
<view class="aic-stat-item">
<text class="label">养护次数</text>
<text class="value">{{careLogs.length || 0}} 次</text>
</view>
</view>
<!-- New detail fields -->
<view class="aic-extra-info">
<view wx:if="{{currentPlant.potMaterial}}" class="aic-info-row">
<text class="label">花园材质:</text>
<text class="value">{{currentPlant.potMaterial}}</text>
</view>
<view wx:if="{{currentPlant.potSize}}" class="aic-info-row">
<text class="label">花园大小:</text>
<text class="value">{{currentPlant.potSize}}</text>
</view>
<view wx:if="{{currentPlant.sunlight}}" class="aic-info-row">
<text class="label">光照条件:</text>
<text class="value">{{currentPlant.sunlight}}</text>
</view>
<view wx:if="{{currentPlant.plantingMaterial}}" class="aic-info-row">
<text class="label">植料土壤:</text>
<text class="value">{{currentPlant.plantingMaterial}}</text>
</view>
</view>
</view>
<view class="section-header"> <view class="section-header">
<text class="h3">成长记录</text> <text class="h3">成长记录</text>
@@ -166,11 +178,11 @@
<text>{{item.title}}</text> <text>{{item.title}}</text>
</view> </view>
<text class="timeline-desc">{{item.content}}</text> <text class="timeline-desc">{{item.content}}</text>
<t-image <image
wx:if="{{item.image}}" wx:if="{{item.image}}"
src="{{item.image}}" src="{{item.image}}"
mode="widthFix" mode="aspectFill"
width="100%" style="width: 220rpx; height: 220rpx; border-radius: 16rpx; margin-top: 16rpx;"
class="timeline-img" class="timeline-img"
bindtap="handlePreviewRecordImage" bindtap="handlePreviewRecordImage"
data-src="{{item.image}}" data-src="{{item.image}}"
@@ -196,74 +208,5 @@
</scroll-view> </scroll-view>
</view> </view>
<!-- Growth Record Popup -->
<t-popup visible="{{showGrowthModal}}" bind:visible-change="onGrowthPopupVisibleChange" placement="center" prevent-scroll-through>
<view class="edit-modal">
<view class="modal-header" catchtouchmove="preventTouchMove">
<text class="modal-title">添加成长记录</text>
<view class="close-btn" bindtap="closeGrowthModal">
<t-icon name="close" size="40rpx" color="#666" />
</view>
</view>
<view class="modal-body">
<!-- Record Type -->
<view class="form-group">
<text class="form-label">记录类型</text>
<view class="chip-group">
<view class="chip {{newRecordType === 'growth' ? 'active' : ''}}" bindtap="setRecordTypeByTap" data-type="growth">
<t-icon name="thumb-up" size="28rpx" />
<text>生长</text>
</view>
<view class="chip {{newRecordType === 'repot' ? 'active' : ''}}" bindtap="setRecordTypeByTap" data-type="repot">
<t-icon name="swap" size="28rpx" />
<text>换盆</text>
</view>
<view class="chip {{newRecordType === 'pest' ? 'active' : ''}}" bindtap="setRecordTypeByTap" data-type="pest">
<t-icon name="error-circle" size="28rpx" />
<text>病虫害</text>
</view>
<view class="chip {{newRecordType === 'other' ? 'active' : ''}}" bindtap="setRecordTypeByTap" data-type="other">
<t-icon name="file" size="28rpx" />
<text>其他</text>
</view>
</view>
</view>
<!-- Content -->
<view class="form-group">
<text class="form-label">备注</text>
<textarea
class="form-textarea"
placeholder="记录这一刻的变化..."
value="{{newRecordContent}}"
bindinput="onRecordContentInput"
auto-height
/>
</view>
<!-- Image Upload -->
<view class="form-group">
<text class="form-label">添加照片</text>
<view class="record-image-upload">
<view wx:if="{{newRecordImage}}" class="uploaded-image-box">
<t-image src="{{newRecordImage}}" mode="aspectFill" width="160rpx" height="160rpx" shape="round" />
<view class="remove-img-btn" bindtap="handleRemoveRecordImage">
<t-icon name="close" size="24rpx" color="#FFF" />
</view>
</view>
<view wx:else class="upload-add-btn" bindtap="handleChooseRecordImage">
<t-icon name="add" size="48rpx" color="#999" />
</view>
</view>
</view>
</view>
<view class="modal-footer" catchtouchmove="preventTouchMove">
<view class="submit-btn" bindtap="handleAddRecord">
<text>保存记录</text>
</view>
</view>
</view>
</t-popup>
</view> </view>
+105
View File
@@ -0,0 +1,105 @@
import request from '../../../utils/request';
Page({
data: {
userLevel: 0,
userLevelTag: '',
currentExp: 0,
maxExp: 1000,
maxExp: 1000,
nextLevelExp: 1000,
showLevelsPopup: false,
allLevels: [],
badges: [
{ id: 1, name: '初级播种者', desc: '成功种植第一棵植物', iconName: 'flower', color: '#4CAF50', unlocked: true },
{ id: 2, name: '勤劳园丁', desc: '总养护次数达到10次', iconName: 'tools', color: '#2196F3', unlocked: true },
{ id: 3, name: '植物专家', desc: '成功识别50种植物', iconName: 'scan', color: '#9C27B0', unlocked: false, progress: '12/50' },
{ id: 4, name: '全勤奖', desc: '连续30天打卡', iconName: 'calendar', color: '#FF9800', unlocked: false, progress: '5/30' },
{ id: 5, name: '分享大师', desc: '发布10条动态', iconName: 'share', color: '#E91E63', unlocked: false, progress: '3/10' },
{ id: 6, name: '收藏家', desc: '收藏20个植物百科', iconName: 'star', color: '#FFC107', unlocked: false, progress: '8/20' }
]
},
onLoad() {
this.fetchData();
},
async fetchData() {
wx.showLoading({ title: '加载中...' });
try {
const [levelRes, profileRes] = await Promise.all([
request.get('/config/level/list'),
request.get('/profile/detail')
]);
this.processData(levelRes, profileRes);
} catch (e) {
console.error('Fetch badges data failed', e);
wx.showToast({ title: '加载失败', icon: 'none' });
} finally {
wx.hideLoading();
}
},
processData(levelsData, profile) {
const levelList = levelsData && levelsData.list ? levelsData.list : [];
levelList.sort((a, b) => a.minSunlight - b.minSunlight); // Ensure sorted by threshold
const totalSunlight = profile.totalSunlight || 0;
// Find current level: highest level where minSunlight <= totalSunlight
let currentLevelConfig = null;
for (let i = levelList.length - 1; i >= 0; i--) {
if (totalSunlight >= levelList[i].minSunlight) {
currentLevelConfig = levelList[i];
break;
}
}
// Check if no level matched (e.g. 0 sunlight but level 1 starts at 0? Logic handles it if sorted)
if (!currentLevelConfig && levelList.length > 0) {
// Fallback to absolute lowest or specific logic
// Assuming level 1 starts at 0, it should be caught.
currentLevelConfig = levelList[0];
}
const currentLevelVal = currentLevelConfig ? currentLevelConfig.level : 0;
// Find next level
const nextLevelConfig = levelList.find(l => l.minSunlight > totalSunlight);
let maxExp = 0;
let nextPerk = '';
if (nextLevelConfig) {
maxExp = nextLevelConfig.minSunlight;
nextPerk = nextLevelConfig.perks;
} else {
// Max level
maxExp = totalSunlight > 0 ? totalSunlight : 100;
nextPerk = '已达到最高等级';
}
// Sanity
if (maxExp < totalSunlight) maxExp = totalSunlight;
const tag = currentLevelConfig ? `Lv.${currentLevelVal} ${currentLevelConfig.title}` : 'Lv.0 园艺新手';
this.setData({
userLevel: currentLevelVal,
userLevelTag: tag,
currentExp: totalSunlight,
maxExp: maxExp,
nextLevelExp: Math.max(0, maxExp - totalSunlight),
nextPerk: nextPerk,
allLevels: levelList // Save for popup
});
},
showLevelList() {
wx.navigateTo({
url: `/pages/profile/badges/level-detail/index?sunlight=${this.data.currentExp}`
});
},
});
+8
View File
@@ -0,0 +1,8 @@
{
"navigationBarTitleText": "成就徽章",
"navigationBarBackgroundColor": "#F4F6F0",
"navigationBarTextStyle": "black",
"usingComponents": {
"t-icon": "tdesign-miniprogram/icon/icon"
}
}

Some files were not shown because too many files have changed in this diff Show More