Merge branch 'main' of github.com:hainatravel/global-sales

dev/timezone
Jimmy Liow 1 year ago
commit 71beef42e1

6
.gitignore vendored

@ -10,7 +10,10 @@ lerna-debug.log*
node_modules
dist
dist-ssr
*.local
*.local
distTest
dev-dist
tmp
# Editor directories and files
.vscode/*
@ -24,3 +27,4 @@ dist-ssr
*.sw?
/package-lock.json

@ -5,6 +5,8 @@
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>销售平台</title>
<link rel="preconnect" crossorigin="anonymous" href="https://haina-sale-system.oss-cn-shenzhen.aliyuncs.com">
<link rel="preconnect" crossorigin="anonymous" href="https://hiana-crm.oss-ap-southeast-1.aliyuncs.com">
</head>
<body>
<div id="root"></div>

@ -1,3 +1,10 @@
export const API_HOST = 'https://p9axztuwd7x8a7.mycht.cn/whatsapp_server'
// export const API_HOST = 'https://p9axztuwd7x8a7.mycht.cn/whatsapp_qqs'
// export const WS_URL = 'wss://p9axztuwd7x8a7.mycht.cn/whatsapp_qqs'; // test:
//
export const API_HOST = 'https://p9axztuwd7x8a7.mycht.cn/whatsapp_server';
export const WS_URL = 'wss://p9axztuwd7x8a7.mycht.cn/whatsapp_server'; // prod:
export const DATE_FORMAT = 'YYYY-MM-DD'
export const DATE_FORMAT = 'YYYY-MM-DD';
export const OSS_URL_CN = 'https://haina-sale-system.oss-cn-shenzhen.aliyuncs.com/WAMedia/';
export const OSS_URL_AP = 'https://hiana-crm.oss-ap-southeast-1.aliyuncs.com/WAMedia/';
export const OSS_URL = OSS_URL_AP;

@ -437,3 +437,15 @@ export const olog = (text, ...args) => {
'background:#fb923c ; padding: 1px; border-radius: 3px; color: #fff',...args
);
};
export const sanitizeFilename = (str) => {
// Remove whitespace and replace with hyphens
str = str.replace(/\s+/g, '-');
// Remove invalid characters and replace with hyphens
str = str.replace(/[^a-zA-Z0-9.-]/g, '-');
// Replace consecutive hyphens with a single hyphen
str = str.replace(/-+/g, '-');
// Trim leading and trailing hyphens
str = str.replace(/^-+|-+$/g, '');
return str;
}

@ -0,0 +1,120 @@
import { createContext, useContext, useEffect, useState } from 'react';
import { App } from 'antd';
const formatBytes = (bytes, decimals = 2) => {
if (bytes === 0) return '';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
};
const ClearCache = (props) => {
const { message } = App.useApp();
const [cacheSizeInfo, setCacheSizeInfo] = useState({
swCacheSize: 0,
diskCacheSize: 0,
totalSize: 0,
});
useEffect(() => {
calcCacheSizes();
}, []);
const clearAllCaches = async () => {
try {
// 1. Clear the service worker cache
if (navigator.serviceWorker.controller) {
const cacheNames = await caches.keys();
await Promise.all(cacheNames.map((name) => caches.delete(name)));
}
// 2. Clear the disk cache (HTTP cache)
const diskCacheName = 'disk-cache';
await window.caches.delete(diskCacheName);
const diskCache = await window.caches.open(diskCacheName);
const diskCacheKeys = await diskCache.keys();
await Promise.all(diskCacheKeys.map((request) => diskCache.delete(request)));
// 3. Clear the IndexedDB cache
const indexedDBNames = await window.indexedDB.databases();
await Promise.all(indexedDBNames.map((dbName) => window.indexedDB.deleteDatabase(dbName.name)));
// Unregister the service worker
const registration = await navigator.serviceWorker.getRegistration();
if (registration) {
await registration.unregister();
console.log('Service worker unregistered');
} else {
console.log('No service worker registered');
}
message.success(`清除成功🎉`);
// Display cache sizes
calcCacheSizes();
} catch (error) {
console.error('Error clearing caches or unregistering service worker:', error);
}
};
const calcCacheSizes = async () => {
try {
let swCacheSize = 0;
let diskCacheSize = 0;
let indexedDBSize = 0;
// 1. Get the service worker cache size
if (navigator.serviceWorker.controller) {
const cacheNames = await caches.keys();
for (const name of cacheNames) {
const cache = await caches.open(name);
const requests = await cache.keys();
for (const request of requests) {
const response = await cache.match(request);
swCacheSize += Number(response.headers.get('Content-Length')) || 0;
}
}
}
// 2. Get the disk cache size
const diskCacheName = 'disk-cache';
const diskCache = await caches.open(diskCacheName);
const diskCacheKeys = await diskCache.keys();
for (const request of diskCacheKeys) {
const response = await diskCache.match(request);
diskCacheSize += Number(response.headers.get('Content-Length')) || 0;
}
// 3. Get the IndexedDB cache size
// const indexedDBNames = await window.indexedDB.databases();
// for (const dbName of indexedDBNames) {
// const db = await window.indexedDB.open(dbName.name);
// const objectStoreNames = db.objectStoreNames;
// if (objectStoreNames !== undefined) {
// const objectStores = Array.from(objectStoreNames).map((storeName) => db.transaction([storeName], 'readonly').objectStore(storeName));
// for (const objectStore of objectStores) {
// const request = objectStore.count();
// request.onsuccess = () => {
// indexedDBSize += request.result;
// };
// }
// }
// }
setCacheSizeInfo({ swCacheSize, diskCacheSize, indexedDBSize, totalSize: Number(swCacheSize) + Number(diskCacheSize) + indexedDBSize });
} catch (error) {
console.error('Error getting cache sizes:', error);
}
};
return (
<>
<a onClick={clearAllCaches}>
清除缓存{/* &nbsp;{cacheSizeInfo.totalSize > 0 && <span>{formatBytes(cacheSizeInfo.totalSize)}</span>} */}
</a>
</>
);
};
export default ClearCache;

