fix(ts): 补全音乐播放器进度交互并修复低风险类型错误
- 音乐列表改用 typed provide/inject + MusicSong,audioBar 接真实 audio 状态(进度/时长/seek/换源重载) - 修复 SocialLogin 验证码因布尔误判而从不显示 - getBoolDictOptions 返回类型收窄为 boolean,:key 统一 String() - CRM/售后 tab.paneName 判空 + String() 后写入查询参数 - demo03 子表入参字段名对齐接口(demo03courses/demo03grade)等单点修复 ts:check 829 → 795,无新增类型错误pull/885/MERGE
parent
a57df0b2de
commit
0970806dca
|
|
@ -28,6 +28,10 @@ export interface StringDictDataType extends DictDataType {
|
|||
value: string
|
||||
}
|
||||
|
||||
export interface BooleanDictDataType extends DictDataType {
|
||||
value: boolean
|
||||
}
|
||||
|
||||
export const getDictOptions = (dictType: string) => {
|
||||
return dictStore.getDictByType(dictType) || []
|
||||
}
|
||||
|
|
@ -62,8 +66,8 @@ export const getStrDictOptions = (dictType: string) => {
|
|||
return dictOption
|
||||
}
|
||||
|
||||
export const getBoolDictOptions = (dictType: string) => {
|
||||
const dictOption: DictDataType[] = []
|
||||
export const getBoolDictOptions = (dictType: string): BooleanDictDataType[] => {
|
||||
const dictOption: BooleanDictDataType[] = []
|
||||
const dictOptions: DictDataType[] = getDictOptions(dictType)
|
||||
dictOptions.forEach((dict: DictDataType) => {
|
||||
dictOption.push({
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@
|
|||
</el-form-item>
|
||||
</el-col>
|
||||
<Verify
|
||||
v-if="loginData.captchaEnable === 'true'"
|
||||
v-if="loginData.captchaEnable"
|
||||
ref="verify"
|
||||
:captchaType="captchaType"
|
||||
:imgSize="{ width: '400px', height: '200px' }"
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
<el-select v-model="queryParams.status" placeholder="请选择平台" clearable class="!w-240px">
|
||||
<el-option
|
||||
v-for="dict in getStrDictOptions(DICT_TYPE.AI_PLATFORM)"
|
||||
:key="dict.value"
|
||||
:key="String(dict.value)"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
|
|
@ -59,7 +59,7 @@
|
|||
>
|
||||
<el-option
|
||||
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
||||
:key="dict.value"
|
||||
:key="String(dict.value)"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@
|
|||
<el-select v-model="formData.mcpClientNames" placeholder="请选择 MCP" clearable multiple>
|
||||
<el-option
|
||||
v-for="dict in getStrDictOptions(DICT_TYPE.AI_MCP_CLIENT_NAME)"
|
||||
:key="dict.value"
|
||||
:key="String(dict.value)"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
|
|
@ -61,7 +61,7 @@
|
|||
<el-radio-group v-model="formData.publicStatus">
|
||||
<el-radio
|
||||
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
||||
:key="dict.value"
|
||||
:key="String(dict.value)"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@
|
|||
>
|
||||
<el-option
|
||||
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
||||
:key="dict.value"
|
||||
:key="String(dict.value)"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@
|
|||
class="w-[45px]"
|
||||
/>
|
||||
<div>
|
||||
<div>{{ currentSong.name }}</div>
|
||||
<div class="text-[12px] text-gray-400">{{ currentSong.singer }}</div>
|
||||
<div>{{ currentSong.title || '暂无音乐' }}</div>
|
||||
<div class="text-[12px] text-gray-400">{{ currentSong.singer || currentSong.desc }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -26,19 +26,26 @@
|
|||
<Icon icon="majesticons:next-circle" :size="20" class="text-gray-300 cursor-pointer" />
|
||||
<div class="flex gap-[16px] items-center">
|
||||
<span>{{ audioProps.currentTime }}</span>
|
||||
<el-slider v-model="audioProgress" color="#409eff" class="w-[160px!important]" />
|
||||
<el-slider
|
||||
v-model="audioProgress"
|
||||
:max="audioDuration"
|
||||
color="#409eff"
|
||||
class="w-[160px!important]"
|
||||
@change="handleProgressChange"
|
||||
/>
|
||||
<span>{{ audioProps.duration }}</span>
|
||||
</div>
|
||||
<!-- 音频 -->
|
||||
<audio
|
||||
v-bind="audioProps"
|
||||
:src="currentAudioUrl"
|
||||
:autoplay="audioProps.autoplay"
|
||||
:muted="audioProps.muted"
|
||||
ref="audioRef"
|
||||
controls
|
||||
v-show="!audioProps"
|
||||
@timeupdate="audioTimeUpdate"
|
||||
>
|
||||
<source :src="audioUrl" />
|
||||
</audio>
|
||||
@loadedmetadata="audioLoadedMetadata"
|
||||
></audio>
|
||||
</div>
|
||||
|
||||
<!-- 音量控制器 -->
|
||||
|
|
@ -55,15 +62,17 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { formatPast } from '@/utils/formatTime'
|
||||
import audioUrl from '@/assets/audio/response.mp3'
|
||||
import { currentSongKey, type MusicSong } from '../types'
|
||||
|
||||
defineOptions({ name: 'Index' })
|
||||
|
||||
const currentSong = inject('currentSong', {})
|
||||
const currentSong = inject(currentSongKey, ref<MusicSong>({}))
|
||||
const currentAudioUrl = computed(() => currentSong.value.audioUrl || audioUrl)
|
||||
|
||||
const audioRef = ref<Nullable<HTMLAudioElement>>(null)
|
||||
const audioProgress = ref(0)
|
||||
const audioDuration = ref(0)
|
||||
// 音频相关属性https://www.runoob.com/tags/ref-av-dom.html
|
||||
const audioProps = reactive({
|
||||
autoplay: true,
|
||||
|
|
@ -74,6 +83,15 @@ const audioProps = reactive({
|
|||
volume: 50
|
||||
})
|
||||
|
||||
const formatAudioTime = (seconds: number) => {
|
||||
if (!Number.isFinite(seconds)) {
|
||||
return '00:00'
|
||||
}
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const remainingSeconds = Math.floor(seconds % 60)
|
||||
return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`
|
||||
}
|
||||
|
||||
function toggleStatus(type: string) {
|
||||
audioProps[type] = !audioProps[type]
|
||||
if (type === 'paused' && audioRef.value) {
|
||||
|
|
@ -86,7 +104,38 @@ function toggleStatus(type: string) {
|
|||
}
|
||||
|
||||
// 更新播放位置
|
||||
function audioTimeUpdate(args) {
|
||||
audioProps.currentTime = formatPast(new Date(args.timeStamp), 'mm:ss')
|
||||
function audioTimeUpdate() {
|
||||
if (!audioRef.value) {
|
||||
return
|
||||
}
|
||||
audioProgress.value = audioRef.value.currentTime
|
||||
audioProps.currentTime = formatAudioTime(audioRef.value.currentTime)
|
||||
}
|
||||
|
||||
function audioLoadedMetadata() {
|
||||
if (!audioRef.value) {
|
||||
return
|
||||
}
|
||||
audioDuration.value = audioRef.value.duration
|
||||
audioProps.duration = formatAudioTime(audioRef.value.duration)
|
||||
}
|
||||
|
||||
function handleProgressChange(value: number | number[]) {
|
||||
if (!audioRef.value || Array.isArray(value)) {
|
||||
return
|
||||
}
|
||||
audioRef.value.currentTime = value
|
||||
audioProgress.value = value
|
||||
audioProps.currentTime = formatAudioTime(value)
|
||||
}
|
||||
|
||||
watch(currentAudioUrl, () => {
|
||||
audioProgress.value = 0
|
||||
audioDuration.value = 0
|
||||
audioProps.currentTime = '00:00'
|
||||
audioProps.duration = '00:00'
|
||||
nextTick(() => {
|
||||
audioRef.value?.load()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
import songCard from './songCard/index.vue'
|
||||
import songInfo from './songInfo/index.vue'
|
||||
import audioBar from './audioBar/index.vue'
|
||||
import { currentSongKey, type MusicSong } from './types'
|
||||
|
||||
defineOptions({ name: 'Index' })
|
||||
|
||||
|
|
@ -40,12 +41,12 @@ const currentType = ref('mine')
|
|||
// loading 状态
|
||||
const loading = ref(false)
|
||||
// 当前音乐
|
||||
const currentSong = ref({})
|
||||
const currentSong = ref<MusicSong>({})
|
||||
|
||||
const mySongList = ref<Recordable[]>([])
|
||||
const squareSongList = ref<Recordable[]>([])
|
||||
const mySongList = ref<MusicSong[]>([])
|
||||
const squareSongList = ref<MusicSong[]>([])
|
||||
|
||||
provide('currentSong', currentSong)
|
||||
provide(currentSongKey, currentSong)
|
||||
|
||||
/*
|
||||
*@Description: 调接口生成音乐列表
|
||||
|
|
@ -86,7 +87,7 @@ function generateMusic(formData: Recordable) {
|
|||
*@MethodAuthor: xiaohong
|
||||
*@Date: 2024-07-19 11:22:33
|
||||
*/
|
||||
function setCurrentSong(music: Recordable) {
|
||||
function setCurrentSong(music: MusicSong) {
|
||||
currentSong.value = music
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,18 +25,17 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { currentSongKey, type MusicSong } from '../types'
|
||||
|
||||
defineOptions({ name: 'Index' })
|
||||
|
||||
defineProps({
|
||||
songInfo: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
withDefaults(defineProps<{ songInfo?: MusicSong }>(), {
|
||||
songInfo: () => ({})
|
||||
})
|
||||
|
||||
const emits = defineEmits(['play'])
|
||||
|
||||
const currentSong = inject('currentSong', {})
|
||||
const currentSong = inject(currentSongKey, ref<MusicSong>({}))
|
||||
|
||||
function playSong() {
|
||||
emits('play')
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { currentSongKey, type MusicSong } from '../types'
|
||||
|
||||
defineOptions({ name: 'Index' })
|
||||
|
||||
const currentSong = inject('currentSong', {})
|
||||
const currentSong = inject(currentSongKey, ref<MusicSong>({}))
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
import type { InjectionKey, Ref } from 'vue'
|
||||
|
||||
export interface MusicSong {
|
||||
id?: number
|
||||
title?: string
|
||||
singer?: string
|
||||
imageUrl?: string
|
||||
audioUrl?: string
|
||||
desc?: string
|
||||
date?: string
|
||||
lyric?: string
|
||||
}
|
||||
|
||||
export const currentSongKey: InjectionKey<Ref<MusicSong>> = Symbol('currentSong')
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.AI_MUSIC_STATUS)"
|
||||
:key="dict.value"
|
||||
:key="String(dict.value)"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
|
|
@ -84,7 +84,7 @@
|
|||
>
|
||||
<el-option
|
||||
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
||||
:key="dict.value"
|
||||
:key="String(dict.value)"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@
|
|||
<el-radio-group v-model="modelData.visible">
|
||||
<el-radio
|
||||
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
||||
:key="dict.value as string"
|
||||
:key="String(dict.value)"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
|
|
|
|||
|
|
@ -219,7 +219,10 @@ const resetQuery = () => {
|
|||
|
||||
/** tab 切换 */
|
||||
const handleTabClick = (tab: TabsPaneContext) => {
|
||||
queryParams.sceneType = tab.paneName
|
||||
if (tab.paneName === undefined) {
|
||||
return
|
||||
}
|
||||
queryParams.sceneType = String(tab.paneName)
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -219,7 +219,10 @@ const resetQuery = () => {
|
|||
|
||||
/** tab 切换 */
|
||||
const handleTabClick = (tab: TabsPaneContext) => {
|
||||
queryParams.sceneType = tab.paneName
|
||||
if (tab.paneName === undefined) {
|
||||
return
|
||||
}
|
||||
queryParams.sceneType = String(tab.paneName)
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@
|
|||
<el-radio-group v-model="formData.master">
|
||||
<el-radio
|
||||
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
||||
:key="dict.value"
|
||||
:key="String(dict.value)"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
|
|
|
|||
|
|
@ -275,7 +275,10 @@ const resetQuery = () => {
|
|||
|
||||
/** tab 切换 */
|
||||
const handleTabClick = (tab: TabsPaneContext) => {
|
||||
queryParams.sceneType = tab.paneName
|
||||
if (tab.paneName === undefined) {
|
||||
return
|
||||
}
|
||||
queryParams.sceneType = String(tab.paneName)
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -294,7 +294,10 @@ const customerList = ref<CustomerApi.CustomerVO[]>([]) // 客户列表
|
|||
|
||||
/** tab 切换 */
|
||||
const handleTabClick = (tab: TabsPaneContext) => {
|
||||
queryParams.sceneType = tab.paneName
|
||||
if (tab.paneName === undefined) {
|
||||
return
|
||||
}
|
||||
queryParams.sceneType = String(tab.paneName)
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -235,7 +235,10 @@ const customerList = ref<CustomerApi.CustomerVO[]>([]) // 客户列表
|
|||
|
||||
/** tab 切换 */
|
||||
const handleTabClick = (tab: TabsPaneContext) => {
|
||||
queryParams.sceneType = tab.paneName
|
||||
if (tab.paneName === undefined) {
|
||||
return
|
||||
}
|
||||
queryParams.sceneType = String(tab.paneName)
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -247,7 +247,10 @@ const customerList = ref<CustomerApi.CustomerVO[]>([]) // 客户列表
|
|||
|
||||
/** tab 切换 */
|
||||
const handleTabClick = (tab: TabsPaneContext) => {
|
||||
queryParams.sceneType = tab.paneName
|
||||
if (tab.paneName === undefined) {
|
||||
return
|
||||
}
|
||||
queryParams.sceneType = String(tab.paneName)
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ProductUnitApi } from '@/api/erp/product/unit'
|
||||
import { ProductUnitApi, type ProductUnitVO } from '@/api/erp/product/unit'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
|
||||
|
|
@ -80,7 +80,7 @@ const submitForm = async () => {
|
|||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as ProductUnitApi.ProductUnitVO
|
||||
const data = formData.value as unknown as ProductUnitVO
|
||||
if (formType.value === 'create') {
|
||||
await ProductUnitApi.createProductUnit(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
<el-radio-group v-model="formData.visible">
|
||||
<el-radio
|
||||
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
||||
:key="dict.value as string"
|
||||
:key="String(dict.value)"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
|
|
|
|||
|
|
@ -125,8 +125,8 @@ const submitForm = async () => {
|
|||
try {
|
||||
const data = formData.value as unknown as Demo03Student
|
||||
// 拼接子表的数据
|
||||
data.demo03Courses = demo03CourseFormRef.value.getData()
|
||||
data.demo03Grade = demo03GradeFormRef.value.getData()
|
||||
data.demo03courses = demo03CourseFormRef.value.getData()
|
||||
data.demo03grade = demo03GradeFormRef.value.getData()
|
||||
if (formType.value === 'create') {
|
||||
await Demo03StudentApi.createDemo03Student(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
|
|
|
|||
|
|
@ -125,8 +125,8 @@ const submitForm = async () => {
|
|||
try {
|
||||
const data = formData.value as unknown as Demo03Student
|
||||
// 拼接子表的数据
|
||||
data.demo03Courses = demo03CourseFormRef.value.getData()
|
||||
data.demo03Grade = demo03GradeFormRef.value.getData()
|
||||
data.demo03courses = demo03CourseFormRef.value.getData()
|
||||
data.demo03grade = demo03GradeFormRef.value.getData()
|
||||
if (formType.value === 'create') {
|
||||
await Demo03StudentApi.createDemo03Student(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@
|
|||
<el-radio-group v-model="formData.status">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:key="String(dict.value)"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
|
|
@ -63,7 +63,7 @@
|
|||
<el-radio-group v-model="formData.recommendHot">
|
||||
<el-radio
|
||||
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
||||
:key="dict.value"
|
||||
:key="String(dict.value)"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
|
|
@ -76,7 +76,7 @@
|
|||
<el-radio-group v-model="formData.recommendBanner">
|
||||
<el-radio
|
||||
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
||||
:key="dict.value"
|
||||
:key="String(dict.value)"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
|
|
|
|||
|
|
@ -246,7 +246,7 @@ const handleEmitChange = () => {
|
|||
|
||||
/** 确认选择时的触发事件 */
|
||||
const emits = defineEmits<{
|
||||
(e: CHANGE_EVENT, v: PointActivityVO | PointActivityVO[] | any): void
|
||||
(e: typeof CHANGE_EVENT, v: PointActivityVO | PointActivityVO[] | any): void
|
||||
}>()
|
||||
|
||||
/** 全选/全不选 */
|
||||
|
|
|
|||
|
|
@ -239,7 +239,10 @@ const resetQuery = () => {
|
|||
|
||||
/** tab 切换 */
|
||||
const tabClick = async (tab: TabsPaneContext) => {
|
||||
queryParams.status = tab.paneName
|
||||
if (tab.paneName === undefined) {
|
||||
return
|
||||
}
|
||||
queryParams.status = String(tab.paneName)
|
||||
await getList()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
<el-radio-group v-model="formData.primaryFlag">
|
||||
<el-radio
|
||||
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
||||
:key="dict.value"
|
||||
:key="String(dict.value)"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ const onAccountChanged = (id: number) => {
|
|||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const onBeforeDialogClose = async (onDone: () => {}) => {
|
||||
const onBeforeDialogClose = async (onDone: () => void) => {
|
||||
try {
|
||||
await message.confirm('修改内容可能还未保存,确定关闭吗?')
|
||||
onDone()
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
<el-radio-group v-model="formData.sslEnable">
|
||||
<el-radio
|
||||
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
||||
:key="dict.value"
|
||||
:key="String(dict.value)"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
|
|
@ -47,7 +47,7 @@
|
|||
<el-radio-group v-model="formData.starttlsEnable">
|
||||
<el-radio
|
||||
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
||||
:key="dict.value"
|
||||
:key="String(dict.value)"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
>
|
||||
<el-option
|
||||
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
||||
:key="dict.value"
|
||||
:key="String(dict.value)"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
|
|
|
|||
Loading…
Reference in New Issue