wangrong
13 小时以前 db685b04e52efd75213ef7540fdc0bed7dccfd9e
更新小程序
1个文件已添加
24个文件已修改
683 ■■■■■ 已修改文件
App.vue 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
components/phone-auth.vue 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
config/proxy.ts 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cool/hooks/wx.ts 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cool/mini-login.ts 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cool/service/request.ts 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cool/store/user.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cool/utils/comm.ts 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cool/utils/qrcode.ts 199 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cool/utils/storage.ts 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
locale/index.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
manifest.json 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/goods/components/filter-bar.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/goods/detail.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/recycle/banner.vue 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/recycle/home.vue 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/cool-ui/components/cl-guide/cl-guide.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/cool-ui/components/cl-page/cl-page.vue 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/cool-ui/components/cl-popup/cl-popup.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/cool-ui/components/cl-status-bar/cl-status-bar.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/cool-ui/components/cl-sticky/cl-sticky.vue 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/cool-ui/components/cl-topbar/cl-topbar.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/mp-html/components/mp-html/node/node.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/mp-html/components/mp-html/parser.js 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
App.vue
@@ -1,23 +1,57 @@
<script setup lang="ts">
import { useStore } from "/@/cool";
import { useStore, storage } from "/@/cool";
import { miniAutoLogin, phoneAuthState } from "/@/cool/mini-login";
import { i18n } from "/@/locale";
import { onLaunch, onShow, onHide } from "@dcloudio/uni-app";
let booted = false;
function boot() {
    if (booted) {
        return;
    }
    booted = true;
    const { dict, user } = useStore();
    const authState = phoneAuthState();
    try {
        const locale = uni.getLocale();
        if (locale) {
            i18n.global.locale = locale;
        }
    } catch (err) {
        console.error(err);
    }
    dict.refresh()?.catch(() => undefined);
    // #ifdef MP-WEIXIN
    miniAutoLogin()
        .catch(() => false)
        .finally(() => {
            authState.ready = true;
            if (user.token && !storage.isExpired("token")) {
                user.get()?.catch(() => undefined);
            }
        });
    // #endif
    // #ifndef MP-WEIXIN
    authState.ready = true;
    if (user.token) {
        user.get()?.catch(() => undefined);
    }
    // #endif
}
onLaunch(() => {
    console.log("App Launch");
    const { dict, user } = useStore();
    // 获取字典
    dict.refresh();
    if (user.token) {
        // 获取登录用户信息
        user.get();
    }
});
onShow(() => {
    console.log("App Show");
    setTimeout(boot, 0);
});
onHide(() => {
components/phone-auth.vue
@@ -24,9 +24,10 @@
import { computed, ref } from "vue";
import { onShow } from "@dcloudio/uni-app";
import { useCool, useStore, useWx } from "/@/cool";
import { phoneAuthState } from "/@/cool/mini-login";
import { useUi } from "/$/cool-ui";
const authState = ((globalThis as any).__phoneAuthState ||= { dismissed: false });
const authState = phoneAuthState();
const { service, router } = useCool();
const { user } = useStore();
@@ -37,7 +38,7 @@
const visible = computed(() => {
    tick.value;
    return !authState.dismissed && !user.info?.phone;
    return authState.ready && !authState.dismissed && (!user.token || !user.info?.phone);
});
function later() {
@@ -87,23 +88,34 @@
    submitting.value = true;
    ui.showLoading();
    const payload = phonePayload(detail);
    const task = user.token
        ? service.user.info.miniPhone(payload).then(async (phone: string) => {
                const info = user.info || (await service.user.info.person());
                if (info) {
                    user.set({ ...info, phone });
                }
                return phone;
            })
        : service.user.login.miniPhone(payload).then(async (res) => {
                user.setToken(res);
                const info = await service.user.info.person();
                if (info) {
                    user.set(info);
                }
                return info?.phone as string | undefined;
    const task = new Promise<string>((resolve) => {
        uni.login({
            provider: "weixin",
            success: (res) => resolve(res.code || ""),
            fail: () => resolve(""),
        });
    })
        .then((loginCode) => {
            return service.user.login.miniPhone({
                ...phonePayload(detail),
                loginCode,
            });
        })
        .then(async (res) => {
        user.setToken(res);
        let info = user.info;
        try {
            info = (await service.user.info.person()) || info;
        } catch (err) {
            console.error(err);
        }
        const phone = info?.phone || res.phone;
        user.set({
            ...(info || {}),
            phone,
        });
        return phone as string | undefined;
    });
    task
        .then((phone) => {
config/proxy.ts
@@ -1,12 +1,12 @@
const proxy = {
    "/dev/": {
        target: "http://127.0.0.1:8001",
        target: "http://womp.jsbogang.com:8001",
        changeOrigin: true,
        rewrite: (path: string) => path.replace(/^\/dev/, ""),
    },
    "/prod/": {
        target: "https://show.cool-admin.com",
        target: "https://womp.jsbogang.com:8001",
        changeOrigin: true,
        rewrite: (path: string) => path.replace(/^\/prod/, "/api"),
    },
cool/hooks/wx.ts
@@ -1,7 +1,7 @@
import { ref } from "vue";
import { onReady, onShow } from "@dcloudio/uni-app";
import { config } from "/@/config";
import { getUrlParam, storage } from "../utils";
import { getSystemInfo, getUrlParam, storage } from "../utils";
import { service } from "../service";
import { useI18n } from "vue-i18n";
// #ifdef H5
@@ -9,7 +9,7 @@
// #endif
export function useWx() {
    const { platform } = uni.getSystemInfoSync();
    const { platform } = getSystemInfo();
    const { t } = useI18n();
    // 授权码
cool/mini-login.ts
New file
@@ -0,0 +1,81 @@
import { reactive } from "vue";
import { BaseService } from "./service/base";
import { useUserStore } from "./store/user";
import { storage } from "./utils";
export type PhoneAuthState = {
    dismissed: boolean;
    ready: boolean;
};
export function phoneAuthState(): PhoneAuthState {
    const host = globalThis as any;
    if (!host.__phoneAuthState) {
        host.__phoneAuthState = reactive({
            dismissed: false,
            ready: false,
        });
    }
    return host.__phoneAuthState;
}
function wxLoginCode() {
    return new Promise<string>((resolve) => {
        uni.login({
            provider: "weixin",
            success: (res) => resolve(res.code || ""),
            fail: () => resolve(""),
        });
    });
}
let pending: Promise<boolean> | null = null;
/**
 * 已授权过手机号的微信用户,再次打开时用 openid 换回登录态。
 */
export function miniAutoLogin(force = false) {
    if (pending && !force) {
        return pending;
    }
    const task = runMiniAutoLogin(force).finally(() => {
        if (pending === task) {
            pending = null;
        }
    });
    pending = task;
    return task;
}
async function runMiniAutoLogin(force = false) {
    const user = useUserStore();
    if (!force && user.token && user.info?.phone && !storage.isExpired("token")) {
        return true;
    }
    const code = await wxLoginCode();
    if (!code) {
        return false;
    }
    const res = await new BaseService("app/user/login").request({
        url: "/miniAuto",
        method: "POST",
        data: { code },
    });
    if (!res?.token) {
        return false;
    }
    user.setToken(res);
    user.set({
        ...(user.info || {}),
        id: res.id,
        unionid: res.unionid,
        phone: res.phone,
        nickName: res.nickName || user.info?.nickName,
        avatarUrl: res.avatarUrl || user.info?.avatarUrl,
    });
    return true;
}
cool/service/request.ts
@@ -10,6 +10,33 @@
// Token 是否刷新中
let isRefreshing = false;
let reloginTask: Promise<boolean> | null = null;
function dropToken() {
    const { user } = useStore();
    user.token = "";
    storage.remove("token");
    storage.remove("token_deadtime");
    storage.remove("refreshToken");
    storage.remove("refreshToken_deadtime");
}
function recoverLogin() {
    if (reloginTask) {
        return reloginTask;
    }
    reloginTask = import("../mini-login")
        .then(({ miniAutoLogin }) => {
            dropToken();
            return miniAutoLogin(true);
        })
        .catch(() => false)
        .finally(() => {
            reloginTask = null;
        });
    return reloginTask;
}
export default function request(options: any) {
    const { user } = useStore();
@@ -45,6 +72,23 @@
                        message: string;
                        data: any;
                    };
                    const invalid = res.statusCode === 401 || message === "登录失效~";
                    const skipRelogin = /miniAuto|refreshToken/.test(options.url || "");
                    if (invalid && !skipRelogin && !options.__retried) {
                        options.__retried = true;
                        recoverLogin()
                            .then((ok) => {
                                if (!ok) {
                                    reject({ message: message || "登录失效~", code: code || 1001 });
                                    return;
                                }
                                Authorization = useStore().user.token || "";
                                next();
                            })
                            .catch((err) => reject(err));
                        return;
                    }
                    // 无权限
                    if (res.statusCode === 401) {
@@ -95,9 +139,18 @@
                // 判断 token 是否过期
                if (storage.isExpired("token")) {
                    // 判断 refreshToken 是否过期
                    if (storage.isExpired("refreshToken")) {
                        // 退出登录
                        return user.logout();
                    if (!storage.get("refreshToken") || storage.isExpired("refreshToken")) {
                        recoverLogin()
                            .then((ok) => {
                                if (!ok) {
                                    reject({ message: "登录失效~", code: 1001 });
                                    return;
                                }
                                Authorization = useStore().user.token || "";
                                next();
                            })
                            .catch((err) => reject(err));
                        return;
                    }
                    // 是否在刷新中
@@ -109,9 +162,19 @@
                                requests = [];
                                isRefreshing = false;
                            })
                            .catch((err) => {
                                user.logout();
                                reject(err);
                            .catch(() => {
                                isRefreshing = false;
                                requests = [];
                                recoverLogin()
                                    .then((ok) => {
                                        if (!ok) {
                                            reject({ message: "登录失效~", code: 1001 });
                                            return;
                                        }
                                        Authorization = useStore().user.token || "";
                                        next();
                                    })
                                    .catch((err) => reject(err));
                            });
                    }
cool/store/user.ts
@@ -5,10 +5,9 @@
import { service } from "../service";
import type { User } from "../types";
// 本地缓存
const data = storage.info();
const useUserStore = defineStore("user", function () {
    const data = storage.info();
    // 标识
    const token = ref(data.token || "");
@@ -75,7 +74,7 @@
                return res;
            })
            .catch(() => {
                logout();
                return undefined;
            });
    }
cool/utils/comm.ts
@@ -1,12 +1,33 @@
import { orderBy } from "lodash-es";
export const { platform } = uni.getSystemInfoSync();
let systemInfo: any;
export let platform = "";
export let isAndroid = false;
export let isIos = false;
// 是否安卓
export const isAndroid = platform == "android";
// 是否苹果
export const isIos = platform == "ios";
export function getSystemInfo() {
    if (systemInfo) {
        return systemInfo;
    }
    const api = uni as any;
    if (
        typeof api.getWindowInfo === "function" &&
        typeof api.getDeviceInfo === "function" &&
        typeof api.getAppBaseInfo === "function"
    ) {
        systemInfo = {
            ...api.getAppBaseInfo(),
            ...api.getDeviceInfo(),
            ...api.getWindowInfo(),
        };
    } else {
        systemInfo = uni.getSystemInfoSync();
    }
    platform = systemInfo?.platform || "";
    isAndroid = platform == "android";
    isIos = platform == "ios";
    return systemInfo;
}
// 是否小数
export function isDecimal(value: any): boolean {
cool/utils/qrcode.ts
@@ -1,39 +1,173 @@
import QRCode from "qrcode";
const QRCODE_KEY = "recycleQrcodeBase64";
function u32(buf: Uint8Array, offset: number, value: number) {
    buf[offset] = (value >>> 24) & 255;
    buf[offset + 1] = (value >>> 16) & 255;
    buf[offset + 2] = (value >>> 8) & 255;
    buf[offset + 3] = value & 255;
}
export function generateQrcode(text: string): Promise<string> {
    return new Promise((resolve) => {
        QRCode.toDataURL(
            text,
            {
                width: 300,
                margin: 2,
            },
            (err, url) => {
                if (err) {
                    console.error("❌ 二维码生成失败", err);
                    resolve("");
                    return;
                }
                resolve(url);
            },
        );
function crc32(data: Uint8Array) {
    let c = 0xffffffff;
    for (let i = 0; i < data.length; i++) {
        c ^= data[i];
        for (let k = 0; k < 8; k++) {
            c = (c >>> 1) ^ (c & 1 ? 0xedb88320 : 0);
        }
    }
    return (c ^ 0xffffffff) >>> 0;
}
function adler32(data: Uint8Array) {
    let a = 1;
    let b = 0;
    for (let i = 0; i < data.length; i++) {
        a = (a + data[i]) % 65521;
        b = (b + a) % 65521;
    }
    return ((b << 16) | a) >>> 0;
}
function chunk(type: string, data: Uint8Array) {
    const out = new Uint8Array(12 + data.length);
    u32(out, 0, data.length);
    out[4] = type.charCodeAt(0);
    out[5] = type.charCodeAt(1);
    out[6] = type.charCodeAt(2);
    out[7] = type.charCodeAt(3);
    out.set(data, 8);
    u32(out, 8 + data.length, crc32(out.subarray(4, 8 + data.length)));
    return out;
}
function concat(parts: Uint8Array[]) {
    const len = parts.reduce((sum, part) => sum + part.length, 0);
    const out = new Uint8Array(len);
    let offset = 0;
    for (const part of parts) {
        out.set(part, offset);
        offset += part.length;
    }
    return out;
}
function zlibStore(raw: Uint8Array) {
    const blocks: Uint8Array[] = [];
    let offset = 0;
    while (offset < raw.length) {
        const len = Math.min(65535, raw.length - offset);
        const last = offset + len >= raw.length;
        const block = new Uint8Array(5 + len);
        block[0] = last ? 1 : 0;
        block[1] = len & 255;
        block[2] = (len >>> 8) & 255;
        const nlen = (~len) & 0xffff;
        block[3] = nlen & 255;
        block[4] = (nlen >>> 8) & 255;
        block.set(raw.subarray(offset, offset + len), 5);
        blocks.push(block);
        offset += len;
    }
    const bodyLen = blocks.reduce((sum, block) => sum + block.length, 0);
    const out = new Uint8Array(2 + bodyLen + 4);
    out[0] = 0x78;
    out[1] = 0x01;
    let cursor = 2;
    for (const block of blocks) {
        out.set(block, cursor);
        cursor += block.length;
    }
    u32(out, cursor, adler32(raw));
    return out;
}
function bytesToBase64(bytes: Uint8Array) {
    const api = typeof uni === "undefined" ? undefined : uni;
    if (api && typeof api.arrayBufferToBase64 === "function") {
        const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
        return api.arrayBufferToBase64(buffer as ArrayBuffer);
    }
    const table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    let out = "";
    for (let i = 0; i < bytes.length; i += 3) {
        const a = bytes[i];
        const b = i + 1 < bytes.length ? bytes[i + 1] : 0;
        const c = i + 2 < bytes.length ? bytes[i + 2] : 0;
        const n = (a << 16) | (b << 8) | c;
        out += table[(n >> 18) & 63];
        out += table[(n >> 12) & 63];
        out += i + 1 < bytes.length ? table[(n >> 6) & 63] : "=";
        out += i + 2 < bytes.length ? table[n & 63] : "=";
    }
    return out;
}
/** 不依赖浏览器 canvas,直接画出可在小程序 image 中显示的 PNG。 */
export function createQrPng(text: string) {
    const qr = QRCode.create(text, { errorCorrectionLevel: "M" });
    const modules = qr.modules;
    const count = modules.size;
    const margin = 4;
    const scale = 8;
    const size = (count + margin * 2) * scale;
    const raw = new Uint8Array(size * (size + 1));
    let cursor = 0;
    for (let y = 0; y < size; y++) {
        raw[cursor++] = 0;
        const row = Math.floor(y / scale) - margin;
        for (let x = 0; x < size; x++) {
            const col = Math.floor(x / scale) - margin;
            const dark =
                row >= 0 && col >= 0 && row < count && col < count && !!modules.get(row, col);
            raw[cursor++] = dark ? 0 : 255;
        }
    }
    const ihdr = new Uint8Array(13);
    u32(ihdr, 0, size);
    u32(ihdr, 4, size);
    ihdr[8] = 8;
    const signature = Uint8Array.from([137, 80, 78, 71, 13, 10, 26, 10]);
    return concat([
        signature,
        chunk("IHDR", ihdr),
        chunk("IDAT", zlibStore(raw)),
        chunk("IEND", new Uint8Array(0)),
    ]);
}
function fileName(text: string) {
    let hash = 0;
    for (let i = 0; i < text.length; i++) {
        hash = (hash * 31 + text.charCodeAt(i)) >>> 0;
    }
    return hash.toString(16);
}
function savePng(bytes: Uint8Array, text: string) {
    const base64 = bytesToBase64(bytes);
    const dataUrl = `data:image/png;base64,${base64}`;
    const root = (globalThis as any).wx?.env?.USER_DATA_PATH;
    if (!root || typeof uni === "undefined" || typeof uni.getFileSystemManager !== "function") {
        return Promise.resolve(dataUrl);
    }
    const filePath = `${root}/recycle-qr-${fileName(text)}.png`;
    return new Promise<string>((resolve) => {
        uni.getFileSystemManager().writeFile({
            filePath,
            data: base64,
            encoding: "base64",
            success: () => resolve(filePath),
            fail: () => resolve(dataUrl),
        });
    });
}
export function generateRecycleQrcode(text: string): Promise<string> {
    return new Promise((resolve) => {
        const cached = uni.getStorageSync(QRCODE_KEY);
        if (cached) return resolve(cached);
export function generateQrcode(text: string) {
    return savePng(createQrPng(text), text);
}
        generateQrcode(text).then((url) => {
            if (url) {
                uni.setStorageSync(QRCODE_KEY, url);
            }
            resolve(url);
        });
    });
export function generateRecycleQrcode(text: string) {
    return generateQrcode(text);
}
/**
@@ -43,10 +177,3 @@
export function clearQrcodeCache(key: string) {
    uni.removeStorageSync(key);
}
/**
 * 清除所有二维码缓存
 */
export function clearAllQrcodeCache() {
    uni.clearStorageSync();
}
cool/utils/storage.ts
@@ -34,10 +34,7 @@
        uni.setStorageSync(key, value);
        if (expires) {
            uni.setStorageSync(
                `${key}${this.suffix}`,
                Date.parse(String(new Date())) + expires * 1000
            );
            uni.setStorageSync(`${key}${this.suffix}`, Date.now() + expires * 1000);
        }
    },
@@ -46,7 +43,15 @@
     * @param {*} key 关键字
     */
    isExpired(key: string): boolean {
        return uni.getStorageSync(`${key}${this.suffix}`) - Date.parse(String(new Date())) <= 0;
        const raw = uni.getStorageSync(`${key}${this.suffix}`);
        if (raw === "" || raw === null || raw === undefined) {
            return false;
        }
        const dead = Number(raw);
        if (!dead) {
            return true;
        }
        return dead - Date.now() <= 0;
    },
    /**
locale/index.ts
@@ -6,7 +6,7 @@
import fr from "./fr.json";
const i18n = createI18n({
    locale: uni.getLocale(),
    locale: "zh-Hans",
    // 配置后,使用命令 cool-i18n create 翻译,会自动更新 locale 目录
    messages: {
manifest.json
@@ -68,21 +68,21 @@
                    },
                    "weixin" : {
                        "__platform__" : [ "ios", "android" ],
                        "appid" : "wx348f72db1512fa2e",
                        "appid" : "wx88c344d1b0db243a",
                        "UniversalLinks" : ""
                    }
                },
                "ad" : {},
                "share" : {
                    "weixin" : {
                        "appid" : "wx348f72db1512fa2e",
                        "appid" : "wx88c344d1b0db243a",
                        "UniversalLinks" : ""
                    }
                },
                "oauth" : {
                    "weixin" : {
                        "appid" : "wx348f72db1512fa2e",
                        "appsecret" : "test",
                        "appid" : "wx88c344d1b0db243a",
                        "appsecret" : "0188e38c7b797cef569a391f37272891",
                        "UniversalLinks" : ""
                    },
                    "apple" : {}
@@ -154,7 +154,10 @@
        "appid" : "wx88c344d1b0db243a",
        "setting" : {
            "urlCheck" : true,
            "es6" : false
            "es6" : true,
            "enhance" : true,
            "postcss" : true,
            "minified" : true
        },
        "usingComponents" : true
    },
pages/goods/components/filter-bar.vue
@@ -324,7 +324,8 @@
            overflow: hidden;
            flex: 1;
            button {
            .reset,
            .confirm {
                height: 64rpx;
                line-height: 64rpx;
                padding: 0;
@@ -337,12 +338,12 @@
                &::after {
                    border: 0;
                }
            }
                &.reset {
                    color: $cl-color-primary;
                    background-color: #fff;
                    border-radius: 80rpx 0 80rpx 80rpx;
                }
            .reset {
                color: $cl-color-primary;
                background-color: #fff;
                border-radius: 80rpx 0 80rpx 80rpx;
            }
        }
    }
pages/goods/detail.vue
@@ -67,6 +67,7 @@
<script lang="ts" setup>
import { reactive, ref } from "vue";
import { useCool } from "/@/cool";
import { getSystemInfo } from "/@/cool/utils";
import { onPageScroll, onReady } from "@dcloudio/uni-app";
import { last } from "lodash-es";
import { useShoppingCart, useSpec } from "/@/hooks";
@@ -78,7 +79,7 @@
const { service, router, refs, setRefs } = useCool();
const spec = useSpec();
const spCart = useShoppingCart();
const { statusBarHeight = 0 } = uni.getSystemInfoSync();
const { statusBarHeight = 0 } = getSystemInfo();
// 顶部选项栏
const tabs = reactive({
pages/recycle/banner.vue
@@ -72,10 +72,8 @@
                    <text class="qrcode-title">我的回收码</text>
                    <text class="qrcode-desc">向回收员出示此码完成回收</text>
                    <view class="qrcode-image-wrap">
                        <image class="qrcode-image" :src="qrcodeUrl" mode="aspectFit" />
                        <image v-if="qrcodeUrl" class="qrcode-image" :src="qrcodeUrl" mode="aspectFit" />
                    </view>
                    <text class="qrcode-code">回收码:{{ userCode }}</text>
                    <view class="qrcode-close" @tap="showQrcode = false">
                        <cl-icon name="close" :size="36" color="#999" />
                    </view>
@@ -114,7 +112,6 @@
    });
    const showQrcode = ref(false);
    const userCode = ref("");
    const qrcodeUrl = ref("");
    // ========== 初始化加载 ==========
@@ -126,14 +123,18 @@
    async function loadDashboard() {
        try {
            const unionid = user.info?.phone||"18918594752";
            service.user.info.dashboard({ unionid:unionid  }).then((res) => {
                console.log("【再生资源看板数据】", res);
                dashboard.value = {
                    carbonPoints: res.carbonPoints ?? 0,
                    recycleCount: res.recycleCount ?? 0,
                    overPercent: res.overPercent ?? 0,
                };
            });
            service.user.info
                .dashboard({ unionid })
                .then((res) => {
                    dashboard.value = {
                        carbonPoints: res.carbonPoints ?? 0,
                        recycleCount: res.recycleCount ?? 0,
                        overPercent: res.overPercent ?? 0,
                    };
                })
                .catch((err) => {
                    console.error("【再生资源看板加载失败】", err);
                });
        } catch (e) {
            console.error("【再生资源看板加载失败】", e);
        }
@@ -141,15 +142,17 @@
    /** 加载用户回收码 */
    async function loadMyCode() {
        const phone = user.info?.phone;
        if (!phone) {
            uni.showToast({ title: "请先授权手机号", icon: "none" });
            showQrcode.value = false;
            return;
        }
        try {
            const phone = user.info?.phone ?? "18918594752";
            const res = await generateRecycleQrcode(phone);
            userCode.value = phone;
            qrcodeUrl.value = res;
            qrcodeUrl.value = await generateRecycleQrcode(phone);
        } catch (e) {
            console.error("【回收码加载失败】", e);
            // 兜底:没有接口时给个默认值
            userCode.value = "RC-LOADING";
            uni.showToast({ title: "回收码生成失败", icon: "none" });
        }
    }
@@ -347,12 +350,6 @@
        .qrcode-image {
            width: 100%;
            height: 100%;
        }
        .qrcode-code {
            font-size: 26rpx;
            color: #666;
            letter-spacing: 2rpx;
        }
        .qrcode-close {
pages/recycle/home.vue
@@ -72,14 +72,8 @@
                    <text class="qrcode-title">我的回收码</text>
                    <text class="qrcode-desc">向回收员出示此码完成回收</text>
                    <view class="qrcode-image-wrap">
                        <!--<image class="qrcode-image" :src="qrcodeUrl" mode="aspectFit" /> -->
                        <canvas
                            canvas-id="recycleQrcode"
                            id="recycleQrcode"
                            style="width: 300px; height: 300px;"
                        />
                        <image v-if="qrcodeUrl" class="qrcode-image" :src="qrcodeUrl" mode="aspectFit" />
                    </view>
                    <text class="qrcode-code">回收码:{{ userCode }}</text>
                    <view class="qrcode-close" @tap="showQrcode = false">
                        <cl-icon name="close" :size="36" color="#999" />
                    </view>
@@ -120,7 +114,6 @@
    });
    const showQrcode = ref(false);
    const userCode = ref("");
    const qrcodeUrl = ref("");
    // ========== 初始化加载 ==========
@@ -132,14 +125,18 @@
    async function loadDashboard() {
        try {
            const unionid = user.info?.phone||"18918594752";
            service.user.info.dashboard({ unionid:unionid  }).then((res) => {
                console.log("【再生资源看板数据】", res);
                dashboard.value = {
                    carbonPoints: res.carbonPoints ?? 0,
                    recycleCount: res.recycleCount ?? 0,
                    overPercent: res.overPercent ?? 0,
                };
            });
            service.user.info
                .dashboard({ unionid })
                .then((res) => {
                    dashboard.value = {
                        carbonPoints: res.carbonPoints ?? 0,
                        recycleCount: res.recycleCount ?? 0,
                        overPercent: res.overPercent ?? 0,
                    };
                })
                .catch((err) => {
                    console.error("【再生资源看板加载失败】", err);
                });
        } catch (e) {
            console.error("【再生资源看板加载失败】", e);
        }
@@ -147,16 +144,17 @@
    /** 加载用户回收码 */
    async function loadMyCode() {
        const phone = user.info?.phone;
        if (!phone) {
            uni.showToast({ title: "请先授权手机号", icon: "none" });
            showQrcode.value = false;
            return;
        }
        try {
            const phone = user.info?.phone ?? "18918594752";
            const res = await generateRecycleQrcode(phone);
            console.log("【我的回收码】", res);
            userCode.value = phone;
            qrcodeUrl.value = res;
            qrcodeUrl.value = await generateRecycleQrcode(phone);
        } catch (e) {
            console.error("【回收码加载失败】", e);
            // 兜底:没有接口时给个默认值
            userCode.value = "RC-LOADING";
            uni.showToast({ title: "回收码生成失败", icon: "none" });
        }
    }
@@ -354,12 +352,6 @@
        .qrcode-image {
            width: 100%;
            height: 100%;
        }
        .qrcode-code {
            font-size: 26rpx;
            color: #666;
            letter-spacing: 2rpx;
        }
        .qrcode-close {
uni_modules/cool-ui/components/cl-guide/cl-guide.vue
@@ -70,6 +70,7 @@
    watch,
    type PropType,
} from "vue";
import { getSystemInfo } from "/@/cool/utils";
export default defineComponent({
    name: "cl-guide",
@@ -88,7 +89,7 @@
    },
    setup(props, { emit }) {
        const { windowHeight } = uni.getSystemInfoSync();
        const { windowHeight } = getSystemInfo();
        const { proxy }: any = getCurrentInstance();
        // 序号
uni_modules/cool-ui/components/cl-page/cl-page.vue
@@ -50,7 +50,7 @@
<script lang="ts">
import { computed, defineComponent, reactive, getCurrentInstance, onMounted } from "vue";
import { useApp, useCool } from "/@/cool";
import { parseRpx } from "/@/cool/utils";
import { getSystemInfo, parseRpx } from "/@/cool/utils";
import { isString } from "lodash-es";
import { useI18n } from "vue-i18n";
@@ -86,7 +86,7 @@
            screenHeight,
            windowHeight,
            safeAreaInsets,
        } = uni.getSystemInfoSync();
        } = getSystemInfo();
        const { proxy }: any = getCurrentInstance();
uni_modules/cool-ui/components/cl-popup/cl-popup.vue
@@ -46,10 +46,8 @@
<script lang="ts">
import { computed, defineComponent, ref, watch, type PropType } from "vue";
import { parseRpx } from "/@/cool/utils";
import { getSystemInfo, parseRpx } from "/@/cool/utils";
import { router } from "/@/cool";
const { statusBarHeight = 0 } = uni.getSystemInfoSync();
let id = 1;
@@ -115,6 +113,7 @@
    emits: ["update:modelValue", "open", "opened", "close", "closed"],
    setup(props, { emit }) {
        const { statusBarHeight = 0 } = getSystemInfo();
        // 是否可见
        const visible = ref(false);
uni_modules/cool-ui/components/cl-status-bar/cl-status-bar.vue
@@ -22,6 +22,7 @@
<script lang="ts">
import { computed, defineComponent } from "vue";
import { useCool } from "/@/cool";
import { getSystemInfo } from "/@/cool/utils";
export default defineComponent({
    name: "cl-status-bar",
@@ -37,7 +38,7 @@
    },
    setup(props) {
        const { statusBarHeight } = uni.getSystemInfoSync();
        const { statusBarHeight } = getSystemInfo();
        const { router } = useCool();
        // 状态栏高度
uni_modules/cool-ui/components/cl-sticky/cl-sticky.vue
@@ -11,8 +11,7 @@
<script lang="ts">
import { computed, defineComponent } from "vue";
import { useCool } from "/@/cool";
const { statusBarHeight } = uni.getSystemInfoSync();
import { getSystemInfo } from "/@/cool/utils";
export default defineComponent({
    name: "cl-sticky",
@@ -35,6 +34,7 @@
    },
    setup(props) {
        const { statusBarHeight = 0 } = getSystemInfo();
        const { router } = useCool();
        // 吸顶高度
uni_modules/cool-ui/components/cl-topbar/cl-topbar.vue
@@ -46,9 +46,7 @@
<script lang="ts">
import { computed, defineComponent, onMounted, ref } from "vue";
import { router } from "/@/cool";
import { isIos } from "/@/cool/utils";
const { statusBarHeight } = uni.getSystemInfoSync();
import { getSystemInfo, isIos } from "/@/cool/utils";
export default defineComponent({
    name: "cl-topbar",
@@ -92,6 +90,7 @@
    },
    setup(props, { emit }) {
        const { statusBarHeight = 0 } = getSystemInfo();
        // 是否页面栈的第一个
        const isFirst = ref(false);
uni_modules/mp-html/components/mp-html/node/node.vue
@@ -125,7 +125,7 @@
    return {
      ctrl: {},
      // #ifdef MP-WEIXIN
      isiOS: uni.getSystemInfoSync().system.includes('iOS')
      isiOS: ((uni.getDeviceInfo ? uni.getDeviceInfo() : uni.getSystemInfoSync()).system || '').includes('iOS')
      // #endif
    }
  },
uni_modules/mp-html/components/mp-html/parser.js
@@ -81,7 +81,9 @@
  // #ifdef MP-WEIXIN
  system
  // #endif
} = uni.getSystemInfoSync()
} = (uni.getWindowInfo && uni.getDeviceInfo
  ? { ...uni.getDeviceInfo(), ...uni.getWindowInfo() }
  : uni.getSystemInfoSync())
const blankChar = makeMap(' ,\r,\n,\t,\f')
let idIndex = 0
vite.config.ts
@@ -54,6 +54,11 @@
                overlay: true,
            },
        },
        define: {
            __VUE_I18N_FULL_INSTALL__: true,
            __VUE_I18N_LEGACY_API__: true,
            __INTLIFY_PROD_DEVTOOLS__: false,
        },
        resolve: {
            alias: {
                "/@": resolve("./"),