From 6e9b5de8d1b6b9ae2f6b1da99b4771ef54465b03 Mon Sep 17 00:00:00 2001 From: Timothy Yin Date: Tue, 21 Jan 2025 03:48:56 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=94=A8=E6=88=B7=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=92=8C=E7=94=A8=E9=87=8F=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/DatePicker.vue | 88 +++ components/aigc/generation/GBTaskCard.vue | 6 +- package.json | 2 + pages/generation/admin/users.vue | 624 +++++++++++++++++++--- pnpm-lock.yaml | 64 +++ typings/types.d.ts | 23 + 6 files changed, 723 insertions(+), 84 deletions(-) create mode 100644 components/DatePicker.vue diff --git a/components/DatePicker.vue b/components/DatePicker.vue new file mode 100644 index 0000000..16913a5 --- /dev/null +++ b/components/DatePicker.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/components/aigc/generation/GBTaskCard.vue b/components/aigc/generation/GBTaskCard.vue index 659f680..41011f7 100644 --- a/components/aigc/generation/GBTaskCard.vue +++ b/components/aigc/generation/GBTaskCard.vue @@ -101,11 +101,11 @@ const onClick = () => {
  • 完成时间

    -

    {{ dayjs(video.complete_time * 1000).format('YYYY-MM-DD HH:mm:ss') }}

    +

    {{ video.complete_time ? dayjs(video.complete_time * 1000).format('YYYY-MM-DD HH:mm:ss') : '进行中' }}

  • 生成耗时

    -

    {{ dayjs.duration(video.duration || 0).format('HH:mm:ss') }}

    +

    {{ video.duration ? dayjs.duration(video.duration || 0).format('HH:mm:ss') : '进行中' }}

  • 驱动文本

    @@ -136,6 +136,7 @@ const onClick = () => { { -import ModalDigitalHumanSelect from '~/components/ModalDigitalHumanSelect.vue' - const toast = useToast() +const dayjs = useDayjs() const loginState = useLoginState() +const systemServices: ServiceType[] = [ + { + tag: 'PPTToVideo', + name: '微课视频(PPT驱动)', + }, + { + tag: 'TextToVideo', + name: '绿幕视频(文字驱动)', + }, + { + tag: 'SparkChat15', + name: '讯飞星火大模型 1.5', + }, + { + tag: 'SparkChat30', + name: '讯飞星火大模型 3.0', + }, + { + tag: 'SparkChat35', + name: '讯飞星火大模型 3.5', + }, + { + tag: 'TenImgToImg', + name: '腾讯混元 - 图生图', + }, + { + tag: 'TenTextToImg', + name: '腾讯混元 - 文生图', + }, +] + const columns = [ { key: 'id', @@ -17,7 +47,7 @@ const columns = [ }, { key: 'username', - label: '用户名', + label: '账户名/姓名', disabled: true, }, { @@ -28,14 +58,6 @@ const columns = [ key: 'mobile', label: '手机号', }, - { - key: 'email', - label: '电子邮箱', - }, - { - key: 'nickname', - label: '昵称', - }, { key: 'sex', label: '性别', @@ -50,9 +72,11 @@ const columns = [ }, ] -const selectedColumns = ref([...columns.filter(row => { - return !['nickname', 'email', 'auth_code'].includes(row.key) -})]) +const selectedColumns = ref([ + ...columns.filter((row) => { + return !['auth_code'].includes(row.key) + }), +]) const page = ref(1) const pageCount = ref(15) const state_filter = ref<'verified' | 'unverified'>('verified') @@ -60,11 +84,14 @@ const is_verified = ref(true) const viewingUser = ref(null) const isSlideOpen = computed({ get: () => !!viewingUser.value, - set: () => viewingUser.value = null, + set: () => (viewingUser.value = null), }) -watch([is_verified, pageCount], () => page.value = 1) -watch(state_filter, () => is_verified.value = state_filter.value === 'verified') +watch([is_verified, pageCount], () => (page.value = 1)) +watch( + state_filter, + () => (is_verified.value = state_filter.value === 'verified') +) const { data: usersData, @@ -72,26 +99,31 @@ const { status: usersDataStatus, } = useAsyncData( 'systemUsers', - () => useFetchWrapped< - req.user.UserList & AuthedRequest, - BaseResponse> - >('App.User_User.ListUser', { - token: loginState.token!, - user_id: loginState.user.id!, - page: page.value, - perpage: pageCount.value, - is_verify: is_verified.value, - }), + () => + useFetchWrapped< + req.user.UserList & AuthedRequest, + BaseResponse> + >('App.User_User.ListUser', { + token: loginState.token!, + user_id: loginState.user.id!, + page: page.value, + perpage: pageCount.value, + is_verify: is_verified.value, + }), { watch: [page, pageCount, is_verified], - }, + transform: (res) => { + res.data.items.unshift(loginState.user) + return res + }, + } ) const isDigitalSelectorOpen = ref(false) const dhPage = ref(1) const dhPageCount = ref(10) -watch(dhPageCount, () => dhPage.value = 1) +watch(dhPageCount, () => (dhPage.value = 1)) const { data: digitalHumansData, @@ -99,26 +131,56 @@ const { status: digitalHumansDataStatus, } = useAsyncData( 'currentUserDigitalHumans', - () => useFetchWrapped< - PagedDataRequest & AuthedRequest, - BaseResponse> - >('App.User_UserDigital.GetList', { - token: loginState.token!, - user_id: loginState.user.id!, - to_user_id: viewingUser.value?.id || 0, - page: dhPage.value, - perpage: dhPageCount.value, - }), + () => + useFetchWrapped< + PagedDataRequest & AuthedRequest, + BaseResponse> + >('App.User_UserDigital.GetList', { + token: loginState.token!, + user_id: loginState.user.id!, + to_user_id: viewingUser.value?.id || 0, + page: dhPage.value, + perpage: dhPageCount.value, + }), { watch: [viewingUser, dhPage, dhPageCount], - }, + } ) +const { + data: userBalances, + status: userBalancesStatus, + refresh: refreshUserBalances, +} = useAsyncData( + 'userServiceBalances', + () => + useFetchWrapped< + PagedDataRequest & AuthedRequest, + BaseResponse> + >('App.User_UserBalance.GetList', { + token: loginState.token!, + user_id: loginState.user.id, + to_user_id: viewingUser.value?.id || 0, + page: 1, + perpage: 20, + }), + { + watch: [viewingUser], + } +) + +const getBalanceByTag = (tag: ServiceTag): ServiceBalance | null => { + return ( + userBalances?.value?.data.items.find((row) => row.request_type === tag) || + null + ) +} + const items = (row: UserSchema) => [ [ { - label: '数字人授权', - icon: 'tabler:user-cog', + label: '服务和用量', + icon: 'tabler:server-cog', click: () => openSlide(row), disabled: row.auth_code === 0, }, @@ -155,12 +217,16 @@ const onDigitalHumansSelected = (digitalHumans: DigitalHumanItem[]) => { token: loginState.token!, user_id: loginState.user.id!, to_user_id: viewingUser.value?.id || 0, - digital_human_array: digitalHumans.map(row => row.id || row.digital_human_id), - }).then(res => { + digital_human_array: digitalHumans.map( + (row) => row.id || row.digital_human_id + ), + }).then((res) => { if (res.ret === 200) { toast.add({ title: '授权成功', - description: `成功授权 ${ res.data.success } 个数字人${ res.data.failed ? `,失败 ${ res.data.failed } 个` : '' }`, + description: `成功授权 ${res.data.success} 个数字人${ + res.data.failed ? `,失败 ${res.data.failed} 个` : '' + }`, color: 'green', icon: 'tabler:check', }) @@ -190,7 +256,7 @@ const revokeDigitalHuman = (uid: number, digitalHumanId: number) => { user_id: loginState.user.id!, to_user_id: uid, digital_human_id: digitalHumanId, - }).then(res => { + }).then((res) => { if (res.ret === 200 && res.data.code === 1) { toast.add({ title: '撤销成功', @@ -224,11 +290,11 @@ const setUserStatus = (uid: number, is_verified: boolean) => { user_id: loginState.user.id!, to_user_id: uid, is_verify: is_verified, - }).then(res => { + }).then((res) => { if (res.ret === 200 && res.data.status === 1) { toast.add({ title: '操作成功', - description: `已${ is_verified ? '启用' : '停用' }账号`, + description: `已${is_verified ? '启用' : '停用'}账号`, color: 'green', icon: is_verified ? 'tabler:shield-check' : 'tabler:cancel', }) @@ -243,21 +309,91 @@ const setUserStatus = (uid: number, is_verified: boolean) => { } }) } + +const userBalanceEditing = ref(false) +const userBalanceState = reactive({ + expire_time: dayjs().add(1, 'month').unix() * 1000, + remain_count: 1800, + request_type: '', +}) + +const isActivateBalance = ref(false) + +const isBalanceEditModalOpen = computed({ + get: () => !!viewingUser && userBalanceEditing.value, + set: (val) => { + userBalanceEditing.value = val + if (!val) { + userBalanceState.expire_time = dayjs().add(1, 'month').unix() * 1000 + userBalanceState.remain_count = 1800 + } + }, +}) + +const udpateBalance = (tag: ServiceTag, isActivate: boolean = false) => { + useFetchWrapped< + { + to_user_id: number + request_type: ServiceTag + expire_time: number + remain_count: number + } & AuthedRequest, + BaseResponse<{ + code: number // 1: success, 0: failed + data_id?: number + }> + >( + isActivate ? 'App.User_UserBalance.Insert' : 'App.User_UserBalance.Update', + { + token: loginState.token!, + user_id: loginState.user.id!, + to_user_id: viewingUser.value?.id || 0, + request_type: tag, + expire_time: userBalanceState.expire_time / 1000, + remain_count: userBalanceState.remain_count, + } + ) + .then((res) => { + if (res.ret === 200 && (res.data.code === 1 || !!res.data.data_id)) { + toast.add({ + title: '操作成功', + description: `已${isActivate ? '开通' : '更新'}服务`, + color: 'green', + icon: 'tabler:check', + }) + } else { + toast.add({ + title: '操作失败', + description: res.msg || '未知错误', + color: 'red', + icon: 'tabler:alert-triangle', + }) + } + }) + .catch((err) => { + toast.add({ + title: '操作失败', + description: err.message || '未知错误', + color: 'red', + icon: 'tabler:alert-triangle', + }) + }) + .finally(() => { + refreshUserBalances() + isBalanceEditModalOpen.value = false + }) +} -
    +
    + + 新增授权 + {
    { - \ No newline at end of file + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 08165b0..505a057 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: '@webav/av-cliper': specifier: ^1.0.10 version: 1.0.10 + date-fns: + specifier: ^4.1.0 + version: 4.1.0 dayjs: specifier: ^1.11.12 version: 1.11.12 @@ -65,6 +68,9 @@ importers: tailwindcss: specifier: ^3.4.7 version: 3.4.17 + v-calendar: + specifier: ^3.1.2 + version: 3.1.2(@popperjs/core@2.11.8)(vue@3.4.34) vue: specifier: ^3.4.34 version: 3.4.34 @@ -1782,6 +1788,9 @@ packages: '@types/linkify-it@3.0.5': resolution: {integrity: sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==} + '@types/lodash@4.17.14': + resolution: {integrity: sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==} + '@types/markdown-it@13.0.9': resolution: {integrity: sha512-1XPwR0+MgXLWfTn9gCsZ55AHOKW1WN+P9vr0PaQh5aerR9LLQXUbjfEAFhjmEmyoYFWAyuN2Mqkn40MZ4ukjBw==} @@ -1791,6 +1800,9 @@ packages: '@types/node@20.14.12': resolution: {integrity: sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ==} + '@types/resize-observer-browser@0.1.11': + resolution: {integrity: sha512-cNw5iH8JkMkb3QkCoe7DaZiawbDQEUX8t7iuQaRTyLOyQCR2h+ibBD4GJt7p5yhUHrlOeL7ZtbxNHeipqNsBzQ==} + '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} @@ -2579,6 +2591,18 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} + date-fns-tz@2.0.1: + resolution: {integrity: sha512-fJCG3Pwx8HUoLhkepdsP7Z5RsucUi+ZBOxyM5d0ZZ6c4SdYustq0VMmOu6Wf7bli+yS/Jwp91TOCqn9jMcVrUA==} + peerDependencies: + date-fns: 2.x + + date-fns@2.30.0: + resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} + engines: {node: '>=0.11'} + + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + dayjs-nuxt@2.1.9: resolution: {integrity: sha512-7L1X8Wm+O2s2o+YK8RH1GK0SUUsh7l0lnIzsjaLGvyu64asLs2KVNoh25J4tQurdILp2TyrFaTo/k3k/ZPub9Q==} @@ -5136,6 +5160,12 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + v-calendar@3.1.2: + resolution: {integrity: sha512-QDWrnp4PWCpzUblctgo4T558PrHgHzDtQnTeUNzKxfNf29FkCeFpwGd9bKjAqktaa2aJLcyRl45T5ln1ku34kg==} + peerDependencies: + '@popperjs/core': ^2.0.0 + vue: ^3.2.0 + vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -5285,6 +5315,11 @@ packages: peerDependencies: vue: ^3.2.0 + vue-screen-utils@1.0.0-beta.13: + resolution: {integrity: sha512-EJ/8TANKhFj+LefDuOvZykwMr3rrLFPLNb++lNBqPOpVigT2ActRg6icH9RFQVm4nHwlHIHSGm5OY/Clar9yIg==} + peerDependencies: + vue: ^3.2.0 + vue@3.4.34: resolution: {integrity: sha512-VZze05HWlA3ItreQ/ka7Sx7PoD0/3St8FEiSlSTVgb6l4hL+RjtP2/8g5WQBzZgyf8WG2f+g1bXzC7zggLhAJA==} peerDependencies: @@ -7472,6 +7507,8 @@ snapshots: '@types/linkify-it@3.0.5': {} + '@types/lodash@4.17.14': {} + '@types/markdown-it@13.0.9': dependencies: '@types/linkify-it': 3.0.5 @@ -7483,6 +7520,8 @@ snapshots: dependencies: undici-types: 5.26.5 + '@types/resize-observer-browser@0.1.11': {} + '@types/resolve@1.20.2': {} '@types/trusted-types@2.0.7': {} @@ -8454,6 +8493,16 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 + date-fns-tz@2.0.1(date-fns@2.30.0): + dependencies: + date-fns: 2.30.0 + + date-fns@2.30.0: + dependencies: + '@babel/runtime': 7.26.0 + + date-fns@4.1.0: {} + dayjs-nuxt@2.1.9(magicast@0.3.4)(rollup@4.19.1): dependencies: '@nuxt/kit': 3.12.4(magicast@0.3.4)(rollup@4.19.1) @@ -11479,6 +11528,17 @@ snapshots: util-deprecate@1.0.2: {} + v-calendar@3.1.2(@popperjs/core@2.11.8)(vue@3.4.34): + dependencies: + '@popperjs/core': 2.11.8 + '@types/lodash': 4.17.14 + '@types/resize-observer-browser': 0.1.11 + date-fns: 2.30.0 + date-fns-tz: 2.0.1(date-fns@2.30.0) + lodash: 4.17.21 + vue: 3.4.34 + vue-screen-utils: 1.0.0-beta.13(vue@3.4.34) + vary@1.1.2: {} vite-hot-client@0.2.3(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)): @@ -11613,6 +11673,10 @@ snapshots: '@vue/devtools-api': 6.6.3 vue: 3.4.34 + vue-screen-utils@1.0.0-beta.13(vue@3.4.34): + dependencies: + vue: 3.4.34 + vue@3.4.34: dependencies: '@vue/compiler-dom': 3.4.34 diff --git a/typings/types.d.ts b/typings/types.d.ts index d60aed5..8597c65 100644 --- a/typings/types.d.ts +++ b/typings/types.d.ts @@ -35,6 +35,29 @@ interface UserSchema { company?: string } +type ServiceTag = + | 'PPTToVideo' + | 'TextToVideo' + | 'SparkChat15' + | 'SparkChat30' + | 'SparkChat35' + | 'TenTextToImg' + | 'TenImgToImg' + +interface ServiceType { + name: string + tag: ServiceTag +} + +interface ServiceBalance { + id: number + user_id: number + create_time: number + expire_time: number + remain_count: number + request_type: ServiceTag +} + interface DigitalHumanItem { user_id: number create_time: number