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
+ })
+}
-
+
-
- 本账号数字人
-
{
-
+
@@ -285,17 +424,31 @@ const setUserStatus = (uid: number, is_verified: boolean) => {
{
+
+
+ {{ row.username }}
+
+ {{ row.id === loginState.user.id ? ' (本账号)' : '' }}
+
+
+
-
+
{{ row.sex === 0 ? '' : row.sex === 1 ? '男' : '女' }}
-
+
@@ -337,9 +510,16 @@ const setUserStatus = (uid: number, is_verified: boolean) => {
-
+
@@ -353,11 +533,282 @@ const setUserStatus = (uid: number, is_verified: boolean) => {
variant="ghost"
@click="closeSlide"
/>
- 数字人授权管理
- {{ viewingUser?.username }} (UID:{{ viewingUser?.id }})
+ 服务和用量管理
+
+ {{ viewingUser?.username }} (UID:{{ viewingUser?.id }})
+
-
+
+
+
+
+
+ {{
+ systemServices.find((s) => s.tag === row.tag)?.name || row.tag
+ }}
+
+
+
+
+ 未开通
+
+
+ 已过期
+
+
+ 生效中
+
+
+
+
+
+ {{
+ !!getBalanceByTag(row.tag)
+ ? dayjs(
+ getBalanceByTag(row.tag)!.create_time * 1000
+ ).format('YYYY-MM-DD HH:mm:ss')
+ : '未开通'
+ }}
+
+
+
+
+
+ {{
+ !!getBalanceByTag(row.tag)
+ ? dayjs(
+ getBalanceByTag(row.tag)!.expire_time * 1000
+ ).format('YYYY-MM-DD HH:mm:ss')
+ : '未开通'
+ }}
+
+
+
+
+
+ {{ getBalanceByTag(row.tag)?.remain_count || 0 }}
+
+
+
+
+ {
+ isActivateBalance = true
+ userBalanceEditing = true
+ userBalanceState.request_type = row.tag
+ }
+ "
+ >
+ 开通
+
+ {
+ isActivateBalance = false
+ userBalanceEditing = true
+ userBalanceState.request_type = row.tag
+ }
+ "
+ >
+ 延续
+
+ {
+ isActivateBalance = false
+ userBalanceEditing = true
+ userBalanceState.request_type = row.tag
+ userBalanceState.expire_time =
+ getBalanceByTag(row.tag)!.expire_time * 1000
+ userBalanceState.remain_count = getBalanceByTag(row.tag)!.remain_count
+ }
+ "
+ >
+ 更新
+
+
+
+
+
+
+
+
+
+
+ {{ isActivateBalance ? '开通' : '续期' }}
+ {{
+ systemServices.find(
+ (s) => s.tag === userBalanceState.request_type
+ )?.name || userBalanceState.request_type
+ }}
+ 服务
+
+
+
+ {{ viewingUser!.username }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 取消
+
+
+ {{ isActivateBalance ? '开通' : '续期' }}
+
+
+
+
+
+
+
+
+
+
{
icon="tabler:cancel"
size="xs"
variant="ghost"
- @click="revokeDigitalHuman(viewingUser?.id || 0, row.digital_human_id)"
+ @click="
+ revokeDigitalHuman(viewingUser?.id || 0, row.digital_human_id)
+ "
>
撤销授权
-
+
+
+ 新增授权
+
{
{
-
\ 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