init project
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import type { Pinia } from "pinia";
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export const DEFAULT_BODY_BACKGROUND = "#FFF";
|
||||
const THEME_STORAGE_KEY = "gao-customer-h5:theme-mode";
|
||||
|
||||
export type ThemeMode = "light" | "dark" | "system";
|
||||
|
||||
function readStoredThemeMode(): ThemeMode {
|
||||
const rawValue = localStorage.getItem(THEME_STORAGE_KEY);
|
||||
if (rawValue === "light" || rawValue === "dark" || rawValue === "system") {
|
||||
return rawValue;
|
||||
}
|
||||
|
||||
return "system";
|
||||
}
|
||||
|
||||
const store = defineStore("globalUI", () => {
|
||||
const bodyBackground = ref<string>(DEFAULT_BODY_BACKGROUND);
|
||||
const themeMode = ref<ThemeMode>(readStoredThemeMode());
|
||||
|
||||
function setBodyBackground(color?: string): void {
|
||||
bodyBackground.value = color || DEFAULT_BODY_BACKGROUND;
|
||||
}
|
||||
|
||||
function setThemeMode(mode: ThemeMode): void {
|
||||
themeMode.value = mode;
|
||||
localStorage.setItem(THEME_STORAGE_KEY, mode);
|
||||
}
|
||||
|
||||
return {
|
||||
bodyBackground,
|
||||
themeMode,
|
||||
setBodyBackground,
|
||||
setThemeMode
|
||||
};
|
||||
});
|
||||
|
||||
export function useGlobalUIStore(pinia?: Pinia) {
|
||||
return store(pinia);
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
export const useNavBarStore = defineStore("nav-bar", () => {
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const height = ref(0);
|
||||
const title = ref("");
|
||||
const backTo = ref<string>();
|
||||
const rightIcon = ref("");
|
||||
const rightText = ref("");
|
||||
const rightTextColor = ref("");
|
||||
const rightAction = shallowRef<(() => void | Promise<void>) | undefined>();
|
||||
const customTitleVisible = ref(false);
|
||||
const customRightVisible = ref(false);
|
||||
|
||||
const isShowing = computed(() => !!router.currentRoute.value.meta.navBarVisible);
|
||||
const canBack = computed(() => !!router.currentRoute.value.meta.navBarCanBack);
|
||||
|
||||
watch(
|
||||
() => route.meta.navBarTitle,
|
||||
(value) => {
|
||||
title.value = typeof value === "string" ? value : "";
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
function setHeight(value: number): void {
|
||||
height.value = value;
|
||||
}
|
||||
|
||||
function setBackTo(value?: string): void {
|
||||
backTo.value = value;
|
||||
}
|
||||
|
||||
function setRightIcon(value?: string, action?: () => void | Promise<void>): void {
|
||||
rightIcon.value = value || "";
|
||||
rightAction.value = action;
|
||||
if (value) {
|
||||
rightText.value = "";
|
||||
rightTextColor.value = "";
|
||||
customRightVisible.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function setRightText(value?: string, color?: string, action?: () => void | Promise<void>): void {
|
||||
rightText.value = value || "";
|
||||
rightTextColor.value = color || "";
|
||||
rightAction.value = action;
|
||||
if (value) {
|
||||
rightIcon.value = "";
|
||||
customRightVisible.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function setCustomTitleVisible(value: boolean): void {
|
||||
customTitleVisible.value = value;
|
||||
}
|
||||
|
||||
function setCustomRightVisible(value: boolean): void {
|
||||
customRightVisible.value = value;
|
||||
if (value) {
|
||||
rightIcon.value = "";
|
||||
rightText.value = "";
|
||||
rightTextColor.value = "";
|
||||
rightAction.value = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function clearRight(): void {
|
||||
rightIcon.value = "";
|
||||
rightText.value = "";
|
||||
rightTextColor.value = "";
|
||||
rightAction.value = undefined;
|
||||
customRightVisible.value = false;
|
||||
}
|
||||
|
||||
return {
|
||||
height,
|
||||
title,
|
||||
backTo,
|
||||
rightIcon,
|
||||
rightText,
|
||||
rightTextColor,
|
||||
rightAction,
|
||||
customTitleVisible,
|
||||
customRightVisible,
|
||||
isShowing,
|
||||
canBack,
|
||||
setHeight,
|
||||
setBackTo,
|
||||
setRightIcon,
|
||||
setRightText,
|
||||
setCustomTitleVisible,
|
||||
setCustomRightVisible,
|
||||
clearRight
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,339 @@
|
||||
import { defineStore } from "pinia";
|
||||
import {
|
||||
OrderType,
|
||||
PermissionAPI,
|
||||
RoleAPI,
|
||||
type ModuleCode,
|
||||
type Permission as UserPermission,
|
||||
type PermissionPayload,
|
||||
type Role as UserRole,
|
||||
type RolePayload
|
||||
} from "timi-web";
|
||||
|
||||
export const gaoModuleCode: ModuleCode = "GAO";
|
||||
const pageSize = 1000;
|
||||
|
||||
export type Permission = UserPermission;
|
||||
export type Role = UserRole & {
|
||||
permissionIdList: string[];
|
||||
childRoleIdList: string[];
|
||||
};
|
||||
|
||||
export type RoleDraft = {
|
||||
id?: string;
|
||||
code: string;
|
||||
name: string;
|
||||
description: string;
|
||||
nameLangId?: string;
|
||||
moduleCode?: ModuleCode;
|
||||
permissionIdList: string[];
|
||||
childRoleIdList: string[];
|
||||
};
|
||||
export type PermissionDraft = {
|
||||
id?: string;
|
||||
code: string;
|
||||
name: string;
|
||||
description: string;
|
||||
nameLangId?: string;
|
||||
moduleCode?: ModuleCode;
|
||||
};
|
||||
|
||||
function normalizeRole(role: UserRole): Role {
|
||||
return {
|
||||
...role,
|
||||
moduleCode: role.moduleCode || gaoModuleCode,
|
||||
description: role.description || "",
|
||||
permissionIdList: role.permissionIdList || role.permissionList?.map(item => item.id) || [],
|
||||
childRoleIdList: role.childRoleIdList || role.childRoleList?.map(item => item.id) || []
|
||||
};
|
||||
}
|
||||
|
||||
function createEmptyRole(): Role {
|
||||
return {
|
||||
id: "",
|
||||
moduleCode: gaoModuleCode,
|
||||
code: "",
|
||||
name: "",
|
||||
description: "",
|
||||
permissionIdList: [],
|
||||
childRoleIdList: []
|
||||
};
|
||||
}
|
||||
|
||||
function createEmptyPermission(): Permission {
|
||||
return {
|
||||
id: "",
|
||||
moduleCode: gaoModuleCode,
|
||||
code: "",
|
||||
name: "",
|
||||
description: ""
|
||||
};
|
||||
}
|
||||
|
||||
export const usePermissionSettingsStore = defineStore("permission-settings", () => {
|
||||
const roleList = ref<Role[]>([]);
|
||||
const permissionList = ref<Permission[]>([]);
|
||||
const loading = ref(false);
|
||||
const loaded = ref(false);
|
||||
let loadTask: Promise<void> | null = null;
|
||||
|
||||
const roleTree = computed<Array<Role & { level: number }>>(() => {
|
||||
const childIdSet = new Set(roleList.value.flatMap(item => item.childRoleIdList));
|
||||
const roots = roleList.value.filter(item => !childIdSet.has(item.id));
|
||||
return roots.flatMap(item => buildRoleTree(item.id, 0, new Set<string>()));
|
||||
});
|
||||
|
||||
// ---------- 基础数据 ----------
|
||||
|
||||
async function loadAll(force = false): Promise<void> {
|
||||
if (loaded.value && !force) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadTask && !force) {
|
||||
await loadTask;
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
loadTask = (async () => {
|
||||
const [roleResult, permissionResult] = await Promise.all([
|
||||
RoleAPI.list({
|
||||
index: 0,
|
||||
size: pageSize,
|
||||
equalsExample: {
|
||||
moduleCode: gaoModuleCode
|
||||
},
|
||||
orderMap: {
|
||||
code: OrderType.ASC
|
||||
}
|
||||
}),
|
||||
PermissionAPI.list({
|
||||
index: 0,
|
||||
size: pageSize,
|
||||
equalsExample: {
|
||||
moduleCode: gaoModuleCode
|
||||
},
|
||||
orderMap: {
|
||||
code: OrderType.ASC
|
||||
}
|
||||
})
|
||||
]);
|
||||
roleList.value = roleResult.list.map(normalizeRole);
|
||||
permissionList.value = permissionResult.list;
|
||||
loaded.value = true;
|
||||
})();
|
||||
|
||||
try {
|
||||
await loadTask;
|
||||
} finally {
|
||||
loadTask = null;
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRoleDetail(id: string): Promise<Role> {
|
||||
if (!id) {
|
||||
return createEmptyRole();
|
||||
}
|
||||
|
||||
const role = normalizeRole(await RoleAPI.detail(id));
|
||||
upsertLocalRole(role);
|
||||
return role;
|
||||
}
|
||||
|
||||
async function loadPermissionDetail(id: string): Promise<Permission> {
|
||||
if (!id) {
|
||||
return createEmptyPermission();
|
||||
}
|
||||
|
||||
const permission = permissionList.value.find(item => item.id === id);
|
||||
if (!permission) {
|
||||
throw new Error("权限不存在");
|
||||
}
|
||||
upsertLocalPermission(permission);
|
||||
return permission;
|
||||
}
|
||||
|
||||
function upsertLocalRole(role: Role): void {
|
||||
const index = roleList.value.findIndex(item => item.id === role.id);
|
||||
if (-1 < index) {
|
||||
roleList.value.splice(index, 1, role);
|
||||
return;
|
||||
}
|
||||
roleList.value.unshift(role);
|
||||
}
|
||||
|
||||
function upsertLocalPermission(permission: Permission): void {
|
||||
const index = permissionList.value.findIndex(item => item.id === permission.id);
|
||||
if (-1 < index) {
|
||||
permissionList.value.splice(index, 1, permission);
|
||||
return;
|
||||
}
|
||||
permissionList.value.unshift(permission);
|
||||
}
|
||||
|
||||
// ---------- 查询 ----------
|
||||
|
||||
function buildRoleTree(roleId: string, level: number, visitedRoleIds: Set<string>): Array<Role & { level: number }> {
|
||||
const current = getRoleById(roleId);
|
||||
if (!current || visitedRoleIds.has(roleId)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const nextVisitedRoleIds = new Set(visitedRoleIds);
|
||||
nextVisitedRoleIds.add(roleId);
|
||||
|
||||
return [{
|
||||
...current,
|
||||
level
|
||||
}, ...current.childRoleIdList.flatMap(childRoleId => buildRoleTree(childRoleId, level + 1, nextVisitedRoleIds))];
|
||||
}
|
||||
|
||||
function getRoleById(id: string): Role | undefined {
|
||||
return roleList.value.find(item => item.id === id);
|
||||
}
|
||||
|
||||
function getPermissionById(id: string): Permission | undefined {
|
||||
return permissionList.value.find(item => item.id === id);
|
||||
}
|
||||
|
||||
function getChildRoles(role: Role): Role[] {
|
||||
return role.childRoleIdList.map(getRoleById).filter(Boolean) as Role[];
|
||||
}
|
||||
|
||||
function getRolePermissionList(role: Role): Permission[] {
|
||||
return role.permissionIdList.map(getPermissionById).filter(Boolean) as Permission[];
|
||||
}
|
||||
|
||||
function getInheritedPermissionList(role: Role): Permission[] {
|
||||
const permissionMap = new Map<string, Permission>();
|
||||
const allPermissionList = role.allPermissionList || [];
|
||||
for (const permission of [...getRolePermissionList(role), ...allPermissionList]) {
|
||||
permissionMap.set(permission.id, permission);
|
||||
}
|
||||
|
||||
for (const childRole of getChildRoles(role)) {
|
||||
for (const permission of getRolePermissionList(childRole)) {
|
||||
permissionMap.set(permission.id, permission);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(permissionMap.values());
|
||||
}
|
||||
|
||||
// ---------- 写入 ----------
|
||||
|
||||
async function upsertRole(draft: RoleDraft, createMode: boolean): Promise<Role | undefined> {
|
||||
const current = getRoleById(draft.id || "");
|
||||
const payload: RolePayload = {
|
||||
...draft,
|
||||
moduleCode: gaoModuleCode,
|
||||
code: draft.code.trim(),
|
||||
nameLangId: current?.nameLangId,
|
||||
name: draft.name.trim(),
|
||||
description: draft.description.trim(),
|
||||
permissionIdList: [...draft.permissionIdList],
|
||||
childRoleIdList: [...draft.childRoleIdList]
|
||||
};
|
||||
|
||||
if (createMode) {
|
||||
await RoleAPI.create({
|
||||
moduleCode: payload.moduleCode,
|
||||
code: payload.code,
|
||||
name: payload.name,
|
||||
description: payload.description,
|
||||
permissionIdList: payload.permissionIdList,
|
||||
childRoleIdList: payload.childRoleIdList
|
||||
});
|
||||
await loadAll(true);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
await RoleAPI.update(payload);
|
||||
const role = await loadRoleDetail(payload.id || "");
|
||||
await loadAll(true);
|
||||
return role;
|
||||
}
|
||||
|
||||
async function removeRole(id: string): Promise<void> {
|
||||
await RoleAPI.remove(id);
|
||||
roleList.value = roleList.value
|
||||
.filter(item => item.id !== id)
|
||||
.map(item => ({
|
||||
...item,
|
||||
childRoleIdList: item.childRoleIdList.filter(childId => childId !== id)
|
||||
}));
|
||||
}
|
||||
|
||||
async function upsertPermission(draft: PermissionDraft, createMode: boolean): Promise<Permission | undefined> {
|
||||
const current = getPermissionById(draft.id || "");
|
||||
const payload: PermissionPayload = {
|
||||
...draft,
|
||||
moduleCode: gaoModuleCode,
|
||||
code: draft.code.trim(),
|
||||
nameLangId: current?.nameLangId,
|
||||
name: draft.name.trim(),
|
||||
description: draft.description.trim()
|
||||
};
|
||||
|
||||
if (createMode) {
|
||||
await PermissionAPI.create({
|
||||
moduleCode: payload.moduleCode,
|
||||
code: payload.code,
|
||||
name: payload.name,
|
||||
description: payload.description
|
||||
});
|
||||
await loadAll(true);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
await PermissionAPI.update(payload);
|
||||
const permission = await loadPermissionDetail(payload.id || "");
|
||||
await loadAll(true);
|
||||
return permission;
|
||||
}
|
||||
|
||||
async function removePermission(id: string): Promise<void> {
|
||||
await PermissionAPI.remove(id);
|
||||
permissionList.value = permissionList.value.filter(item => item.id !== id);
|
||||
roleList.value = roleList.value.map(item => ({
|
||||
...item,
|
||||
permissionIdList: item.permissionIdList.filter(permissionId => permissionId !== id)
|
||||
}));
|
||||
}
|
||||
|
||||
async function setRolePermissionList(roleId: string, permissionIds: string[]): Promise<void> {
|
||||
const role = await loadRoleDetail(roleId);
|
||||
await upsertRole({
|
||||
id: role.id,
|
||||
moduleCode: gaoModuleCode,
|
||||
code: role.code,
|
||||
name: role.name || "",
|
||||
description: role.description || "",
|
||||
permissionIdList: [...permissionIds],
|
||||
childRoleIdList: [...role.childRoleIdList]
|
||||
}, false);
|
||||
}
|
||||
|
||||
return {
|
||||
roleList,
|
||||
permissionList,
|
||||
roleTree,
|
||||
loading,
|
||||
loaded,
|
||||
loadAll,
|
||||
loadRoleDetail,
|
||||
loadPermissionDetail,
|
||||
getRoleById,
|
||||
getPermissionById,
|
||||
getChildRoles,
|
||||
getRolePermissionList,
|
||||
getInheritedPermissionList,
|
||||
upsertRole,
|
||||
removeRole,
|
||||
upsertPermission,
|
||||
removePermission,
|
||||
setRolePermissionList
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,232 @@
|
||||
import scanSuccessSoundUrl from "@/assets/sound/scan-success.mp3";
|
||||
import registrationSuccessSoundUrl from "@/assets/sound/registration-success.mp3";
|
||||
|
||||
export interface SoundSetting {
|
||||
scanSuccess: boolean;
|
||||
registerSuccess: boolean;
|
||||
volume: number;
|
||||
}
|
||||
|
||||
type SoundType = "scanSuccess" | "registerSuccess";
|
||||
|
||||
const SOUND_STORAGE_KEY = "gao-customer-h5:sound";
|
||||
const soundUrlMap: Record<SoundType, string> = {
|
||||
scanSuccess: scanSuccessSoundUrl,
|
||||
registerSuccess: registrationSuccessSoundUrl
|
||||
};
|
||||
// Windows 某些设备链路对极短提示音的起音处理不稳定,直接播放会偏小。
|
||||
// 这里先补一段极低音量的预热头,把输出链路“叫醒”后再接原音效。
|
||||
const SCAN_SUCCESS_WARMUP_HEAD_MS = 120;
|
||||
const SCAN_SUCCESS_WARMUP_GAIN = .08;
|
||||
|
||||
function createDefaultSoundSetting(): SoundSetting {
|
||||
return {
|
||||
scanSuccess: true,
|
||||
registerSuccess: true,
|
||||
volume: 70
|
||||
};
|
||||
}
|
||||
|
||||
function readStoredSoundSetting(): SoundSetting {
|
||||
const rawValue = localStorage.getItem(SOUND_STORAGE_KEY);
|
||||
if (!rawValue) {
|
||||
return createDefaultSoundSetting();
|
||||
}
|
||||
|
||||
try {
|
||||
const parsedValue = JSON.parse(rawValue) as Partial<SoundSetting>;
|
||||
const volume = Number(parsedValue.volume);
|
||||
return {
|
||||
scanSuccess: parsedValue.scanSuccess !== false,
|
||||
registerSuccess: parsedValue.registerSuccess !== false,
|
||||
volume: Number.isFinite(volume) ? Math.min(100, Math.max(0, volume)) : 70
|
||||
};
|
||||
} catch {
|
||||
return createDefaultSoundSetting();
|
||||
}
|
||||
}
|
||||
|
||||
export const useSettingStore = defineStore("setting", () => {
|
||||
const sound = ref<SoundSetting>(readStoredSoundSetting());
|
||||
const soundPlayerMap = shallowRef<Record<SoundType, HTMLAudioElement> | null>(null);
|
||||
const audioContext = shallowRef<AudioContext | null>(null);
|
||||
const scanSuccessBuffer = shallowRef<AudioBuffer | null>(null);
|
||||
const activeScanSuccessSource = shallowRef<AudioBufferSourceNode | null>(null);
|
||||
const soundUnlocked = ref(false);
|
||||
|
||||
function setSound(value: Partial<SoundSetting>): void {
|
||||
sound.value = {
|
||||
scanSuccess: value.scanSuccess ?? sound.value.scanSuccess,
|
||||
registerSuccess: value.registerSuccess ?? sound.value.registerSuccess,
|
||||
volume: Math.min(100, Math.max(0, value.volume ?? sound.value.volume))
|
||||
};
|
||||
localStorage.setItem(SOUND_STORAGE_KEY, JSON.stringify(sound.value));
|
||||
applySoundVolume();
|
||||
}
|
||||
|
||||
function loadSoundPlayers(): void {
|
||||
if (soundPlayerMap.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
soundPlayerMap.value = {
|
||||
scanSuccess: createSoundPlayer(soundUrlMap.scanSuccess),
|
||||
registerSuccess: createSoundPlayer(soundUrlMap.registerSuccess)
|
||||
};
|
||||
applySoundVolume();
|
||||
}
|
||||
|
||||
function getAudioContext(): AudioContext {
|
||||
if (audioContext.value) {
|
||||
return audioContext.value;
|
||||
}
|
||||
|
||||
audioContext.value = new window.AudioContext();
|
||||
return audioContext.value;
|
||||
}
|
||||
|
||||
function createSoundPlayer(url: string): HTMLAudioElement {
|
||||
const player = new Audio(url);
|
||||
player.preload = "auto";
|
||||
player.load();
|
||||
return player;
|
||||
}
|
||||
|
||||
function applySoundVolume(): void {
|
||||
if (!soundPlayerMap.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const volume = sound.value.volume / 100;
|
||||
Object.values(soundPlayerMap.value).forEach((player) => {
|
||||
player.volume = volume;
|
||||
});
|
||||
}
|
||||
|
||||
async function unlockSoundPlayers(): Promise<boolean> {
|
||||
loadSoundPlayers();
|
||||
if (soundUnlocked.value || !soundPlayerMap.value) {
|
||||
return soundUnlocked.value;
|
||||
}
|
||||
|
||||
try {
|
||||
await getAudioContext().resume();
|
||||
for (const player of Object.values(soundPlayerMap.value)) {
|
||||
player.muted = true;
|
||||
player.volume = 0;
|
||||
player.currentTime = 0;
|
||||
await player.play();
|
||||
player.pause();
|
||||
player.currentTime = 0;
|
||||
player.muted = false;
|
||||
}
|
||||
applySoundVolume();
|
||||
soundUnlocked.value = true;
|
||||
return true;
|
||||
} catch {
|
||||
Object.values(soundPlayerMap.value).forEach((player) => {
|
||||
player.muted = false;
|
||||
});
|
||||
applySoundVolume();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadScanSuccessBuffer(): Promise<AudioBuffer> {
|
||||
if (scanSuccessBuffer.value) {
|
||||
return scanSuccessBuffer.value;
|
||||
}
|
||||
|
||||
const response = await fetch(soundUrlMap.scanSuccess);
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
scanSuccessBuffer.value = await getAudioContext().decodeAudioData(arrayBuffer.slice(0));
|
||||
return scanSuccessBuffer.value;
|
||||
}
|
||||
|
||||
async function playScanSuccessBufferSound(): Promise<void> {
|
||||
const context = getAudioContext();
|
||||
if (context.state === "suspended") {
|
||||
await context.resume();
|
||||
}
|
||||
|
||||
activeScanSuccessSource.value?.stop();
|
||||
activeScanSuccessSource.value?.disconnect();
|
||||
|
||||
const source = context.createBufferSource();
|
||||
const gainNode = context.createGain();
|
||||
source.buffer = await buildScanSuccessPlaybackBuffer();
|
||||
gainNode.gain.value = sound.value.volume / 100;
|
||||
source.connect(gainNode);
|
||||
gainNode.connect(context.destination);
|
||||
source.onended = () => {
|
||||
if (activeScanSuccessSource.value === source) {
|
||||
activeScanSuccessSource.value = null;
|
||||
}
|
||||
source.disconnect();
|
||||
gainNode.disconnect();
|
||||
};
|
||||
activeScanSuccessSource.value = source;
|
||||
source.start(0);
|
||||
}
|
||||
|
||||
async function buildScanSuccessPlaybackBuffer(): Promise<AudioBuffer> {
|
||||
const sourceBuffer = await loadScanSuccessBuffer();
|
||||
const context = getAudioContext();
|
||||
const warmupFrameCount = Math.max(1, Math.round(context.sampleRate * SCAN_SUCCESS_WARMUP_HEAD_MS / 1000));
|
||||
const outputBuffer = context.createBuffer(
|
||||
sourceBuffer.numberOfChannels,
|
||||
sourceBuffer.length + warmupFrameCount,
|
||||
sourceBuffer.sampleRate
|
||||
);
|
||||
|
||||
for (let channelIndex = 0; channelIndex < sourceBuffer.numberOfChannels; channelIndex += 1) {
|
||||
const outputData = outputBuffer.getChannelData(channelIndex);
|
||||
const sourceData = sourceBuffer.getChannelData(channelIndex);
|
||||
const warmupSourceLength = Math.min(sourceData.length, warmupFrameCount);
|
||||
// 预热头不能是纯静音;纯静音对这类 Windows 问题无效。
|
||||
for (let frameIndex = 0; frameIndex < warmupFrameCount; frameIndex += 1) {
|
||||
const sourceIndex = frameIndex % warmupSourceLength;
|
||||
outputData[frameIndex] = sourceData[sourceIndex] * SCAN_SUCCESS_WARMUP_GAIN;
|
||||
}
|
||||
outputData.set(sourceData, warmupFrameCount);
|
||||
}
|
||||
|
||||
return outputBuffer;
|
||||
}
|
||||
|
||||
async function playSound(type: SoundType): Promise<void> {
|
||||
if (type === "scanSuccess") {
|
||||
await playScanSuccessBufferSound();
|
||||
return;
|
||||
}
|
||||
|
||||
loadSoundPlayers();
|
||||
if (!sound.value[type] || !soundPlayerMap.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const player = soundPlayerMap.value[type];
|
||||
player.pause();
|
||||
player.muted = false;
|
||||
player.volume = sound.value.volume / 100;
|
||||
player.currentTime = 0;
|
||||
await player.play();
|
||||
}
|
||||
|
||||
async function playScanSuccessSound(): Promise<void> {
|
||||
await playSound("scanSuccess");
|
||||
}
|
||||
|
||||
async function playRegisterSuccessSound(): Promise<void> {
|
||||
await playSound("registerSuccess");
|
||||
}
|
||||
|
||||
return {
|
||||
sound,
|
||||
setSound,
|
||||
loadSoundPlayers,
|
||||
unlockSoundPlayers,
|
||||
playScanSuccessSound,
|
||||
playRegisterSuccessSound
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
export const useTabBarStore = defineStore("tab-bar", () => {
|
||||
const router = useRouter();
|
||||
|
||||
const isShowing = computed(() => !!router.currentRoute.value.meta.tabBarVisible);
|
||||
|
||||
return {
|
||||
isShowing
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user