提交 e76aa13e authored 作者: 王鹏飞's avatar 王鹏飞

feat: 直播相关部分修改

上级 080fbef8
{
"globals": {
"$": true,
"$$": true,
"$computed": true,
"$customRef": true,
"$ref": true,
"$shallowRef": true,
"$toRef": true,
"Component": true,
"ComponentPublicInstance": true,
"ComputedRef": true,
"DirectiveBinding": true,
"EffectScope": true,
"ExtractDefaultPropTypes": true,
"ExtractPropTypes": true,
"ExtractPublicPropTypes": true,
"InjectionKey": true,
"MaybeRef": true,
"MaybeRefOrGetter": true,
"PropType": true,
"Ref": true,
"ShallowRef": true,
"Slot": true,
"Slots": true,
"VNode": true,
"WritableComputedRef": true,
"asyncComputed": true,
"autoResetRef": true,
"computed": true,
......@@ -29,7 +32,10 @@
"createGlobalState": true,
"createInjectionState": true,
"createReactiveFn": true,
"createRef": true,
"createReusableTemplate": true,
"createSharedComposable": true,
"createTemplatePromise": true,
"createUnrefFn": true,
"customRef": true,
"debouncedRef": true,
......@@ -41,14 +47,17 @@
"extendRef": true,
"getCurrentInstance": true,
"getCurrentScope": true,
"getCurrentWatcher": true,
"h": true,
"ignorableWatch": true,
"inject": true,
"injectLocal": true,
"isDefined": true,
"isProxy": true,
"isReactive": true,
"isReadonly": true,
"isRef": true,
"isShallow": true,
"makeDestructurable": true,
"markRaw": true,
"nextTick": true,
......@@ -60,6 +69,7 @@
"onBeforeUpdate": true,
"onClickOutside": true,
"onDeactivated": true,
"onElementRemoval": true,
"onErrorCaptured": true,
"onKeyStroke": true,
"onLongPress": true,
......@@ -71,8 +81,10 @@
"onStartTyping": true,
"onUnmounted": true,
"onUpdated": true,
"onWatcherCleanup": true,
"pausableWatch": true,
"provide": true,
"provideLocal": true,
"reactify": true,
"reactifyObject": true,
"reactive": true,
......@@ -101,6 +113,7 @@
"toReactive": true,
"toRef": true,
"toRefs": true,
"toValue": true,
"triggerRef": true,
"tryOnBeforeMount": true,
"tryOnBeforeUnmount": true,
......@@ -111,11 +124,14 @@
"unrefElement": true,
"until": true,
"useActiveElement": true,
"useAnimate": true,
"useArrayDifference": true,
"useArrayEvery": true,
"useArrayFilter": true,
"useArrayFind": true,
"useArrayFindIndex": true,
"useArrayFindLast": true,
"useArrayIncludes": true,
"useArrayJoin": true,
"useArrayMap": true,
"useArrayReduce": true,
......@@ -132,9 +148,11 @@
"useBrowserLocation": true,
"useCached": true,
"useClipboard": true,
"useClipboardItems": true,
"useCloned": true,
"useColorMode": true,
"useConfirmDialog": true,
"useCountdown": true,
"useCounter": true,
"useCssModule": true,
"useCssVar": true,
......@@ -173,6 +191,7 @@
"useFullscreen": true,
"useGamepad": true,
"useGeolocation": true,
"useId": true,
"useIdle": true,
"useImage": true,
"useInfiniteScroll": true,
......@@ -189,6 +208,7 @@
"useMediaQuery": true,
"useMemoize": true,
"useMemory": true,
"useModel": true,
"useMounted": true,
"useMouse": true,
"useMouseInElement": true,
......@@ -202,6 +222,8 @@
"useOnline": true,
"usePageLeave": true,
"useParallax": true,
"useParentElement": true,
"usePerformanceObserver": true,
"usePermission": true,
"usePointer": true,
"usePointerLock": true,
......@@ -211,12 +233,14 @@
"usePreferredDark": true,
"usePreferredLanguages": true,
"usePreferredReducedMotion": true,
"usePreferredReducedTransparency": true,
"usePrevious": true,
"useRafFn": true,
"useRefHistory": true,
"useResizeObserver": true,
"useRoute": true,
"useRouter": true,
"useSSRWidth": true,
"useScreenOrientation": true,
"useScreenSafeArea": true,
"useScriptTag": true,
......@@ -234,6 +258,7 @@
"useStyleTag": true,
"useSupported": true,
"useSwipe": true,
"useTemplateRef": true,
"useTemplateRefsList": true,
"useTextDirection": true,
"useTextSelection": true,
......@@ -269,8 +294,10 @@
"watchArray": true,
"watchAtMost": true,
"watchDebounced": true,
"watchDeep": true,
"watchEffect": true,
"watchIgnorable": true,
"watchImmediate": true,
"watchOnce": true,
"watchPausable": true,
"watchPostEffect": true,
......@@ -278,35 +305,6 @@
"watchThrottled": true,
"watchTriggerable": true,
"watchWithFilter": true,
"whenever": true,
"toValue": true,
"createReusableTemplate": true,
"createTemplatePromise": true,
"useAnimate": true,
"useArrayDifference": true,
"useArrayIncludes": true,
"useParentElement": true,
"usePerformanceObserver": true,
"watchDeep": true,
"watchImmediate": true,
"ExtractDefaultPropTypes": true,
"ExtractPropTypes": true,
"ExtractPublicPropTypes": true,
"WritableComputedRef": true,
"injectLocal": true,
"provideLocal": true,
"useClipboardItems": true,
"DirectiveBinding": true,
"MaybeRef": true,
"MaybeRefOrGetter": true,
"onWatcherCleanup": true,
"useId": true,
"useModel": true,
"useTemplateRef": true,
"createRef": true,
"onElementRemoval": true,
"useCountdown": true,
"usePreferredReducedTransparency": true,
"useSSRWidth": true
"whenever": true
}
}
......@@ -13,297 +13,299 @@ declare global {
const $ref: typeof import('vue/macros')['$ref']
const $shallowRef: typeof import('vue/macros')['$shallowRef']
const $toRef: typeof import('vue/macros')['$toRef']
const EffectScope: typeof import('vue')['EffectScope']
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
const computed: typeof import('vue')['computed']
const computedAsync: typeof import('@vueuse/core')['computedAsync']
const computedEager: typeof import('@vueuse/core')['computedEager']
const computedInject: typeof import('@vueuse/core')['computedInject']
const computedWithControl: typeof import('@vueuse/core')['computedWithControl']
const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
const controlledRef: typeof import('@vueuse/core')['controlledRef']
const createApp: typeof import('vue')['createApp']
const createEventHook: typeof import('@vueuse/core')['createEventHook']
const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
const createInjectionState: typeof import('@vueuse/core')['createInjectionState']
const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
const createRef: typeof import('@vueuse/core')['createRef']
const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate']
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise']
const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn']
const customRef: typeof import('vue')['customRef']
const debouncedRef: typeof import('@vueuse/core')['debouncedRef']
const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent']
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
const effectScope: typeof import('vue')['effectScope']
const extendRef: typeof import('@vueuse/core')['extendRef']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const h: typeof import('vue')['h']
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
const inject: typeof import('vue')['inject']
const injectLocal: typeof import('@vueuse/core')['injectLocal']
const isDefined: typeof import('@vueuse/core')['isDefined']
const isProxy: typeof import('vue')['isProxy']
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
const onDeactivated: typeof import('vue')['onDeactivated']
const onElementRemoval: typeof import('@vueuse/core')['onElementRemoval']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke']
const onLongPress: typeof import('@vueuse/core')['onLongPress']
const onMounted: typeof import('vue')['onMounted']
const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onScopeDispose: typeof import('vue')['onScopeDispose']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onStartTyping: typeof import('@vueuse/core')['onStartTyping']
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
const provide: typeof import('vue')['provide']
const provideLocal: typeof import('@vueuse/core')['provideLocal']
const reactify: typeof import('@vueuse/core')['reactify']
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
const reactive: typeof import('vue')['reactive']
const reactiveComputed: typeof import('@vueuse/core')['reactiveComputed']
const reactiveOmit: typeof import('@vueuse/core')['reactiveOmit']
const reactivePick: typeof import('@vueuse/core')['reactivePick']
const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']
const refAutoReset: typeof import('@vueuse/core')['refAutoReset']
const refDebounced: typeof import('@vueuse/core')['refDebounced']
const refDefault: typeof import('@vueuse/core')['refDefault']
const refThrottled: typeof import('@vueuse/core')['refThrottled']
const refWithControl: typeof import('@vueuse/core')['refWithControl']
const resolveComponent: typeof import('vue')['resolveComponent']
const resolveRef: typeof import('@vueuse/core')['resolveRef']
const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const syncRef: typeof import('@vueuse/core')['syncRef']
const syncRefs: typeof import('@vueuse/core')['syncRefs']
const templateRef: typeof import('@vueuse/core')['templateRef']
const throttledRef: typeof import('@vueuse/core')['throttledRef']
const throttledWatch: typeof import('@vueuse/core')['throttledWatch']
const toRaw: typeof import('vue')['toRaw']
const toReactive: typeof import('@vueuse/core')['toReactive']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('vue')['toValue']
const triggerRef: typeof import('vue')['triggerRef']
const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted']
const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose']
const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted']
const unref: typeof import('vue')['unref']
const unrefElement: typeof import('@vueuse/core')['unrefElement']
const until: typeof import('@vueuse/core')['until']
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
const useAnimate: typeof import('@vueuse/core')['useAnimate']
const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference']
const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery']
const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter']
const useArrayFind: typeof import('@vueuse/core')['useArrayFind']
const useArrayFindIndex: typeof import('@vueuse/core')['useArrayFindIndex']
const useArrayFindLast: typeof import('@vueuse/core')['useArrayFindLast']
const useArrayIncludes: typeof import('@vueuse/core')['useArrayIncludes']
const useArrayJoin: typeof import('@vueuse/core')['useArrayJoin']
const useArrayMap: typeof import('@vueuse/core')['useArrayMap']
const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce']
const useArraySome: typeof import('@vueuse/core')['useArraySome']
const useArrayUnique: typeof import('@vueuse/core')['useArrayUnique']
const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
const useAttrs: typeof import('vue')['useAttrs']
const useBase64: typeof import('@vueuse/core')['useBase64']
const useBattery: typeof import('@vueuse/core')['useBattery']
const useBluetooth: typeof import('@vueuse/core')['useBluetooth']
const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints']
const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel']
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
const useCached: typeof import('@vueuse/core')['useCached']
const useClipboard: typeof import('@vueuse/core')['useClipboard']
const useClipboardItems: typeof import('@vueuse/core')['useClipboardItems']
const useCloned: typeof import('@vueuse/core')['useCloned']
const useColorMode: typeof import('@vueuse/core')['useColorMode']
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
const useCountdown: typeof import('@vueuse/core')['useCountdown']
const useCounter: typeof import('@vueuse/core')['useCounter']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVar: typeof import('@vueuse/core')['useCssVar']
const useCssVars: typeof import('vue')['useCssVars']
const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement']
const useCycleList: typeof import('@vueuse/core')['useCycleList']
const useDark: typeof import('@vueuse/core')['useDark']
const useDateFormat: typeof import('@vueuse/core')['useDateFormat']
const useDebounce: typeof import('@vueuse/core')['useDebounce']
const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']
const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory']
const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion']
const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation']
const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio']
const useDevicesList: typeof import('@vueuse/core')['useDevicesList']
const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia']
const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility']
const useDraggable: typeof import('@vueuse/core')['useDraggable']
const useDropZone: typeof import('@vueuse/core')['useDropZone']
const useElementBounding: typeof import('@vueuse/core')['useElementBounding']
const useElementByPoint: typeof import('@vueuse/core')['useElementByPoint']
const useElementHover: typeof import('@vueuse/core')['useElementHover']
const useElementSize: typeof import('@vueuse/core')['useElementSize']
const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility']
const useEventBus: typeof import('@vueuse/core')['useEventBus']
const useEventListener: typeof import('@vueuse/core')['useEventListener']
const useEventSource: typeof import('@vueuse/core')['useEventSource']
const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper']
const useFavicon: typeof import('@vueuse/core')['useFavicon']
const useFetch: typeof import('@vueuse/core')['useFetch']
const useFileDialog: typeof import('@vueuse/core')['useFileDialog']
const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess']
const useFocus: typeof import('@vueuse/core')['useFocus']
const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin']
const useFps: typeof import('@vueuse/core')['useFps']
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
const useGamepad: typeof import('@vueuse/core')['useGamepad']
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
const useId: typeof import('vue')['useId']
const useIdle: typeof import('@vueuse/core')['useIdle']
const useImage: typeof import('@vueuse/core')['useImage']
const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver']
const useInterval: typeof import('@vueuse/core')['useInterval']
const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn']
const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier']
const useLastChanged: typeof import('@vueuse/core')['useLastChanged']
const useLink: typeof import('vue-router')['useLink']
const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage']
const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys']
const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory']
const useMediaControls: typeof import('@vueuse/core')['useMediaControls']
const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery']
const useMemoize: typeof import('@vueuse/core')['useMemoize']
const useMemory: typeof import('@vueuse/core')['useMemory']
const useModel: typeof import('vue')['useModel']
const useMounted: typeof import('@vueuse/core')['useMounted']
const useMouse: typeof import('@vueuse/core')['useMouse']
const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement']
const useMousePressed: typeof import('@vueuse/core')['useMousePressed']
const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver']
const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage']
const useNetwork: typeof import('@vueuse/core')['useNetwork']
const useNow: typeof import('@vueuse/core')['useNow']
const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl']
const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination']
const useOnline: typeof import('@vueuse/core')['useOnline']
const usePageLeave: typeof import('@vueuse/core')['usePageLeave']
const useParallax: typeof import('@vueuse/core')['useParallax']
const useParentElement: typeof import('@vueuse/core')['useParentElement']
const usePerformanceObserver: typeof import('@vueuse/core')['usePerformanceObserver']
const usePermission: typeof import('@vueuse/core')['usePermission']
const usePointer: typeof import('@vueuse/core')['usePointer']
const usePointerLock: typeof import('@vueuse/core')['usePointerLock']
const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe']
const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme']
const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast']
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion']
const usePreferredReducedTransparency: typeof import('@vueuse/core')['usePreferredReducedTransparency']
const usePrevious: typeof import('@vueuse/core')['usePrevious']
const useRafFn: typeof import('@vueuse/core')['useRafFn']
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
const useRoute: typeof import('vue-router')['useRoute']
const useRouter: typeof import('vue-router')['useRouter']
const useSSRWidth: typeof import('@vueuse/core')['useSSRWidth']
const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation']
const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea']
const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
const useScroll: typeof import('@vueuse/core')['useScroll']
const useScrollLock: typeof import('@vueuse/core')['useScrollLock']
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
const useShare: typeof import('@vueuse/core')['useShare']
const useSlots: typeof import('vue')['useSlots']
const useSorted: typeof import('@vueuse/core')['useSorted']
const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition']
const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis']
const useStepper: typeof import('@vueuse/core')['useStepper']
const useStorage: typeof import('@vueuse/core')['useStorage']
const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync']
const useStyleTag: typeof import('@vueuse/core')['useStyleTag']
const useSupported: typeof import('@vueuse/core')['useSupported']
const useSwipe: typeof import('@vueuse/core')['useSwipe']
const useTemplateRef: typeof import('vue')['useTemplateRef']
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
const useTextDirection: typeof import('@vueuse/core')['useTextDirection']
const useTextSelection: typeof import('@vueuse/core')['useTextSelection']
const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize']
const useThrottle: typeof import('@vueuse/core')['useThrottle']
const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn']
const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory']
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
const useTimeout: typeof import('@vueuse/core')['useTimeout']
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll']
const useTimestamp: typeof import('@vueuse/core')['useTimestamp']
const useTitle: typeof import('@vueuse/core')['useTitle']
const useToNumber: typeof import('@vueuse/core')['useToNumber']
const useToString: typeof import('@vueuse/core')['useToString']
const useToggle: typeof import('@vueuse/core')['useToggle']
const useTransition: typeof import('@vueuse/core')['useTransition']
const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams']
const useUserMedia: typeof import('@vueuse/core')['useUserMedia']
const useVModel: typeof import('@vueuse/core')['useVModel']
const useVModels: typeof import('@vueuse/core')['useVModels']
const useVibrate: typeof import('@vueuse/core')['useVibrate']
const useVirtualList: typeof import('@vueuse/core')['useVirtualList']
const useWakeLock: typeof import('@vueuse/core')['useWakeLock']
const useWebNotification: typeof import('@vueuse/core')['useWebNotification']
const useWebSocket: typeof import('@vueuse/core')['useWebSocket']
const useWebWorker: typeof import('@vueuse/core')['useWebWorker']
const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn']
const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus']
const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll']
const useWindowSize: typeof import('@vueuse/core')['useWindowSize']
const watch: typeof import('vue')['watch']
const watchArray: typeof import('@vueuse/core')['watchArray']
const watchAtMost: typeof import('@vueuse/core')['watchAtMost']
const watchDebounced: typeof import('@vueuse/core')['watchDebounced']
const watchDeep: typeof import('@vueuse/core')['watchDeep']
const watchEffect: typeof import('vue')['watchEffect']
const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable']
const watchImmediate: typeof import('@vueuse/core')['watchImmediate']
const watchOnce: typeof import('@vueuse/core')['watchOnce']
const watchPausable: typeof import('@vueuse/core')['watchPausable']
const watchPostEffect: typeof import('vue')['watchPostEffect']
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
const watchThrottled: typeof import('@vueuse/core')['watchThrottled']
const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable']
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
const whenever: typeof import('@vueuse/core')['whenever']
const EffectScope: typeof import('vue').EffectScope
const asyncComputed: typeof import('@vueuse/core').asyncComputed
const autoResetRef: typeof import('@vueuse/core').autoResetRef
const computed: typeof import('vue').computed
const computedAsync: typeof import('@vueuse/core').computedAsync
const computedEager: typeof import('@vueuse/core').computedEager
const computedInject: typeof import('@vueuse/core').computedInject
const computedWithControl: typeof import('@vueuse/core').computedWithControl
const controlledComputed: typeof import('@vueuse/core').controlledComputed
const controlledRef: typeof import('@vueuse/core').controlledRef
const createApp: typeof import('vue').createApp
const createEventHook: typeof import('@vueuse/core').createEventHook
const createGlobalState: typeof import('@vueuse/core').createGlobalState
const createInjectionState: typeof import('@vueuse/core').createInjectionState
const createReactiveFn: typeof import('@vueuse/core').createReactiveFn
const createRef: typeof import('@vueuse/core').createRef
const createReusableTemplate: typeof import('@vueuse/core').createReusableTemplate
const createSharedComposable: typeof import('@vueuse/core').createSharedComposable
const createTemplatePromise: typeof import('@vueuse/core').createTemplatePromise
const createUnrefFn: typeof import('@vueuse/core').createUnrefFn
const customRef: typeof import('vue').customRef
const debouncedRef: typeof import('@vueuse/core').debouncedRef
const debouncedWatch: typeof import('@vueuse/core').debouncedWatch
const defineAsyncComponent: typeof import('vue').defineAsyncComponent
const defineComponent: typeof import('vue').defineComponent
const eagerComputed: typeof import('@vueuse/core').eagerComputed
const effectScope: typeof import('vue').effectScope
const extendRef: typeof import('@vueuse/core').extendRef
const getCurrentInstance: typeof import('vue').getCurrentInstance
const getCurrentScope: typeof import('vue').getCurrentScope
const getCurrentWatcher: typeof import('vue').getCurrentWatcher
const h: typeof import('vue').h
const ignorableWatch: typeof import('@vueuse/core').ignorableWatch
const inject: typeof import('vue').inject
const injectLocal: typeof import('@vueuse/core').injectLocal
const isDefined: typeof import('@vueuse/core').isDefined
const isProxy: typeof import('vue').isProxy
const isReactive: typeof import('vue').isReactive
const isReadonly: typeof import('vue').isReadonly
const isRef: typeof import('vue').isRef
const isShallow: typeof import('vue').isShallow
const makeDestructurable: typeof import('@vueuse/core').makeDestructurable
const markRaw: typeof import('vue').markRaw
const nextTick: typeof import('vue').nextTick
const onActivated: typeof import('vue').onActivated
const onBeforeMount: typeof import('vue').onBeforeMount
const onBeforeRouteLeave: typeof import('vue-router').onBeforeRouteLeave
const onBeforeRouteUpdate: typeof import('vue-router').onBeforeRouteUpdate
const onBeforeUnmount: typeof import('vue').onBeforeUnmount
const onBeforeUpdate: typeof import('vue').onBeforeUpdate
const onClickOutside: typeof import('@vueuse/core').onClickOutside
const onDeactivated: typeof import('vue').onDeactivated
const onElementRemoval: typeof import('@vueuse/core').onElementRemoval
const onErrorCaptured: typeof import('vue').onErrorCaptured
const onKeyStroke: typeof import('@vueuse/core').onKeyStroke
const onLongPress: typeof import('@vueuse/core').onLongPress
const onMounted: typeof import('vue').onMounted
const onRenderTracked: typeof import('vue').onRenderTracked
const onRenderTriggered: typeof import('vue').onRenderTriggered
const onScopeDispose: typeof import('vue').onScopeDispose
const onServerPrefetch: typeof import('vue').onServerPrefetch
const onStartTyping: typeof import('@vueuse/core').onStartTyping
const onUnmounted: typeof import('vue').onUnmounted
const onUpdated: typeof import('vue').onUpdated
const onWatcherCleanup: typeof import('vue').onWatcherCleanup
const pausableWatch: typeof import('@vueuse/core').pausableWatch
const provide: typeof import('vue').provide
const provideLocal: typeof import('@vueuse/core').provideLocal
const reactify: typeof import('@vueuse/core').reactify
const reactifyObject: typeof import('@vueuse/core').reactifyObject
const reactive: typeof import('vue').reactive
const reactiveComputed: typeof import('@vueuse/core').reactiveComputed
const reactiveOmit: typeof import('@vueuse/core').reactiveOmit
const reactivePick: typeof import('@vueuse/core').reactivePick
const readonly: typeof import('vue').readonly
const ref: typeof import('vue').ref
const refAutoReset: typeof import('@vueuse/core').refAutoReset
const refDebounced: typeof import('@vueuse/core').refDebounced
const refDefault: typeof import('@vueuse/core').refDefault
const refThrottled: typeof import('@vueuse/core').refThrottled
const refWithControl: typeof import('@vueuse/core').refWithControl
const resolveComponent: typeof import('vue').resolveComponent
const resolveRef: typeof import('@vueuse/core').resolveRef
const resolveUnref: typeof import('@vueuse/core').resolveUnref
const shallowReactive: typeof import('vue').shallowReactive
const shallowReadonly: typeof import('vue').shallowReadonly
const shallowRef: typeof import('vue').shallowRef
const syncRef: typeof import('@vueuse/core').syncRef
const syncRefs: typeof import('@vueuse/core').syncRefs
const templateRef: typeof import('@vueuse/core').templateRef
const throttledRef: typeof import('@vueuse/core').throttledRef
const throttledWatch: typeof import('@vueuse/core').throttledWatch
const toRaw: typeof import('vue').toRaw
const toReactive: typeof import('@vueuse/core').toReactive
const toRef: typeof import('vue').toRef
const toRefs: typeof import('vue').toRefs
const toValue: typeof import('vue').toValue
const triggerRef: typeof import('vue').triggerRef
const tryOnBeforeMount: typeof import('@vueuse/core').tryOnBeforeMount
const tryOnBeforeUnmount: typeof import('@vueuse/core').tryOnBeforeUnmount
const tryOnMounted: typeof import('@vueuse/core').tryOnMounted
const tryOnScopeDispose: typeof import('@vueuse/core').tryOnScopeDispose
const tryOnUnmounted: typeof import('@vueuse/core').tryOnUnmounted
const unref: typeof import('vue').unref
const unrefElement: typeof import('@vueuse/core').unrefElement
const until: typeof import('@vueuse/core').until
const useActiveElement: typeof import('@vueuse/core').useActiveElement
const useAnimate: typeof import('@vueuse/core').useAnimate
const useArrayDifference: typeof import('@vueuse/core').useArrayDifference
const useArrayEvery: typeof import('@vueuse/core').useArrayEvery
const useArrayFilter: typeof import('@vueuse/core').useArrayFilter
const useArrayFind: typeof import('@vueuse/core').useArrayFind
const useArrayFindIndex: typeof import('@vueuse/core').useArrayFindIndex
const useArrayFindLast: typeof import('@vueuse/core').useArrayFindLast
const useArrayIncludes: typeof import('@vueuse/core').useArrayIncludes
const useArrayJoin: typeof import('@vueuse/core').useArrayJoin
const useArrayMap: typeof import('@vueuse/core').useArrayMap
const useArrayReduce: typeof import('@vueuse/core').useArrayReduce
const useArraySome: typeof import('@vueuse/core').useArraySome
const useArrayUnique: typeof import('@vueuse/core').useArrayUnique
const useAsyncQueue: typeof import('@vueuse/core').useAsyncQueue
const useAsyncState: typeof import('@vueuse/core').useAsyncState
const useAttrs: typeof import('vue').useAttrs
const useBase64: typeof import('@vueuse/core').useBase64
const useBattery: typeof import('@vueuse/core').useBattery
const useBluetooth: typeof import('@vueuse/core').useBluetooth
const useBreakpoints: typeof import('@vueuse/core').useBreakpoints
const useBroadcastChannel: typeof import('@vueuse/core').useBroadcastChannel
const useBrowserLocation: typeof import('@vueuse/core').useBrowserLocation
const useCached: typeof import('@vueuse/core').useCached
const useClipboard: typeof import('@vueuse/core').useClipboard
const useClipboardItems: typeof import('@vueuse/core').useClipboardItems
const useCloned: typeof import('@vueuse/core').useCloned
const useColorMode: typeof import('@vueuse/core').useColorMode
const useConfirmDialog: typeof import('@vueuse/core').useConfirmDialog
const useCountdown: typeof import('@vueuse/core').useCountdown
const useCounter: typeof import('@vueuse/core').useCounter
const useCssModule: typeof import('vue').useCssModule
const useCssVar: typeof import('@vueuse/core').useCssVar
const useCssVars: typeof import('vue').useCssVars
const useCurrentElement: typeof import('@vueuse/core').useCurrentElement
const useCycleList: typeof import('@vueuse/core').useCycleList
const useDark: typeof import('@vueuse/core').useDark
const useDateFormat: typeof import('@vueuse/core').useDateFormat
const useDebounce: typeof import('@vueuse/core').useDebounce
const useDebounceFn: typeof import('@vueuse/core').useDebounceFn
const useDebouncedRefHistory: typeof import('@vueuse/core').useDebouncedRefHistory
const useDeviceMotion: typeof import('@vueuse/core').useDeviceMotion
const useDeviceOrientation: typeof import('@vueuse/core').useDeviceOrientation
const useDevicePixelRatio: typeof import('@vueuse/core').useDevicePixelRatio
const useDevicesList: typeof import('@vueuse/core').useDevicesList
const useDisplayMedia: typeof import('@vueuse/core').useDisplayMedia
const useDocumentVisibility: typeof import('@vueuse/core').useDocumentVisibility
const useDraggable: typeof import('@vueuse/core').useDraggable
const useDropZone: typeof import('@vueuse/core').useDropZone
const useElementBounding: typeof import('@vueuse/core').useElementBounding
const useElementByPoint: typeof import('@vueuse/core').useElementByPoint
const useElementHover: typeof import('@vueuse/core').useElementHover
const useElementSize: typeof import('@vueuse/core').useElementSize
const useElementVisibility: typeof import('@vueuse/core').useElementVisibility
const useEventBus: typeof import('@vueuse/core').useEventBus
const useEventListener: typeof import('@vueuse/core').useEventListener
const useEventSource: typeof import('@vueuse/core').useEventSource
const useEyeDropper: typeof import('@vueuse/core').useEyeDropper
const useFavicon: typeof import('@vueuse/core').useFavicon
const useFetch: typeof import('@vueuse/core').useFetch
const useFileDialog: typeof import('@vueuse/core').useFileDialog
const useFileSystemAccess: typeof import('@vueuse/core').useFileSystemAccess
const useFocus: typeof import('@vueuse/core').useFocus
const useFocusWithin: typeof import('@vueuse/core').useFocusWithin
const useFps: typeof import('@vueuse/core').useFps
const useFullscreen: typeof import('@vueuse/core').useFullscreen
const useGamepad: typeof import('@vueuse/core').useGamepad
const useGeolocation: typeof import('@vueuse/core').useGeolocation
const useId: typeof import('vue').useId
const useIdle: typeof import('@vueuse/core').useIdle
const useImage: typeof import('@vueuse/core').useImage
const useInfiniteScroll: typeof import('@vueuse/core').useInfiniteScroll
const useIntersectionObserver: typeof import('@vueuse/core').useIntersectionObserver
const useInterval: typeof import('@vueuse/core').useInterval
const useIntervalFn: typeof import('@vueuse/core').useIntervalFn
const useKeyModifier: typeof import('@vueuse/core').useKeyModifier
const useLastChanged: typeof import('@vueuse/core').useLastChanged
const useLink: typeof import('vue-router').useLink
const useLocalStorage: typeof import('@vueuse/core').useLocalStorage
const useMagicKeys: typeof import('@vueuse/core').useMagicKeys
const useManualRefHistory: typeof import('@vueuse/core').useManualRefHistory
const useMediaControls: typeof import('@vueuse/core').useMediaControls
const useMediaQuery: typeof import('@vueuse/core').useMediaQuery
const useMemoize: typeof import('@vueuse/core').useMemoize
const useMemory: typeof import('@vueuse/core').useMemory
const useModel: typeof import('vue').useModel
const useMounted: typeof import('@vueuse/core').useMounted
const useMouse: typeof import('@vueuse/core').useMouse
const useMouseInElement: typeof import('@vueuse/core').useMouseInElement
const useMousePressed: typeof import('@vueuse/core').useMousePressed
const useMutationObserver: typeof import('@vueuse/core').useMutationObserver
const useNavigatorLanguage: typeof import('@vueuse/core').useNavigatorLanguage
const useNetwork: typeof import('@vueuse/core').useNetwork
const useNow: typeof import('@vueuse/core').useNow
const useObjectUrl: typeof import('@vueuse/core').useObjectUrl
const useOffsetPagination: typeof import('@vueuse/core').useOffsetPagination
const useOnline: typeof import('@vueuse/core').useOnline
const usePageLeave: typeof import('@vueuse/core').usePageLeave
const useParallax: typeof import('@vueuse/core').useParallax
const useParentElement: typeof import('@vueuse/core').useParentElement
const usePerformanceObserver: typeof import('@vueuse/core').usePerformanceObserver
const usePermission: typeof import('@vueuse/core').usePermission
const usePointer: typeof import('@vueuse/core').usePointer
const usePointerLock: typeof import('@vueuse/core').usePointerLock
const usePointerSwipe: typeof import('@vueuse/core').usePointerSwipe
const usePreferredColorScheme: typeof import('@vueuse/core').usePreferredColorScheme
const usePreferredContrast: typeof import('@vueuse/core').usePreferredContrast
const usePreferredDark: typeof import('@vueuse/core').usePreferredDark
const usePreferredLanguages: typeof import('@vueuse/core').usePreferredLanguages
const usePreferredReducedMotion: typeof import('@vueuse/core').usePreferredReducedMotion
const usePreferredReducedTransparency: typeof import('@vueuse/core').usePreferredReducedTransparency
const usePrevious: typeof import('@vueuse/core').usePrevious
const useRafFn: typeof import('@vueuse/core').useRafFn
const useRefHistory: typeof import('@vueuse/core').useRefHistory
const useResizeObserver: typeof import('@vueuse/core').useResizeObserver
const useRoute: typeof import('vue-router').useRoute
const useRouter: typeof import('vue-router').useRouter
const useSSRWidth: typeof import('@vueuse/core').useSSRWidth
const useScreenOrientation: typeof import('@vueuse/core').useScreenOrientation
const useScreenSafeArea: typeof import('@vueuse/core').useScreenSafeArea
const useScriptTag: typeof import('@vueuse/core').useScriptTag
const useScroll: typeof import('@vueuse/core').useScroll
const useScrollLock: typeof import('@vueuse/core').useScrollLock
const useSessionStorage: typeof import('@vueuse/core').useSessionStorage
const useShare: typeof import('@vueuse/core').useShare
const useSlots: typeof import('vue').useSlots
const useSorted: typeof import('@vueuse/core').useSorted
const useSpeechRecognition: typeof import('@vueuse/core').useSpeechRecognition
const useSpeechSynthesis: typeof import('@vueuse/core').useSpeechSynthesis
const useStepper: typeof import('@vueuse/core').useStepper
const useStorage: typeof import('@vueuse/core').useStorage
const useStorageAsync: typeof import('@vueuse/core').useStorageAsync
const useStyleTag: typeof import('@vueuse/core').useStyleTag
const useSupported: typeof import('@vueuse/core').useSupported
const useSwipe: typeof import('@vueuse/core').useSwipe
const useTemplateRef: typeof import('vue').useTemplateRef
const useTemplateRefsList: typeof import('@vueuse/core').useTemplateRefsList
const useTextDirection: typeof import('@vueuse/core').useTextDirection
const useTextSelection: typeof import('@vueuse/core').useTextSelection
const useTextareaAutosize: typeof import('@vueuse/core').useTextareaAutosize
const useThrottle: typeof import('@vueuse/core').useThrottle
const useThrottleFn: typeof import('@vueuse/core').useThrottleFn
const useThrottledRefHistory: typeof import('@vueuse/core').useThrottledRefHistory
const useTimeAgo: typeof import('@vueuse/core').useTimeAgo
const useTimeout: typeof import('@vueuse/core').useTimeout
const useTimeoutFn: typeof import('@vueuse/core').useTimeoutFn
const useTimeoutPoll: typeof import('@vueuse/core').useTimeoutPoll
const useTimestamp: typeof import('@vueuse/core').useTimestamp
const useTitle: typeof import('@vueuse/core').useTitle
const useToNumber: typeof import('@vueuse/core').useToNumber
const useToString: typeof import('@vueuse/core').useToString
const useToggle: typeof import('@vueuse/core').useToggle
const useTransition: typeof import('@vueuse/core').useTransition
const useUrlSearchParams: typeof import('@vueuse/core').useUrlSearchParams
const useUserMedia: typeof import('@vueuse/core').useUserMedia
const useVModel: typeof import('@vueuse/core').useVModel
const useVModels: typeof import('@vueuse/core').useVModels
const useVibrate: typeof import('@vueuse/core').useVibrate
const useVirtualList: typeof import('@vueuse/core').useVirtualList
const useWakeLock: typeof import('@vueuse/core').useWakeLock
const useWebNotification: typeof import('@vueuse/core').useWebNotification
const useWebSocket: typeof import('@vueuse/core').useWebSocket
const useWebWorker: typeof import('@vueuse/core').useWebWorker
const useWebWorkerFn: typeof import('@vueuse/core').useWebWorkerFn
const useWindowFocus: typeof import('@vueuse/core').useWindowFocus
const useWindowScroll: typeof import('@vueuse/core').useWindowScroll
const useWindowSize: typeof import('@vueuse/core').useWindowSize
const watch: typeof import('vue').watch
const watchArray: typeof import('@vueuse/core').watchArray
const watchAtMost: typeof import('@vueuse/core').watchAtMost
const watchDebounced: typeof import('@vueuse/core').watchDebounced
const watchDeep: typeof import('@vueuse/core').watchDeep
const watchEffect: typeof import('vue').watchEffect
const watchIgnorable: typeof import('@vueuse/core').watchIgnorable
const watchImmediate: typeof import('@vueuse/core').watchImmediate
const watchOnce: typeof import('@vueuse/core').watchOnce
const watchPausable: typeof import('@vueuse/core').watchPausable
const watchPostEffect: typeof import('vue').watchPostEffect
const watchSyncEffect: typeof import('vue').watchSyncEffect
const watchThrottled: typeof import('@vueuse/core').watchThrottled
const watchTriggerable: typeof import('@vueuse/core').watchTriggerable
const watchWithFilter: typeof import('@vueuse/core').watchWithFilter
const whenever: typeof import('@vueuse/core').whenever
}
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue')
}
......@@ -11,6 +11,6 @@
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
<script src="https://webapp-pub.ezijing.com/plugins/sky-agents/sky-agent.umd.cjs?v=2"></script>
<script src="https://webapp-pub.ezijing.com/plugins/tinymce/tinymce.min.js"></script>
<script src="https://webapp-pub.ezijing.com/plugins/tinymce@6/tinymce.min.js"></script>
</body>
</html>
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -54,20 +54,20 @@
"@types/crypto-js": "^4.2.2",
"@types/file-saver": "^2.0.7",
"@types/node": "^20.17.6",
"@vitejs/plugin-vue": "^5.1.4",
"@vue-macros/reactivity-transform": "^1.1.3",
"@vitejs/plugin-vue": "^6.0.1",
"@vue-macros/reactivity-transform": "^1.1.6",
"@vue/eslint-config-typescript": "^14.1.3",
"@vue/tsconfig": "^0.5.1",
"ali-oss": "^6.21.0",
"chalk": "^5.3.0",
"eslint": "^9.14.0",
"eslint-plugin-vue": "^9.30.0",
"sass": "^1.79.6",
"sass-embedded": "^1.93.3",
"typescript": "~5.6.3",
"unplugin-auto-import": "^0.18.3",
"vite": "^5.4.10",
"vite-plugin-checker": "^0.8.0",
"vite-plugin-mkcert": "^1.17.6",
"unplugin-auto-import": "^20.2.0",
"vite": "^7.1.12",
"vite-plugin-checker": "^0.11.0",
"vite-plugin-mkcert": "^1.17.9",
"vue-tsc": "^2.1.10"
}
}
<script setup lang="ts">
<script setup>
import { getProductList } from '@/api/base'
const options = ref([])
async function fetchList() {
const res = await getProductList({ 'per-page': 1000 })
options.value = res.data.list
const res = await getProductList({ page: 1, 'per-page': 1000 })
options.value = res.data.list?.map((item) => {
const picture_addreses = JSON.parse(item.picture_addreses)
const [picture] = picture_addreses
return {
...item,
picture_url: picture.url,
}
})
}
watchEffect(() => {
fetchList()
......@@ -18,5 +25,25 @@ watchEffect(() => {
style="width: 100%"
value-key="id"
filterable
:props="{ label: 'title', value: 'id' }" />
:item-height="60"
:props="{ label: 'title', value: 'id' }">
<template #default="{ item }">
<div class="custom-item">
<el-image :src="item.picture_url" style="width: 40px; height: 40px" />
<span>{{ item.title }}</span>
<span style="color: var(--el-text-color-secondary); font-size: 13px">{{
item.live_commodity_type_full_name
}}</span>
</div>
</template>
</el-select-v2>
</template>
<style>
.custom-item {
padding: 10px 0;
gap: 10px;
display: flex;
align-items: center;
}
</style>
<script setup lang="ts">
interface Props {
url: string
}
const props = defineProps<Props>()
// 文件扩展名
const fileExtensionName = $computed(() => {
return props.url.split('.').pop() || ''
})
// office文件
const officeUrl = $computed(() => {
return ['pptx', 'doc', 'docx', 'xls', 'xlsx'].includes(fileExtensionName)
? `https://view.officeapps.live.com/op/view.aspx?src=${props.url}`
: ''
})
</script>
<template>
<iframe allowfullscreen :src="officeUrl" :key="officeUrl" frameborder="0" style="width: 100%; height: 100%" v-if="officeUrl"></iframe>
<object :data="url" style="width: 100%; height: 100%; object-fit: none" v-else></object>
</template>
......@@ -14,7 +14,7 @@ interface Props {
}
const props = withDefaults(defineProps<Props>(), {
prefix: 'upload/saas-dml/'
prefix: 'upload/saas-dml/',
})
const emit = defineEmits(['update:modelValue', 'success'])
......@@ -24,7 +24,7 @@ const uploadData = ref()
const fileList = ref<UploadUserFile[]>([])
watchEffect(() => {
fileList.value = Array.isArray(props.modelValue) ? props.modelValue.map(item => ({ ...item })) : []
fileList.value = Array.isArray(props.modelValue) ? props.modelValue.map((item) => ({ ...item })) : []
})
const showFileList = computed(() => {
......@@ -51,7 +51,7 @@ const handleBeforeUpload = async (file: any) => {
policy: response.policy,
signature: response.signature,
success_action_status: '200',
url: `${response.host}/${key}`
url: `${response.host}/${key}`,
}
file.url = `${response.host}/${key}`
if (props.beforeUpload) {
......@@ -70,8 +70,8 @@ const handleSuccess = (response: any, file: any, files: any) => {
name: last.name,
url: last.url || last.raw?.url,
size: last.size || last.raw?.size,
type: last.type || last.raw?.type
}
type: last.type || last.raw?.type,
},
])
} else {
emit(
......@@ -81,7 +81,7 @@ const handleSuccess = (response: any, file: any, files: any) => {
name: item.name,
url: item.url || item.raw?.url,
size: item.size || item.raw?.size,
type: item.type || item.raw?.type
type: item.type || item.raw?.type,
}
})
)
......@@ -112,7 +112,7 @@ const handleRemove: UploadProps['onRemove'] = (file, files) => {
}
// 预览
const handlePreview: UploadProps['onPreview'] = uploadFile => {
const handlePreview: UploadProps['onPreview'] = (uploadFile) => {
window.open(uploadFile.url)
}
</script>
......@@ -144,6 +144,7 @@ const handlePreview: UploadProps['onPreview'] = uploadFile => {
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</div>
</slot>
<template #tip>
<div class="el-upload__tip"><slot name="tip"></slot></div>
</template>
......
......@@ -18,10 +18,10 @@ const currentSubmenu = computed(() => {
})
function findMenu(path: string, menus: IMenuItem[]) {
return menus.find(item => {
return menus.find((item) => {
if (
item.children &&
item.children.find(item => {
item.children.find((item) => {
const regExp = new RegExp(`^${item.path.replaceAll('?', '\\?')}`)
return regExp.test(path)
})
......@@ -38,15 +38,19 @@ function findMenu(path: string, menus: IMenuItem[]) {
<aside class="app-aside">
<nav class="menu">
<ul>
<li v-for="item in menus" :key="item.path" :class="{ 'is-active': item.path === currentMenu?.path }" v-permission="item.tag">
<li
v-for="item in menus"
:key="item.path"
:class="{ 'is-active': item.path === currentMenu?.path }"
v-permission="item.tag">
<div class="menu-item">
<template v-if="item.children">
<RouterLink :to="item.path">
<RouterLink :to="{ path: item.path, query: { experiment_id: $route.query.experiment_id } }">
<component :is="item.icon" class="menu-icon"></component>
</RouterLink>
</template>
<el-tooltip :content="item.name" placement="right" v-else>
<RouterLink :to="item.path">
<RouterLink :to="{ path: item.path, query: { experiment_id: $route.query.experiment_id } }">
<component :is="item.icon" class="menu-icon"></component>
</RouterLink>
</el-tooltip>
......@@ -59,7 +63,7 @@ function findMenu(path: string, menus: IMenuItem[]) {
:key="submenu.path"
:class="{ 'is-active': submenu.path === currentSubmenu?.path }"
v-permission="submenu.tag">
<RouterLink :to="submenu.path">
<RouterLink :to="{ path: submenu.path, query: { experiment_id: $route.query.experiment_id } }">
<component :is="submenu.icon" class="submenu-icon" v-if="submenu.icon"></component>
{{ submenu.name }}
</RouterLink>
......@@ -138,6 +142,7 @@ function findMenu(path: string, menus: IMenuItem[]) {
padding: 0 20px;
}
svg {
color: #9a9a9a;
fill: #9a9a9a;
}
.submenu-icon {
......@@ -148,6 +153,7 @@ function findMenu(path: string, menus: IMenuItem[]) {
color: #fff;
background-color: var(--main-color);
svg {
color: #fff;
fill: #fff;
}
}
......
import httpRequest from '@/utils/axios'
// 获取直播练习报告
export function getReport(params?: { id: string }) {
return httpRequest.get('/api/lab/v1/experiment/live-practice-report/detail', { params })
}
// 获取直播练习报告列表
export function getReportList(params?: { live_commodity_type_id?: string; live_commodity_title?: string }) {
return httpRequest.get('/api/lab/v1/experiment/live-practice-report/list', { params })
}
// 创建直播练习报告
export function createReport(data: { live_practice_id: string; report_name: string; report_url: string }) {
return httpRequest.post('/api/lab/v1/experiment/live-practice-report/create', data)
}
// 删除直播练习报告
export function deleteReport(data: { id: string }) {
return httpRequest.post('/api/lab/v1/experiment/live-practice-report/delete', data)
}
// 获取实验直播的列表
export function getTestList(params?: {
live_commodity_id?: string
live_commodity_type_id?: string
live_commodity_title?: string
}) {
return httpRequest.get('/api/lab/v1/experiment/live-practice/list', { params })
}
<script setup>
import { ElMessage } from 'element-plus'
import { Document } from '@element-plus/icons-vue'
import { createReport, getReport, getTestList } from '../api'
import AppUpload from '@/components/base/AppUpload.vue'
const props = defineProps(['data', 'action'])
const emit = defineEmits(['update', 'update:modelValue'])
const actonMap = { add: '添加', update: '编辑', view: '查看' }
const isUpdate = computed(() => props.action === 'update')
const title = computed(() => actonMap[props.action] + '直播总结')
const formRef = ref()
const form = reactive({
live_practice_id: '',
report_name: '',
report_url: '',
})
watchEffect(() => {
if (props.data) Object.assign(form, props.data)
})
const rules = ref({
live_practice_id: [{ required: true, message: '请选择' }],
report_url: [{ required: true, message: '请选择' }],
})
const options = ref([])
onMounted(() => {
getTestList().then((res) => {
options.value = res.data.list
})
})
async function fetchInfo() {
const res = await getReport({ id: props.data.id })
Object.assign(form, res.data.detail)
}
watchEffect(() => {
if (props.action !== 'add') fetchInfo()
})
// 提交
async function handleSubmit() {
await formRef.value?.validate()
isUpdate.value ? handleUpdate() : handleAdd()
}
// 新建
async function handleAdd() {
await createReport(form)
ElMessage({ message: '创建成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
}
// 编辑
async function handleUpdate() {
await updateTalk(form)
ElMessage({ message: '修改成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
}
function handleSuccess(file) {
if (!form.report_name) {
form.report_name = file.name
}
}
</script>
<template>
<el-dialog :title="title" :close-on-click-modal="false" @closed="$emit('update:modelValue', false)" width="500px">
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-position="right"
label-width="110px"
:disabled="action === 'view'">
<el-form-item label="关联直播" prop="live_practice_id">
<el-select v-model="form.live_practice_id" style="width: 100%" placeholder="请选择" filterable>
<el-option
v-for="item in options"
:key="item.id"
:label="item.live_commodity_title"
:value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="总结报告文件" prop="report_url">
<div style="width: 100%">
<AppUpload v-model="form.report_url" accept=".doc,.docx,.pdf" @success="handleSuccess">
<el-button type="primary" plain round>本地上传</el-button>
<template #tip>报告文件只能上传一个,支持格式包含:doc docx pdf,大小不超过50M</template>
</AppUpload>
<div class="upload-preview" v-if="form.report_url">
<a :href="form.report_url" target="_blank">
<el-icon><Document /></el-icon>
<span>{{ form.report_name || form.report_url }}</span>
</a>
</div>
</div>
</el-form-item>
</el-form>
<template #footer>
<el-row justify="center">
<el-button round auto-insert-space @click="$emit('update:modelValue', false)">关闭</el-button>
<el-button type="primary" round auto-insert-space @click="handleSubmit" v-if="action !== 'view'"
>保存</el-button
>
</el-row>
</template>
</el-dialog>
</template>
<style lang="scss" scoped>
.upload-preview {
a {
display: flex;
align-items: center;
white-space: nowrap;
gap: 5px;
overflow: hidden;
text-overflow: ellipsis;
}
}
</style>
import type { RouteRecordRaw } from 'vue-router'
import Layout from '@/components/layout/Index.vue'
const routes: RouteRecordRaw[] = [
{
path: '/live/reports',
component: Layout,
children: [{ path: '', component: () => import('./views/Index.vue') }]
}
]
export { routes }
<script setup>
import { ElMessageBox, ElMessage } from 'element-plus'
import LiveProductCategory from '@/components/LiveProductCategory.vue'
import { getReportList, deleteReport } from '../api'
const FormDialog = defineAsyncComponent(() => import('../components/FormDialog.vue'))
const appList = ref(null)
// 刷新
const handleRefresh = () => {
appList.value?.refetch()
}
const listParams = reactive({ live_commodity_type_id: '', live_commodity_title: '' })
// 列表配置
const listOptions = computed(() => {
return {
remote: {
httpRequest: getReportList,
params: listParams,
beforeRequest(params, isReset) {
if (isReset) listParams.live_commodity_type_id = ''
params.live_commodity_type_id = listParams.live_commodity_type_id
return params
},
},
filters: [
{ label: '直播主题品类', prop: 'live_commodity_type_id', slots: 'filter-category' },
{ label: '直播主题名称', prop: 'live_commodity_title', type: 'input' },
],
columns: [
{ label: '序号', type: 'index', width: 60 },
{ label: '创建人', prop: 'created_operator_name' },
{ label: '直播主题标题', prop: 'live_commodity_title' },
{ label: '直播话术名称', prop: 'live_speech_name' },
{ label: '所属直播主题品类', prop: 'live_commodity_type_full_name' },
{ label: '报告名称', prop: 'report_name' },
{ label: '上传时间', prop: 'created_time' },
{ label: '操作', slots: 'table-x', width: 200 },
],
}
})
const action = ref(null)
const formVisible = ref(false)
const currentRow = ref(null)
// 新建
const handleAdd = () => {
action.value = 'add'
currentRow.value = null
formVisible.value = true
}
// 查看
const handleView = (row) => {
action.value = 'view'
currentRow.value = row
formVisible.value = true
}
// 删除
const handleRemove = async (row) => {
await ElMessageBox.confirm('此操作将永久删除该数据, 是否继续?', '提示')
await deleteReport({ id: row.id })
appList.value?.refetch()
ElMessage.success('删除成功')
}
</script>
<template>
<AppCard title="直播总结管理">
<AppList v-bind="listOptions" ref="appList">
<template #header-buttons>
<el-button type="primary" @click="handleAdd">上传总结报告</el-button>
</template>
<template #filter-category>
<LiveProductCategory v-model="listParams.live_commodity_type_id" @change="handleRefresh"></LiveProductCategory>
</template>
<template #table-x="{ row }">
<el-button text type="primary" @click="handleView(row)">查看</el-button>
<el-button text type="primary" @click="handleRemove(row)">删除</el-button>
</template>
</AppList>
</AppCard>
<!-- 新建/修改 -->
<FormDialog v-model="formVisible" :action="action" :data="currentRow" @update="handleRefresh" v-if="formVisible" />
</template>
import httpRequest from '@/utils/axios'
// 获取直播成绩详情
export function getScoreDetail(params?: { id: string }) {
// return {
// code: 0,
// message: 'OK',
// data: {
// detail: {
// id: '7391764086012248064',
// experiment_id: '7028276368903241728',
// student_id: '6954620265695281152',
// sso_id: '6911878751345180672',
// status: '1',
// commit_time: '2025-11-05 17:28:21',
// total_score: null,
// score_details: null,
// checker_id: '6911878751345180672',
// check_time: null,
// comment: null,
// created_time: '2025-11-05 17:11:30',
// updated_time: '2025-11-05 17:28:22',
// student_name: '李四',
// specialty_id: '6961130172380610560',
// specialty_name: '2023大赛专业',
// class_id: '6963792833715109888',
// class_name: '陈双喜测试3',
// status_name: '已提交',
// live_data: {
// commodity_types: [
// {
// id: '7391776484496506880',
// experiment_id: '7028276368903241728',
// name: 'abc',
// pid: '0',
// level: '0',
// status: '1',
// created_operator: '6911878751345180672',
// updated_operator: '6911878751345180672',
// created_time: '2025-11-05 18:00:46',
// updated_time: '2025-11-05 18:00:46',
// children: [
// {
// id: '7391776536967249920',
// experiment_id: '7028276368903241728',
// name: 'qwe',
// pid: '7391776484496506880',
// level: '1',
// status: '1',
// created_operator: '6911878751345180672',
// updated_operator: '6911878751345180672',
// created_time: '2025-11-05 18:00:58',
// updated_time: '2025-11-05 18:00:58',
// children: [],
// },
// ],
// },
// ],
// commodity_attrs: [
// {
// id: '7391778517379186688',
// experiment_id: '7028276368903241728',
// name: '123',
// live_commodity_type_id: '7260532289816231936',
// is_importance: '0',
// is_essential: '0',
// status: '1',
// created_operator: '6911878751345180672',
// updated_operator: '6911878751345180672',
// created_time: '2025-11-05 18:08:51',
// updated_time: '2025-11-05 18:08:51',
// live_commodity_type_full_name: '男装',
// },
// ],
// commodities: [
// {
// id: '7391773963229069312',
// experiment_id: '7028276368903241728',
// live_commodity_type_id: '7260533071559000064',
// title: '商品标题',
// shopping_guide_short_title: '导购短标题',
// live_commodity_attrs:
// '{"importance_attrs": [{"id": "7260540571196850176", "value": "L", "attr_name": "大小"}], "unimportance_attrs": [{"id": "7260539754020601856", "value": "黑色", "attr_name": "颜色"}, {"id": "7260540486702596096", "value": "123", "attr_name": "功能"}]}',
// picture_addreses:
// '[{"url": "https://webapp-pub.ezijing.com/upload/saas-lab/8e04b573ec12b658b2584f56d3272c37.png", "name": "image-14_d89dc93.png", "size": 1241703, "type": "image/png", "upload_time": "2025-11-05 17:50:13"}, {"url": "https://webapp-pub.ezijing.com/upload/saas-lab/e2ac8faee61cea295917892170209916.jpg", "name": "p2371864015.jpg", "size": 463116, "type": "image/jpeg", "upload_time": "2025-11-06 13:17:42"}, {"url": "https://webapp-pub.ezijing.com/upload/saas-lab/5d9354ef327f9565f49f13649dc4d6d8.jpg", "name": "p2517001827.jpg", "size": 112763, "type": "image/jpeg", "upload_time": "2025-11-06 13:17:46"}, {"url": "https://webapp-pub.ezijing.com/upload/saas-lab/9618c22ecdc19c135f13554f108685a6.jpg", "name": "p2517001827.jpg", "size": 112763, "type": "image/jpeg", "upload_time": "2025-11-06 13:18:03"}, {"url": "https://webapp-pub.ezijing.com/upload/saas-lab/c52d0fd3ff52db3e03950455345db338.jpeg", "name": "Bliss.jpeg", "size": 1028346, "type": "image/jpeg", "upload_time": "2025-11-06 13:18:14"}]',
// picture_34_addreses:
// '[{"url": "https://webapp-pub.ezijing.com/upload/saas-lab/8e04b573ec12b658b2584f56d3272c37.png", "name": "image-14_d89dc93.png", "size": 1241703, "type": "image/png", "upload_time": "2025-11-05 17:50:13"}, {"url": "https://webapp-pub.ezijing.com/upload/saas-lab/e2ac8faee61cea295917892170209916.jpg", "name": "p2371864015.jpg", "size": 463116, "type": "image/jpeg", "upload_time": "2025-11-06 13:17:42"}, {"url": "https://webapp-pub.ezijing.com/upload/saas-lab/5d9354ef327f9565f49f13649dc4d6d8.jpg", "name": "p2517001827.jpg", "size": 112763, "type": "image/jpeg", "upload_time": "2025-11-06 13:17:46"}, {"url": "https://webapp-pub.ezijing.com/upload/saas-lab/9618c22ecdc19c135f13554f108685a6.jpg", "name": "p2517001827.jpg", "size": 112763, "type": "image/jpeg", "upload_time": "2025-11-06 13:18:03"}, {"url": "https://webapp-pub.ezijing.com/upload/saas-lab/c52d0fd3ff52db3e03950455345db338.jpeg", "name": "Bliss.jpeg", "size": 1028346, "type": "image/jpeg", "upload_time": "2025-11-06 13:18:14"}]',
// video_url: 'https://webapp-pub.ezijing.com/upload/saas-lab/f267dffd02eec005f922699ec69bbd47.mp4',
// info: '{"sku": [{"price": "100", "specs": ["黑色", "L"], "stock": 200}, {"price": "100", "specs": ["黑色", "M"], "stock": 200}, {"price": "100", "specs": ["白色", "L"], "stock": 200}, {"price": "100", "specs": ["白色", "M"], "stock": 200}], "specs": [{"name": "颜色", "values": ["黑色", "白色"]}, {"name": "大小", "values": ["L", "M"]}], "delivery_mode": "1", "delivery_time": "1", "reference_price": "100", "order_stock_count": "1", "shipping_template": "1", "after_sales_policy": "3"}',
// created_operator: '6911878751345180672',
// updated_operator: '6602032005293015040',
// created_time: '2025-11-05 17:50:45',
// updated_time: '2025-11-06 13:18:55',
// live_commodity_type_full_name: '男装/短裤',
// },
// ],
// speeches: [
// {
// id: '7391774388816707584',
// experiment_id: '7028276368903241728',
// name: '123',
// live_commodity_id: '7391773963229069312',
// selling_point:
// '商品标题:智能恒温电热水壶 \n\n卖点:精准控温;一键烧水;防干烧保护;304不锈钢内胆;大容量设计;静音沸腾',
// marketing_campaign:
// '商品标题:智能恒温保温杯——喝出健康好温度 \n\n精准控温,暖你每一刻; \n职场精英专属礼遇; \n24小时锁温挑战赛; \n买一赠一限时抢; \n直播间秒杀半价起',
// duration: '15',
// content:
// ' <p>\n <span\n style="\n padding: 0 5px;\n display: inline-block;\n background-color: rgba(25, 102, 255, 0.08);\n color: rgb(25, 102, 255);\n "\n >1.开场欢迎:</span>\n </p>\n <p>\n "大家好,欢迎来到我的直播间!我是[你的名字],今天我们将一起分享/探讨/体验[直播主题]。如果你喜欢我们今天的内容,别忘了点赞和关注哦!"\n </p>\n <p>\n <span\n style="\n padding: 0 5px;\n display: inline-block;\n background-color: rgba(25, 102, 255, 0.08);\n color: rgb(25, 102, 255);\n "\n >2.互动提问:</span>\n </p>\n <p>\n "我看到评论区有很多小伙伴在问我关于[话题]的问题,我现在就来一一为大家解答。如果你也有问题,可以在评论区留言,我会尽快回复大家!"\n </p>\n <p>\n <span\n style="\n padding: 0 5px;\n display: inline-block;\n background-color: rgba(25, 102, 255, 0.08);\n color: rgb(25, 102, 255);\n "\n >3.产品介绍:</span>\n </p>\n <p>\n "接下来我要给大家介绍的这款产品,它的特点是[特点],使用起来非常[方便/舒适/有效]。我自己也是它的忠实粉丝,今天就来给大家详细展示一下它的使用方法和效果。"\n </p>\n <p>\n <span\n style="\n padding: 0 5px;\n display: inline-block;\n background-color: rgba(25, 102, 255, 0.08);\n color: rgb(25, 102, 255);\n "\n >4.引导互动:</span>\n </p>\n <p>\n "我知道大家都很聪明,那么我来出一个小问题考考大家,第一个答对的小伙伴将获得我们的小礼物一份,准备好了吗?问题是……"\n </p>',
// created_operator: '6911878751345180672',
// updated_operator: '6911878751345180672',
// created_time: '2025-11-05 17:52:26',
// updated_time: '2025-11-05 17:52:26',
// live_commodity_title: '商品标题',
// live_commodity_type_full_name: '男装/短裤',
// },
// ],
// practices: [
// {
// id: '7391774560946749440',
// experiment_id: '7028276368903241728',
// live_commodity_id: '7391773963229069312',
// live_speech_id: '7391774388816707584',
// duration: '2',
// upload_way: '1',
// created_operator: '6911878751345180672',
// updated_operator: '6911878751345180672',
// created_time: '2025-11-05 17:53:07',
// updated_time: '2025-11-05 17:53:07',
// live_commodity_title: '商品标题',
// live_commodity_type_full_name: '男装/短裤',
// },
// ],
// practice_records: [
// {
// id: '7391774604890472448',
// live_practice_id: '7391774560946749440',
// live_practice_info:
// '{"id": "7391774560946749440", "duration": "2", "upload_way": "1", "live_speech": {"id": "7391774388816707584", "name": "123", "content": " <p>\\n <span\\n style=\\"\\n padding: 0 5px;\\n display: inline-block;\\n background-color: rgba(25, 102, 255, 0.08);\\n color: rgb(25, 102, 255);\\n \\"\\n >1.开场欢迎:</span>\\n </p>\\n <p>\\n \\"大家好,欢迎来到我的直播间!我是[你的名字],今天我们将一起分享/探讨/体验[直播主题]。如果你喜欢我们今天的内容,别忘了点赞和关注哦!\\"\\n </p>\\n <p>\\n <span\\n style=\\"\\n padding: 0 5px;\\n display: inline-block;\\n background-color: rgba(25, 102, 255, 0.08);\\n color: rgb(25, 102, 255);\\n \\"\\n >2.互动提问:</span>\\n </p>\\n <p>\\n \\"我看到评论区有很多小伙伴在问我关于[话题]的问题,我现在就来一一为大家解答。如果你也有问题,可以在评论区留言,我会尽快回复大家!\\"\\n </p>\\n <p>\\n <span\\n style=\\"\\n padding: 0 5px;\\n display: inline-block;\\n background-color: rgba(25, 102, 255, 0.08);\\n color: rgb(25, 102, 255);\\n \\"\\n >3.产品介绍:</span>\\n </p>\\n <p>\\n \\"接下来我要给大家介绍的这款产品,它的特点是[特点],使用起来非常[方便/舒适/有效]。我自己也是它的忠实粉丝,今天就来给大家详细展示一下它的使用方法和效果。\\"\\n </p>\\n <p>\\n <span\\n style=\\"\\n padding: 0 5px;\\n display: inline-block;\\n background-color: rgba(25, 102, 255, 0.08);\\n color: rgb(25, 102, 255);\\n \\"\\n >4.引导互动:</span>\\n </p>\\n <p>\\n \\"我知道大家都很聪明,那么我来出一个小问题考考大家,第一个答对的小伙伴将获得我们的小礼物一份,准备好了吗?问题是……\\"\\n </p>", "duration": "15", "created_time": "2025-11-05 17:52:26", "updated_time": "2025-11-05 17:52:26", "experiment_id": "7028276368903241728", "selling_point": "商品标题:智能恒温电热水壶 \\n\\n卖点:精准控温;一键烧水;防干烧保护;304不锈钢内胆;大容量设计;静音沸腾", "created_operator": {"id": "6911878751345180672", "avatar": "", "nickname": "李四", "username": "ZJ_6911878751345180672", "real_name": "李四"}, "updated_operator": {"id": "6911878751345180672", "avatar": "", "nickname": "李四", "username": "ZJ_6911878751345180672", "real_name": "李四"}, "live_commodity_id": "7391773963229069312", "marketing_campaign": "商品标题:智能恒温保温杯——喝出健康好温度 \\n\\n精准控温,暖你每一刻; \\n职场精英专属礼遇; \\n24小时锁温挑战赛; \\n买一赠一限时抢; \\n直播间秒杀半价起", "live_commodity_title": "商品标题", "live_commodity_type_id": "7260533071559000064", "live_commodity_type_name": "短裤", "live_commodity_type_full_name": "男装 > 短裤"}, "created_time": "2025-11-05 17:53:07", "current_user": {"id": "6911878751345180672", "avatar": "", "nickname": "李四", "username": "ZJ_6911878751345180672", "real_name": "李四"}, "updated_time": "2025-11-05 17:53:07", "experiment_id": "7028276368903241728", "live_commodity": {"id": "7391773963229069312", "info": "{\\"sku\\": [{\\"price\\": \\"100\\", \\"specs\\": [\\"黑色\\"], \\"stock\\": 290}], \\"specs\\": [{\\"name\\": \\"颜色\\", \\"values\\": [\\"黑色\\"]}], \\"delivery_mode\\": \\"1\\", \\"delivery_time\\": \\"1\\", \\"reference_price\\": \\"100\\", \\"order_stock_count\\": \\"1\\", \\"shipping_template\\": \\"1\\", \\"after_sales_policy\\": \\"3\\"}", "title": "商品标题", "video_url": "", "created_time": "2025-11-05 17:50:45", "updated_time": "2025-11-05 17:50:45", "experiment_id": "7028276368903241728", "created_operator": {"id": "6911878751345180672", "avatar": "", "nickname": "李四", "username": "ZJ_6911878751345180672", "real_name": "李四"}, "picture_addreses": "[{\\"url\\": \\"https://webapp-pub.ezijing.com/upload/saas-lab/8e04b573ec12b658b2584f56d3272c37.png\\", \\"name\\": \\"image-14_d89dc93.png\\", \\"size\\": 1241703, \\"type\\": \\"image/png\\", \\"upload_time\\": \\"2025-11-05 17:50:13\\"}]", "updated_operator": {"id": "6911878751345180672", "avatar": "", "nickname": "李四", "username": "ZJ_6911878751345180672", "real_name": "李四"}, "picture_34_addreses": "[{\\"url\\": \\"https://webapp-pub.ezijing.com/upload/saas-lab/8e04b573ec12b658b2584f56d3272c37.png\\", \\"name\\": \\"image-14_d89dc93.png\\", \\"size\\": 1241703, \\"type\\": \\"image/png\\", \\"upload_time\\": \\"2025-11-05 17:50:13\\"}]", "live_commodity_attrs": "{\\"importance_attrs\\": [{\\"id\\": \\"7260540571196850176\\", \\"value\\": \\"L\\", \\"attr_name\\": \\"大小\\"}], \\"unimportance_attrs\\": [{\\"id\\": \\"7260539754020601856\\", \\"value\\": \\"黑色\\", \\"attr_name\\": \\"颜色\\"}]}", "live_commodity_type_id": "7260533071559000064", "live_commodity_type_name": "短裤", "shopping_guide_short_title": "导购短标题", "live_commodity_type_full_name": "男装 > 短裤"}, "live_speech_id": "7391774388816707584", "practice_count": 0, "created_operator": {"id": "6911878751345180672", "avatar": "", "nickname": "李四", "username": "ZJ_6911878751345180672", "real_name": "李四"}, "updated_operator": {"id": "6911878751345180672", "avatar": "", "nickname": "李四", "username": "ZJ_6911878751345180672", "real_name": "李四"}, "live_commodity_id": "7391773963229069312", "live_commodity_type_id": "7260533071559000064", "live_commodity_type_name": "短裤", "live_commodity_type_full_name": "男装 > 短裤"}',
// live_speech_info:
// '{"id": "7391774388816707584", "name": "123", "content": " <p>\\n <span\\n style=\\"\\n padding: 0 5px;\\n display: inline-block;\\n background-color: rgba(25, 102, 255, 0.08);\\n color: rgb(25, 102, 255);\\n \\"\\n >1.开场欢迎:</span>\\n </p>\\n <p>\\n \\"大家好,欢迎来到我的直播间!我是[你的名字],今天我们将一起分享/探讨/体验[直播主题]。如果你喜欢我们今天的内容,别忘了点赞和关注哦!\\"\\n </p>\\n <p>\\n <span\\n style=\\"\\n padding: 0 5px;\\n display: inline-block;\\n background-color: rgba(25, 102, 255, 0.08);\\n color: rgb(25, 102, 255);\\n \\"\\n >2.互动提问:</span>\\n </p>\\n <p>\\n \\"我看到评论区有很多小伙伴在问我关于[话题]的问题,我现在就来一一为大家解答。如果你也有问题,可以在评论区留言,我会尽快回复大家!\\"\\n </p>\\n <p>\\n <span\\n style=\\"\\n padding: 0 5px;\\n display: inline-block;\\n background-color: rgba(25, 102, 255, 0.08);\\n color: rgb(25, 102, 255);\\n \\"\\n >3.产品介绍:</span>\\n </p>\\n <p>\\n \\"接下来我要给大家介绍的这款产品,它的特点是[特点],使用起来非常[方便/舒适/有效]。我自己也是它的忠实粉丝,今天就来给大家详细展示一下它的使用方法和效果。\\"\\n </p>\\n <p>\\n <span\\n style=\\"\\n padding: 0 5px;\\n display: inline-block;\\n background-color: rgba(25, 102, 255, 0.08);\\n color: rgb(25, 102, 255);\\n \\"\\n >4.引导互动:</span>\\n </p>\\n <p>\\n \\"我知道大家都很聪明,那么我来出一个小问题考考大家,第一个答对的小伙伴将获得我们的小礼物一份,准备好了吗?问题是……\\"\\n </p>", "duration": "15", "created_time": "2025-11-05 17:52:26", "updated_time": "2025-11-05 17:52:26", "experiment_id": "7028276368903241728", "selling_point": "商品标题:智能恒温电热水壶 \\n\\n卖点:精准控温;一键烧水;防干烧保护;304不锈钢内胆;大容量设计;静音沸腾", "created_operator": {"id": "6911878751345180672", "avatar": "", "nickname": "李四", "username": "ZJ_6911878751345180672", "real_name": "李四"}, "updated_operator": {"id": "6911878751345180672", "avatar": "", "nickname": "李四", "username": "ZJ_6911878751345180672", "real_name": "李四"}, "live_commodity_id": "7391773963229069312", "marketing_campaign": "商品标题:智能恒温保温杯——喝出健康好温度 \\n\\n精准控温,暖你每一刻; \\n职场精英专属礼遇; \\n24小时锁温挑战赛; \\n买一赠一限时抢; \\n直播间秒杀半价起", "live_commodity_title": "商品标题", "live_commodity_type_id": "7260533071559000064", "live_commodity_type_name": "短裤", "live_commodity_type_full_name": "男装 > 短裤"}',
// live_video_addres:
// 'https://saas-lab-api.ezijing.com/files/c7092077591b3df475519e01699b6814_RTC6911878751345180672.mp4?file_path=/var/log/files&file_name=c7092077591b3df475519e01699b6814_RTC6911878751345180672.mp4',
// live_video_size: '2014426',
// live_duration: '5.828',
// live_start_time: '2025-11-05 17:53:12',
// live_end_time: '2025-11-05 17:53:18',
// live_info:
// '{"messages":[{"id":"YbtQN7yD5oe3-wA0FjhhC","currentTime":0,"timestamp":1762336392207,"type":"join","content":"来了","user":{"id":"HFgs","name":"用户_HFgs","avatar":"/live/avatar/7.jpeg"}},{"id":"Mln7LgSBbvEx_XtVXy15Q","currentTime":0,"timestamp":1762336392207,"type":"join","content":"来了","user":{"id":"mlHf","name":"用户_mlHf","avatar":"/live/avatar/5.jpeg"}},{"id":"KQqGQrzswDS7Kqslv2ehH","currentTime":0,"timestamp":1762336392207,"type":"join","content":"来了","user":{"id":"cAgU","name":"用户_cAgU","avatar":"/live/avatar/5.jpeg"}},{"id":"_xwOtqDqHdE9qsjrtwDkd","currentTime":0,"timestamp":1762336392207,"type":"join","content":"来了","user":{"id":"v-97","name":"用户_v-97","avatar":"/live/avatar/2.png"}},{"id":"p61DenOIpJssCZsAd46ul","currentTime":0,"timestamp":1762336392208,"type":"join","content":"来了","user":{"id":"EDAF","name":"用户_EDAF","avatar":"/live/avatar/10.jpeg"}},{"id":"0kSeTddyKldULi33wesJi","currentTime":0,"timestamp":1762336392208,"type":"join","content":"来了","user":{"id":"sfQv","name":"用户_sfQv","avatar":"/live/avatar/8.jpeg"}},{"id":"heTJzjNaC__5b4apX9GeL","currentTime":0,"timestamp":1762336392208,"type":"join","content":"来了","user":{"id":"5_NK","name":"用户_5_NK","avatar":"/live/avatar/12.jpeg"}},{"id":"9gip64bh4DlxPzgmi8Xjm","currentTime":0,"timestamp":1762336392208,"type":"join","content":"来了","user":{"id":"rPS_","name":"用户_rPS_","avatar":"/live/avatar/10.jpeg"}},{"id":"xCBphLGgIBEVvv10Ga1Mz","currentTime":0,"timestamp":1762336392208,"type":"join","content":"来了","user":{"id":"EcET","name":"用户_EcET","avatar":"/live/avatar/3.png"}},{"id":"WCNWhvPXqlUvYqmm9SAd9","currentTime":0,"timestamp":1762336392208,"type":"join","content":"来了","user":{"id":"1cN7","name":"用户_1cN7","avatar":"/live/avatar/2.png"}},{"id":"6cYGqIcpfP4rTmSHbEczB","currentTime":0,"timestamp":1762336392208,"type":"join","content":"来了","user":{"id":"KVMb","name":"用户_KVMb","avatar":"/live/avatar/6.jpeg"}},{"id":"BBZJ47KFnZD7tBpM3CTvy","currentTime":0,"timestamp":1762336392208,"type":"join","content":"来了","user":{"id":"UVZb","name":"用户_UVZb","avatar":"/live/avatar/8.jpeg"}},{"id":"AGS5d6hT_5-h48FREVSbR","currentTime":0,"timestamp":1762336392209,"type":"join","content":"来了","user":{"id":"ZjT3","name":"用户_ZjT3","avatar":"/live/avatar/5.jpeg"}},{"id":"HeVYB-kGXJtRXkgcDCn9t","currentTime":0,"timestamp":1762336392209,"type":"join","content":"来了","user":{"id":"Z-8k","name":"用户_Z-8k","avatar":"/live/avatar/2.png"}},{"id":"RQamvgYXWB-EoimcUtSQ9","currentTime":0,"timestamp":1762336392209,"type":"join","content":"来了","user":{"id":"odAe","name":"用户_odAe","avatar":"/live/avatar/5.jpeg"}},{"id":"_7sa7I_Z1_B7U3i6E9vJu","currentTime":0,"timestamp":1762336392209,"type":"join","content":"来了","user":{"id":"xUa1","name":"用户_xUa1","avatar":"/live/avatar/9.jpeg"}},{"id":"5RWpS81g_CsgAjT5sRMRq","currentTime":0,"timestamp":1762336392209,"type":"join","content":"来了","user":{"id":"ZuIy","name":"用户_ZuIy","avatar":"/live/avatar/4.jpeg"}},{"id":"vJI3Ufqe2Fp5ADPTlyc96","currentTime":0,"timestamp":1762336392209,"type":"join","content":"来了","user":{"id":"3y0l","name":"用户_3y0l","avatar":"/live/avatar/5.jpeg"}},{"id":"8lfgCLBIFLRAtXyMqrMsS","currentTime":0,"timestamp":1762336392209,"type":"join","content":"来了","user":{"id":"VEbm","name":"用户_VEbm","avatar":"/live/avatar/10.jpeg"}},{"id":"fUyY7IwDutHxl7UNz0PHa","currentTime":0,"timestamp":1762336392209,"type":"join","content":"来了","user":{"id":"ZT2i","name":"用户_ZT2i","avatar":"/live/avatar/6.jpeg"}},{"id":"e2kiuv-wrT_bc0bBlpw2K","currentTime":0,"timestamp":1762336394210,"type":"join","content":"来了","user":{"id":"L0r5","name":"用户_L0r5","avatar":"/live/avatar/11.jpeg"}},{"id":"hcQbpOM_SEGCAF32_3bzU","currentTime":0,"timestamp":1762336394210,"type":"leave","content":"离开了","user":{"id":"5Wt_","name":"用户_5Wt_","avatar":"/live/avatar/7.jpeg"}},{"id":"wCKi4WczH022nHcRqBgfJ","currentTime":0,"timestamp":1762336394210,"type":"gift","content":"送了一个鲜花","user":{"id":"sfQv","name":"用户_sfQv","avatar":"/live/avatar/8.jpeg"}},{"id":"HnuZoPSlweidAWrDP0_ZX","currentTime":0,"timestamp":1762336394210,"type":"like","content":"点赞了","user":{"id":"ZT2i","name":"用户_ZT2i","avatar":"/live/avatar/6.jpeg"}},{"id":"jSwxXquwtho5QqscuEs9W","currentTime":0,"timestamp":1762336395210,"type":"join","content":"来了","user":{"id":"_VsY","name":"用户__VsY","avatar":"/live/avatar/11.jpeg"}},{"id":"huvyorltBb41DbMdTLoaj","currentTime":0,"timestamp":1762336395210,"type":"leave","content":"离开了","user":{"id":"GlCP","name":"用户_GlCP","avatar":"/live/avatar/13.jpeg"}},{"id":"CL244JXoO5q23h3CsnELz","currentTime":0,"timestamp":1762336395210,"type":"gift","content":"送了一个棒棒糖","user":{"id":"5_NK","name":"用户_5_NK","avatar":"/live/avatar/12.jpeg"}},{"id":"t0CHJdsdEGHTfh5LN-fNg","currentTime":0,"timestamp":1762336395210,"type":"like","content":"点赞了","user":{"id":"sfQv","name":"用户_sfQv","avatar":"/live/avatar/8.jpeg"}},{"id":"BsNRvhbsALKBT459RjMQf","currentTime":0,"timestamp":1762336396210,"type":"join","content":"来了","user":{"id":"i1Nm","name":"用户_i1Nm","avatar":"/live/avatar/11.jpeg"}},{"id":"utBUjGTZNomaoILuU5uru","currentTime":0,"timestamp":1762336396210,"type":"leave","content":"离开了","user":{"id":"9O5E","name":"用户_9O5E","avatar":"/live/avatar/10.jpeg"}},{"id":"t_9tq1iUWK1gK1a96x9-p","currentTime":0,"timestamp":1762336396210,"type":"gift","content":"送了一个兔耳朵","user":{"id":"5_NK","name":"用户_5_NK","avatar":"/live/avatar/12.jpeg"}},{"id":"7o0q2qpigzetPAM5WdVRl","currentTime":0,"timestamp":1762336396210,"type":"like","content":"点赞了","user":{"id":"5_NK","name":"用户_5_NK","avatar":"/live/avatar/12.jpeg"}},{"id":"id2icmHNPMdhBYBv16-he","currentTime":0,"timestamp":1762336397210,"type":"join","content":"来了","user":{"id":"NAmT","name":"用户_NAmT","avatar":"/live/avatar/8.jpeg"}},{"id":"TTFk2PBchwGt19Ed8FY0R","currentTime":0,"timestamp":1762336397210,"type":"leave","content":"离开了","user":{"id":"FbCt","name":"用户_FbCt","avatar":"/live/avatar/1.png"}},{"id":"Cb_7BMjHiqsO99fd12MDO","currentTime":0,"timestamp":1762336397210,"type":"gift","content":"送了一个鲜花","user":{"id":"cAgU","name":"用户_cAgU","avatar":"/live/avatar/5.jpeg"}},{"id":"AsKvB8LYadrgPp1uCbYC1","currentTime":0,"timestamp":1762336397210,"type":"like","content":"点赞了","user":{"id":"odAe","name":"用户_odAe","avatar":"/live/avatar/5.jpeg"}}],"stats":{"totalViewers":24,"peakViewers":24,"totalGifts":4,"totalLikes":4,"totalGiftViewers":4}}',
// created_operator: '6911878751345180672',
// updated_operator: '6911878751345180672',
// created_time: '2025-11-05 17:53:18',
// updated_time: '2025-11-05 17:54:02',
// ai_status: '2',
// ai_level: '',
// subtitle:
// '{"TaskId":"4291bff52d34400fb5c2eb896f2247b1","RequestId":"81457C30-9DD8-56D5-BA11-3343E4431ED2","StatusText":"SUCCESS_WITH_NO_VALID_FRAGMENT","BizDuration":0,"SolveTime":0,"RequestTime":0,"StatusCode":21050003}',
// task_id: '',
// illegal_word: '',
// order_count: '0',
// improvement_plan: null,
// },
// ],
// reports: [],
// orders: [],
// },
// },
// },
// }
return httpRequest.get('/api/lab/v1/experiment/live-student-record/detail', { params })
}
// 获取直播成绩列表
export function getScoreList(params?: { name?: string }) {
return httpRequest.get('/api/lab/v1/experiment/live-student-record/list', { params })
}
// 导出成绩
export function exportScore(params?: { name?: string }) {
return httpRequest.get('/api/lab/v1/experiment/live-student-record/export', { params })
}
// 更新成绩
export function updateScore(data: { id: string; score_status: number; total_score: number; score_details: string }) {
return httpRequest.post('/api/lab/v1/experiment/live-student-record/score', data)
}
export function studentSubmit() {
return httpRequest.post('/api/lab/v1/experiment/live-student-record/submit')
}
<script setup lang="ts">
withDefaults(
defineProps<{
hasSaveButton?: boolean
hasNextButton?: boolean
maxScore: number
}>(),
{ hasSaveButton: true, hasNextButton: true }
)
const emits = defineEmits<{
(e: 'save', params: { score: number | undefined; comment: string }): void
(e: 'next'): void
}>()
const score = defineModel<number | undefined>('score', { required: true })
const comment = defineModel<string>('comment', { required: true })
</script>
<template>
<div class="score-card">
<div class="score-card-title">
本模块分值:<span>{{ maxScore }}</span>
</div>
<div class="score-card-form">
<el-form label-suffix=":" label-width="46px">
<el-form-item label="得分">
<el-input-number v-model="score" :min="0" :max="maxScore" :controls="false" />
</el-form-item>
<el-form-item label="评语">
<el-input v-model="comment" type="textarea" :rows="6" />
</el-form-item>
<el-form-item>
<el-button type="primary" auto-insert-space @click="emits('save', { score, comment })" v-if="hasSaveButton"
>保存</el-button
>
<el-button type="primary" plain auto-insert-space @click="emits('next')" v-if="hasNextButton"
>下一步</el-button
>
</el-form-item>
</el-form>
</div>
<div class="score-card-content">
<slot></slot>
</div>
</div>
</template>
<style lang="scss" scoped>
.score-card {
position: relative;
display: flex;
flex-direction: column;
gap: 20px;
border-radius: 10px;
border: 1px solid #dcdfe6;
margin: 20px 0 40px;
}
.score-card-title {
position: absolute;
left: 20px;
top: 0;
background-color: #fff;
font-size: 16px;
font-weight: bold;
transform: translateY(-50%);
padding: 0 10px;
span {
font-size: 18px;
color: var(--main-color);
}
}
.score-card-form {
display: flex;
flex-direction: column;
gap: 10px;
padding: 40px 30px 0;
}
.score-card-content {
border-top: 1px solid #dcdfe6;
padding: 30px;
}
</style>
<script setup>
import VueMarkdown from 'vue-markdown-render'
import Demo from '../../test/components/Demo.vue'
const props = defineProps({
data: { type: Object },
})
const detail = computed(() => {
return { ...props.data, live_info: JSON.parse(props.data.live_info) }
})
function formatDuration(seconds) {
const minutes = Math.floor(seconds / 60)
const remainingSeconds = (seconds % 60).toFixed(2)
return minutes > 0 ? `${minutes}m${remainingSeconds}s` : `${remainingSeconds}s`
}
</script>
<template>
<div
class="live-view"
style="border: 1px solid #eee; border-radius: 10px; margin-bottom: 20px; overflow: hidden"
v-if="detail">
<AppCard>
<div class="live-info">
<div class="live-info-header">
<h2>练习表现</h2>
<p>评分等级</p>
<h3>{{ detail.ai_level || '--' }}</h3>
<p>主播完成质量良好,需要关注细节勤加练习</p>
</div>
<div class="live-info-item">
<div>
<p>主播姓名</p>
<p>{{ detail.created_operator.real_name || detail.created_operator.nickname }}</p>
</div>
<div>
<p>直播时长</p>
<p>{{ formatDuration(detail.live_duration) }}</p>
</div>
<div>
<p>开始时间</p>
<p>{{ detail.live_start_time }}</p>
</div>
<div>
<p>结束时间</p>
<p>{{ detail.live_end_time }}</p>
</div>
<div>
<p>视频状态</p>
<p>已上传</p>
</div>
<div>
<p>观众总人数</p>
<p>{{ detail.live_info.stats.totalViewers }}</p>
</div>
<div>
<p>最高峰人数</p>
<p>{{ detail.live_info.stats.peakViewers }}</p>
</div>
<div>
<p>点赞数</p>
<p>{{ detail.live_info.stats.totalLikes }}</p>
</div>
<div>
<p>刷礼物人数</p>
<p>{{ detail.live_info.stats.totalGiftViewers }}</p>
</div>
<div>
<p>刷礼物总数</p>
<p>{{ detail.live_info.stats.totalGifts }}</p>
</div>
<div>
<p>下单量</p>
<p>{{ detail.orders_count }}</p>
</div>
</div>
</div>
</AppCard>
<AppCard>
<h2 class="live-title">违规情况</h2>
<div class="live-markdown">
<VueMarkdown :source="detail.illegal_word" v-if="detail.illegal_word" />
</div>
</AppCard>
<AppCard>
<h2 class="live-title">卖点讲解</h2>
<el-steps direction="vertical" :active="1">
<el-step
v-for="(item, index) in detail.words"
:key="index"
:title="item.name"
:status="item.time ? 'success' : 'error'">
<template #description> {{ item.time }} {{ item.subtitle }} </template>
</el-step>
</el-steps>
</AppCard>
<AppCard>
<h2 class="live-title">产生订单</h2>
<el-steps direction="vertical" :active="1">
<el-step
v-for="(item, index) in detail.words"
:key="index"
:title="item.name"
:status="item.time ? 'success' : 'error'">
<template #description> {{ item.time }} {{ item.subtitle }} </template>
</el-step>
</el-steps>
</AppCard>
</div>
<Demo :id="detail.live_practice_id" :recordId="detail.id" :isView="true" />
</template>
<style lang="scss" scoped>
.live-view {
.live-info {
display: flex;
gap: 12px;
p {
font-size: 14px;
color: #525252;
margin: 10px 0;
}
.live-info-header {
display: flex;
flex-direction: column;
border-right: 1px solid #e5e5e5;
padding-right: 12px;
h2 {
font-size: 24px;
font-weight: bold;
}
h3 {
font-size: 40px;
font-weight: bold;
}
}
.live-info-item {
flex: 1;
display: flex;
flex-wrap: wrap;
gap: 12px;
div {
width: 180px;
}
}
}
.live-title {
font-size: 20px;
font-weight: bold;
margin-bottom: 20px;
}
.live-tips {
margin-top: 10px;
font-size: 14px;
color: #000;
b {
font-size: 14px;
font-weight: bold;
margin-right: 20px;
}
}
.el-step__description {
margin-bottom: 20px;
}
.live-markdown {
line-height: 1.8;
li {
list-style: disc;
margin-left: 20px;
}
p {
margin: 10px 0;
}
strong,
b {
font-weight: bold;
}
}
}
</style>
import type { RouteRecordRaw } from 'vue-router'
import Layout from '@/components/layout/Index.vue'
const routes: RouteRecordRaw[] = [
{
path: '/live/score',
component: Layout,
children: [
{ path: '', component: () => import('./views/Index.vue') },
{ path: 'view', component: () => import('./views/View.vue') },
],
},
]
export { routes }
<script setup>
import LiveProductCategory from '@/components/LiveProductCategory.vue'
import { getScoreList, studentSubmit } from '../api'
const route = useRoute()
const appList = ref(null)
// 刷新
const handleRefresh = () => {
appList.value?.refetch()
}
const listParams = reactive({ name: '', status: '' })
// 列表配置
const listOptions = computed(() => {
return {
remote: { httpRequest: getScoreList, params: listParams },
filters: [
{ label: '姓名', prop: 'name', type: 'input' },
{
label: '状态',
prop: 'status',
type: 'select',
options: [
{ label: '未批改', value: '1' },
{ label: '已批改', value: '2' },
],
},
],
columns: [
{ label: '序号', type: 'index', width: 60 },
{ label: '姓名', prop: 'student_name' },
{ label: '专业', prop: 'specialty_name' },
{ label: '班级', prop: 'class_name' },
{ label: '成绩', prop: 'score_display' },
{ label: '状态', prop: 'status_name' },
{ label: '提交时间', prop: 'commit_time' },
{ label: '批改时间', prop: 'commit_time' },
{ label: '操作', slots: 'table-x', width: 200 },
],
}
})
const handleSubmit = () => {
studentSubmit().then(() => {
handleRefresh()
})
}
const handleExport = () => {
window.open(`/api/lab/v1/experiment/live-student-record/export?experiment_id=${route.query.experiment_id}`)
}
</script>
<template>
<AppCard title="直播成绩管理">
<AppList v-bind="listOptions" ref="appList">
<template #header-buttons>
<el-button type="primary" @click="handleSubmit">提交成绩</el-button>
<el-button type="primary" @click="handleExport">导出成绩</el-button>
</template>
<template #filter-category>
<LiveProductCategory v-model="listParams.live_commodity_type_id" @change="handleRefresh"></LiveProductCategory>
</template>
<template #table-x="{ row }">
<el-button text type="primary">
<router-link :to="{ path: '/live/score/view', query: { ...$route.query, id: row.id } }" target="_blank"
>评分</router-link
>
</el-button>
</template>
</AppList>
</AppCard>
</template>
<script setup>
import { getScoreDetail, updateScore } from '../api'
import { useMapStore } from '@/stores/map'
import {
getNameByValue,
importType,
requiredType,
deliveryMode,
deliveryTime,
shippingTemplate,
afterSalesPolicy,
} from '@/utils/dictionary'
import ScoreCard from '../components/ScoreCard.vue'
import ScoreCardLive from '../components/ScoreCardLive.vue'
import Preview from '@/components/Preview.vue'
import { ElMessageBox } from 'element-plus'
const route = useRoute()
const id = route.query.id
const statusList = useMapStore().getMapValuesByKey('system_status')
const detail = ref(null)
const scoreDetails = reactive({
commodity_type: { score: null, comment: '' },
commodity_attr: { score: null, comment: '' },
commodity: { score: null, comment: '' },
speech: { score: null, comment: '' },
practice_record1: { score: null, comment: '' },
improvement_plan: { score: null, comment: '' },
practice_record2: { score: null, comment: '' },
report: { score: null, comment: '' },
})
const totalScore = computed(() => {
return Object.values(scoreDetails).reduce((acc, curr) => acc + (curr.score || 0), 0)
})
const fetchDetail = async () => {
const res = await getScoreDetail({ id })
detail.value = res.data.detail
Object.assign(scoreDetails, res.data.detail.score_details ? JSON.parse(res.data.detail.score_details) : {})
}
onMounted(() => {
fetchDetail()
})
// 商品类别
const categoryTableOptions = {
columns: [
{ label: '商品品类名称', prop: 'name', align: 'left' },
{ label: '层级', prop: 'level' },
{
label: '状态',
prop: 'status',
computed({ row }) {
const color = row.status === '1' ? 'var(--main-success-color)' : 'var(--main-color)'
return `<span style="color: ${color}">${getNameByValue(row.status, statusList)}</span>`
},
},
],
}
// 商品属性
const attrsTableOptions = {
columns: [
{ label: '序号', type: 'index', width: 60 },
{ label: '商品属性名称', prop: 'name' },
{ label: '所属商品品类', prop: 'live_commodity_type_full_name' },
{
label: '重要性',
prop: 'is_importance',
computed({ row }) {
return getNameByValue(row.is_importance, importType)
},
},
{
label: '必要性',
prop: 'is_essential',
computed({ row }) {
return getNameByValue(row.is_essential, requiredType)
},
},
{
label: '状态',
prop: 'status',
computed({ row }) {
const color = row.status === '1' ? 'var(--main-success-color)' : 'var(--main-color)'
return `<span style="color: ${color}">${getNameByValue(row.status, statusList)}</span>`
},
},
{ label: '更新时间', prop: 'updated_time' },
],
}
// 商品管理
const productList = computed(() => {
return detail.value.live_data?.commodities?.map((item) => {
try {
item.info = JSON.parse(item.info)
item.live_commodity_attrs = JSON.parse(item.live_commodity_attrs)
item.picture_34_addreses = JSON.parse(item.picture_34_addreses)
item.picture_addreses = JSON.parse(item.picture_addreses)
} catch (error) {
console.log(error)
}
console.log(item)
return item
})
})
const productTableOptions = {
columns: [
{
type: 'expand',
slots: 'table-expand',
},
{ label: '序号', type: 'index', width: 60 },
{ label: '商品标题', prop: 'title' },
{ label: '所属商品品类', prop: 'live_commodity_type_full_name' },
{ label: '参考价(¥)', prop: 'info.reference_price' },
{
label: '发货模式',
prop: 'delivery_mode',
computed({ row }) {
return getNameByValue(row.info.delivery_mode, deliveryMode)
},
},
{
label: '发货时效',
prop: 'delivery_time',
computed({ row }) {
return getNameByValue(row.info.delivery_time, deliveryTime)
},
},
{
label: '运费模板',
prop: 'shipping_template',
computed({ row }) {
return getNameByValue(row.info.shipping_template, shippingTemplate)
},
},
{
label: '售后政策',
prop: 'after_sales_policy',
computed({ row }) {
return getNameByValue(row.info.after_sales_policy, afterSalesPolicy)
},
},
],
}
// 商品规格
const productSpecsTableOptions = {
columns: [
{ label: '规格名称', prop: 'name' },
{
label: '规格值',
prop: 'value',
computed({ row }) {
return row.values.join(' / ')
},
},
],
}
// 商品sku
const productSkuTableOptions = {
columns: [
{
label: '规格',
prop: 'name',
computed({ row }) {
return row.specs.join(' / ')
},
},
{ label: '价格(¥)', prop: 'price' },
{ label: '库存', prop: 'stock' },
],
}
// 直播话术
const talkTableOptions = {
columns: [
{ type: 'expand', slots: 'table-expand' },
{ label: '序号', type: 'index', width: 60 },
{ label: '直播话术名称', prop: 'name' },
{ label: '直播主题标题', prop: 'live_commodity_title' },
{
label: '话术时长',
prop: 'duration',
computed({ row }) {
return `${row.duration}分钟`
},
},
{ label: '所属直播主题品类', prop: 'live_commodity_type_full_name' },
],
}
const activeTab = ref(1)
const handleAIScore = () => {
console.log('AI一键评分')
}
const handleSave = (key, params) => {
scoreDetails[key] = params
commitScore(0).then(() => {
ElMessage.success('保存成功')
})
}
const handleNext = () => {
activeTab.value++
}
const commitScore = (status = 0) => {
return updateScore({
id: route.query.id,
score_status: status,
total_score: totalScore.value,
score_details: JSON.stringify(scoreDetails),
})
}
const handlePublishScore = () => {
ElMessageBox.confirm('确定要发布成绩吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(async () => {
await commitScore(1)
await fetchDetail()
ElMessage.success('发布成绩成功')
})
}
</script>
<template>
<AppCard title="直播成绩管理" v-if="detail">
<div class="score-header">
<el-form label-suffix=":" inline class="info">
<el-form-item label="姓名">{{ detail.student_name }}</el-form-item>
<el-form-item label="专业">{{ detail.specialty_name }}</el-form-item>
<el-form-item label="班级">{{ detail.class_name }}</el-form-item>
<el-form-item label="状态">{{ detail.status_name }}</el-form-item>
<el-form-item label="提交时间">{{ detail.commit_time }}</el-form-item>
<el-form-item>
<el-button type="primary" :disabled="detail.status == '2'" @click="handleAIScore">AI一键评分</el-button>
<el-button type="primary" :disabled="detail.status == '2'" @click="handlePublishScore">发布成绩</el-button>
</el-form-item>
</el-form>
<div class="score-box">
<span>{{ totalScore || '--' }}</span>
</div>
</div>
<el-divider />
<el-tabs stretch v-model="activeTab" class="score-tabs">
<el-tab-pane label="商品品类管理" :name="1">
<ScoreCard
:maxScore="5"
:hasSaveButton="detail.status != '2'"
:score="scoreDetails.commodity_type.score"
:comment="scoreDetails.commodity_type.comment"
@save="handleSave('commodity_type', $event)"
@next="handleNext">
<AppList v-bind="categoryTableOptions" row-key="id" :data="detail.live_data.commodity_types" />
</ScoreCard>
</el-tab-pane>
<el-tab-pane label="商品属性管理" :name="2" lazy>
<ScoreCard
:maxScore="5"
:hasSaveButton="detail.status != '2'"
:score="scoreDetails.commodity_attr.score"
:comment="scoreDetails.commodity_attr.comment"
@save="handleSave('commodity_attr', $event)"
@next="handleNext">
<AppList v-bind="attrsTableOptions" :data="detail.live_data.commodity_attrs" />
</ScoreCard>
</el-tab-pane>
<el-tab-pane label="商品管理" :name="3" lazy>
<ScoreCard
:maxScore="15"
:hasSaveButton="detail.status != '2'"
:score="scoreDetails.commodity.score"
:comment="scoreDetails.commodity.comment"
@save="handleSave('commodity', $event)"
@next="handleNext">
<AppList v-bind="productTableOptions" :data="productList" :default-expand-all="productList.length === 1">
<template #table-expand="{ row }">
<div class="table-expand-box">
<el-form label-position="top">
<el-row>
<el-col :span="12">
<el-form-item label="主图">
<el-image
:src="item.url"
v-for="item in row.picture_addreses"
:key="item.url"
fit="cover"
preview-teleported
:preview-src-list="row.picture_addreses.map((item) => item.url)"
style="width: 100px; height: 100px; margin-right: 10px"></el-image>
</el-form-item>
<el-form-item label="主图3:4">
<el-image
:src="item.url"
v-for="item in row.picture_34_addreses"
:key="item.url"
fit="cover"
preview-teleported
:preview-src-list="row.picture_addreses.map((item) => item.url)"
style="width: 100px; height: 134px; margin-right: 10px"></el-image>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="主图视频">
<video
:src="row.video_url"
controls
style="width: 100%; height: 282px"
v-if="row.video_url"></video>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="商品规格">
<AppList v-bind="productSpecsTableOptions" :data="row.info?.specs" style="width: 100%"></AppList>
</el-form-item>
<el-form-item label="价格与库存">
<AppList v-bind="productSkuTableOptions" :data="row.info?.sku" style="width: 100%"></AppList>
</el-form-item>
</el-form>
</div>
</template>
</AppList>
</ScoreCard>
</el-tab-pane>
<el-tab-pane label="直播话术管理" :name="4" lazy>
<ScoreCard
:maxScore="15"
:hasSaveButton="detail.status != '2'"
:score="scoreDetails.speech.score"
:comment="scoreDetails.speech.comment"
@save="handleSave('speech', $event)"
@next="handleNext">
<AppList
v-bind="talkTableOptions"
:data="detail.live_data.speeches"
:default-expand-all="detail.live_data.speeches.length === 1">
<template #table-expand="{ row }">
<div class="table-expand-box">
<el-form label-position="top">
<el-form-item label="直播主题卖点">
<div class="form-box" v-html="row.selling_point"></div>
</el-form-item>
<el-form-item label="营销活动">
<div class="form-box" v-html="row.marketing_campaign"></div>
</el-form-item>
<el-form-item label="直播话术内容">
<div class="form-box" v-html="row.content"></div>
</el-form-item>
</el-form>
</div>
</template>
</AppList>
</ScoreCard>
</el-tab-pane>
<el-tab-pane label="首次直播演练" :name="5" lazy>
<ScoreCard
:maxScore="20"
:hasSaveButton="detail.status != '2'"
:score="scoreDetails.practice_record1.score"
:comment="scoreDetails.practice_record1.comment"
@save="handleSave('practice_record1', $event)"
@next="handleNext">
<ScoreCardLive :data="detail.live_data.practice_records[0]" v-if="detail.live_data.practice_records[0]" />
<el-empty v-else />
</ScoreCard>
</el-tab-pane>
<el-tab-pane label="直播复盘分析" :name="6" lazy>
<ScoreCard
:maxScore="10"
:hasSaveButton="detail.status != '2'"
:score="scoreDetails.improvement_plan.score"
:comment="scoreDetails.improvement_plan.comment"
@save="handleSave('improvement_plan', $event)"
@next="handleNext">
<el-form label-position="top" v-if="detail.live_data.practice_records[0]?.improvement_plan">
<el-form-item label="改进方案">
<div class="form-box" v-html="detail.live_data.practice_records[0]?.improvement_plan"></div>
</el-form-item>
</el-form>
<el-empty v-else />
</ScoreCard>
</el-tab-pane>
<el-tab-pane label="二次直播演练" :name="7" lazy>
<ScoreCard
:maxScore="15"
:hasSaveButton="detail.status != '2'"
:score="scoreDetails.practice_record2.score"
:comment="scoreDetails.practice_record2.comment"
@save="handleSave('practice_record2', $event)"
@next="handleNext">
<ScoreCardLive :data="detail.live_data.practice_records[1]" v-if="detail.live_data.practice_records[1]" />
<el-empty v-else />
</ScoreCard>
</el-tab-pane>
<el-tab-pane label="直播总结汇报" :name="8" lazy>
<ScoreCard
:maxScore="15"
:hasSaveButton="detail.status != '2'"
:hasNextButton="false"
:score="scoreDetails.report.score"
:comment="scoreDetails.report.comment"
@save="handleSave('report', $event)"
@next="handleNext">
<template v-if="detail.live_data.reports.length > 0">
<div v-for="item in detail.live_data.reports" :key="item.report_url">
<Preview :url="item.report_url" />
</div>
</template>
<el-empty v-else />
</ScoreCard>
</el-tab-pane>
</el-tabs>
</AppCard>
</template>
<style lang="scss" scoped>
.score-tabs {
:deep(.el-tabs__header) {
width: 1000px;
margin: 0 auto 20px;
}
:deep(.el-tabs__nav-wrap::after) {
display: none;
}
}
.score-header {
display: flex;
align-items: center;
.el-form {
flex: 1;
}
}
.score-box {
width: 100px;
border: 1px solid #dcdfe6;
padding: 10px;
border-radius: 10px;
font-size: 30px;
color: var(--main-color);
display: flex;
align-items: center;
justify-content: center;
}
.table-expand-box {
border: 1px solid #dcdfe6;
padding: 20px;
border-radius: 10px;
}
.form-box {
width: 100%;
border: 1px solid #dcdfe6;
padding: 10px;
border-radius: 6px;
}
</style>
......@@ -66,3 +66,8 @@ export function getRecord(params: { id: string }) {
export function pushSubtitle(params: { subtitle: string; selling_point: string; marketing_campaign: string }) {
return httpRequest.get('/api/lab/v1/experiment/live-practice/push-subtitle', { params })
}
// 更新改进方案
export function updateImprovementPlan(data: { id: string; improvement_plan: string }) {
return httpRequest.post('/api/lab/v1/experiment/live-practice/submit-improvement-plan', data)
}
<script setup lang="ts">
import AppEditor from '@/components/base/AppEditor.vue'
import { updateImprovementPlan } from '../api'
import { ElMessage } from 'element-plus'
const props = defineProps<{ id: string; content?: string }>()
const dialogVisible = ref<boolean>(false)
const text = ref<string>(props.content || '')
async function handleSubmit() {
await updateImprovementPlan({ id: props.id, improvement_plan: text.value })
dialogVisible.value = false
ElMessage.success('保存成功')
}
</script>
<template>
<el-button text type="primary" @click="dialogVisible = true">改进方案</el-button>
<el-dialog title="改进方案" width="800px" v-model="dialogVisible" v-if="dialogVisible" append-to-body>
<AppEditor v-model="text" />
<template #footer>
<el-row justify="center">
<el-button type="primary" round auto-insert-space @click="handleSubmit">保存</el-button>
<el-button round auto-insert-space @click="dialogVisible = false">取消</el-button>
</el-row>
</template>
</el-dialog>
</template>
......@@ -109,7 +109,7 @@ const productUrl = computed(() => {
<img src="/live/game.png" style="height: 36px; margin: 8px 15px" />
</div>
</div>
<div class="bottom" v-if="false">
<div class="bottom">
<div class="item">
<div class="topBar">
<div class="talk">
......
......@@ -3,6 +3,8 @@ import { ElMessage, ElLoading } from 'element-plus'
import { useFileDialog } from '@vueuse/core'
import { upload } from '@/utils/upload'
import { getRecordList, saveTestRecord, getRecord } from '../api'
import ImprovementPlan from './ImprovementPlan.vue'
const props = defineProps(['data'])
function formatDuration(seconds) {
......@@ -60,7 +62,7 @@ const listOptions = {
return row.ai_level || '--'
},
},
{ label: '操作', slots: 'table-x', width: 120 },
{ label: '操作', slots: 'table-x', width: 140 },
],
}
......@@ -107,18 +109,31 @@ const handleUpload = (row) => {
<AppList v-bind="listOptions" ref="appList">
<template #table-x="{ row }">
<el-button text type="primary">
<router-link :to="{ path: 'test/view', query: { ...$route.query, id: data.id, record_id: row.id } }"
>查看</router-link
<router-link
:to="{ path: 'test/reply', query: { ...$route.query, id: data.id, record_id: row.id } }"
target="_blank"
>直播视频回放
</router-link>
</el-button>
<br />
<el-button text type="primary">
<router-link
:to="{ path: 'test/view', query: { ...$route.query, id: data.id, record_id: row.id } }"
target="_blank"
>查看AI评价</router-link
>
</el-button>
<br />
<ImprovementPlan :id="row.id" :content="row.improvement_plan" v-if="row.id" />
<br />
<el-button
type="primary"
text
:loading="uploading && currentRow.id === row.id"
:disabled="uploading"
@click="handleUpload(row)"
>上传</el-button
>上传直播视频</el-button
>
</el-button>
</template>
</AppList>
</el-dialog>
......
......@@ -13,6 +13,7 @@ const routes: RouteRecordRaw[] = [
{ path: '', component: () => import('./views/Index.vue') },
{ path: 'demo', component: () => import('./views/Demo.vue') },
{ path: 'view', component: () => import('./views/View.vue') },
{ path: 'reply', component: () => import('./views/Reply.vue') },
],
},
{
......
<script setup>
import Demo from '../components/Demo.vue'
const route = useRoute()
const id = route.query.id
const recordId = route.query.record_id
</script>
<template>
<AppCard title="直播回放" full>
<Demo :id="id" :recordId="recordId" :isView="true" />
</AppCard>
</template>
......@@ -2,6 +2,7 @@
import VueMarkdown from 'vue-markdown-render'
import Demo from '../components/Demo.vue'
import { getRecord } from '../api'
import ImprovementPlan from '../components/ImprovementPlan.vue'
const route = useRoute()
const id = route.query.id
......@@ -111,7 +112,8 @@ function formatDuration(seconds) {
</div>
<div>
<p>操作</p>
<el-button type="primary" link @click="dialogVisible = true">查看</el-button>
<el-button type="primary" link @click="dialogVisible = true">查看直播回放</el-button>
<ImprovementPlan :id="recordId" :content="detail.improvement_plan" v-if="recordId" />
</div>
</div>
</div>
......
import type { IMenuItem } from '@/types'
import { defineStore } from 'pinia'
import { useUserStore } from '@/stores/user'
import { Document, DocumentChecked } from '@element-plus/icons-vue'
import IconMetadata from '@/components/icon/IconMetadata.vue'
import IconConnect from '@/components/icon/IconConnect.vue'
......@@ -373,6 +374,15 @@ const adminMenus: IMenuItem[] = [
{ id: 24, name: '直播话术管理', path: '/live/talk', icon: markRaw(IconLiveTalk) },
{ id: 25, name: '直播', path: '/live/test', icon: markRaw(IconLiveTest) },
{ id: 201, name: '订单管理', path: '/live/order', icon: markRaw(IconCard) },
{ id: 202, name: '直播总结管理', path: '/live/reports', icon: markRaw(Document) },
{
id: 203,
name: '直播成绩管理',
path: '/live/score',
icon: markRaw(DocumentChecked),
role: [5, 6],
},
],
},
{
......
......@@ -55,6 +55,11 @@ export default defineConfig(({ mode }) => ({
changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api\/duan/, ''),
},
'/api/dev': {
target: 'http://localhost:20081',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/dev/, ''),
},
// '/api/lab': {
// target: 'http://local-com-resource-api.frontend.ezijing.com',
// changeOrigin: true,
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论