feat: 移动端兼容优化
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { Check, CircleCheck, CircleClose, Close, Delete, DocumentCopy, Edit, Files, Hide, Key, Plus, PriceTag, Reading, Refresh, Timer, View } from '@element-plus/icons-vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import {
|
||||
createToken,
|
||||
@@ -46,6 +46,13 @@ const currentFormData = ref<TokenFormData>({
|
||||
});
|
||||
const router = useRouter();
|
||||
|
||||
// 移动端检测
|
||||
const isMobile = ref(false);
|
||||
|
||||
function checkMobile() {
|
||||
isMobile.value = window.innerWidth < 768;
|
||||
}
|
||||
|
||||
// 防抖和节流控制
|
||||
const operatingTokenId = ref<string>('');
|
||||
|
||||
@@ -341,8 +348,14 @@ function isOperating(tokenId: string) {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
checkMobile();
|
||||
window.addEventListener('resize', checkMobile);
|
||||
await fetchTokenList();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', checkMobile);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -384,7 +397,9 @@ onMounted(async () => {
|
||||
</div>
|
||||
|
||||
<!-- Token列表 -->
|
||||
<div v-if="hasTokens" class="token-table-wrapper">
|
||||
<div v-if="hasTokens">
|
||||
<!-- 桌面端表格 -->
|
||||
<div v-if="!isMobile" class="token-table-wrapper">
|
||||
<el-table
|
||||
:data="tokenList"
|
||||
stripe
|
||||
@@ -586,6 +601,101 @@ onMounted(async () => {
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<!-- 移动端卡片列表 -->
|
||||
<div v-else class="mobile-token-list">
|
||||
<div v-for="token in tokenList" :key="token.id" class="mobile-token-card">
|
||||
<div class="mobile-card-header">
|
||||
<div class="mobile-card-title">
|
||||
<el-icon class="title-icon"><PriceTag /></el-icon>
|
||||
<span>{{ token.name }}</span>
|
||||
</div>
|
||||
<el-tag :type="token.isDisabled ? 'danger' : 'success'" size="small">
|
||||
{{ token.isDisabled ? '已禁用' : '启用中' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<div class="mobile-card-body">
|
||||
<!-- API密钥 -->
|
||||
<div class="mobile-info-item">
|
||||
<div class="mobile-info-label">API密钥</div>
|
||||
<div class="mobile-key-row">
|
||||
<span class="mobile-key-text">
|
||||
{{ token.showKey ? token.apiKey : '•••••••••••••••••••••' }}
|
||||
</span>
|
||||
<div class="mobile-key-actions">
|
||||
<el-button
|
||||
:icon="token.showKey ? Hide : View"
|
||||
size="small"
|
||||
text
|
||||
@click="toggleKeyVisibility(token)"
|
||||
/>
|
||||
<el-button
|
||||
:icon="DocumentCopy"
|
||||
size="small"
|
||||
type="primary"
|
||||
text
|
||||
@click="copyApiKey(token.apiKey, token.name)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 配额信息 -->
|
||||
<div v-if="token.premiumQuotaLimit" class="mobile-info-item">
|
||||
<div class="mobile-info-label">配额使用</div>
|
||||
<div class="mobile-quota-info">
|
||||
<el-progress
|
||||
:percentage="getQuotaPercentage(token.premiumUsedQuota, token.premiumQuotaLimit)"
|
||||
:color="getQuotaColor(getQuotaPercentage(token.premiumUsedQuota, token.premiumQuotaLimit))"
|
||||
:stroke-width="8"
|
||||
/>
|
||||
<div class="mobile-quota-text">
|
||||
{{ formatQuota(token.premiumUsedQuota) }} / {{ formatQuota(token.premiumQuotaLimit) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 过期时间 -->
|
||||
<div v-if="token.expireTime" class="mobile-info-item">
|
||||
<div class="mobile-info-label">过期时间</div>
|
||||
<div class="mobile-info-value">{{ formatDateTime(token.expireTime) }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 创建时间 -->
|
||||
<div class="mobile-info-item">
|
||||
<div class="mobile-info-label">创建时间</div>
|
||||
<div class="mobile-info-value">{{ formatDateTime(token.creationTime) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="mobile-card-actions">
|
||||
<el-button size="small" :icon="Edit" @click="showEditDialog(token)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
:type="token.isDisabled ? 'success' : 'warning'"
|
||||
:icon="token.isDisabled ? Check : Close"
|
||||
:loading="isOperating(token.id)"
|
||||
@click="handleToggle(token)"
|
||||
>
|
||||
{{ token.isDisabled ? '启用' : '禁用' }}
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="danger"
|
||||
:icon="Delete"
|
||||
:loading="isOperating(token.id)"
|
||||
@click="handleDelete(token)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div v-if="totalCount > 0" class="pagination-container">
|
||||
@@ -1170,5 +1280,114 @@ onMounted(async () => {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 移动端卡片列表 */
|
||||
.mobile-token-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.mobile-token-card {
|
||||
background: #fff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.mobile-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.mobile-card-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
|
||||
.title-icon {
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-card-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.mobile-info-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.mobile-info-label {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.mobile-info-value {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.mobile-key-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
background: #f5f7fa;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.mobile-key-text {
|
||||
flex: 1;
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
font-family: monospace;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.mobile-key-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.mobile-quota-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.mobile-quota-text {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mobile-card-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
|
||||
.el-button {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { ChatLineRound, List, Refresh, Search } from '@element-plus/icons-vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import { getRechargeLog } from '@/api/model/index.ts';
|
||||
import { isUserVip } from '@/utils/user.ts';
|
||||
|
||||
@@ -26,6 +26,13 @@ const pageSize = ref(10);
|
||||
const showWechatFullscreen = ref(false);
|
||||
const showWxGroupFullscreen = ref(false);
|
||||
|
||||
// 移动端检测
|
||||
const isMobile = ref(false);
|
||||
|
||||
function checkMobile() {
|
||||
isMobile.value = window.innerWidth < 768;
|
||||
}
|
||||
|
||||
const wxSrc = computed(
|
||||
() => `/src/assets/images/wx.png`,
|
||||
);
|
||||
@@ -148,8 +155,14 @@ const showPagination = computed(() => {
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
checkMobile();
|
||||
window.addEventListener('resize', checkMobile);
|
||||
fetchRechargeLog();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', checkMobile);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
|
||||
interface TokenFormData {
|
||||
id?: string;
|
||||
@@ -43,6 +43,22 @@ const submitting = ref(false);
|
||||
const neverExpire = ref(false); // 永不过期开关
|
||||
const unlimitedQuota = ref(false); // 无限制额度开关
|
||||
|
||||
// 移动端检测
|
||||
const isMobile = ref(false);
|
||||
|
||||
function checkMobile() {
|
||||
isMobile.value = window.innerWidth < 768;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
checkMobile();
|
||||
window.addEventListener('resize', checkMobile);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', checkMobile);
|
||||
});
|
||||
|
||||
const quotaUnitOptions = [
|
||||
{ label: '个', value: '个', multiplier: 1 },
|
||||
{ label: '十', value: '十', multiplier: 10 },
|
||||
@@ -180,12 +196,13 @@ const dialogTitle = computed(() => props.mode === 'create' ? '新增 API密钥'
|
||||
<el-dialog
|
||||
:model-value="visible"
|
||||
:title="dialogTitle"
|
||||
width="540px"
|
||||
:width="isMobile ? '95%' : '540px'"
|
||||
:fullscreen="isMobile"
|
||||
:close-on-click-modal="false"
|
||||
:show-close="!submitting"
|
||||
@close="handleClose"
|
||||
>
|
||||
<el-form :model="localFormData" label-width="110px" label-position="right">
|
||||
<el-form :model="localFormData" :label-width="isMobile ? '100%' : '110px'" :label-position="isMobile ? 'top' : 'right'">
|
||||
<el-form-item label="API密钥名称" required>
|
||||
<el-input
|
||||
v-model="localFormData.name"
|
||||
|
||||
@@ -194,7 +194,7 @@ function toggleMobileMenu() {
|
||||
v-model="mobileMenuVisible"
|
||||
direction="rtl"
|
||||
:size="280"
|
||||
:show-close="false"
|
||||
:close-on-click-modal="true"
|
||||
class="mobile-drawer"
|
||||
>
|
||||
<template #header>
|
||||
|
||||
@@ -489,11 +489,19 @@ watch(dateRange, () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Download Button Overlay -->
|
||||
<div v-if="currentTask.storeUrl" class="absolute bottom-4 right-4 flex gap-2 opacity-0 group-hover:opacity-100 transition-opacity z-10">
|
||||
<!-- Desktop Button Overlay -->
|
||||
<div v-if="currentTask.storeUrl" class="hidden md:flex absolute bottom-4 right-4 gap-2 opacity-0 group-hover:opacity-100 transition-opacity z-10">
|
||||
<el-button circle type="primary" :icon="ZoomIn" @click="handlePreview(currentTask.storeUrl)" />
|
||||
<el-button circle type="primary" :icon="Download" @click="downloadImage(currentTask.storeUrl)" />
|
||||
</div>
|
||||
|
||||
<!-- Mobile Button Overlay -->
|
||||
<div v-if="currentTask.storeUrl" class="md:hidden absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/70 to-transparent p-3 z-10">
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<el-button circle type="primary" :icon="ZoomIn" @click="handlePreview(currentTask.storeUrl)" />
|
||||
<el-button circle type="primary" :icon="Download" @click="downloadImage(currentTask.storeUrl)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Info -->
|
||||
|
||||
@@ -555,11 +555,19 @@ watch([() => searchForm.TaskStatus, () => searchForm.PublishStatus, dateRange],
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Download Button Overlay -->
|
||||
<div v-if="currentTask.storeUrl" class="absolute bottom-4 right-4 flex gap-2 opacity-0 group-hover:opacity-100 transition-opacity z-10">
|
||||
<!-- Desktop Button Overlay -->
|
||||
<div v-if="currentTask.storeUrl" class="hidden md:flex absolute bottom-4 right-4 gap-2 opacity-0 group-hover:opacity-100 transition-opacity z-10">
|
||||
<el-button circle type="primary" :icon="ZoomIn" @click="handlePreview(currentTask.storeUrl)" />
|
||||
<el-button circle type="primary" :icon="Download" @click="downloadImage(currentTask.storeUrl)" />
|
||||
</div>
|
||||
|
||||
<!-- Mobile Button Overlay -->
|
||||
<div v-if="currentTask.storeUrl" class="md:hidden absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/70 to-transparent p-3 z-10">
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<el-button circle type="primary" :icon="ZoomIn" @click="handlePreview(currentTask.storeUrl)" />
|
||||
<el-button circle type="primary" :icon="Download" @click="downloadImage(currentTask.storeUrl)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Info -->
|
||||
@@ -637,28 +645,21 @@ watch([() => searchForm.TaskStatus, () => searchForm.PublishStatus, dateRange],
|
||||
<el-tag size="small" type="info" effect="plain">未发布</el-tag>
|
||||
</div>
|
||||
|
||||
<div v-if="currentTask.taskStatus === 'Success'" class="pt-2 space-y-2">
|
||||
<div class="grid grid-cols-1 gap-1">
|
||||
<!-- 操作按钮 - 所有状态都可以使用提示词 -->
|
||||
<div class="pt-2 space-y-2">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
:icon="MagicStick"
|
||||
@click="$emit('use-prompt', currentTask.prompt)"
|
||||
class="w-full"
|
||||
@click="$emit('use-prompt', currentTask.prompt); dialogVisible = false"
|
||||
>
|
||||
使用提示词
|
||||
</el-button>
|
||||
|
||||
<!-- 成功状态才显示发布按钮 -->
|
||||
<el-button
|
||||
v-if="false"
|
||||
type="primary"
|
||||
plain
|
||||
:icon="Picture"
|
||||
@click="$emit('use-reference', currentTask.storeUrl)"
|
||||
>
|
||||
做参考图
|
||||
</el-button>
|
||||
</div>
|
||||
<el-button
|
||||
v-if="currentTask.publishStatus === 'Unpublished'"
|
||||
v-if="currentTask.taskStatus === 'Success' && currentTask.publishStatus === 'Unpublished'"
|
||||
type="success"
|
||||
class="w-full"
|
||||
:icon="Share"
|
||||
@@ -666,7 +667,7 @@ watch([() => searchForm.TaskStatus, () => searchForm.PublishStatus, dateRange],
|
||||
>
|
||||
发布到广场
|
||||
</el-button>
|
||||
<el-button v-else type="info" disabled class="w-full">
|
||||
<el-button v-else-if="currentTask.taskStatus === 'Success'" type="info" disabled class="w-full">
|
||||
已发布
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
@@ -129,7 +129,7 @@ async function handleDownload() {
|
||||
</div>
|
||||
|
||||
<!-- Mobile Actions Bar -->
|
||||
<div v-if="task.taskStatus === 'Success'" class="absolute bottom-0 left-0 right-0 md:hidden bg-gradient-to-t from-black/70 to-transparent p-2 z-20">
|
||||
<div class="absolute bottom-0 left-0 right-0 md:hidden bg-gradient-to-t from-black/70 to-transparent p-2 z-20">
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<el-button
|
||||
type="primary"
|
||||
@@ -140,6 +140,7 @@ async function handleDownload() {
|
||||
>
|
||||
查看详情
|
||||
</el-button>
|
||||
<template v-if="task.taskStatus === 'Success'">
|
||||
<el-button circle size="small" :icon="ZoomIn" @click.stop="handlePreview" />
|
||||
<el-button circle size="small" :icon="Download" @click.stop="handleDownload" />
|
||||
<el-button
|
||||
@@ -150,6 +151,7 @@ async function handleDownload() {
|
||||
:icon="Share"
|
||||
@click.stop="$emit('publish', task)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user