| | |
| | | <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(() => { |
| | |
| | | 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(); |
| | |
| | | |
| | | const visible = computed(() => { |
| | | tick.value; |
| | | return !authState.dismissed && !user.info?.phone; |
| | | return authState.ready && !authState.dismissed && (!user.token || !user.info?.phone); |
| | | }); |
| | | |
| | | function later() { |
| | |
| | | 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; |
| | | const task = new Promise<string>((resolve) => { |
| | | uni.login({ |
| | | provider: "weixin", |
| | | success: (res) => resolve(res.code || ""), |
| | | fail: () => resolve(""), |
| | | }); |
| | | }) |
| | | : service.user.login.miniPhone(payload).then(async (res) => { |
| | | .then((loginCode) => { |
| | | return service.user.login.miniPhone({ |
| | | ...phonePayload(detail), |
| | | loginCode, |
| | | }); |
| | | }) |
| | | .then(async (res) => { |
| | | user.setToken(res); |
| | | const info = await service.user.info.person(); |
| | | if (info) { |
| | | user.set(info); |
| | | let info = user.info; |
| | | try { |
| | | info = (await service.user.info.person()) || info; |
| | | } catch (err) { |
| | | console.error(err); |
| | | } |
| | | return info?.phone as string | undefined; |
| | | const phone = info?.phone || res.phone; |
| | | user.set({ |
| | | ...(info || {}), |
| | | phone, |
| | | }); |
| | | return phone as string | undefined; |
| | | }); |
| | | |
| | | task |
| | |
| | | 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"), |
| | | }, |
| | |
| | | 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 |
| | |
| | | // #endif |
| | | |
| | | export function useWx() { |
| | | const { platform } = uni.getSystemInfoSync(); |
| | | const { platform } = getSystemInfo(); |
| | | const { t } = useI18n(); |
| | | |
| | | // 授权码 |
| New file |
| | |
| | | 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; |
| | | } |
| | |
| | | // 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(); |
| | | |
| | |
| | | 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) { |
| | |
| | | // 判断 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; |
| | | } |
| | | |
| | | // 是否在刷新中 |
| | |
| | | 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)); |
| | | }); |
| | | } |
| | | |
| | |
| | | import { service } from "../service"; |
| | | import type { User } from "../types"; |
| | | |
| | | // 本地缓存 |
| | | const useUserStore = defineStore("user", function () { |
| | | const data = storage.info(); |
| | | |
| | | const useUserStore = defineStore("user", function () { |
| | | // 标识 |
| | | const token = ref(data.token || ""); |
| | | |
| | |
| | | return res; |
| | | }) |
| | | .catch(() => { |
| | | logout(); |
| | | return undefined; |
| | | }); |
| | | } |
| | | |
| | |
| | | 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 { |
| | |
| | | import QRCode from "qrcode"; |
| | | |
| | | const QRCODE_KEY = "recycleQrcodeBase64"; |
| | | |
| | | 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; |
| | | 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; |
| | | } |
| | | 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); |
| | | |
| | | generateQrcode(text).then((url) => { |
| | | if (url) { |
| | | uni.setStorageSync(QRCODE_KEY, url); |
| | | export function generateQrcode(text: string) { |
| | | return savePng(createQrPng(text), text); |
| | | } |
| | | resolve(url); |
| | | }); |
| | | }); |
| | | |
| | | export function generateRecycleQrcode(text: string) { |
| | | return generateQrcode(text); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | export function clearQrcodeCache(key: string) { |
| | | uni.removeStorageSync(key); |
| | | } |
| | | |
| | | /** |
| | | * 清除所有二维码缓存 |
| | | */ |
| | | export function clearAllQrcodeCache() { |
| | | uni.clearStorageSync(); |
| | | } |
| | |
| | | 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); |
| | | } |
| | | }, |
| | | |
| | |
| | | * @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; |
| | | }, |
| | | |
| | | /** |
| | |
| | | import fr from "./fr.json"; |
| | | |
| | | const i18n = createI18n({ |
| | | locale: uni.getLocale(), |
| | | locale: "zh-Hans", |
| | | |
| | | // 配置后,使用命令 cool-i18n create 翻译,会自动更新 locale 目录 |
| | | messages: { |
| | |
| | | }, |
| | | "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" : {} |
| | |
| | | "appid" : "wx88c344d1b0db243a", |
| | | "setting" : { |
| | | "urlCheck" : true, |
| | | "es6" : false |
| | | "es6" : true, |
| | | "enhance" : true, |
| | | "postcss" : true, |
| | | "minified" : true |
| | | }, |
| | | "usingComponents" : true |
| | | }, |
| | |
| | | overflow: hidden; |
| | | flex: 1; |
| | | |
| | | button { |
| | | .reset, |
| | | .confirm { |
| | | height: 64rpx; |
| | | line-height: 64rpx; |
| | | padding: 0; |
| | |
| | | &::after { |
| | | border: 0; |
| | | } |
| | | } |
| | | |
| | | &.reset { |
| | | .reset { |
| | | color: $cl-color-primary; |
| | | background-color: #fff; |
| | | border-radius: 80rpx 0 80rpx 80rpx; |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | <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"; |
| | |
| | | const { service, router, refs, setRefs } = useCool(); |
| | | const spec = useSpec(); |
| | | const spCart = useShoppingCart(); |
| | | const { statusBarHeight = 0 } = uni.getSystemInfoSync(); |
| | | const { statusBarHeight = 0 } = getSystemInfo(); |
| | | |
| | | // 顶部选项栏 |
| | | const tabs = reactive({ |
| | |
| | | <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> |
| | |
| | | }); |
| | | |
| | | const showQrcode = ref(false); |
| | | const userCode = ref(""); |
| | | const qrcodeUrl = ref(""); |
| | | |
| | | // ========== 初始化加载 ========== |
| | |
| | | async function loadDashboard() { |
| | | try { |
| | | const unionid = user.info?.phone||"18918594752"; |
| | | service.user.info.dashboard({ unionid:unionid }).then((res) => { |
| | | console.log("【再生资源看板数据】", res); |
| | | 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); |
| | |
| | | |
| | | /** 加载用户回收码 */ |
| | | 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" }); |
| | | } |
| | | } |
| | | |
| | |
| | | .qrcode-image { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | .qrcode-code { |
| | | font-size: 26rpx; |
| | | color: #666; |
| | | letter-spacing: 2rpx; |
| | | } |
| | | |
| | | .qrcode-close { |
| | |
| | | <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> |
| | |
| | | }); |
| | | |
| | | const showQrcode = ref(false); |
| | | const userCode = ref(""); |
| | | const qrcodeUrl = ref(""); |
| | | |
| | | // ========== 初始化加载 ========== |
| | |
| | | async function loadDashboard() { |
| | | try { |
| | | const unionid = user.info?.phone||"18918594752"; |
| | | service.user.info.dashboard({ unionid:unionid }).then((res) => { |
| | | console.log("【再生资源看板数据】", res); |
| | | 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); |
| | |
| | | |
| | | /** 加载用户回收码 */ |
| | | 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" }); |
| | | } |
| | | } |
| | | |
| | |
| | | .qrcode-image { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | .qrcode-code { |
| | | font-size: 26rpx; |
| | | color: #666; |
| | | letter-spacing: 2rpx; |
| | | } |
| | | |
| | | .qrcode-close { |
| | |
| | | watch, |
| | | type PropType, |
| | | } from "vue"; |
| | | import { getSystemInfo } from "/@/cool/utils"; |
| | | |
| | | export default defineComponent({ |
| | | name: "cl-guide", |
| | |
| | | }, |
| | | |
| | | setup(props, { emit }) { |
| | | const { windowHeight } = uni.getSystemInfoSync(); |
| | | const { windowHeight } = getSystemInfo(); |
| | | const { proxy }: any = getCurrentInstance(); |
| | | |
| | | // 序号 |
| | |
| | | <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"; |
| | | |
| | |
| | | screenHeight, |
| | | windowHeight, |
| | | safeAreaInsets, |
| | | } = uni.getSystemInfoSync(); |
| | | } = getSystemInfo(); |
| | | |
| | | const { proxy }: any = getCurrentInstance(); |
| | | |
| | |
| | | |
| | | <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; |
| | | |
| | |
| | | emits: ["update:modelValue", "open", "opened", "close", "closed"], |
| | | |
| | | setup(props, { emit }) { |
| | | const { statusBarHeight = 0 } = getSystemInfo(); |
| | | // 是否可见 |
| | | const visible = ref(false); |
| | | |
| | |
| | | <script lang="ts"> |
| | | import { computed, defineComponent } from "vue"; |
| | | import { useCool } from "/@/cool"; |
| | | import { getSystemInfo } from "/@/cool/utils"; |
| | | |
| | | export default defineComponent({ |
| | | name: "cl-status-bar", |
| | |
| | | }, |
| | | |
| | | setup(props) { |
| | | const { statusBarHeight } = uni.getSystemInfoSync(); |
| | | const { statusBarHeight } = getSystemInfo(); |
| | | const { router } = useCool(); |
| | | |
| | | // 状态栏高度 |
| | |
| | | <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", |
| | |
| | | }, |
| | | |
| | | setup(props) { |
| | | const { statusBarHeight = 0 } = getSystemInfo(); |
| | | const { router } = useCool(); |
| | | |
| | | // 吸顶高度 |
| | |
| | | <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", |
| | |
| | | }, |
| | | |
| | | setup(props, { emit }) { |
| | | const { statusBarHeight = 0 } = getSystemInfo(); |
| | | // 是否页面栈的第一个 |
| | | const isFirst = ref(false); |
| | | |
| | |
| | | return { |
| | | ctrl: {}, |
| | | // #ifdef MP-WEIXIN |
| | | isiOS: uni.getSystemInfoSync().system.includes('iOS') |
| | | isiOS: ((uni.getDeviceInfo ? uni.getDeviceInfo() : uni.getSystemInfoSync()).system || '').includes('iOS') |
| | | // #endif |
| | | } |
| | | }, |
| | |
| | | // #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 |
| | | |
| | |
| | | overlay: true, |
| | | }, |
| | | }, |
| | | define: { |
| | | __VUE_I18N_FULL_INSTALL__: true, |
| | | __VUE_I18N_LEGACY_API__: true, |
| | | __INTLIFY_PROD_DEVTOOLS__: false, |
| | | }, |
| | | resolve: { |
| | | alias: { |
| | | "/@": resolve("./"), |