feat: 接口交互的调整

This commit is contained in:
2026-04-06 17:08:06 +08:00
parent ba3a1ba894
commit 3135957dda
9 changed files with 300 additions and 205 deletions

View File

@@ -14,7 +14,9 @@ const KeepAliveList = ref([])
router.beforeEach((to, from, next) => {
const keepAlive = to?.meta?.keepAlive
Session.set('token', to.query.token)
if (to.query.token) {
Session.set('token', to.query.token)
}
if (!router.hasRoute(to.name)) {
router.push('/home')

View File

@@ -1,29 +1,29 @@
/* eslint-disable */
// @ts-ignore
import request from '@common/ajax';
import type { Response } from './types';
import request from '@common/ajax'
import type { Response } from './types'
export interface GeneratorPhotoTask {
pageNum: number;
pageSize: number;
taskStatus: string;
pageNum: number
pageSize: number
taskStatus?: string
}
export interface GeneratorPhotoTaskListItem {
records: PhotoTaskRecordsItem[];
records: PhotoTaskRecordsItem[]
}
export interface PhotoTaskRecordsItem {
sceneName: string;
createTime: string;
taskStatus: string;
generatorPhoto: string;
sceneName: string
createTime: string
taskStatus: string
generatorPhoto: string
}
export const generatorPhotoTaskList = (params: GeneratorPhotoTask) => {
return request<Response<GeneratorPhotoTaskListItem[]>>({
return request<Response<GeneratorPhotoTaskListItem>>({
url: '/aigc/generatorPhotoTaskList',
method: 'post',
params,
});
};
params
})
}

View File

@@ -3,7 +3,8 @@ import { tansParams } from '@utils/tansParams'
import { getAccessToken } from '@/constant/token'
// 获取.env中的服务地址
const { VITE_BASE_API, VITE_ENV } = import.meta.env
const VITE_BASE_API = (import.meta.env.VITE_BASE_API || '').trim()
const VITE_ENV = (import.meta.env.VITE_ENV || '').trim()
console.log('🚀 ~ VITE_ENV:', VITE_ENV)
console.log('🚀 ~ VITE_BASE_API:', VITE_BASE_API)
@@ -18,21 +19,31 @@ const instance = axios.create({
// 添加拦截器
instance.interceptors.request.use((config) => {
const token = getAccessToken();
config.headers['Authorization'] = `Bearer ${token}`
const token = getAccessToken()
console.log('🚀 ~ Request Token:', token)
if (token) {
// 兼容不同版本的 axios 设置 header
config.headers = config.headers || {}
config.headers.Authorization = `Bearer ${token}`
}
console.log('🚀 ~ Final Headers:', config.headers)
// get请求映射params参数
if (config.method === 'get' && config.params) {
const method = (config.method || '').toLowerCase()
if (method === 'get' && config.params) {
let url = config.url + '?' + tansParams(config.params)
console.log('🚀 ~ url:', url)
url = url.slice(0, -1)
config.params = {}
config.url = url
}
// post/put/patch 请求将 params 映射到 bodydata
if ((config.method === 'post' || config.method === 'put' || config.method === 'patch') && config.params) {
if (
(method === 'post' || method === 'put' || method === 'patch') &&
config.params
) {
config.data = config.params
config.params = {}
}
@@ -43,9 +54,16 @@ instance.interceptors.request.use((config) => {
// 添加响应拦截器
instance.interceptors.response.use(
(res) => {
return Promise.resolve(res.data.data)
console.log('🚀 ~ res:', res)
if (res.data.code === 0) {
return Promise.resolve(res.data.data)
} else {
console.error('业务报错:', res.data.msg)
return Promise.reject(res.data)
}
},
(error) => {
console.error('网络或服务器报错:', error)
return Promise.reject(error)
}
)

View File

@@ -1,7 +1,7 @@
import { Session } from '@/utils/storage'
export const getAccessToken = () => {
const token = Session.getToken()
// const token = 'H7rzcCtsvPqwc0ecDnBT9mlhOqHsWDyS6nXkRjnd1oAgOQqlEhsCy4OZPQ8YyVBj57pmNpwSXJYcd6Ox4YB-W1pHr4aTM9_d6nYJ3OrbRB9f0J7kP9FbbviN609nGO0m'
return token;
}
export const getAccessToken = () => {
const token = Session.getToken()
// const token = 'AqmeApe592cOm__Xx8B8NAwJuS_3l8T9GLq9jYqxGSPojQUjmMLbgkG-h5OijP6OdTxwtFm-4ZlShqKYS_pr1OJ1ItZkbRKZjGZh0BIokJ0QIxcOkMQOAf9_XudBedvT'
return token
}

View File

@@ -44,13 +44,24 @@ export const Local = {
export const Session = {
// 设置临时缓存
set(key: string, val: any) {
window.sessionStorage.setItem(key, JSON.stringify(val))
if (val === undefined || val === null) {
window.sessionStorage.removeItem(key)
} else {
window.sessionStorage.setItem(key, JSON.stringify(val))
}
},
// 获取临时缓存
get(key: string) {
let json = <string>window.sessionStorage.getItem(key)
return JSON.parse(json)
if (!json || json === 'undefined' || json === 'null') {
return null
}
try {
return JSON.parse(json)
} catch (e) {
return json
}
},
// 移除临时缓存

View File

@@ -1,7 +1,13 @@
<template>
<div class="page">
<!-- 顶部栏 -->
<TopNavBar title="生成中" color="white" :showHistory="true" @back="onBack" @history="onHistory" />
<TopNavBar
title="生成中"
color="white"
:showHistory="true"
@back="onBack"
@history="onHistory"
/>
<!-- 中间内容 -->
<div class="content">
@@ -9,17 +15,13 @@
<div class="desc">
<div class="main">预计等待5~7分钟</div>
<div class="sub">
WHEE云处理中特效生成需要一定时间请耐心等待
</div>
<div class="sub">WHEE云处理中特效生成需要一定时间请耐心等待</div>
</div>
</div>
<!-- 底部按钮 -->
<div class="footer">
<button class="btn primary" @click="handleGenerate">
生成新的特效
</button>
<button class="btn primary" @click="handleGenerate">生成新的特效</button>
<button class="btn outline" @click="handleHistory">
<span>查看生成记录</span>
@@ -28,30 +30,30 @@
</div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router';
<script setup lang="ts" name="generate">
import { useRouter } from 'vue-router'
// @ts-ignore
import TopNavBar from '../components/TopNavBar.vue';
import TopNavBar from '../components/TopNavBar.vue'
const router = useRouter();
const router = useRouter()
const handleGenerate = () => {
console.log('重新生成')
router.back()
}
const handleHistory = () => {
console.log('查看记录')
router.push('/history');
router.push('/history')
}
const onBack = () => {
router.back();
};
router.back()
}
const onHistory = () => {
handleHistory();
};
handleHistory()
}
</script>
<style scoped>
@@ -86,7 +88,7 @@ const onHistory = () => {
text-align: center;
font-style: italic;
text-stroke: 1px rgba(255, 255, 255, 0.2);
background: linear-gradient(45deg, #A6E0C6 0%, #7DBBF6 100%);
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);
@@ -120,7 +122,7 @@ const onHistory = () => {
.main {
font-size: 20px;
margin-bottom: 8px;
color: #FFFFFF;
color: #ffffff;
}
.sub {
@@ -149,7 +151,7 @@ const onHistory = () => {
/* 渐变按钮 */
.primary {
background: linear-gradient(90deg, #D9FBC8 0%, #8BE2FF 100%);
background: linear-gradient(90deg, #d9fbc8 0%, #8be2ff 100%);
color: #000;
border: none;
font-weight: 500;
@@ -170,18 +172,16 @@ const onHistory = () => {
/* 内层内容 */
.outline::before {
content: "";
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
background: linear-gradient(90deg, #D9FBC8 0%, #8BE2FF 100%);
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: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
}
@@ -192,4 +192,4 @@ const onHistory = () => {
z-index: 1;
color: #fff;
}
</style>
</style>

View File

@@ -1,11 +1,20 @@
<template>
<div class="page">
<!-- Header -->
<TopNavBar title="最近任务" color="white" :showHistory="false" @back="onBack" />
<TopNavBar
title="最近任务"
color="white"
:showHistory="false"
@back="onBack"
/>
<!-- 列表 -->
<div class="list">
<div v-for="(item, index) in list" :key="`${item.name}-${index}`" class="card">
<div
v-for="(item, index) in list"
:key="`${item.name}-${index}`"
class="card"
>
<!-- 排队中 -->
<template v-if="item.status == 0">
<div class="left queued-box">排队中</div>
@@ -25,10 +34,15 @@
<div class="name">{{ item.name }}</div>
<div class="progress">
<div class="bar" :style="{ width: (item.progress || 0) + '%' }"></div>
<div
class="bar"
:style="{ width: (item.progress || 0) + '%' }"
></div>
</div>
<div class="status-text">生成中 {{ item.progress ? item.progress + '%' : '' }}</div>
<div class="status-text">
生成中 {{ item.progress ? item.progress + '%' : '' }}
</div>
</div>
</template>
@@ -56,20 +70,22 @@
</div>
</template>
</div>
<div class="tip"> {{ list.length == 0 ? '暂无记录' : `仅保留最近20天生成记录` }}</div>
<div class="tip">
{{ list.length == 0 ? '暂无记录' : `仅保留最近20天生成记录` }}
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { onMounted, reactive } from 'vue'
import { useRouter } from 'vue-router'
// @ts-ignore
import TopNavBar from '../components/TopNavBar.vue';
import TopNavBar from '../components/TopNavBar.vue'
// @ts-ignore
import { generatorPhotoTaskList } from '@api';
import { closeToast, showLoadingToast } from 'vant';
import 'vant/lib/toast/style';
import { generatorPhotoTaskList } from '@api'
import { closeToast, showLoadingToast } from 'vant'
import 'vant/lib/toast/style'
interface TaskItem {
name: string
@@ -79,44 +95,58 @@ interface TaskItem {
time?: string
}
const list: TaskItem[] = []
const list = reactive<TaskItem[]>([])
const router = useRouter();
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 } });
router.back()
}
const getTaskList = () => {
showLoadingToast('加载中...');
generatorPhotoTaskList({ pageNum: 1, pageSize: 20 }).then(res => {
closeToast();
if (res.code === 0) {
const data = res.data || [];
list.splice(0, list.length, ...data.map(item => {
let statusNum: number = item.taskStatus as unknown as number; // 先尝试直接转换为数字
const lookPicture = (item: TaskItem) => {
console.log('查看图片')
const encoded = encodeURIComponent(JSON.stringify(item))
router.push({ path: '/prepicture', query: { data: encoded } })
}
const getTaskList = async () => {
showLoadingToast('加载中...')
try {
const res = await generatorPhotoTaskList({
pageNum: 1,
pageSize: 20,
taskStatus: '1'
})
console.log('🚀 ~ generatorPhotoTaskList res:', res)
closeToast()
// 因为拦截器返回了 res.data.data所以这里的 res 直接就是数据内容
// 根据后端返回结构,这里 res 应该是 { records: [] }
const data = res?.records || []
list.splice(
0,
list.length,
...data.map((item: any) => {
let statusNum: number = Number(item.taskStatus)
return {
name: item.sceneName,
status: statusNum as 0 | 1 | 2 | 3,
progress: statusNum === 1 ? 50 : undefined, // 若后端有真实进度请替换
progress: statusNum === 1 ? 50 : undefined,
cover: item.generatorPhoto,
time: item.createTime,
} as TaskItem;
}));
}
});
time: item.createTime
} as TaskItem
})
)
} catch (error) {
console.error('🚀 ~ getTaskList error details:', error)
closeToast()
}
}
onMounted(() => {
getTaskList();
});
getTaskList()
})
</script>
<style scoped>
@@ -248,4 +278,4 @@ onMounted(() => {
font-size: 12px;
margin-top: 20px;
}
</style>
</style>

View File

@@ -1,24 +1,42 @@
<template>
<div class="app-container">
<transition name="fade" mode="out-in">
<img :key="activeScene.sceneName" :src="activeScene.sceneCoverUrl" class="bg-image" />
<img
:key="activeScene.sceneName"
:src="activeScene.sceneCoverUrl"
class="bg-image"
/>
</transition>
<div class="gradient-overlay"></div>
<TopNavBar title="" color="white" :showHistory="true" @back="onBack" @history="onHistory" />
<TopNavBar
title=""
color="white"
:showHistory="true"
@back="onBack"
@history="onHistory"
/>
<div class="content-layer">
<div class="style-tabs">
<div v-for="tab in styles" :key="tab.id" :class="['tab-item', { active: activeStyleId === tab.id }]"
@click="handleStyleChange(tab.id)">
<div
v-for="tab in styles"
:key="tab.id"
:class="['tab-item', { active: activeStyleId === tab.id }]"
@click="handleStyleChange(tab.id)"
>
{{ tab.name }}
</div>
</div>
<div class="scene-list-container">
<div v-for="(item, index) in currentScenes" :key="`${activeStyleId}-${index}`"
:class="['scene-card', { active: activeSceneIndex === index }]" @click="activeSceneIndex = index">
<div
v-for="(item, index) in currentScenes"
:key="`${activeStyleId}-${index}`"
:class="['scene-card', { active: activeSceneIndex === index }]"
@click="activeSceneIndex = index"
>
<img :src="item.sceneCoverUrl" class="thumb-img" />
<div class="scene-name">{{ item.sceneName }}</div>
</div>
@@ -35,92 +53,108 @@
</div>
<!-- 协议提示 -->
<van-popup v-model:show="showAgree" round :close-on-click-overlay="false"
:style="{ padding: '30px 24px', width: '80%' }">
<AgreementTip @cancel="cancelAgree" @confirm="onAgree" @view-rule="onViewRule" />
<van-popup
v-model:show="showAgree"
round
:close-on-click-overlay="false"
:style="{ padding: '30px 24px', width: '80%' }"
>
<AgreementTip
@cancel="cancelAgree"
@confirm="onAgree"
@view-rule="onViewRule"
/>
</van-popup>
<!-- 选图说明 -->
<van-popup v-model:show="showGuide" round position="bottom" closeable close-icon-position="top-left">
<van-popup
v-model:show="showGuide"
round
position="bottom"
closeable
close-icon-position="top-left"
>
<PhotoGuide @start="onStartSelect" />
</van-popup>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, reactive } from 'vue';
import { useRouter } from 'vue-router';
<script setup lang="ts" name="home">
import { ref, computed, onMounted, reactive, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { Session } from '@/utils/storage'
// @ts-ignore
import TopNavBar from '../components/TopNavBar.vue';
import TopNavBar from '../components/TopNavBar.vue'
// @ts-ignore
import PhotoGuide from '../components/PhotoGuide.vue';
import PhotoGuide from '../components/PhotoGuide.vue'
// @ts-ignore
import AgreementTip from '../components/AgreementTip.vue';
import AgreementTip from '../components/AgreementTip.vue'
// @ts-ignore
import { getAigcSceneList, AigcSceneListItem, AigcSceneStyleItem } from '@api';
import { getAigcSceneList, AigcSceneListItem, AigcSceneStyleItem } from '@api'
// @ts-ignore
import { createGeneratorPhotoTask } from '@api';
import { showFailToast, showSuccessToast } from 'vant';
import 'vant/lib/toast/style';
import { createGeneratorPhotoTask } from '@api'
import { showFailToast, showSuccessToast } from 'vant'
import 'vant/lib/toast/style'
// --- 测试数据 ---
const styles = [
{ id: 'real', name: '真实风格', sceneStyle: '0' },
{ id: 'comic', name: '漫画风格', sceneStyle: '1' }
];
]
const mockData = reactive({
real: [] as AigcSceneStyleItem[],
comic: [] as AigcSceneStyleItem[]
});
})
const router = useRouter();
const router = useRouter()
const route = useRoute()
// --- 状态 ---
const activeStyleId = ref<'real' | 'comic'>('real');
const activeSceneIndex = ref(0);
const showGuide = ref(false);
const activeStyleId = ref<'real' | 'comic'>('real')
const activeSceneIndex = ref(0)
const showGuide = ref(false)
const showAgree = ref(true);
const showAgree = ref(!Session.get('hasAgreedPrivacy'))
const cancelAgree = () => {
console.log("用户拒绝了协议");
showAgree.value = false;
if (window.wx && wx.miniProgram) {
wx.miniProgram.navigateBack();
console.log('用户拒绝了协议')
showAgree.value = false
const wx = (window as any).wx
if (wx && wx.miniProgram) {
wx.miniProgram.navigateBack()
}
};
}
const onAgree = () => {
console.log("用户同意了协议");
showAgree.value = false;
console.log('用户同意了协议')
showAgree.value = false
Session.set('hasAgreedPrivacy', true)
// 执行下一步选图逻辑
};
}
const onViewRule = () => {
console.log("跳转到规则详情页");
console.log('跳转到规则详情页')
// 可以是打开另一个 popup 或者 router.push
};
}
const onBack = () => {
router.back();
};
router.back()
}
const onHistory = () => {
router.push('/history');
};
router.push('/history')
}
const generateAction = () => {
showGuide.value = true;
showGuide.value = true
}
const onStartSelect = () => {
console.log("用户已阅读说明,开始打开相册逻辑...");
showGuide.value = false;
console.log('用户已阅读说明,开始打开相册逻辑...')
showGuide.value = false
// 这里写调用系统相册的代码
uploadImage();
};
uploadImage()
}
// 调用上传
const uploadImage = () => {
@@ -133,73 +167,71 @@ const uploadImage = () => {
// return
/// 调用小程序上传接口
if (window.wx && wx.miniProgram) {
const wx = (window as any).wx
if (wx && wx.miniProgram) {
wx.miniProgram.navigateTo({
url: '/pages-bridge/UploadImage'
});
})
}
}
window.addEventListener('hashchange', () => {
const imageUrl = getHashParam('imageUrl')
console.log('从 hash 中获取的 imageUrl:', imageUrl)
if (imageUrl) {
sendGeneratorPhotoTask(imageUrl)
}
})
// 监听 URL 中的 imageUrl 参数(通常从小程序跳转回来时携带)
watch(
() => route.query.imageUrl,
(newUrl) => {
if (newUrl && typeof newUrl === 'string' && newUrl !== 'error') {
console.log('检测到 hash 中的 imageUrl:', newUrl)
sendGeneratorPhotoTask(newUrl)
const getHashParam = (key) => {
const hash = window.location.hash
console.log('hash URL:', hash)
const queryStr = hash.split('?')[1] || ''
const params = queryStr.split('&')
for (let i = 0; i < params.length; i++) {
const [k, v] = params[i].split('=')
if (k === key) {
return decodeURIComponent(v || '')
// 处理完后清除 URL 参数,防止返回或刷新时重复触发
const newQuery = { ...route.query }
delete newQuery.imageUrl
router.replace({ query: newQuery })
}
}
return null
}
},
{ immediate: true }
)
/// 发送生成任务
const sendGeneratorPhotoTask = async (photoUrl: string) => {
const photoUrlList = [photoUrl];
const photoUrlList = [photoUrl]
try {
const response = await createGeneratorPhotoTask({ photoUrlList: photoUrlList, sceneId: activeScene.value.sceneStyleId });
const response = await createGeneratorPhotoTask({
photoUrlList: photoUrlList,
sceneId: activeScene.value.sceneStyleId
})
if (response) {
showSuccessToast('生成任务创建成功!');
router.push('/generate');
showSuccessToast('生成任务创建成功!')
router.push('/generate')
} else {
showFailToast('生成任务创建失败,请试。');
showFailToast('生成任务创建失败,请稍后再试。')
}
} catch (error) {
console.error('创建生成任务时发生错误:', error);
showFailToast('创建生成任务时发生错误,请稍后再试。');
console.error('创建生成任务时发生错误:', error)
showFailToast('创建生成任务时发生错误,请稍后再试。')
}
};
}
// --- 计算属性 ---
const currentScenes = computed(() => mockData[activeStyleId.value] || []);
const activeScene = computed(() => currentScenes.value[activeSceneIndex.value] || {});
const currentScenes = computed(() => mockData[activeStyleId.value] || [])
const activeScene = computed(
() => currentScenes.value[activeSceneIndex.value] || {}
)
// --- 方法 ---
const handleStyleChange = (id: 'real' | 'comic') => {
activeStyleId.value = id;
activeSceneIndex.value = 0;
};
const handleStyleChange = (id: any) => {
activeStyleId.value = id
activeSceneIndex.value = 0
}
// 数据请求
const fetchSceneList = async () => {
const list: AigcSceneListItem[] = await getAigcSceneList();
const realArr: any[] = [];
const comicArr: any[] = [];
const list: AigcSceneListItem[] = await getAigcSceneList()
const realArr: any[] = []
const comicArr: any[] = []
list.forEach((scene) => {
const styles = scene.aigcSceneStyleList || [];
const styles = scene.aigcSceneStyleList || []
styles.forEach((s: AigcSceneStyleItem) => {
const styleItem: AigcSceneStyleItem = {
sceneName: scene.sceneName,
@@ -207,27 +239,25 @@ const fetchSceneList = async () => {
sortIndex: scene.sortIndex,
sceneStyle: s.sceneStyle,
sceneStyleId: s.sceneStyleId,
sceneCoverUrl: s.sceneCoverUrl,
};
sceneCoverUrl: s.sceneCoverUrl
}
if (s.sceneStyle === '0') {
realArr.push(styleItem);
realArr.push(styleItem)
}
if (s.sceneStyle === '1') {
comicArr.push(styleItem);
comicArr.push(styleItem)
}
});
});
mockData.real = realArr;
mockData.comic = comicArr;
console.log('处理后的场景列表:', JSON.stringify(mockData));
};
})
})
mockData.real = realArr
mockData.comic = comicArr
console.log('处理后的场景列表:', JSON.stringify(mockData))
}
onMounted(() => {
fetchSceneList();
});
fetchSceneList()
})
</script>
<style scoped>
@@ -257,11 +287,15 @@ onMounted(() => {
inset: 0;
height: 60%;
z-index: 1;
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%);
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%
);
}
/* 内容布局层 */
.content-layer {
position: relative;
@@ -379,7 +413,7 @@ onMounted(() => {
text-align: center;
font-size: 56px;
margin: 12px 0 32px;
background: linear-gradient(to bottom, #FFFFFF 0%, #CBF2FC 100%);
background: linear-gradient(to bottom, #ffffff 0%, #cbf2fc 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: 900;
@@ -419,7 +453,7 @@ onMounted(() => {
height: 60px;
border-radius: 30px;
border: none;
background: linear-gradient(90deg, #D9FBC8 0%, #8BE2FF 100%);
background: linear-gradient(90deg, #d9fbc8 0%, #8be2ff 100%);
box-shadow: 0px 0px 20px 0px rgba(217, 251, 200, 0.3);
font-weight: bold;
font-size: 20px;
@@ -437,4 +471,4 @@ onMounted(() => {
.fade-leave-to {
opacity: 0;
}
</style>
</style>

View File

@@ -29,7 +29,7 @@
</div>
</template>
<script setup lang="ts">
<script setup lang="ts" name="prepicture">
import { ref, computed } from 'vue';
import { useRouter, useRoute } from 'vue-router';
// @ts-ignore