feat: 电子药箱小程序 - 4Tab药品管理
- 电子药箱: 药品库存管理 + 手动录入 + 今日用药打卡 - 药品百科: 搜索 + 分类筛选 + 20种药品静态数据 - 惠教中心: 4条药品使用指南课程 - 我的: 家庭成员信息管理 - 自定义TabBar + navigation-bar组件 - SVG药品分类插图
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
// utils/sse.ts
|
||||
import { request } from './request'
|
||||
import { BASE_URL } from '../env'
|
||||
|
||||
//唯一id
|
||||
const sign = Date.now().toString(36) + Math.random().toString(36).slice(2)
|
||||
const listeners: Record<string, ((data: any) => void)[]> = {}
|
||||
|
||||
let requestTask: any = null
|
||||
let reconnectTimer: any = null
|
||||
let requestTimer: any = null
|
||||
|
||||
|
||||
// 不支持TextDecoder的话手动转码utf8
|
||||
function utf8Decode(uint8: Uint8Array): string {
|
||||
let str = '';
|
||||
let i = 0;
|
||||
while (i < uint8.length) {
|
||||
const byte1 = uint8[i];
|
||||
let charCode = byte1;
|
||||
if (byte1 <= 0x7F) {
|
||||
i += 1;
|
||||
} else if (byte1 >= 0xC0 && byte1 <= 0xDF) {
|
||||
const byte2 = uint8[i + 1];
|
||||
charCode = ((byte1 & 0x1F) << 6) | (byte2 & 0x3F);
|
||||
i += 2;
|
||||
} else if (byte1 >= 0xE0 && byte1 <= 0xEF) {
|
||||
const byte2 = uint8[i + 1];
|
||||
const byte3 = uint8[i + 2];
|
||||
charCode = ((byte1 & 0x0F) << 12) | ((byte2 & 0x3F) << 6) | (byte3 & 0x3F);
|
||||
i += 3;
|
||||
} else if (byte1 >= 0xF0 && byte1 <= 0xF7) {
|
||||
const byte2 = uint8[i + 1];
|
||||
const byte3 = uint8[i + 2];
|
||||
const byte4 = uint8[i + 3];
|
||||
charCode = ((byte1 & 0x07) << 18) | ((byte2 & 0x3F) << 12) | ((byte3 & 0x3F) << 6) | (byte4 & 0x3F);
|
||||
i += 4;
|
||||
}
|
||||
str += String.fromCharCode(charCode);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
export function startSSE() {
|
||||
if (requestTimer) return //已经启动过就不重复
|
||||
// if (wx.getStorageSync("sseId")) leave();
|
||||
wx.setStorageSync("sseId", sign)
|
||||
requestSSE()
|
||||
//30s连接一次,保持连接
|
||||
// requestTimer = setInterval(() => {
|
||||
// requestSSE()
|
||||
// }, 30000)
|
||||
}
|
||||
|
||||
async function requestSSE() {
|
||||
try {
|
||||
requestTask = (wx as any).request({
|
||||
url: `${BASE_URL}/api/auth/message/subscribe/${sign}`,
|
||||
method: 'GET',
|
||||
enableChunked: true,
|
||||
responseType: 'arraybuffer',
|
||||
header: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
|
||||
requestTask?.onChunkReceived?.((chunk: any) => {
|
||||
const uint8 = new Uint8Array(chunk.data)
|
||||
let chunkStr = ''
|
||||
try {
|
||||
//真机环境不支持TextDecoder
|
||||
// @ts-ignore
|
||||
chunkStr = new TextDecoder('utf-8').decode(uint8);
|
||||
} catch {
|
||||
chunkStr = utf8Decode(uint8);
|
||||
}
|
||||
const lines = chunkStr.trim().split('\n')
|
||||
for (const line of lines) {
|
||||
try {
|
||||
// console.log(JSON.parse(line), 'lllllll')
|
||||
if (JSON.parse(line).msg == 'auto close') {
|
||||
requestSSE()
|
||||
}
|
||||
} catch {
|
||||
if (line.startsWith('data:')) {
|
||||
try {
|
||||
const json = JSON.parse(line.slice(5))
|
||||
const app = getApp<IAppOption>()
|
||||
const data = json.data
|
||||
const eventName = json.event
|
||||
//分类保存
|
||||
if (data && data.type) {
|
||||
if (!app.globalData.sseStore) app.globalData.sseStore = {}
|
||||
if (!app.globalData.sseStore[data.type]) app.globalData.sseStore[data.type] = []
|
||||
|
||||
//可能会传sku对应商品,直播评论,分类的通知
|
||||
//存入传入的数组
|
||||
app.globalData.sseStore[data.type].push(data)
|
||||
|
||||
//最多只保留100条
|
||||
if (app.globalData.sseStore[data.type].length > 100) {
|
||||
app.globalData.sseStore[data.type].shift()
|
||||
}
|
||||
}
|
||||
|
||||
if (eventName && listeners[eventName]) {
|
||||
listeners[eventName].forEach(fn => fn(data))
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error('解析返回数据失败:', err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
console.warn('sse请求失败:', err)
|
||||
}
|
||||
|
||||
requestTask.onError?.((err: any) => {
|
||||
console.warn('sse连接出错,5秒后重连', err)
|
||||
scheduleReconnect()
|
||||
})
|
||||
|
||||
requestTask.then?.(() => {
|
||||
console.log('sse连接出错,5秒后重连')
|
||||
scheduleReconnect()
|
||||
})
|
||||
}
|
||||
|
||||
//注册事件监听
|
||||
export function addCustomEventListener(eventName: string, fn: (data: any) => void) {
|
||||
if (!listeners[eventName]) listeners[eventName] = []
|
||||
listeners[eventName].push(fn)
|
||||
}
|
||||
|
||||
//移除事件监听
|
||||
export function removeCustomEventListener(eventName: string, fn: (data: any) => void) {
|
||||
if (!listeners[eventName]) return
|
||||
listeners[eventName] = listeners[eventName].filter(f => f !== fn)
|
||||
if (listeners[eventName].length === 0) delete listeners[eventName]
|
||||
}
|
||||
|
||||
//加入频道
|
||||
export async function join(name: string) {
|
||||
const app = getApp<IAppOption>();
|
||||
try {
|
||||
if (app.globalData.joinedArray.includes(name)) {
|
||||
return; //加入过就不加入了
|
||||
}
|
||||
app.globalData.joinedArray = [name, ...app.globalData.joinedArray]; //为什么在request后就不能push了??
|
||||
await request({
|
||||
options: {
|
||||
url: `/auth/message/join`,
|
||||
method: 'POST',
|
||||
data: { id: sign, name }
|
||||
},
|
||||
isLoading: false
|
||||
})
|
||||
} catch {
|
||||
app.globalData.joinedArray = app.globalData.joinedArray.filter(item => item !== name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//离开频道
|
||||
export async function leave(name: string) {
|
||||
const app = getApp<IAppOption>()
|
||||
try {
|
||||
app.globalData.joinedArray = app.globalData.joinedArray.filter(item => item !== name);
|
||||
await request({
|
||||
options: {
|
||||
url: `/auth/message/leave`,
|
||||
method: 'POST',
|
||||
data: { id: wx.getStorageSync('sseId'), name }
|
||||
// data: { id: wx.getStorageSync('sseId'), name: String(shopId) }
|
||||
},
|
||||
isLoading: false
|
||||
})
|
||||
} catch {
|
||||
app.globalData.joinedArray = [name, ...app.globalData.joinedArray];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
//重连
|
||||
function scheduleReconnect() {
|
||||
stopSSE()
|
||||
reconnectTimer = setTimeout(() => {
|
||||
console.log('重连sse中...')
|
||||
startSSE()
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
|
||||
//停止sse
|
||||
export function stopSSE() {
|
||||
if (requestTask) {
|
||||
requestTask.abort?.()
|
||||
requestTask = null
|
||||
}
|
||||
clearTimeout(reconnectTimer)
|
||||
}
|
||||
|
||||
//获取sse的某个分类数据
|
||||
export function getSSEStore(type: string) {
|
||||
const app = getApp<IAppOption>()
|
||||
return app.globalData.sseStore[type] || []
|
||||
}
|
||||
|
||||
//删除sse的某个分类数据
|
||||
export function clearSSEStore(type?: string) {
|
||||
const app = getApp<IAppOption>()
|
||||
if (type) {
|
||||
app.globalData.sseStore[type] = []
|
||||
} else {
|
||||
Object.keys(app.globalData.sseStore).forEach(k => {
|
||||
app.globalData.sseStore[k] = []
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user