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 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 5 globalData: {
95 6 // domain: 'http://localhost:3003',
96 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 19 getTypeList(){
98 20 const globalData = this;
99 21 return [
... ... @@ -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 96 // TTS接口请求方法
152 97 async requestTTS(text, voice) {
153 98 return new Promise((resolve, reject) => {
... ... @@ -201,8 +146,8 @@ App({
201 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 152 setAudioList(action, param){
208 153 const storageKey = 'audioList';
... ...
app.json
... ... @@ -7,7 +7,8 @@
7 7 "backgroundTextStyle": "light",
8 8 "navigationBarBackgroundColor": "#fff",
9 9 "navigationBarTitleText": "文字转语音",
10   - "navigationBarTextStyle": "black"
  10 + "navigationBarTextStyle": "black",
  11 + "navigationStyle": "custom"
11 12 },
12 13 "sitemapLocation": "sitemap.json"
13 14 }
14 15 \ No newline at end of file
... ...
app.wxss
1 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 52 .container {
3 53 width: 100%;
4 54 height: 100%;
... ...
pages/index/index.wxml
1 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 8 <view class="text-view">
4 9 <textarea
5 10 auto-focus
... ...
pages/index/index.wxss
... ... @@ -7,6 +7,72 @@ page {
7 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 77 button {
12 78 border: none !important;
... ... @@ -21,7 +87,8 @@ button::before {
21 87 border: none !important;
22 88 }
23 89  
24   -.container {
  90 +.container-custom {
  91 + position: relative;
25 92 width: 100%;
26 93 max-width: 100vw;
27 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 +};
... ...