@ -2,13 +2,11 @@ import { App, Upload, Button, message, Tooltip } from 'antd';
import { useState } from 'react';
import { FileAddOutlined } from '@ant-design/icons';
import { v4 as uuid } from 'uuid';
import { API_HOST } from '@/config';
import { API_HOST, OSS_URL as aliOSSHost } from '@/config';
import { whatsappSupportFileTypes } from '@/channel/whatsappUtils';
import { isEmpty, } from '@/utils/commons';
import { isEmpty, sanitizeFilename } from '@/utils/commons';
// import useConversationStore from '@/stores/ConversationStore';
const aliOSSHost = `https://haina-sale-system.oss-cn-shenzhen.aliyuncs.com/WAMedia/`;
const ImageUpload = ({ disabled, invokeUploadFileMessage, invokeSendUploadMessage }) => {
const { message: appMessage } = App.useApp();
@ -33,7 +31,7 @@ const ImageUpload = ({ disabled, invokeUploadFileMessage, invokeSendUploadMessag
const lastDotIndex = file.name.lastIndexOf('.');
const suffix = file.name.slice(lastDotIndex+1);
const baseName = file.name.slice(0, lastDotIndex);
const rename = baseName + '-' + Date.now() + '.' + suffix;
const rename = sanitizeFilename(baseName) + '-' + Date.now() + '.' + suffix;
const dataUri = aliOSSHost + rename;
//
reader.onload = (event) => {

@ -23,12 +23,11 @@ import { sentMsgTypeMapped, whatsappSupportFileTypes } from '@/channel/whatsappU
import InputTemplate from './Input/Template';
import InputEmoji from './Input/Emoji';
import InputMediaUpload from './Input/MediaUpload';
import { OSS_URL as aliOSSHost } from '@/config';
import { postUploadFileItem } from '@/actions/CommonActions';
import ExpireTimeClock from './ExpireTimeClock';
import dayjs from 'dayjs';
const aliOSSHost = `https://haina-sale-system.oss-cn-shenzhen.aliyuncs.com/WAMedia/`;
const InputComposer = ({ mobile }) => {
const userId = useAuthStore((state) => state.loginUser.userId);
const websocket = useConversationStore((state) => state.websocket);

@ -11,6 +11,7 @@ import '@/assets/App.css'
import AppLogo from '@/assets/logo-gh.png'
import 'react-chat-elements/dist/main.css'
import ReloadPrompt from './ReloadPrompt';
import ClearCache from './ClearCache';
const { Header, Footer, Content } = Layout
const { Title } = Typography
@ -44,12 +45,18 @@ function DesktopApp() {
useEffect(() => {
let interval;
if (totalNotify > 0) {
if ('setAppBadge' in navigator) {
navigator.setAppBadge(totalNotify).catch((error) => {});
}
interval = setInterval(() => {
document.title = isTitleVisible ? `🔔🔥💬【${totalNotify}条新消息】` : '______________';
setIsTitleVisible(!isTitleVisible);
}, 500);
} else {
document.title = '销售平台';
if ('clearAppBadge' in navigator) {
navigator.clearAppBadge().catch((error) => {});
}
}
return () => clearInterval(interval);
}, [totalNotify, isTitleVisible]);
@ -98,10 +105,9 @@ function DesktopApp() {
key: '1',
},
{ type: 'divider' },
{
label: <ReloadPrompt force />,
key: 'reload',
},
{ label: <ReloadPrompt force />, key: 'reload' },
{ type: 'divider' },
{ label: <ClearCache />, key: 'clearcache' },
{ type: 'divider' },
{
label: <Link to='/p/dingding/logout'>退出</Link>,

@ -7,6 +7,8 @@ import { Col, Layout, Row, Typography, theme, Space, Avatar, Dropdown, Flex } fr
import { DownOutlined } from '@ant-design/icons';
import { NavLink, Outlet, Link } from 'react-router-dom';
import ReloadPrompt from './ReloadPrompt';
import ClearCache from './ClearCache';
const { Header, Footer, Content } = Layout;
const { Title } = Typography;
@ -55,10 +57,9 @@ function MobileApp() {
<Dropdown
menu={{
items: [
{
label: <ReloadPrompt force />,
key: 'reload',
},
{ label: <ReloadPrompt force />, key: 'reload' },
{ type: 'divider' },
{ label: <ClearCache />, key: 'clearcache' },
{ type: 'divider' },
{
label: <Link to='/p/dingding/logout'>退出</Link>,

@ -12,6 +12,7 @@ function ReloadPrompt({ force }) {
needRefresh: [needRefresh, setNeedRefresh],
updateServiceWorker,
} = useRegisterSW({
// If onRegisteredSW is present, onRegistered will never be called.
onRegistered(r) {
r &&
setTimeout(() => {
@ -47,8 +48,12 @@ function ReloadPrompt({ force }) {
return (
<>
{/* {offlineReady && (<span>{APP_VERSION}</span>) } */}
{ (force || needRefresh) && (
<a className=' text-sky-600 pr-2' onClick={() => updateServiceWorker(true)}>
{(force || needRefresh) && (
<a
className=' text-sky-600 pr-2'
onClick={() => {
needRefresh ? updateServiceWorker(true) : window.location.reload(true);
}}>
系统更新{needRefresh && '🚀'}
</a>
)}

@ -14,11 +14,60 @@ const buildDatePlugin = () => {
};
// PWA plugin
const manifestForPlugIn = {
// strategies: 'generateSW',
registerType: 'prompt',
// includeAssests: ['/src/assets/logo-gh.png'],
// registerType: 'autoUpdate',
devOptions: {
enabled: true
},
workbox: {
globPatterns: ['**/*.{json,ico,png,svg,woff2}'],
globPatterns: ['**/*.{json,css,js,html,ico,png,svg,woff2}'],
maximumFileSizeToCacheInBytes: 3000000,
runtimeCaching: [
{
urlPattern: /^https:\/\/haina-sale-system\.oss-cn-shenzhen\.aliyuncs\.com\/.*/i,
handler: 'CacheFirst',
options: {
cacheName: 'oss-cn-media',
expiration: {
maxEntries: 20,
maxAgeSeconds: 60 * 60 * 24 * 7 // <== 7 days
},
cacheableResponse: {
statuses: [0, 200]
}
}
},
{
urlPattern: /^https:\/\/hiana-crm\.oss-ap-southeast-1\.aliyuncs\.com\/.*/i,
handler: 'CacheFirst',
options: {
cacheName: 'oss-ap-media',
expiration: {
maxEntries: 20,
maxAgeSeconds: 60 * 60 * 24 * 7 // <== 7 days
},
cacheableResponse: {
statuses: [0, 200]
}
}
},
{
urlPattern: /^https:\/\/static-legacy\.dingtalk.com\/.*/i,
handler: 'CacheFirst',
options: {
cacheName: 'dingtalk-avatar-media',
expiration: {
maxEntries: 50,
maxAgeSeconds: 60 * 60 * 24 * 7 // <== 7 days
},
cacheableResponse: {
statuses: [0, 200]
}
}
},
],
},
manifest: {
name: 'Sales CRM',
@ -50,7 +99,7 @@ const manifestForPlugIn = {
display: 'standalone',
display_override: ['window-controls-overlay'],
scope: '/',
start_url: 'https://sales.mycht.cn/',
start_url: '/',
orientation: 'portrait',
},
};

Loading…
Cancel
Save