Commit 98ac06d9248ea888387dffcc65484e2089e95dd7

Authored by xwenliang
1 parent 0a02df01

refactor: use new ui

1 -//app.js 1 +// zooble@2025-12-14
  2 +const { createRequest } = require('./utils/request.js');
  3 +
2 App({ 4 App({
3 - async onLaunch () {  
4 - this.login();  
5 - // 移除 2022-12-05 之前的本地存储,因为可能有敏感词存在  
6 - const timestamp = new Date('2022-12-05').getTime();  
7 - const audioList = this.globalData.getAudioList().filter(v => v.time > timestamp);  
8 - const storageKey = 'audioList';  
9 - wx.setStorageSync(storageKey, audioList);  
10 - },  
11 - // 将登录态封装进去  
12 - // todo: 多个请求同时发起,且登录态都失效时,ssid 会相互覆盖  
13 - async request(config){  
14 - let ssid = wx.getStorageSync('ssid');  
15 - if(!ssid){  
16 - ssid = await this.login();  
17 - }  
18 - const initialSuccess = config.success;  
19 - config.data = {  
20 - ...config.data,  
21 - ssid  
22 - };  
23 - config.success = async resp => {  
24 - if(resp.data.code === 5001000){  
25 - wx.setStorageSync('ssid', '');  
26 - delete config.ssid;  
27 - this.request(config);  
28 - }  
29 - else{  
30 - initialSuccess(resp);  
31 - }  
32 - };  
33 - config = {  
34 - header: {  
35 - 'content-type': 'application/json'  
36 - },  
37 - dataType: 'json',  
38 - method: 'POST',  
39 - ...config  
40 - };  
41 - return wx.request(config);  
42 - },  
43 - login(){  
44 - let ssid = wx.getStorageSync('ssid');  
45 - if(ssid){  
46 - return ssid;  
47 - }  
48 - wx.showLoading({  
49 - title: '登录中...',  
50 - mask: true  
51 - });  
52 - return new Promise((resolve, reject) => {  
53 - wx.login({  
54 - success: res => {  
55 - wx.request({  
56 - url: `${this.globalData.domain}/open-api/mp/login`,  
57 - data: {  
58 - code: res.code,  
59 - appid: 'wx330e54aa6000516d'  
60 - },  
61 - header: {  
62 - 'content-type': 'application/json'  
63 - },  
64 - dataType: 'json',  
65 - method: 'POST',  
66 - success: resp => {  
67 - const {  
68 - ssid,  
69 - openid  
70 - } = resp.data?.data?.user;  
71 - wx.setStorageSync('ssid', ssid);  
72 - wx.setStorageSync('openid', openid);  
73 - resolve(ssid);  
74 - },  
75 - fail: resp => {  
76 - wx.showModal({  
77 - title: '提示',  
78 - content: '登录失败,点击确定重试',  
79 - showCancel: false,  
80 - success: res => {  
81 - this.login();  
82 - }  
83 - });  
84 - reject(resp.errMsg);  
85 - },  
86 - complete: () => {  
87 - wx.hideLoading();  
88 - }  
89 - });  
90 - }  
91 - });  
92 - });  
93 - },  
94 globalData: { 5 globalData: {
95 // domain: 'http://localhost:3003', 6 // domain: 'http://localhost:3003',
96 domain: 'https://api.xwenliang.cn', 7 domain: 'https://api.xwenliang.cn',
  8 + appid: 'wx330e54aa6000516d',
  9 + cid: '',
  10 + safeAreaTop: 0,
  11 + safeAreaBottom: 0,
  12 + statusBarHeight: 0,
  13 + navBarHeight: 0,
  14 + totalNavHeight: 0, // 导航栏总高度(状态栏 + 导航栏)
  15 + tabBarHeight: 56,
  16 + totalTabBarHeight: 0, // tab总高度(tab + 底部安全区)
  17 + contentHeight: 0, // 内容区域高度(全屏高度 - 导航栏总高度 - 底部安全区)
  18 + contentWithoutTabbarHeight: 0, // 内容区域高度 - tab 高度
97 getTypeList(){ 19 getTypeList(){
98 const globalData = this; 20 const globalData = this;
99 return [ 21 return [
@@ -148,6 +70,29 @@ App({ @@ -148,6 +70,29 @@ App({
148 } 70 }
149 ]; 71 ];
150 }, 72 },
  73 + onLaunch(option){
  74 + const { query } = option;
  75 + // 获取渠道值
  76 + this.globalData.cid = query.cid || '';
  77 + // 挂载 request
  78 + const { domain, appid, cid } = this.globalData;
  79 + this.request = createRequest({ domain, appid, cid });
  80 +
  81 + // 获取系统信息,计算安全区域
  82 + const windowInfo = wx.getWindowInfo ? wx.getWindowInfo() : wx.getSystemInfoSync();
  83 + const { statusBarHeight, safeArea, windowHeight, screenHeight } = windowInfo;
  84 + this.globalData.statusBarHeight = statusBarHeight;
  85 + this.globalData.safeAreaTop = safeArea.top;
  86 + this.globalData.safeAreaBottom = screenHeight - safeArea.bottom;
  87 + // 右上角胶囊位置信息
  88 + const menuButtonInfo = wx.getMenuButtonBoundingClientRect();
  89 + // 导航栏高度,是胶囊以及胶囊的上下边距所包含的高度,通常是 32+4+4
  90 + this.globalData.navBarHeight = menuButtonInfo.height + (menuButtonInfo.top - statusBarHeight) * 2;
  91 + this.globalData.totalNavHeight = statusBarHeight + this.globalData.navBarHeight;
  92 + this.globalData.contentHeight = windowHeight - this.globalData.totalNavHeight;
  93 + this.globalData.totalTabBarHeight = this.globalData.tabBarHeight + this.globalData.safeAreaBottom;
  94 + this.globalData.contentWithoutTabbarHeight = this.globalData.contentHeight - this.globalData.totalTabBarHeight;
  95 + },
151 // TTS接口请求方法 96 // TTS接口请求方法
152 async requestTTS(text, voice) { 97 async requestTTS(text, voice) {
153 return new Promise((resolve, reject) => { 98 return new Promise((resolve, reject) => {
@@ -201,8 +146,8 @@ App({ @@ -201,8 +146,8 @@ App({
201 return wx.getStorageSync(storageKey) || []; 146 return wx.getStorageSync(storageKey) || [];
202 }, 147 },
203 /** 148 /**
204 - * @param {(add|delete)} action - 增或删  
205 - * @param {object|number} [param] - 增加时是 audio 描述,删除时是唯一特征 path 值 149 + * @param {(add|delete)} action - 增或删
  150 + * @param {object|number} [param] - 增加时是 audio 描述,删除时是唯一特征 path 值
206 */ 151 */
207 setAudioList(action, param){ 152 setAudioList(action, param){
208 const storageKey = 'audioList'; 153 const storageKey = 'audioList';
app.json
@@ -7,7 +7,8 @@ @@ -7,7 +7,8 @@
7 "backgroundTextStyle": "light", 7 "backgroundTextStyle": "light",
8 "navigationBarBackgroundColor": "#fff", 8 "navigationBarBackgroundColor": "#fff",
9 "navigationBarTitleText": "文字转语音", 9 "navigationBarTitleText": "文字转语音",
10 - "navigationBarTextStyle": "black" 10 + "navigationBarTextStyle": "black",
  11 + "navigationStyle": "custom"
11 }, 12 },
12 "sitemapLocation": "sitemap.json" 13 "sitemapLocation": "sitemap.json"
13 } 14 }
14 \ No newline at end of file 15 \ No newline at end of file
app.wxss
1 /**app.wxss**/ 1 /**app.wxss**/
  2 +page {
  3 + height: 100%;
  4 + background: #D7E5FA;
  5 + background-image: url(https://gallery.xwenliang.cn/miniprogram/2025-11-13-ea3e6a30ac.jpg);
  6 + background-position: top center;
  7 + background-size: 100% auto;
  8 + background-repeat: no-repeat;
  9 + overflow: hidden;
  10 + width: 100%;
  11 + height: 100%;
  12 +}
  13 +
  14 +.container-custom {
  15 + width: 100%;
  16 + height: 100%;
  17 + display: flex;
  18 + flex-direction: column;
  19 + box-sizing: border-box;
  20 + overflow-x: hidden;
  21 + background-repeat: no-repeat;
  22 +}
  23 +
  24 +.container-default {
  25 + width: 100%;
  26 + height: 100%;
  27 + display: flex;
  28 + flex-direction: column;
  29 + overflow-x: hidden;
  30 +}
  31 +
  32 +.container-default-box {
  33 + width: 100%;
  34 + overflow-x: hidden;
  35 + overflow-y: auto;
  36 + box-sizing: border-box;
  37 + padding: 0 8px;
  38 +}
  39 +
  40 +/* 全局安全区域样式类 */
  41 +.safe-area-top {
  42 + padding-top: constant(safe-area-inset-top);
  43 + padding-top: env(safe-area-inset-top);
  44 +}
  45 +
  46 +.safe-area-bottom {
  47 + padding-bottom: constant(safe-area-inset-bottom);
  48 + padding-bottom: env(safe-area-inset-bottom);
  49 +}
  50 +
  51 +/* 保留原有的 container 类以兼容其他页面 */
2 .container { 52 .container {
3 width: 100%; 53 width: 100%;
4 height: 100%; 54 height: 100%;
pages/index/index.wxml
1 <!--index.wxml--> 1 <!--index.wxml-->
2 -<view class="container"> 2 +<view class="container-custom">
  3 + <!-- 动态背景 -->
  4 + <view class="bg">
  5 + <view class="ball ball-1"></view>
  6 + <view class="ball ball-2"></view>
  7 + </view>
3 <view class="text-view"> 8 <view class="text-view">
4 <textarea 9 <textarea
5 auto-focus 10 auto-focus
pages/index/index.wxss
@@ -7,6 +7,72 @@ page { @@ -7,6 +7,72 @@ page {
7 box-sizing: border-box; 7 box-sizing: border-box;
8 } 8 }
9 9
  10 +/* 动态背景样式 */
  11 +.bg {
  12 + position: fixed;
  13 + top: 0;
  14 + left: 0;
  15 + width: 100%;
  16 + height: 100%;
  17 + filter: blur(120px);
  18 + z-index: -1;
  19 +}
  20 +
  21 +.ball {
  22 + position: absolute;
  23 + border-radius: 50%;
  24 +}
  25 +
  26 +.ball-1 {
  27 + width: 400rpx;
  28 + height: 400rpx;
  29 + background-color: #4472c4;
  30 + animation: move-1 5s infinite;
  31 +}
  32 +
  33 +.ball-2 {
  34 + width: 500rpx;
  35 + height: 500rpx;
  36 + background-color: #00b050;
  37 + animation: move-2 5s infinite;
  38 +}
  39 +
  40 +@keyframes move-1 {
  41 + 0% {
  42 + transform: translate(0, 0);
  43 + }
  44 + 25% {
  45 + transform: translate(200rpx, 150rpx);
  46 + }
  47 + 50% {
  48 + transform: translate(0, 300rpx);
  49 + }
  50 + 75% {
  51 + transform: translate(-100rpx, 150rpx);
  52 + }
  53 + 100% {
  54 + transform: translate(0, 0);
  55 + }
  56 +}
  57 +
  58 +@keyframes move-2 {
  59 + 0% {
  60 + transform: translate(0, 0);
  61 + }
  62 + 25% {
  63 + transform: translate(-200rpx, -180rpx);
  64 + }
  65 + 50% {
  66 + transform: translate(0, -320rpx);
  67 + }
  68 + 75% {
  69 + transform: translate(100rpx, -180rpx);
  70 + }
  71 + 100% {
  72 + transform: translate(0, 0);
  73 + }
  74 +}
  75 +
10 /* 全局移除按钮边框 */ 76 /* 全局移除按钮边框 */
11 button { 77 button {
12 border: none !important; 78 border: none !important;
@@ -21,7 +87,8 @@ button::before { @@ -21,7 +87,8 @@ button::before {
21 border: none !important; 87 border: none !important;
22 } 88 }
23 89
24 -.container { 90 +.container-custom {
  91 + position: relative;
25 width: 100%; 92 width: 100%;
26 max-width: 100vw; 93 max-width: 100vw;
27 overflow-x: hidden; 94 overflow-x: hidden;
utils/request.js 0 → 100644
  1 +// zooble@2025-12-14
  2 +
  3 +/**
  4 + * 创建一个请求函数,该函数闭包了登录和重试逻辑
  5 + * @param {object} baseConfig 基础配置
  6 + * @param {string} baseConfig.domain API域名
  7 + * @param {string} baseConfig.appid 小程序ID
  8 + * @param {string} base.cid 渠道ID
  9 + */
  10 +function createRequest(baseConfig) {
  11 + let loginPromise = null;
  12 +
  13 + // 登录函数
  14 + const login = () => {
  15 + if (loginPromise) {
  16 + return loginPromise;
  17 + }
  18 +
  19 + const ssid = wx.getStorageSync('ssid');
  20 + if (ssid) {
  21 + return Promise.resolve(ssid);
  22 + }
  23 +
  24 + wx.showLoading({ mask: true });
  25 +
  26 + loginPromise = new Promise((resolve, reject) => {
  27 + wx.login({
  28 + success: res => {
  29 + if (!res.code) {
  30 + const errorMsg = '登录失败:无法获取code';
  31 + wx.showModal({ title: '提示', content: errorMsg, showCancel: false });
  32 + return reject(new Error(errorMsg));
  33 + }
  34 +
  35 + const { domain, appid } = baseConfig;
  36 + wx.request({
  37 + url: `${domain}/open-api/mp/login`,
  38 + data: { code: res.code, appid },
  39 + method: 'POST',
  40 + header: { 'content-type': 'application/json' },
  41 + dataType: 'json',
  42 + success: resp => {
  43 + const user = resp.data?.data?.user;
  44 + if (user && user.ssid) {
  45 + wx.setStorageSync('ssid', user.ssid);
  46 + wx.setStorageSync('openid', user.openid);
  47 + resolve(user.ssid);
  48 + } else {
  49 + const errorMsg = resp.data.msg || '登录失败:无效的用户信息';
  50 + wx.showModal({ title: '提示', content: errorMsg, showCancel: false });
  51 + reject(new Error(errorMsg));
  52 + }
  53 + },
  54 + fail: err => {
  55 + wx.showModal({ title: '提示', content: '登录请求失败', showCancel: false });
  56 + reject(err);
  57 + },
  58 + complete: () => {
  59 + wx.hideLoading();
  60 + loginPromise = null;
  61 + }
  62 + });
  63 + },
  64 + fail: err => {
  65 + wx.hideLoading();
  66 + loginPromise = null;
  67 + wx.showModal({ title: '提示', content: '微信登录失败', showCancel: false });
  68 + reject(err);
  69 + }
  70 + });
  71 + });
  72 + return loginPromise;
  73 + };
  74 +
  75 + // 返回的请求函数
  76 + const request = async (config) => {
  77 + let ssid = wx.getStorageSync('ssid');
  78 + if (!ssid) {
  79 + try {
  80 + ssid = await login();
  81 + } catch (e) {
  82 + if (config.fail) config.fail(e);
  83 + // early return on login failure
  84 + return;
  85 + }
  86 + }
  87 +
  88 + const finalConfig = {
  89 + header: { 'content-type': 'application/json' },
  90 + dataType: 'json',
  91 + method: 'POST',
  92 + ...config,
  93 + data: {
  94 + ...config.data,
  95 + ssid,
  96 + cid: baseConfig.cid,
  97 + },
  98 + success: (resp) => {
  99 + // 登录态失效,自动重试
  100 + if ([5001000, 5004000].includes(resp.data.code)) {
  101 + console.log('ssid expired, retrying request');
  102 + wx.setStorageSync('ssid', '');
  103 + // 传入原始 config 递归调用,避免 success 被多次包装
  104 + request(config);
  105 + } else {
  106 + if (config.success) {
  107 + config.success(resp);
  108 + }
  109 + }
  110 + },
  111 + };
  112 +
  113 + return wx.request(finalConfig);
  114 + };
  115 +
  116 + return request;
  117 +};
  118 +
  119 +module.exports = {
  120 + createRequest
  121 +};