Merge pull request #345 from whoismonay/main
mod(frontend): 管理员订阅/兑换码分组选择展示备注
This commit is contained in:
@@ -238,7 +238,30 @@
|
|||||||
v-model="generateForm.group_id"
|
v-model="generateForm.group_id"
|
||||||
:options="subscriptionGroupOptions"
|
:options="subscriptionGroupOptions"
|
||||||
:placeholder="t('admin.redeem.selectGroupPlaceholder')"
|
:placeholder="t('admin.redeem.selectGroupPlaceholder')"
|
||||||
/>
|
>
|
||||||
|
<template #selected="{ option }">
|
||||||
|
<GroupBadge
|
||||||
|
v-if="option"
|
||||||
|
:name="(option as unknown as GroupOption).label"
|
||||||
|
:platform="(option as unknown as GroupOption).platform"
|
||||||
|
:subscription-type="(option as unknown as GroupOption).subscriptionType"
|
||||||
|
:rate-multiplier="(option as unknown as GroupOption).rate"
|
||||||
|
/>
|
||||||
|
<span v-else class="text-gray-400">{{
|
||||||
|
t('admin.redeem.selectGroupPlaceholder')
|
||||||
|
}}</span>
|
||||||
|
</template>
|
||||||
|
<template #option="{ option, selected }">
|
||||||
|
<GroupOptionItem
|
||||||
|
:name="(option as unknown as GroupOption).label"
|
||||||
|
:platform="(option as unknown as GroupOption).platform"
|
||||||
|
:subscription-type="(option as unknown as GroupOption).subscriptionType"
|
||||||
|
:rate-multiplier="(option as unknown as GroupOption).rate"
|
||||||
|
:description="(option as unknown as GroupOption).description"
|
||||||
|
:selected="selected"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="input-label">{{ t('admin.redeem.validityDays') }}</label>
|
<label class="input-label">{{ t('admin.redeem.validityDays') }}</label>
|
||||||
@@ -370,7 +393,7 @@ import { useAppStore } from '@/stores/app'
|
|||||||
import { useClipboard } from '@/composables/useClipboard'
|
import { useClipboard } from '@/composables/useClipboard'
|
||||||
import { adminAPI } from '@/api/admin'
|
import { adminAPI } from '@/api/admin'
|
||||||
import { formatDateTime } from '@/utils/format'
|
import { formatDateTime } from '@/utils/format'
|
||||||
import type { RedeemCode, RedeemCodeType, Group } from '@/types'
|
import type { RedeemCode, RedeemCodeType, Group, GroupPlatform, SubscriptionType } from '@/types'
|
||||||
import type { Column } from '@/components/common/types'
|
import type { Column } from '@/components/common/types'
|
||||||
import AppLayout from '@/components/layout/AppLayout.vue'
|
import AppLayout from '@/components/layout/AppLayout.vue'
|
||||||
import TablePageLayout from '@/components/layout/TablePageLayout.vue'
|
import TablePageLayout from '@/components/layout/TablePageLayout.vue'
|
||||||
@@ -378,12 +401,23 @@ import DataTable from '@/components/common/DataTable.vue'
|
|||||||
import Pagination from '@/components/common/Pagination.vue'
|
import Pagination from '@/components/common/Pagination.vue'
|
||||||
import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
|
import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
|
||||||
import Select from '@/components/common/Select.vue'
|
import Select from '@/components/common/Select.vue'
|
||||||
|
import GroupBadge from '@/components/common/GroupBadge.vue'
|
||||||
|
import GroupOptionItem from '@/components/common/GroupOptionItem.vue'
|
||||||
import Icon from '@/components/icons/Icon.vue'
|
import Icon from '@/components/icons/Icon.vue'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
const { copyToClipboard: clipboardCopy } = useClipboard()
|
const { copyToClipboard: clipboardCopy } = useClipboard()
|
||||||
|
|
||||||
|
interface GroupOption {
|
||||||
|
value: number
|
||||||
|
label: string
|
||||||
|
description: string | null
|
||||||
|
platform: GroupPlatform
|
||||||
|
subscriptionType: SubscriptionType
|
||||||
|
rate: number
|
||||||
|
}
|
||||||
|
|
||||||
const showGenerateDialog = ref(false)
|
const showGenerateDialog = ref(false)
|
||||||
const showResultDialog = ref(false)
|
const showResultDialog = ref(false)
|
||||||
const generatedCodes = ref<RedeemCode[]>([])
|
const generatedCodes = ref<RedeemCode[]>([])
|
||||||
@@ -395,7 +429,11 @@ const subscriptionGroupOptions = computed(() => {
|
|||||||
.filter((g) => g.subscription_type === 'subscription')
|
.filter((g) => g.subscription_type === 'subscription')
|
||||||
.map((g) => ({
|
.map((g) => ({
|
||||||
value: g.id,
|
value: g.id,
|
||||||
label: g.name
|
label: g.name,
|
||||||
|
description: g.description,
|
||||||
|
platform: g.platform,
|
||||||
|
subscriptionType: g.subscription_type,
|
||||||
|
rate: g.rate_multiplier
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -466,7 +466,28 @@
|
|||||||
v-model="assignForm.group_id"
|
v-model="assignForm.group_id"
|
||||||
:options="subscriptionGroupOptions"
|
:options="subscriptionGroupOptions"
|
||||||
:placeholder="t('admin.subscriptions.selectGroup')"
|
:placeholder="t('admin.subscriptions.selectGroup')"
|
||||||
/>
|
>
|
||||||
|
<template #selected="{ option }">
|
||||||
|
<GroupBadge
|
||||||
|
v-if="option"
|
||||||
|
:name="(option as unknown as GroupOption).label"
|
||||||
|
:platform="(option as unknown as GroupOption).platform"
|
||||||
|
:subscription-type="(option as unknown as GroupOption).subscriptionType"
|
||||||
|
:rate-multiplier="(option as unknown as GroupOption).rate"
|
||||||
|
/>
|
||||||
|
<span v-else class="text-gray-400">{{ t('admin.subscriptions.selectGroup') }}</span>
|
||||||
|
</template>
|
||||||
|
<template #option="{ option, selected }">
|
||||||
|
<GroupOptionItem
|
||||||
|
:name="(option as unknown as GroupOption).label"
|
||||||
|
:platform="(option as unknown as GroupOption).platform"
|
||||||
|
:subscription-type="(option as unknown as GroupOption).subscriptionType"
|
||||||
|
:rate-multiplier="(option as unknown as GroupOption).rate"
|
||||||
|
:description="(option as unknown as GroupOption).description"
|
||||||
|
:selected="selected"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Select>
|
||||||
<p class="input-hint">{{ t('admin.subscriptions.groupHint') }}</p>
|
<p class="input-hint">{{ t('admin.subscriptions.groupHint') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -599,7 +620,7 @@ import { ref, reactive, computed, onMounted, onUnmounted } from 'vue'
|
|||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useAppStore } from '@/stores/app'
|
import { useAppStore } from '@/stores/app'
|
||||||
import { adminAPI } from '@/api/admin'
|
import { adminAPI } from '@/api/admin'
|
||||||
import type { UserSubscription, Group } from '@/types'
|
import type { UserSubscription, Group, GroupPlatform, SubscriptionType } from '@/types'
|
||||||
import type { SimpleUser } from '@/api/admin/usage'
|
import type { SimpleUser } from '@/api/admin/usage'
|
||||||
import type { Column } from '@/components/common/types'
|
import type { Column } from '@/components/common/types'
|
||||||
import { formatDateOnly } from '@/utils/format'
|
import { formatDateOnly } from '@/utils/format'
|
||||||
@@ -612,11 +633,21 @@ import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
|
|||||||
import EmptyState from '@/components/common/EmptyState.vue'
|
import EmptyState from '@/components/common/EmptyState.vue'
|
||||||
import Select from '@/components/common/Select.vue'
|
import Select from '@/components/common/Select.vue'
|
||||||
import GroupBadge from '@/components/common/GroupBadge.vue'
|
import GroupBadge from '@/components/common/GroupBadge.vue'
|
||||||
|
import GroupOptionItem from '@/components/common/GroupOptionItem.vue'
|
||||||
import Icon from '@/components/icons/Icon.vue'
|
import Icon from '@/components/icons/Icon.vue'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
|
|
||||||
|
interface GroupOption {
|
||||||
|
value: number
|
||||||
|
label: string
|
||||||
|
description: string | null
|
||||||
|
platform: GroupPlatform
|
||||||
|
subscriptionType: SubscriptionType
|
||||||
|
rate: number
|
||||||
|
}
|
||||||
|
|
||||||
// User column display mode: 'email' or 'username'
|
// User column display mode: 'email' or 'username'
|
||||||
const userColumnMode = ref<'email' | 'username'>('email')
|
const userColumnMode = ref<'email' | 'username'>('email')
|
||||||
const USER_COLUMN_MODE_KEY = 'subscription-user-column-mode'
|
const USER_COLUMN_MODE_KEY = 'subscription-user-column-mode'
|
||||||
@@ -792,7 +823,14 @@ const groupOptions = computed(() => [
|
|||||||
const subscriptionGroupOptions = computed(() =>
|
const subscriptionGroupOptions = computed(() =>
|
||||||
groups.value
|
groups.value
|
||||||
.filter((g) => g.subscription_type === 'subscription' && g.status === 'active')
|
.filter((g) => g.subscription_type === 'subscription' && g.status === 'active')
|
||||||
.map((g) => ({ value: g.id, label: g.name }))
|
.map((g) => ({
|
||||||
|
value: g.id,
|
||||||
|
label: g.name,
|
||||||
|
description: g.description,
|
||||||
|
platform: g.platform,
|
||||||
|
subscriptionType: g.subscription_type,
|
||||||
|
rate: g.rate_multiplier
|
||||||
|
}))
|
||||||
)
|
)
|
||||||
|
|
||||||
const applyFilters = () => {
|
const applyFilters = () => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineConfig, Plugin } from 'vite'
|
import { defineConfig, loadEnv, Plugin } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
import checker from 'vite-plugin-checker'
|
import checker from 'vite-plugin-checker'
|
||||||
import { resolve } from 'path'
|
import { resolve } from 'path'
|
||||||
@@ -7,9 +7,7 @@ import { resolve } from 'path'
|
|||||||
* Vite 插件:开发模式下注入公开配置到 index.html
|
* Vite 插件:开发模式下注入公开配置到 index.html
|
||||||
* 与生产模式的后端注入行为保持一致,消除闪烁
|
* 与生产模式的后端注入行为保持一致,消除闪烁
|
||||||
*/
|
*/
|
||||||
function injectPublicSettings(): Plugin {
|
function injectPublicSettings(backendUrl: string): Plugin {
|
||||||
const backendUrl = process.env.VITE_DEV_PROXY_TARGET || 'http://localhost:8080'
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: 'inject-public-settings',
|
name: 'inject-public-settings',
|
||||||
transformIndexHtml: {
|
transformIndexHtml: {
|
||||||
@@ -35,15 +33,21 @@ function injectPublicSettings(): Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig(({ mode }) => {
|
||||||
plugins: [
|
// 加载环境变量
|
||||||
vue(),
|
const env = loadEnv(mode, process.cwd(), '')
|
||||||
checker({
|
const backendUrl = env.VITE_DEV_PROXY_TARGET || 'http://localhost:8080'
|
||||||
typescript: true,
|
const devPort = Number(env.VITE_DEV_PORT || 3000)
|
||||||
vueTsc: true
|
|
||||||
}),
|
return {
|
||||||
injectPublicSettings()
|
plugins: [
|
||||||
],
|
vue(),
|
||||||
|
checker({
|
||||||
|
typescript: true,
|
||||||
|
vueTsc: true
|
||||||
|
}),
|
||||||
|
injectPublicSettings(backendUrl)
|
||||||
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': resolve(__dirname, 'src'),
|
'@': resolve(__dirname, 'src'),
|
||||||
@@ -102,17 +106,18 @@ export default defineConfig({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
port: Number(process.env.VITE_DEV_PORT || 3000),
|
port: devPort,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: process.env.VITE_DEV_PROXY_TARGET || 'http://localhost:8080',
|
target: backendUrl,
|
||||||
changeOrigin: true
|
changeOrigin: true
|
||||||
},
|
},
|
||||||
'/setup': {
|
'/setup': {
|
||||||
target: process.env.VITE_DEV_PROXY_TARGET || 'http://localhost:8080',
|
target: backendUrl,
|
||||||
changeOrigin: true
|
changeOrigin: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user