diff --git a/nkebao/src/pages/mobile/mine/setting/index.tsx b/nkebao/src/pages/mobile/mine/setting/index.tsx
index 8090bdc2..a15424bf 100644
--- a/nkebao/src/pages/mobile/mine/setting/index.tsx
+++ b/nkebao/src/pages/mobile/mine/setting/index.tsx
@@ -1,6 +1,6 @@
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
-import { NavBar, List, Switch, Button, Dialog, Toast, Card } from "antd-mobile";
+import { List, Switch, Button, Dialog, Toast, Card } from "antd-mobile";
import {
UserOutlined,
SafetyOutlined,
@@ -8,14 +8,16 @@ import {
LogoutOutlined,
SettingOutlined,
LockOutlined,
- HeartOutlined,
- StarOutlined,
+ ReloadOutlined,
} from "@ant-design/icons";
import Layout from "@/components/Layout/Layout";
import { useUserStore } from "@/store/module/user";
import { useSettingsStore } from "@/store/module/settings";
import style from "./index.module.scss";
import NavCommon from "@/components/NavCommon";
+import { sendMessageToParent, TYPE_EMUE } from "@/utils/postApp";
+import { updateChecker } from "@/utils/updateChecker";
+
interface SettingItem {
id: string;
title: string;
@@ -32,7 +34,7 @@ interface SettingItem {
const Setting: React.FC = () => {
const navigate = useNavigate();
const { user, logout } = useUserStore();
- const { settings, updateSetting } = useSettingsStore();
+ const { settings } = useSettingsStore();
const [showLogoutDialog, setShowLogoutDialog] = useState(false);
const [avatarError, setAvatarError] = useState(false);
@@ -57,13 +59,30 @@ const Setting: React.FC = () => {
Dialog.confirm({
content: "确定要清除缓存吗?这将清除所有本地数据。",
onConfirm: () => {
- localStorage.clear();
- sessionStorage.clear();
+ sendMessageToParent(
+ {
+ action: "clearCache",
+ },
+ TYPE_EMUE.FUNCTION,
+ );
+ },
+ });
+ };
+
+ // 在设置页面添加手动检查更新功能
+ const handleCheckUpdate = () => {
+ updateChecker.checkForUpdate().then(result => {
+ if (result.hasUpdate) {
Toast.show({
- content: "缓存已清除",
+ content: "发现新版本,请刷新页面",
position: "top",
});
- },
+ } else {
+ Toast.show({
+ content: "当前已是最新版本",
+ position: "top",
+ });
+ }
});
};
@@ -114,6 +133,15 @@ const Setting: React.FC = () => {
color: "var(--primary-color)",
badge: "2.3MB",
},
+ {
+ id: "checkUpdate",
+ title: "检查更新",
+ description: "检查应用是否有新版本",
+ icon:
,
+ type: "button",
+ onClick: handleCheckUpdate,
+ color: "var(--primary-color)",
+ },
],
},
{
@@ -258,12 +286,14 @@ const Setting: React.FC = () => {
-

+
存客宝
-
版本 3.0.0
-
Build 2025-7-30
+
+ 版本 {settings.appVersion}
+
+
Build 2025-08-04
diff --git a/nkebao/src/pages/mobile/scenarios/plan/list/index.tsx b/nkebao/src/pages/mobile/scenarios/plan/list/index.tsx
index cac548d8..230bf532 100644
--- a/nkebao/src/pages/mobile/scenarios/plan/list/index.tsx
+++ b/nkebao/src/pages/mobile/scenarios/plan/list/index.tsx
@@ -65,23 +65,6 @@ const ScenarioList: React.FC = () => {
const [total, setTotal] = useState(0);
const pageSize = 20;
- // 获取渠道中文名称
- const getChannelName = (channel: string) => {
- const channelMap: Record
= {
- douyin: "抖音直播获客",
- kuaishou: "快手直播获客",
- xiaohongshu: "小红书种草获客",
- weibo: "微博话题获客",
- haibao: "海报扫码获客",
- phone: "电话号码获客",
- gongzhonghao: "公众号引流获客",
- weixinqun: "微信群裂变获客",
- payment: "付款码获客",
- api: "API接口获客",
- };
- return channelMap[channel] || `${channel}获客`;
- };
-
// 获取计划列表数据
const fetchPlanList = async (page: number, isLoadMore: boolean = false) => {
if (!scenarioId) return;
@@ -409,7 +392,7 @@ const ScenarioList: React.FC = () => {
}
loading={loading}
footer={
-
+
(
diff --git a/nkebao/src/store/module/user.ts b/nkebao/src/store/module/user.ts
index ae4f66d2..07838d87 100644
--- a/nkebao/src/store/module/user.ts
+++ b/nkebao/src/store/module/user.ts
@@ -1,4 +1,5 @@
import { createPersistStore } from "@/store/createPersistStore";
+import { Toast } from "antd-mobile";
export interface User {
id: number;
@@ -60,6 +61,16 @@ export const useUserStore = createPersistStore(
deviceTotal: deviceTotal,
};
set({ user, token, isLoggedIn: true });
+
+ Toast.show({ content: "登录成功", position: "top" });
+
+ // 根据设备数量判断跳转
+ if (deviceTotal > 0) {
+ window.location.href = "/";
+ } else {
+ // 没有设备,跳转到引导页面
+ window.location.href = "/guide";
+ }
},
logout: () => {
// 清除localStorage中的token
diff --git a/nkebao/src/styles/global.scss b/nkebao/src/styles/global.scss
index ca4e01d0..6e4995b9 100644
--- a/nkebao/src/styles/global.scss
+++ b/nkebao/src/styles/global.scss
@@ -264,3 +264,43 @@ button {
align-items: center;
gap: 6px;
}
+.pagination-container {
+ display: flex;
+ justify-content: center;
+ padding: 14px 0;
+ background: white;
+ border-radius: 12px;
+ margin-top: 16px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+
+ :global(.ant-pagination) {
+ .ant-pagination-item {
+ border-radius: 6px;
+ border: 1px solid #d9d9d9;
+
+ &:hover {
+ border-color: var(--primary-color);
+ }
+
+ &.ant-pagination-item-active {
+ background: var(--primary-color);
+ border-color: var(--primary-color);
+
+ a {
+ color: white;
+ }
+ }
+ }
+
+ .ant-pagination-prev,
+ .ant-pagination-next {
+ border-radius: 6px;
+ border: 1px solid #d9d9d9;
+
+ &:hover {
+ border-color: var(--primary-color);
+ color: var(--primary-color);
+ }
+ }
+ }
+}
diff --git a/nkebao/src/utils/common.ts b/nkebao/src/utils/common.ts
index ebba36f1..995b9c05 100644
--- a/nkebao/src/utils/common.ts
+++ b/nkebao/src/utils/common.ts
@@ -1,5 +1,5 @@
import { Modal } from "antd-mobile";
-
+import { getSetting } from "@/store/module/settings";
/**
* 通用js调用弹窗,Promise风格
* @param content 弹窗内容
@@ -49,7 +49,7 @@ export function getSafeAreaHeight() {
// 2. 设备检测
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
const isAndroid = /Android/.test(navigator.userAgent);
- const isAppMode = Boolean(localStorage.getItem("isAppMode"));
+ const isAppMode = getSetting("isAppMode");
if (isIOS && isAppMode) {
// iOS 设备
const isIPhoneX = window.screen.height >= 812;
diff --git a/nkebao/src/utils/postApp.ts b/nkebao/src/utils/postApp.ts
new file mode 100644
index 00000000..0ed8ab2d
--- /dev/null
+++ b/nkebao/src/utils/postApp.ts
@@ -0,0 +1,72 @@
+export interface Message {
+ type: number; // 数据类型:0数据交互 1App功能调用
+ data: any;
+}
+export const TYPE_EMUE = {
+ CONNECT: 0,
+ DATA: 1,
+ FUNCTION: 2,
+ CONFIG: 3,
+};
+// 向 App 发送消息
+export const sendMessageToParent = (message: any, type: number) => {
+ const params: Message = {
+ type: type,
+ data: message,
+ };
+
+ if (window.uni && window.uni.postMessage) {
+ try {
+ window.uni.postMessage({
+ data: params,
+ });
+ console.log("[存客宝]SendMessage=>\n" + JSON.stringify(params));
+ } catch (e) {
+ console.error(
+ "[存客宝]SendMessage=>\n" + JSON.stringify(params) + "发送失败:",
+ e,
+ );
+ }
+ } else {
+ console.error(
+ "[存客宝]SendMessage=>\n" + JSON.stringify(params) + "无法发送消息",
+ );
+ }
+};
+// 解析 URL 参数中的消息
+export const parseUrlMessage = (): Promise => {
+ return new Promise((resolve, reject) => {
+ const search = window.location.search.substring(1);
+ let messageParam = null;
+
+ if (search) {
+ const pairs = search.split("&");
+ for (const pair of pairs) {
+ const [key, value] = pair.split("=");
+ if (key === "message" && value) {
+ messageParam = decodeURIComponent(value);
+ break;
+ }
+ }
+ }
+
+ if (messageParam) {
+ try {
+ const message = JSON.parse(decodeURIComponent(messageParam));
+ console.log("[存客宝]ReceiveMessage=>\n" + JSON.stringify(message));
+ resolve(message);
+ // 清除URL中的message参数
+ const newUrl =
+ window.location.pathname +
+ window.location.search
+ .replace(/[?&]message=[^&]*/, "")
+ .replace(/^&/, "?");
+ window.history.replaceState({}, "", newUrl);
+ } catch (e) {
+ console.error("解析URL消息失败:", e);
+ reject(e);
+ }
+ }
+ reject(null);
+ });
+};
diff --git a/nkebao/src/utils/updateChecker.ts b/nkebao/src/utils/updateChecker.ts
new file mode 100644
index 00000000..aca1700c
--- /dev/null
+++ b/nkebao/src/utils/updateChecker.ts
@@ -0,0 +1,217 @@
+/**
+ * 应用更新检测工具
+ */
+
+interface UpdateInfo {
+ hasUpdate: boolean;
+ version?: string;
+ timestamp?: number;
+}
+
+class UpdateChecker {
+ private currentVersion: string;
+ private checkInterval: number = 1000; // 1秒检查一次(用于测试)
+ private intervalId: NodeJS.Timeout | null = null;
+ private updateCallbacks: ((info: UpdateInfo) => void)[] = [];
+ private currentHashes: string[] = [];
+
+ constructor() {
+ // 从package.json获取版本号
+ this.currentVersion = import.meta.env.VITE_APP_VERSION || "1.0.0";
+ // 初始化当前哈希值
+ this.initCurrentHashes();
+ }
+
+ /**
+ * 初始化当前哈希值
+ */
+ private initCurrentHashes() {
+ // 从当前页面的资源中提取哈希值
+ const scripts = document.querySelectorAll("script[src]");
+ const links = document.querySelectorAll("link[href]");
+
+ const scriptHashes = Array.from(scripts)
+ .map(script => script.getAttribute("src"))
+ .filter(
+ src => src && (src.includes("assets/") || src.includes("/assets/")),
+ )
+ .map(src => {
+ // 修改正则表达式,匹配包含字母、数字和下划线的哈希值
+ const match = src?.match(/[a-zA-Z0-9_-]{8,}/);
+ return match ? match[0] : "";
+ })
+ .filter(hash => hash);
+
+ const linkHashes = Array.from(links)
+ .map(link => link.getAttribute("href"))
+ .filter(
+ href => href && (href.includes("assets/") || href.includes("/assets/")),
+ )
+ .map(href => {
+ // 修改正则表达式,匹配包含字母、数字和下划线的哈希值
+ const match = href?.match(/[a-zA-Z0-9_-]{8,}/);
+ return match ? match[0] : "";
+ })
+ .filter(hash => hash);
+
+ this.currentHashes = [...new Set([...scriptHashes, ...linkHashes])];
+ }
+
+ /**
+ * 开始检测更新
+ */
+ start() {
+ if (this.intervalId) {
+ return;
+ }
+
+ // 立即检查一次
+ this.checkForUpdate();
+
+ // 设置定时检查
+ this.intervalId = setInterval(() => {
+ this.checkForUpdate();
+ }, this.checkInterval);
+ }
+
+ /**
+ * 停止检测更新
+ */
+ stop() {
+ if (this.intervalId) {
+ clearInterval(this.intervalId);
+ this.intervalId = null;
+ }
+ }
+
+ /**
+ * 检查更新
+ */
+ async checkForUpdate(): Promise {
+ try {
+ // 获取新的manifest文件
+ let manifestResponse;
+ let manifestPath = "/.vite/manifest.json";
+
+ try {
+ manifestResponse = await fetch(manifestPath, {
+ cache: "no-cache",
+ headers: {
+ "Cache-Control": "no-cache",
+ Pragma: "no-cache",
+ },
+ });
+ } catch (error) {
+ // 如果.vite路径失败,尝试根路径
+ manifestPath = "/manifest.json";
+ manifestResponse = await fetch(manifestPath, {
+ cache: "no-cache",
+ headers: {
+ "Cache-Control": "no-cache",
+ Pragma: "no-cache",
+ },
+ });
+ }
+
+ if (!manifestResponse.ok) {
+ return { hasUpdate: false };
+ }
+
+ const manifest = await manifestResponse.json();
+
+ // 从Vite manifest中提取文件哈希
+ const newHashes: string[] = [];
+
+ Object.values(manifest).forEach((entry: any) => {
+ if (entry.file && entry.file.includes("assets/")) {
+ // console.log("处理manifest entry file:", entry.file);
+ // 修改正则表达式,匹配包含字母、数字和下划线的哈希值
+ const match = entry.file.match(/[a-zA-Z0-9_-]{8,}/);
+ if (match) {
+ const hash = match[0];
+ newHashes.push(hash);
+ }
+ }
+ // 也检查CSS文件
+ if (entry.css) {
+ entry.css.forEach((cssFile: string) => {
+ if (cssFile.includes("assets/")) {
+ // console.log("处理manifest entry css:", cssFile);
+ // 修改正则表达式,匹配包含字母、数字和下划线的哈希值
+ const match = cssFile.match(/[a-zA-Z0-9_-]{8,}/);
+ if (match) {
+ const hash = match[0];
+ // console.log("提取的manifest css哈希:", hash);
+ newHashes.push(hash);
+ }
+ }
+ });
+ }
+ });
+
+ // 去重新哈希值数组
+ const uniqueNewHashes = [...new Set(newHashes)];
+
+ // 比较哈希值
+ const hasUpdate = this.compareHashes(this.currentHashes, uniqueNewHashes);
+
+ const updateInfo: UpdateInfo = {
+ hasUpdate,
+ version: manifest.version || this.currentVersion,
+ timestamp: Date.now(),
+ };
+
+ // 通知所有回调
+ this.updateCallbacks.forEach(callback => callback(updateInfo));
+
+ return updateInfo;
+ } catch (error) {
+ return { hasUpdate: false };
+ }
+ }
+
+ /**
+ * 比较哈希值
+ */
+ private compareHashes(current: string[], newHashes: string[]): boolean {
+ if (current.length !== newHashes.length) {
+ return true;
+ }
+
+ // 对两个数组进行排序后比较,忽略顺序
+ const sortedCurrent = [...current].sort();
+ const sortedNewHashes = [...newHashes].sort();
+
+ const hasUpdate = sortedCurrent.some((hash, index) => {
+ return hash !== sortedNewHashes[index];
+ });
+
+ return hasUpdate;
+ }
+
+ /**
+ * 注册更新回调
+ */
+ onUpdate(callback: (info: UpdateInfo) => void) {
+ this.updateCallbacks.push(callback);
+ }
+
+ /**
+ * 移除更新回调
+ */
+ offUpdate(callback: (info: UpdateInfo) => void) {
+ const index = this.updateCallbacks.indexOf(callback);
+ if (index > -1) {
+ this.updateCallbacks.splice(index, 1);
+ }
+ }
+
+ /**
+ * 强制刷新页面
+ */
+ forceReload() {
+ window.location.reload();
+ }
+}
+
+export const updateChecker = new UpdateChecker();
diff --git a/nkebao/vite.config.ts b/nkebao/vite.config.ts
index eb0ce0a3..d03fd648 100644
--- a/nkebao/vite.config.ts
+++ b/nkebao/vite.config.ts
@@ -38,5 +38,13 @@ export default defineConfig({
minify: "esbuild",
// 启用源码映射(可选,生产环境可以关闭)
sourcemap: false,
+ // 生成manifest文件
+ manifest: true,
+ },
+ define: {
+ // 注入版本信息
+ "import.meta.env.VITE_APP_VERSION": JSON.stringify(
+ process.env.npm_package_version,
+ ),
},
});