diff --git a/Cunkebao/.env.development b/Cunkebao/.env.development index c008d630..2ddb67f5 100644 --- a/Cunkebao/.env.development +++ b/Cunkebao/.env.development @@ -1,4 +1,6 @@ # 基础环境变量示例 VITE_API_BASE_URL=http://www.yishi.com +VITE_API_BASE_URL2=https://kf.quwanzhi.com:9991 +VITE_API_WS_URL=wss://kf.quwanzhi.com:9993 # VITE_API_BASE_URL=https://ckbapi.quwanzhi.com VITE_APP_TITLE=存客宝 diff --git a/Cunkebao/.env.production b/Cunkebao/.env.production index d71cee1d..838935bb 100644 --- a/Cunkebao/.env.production +++ b/Cunkebao/.env.production @@ -1,4 +1,6 @@ # 基础环境变量示例 VITE_API_BASE_URL=https://ckbapi.quwanzhi.com +VITE_API_BASE_URL2=https://kf.quwanzhi.com:9991 +VITE_API_WS_URL=wss://kf.quwanzhi.com:9993 # VITE_API_BASE_URL=http://www.yishi.com VITE_APP_TITLE=存客宝 diff --git a/Cunkebao/dist/.vite/manifest.json b/Cunkebao/dist/.vite/manifest.json index 4f0c258e..1fc771a8 100644 --- a/Cunkebao/dist/.vite/manifest.json +++ b/Cunkebao/dist/.vite/manifest.json @@ -1,18 +1,14 @@ { - "_charts-TuAbbBZ5.js": { - "file": "assets/charts-TuAbbBZ5.js", + "_charts-CLRTJ7Uf.js": { + "file": "assets/charts-CLRTJ7Uf.js", "name": "charts", "imports": [ - "_ui-D1w-jetn.js", + "_ui-BFvqeNzU.js", "_vendor-2vc8h_ct.js" ] }, - "_ui-D0C0OGrH.css": { - "file": "assets/ui-D0C0OGrH.css", - "src": "_ui-D0C0OGrH.css" - }, - "_ui-D1w-jetn.js": { - "file": "assets/ui-D1w-jetn.js", + "_ui-BFvqeNzU.js": { + "file": "assets/ui-BFvqeNzU.js", "name": "ui", "imports": [ "_vendor-2vc8h_ct.js" @@ -21,6 +17,10 @@ "assets/ui-D0C0OGrH.css" ] }, + "_ui-D0C0OGrH.css": { + "file": "assets/ui-D0C0OGrH.css", + "src": "_ui-D0C0OGrH.css" + }, "_utils-6WF66_dS.js": { "file": "assets/utils-6WF66_dS.js", "name": "utils", @@ -33,18 +33,18 @@ "name": "vendor" }, "index.html": { - "file": "assets/index-D3HSx5Yt.js", + "file": "assets/index-C48GlG01.js", "name": "index", "src": "index.html", "isEntry": true, "imports": [ "_vendor-2vc8h_ct.js", - "_ui-D1w-jetn.js", + "_ui-BFvqeNzU.js", "_utils-6WF66_dS.js", - "_charts-TuAbbBZ5.js" + "_charts-CLRTJ7Uf.js" ], "css": [ - "assets/index-B0SB167P.css" + "assets/index-Ta4vyxDJ.css" ] } } \ No newline at end of file diff --git a/Cunkebao/dist/index.html b/Cunkebao/dist/index.html index f79f7013..8a58e165 100644 --- a/Cunkebao/dist/index.html +++ b/Cunkebao/dist/index.html @@ -10,14 +10,14 @@ } - - + + - + - + - +
diff --git a/Cunkebao/index.html b/Cunkebao/index.html index 92ab92a7..16de6c67 100644 --- a/Cunkebao/index.html +++ b/Cunkebao/index.html @@ -10,7 +10,7 @@ } - +
diff --git a/Cunkebao/src/android-polyfills.ts b/Cunkebao/src/android-polyfills.ts new file mode 100644 index 00000000..583fd397 --- /dev/null +++ b/Cunkebao/src/android-polyfills.ts @@ -0,0 +1,352 @@ +// Android 专用 polyfill - 解决Android 7等低版本系统的兼容性问题 + +// 检测是否为Android设备 +const isAndroid = () => { + return /Android/i.test(navigator.userAgent); +}; + +// 检测Android版本 +const getAndroidVersion = () => { + const match = navigator.userAgent.match(/Android\s+(\d+)/); + return match ? parseInt(match[1]) : 0; +}; + +// 检测是否为低版本Android +const isLowVersionAndroid = () => { + const version = getAndroidVersion(); + return version <= 7; // Android 7及以下版本 +}; + +// 只在Android设备上执行polyfill +if (isAndroid() && isLowVersionAndroid()) { + console.log("检测到低版本Android系统,启用兼容性polyfill"); + + // 修复Array.prototype.includes在Android WebView中的问题 + if (!Array.prototype.includes) { + Array.prototype.includes = function (searchElement, fromIndex) { + if (this == null) { + throw new TypeError('"this" is null or not defined'); + } + var o = Object(this); + var len = o.length >>> 0; + if (len === 0) { + return false; + } + var n = fromIndex | 0; + var k = Math.max(n >= 0 ? n : len + n, 0); + while (k < len) { + if (o[k] === searchElement) { + return true; + } + k++; + } + return false; + }; + } + + // 修复String.prototype.includes在Android WebView中的问题 + if (!String.prototype.includes) { + String.prototype.includes = function (search, start) { + if (typeof start !== "number") { + start = 0; + } + if (start + search.length > this.length) { + return false; + } else { + return this.indexOf(search, start) !== -1; + } + }; + } + + // 修复String.prototype.startsWith在Android WebView中的问题 + if (!String.prototype.startsWith) { + String.prototype.startsWith = function (searchString, position) { + position = position || 0; + return this.substr(position, searchString.length) === searchString; + }; + } + + // 修复String.prototype.endsWith在Android WebView中的问题 + if (!String.prototype.endsWith) { + String.prototype.endsWith = function (searchString, length) { + if (length === undefined || length > this.length) { + length = this.length; + } + return ( + this.substring(length - searchString.length, length) === searchString + ); + }; + } + + // 修复Array.prototype.find在Android WebView中的问题 + if (!Array.prototype.find) { + Array.prototype.find = function (predicate) { + if (this == null) { + throw new TypeError("Array.prototype.find called on null or undefined"); + } + if (typeof predicate !== "function") { + throw new TypeError("predicate must be a function"); + } + var list = Object(this); + var length = parseInt(list.length) || 0; + var thisArg = arguments[1]; + for (var i = 0; i < length; i++) { + var element = list[i]; + if (predicate.call(thisArg, element, i, list)) { + return element; + } + } + return undefined; + }; + } + + // 修复Array.prototype.findIndex在Android WebView中的问题 + if (!Array.prototype.findIndex) { + Array.prototype.findIndex = function (predicate) { + if (this == null) { + throw new TypeError( + "Array.prototype.findIndex called on null or undefined", + ); + } + if (typeof predicate !== "function") { + throw new TypeError("predicate must be a function"); + } + var list = Object(this); + var length = parseInt(list.length) || 0; + var thisArg = arguments[1]; + for (var i = 0; i < length; i++) { + var element = list[i]; + if (predicate.call(thisArg, element, i, list)) { + return i; + } + } + return -1; + }; + } + + // 修复Object.assign在Android WebView中的问题 + if (typeof Object.assign !== "function") { + Object.assign = function (target) { + if (target == null) { + throw new TypeError("Cannot convert undefined or null to object"); + } + var to = Object(target); + for (var index = 1; index < arguments.length; index++) { + var nextSource = arguments[index]; + if (nextSource != null) { + for (var nextKey in nextSource) { + if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { + to[nextKey] = nextSource[nextKey]; + } + } + } + } + return to; + }; + } + + // 修复Array.from在Android WebView中的问题 + if (!Array.from) { + Array.from = (function () { + var toStr = Object.prototype.toString; + var isCallable = function (fn) { + return ( + typeof fn === "function" || toStr.call(fn) === "[object Function]" + ); + }; + var toInteger = function (value) { + var number = Number(value); + if (isNaN(number)) { + return 0; + } + if (number === 0 || !isFinite(number)) { + return number; + } + return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number)); + }; + var maxSafeInteger = Math.pow(2, 53) - 1; + var toLength = function (value) { + var len = toInteger(value); + return Math.min(Math.max(len, 0), maxSafeInteger); + }; + return function from(arrayLike) { + var C = this; + var items = Object(arrayLike); + if (arrayLike == null) { + throw new TypeError( + "Array.from requires an array-like object - not null or undefined", + ); + } + var mapFunction = arguments.length > 1 ? arguments[1] : void undefined; + var T; + if (typeof mapFunction !== "undefined") { + if (typeof mapFunction !== "function") { + throw new TypeError( + "Array.from: when provided, the second argument must be a function", + ); + } + if (arguments.length > 2) { + T = arguments[2]; + } + } + var len = toLength(items.length); + var A = isCallable(C) ? Object(new C(len)) : new Array(len); + var k = 0; + var kValue; + while (k < len) { + kValue = items[k]; + if (mapFunction) { + A[k] = + typeof T === "undefined" + ? mapFunction(kValue, k) + : mapFunction.call(T, kValue, k); + } else { + A[k] = kValue; + } + k += 1; + } + A.length = len; + return A; + }; + })(); + } + + // 修复requestAnimationFrame在Android WebView中的问题 + if (!window.requestAnimationFrame) { + window.requestAnimationFrame = function (callback) { + return setTimeout(function () { + callback(Date.now()); + }, 1000 / 60); + }; + } + + if (!window.cancelAnimationFrame) { + window.cancelAnimationFrame = function (id) { + clearTimeout(id); + }; + } + + // 修复IntersectionObserver在Android WebView中的问题 + if (!window.IntersectionObserver) { + window.IntersectionObserver = function (callback, options) { + this.callback = callback; + this.options = options || {}; + this.observers = []; + + this.observe = function (element) { + this.observers.push(element); + // 简单的实现,实际项目中可能需要更复杂的逻辑 + setTimeout(() => { + this.callback([ + { + target: element, + isIntersecting: true, + intersectionRatio: 1, + }, + ]); + }, 100); + }; + + this.unobserve = function (element) { + var index = this.observers.indexOf(element); + if (index > -1) { + this.observers.splice(index, 1); + } + }; + + this.disconnect = function () { + this.observers = []; + }; + }; + } + + // 修复ResizeObserver在Android WebView中的问题 + if (!window.ResizeObserver) { + window.ResizeObserver = function (callback) { + this.callback = callback; + this.observers = []; + + this.observe = function (element) { + this.observers.push(element); + }; + + this.unobserve = function (element) { + var index = this.observers.indexOf(element); + if (index > -1) { + this.observers.splice(index, 1); + } + }; + + this.disconnect = function () { + this.observers = []; + }; + }; + } + + // 修复URLSearchParams在Android WebView中的问题 + if (!window.URLSearchParams) { + window.URLSearchParams = function (init) { + this.params = {}; + + if (init) { + if (typeof init === "string") { + if (init.charAt(0) === "?") { + init = init.slice(1); + } + var pairs = init.split("&"); + for (var i = 0; i < pairs.length; i++) { + var pair = pairs[i].split("="); + var key = decodeURIComponent(pair[0]); + var value = decodeURIComponent(pair[1] || ""); + this.append(key, value); + } + } + } + + this.append = function (name, value) { + if (!this.params[name]) { + this.params[name] = []; + } + this.params[name].push(value); + }; + + this.get = function (name) { + return this.params[name] ? this.params[name][0] : null; + }; + + this.getAll = function (name) { + return this.params[name] || []; + }; + + this.has = function (name) { + return !!this.params[name]; + }; + + this.set = function (name, value) { + this.params[name] = [value]; + }; + + this.delete = function (name) { + delete this.params[name]; + }; + + this.toString = function () { + var pairs = []; + for (var key in this.params) { + if (this.params.hasOwnProperty(key)) { + for (var i = 0; i < this.params[key].length; i++) { + pairs.push( + encodeURIComponent(key) + + "=" + + encodeURIComponent(this.params[key][i]), + ); + } + } + } + return pairs.join("&"); + }; + }; + } + + console.log("Android兼容性polyfill已加载完成"); +} diff --git a/Cunkebao/src/api/request2.ts b/Cunkebao/src/api/request2.ts new file mode 100644 index 00000000..cacbdaf7 --- /dev/null +++ b/Cunkebao/src/api/request2.ts @@ -0,0 +1,81 @@ +import axios, { + AxiosInstance, + AxiosRequestConfig, + Method, + AxiosResponse, +} from "axios"; +import { Toast } from "antd-mobile"; +import { useUserStore } from "@/store/module/user"; +const DEFAULT_DEBOUNCE_GAP = 1000; +const debounceMap = new Map(); + +interface RequestConfig extends AxiosRequestConfig { + headers: { + Client?: string; + "Content-Type"?: string; + }; +} + +const instance: AxiosInstance = axios.create({ + baseURL: (import.meta as any).env?.VITE_API_BASE_URL2 || "/api", + timeout: 20000, + headers: { + "Content-Type": "application/json", + Client: "kefu-client", + }, +}); + +instance.interceptors.request.use((config: any) => { + // 在每次请求时动态获取最新的 token2 + const { token2 } = useUserStore.getState(); + + if (token2) { + config.headers = config.headers || {}; + config.headers["Authorization"] = `bearer ${token2}`; + } + return config; +}); + +instance.interceptors.response.use( + (res: AxiosResponse) => { + return res.data; + }, + err => { + Toast.show({ content: err.message || "网络异常", position: "top" }); + return Promise.reject(err); + }, +); + +export function request( + url: string, + data?: any, + method: Method = "GET", + config?: RequestConfig, + debounceGap?: number, +): Promise { + const gap = + typeof debounceGap === "number" ? debounceGap : DEFAULT_DEBOUNCE_GAP; + const key = `${method}_${url}_${JSON.stringify(data)}`; + const now = Date.now(); + const last = debounceMap.get(key) || 0; + if (gap > 0 && now - last < gap) { + // Toast.show({ content: '请求过于频繁,请稍后再试', position: 'top' }); + return Promise.reject("请求过于频繁,请稍后再试"); + } + debounceMap.set(key, now); + + const axiosConfig: RequestConfig = { + url, + method, + ...config, + }; + + if (method.toUpperCase() === "GET") { + axiosConfig.params = data; + } else { + axiosConfig.data = data; + } + return instance(axiosConfig); +} + +export default request; diff --git a/Cunkebao/src/components/AccountSelection/data.ts b/Cunkebao/src/components/AccountSelection/data.ts index 8c65ee8d..c0ce4343 100644 --- a/Cunkebao/src/components/AccountSelection/data.ts +++ b/Cunkebao/src/components/AccountSelection/data.ts @@ -31,4 +31,5 @@ export interface AccountSelectionProps { showSelectedList?: boolean; readonly?: boolean; onConfirm?: (selectedOptions: AccountItem[]) => void; + accountGroups?: any[]; // 传递账号组数据 } diff --git a/Cunkebao/src/components/AndroidCompatibilityCheck.tsx b/Cunkebao/src/components/AndroidCompatibilityCheck.tsx new file mode 100644 index 00000000..55bc0cc0 --- /dev/null +++ b/Cunkebao/src/components/AndroidCompatibilityCheck.tsx @@ -0,0 +1,228 @@ +import React, { useEffect, useState } from "react"; + +interface AndroidCompatibilityInfo { + isAndroid: boolean; + androidVersion: number; + chromeVersion: number; + webViewVersion: number; + issues: string[]; + suggestions: string[]; +} + +const AndroidCompatibilityCheck: React.FC = () => { + const [compatibility, setCompatibility] = useState({ + isAndroid: false, + androidVersion: 0, + chromeVersion: 0, + webViewVersion: 0, + issues: [], + suggestions: [], + }); + + useEffect(() => { + const checkAndroidCompatibility = () => { + const ua = navigator.userAgent; + const issues: string[] = []; + const suggestions: string[] = []; + let isAndroid = false; + let androidVersion = 0; + let chromeVersion = 0; + let webViewVersion = 0; + + // 检测Android系统 + if (ua.indexOf("Android") > -1) { + isAndroid = true; + const androidMatch = ua.match(/Android\s+(\d+)/); + if (androidMatch) { + androidVersion = parseInt(androidMatch[1]); + } + + // 检测Chrome版本 + const chromeMatch = ua.match(/Chrome\/(\d+)/); + if (chromeMatch) { + chromeVersion = parseInt(chromeMatch[1]); + } + + // 检测WebView版本 + const webViewMatch = ua.match(/Version\/\d+\.\d+/); + if (webViewMatch) { + const versionMatch = webViewMatch[0].match(/\d+/); + if (versionMatch) { + webViewVersion = parseInt(versionMatch[0]); + } + } + + // Android 7 (API 24) 兼容性检查 + if (androidVersion === 7) { + issues.push("Android 7 系统对ES6+特性支持不完整"); + suggestions.push("建议升级到Android 8+或使用最新版Chrome"); + } + + // Android 6 (API 23) 兼容性检查 + if (androidVersion === 6) { + issues.push("Android 6 系统对现代Web特性支持有限"); + suggestions.push("强烈建议升级系统或使用最新版Chrome"); + } + + // Chrome版本检查 + if (chromeVersion > 0 && chromeVersion < 50) { + issues.push(`Chrome版本过低 (${chromeVersion}),建议升级到50+`); + suggestions.push("请在Google Play商店更新Chrome浏览器"); + } + + // WebView版本检查 + if (webViewVersion > 0 && webViewVersion < 50) { + issues.push(`WebView版本过低 (${webViewVersion}),可能影响应用功能`); + suggestions.push("建议使用Chrome浏览器或更新系统WebView"); + } + + // 检测特定问题 + const features = { + Promise: typeof Promise !== "undefined", + fetch: typeof fetch !== "undefined", + "Array.from": typeof Array.from !== "undefined", + "Object.assign": typeof Object.assign !== "undefined", + "String.includes": typeof String.prototype.includes !== "undefined", + "Array.includes": typeof Array.prototype.includes !== "undefined", + requestAnimationFrame: typeof requestAnimationFrame !== "undefined", + IntersectionObserver: typeof IntersectionObserver !== "undefined", + ResizeObserver: typeof ResizeObserver !== "undefined", + URLSearchParams: typeof URLSearchParams !== "undefined", + TextEncoder: typeof TextEncoder !== "undefined", + AbortController: typeof AbortController !== "undefined", + }; + + Object.entries(features).forEach(([feature, supported]) => { + if (!supported) { + issues.push(`${feature} 特性不支持`); + } + }); + + // 微信内置浏览器检测 + if (ua.indexOf("MicroMessenger") > -1) { + issues.push("微信内置浏览器对某些Web特性支持有限"); + suggestions.push("建议在系统浏览器中打开以获得最佳体验"); + } + + // QQ内置浏览器检测 + if (ua.indexOf("QQ/") > -1) { + issues.push("QQ内置浏览器对某些Web特性支持有限"); + suggestions.push("建议在系统浏览器中打开以获得最佳体验"); + } + } + + setCompatibility({ + isAndroid, + androidVersion, + chromeVersion, + webViewVersion, + issues, + suggestions, + }); + }; + + checkAndroidCompatibility(); + }, []); + + if (!compatibility.isAndroid || compatibility.issues.length === 0) { + return null; + } + + return ( +
+
+ 🚨 Android 兼容性警告 +
+ +
+ 系统版本: Android {compatibility.androidVersion} + {compatibility.chromeVersion > 0 && + ` | Chrome: ${compatibility.chromeVersion}`} + {compatibility.webViewVersion > 0 && + ` | WebView: ${compatibility.webViewVersion}`} +
+ +
+
+ 检测到的问题: +
+
+ {compatibility.issues.map((issue, index) => ( +
+ • {issue} +
+ ))} +
+
+ + {compatibility.suggestions.length > 0 && ( +
+
+ 建议解决方案: +
+
+ {compatibility.suggestions.map((suggestion, index) => ( +
+ • {suggestion} +
+ ))} +
+
+ )} + +
+ 💡 应用已启用兼容模式,但建议升级系统以获得最佳体验 +
+ + +
+ ); +}; + +export default AndroidCompatibilityCheck; diff --git a/Cunkebao/src/components/CompatibilityCheck.tsx b/Cunkebao/src/components/CompatibilityCheck.tsx new file mode 100644 index 00000000..563d4242 --- /dev/null +++ b/Cunkebao/src/components/CompatibilityCheck.tsx @@ -0,0 +1,125 @@ +import React, { useEffect, useState } from "react"; + +interface CompatibilityInfo { + isCompatible: boolean; + browser: string; + version: string; + issues: string[]; +} + +const CompatibilityCheck: React.FC = () => { + const [compatibility, setCompatibility] = useState({ + isCompatible: true, + browser: "", + version: "", + issues: [], + }); + + useEffect(() => { + const checkCompatibility = () => { + const ua = navigator.userAgent; + const issues: string[] = []; + let browser = "Unknown"; + let version = "Unknown"; + + // 检测浏览器类型和版本 + if (ua.indexOf("Chrome") > -1) { + browser = "Chrome"; + const match = ua.match(/Chrome\/(\d+)/); + version = match ? match[1] : "Unknown"; + if (parseInt(version) < 50) { + issues.push("Chrome版本过低,建议升级到50+"); + } + } else if (ua.indexOf("Firefox") > -1) { + browser = "Firefox"; + const match = ua.match(/Firefox\/(\d+)/); + version = match ? match[1] : "Unknown"; + if (parseInt(version) < 50) { + issues.push("Firefox版本过低,建议升级到50+"); + } + } else if (ua.indexOf("Safari") > -1 && ua.indexOf("Chrome") === -1) { + browser = "Safari"; + const match = ua.match(/Version\/(\d+)/); + version = match ? match[1] : "Unknown"; + if (parseInt(version) < 10) { + issues.push("Safari版本过低,建议升级到10+"); + } + } else if (ua.indexOf("MSIE") > -1 || ua.indexOf("Trident") > -1) { + browser = "Internet Explorer"; + const match = ua.match(/(?:MSIE |rv:)(\d+)/); + version = match ? match[1] : "Unknown"; + issues.push("Internet Explorer不受支持,建议使用现代浏览器"); + } else if (ua.indexOf("Edge") > -1) { + browser = "Edge"; + const match = ua.match(/Edge\/(\d+)/); + version = match ? match[1] : "Unknown"; + if (parseInt(version) < 12) { + issues.push("Edge版本过低,建议升级到12+"); + } + } + + // 检测ES6+特性支持 + const features = { + Promise: typeof Promise !== "undefined", + fetch: typeof fetch !== "undefined", + "Array.from": typeof Array.from !== "undefined", + "Object.assign": typeof Object.assign !== "undefined", + "String.includes": typeof String.prototype.includes !== "undefined", + "Array.includes": typeof Array.prototype.includes !== "undefined", + }; + + Object.entries(features).forEach(([feature, supported]) => { + if (!supported) { + issues.push(`${feature} 特性不支持`); + } + }); + + setCompatibility({ + isCompatible: issues.length === 0, + browser, + version, + issues, + }); + }; + + checkCompatibility(); + }, []); + + if (compatibility.isCompatible) { + return null; // 兼容时不需要显示 + } + + return ( +
+
+ 浏览器兼容性警告 +
+
+ 当前浏览器: {compatibility.browser} {compatibility.version} +
+
+ {compatibility.issues.map((issue, index) => ( +
{issue}
+ ))} +
+
+ 建议使用 Chrome 50+、Firefox 50+、Safari 10+ 或 Edge 12+ +
+
+ ); +}; + +export default CompatibilityCheck; diff --git a/Cunkebao/src/components/DeviceSelection/data.ts b/Cunkebao/src/components/DeviceSelection/data.ts index abc9a214..d002905c 100644 --- a/Cunkebao/src/components/DeviceSelection/data.ts +++ b/Cunkebao/src/components/DeviceSelection/data.ts @@ -8,6 +8,8 @@ export interface DeviceSelectionItem { wxid?: string; nickname?: string; usedInPlans?: number; + avatar?: string; + totalFriend?: number; } // 组件属性接口 @@ -23,4 +25,5 @@ export interface DeviceSelectionProps { showInput?: boolean; // 新增 showSelectedList?: boolean; // 新增 readonly?: boolean; // 新增 + deviceGroups?: any[]; // 传递设备组数据 } diff --git a/Cunkebao/src/components/DeviceSelection/index.module.scss b/Cunkebao/src/components/DeviceSelection/index.module.scss index ea776f81..8d004a48 100644 --- a/Cunkebao/src/components/DeviceSelection/index.module.scss +++ b/Cunkebao/src/components/DeviceSelection/index.module.scss @@ -67,60 +67,152 @@ } .deviceItem { display: flex; - align-items: flex-start; - gap: 12px; - padding: 16px; - border-radius: 12px; - border: 1px solid #f0f0f0; + flex-direction: column; + padding: 12px; background: #fff; - cursor: pointer; - transition: background 0.2s; + border-radius: 12px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + transition: all 0.2s ease; + border: 1px solid #f5f5f5; + &:hover { - background: #f5f6fa; + transform: translateY(-1px); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); + } +} + +.headerRow { + display: flex; + align-items: center; + gap: 8px; +} + +.checkboxContainer { + flex-shrink: 0; +} + +.imeiText { + font-size: 13px; + color: #666; + font-family: monospace; + flex: 1; +} + +.mainContent { + display: flex; + align-items: center; + gap: 12px; + cursor: pointer; + padding: 8px; + border-radius: 8px; + transition: background-color 0.2s ease; + + &:hover { + background-color: #f8f9fa; } } .deviceCheckbox { - margin-top: 4px; + flex-shrink: 0; } .deviceInfo { flex: 1; + min-width: 0; + display: flex; + align-items: center; + gap: 12px; } +.deviceAvatar { + width: 64px; + height: 64px; + border-radius: 6px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.25); + flex-shrink: 0; + + img { + width: 100%; + height: 100%; + object-fit: cover; + } + + .avatarText { + font-size: 18px; + color: #fff; + font-weight: 700; + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + } +} + +.deviceContent { + flex: 1; + min-width: 0; +} + .deviceInfoRow { display: flex; align-items: center; - justify-content: space-between; + gap: 6px; + margin-bottom: 6px; } .deviceName { - font-weight: 500; font-size: 16px; - color: #222; + font-weight: 600; + color: #1a1a1a; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .statusOnline { - width: 56px; - height: 24px; - border-radius: 12px; - background: #52c41a; - color: #fff; - font-size: 13px; - display: flex; - align-items: center; - justify-content: center; + font-size: 11px; + padding: 1px 6px; + border-radius: 8px; + color: #52c41a; + background: #f6ffed; + border: 1px solid #b7eb8f; + font-weight: 500; } .statusOffline { - width: 56px; - height: 24px; - border-radius: 12px; - background: #e5e6eb; - color: #888; - font-size: 13px; - display: flex; - align-items: center; - justify-content: center; + font-size: 11px; + padding: 1px 6px; + border-radius: 8px; + color: #ff4d4f; + background: #fff2f0; + border: 1px solid #ffccc7; + font-weight: 500; } .deviceInfoDetail { + display: flex; + flex-direction: column; + gap: 4px; +} + +.infoItem { + display: flex; + align-items: center; + gap: 8px; +} + +.infoLabel { font-size: 13px; - color: #888; - margin-top: 4px; + color: #666; + min-width: 50px; +} + +.infoValue { + font-size: 13px; + color: #333; + + &.imei { + font-family: monospace; + } + + &.friendCount { + font-weight: 500; + } } .loadingBox { display: flex; diff --git a/Cunkebao/src/components/DeviceSelection/index.tsx b/Cunkebao/src/components/DeviceSelection/index.tsx index 9d31e268..ba6952cd 100644 --- a/Cunkebao/src/components/DeviceSelection/index.tsx +++ b/Cunkebao/src/components/DeviceSelection/index.tsx @@ -46,6 +46,12 @@ const DeviceSelection: React.FC = ({ onSelect(selectedOptions.filter(v => v.id !== id)); }; + // 清除所有已选设备 + const handleClearAll = () => { + if (readonly) return; + onSelect([]); + }; + return ( <> {/* mode=input 显示输入框,mode=dialog不显示 */} @@ -57,6 +63,7 @@ const DeviceSelection: React.FC = ({ onClick={openPopup} prefix={} allowClear={!readonly} + onClear={handleClearAll} size="large" readOnly={readonly} disabled={readonly} @@ -86,11 +93,52 @@ const DeviceSelection: React.FC = ({ style={{ display: "flex", alignItems: "center", - padding: "4px 8px", + padding: "8px 12px", borderBottom: "1px solid #f0f0f0", fontSize: 14, }} > + {/* 头像 */} +
+ {device.avatar ? ( + 头像 + ) : ( + + {(device.memo || device.wechatId || "设")[0]} + + )} +
+
= ({ textOverflow: "ellipsis", }} > - 【 {device.memo}】 - {device.wechatId} + {device.memo} - {device.wechatId}
{!readonly && ( - - {/* 提示文字 */} -
- 更新将自动重启应用 + +
{/* 动画样式 */} + + +
+

Custom PWA Icons

+

Your app will now have proper icons when added to home screen

+ +
+ App Icon Preview +
+ +

Make sure to use all required icon sizes for best results across devices

+
+ + + + + \ No newline at end of file