generated from duanshuwen/webapp-vue-frontend
feat: 完成生成等界面的编码
This commit is contained in:
2
.npmrc
2
.npmrc
@@ -1,3 +1,3 @@
|
||||
registry=https://r.npm.taobao.org/
|
||||
registry=https://registry.npmmirror.com/
|
||||
sass_binary_site=https://cdn.npmmirror.com/binaries/node-sass
|
||||
shamefully-hoist=true
|
||||
|
||||
5889
package-lock.json
generated
Normal file
5889
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,7 @@
|
||||
"vant": "^4.9.22",
|
||||
"vconsole": "^3.15.1",
|
||||
"vue": "^3.5.27",
|
||||
"vue-easy-lightbox": "^1.19.0",
|
||||
"vue-router": "^4.6.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
BIN
src/assets/images/back_left.png
Normal file
BIN
src/assets/images/back_left.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/assets/images/back_left_white.png
Normal file
BIN
src/assets/images/back_left_white.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 974 B |
BIN
src/assets/images/history_line.png
Normal file
BIN
src/assets/images/history_line.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
BIN
src/assets/images/history_line_white.png
Normal file
BIN
src/assets/images/history_line_white.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
@@ -10,6 +10,33 @@ const routes = [
|
||||
title: '首页', // 自动设置当前页面的标题
|
||||
keepAlive: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/generate',
|
||||
name: 'generate',
|
||||
component: () => import('@/views/generate/index.vue'),
|
||||
meta: {
|
||||
title: '生成中',
|
||||
keepAlive: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/history',
|
||||
name: 'history',
|
||||
component: () => import('@/views/history/index.vue'),
|
||||
meta: {
|
||||
title: '最近任务',
|
||||
keepAlive: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/prepicture',
|
||||
name: 'prepicture',
|
||||
component: () => import('@/views/prepicture/index.vue'),
|
||||
meta: {
|
||||
title: '预览图片',
|
||||
keepAlive: true
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
80
src/views/components/navBar.vue
Normal file
80
src/views/components/navBar.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<div :class="['top-nav', colorClass]">
|
||||
<img
|
||||
class="icon"
|
||||
:src="backSrc"
|
||||
alt="back"
|
||||
@click="emitBack"
|
||||
/>
|
||||
|
||||
<div class="nav-title">{{ title }}</div>
|
||||
|
||||
<img
|
||||
v-if="showHistory"
|
||||
class="icon-history"
|
||||
:src="historySrc"
|
||||
alt="history"
|
||||
@click="emitHistory"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import back from '@/assets/images/back_left.png';
|
||||
import backWhite from '@/assets/images/back_left_white.png';
|
||||
import history from '@/assets/images/history_line.png';
|
||||
import historyWhite from '@/assets/images/history_line_white.png';
|
||||
|
||||
const props = defineProps({
|
||||
title: { type: String, default: '' },
|
||||
color: { type: String, default: 'white' }, // 'white' or 'black'
|
||||
showHistory: { type: Boolean, default: true }
|
||||
});
|
||||
|
||||
const emit = defineEmits(['back', 'history']);
|
||||
|
||||
const emitBack = () => emit('back');
|
||||
const emitHistory = () => emit('history');
|
||||
|
||||
const backSrc = computed(() => (props.color === 'white' ? backWhite : back));
|
||||
const historySrc = computed(() => (props.color === 'white' ? historyWhite : history));
|
||||
const colorClass = computed(() => (props.color === 'white' ? 'nav-white' : 'nav-black'));
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.top-nav {
|
||||
position: absolute;
|
||||
top: 44px;
|
||||
left: 20px;
|
||||
right: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.top-nav .icon,
|
||||
.top-nav .icon-history {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.nav-white .nav-title {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.nav-black .nav-title {
|
||||
color: #000;
|
||||
}
|
||||
</style>
|
||||
189
src/views/generate/index.vue
Normal file
189
src/views/generate/index.vue
Normal file
@@ -0,0 +1,189 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<!-- 顶部栏 -->
|
||||
<NavBar title="生成中" color="white" :showHistory="true" @back="onBack" @history="onHistory" />
|
||||
|
||||
<!-- 中间内容 -->
|
||||
<div class="content">
|
||||
<div class="ai-text">AI</div>
|
||||
|
||||
<div class="desc">
|
||||
<div class="main">预计等待5~7分钟</div>
|
||||
<div class="sub">
|
||||
WHEE云处理中,特效生成需要一定时间,请耐心等待。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<div class="footer">
|
||||
<button class="btn primary" @click="handleGenerate">
|
||||
生成新的特效
|
||||
</button>
|
||||
|
||||
<button class="btn outline" @click="handleHistory">
|
||||
<span>查看生成记录</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router';
|
||||
import NavBar from '../components/navBar.vue';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const handleGenerate = () => {
|
||||
console.log('重新生成')
|
||||
|
||||
}
|
||||
|
||||
const handleHistory = () => {
|
||||
console.log('查看记录')
|
||||
router.push('/history');
|
||||
}
|
||||
|
||||
const onBack = () => {
|
||||
router.back();
|
||||
};
|
||||
|
||||
const onHistory = () => {
|
||||
handleHistory();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
height: 100vh;
|
||||
background: #000;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 中间 */
|
||||
.content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* AI 渐变字 */
|
||||
.ai-text {
|
||||
width: 100%;
|
||||
font-size: 88px;
|
||||
font-weight: bold;
|
||||
font-family: SF Pro, SF Pro;
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
text-stroke: 1px rgba(255,255,255,0.2);
|
||||
background: linear-gradient(45deg, #A6E0C6 0%, #7DBBF6 100%); -webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
-webkit-text-stroke: 1px rgba(255,255,255,0.2);
|
||||
animation: breathing 2s ease-in-out infinite;
|
||||
/* text-shadow: 0 0 20px rgba(110, 193, 255, 0.5); */
|
||||
}
|
||||
|
||||
@keyframes breathing {
|
||||
0% {
|
||||
opacity: 0.6;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
100% {
|
||||
opacity: 0.6;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
|
||||
/* 文案 */
|
||||
.desc {
|
||||
text-align: center;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.main {
|
||||
font-size: 20px;
|
||||
margin-bottom: 8px;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.sub {
|
||||
font-size: 12px;
|
||||
color: #888888;
|
||||
max-width: 260px;
|
||||
}
|
||||
|
||||
/* 底部按钮 */
|
||||
.footer {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
height: 52px;
|
||||
border-radius: 26px;
|
||||
font-size: 16px;
|
||||
margin-bottom: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
|
||||
/* 渐变按钮 */
|
||||
.primary {
|
||||
background: linear-gradient(90deg, #D9FBC8 0%, #8BE2FF 100%);
|
||||
color: #000;
|
||||
border: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 描边按钮 */
|
||||
.outline {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 52px;
|
||||
border-radius: 26px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
background: #000; /* 页面背景色 */
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 内层内容 */
|
||||
.outline::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
background: linear-gradient(90deg, #D9FBC8 0%, #8BE2FF 100%);
|
||||
padding: 1.5px; /* 控制边框粗细 */
|
||||
|
||||
/* 核心:挖空中间 */
|
||||
-webkit-mask:
|
||||
linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
}
|
||||
|
||||
/* 文字层 */
|
||||
.outline span {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
260
src/views/history/index.vue
Normal file
260
src/views/history/index.vue
Normal file
@@ -0,0 +1,260 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<!-- Header -->
|
||||
<NavBar title="最近任务" color="white" :showHistory="false" @back="onBack" />
|
||||
|
||||
<!-- 列表 -->
|
||||
<div class="list">
|
||||
<div
|
||||
v-for="item in list"
|
||||
:key="item.id"
|
||||
class="card"
|
||||
>
|
||||
<!-- 生成中 -->
|
||||
<template v-if="item.status === 'processing'">
|
||||
<div class="left processing-box">
|
||||
生成中
|
||||
</div>
|
||||
|
||||
<div class="right">
|
||||
<div class="name">{{ item.name }}</div>
|
||||
|
||||
<div class="progress">
|
||||
<div
|
||||
class="bar"
|
||||
:style="{ width: item.progress + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div class="status-text">生成中</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 已完成 -->
|
||||
<template v-else>
|
||||
<img class="thumb" :src="item.cover" />
|
||||
|
||||
<div class="right">
|
||||
<div class="name">{{ item.name }}</div>
|
||||
<div class="time">{{ item.time }}</div>
|
||||
</div>
|
||||
|
||||
<button class="view-btn" @click="lookPicture(item)">查看</button>
|
||||
</template>
|
||||
</div>
|
||||
<div class="tip">仅保留最近20天生成记录</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router';
|
||||
import NavBar from '../components/navBar.vue';
|
||||
|
||||
interface TaskItem {
|
||||
id: number
|
||||
name: string
|
||||
status: 'processing' | 'done'
|
||||
progress?: number
|
||||
cover?: string
|
||||
time?: string
|
||||
}
|
||||
|
||||
const list: TaskItem[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: '冬日情书',
|
||||
status: 'processing',
|
||||
progress: 40,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '无边戏水',
|
||||
status: 'done',
|
||||
cover: 'https://pic.rmb.bdstatic.com/bjh/news/1660587f950922830e980e276b00a912.jpeg',
|
||||
time: '2026-02-23 16:27:35',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '冬日情书',
|
||||
status: 'processing',
|
||||
progress: 40,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '无边戏水',
|
||||
status: 'done',
|
||||
cover: 'https://pic.rmb.bdstatic.com/bjh/news/1660587f950922830e980e276b00a912.jpeg',
|
||||
time: '2026-02-23 16:27:35',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: '冬日情书',
|
||||
status: 'processing',
|
||||
progress: 40,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: '无边戏水',
|
||||
status: 'done',
|
||||
cover: 'https://pic.rmb.bdstatic.com/bjh/news/1660587f950922830e980e276b00a912.jpeg',
|
||||
time: '2026-02-23 16:27:35',
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: '冬日情书',
|
||||
status: 'processing',
|
||||
progress: 40,
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: '无边戏水',
|
||||
status: 'done',
|
||||
cover: 'https://pic.rmb.bdstatic.com/bjh/news/1660587f950922830e980e276b00a912.jpeg',
|
||||
time: '2026-02-23 16:27:35',
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: '冬日情书',
|
||||
status: 'processing',
|
||||
progress: 40,
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: '无边戏水',
|
||||
status: 'done',
|
||||
cover: 'https://pic.rmb.bdstatic.com/bjh/news/1660587f950922830e980e276b00a912.jpeg',
|
||||
time: '2026-02-23 16:27:35',
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
name: '冬日情书',
|
||||
status: 'processing',
|
||||
progress: 40,
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
name: '无边戏水',
|
||||
status: 'done',
|
||||
cover: 'https://pic.rmb.bdstatic.com/bjh/news/1660587f950922830e980e276b00a912.jpeg',
|
||||
time: '2026-02-23 16:27:35',
|
||||
},
|
||||
]
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const onBack = () => {
|
||||
router.back();
|
||||
};
|
||||
|
||||
const lookPicture = (item: TaskItem) => {
|
||||
console.log('查看图片');
|
||||
const encoded = encodeURIComponent(JSON.stringify(item));
|
||||
router.push({ path: '/prepicture', query: { data: encoded } });
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #000;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 列表(放在 NavBar 下面并可滚动) */
|
||||
.list {
|
||||
padding: 16px;
|
||||
margin-top: 72px; /* 留出 NavBar 空间(56px header + 24px margin) */
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* 卡片 */
|
||||
.card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #1a1a1a;
|
||||
border-radius: 16px;
|
||||
padding: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* 左侧生成中 */
|
||||
.processing-box {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: 12px;
|
||||
background: #1f2a36;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #ccc;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 已完成缩略图 */
|
||||
.thumb {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: 12px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* 右侧 */
|
||||
.right {
|
||||
flex: 1;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* 进度条 */
|
||||
.progress {
|
||||
height: 4px;
|
||||
background: #333;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #6ec1ff, #b7f8c8);
|
||||
}
|
||||
|
||||
/* 状态 */
|
||||
.status-text {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
/* 时间 */
|
||||
.time {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
/* 查看按钮 */
|
||||
.view-btn {
|
||||
background: #2a2a2a;
|
||||
border: none;
|
||||
color: #fff;
|
||||
border-radius: 14px;
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 底部提示 */
|
||||
.tip {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -6,10 +6,7 @@
|
||||
|
||||
<div class="gradient-overlay"></div>
|
||||
|
||||
<div class="top-nav">
|
||||
<span class="icon">〈</span>
|
||||
<span class="icon-history">🕙</span>
|
||||
</div>
|
||||
<NavBar color="white" :showHistory="true" @back="onBack" @history="onHistory" />
|
||||
|
||||
<div class="content-layer">
|
||||
<div class="style-tabs">
|
||||
@@ -32,7 +29,7 @@
|
||||
<div class="quota-text">✨ 今日还可生成 <span>2</span> 张</div>
|
||||
|
||||
<div class="footer-section">
|
||||
<button class="generate-btn">立即生成</button>
|
||||
<button class="generate-btn" @click="generateAction">立即生成</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -40,6 +37,8 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import NavBar from '../components/navBar.vue';
|
||||
|
||||
// --- 测试数据 ---
|
||||
const styles = [
|
||||
@@ -67,6 +66,20 @@ const mockData = {
|
||||
const activeStyleId = ref('real');
|
||||
const activeSceneIndex = ref(0);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const onBack = () => {
|
||||
router.back();
|
||||
};
|
||||
|
||||
const onHistory = () => {
|
||||
router.push('/history');
|
||||
};
|
||||
|
||||
const generateAction = () => {
|
||||
router.push('/generate');
|
||||
}
|
||||
|
||||
// --- 计算属性 ---
|
||||
const currentScenes = computed(() => mockData[activeStyleId.value] || []);
|
||||
const activeScene = computed(() => currentScenes.value[activeSceneIndex.value] || {});
|
||||
@@ -108,28 +121,7 @@ const handleStyleChange = (id) => {
|
||||
background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.1) 70%, rgba(0, 0, 0, 0.7) 85%, #000 100%);
|
||||
}
|
||||
|
||||
/* 顶部导航 */
|
||||
.top-nav {
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
left: 20px;
|
||||
right: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.top-nav .icon {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.top-nav .icon-history {
|
||||
font-size: 22px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 内容布局层 */
|
||||
.content-layer {
|
||||
|
||||
210
src/views/prepicture/index.vue
Normal file
210
src/views/prepicture/index.vue
Normal file
@@ -0,0 +1,210 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<NavBar :title="title" color="white" :showHistory="false" @back="onBack" />
|
||||
|
||||
<div class="image-area" v-if="sceneData">
|
||||
<div
|
||||
class="img-wrapper"
|
||||
@click="showLightbox = true"
|
||||
>
|
||||
<img
|
||||
:src="imageUrl"
|
||||
:alt="title"
|
||||
class="scene-img"
|
||||
/>
|
||||
<div class="zoom-hint">
|
||||
<span>点击图片全屏预览</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-section" v-if="sceneData">
|
||||
<button class="save-btn" @click="handleSave">
|
||||
保存
|
||||
</button>
|
||||
|
||||
<p class="disclaimer">
|
||||
{{ disclaimerText }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<VueEasyLightbox
|
||||
:visible="showLightbox"
|
||||
:imgs="[imageUrl]"
|
||||
:index="0"
|
||||
@hide="handleHide"
|
||||
>
|
||||
<template #toolbar>
|
||||
<div class="lightbox-close" @click="handleHide">×</div>
|
||||
</template>
|
||||
</VueEasyLightbox>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import NavBar from '../components/navBar.vue';
|
||||
import { ref, computed } from 'vue';
|
||||
import VueEasyLightbox from 'vue-easy-lightbox';
|
||||
import 'vue-easy-lightbox/dist/external-css/vue-easy-lightbox.css'; // 导入其 CSS
|
||||
|
||||
const disclaimerText = ref('脑洞特效由 AI 随机生成,如未达预期敬请谅解。')
|
||||
|
||||
// sceneData only populated from route query; no defaults
|
||||
const sceneData = ref(null as null | Record<string, any>);
|
||||
const route = useRoute();
|
||||
if (route.query && route.query.data) {
|
||||
try {
|
||||
const parsed = JSON.parse(decodeURIComponent(route.query.data as string));
|
||||
if (parsed) sceneData.value = parsed;
|
||||
} catch (e) {
|
||||
console.warn('无法解析传入的预览数据', e);
|
||||
}
|
||||
}
|
||||
const title = computed(() => (sceneData.value ? sceneData.value.title || '' : ''));
|
||||
const imageUrl = computed(() => (sceneData.value ? sceneData.value.cover || sceneData.value.imageUrl || '' : ''));
|
||||
|
||||
|
||||
const router = useRouter();
|
||||
const onBack = () => {
|
||||
router.back();
|
||||
};
|
||||
|
||||
// --- 状态和逻辑 ---
|
||||
const showLightbox = ref(false);
|
||||
|
||||
const handleHide = () => {
|
||||
showLightbox.value = false;
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
console.log('保存图片:', sceneData.value ? (sceneData.value.title || sceneData.value.name) : '');
|
||||
alert('图片已保存至相册 (示例)');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* --- 布局样式 --- */
|
||||
.app-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
font-family: sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* --- 顶部导航 --- */
|
||||
.top-nav {
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
left: 20px;
|
||||
right: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
z-index: 10;
|
||||
}
|
||||
.top-nav .icon { font-size: 24px; font-weight: bold; cursor: pointer; }
|
||||
.top-nav .title { font-size: 18px; font-weight: 500; }
|
||||
.top-nav .right-placeholder { width: 24px; } /* 保持标题居中 */
|
||||
|
||||
/* --- 主预览区域 --- */
|
||||
.image-area {
|
||||
flex: 1; /* 占据中间所有剩余空间 */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #000;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.img-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 450px; /* 在平板和桌面端限制最大宽度 */
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.scene-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover; /* 裁剪填充,或 use contain to show whole image */
|
||||
border: none;
|
||||
}
|
||||
|
||||
.zoom-hint {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 16px;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
color: #ccc;
|
||||
pointer-events: none; /* 穿透点击 */
|
||||
}
|
||||
|
||||
/* --- 底部操作区域 --- */
|
||||
.footer-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 200px; /* 固定高度 */
|
||||
background-color: #000;
|
||||
padding: 0 30px;
|
||||
padding-bottom: env(safe-area-inset-bottom, 20px); /* 处理 iOS Home Bar */
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
border-radius: 30px;
|
||||
border: none;
|
||||
/* 还原渐变:从浅绿到淡蓝 */
|
||||
background: linear-gradient(90deg, #D9FBC8 0%, #8BE2FF 100%);
|
||||
color: #000;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 4px 15px rgba(160, 241, 234, 0.3);
|
||||
cursor: pointer;
|
||||
transition: transform 0.1s;
|
||||
}
|
||||
.save-btn:active { transform: scale(0.98); }
|
||||
|
||||
.disclaimer {
|
||||
font-size: 11px;
|
||||
color: #888;
|
||||
margin-top: 15px;
|
||||
text-align: center;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* --- Lightbox 自定义样式 --- */
|
||||
/* 自定义 Lightbox 的关闭按钮 */
|
||||
.lightbox-close {
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
right: 20px;
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
z-index: 10001;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 18px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user