Commit 8ab764c7 authored by 黄同智's avatar 黄同智

初始化web端

parent 786b9e4e
node_modules
dist
.vscode
.DS_Store
pnpm-lock.yaml
{
"printWidth": 140,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"quoteProps": "as-needed",
"jsxSingleQuote": false,
"trailingComma": "es5",
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "always",
"requirePragma": false,
"insertPragma": false,
"proseWrap": "preserve",
"htmlWhitespaceSensitivity": "css",
"vueIndentScriptAndStyle": false,
"endOfLine": "lf",
"embeddedLanguageFormatting": "auto",
"singleAttributePerLine": false
}
\ No newline at end of file
<!doctype html> <!DOCTYPE html>
<html lang="zh-CN"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>金水流域闸站联合调度数据支撑系统</title> <title>Vue 3 最佳实践</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
......
This diff is collapsed.
{ {
"name": "jinshui-water-dashboard", "name": "webh5",
"private": true, "private": true,
"version": "0.1.0", "version": "0.0.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite --host 0.0.0.0", "dev": "vite",
"build": "vite build", "build": "tsc && vite build",
"preview": "vite preview --host 0.0.0.0" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@vitejs/plugin-vue": "latest", "@ffmpeg/ffmpeg": "^0.12.15",
"vite": "latest", "@ffmpeg/util": "^0.12.2",
"vue": "latest" "@vue-flow/additional-components": "^1.3.3",
"@vue-flow/core": "^1.48.2",
"axios": "^1.18.1",
"element-plus": "^2.14.2",
"pinia": "^2.2.0",
"swiper": "^14.0.5",
"three": "^0.185.1",
"v3-cropper": "^1.2.1",
"vue": "^3.5.0",
"vue-router": "^4.4.0"
}, },
"devDependencies": {} "devDependencies": {
"@types/node": "^20.11.0",
"@types/three": "^0.185.1",
"@vitejs/plugin-vue": "^4.3.0",
"sass": "^1.101.0",
"typescript": "^5.6.0",
"vite": "^5.4.0",
"vite-svg-loader": "^5.1.1"
}
} }
allowBuilds:
'@parcel/watcher': false
esbuild: true
onnxruntime-node: set this to true or false
protobufjs: set this to true or false
sharp: set this to true or false
vue-demi: true
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
// Classic Worker for FFmpeg - uses importScripts to load UMD core
// Avoids Vite module worker bundling issues entirely
let ffmpeg = null;
self.onmessage = async (e) => {
const { id, type, data } = e.data;
try {
if (type === 'LOAD') {
// data: { coreURL, wasmURL }
const { coreURL, wasmURL } = data;
// importScripts works in classic workers - loads UMD core synchronously
importScripts(coreURL);
// createFFmpegCore is set by the UMD script
if (!self.createFFmpegCore) {
self.postMessage({ id, type: 'ERROR', data: 'createFFmpegCore not found after importScripts' });
return;
}
// Emscripten locateFile hack: encode wasmURL in the URL hash
const mainScriptUrlOrBlob = coreURL + '#' + btoa(JSON.stringify({ wasmURL }));
ffmpeg = await self.createFFmpegCore({
mainScriptUrlOrBlob,
});
// Set up log and progress callbacks
ffmpeg.setLogger((logData) => {
self.postMessage({ type: 'LOG', data: logData });
});
ffmpeg.setProgress((progressData) => {
self.postMessage({ type: 'PROGRESS', data: progressData });
});
self.postMessage({ id, type: 'LOAD', data: true });
return;
}
if (!ffmpeg) {
self.postMessage({ id, type: 'ERROR', data: 'ffmpeg not loaded' });
return;
}
switch (type) {
case 'WRITE_FILE': {
// data: { path, data }
ffmpeg.FS.writeFile(data.path, data.data);
self.postMessage({ id, type: 'WRITE_FILE', data: true });
break;
}
case 'READ_FILE': {
// data: { path, encoding }
const result = ffmpeg.FS.readFile(data.path, { encoding: data.encoding || 'binary' });
const trans = [];
if (result instanceof Uint8Array) trans.push(result.buffer);
self.postMessage({ id, type: 'READ_FILE', data: result }, trans);
break;
}
case 'DELETE_FILE': {
ffmpeg.FS.unlink(data.path);
self.postMessage({ id, type: 'DELETE_FILE', data: true });
break;
}
case 'EXEC': {
// data: { args, timeout }
ffmpeg.setTimeout(data.timeout ?? -1);
ffmpeg.exec(...data.args);
const ret = ffmpeg.ret;
ffmpeg.reset();
self.postMessage({ id, type: 'EXEC', data: ret });
break;
}
default:
self.postMessage({ id, type: 'ERROR', data: 'unknown message type: ' + type });
}
} catch (err) {
self.postMessage({ id, type: 'ERROR', data: (err && err.message) ? err.message : String(err) });
}
};
This diff was suppressed by a .gitattributes entry.
html, body, #app {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
:root {
--main-color: 144, 34, 253;
}
/* 清除内外边距 */
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* 设置根字体大小(方便使用 rem) */
html {
font-size: 16px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
scroll-behavior: smooth;
}
/* 基础 body 样式 */
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
font-size: 14px;
line-height: 1.5;
color: #303133;
background-color: #f0f2f5;
}
// 清除 i标签默认样式
i {
font-style: normal;
}
/* 清除列表默认样式 */
ul,
ol {
list-style: none;
}
/* 清除链接默认样式 */
a {
color: #409EFF;
text-decoration: none;
transition: color 0.3s ease;
&:hover {
color: #66b1ff;
}
}
// ========================================
// 分段步长间距工具类生成器
// 规则:
// 0-120:偶数(0, 2, 4, 6, ..., 120)
// 120-240:步长 10(130, 140, 150, ..., 240)
// 240-800:步长 100(300, 400, 500, ..., 800)
// ========================================
@use "sass:list";
// ----- 生成数值列表 -----
$spacing-values: ();
// 1. 0-120
@for $i from 0 through 150 {
// $value: $i * 1;
$spacing-values: list.append($spacing-values, $i * 2);
// 在这里直接生成 .m-#{$value} 等类
}
// 2. 120-240 步长 10
@for $i from 13 through 24 {
$spacing-values: list.append($spacing-values, $i * 10);
// 在这里直接生成 .m-#{$value} 等类
}
// 3. 240-800 步长 100
@for $i from 3 through 8 {
$spacing-values: list.append($spacing-values, $i * 100);
// 在这里直接生成 .m-#{$value} 等类
}
// ----- 批量生成间距类 -----
@each $value in $spacing-values {
// Margin
.m-#{$value} { margin: #{$value}px; }
.mt-#{$value} { margin-top: #{$value}px; }
.mb-#{$value} { margin-bottom: #{$value}px; }
.ml-#{$value} { margin-left: #{$value}px; }
.mr-#{$value} { margin-right: #{$value}px; }
.mx-#{$value} {
margin-left: #{$value}px;
margin-right: #{$value}px;
}
.my-#{$value} {
margin-top: #{$value}px;
margin-bottom: #{$value}px;
}
// Padding
.p-#{$value} { padding: #{$value}px; }
.pt-#{$value} { padding-top: #{$value}px; }
.pb-#{$value} { padding-bottom: #{$value}px; }
.pl-#{$value} { padding-left: #{$value}px; }
.pr-#{$value} { padding-right: #{$value}px; }
.px-#{$value} {
padding-left: #{$value}px;
padding-right: #{$value}px;
}
.py-#{$value} {
padding-top: #{$value}px;
padding-bottom: #{$value}px;
}
// Width
.w-#{$value} { width: #{$value}px; }
// Height
.h-#{$value} { height: #{$value}px; }
.wh-#{$value} { width: #{$value}px; height: #{$value}px; }
// Max Width
.maxw-#{$value} { max-width: #{$value}px; }
// Max Height
.maxh-#{$value} { max-height: #{$value}px; }
// Min Width
.minw-#{$value} { min-width: #{$value}px; }
// Min Height
.minh-#{$value} { min-height: #{$value}px; }
// 字体大小
.fs-#{$value} { font-size: #{$value}px; }
// 字体行高
.lh-#{$value} { line-height: #{$value}px; }
// 字体粗细
.fw-#{$value} { font-weight: #{$value}; }
// 间隔
.gap-#{$value} { gap: #{$value}px; }
// 圆角
.round-#{$value} { border-radius: #{$value}px; }
// top/bottom/left/right
.top-#{$value} { top: #{$value}px; }
.bottom-#{$value} { bottom: #{$value}px; }
.left-#{$value} { left: #{$value}px; }
.right-#{$value} { right: #{$value}px; }
}
// ----- 文本对齐 -----
.text-left { text-align: left; }
.text-center { text-align: center; }
.text-right { text-align: right; }
.text-justify { text-align: justify; }
.font-bold { font-weight: bold; }
// ----- Flex 布局 -----
.flex { display: flex; }
.flex-col { display: flex; flex-direction: column; }
.flex-wrap { display: flex; flex-wrap: wrap; }
.flex-1 { flex: 1; }
.flex-center { display: flex; justify-content: center; align-items: center; }
.flex-between { display: flex; justify-content: space-between; align-items: center; }
.flex-items-center { display: flex; align-items: center; }
.justify-between { justify-content: space-between; }
.items-center { align-items: center; }
.items-start { align-items: flex-start; }
.items-end { align-items: flex-end; }
.justify-center { justify-content: center; }
.justify-start { justify-content: flex-start; }
.justify-end { justify-content: flex-end; }
.justify-around { justify-content: space-around; }
.justify-evenly { justify-content: space-evenly; }
// ----- Position 布局 -----
.relative { position: relative; }
.absolute { position: absolute; }
.fixed { position: fixed; }
.sticky { position: sticky; }
.z-1 { z-index: 1; }
.z-2 { z-index: 2; }
.z-99999 { z-index: 99999; }
// ---- Border -----
.border-none { border: none; }
.border-aaa { border: 1px solid #aaa; }
.border-bbb { border: 1px solid #bbb; }
.border-ccc { border: 1px solid #ccc; }
.border-ddd { border: 1px solid #ddd; }
.border-eee { border: 1px solid #eee; }
.border-f5 { border: 1px solid #f5f5f5; }
.border-fff { border: 1px solid #fff; }
// ----- 背景颜色 -----
.bg-none { background: none; }
.bg-000 { background: #000; }
.bg-111 { background: #1f1f1f; }
.bg-222 { background: #222; }
.bg-333 { background: #333; }
.bg-444 { background: #444; }
.bg-666 { background: #666; }
.bg-888 { background: #888; }
.bg-999 { background: #999; }
.bg-aaa { background: #aaa; }
.bg-bbb { background: #bbb; }
.bg-ccc { background: #ccc; }
.bg-ddd { background: #ddd; }
.bg-eee { background: #eee; }
.bg-f5 { background: #f5f5f5; }
.bg-fff { background: #fff; }
.bg-main { background: rgba(var(--main-color), 1); }
// ---- 颜色 -----
.color-000 { color: #000; }
.color-999 { color: #999; }
.color-888 { color: #888; }
.color-666 { color: #666; }
.color-444 { color: #444; }
.color-333 { color: #333; }
.color-aaa { color: #aaa; }
.color-bbb { color: #bbb; }
.color-ccc { color: #ccc; }
.color-ddd { color: #ddd; }
.color-eee { color: #eee; }
.color-fff { color: #fff; }
.color-main { color: rgba(var(--main-color), 1); }
// ----- 宽高 -----
.w-full { width: 100%; }
.h-full { height: 100%; }
.vw100 { width: 100vw; }
.vh100 { height: 100vh; }
// ---- 显示隐藏 -----
.hidden { overflow: hidden; }
.hide { display: none; }
.over-auto { overflow: auto; }
.over-scroll { overflow: scroll; }
.ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ellipsis-2 { overflow: hidden; text-overflow: ellipsis; -webkit-line-clamp: 2;line-clamp: 2; -webkit-box-orient: vertical; display: -webkit-box; }
.ellipsis-3 { overflow: hidden; text-overflow: ellipsis; -webkit-line-clamp: 3; line-clamp: 3; -webkit-box-orient: vertical; display: -webkit-box; }
// ----- 圆形 -----
.round { border-radius: 50%; }
.pointer { cursor: pointer; }
// 透明度
.opacity-0 { opacity: 0; }
.opacity-10 { opacity: 0.1; }
.opacity-20 { opacity: 0.2; }
.opacity-30 { opacity: 0.3; }
.opacity-40 { opacity: 0.4; }
.opacity-50 { opacity: 0.5; }
.opacity-60 { opacity: 0.6; }
.opacity-70 { opacity: 0.7; }
.opacity-80 { opacity: 0.8; }
.opacity-90 { opacity: 0.9; }
.cover { object-fit: cover; }
// 多选样式修改
.full-area-checkbox {
position: absolute !important;
inset: 0 !important;
/* 撑满整个卡片 */
width: 100% !important;
height: 100% !important;
margin: 0 !important;
cursor: pointer !important;
/* 隐藏原生复选框框体 */
:deep(.el-checkbox__input) {
position: absolute !important;
inset: 0 !important;
width: 100% !important;
height: 100% !important;
opacity: 0;
cursor: pointer !important;
}
:deep(.el-checkbox__label) {
display: none !important;
}
}
\ No newline at end of file
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
$colors: (
'primary': (
'base': #9022fd,
),
)
);
@use "element-plus/theme-chalk/src/index.scss" as *;
\ No newline at end of file
This diff is collapsed.
import { get, put, post } from './http'
import type { Node, Edge } from '@vue-flow/core'
// 画布快照:与 Vue Flow toObject() 产物对齐
export interface FlowSnapshot {
nodes: Node[]
edges: Edge[]
viewport?: { x: number; y: number; zoom: number }
position?: [number, number]
zoom?: number
}
// 后端返回结构(按需调整字段名以匹配你的接口)
export interface FlowRecord extends FlowSnapshot {
id: string
title?: string
updatedAt?: string
}
export interface UploadResult {
url: string
[key: string]: any
}
// 读取画布
export function getFlow(projectId: string) {
return get<FlowRecord>(`/flow/${projectId}`)
}
// 保存画布(整图覆盖写)
export function saveFlow(projectId: string, data: FlowSnapshot) {
return put<FlowRecord>(`/flow/${projectId}`, data)
}
// 上传媒体文件,拿到可持久访问的 URL
export function uploadMedia(file: File) {
const form = new FormData()
form.append('file', file)
return post<UploadResult>('/upload', form, {
headers: { 'Content-Type': 'multipart/form-data' },
// 上传可能较慢,放宽超时
timeout: 60000,
})
}
import axios, { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse, Canceler } from 'axios'
const pendingRequests = new Map<string, Canceler>()
function buildRequestKey(config: AxiosRequestConfig) {
const { method, url, params, data } = config
const paramsString = params ? JSON.stringify(params) : ''
const dataString = data ? JSON.stringify(data) : ''
return [method, url, paramsString, dataString].join('&')
}
function addPendingRequest(config: AxiosRequestConfig) {
const requestKey = buildRequestKey(config)
if (pendingRequests.has(requestKey)) {
const cancel = pendingRequests.get(requestKey)
cancel?.(`取消重复请求:${requestKey}`)
}
config.cancelToken = new axios.CancelToken((cancel) => {
pendingRequests.set(requestKey, cancel)
})
}
function removePendingRequest(config: AxiosRequestConfig) {
const requestKey = buildRequestKey(config)
if (pendingRequests.has(requestKey)) {
pendingRequests.delete(requestKey)
}
}
const http: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || '',
timeout: 10000,
headers: {
'Content-Type': 'application/json;charset=UTF-8',
},
withCredentials: true,
})
http.interceptors.request.use(
(config) => {
if (config) {
removePendingRequest(config)
addPendingRequest(config)
}
const token = localStorage.getItem('auth_token')
if (token && config.headers) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
(error) => {
return Promise.reject(error)
}
)
http.interceptors.response.use(
(response: AxiosResponse) => {
removePendingRequest(response.config)
const { status, data } = response
if (status === 200) {
return data
}
if (status === 204) {
return data || null
}
if (status === 401) {
localStorage.removeItem('auth_token')
localStorage.removeItem('auth_user')
return Promise.reject(new Error('未授权,请重新登录'))
}
if (status === 403) {
return Promise.reject(new Error('无权访问,权限不足'))
}
const message = data?.message || response.statusText || '请求失败'
return Promise.reject(new Error(`HTTP ${status}${message}`))
},
(error: AxiosError) => {
if (axios.isCancel(error)) {
return Promise.reject(new Error(error.message || '请求已取消'))
}
if (error.response) {
const { status, data } = error.response
const statusMessageMap: Record<number, string> = {
400: '请求参数错误',
401: '未授权,请重新登录',
403: '无权访问,权限不足',
404: '请求地址不存在',
500: '服务器内部错误',
502: '网关错误',
503: '服务不可用',
504: '网关超时',
}
const message = (data as any)?.message || statusMessageMap[status] || error.message || '请求失败'
return Promise.reject(new Error(`HTTP ${status}${message}`))
}
return Promise.reject(new Error(error.message || '网络错误'))
}
)
export interface HttpRequestConfig<T = any> extends AxiosRequestConfig<T> {
showLoading?: boolean
}
export function get<T = any>(url: string, config?: HttpRequestConfig) {
return http.get<T, T>(url, config)
}
export function post<T = any>(url: string, data?: unknown, config?: HttpRequestConfig) {
return http.post<T, T>(url, data, config)
}
export function put<T = any>(url: string, data?: unknown, config?: HttpRequestConfig) {
return http.put<T, T>(url, data, config)
}
export function del<T = any>(url: string, config?: HttpRequestConfig) {
return http.delete<T, T>(url, config)
}
export function request<T = any>(config: HttpRequestConfig): Promise<T>
export function request<T = any>(url: string, config?: HttpRequestConfig): Promise<T>
export function request<T = any>(urlOrConfig: string | HttpRequestConfig, config?: HttpRequestConfig) {
if (typeof urlOrConfig === 'string') {
const cfg = Object.assign({}, config || {}, { url: urlOrConfig }) as AxiosRequestConfig
return http.request<T, T>(cfg)
}
return http.request<T, T>(urlOrConfig)
}
export default http
import { get, post, put, del, request } from './http'
export interface User {
id: number
name: string
username: string
email: string
phone: string
website: string
}
// get请求
export function getUsers(params?: any) {
return get<User[]>('/users', params)
}
// post请求
export function createUser(data: Partial<User>) {
return post<User>('/users', data)
}
// put请求
export function updateUser(id: number, data: Partial<User>) {
return put<User>(`/users/${id}`, data)
}
// del请求
export function deleteUser(id: number) {
return del(`/users/${id}`)
}
<template>
<ElDialog
v-model="dialogVisible"
:width="width"
:align-center="alignCenter"
class="custom-dialog"
append-to-body
:style="{ background: bgColor }"
>
<div class="absolute top-10 right-10 round-4 w-20 h-20 flex-center pointer hover" @click="close">
<SvgIcon name="close" size="14" />
</div>
<slot></slot>
</ElDialog>
</template>
<!-- 标题、内容、底部按钮。都是自定义 -->
<script lang="ts" setup>
import { ref, computed } from 'vue';
import SvgIcon from '@/components/SvgIcon/SvgIcon.vue';
const props = defineProps({
modelValue: {
type: Boolean,
default: false,
},
alignCenter: {
type: Boolean,
default: true,
},
width: {
type: String,
default: '50%',
},
bgColor: {
type: String,
default: 'linear-gradient(to bottom, #1c2523 0%, #1a1a1a 20%)',
},
});
const emit = defineEmits(['update:modelValue']);
const close = () => {
emit('update:modelValue', false);
}
const dialogVisible = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
</script>
<style lang="scss">
.custom-dialog {
border-radius: 8px;
padding: 0;
.el-dialog__header {
display: none;
}
}
</style>
<style scoped>
.hover {
&:hover {
background: rgba(255, 255, 255, 0.1);
}
}
</style>
<template>
<svg
t="1783923572971"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="6980"
:width="size"
:height="size"
>
<path
d="M512 612.2752l-309.7088-313.344a58.0352 58.0352 0 0 0-82.752 0 59.6864 59.6864 0 0 0 0 83.7248l351.0912 355.2a58.0352 58.0352 0 0 0 82.7392 0l351.104-355.2a59.6864 59.6864 0 0 0 0-83.712 58.0352 58.0352 0 0 0-82.7648 0L512 612.288z"
:fill="color"
p-id="6981"
></path>
</svg>
</template>
<script setup lang="ts">
const props = defineProps({
size: {
type: String,
default: '14',
},
color: {
type: String,
default: '#aaa',
}
})
</script>
<style scoped></style>
<template>
<component
:is="iconComponent"
class="svg-icon"
:class="className"
:style="{
fontSize: size + 'px',
color: color,
'--svg-color': color
}"
v-bind="$attrs"
/>
</template>
<script setup>
import { computed, defineAsyncComponent } from 'vue'
const props = defineProps({
name: {
type: String,
required: true,
// 例如: 'arrow', 'heart'
},
size: {
type: [Number, String],
default: 14,
},
color: {
type: String,
default: 'currentColor',
},
className: {
type: String,
default: '',
},
})
// 动态导入 SVG 组件
const iconComponent = computed(() => {
// Vite 在编译时处理这个动态导入
return defineAsyncComponent(() =>
import(`@/static/svgs/${props.name}.svg`)
)
})
</script>
<style scoped>
.svg-icon {
display: inline-block;
vertical-align: middle;
flex-shrink: 0;
line-height: 1;
width: 1em;
height: 1em;
}
/* color prop → currentColor,子元素继承 */
.svg-icon :deep(*:not([fill="none"])) {
fill: currentColor !important;
}
.svg-icon :deep([stroke]) {
stroke: currentColor !important;
}
</style>
\ No newline at end of file
import { useVueFlow } from "@vue-flow/core";
const { addNodes, addEdges } = useVueFlow();
const typeNode: any = {
'image': '图片',
'text': '文本',
}
export function generateRandomNode(type: string) {
return {
id: Math.random().toString(),
type: type, //special
position: { x: 340, y: 150 },
label: "Random Node",
connectable: true,
data: {
hello: "world",
label: typeNode[type],
},
};
}
export function onHandleClick(event: MouseEvent, props: any) {
event.stopPropagation();
console.log(32222, props);
// 计算新节点的位置
// 这里我们简单地将新节点放在当前节点的右侧 200px 处
// 你可以根据 event.clientX/Y 获取鼠标点击的屏幕坐标,然后通过 project 方法转换为画布坐标
const newNodePosition = {
x: props.position.x + 200,
y: props.position.y,
};
addNodes({
id: `node_${Date.now()}`,
type: props.handleType, // 或者是你的自定义节点类型 'special'
position: newNodePosition,
data: { label: "New Node" },
});
addEdges({
id: `e${props.id}-node_${Date.now()}`,
source: props.id,
target: `node_${Date.now()}`,
});
}
\ No newline at end of file
<template>
<el-container class="h-full">
<el-aside width="240px" style="background-color: #fff">
<div class="fs-50 p-10">LOGO</div>
<div class="px-10">
<el-menu style="border-right: none;" router :default-active="$route.path">
<template v-for="(item, index) in menus" :key="index">
<el-sub-menu v-if="item.children && item.children.length" :index="item.path">
<template #title>
<SvgIcon name="HD" size="20" class="mr-10 iconscoal" />
<span>{{ item.name }}</span>
</template>
<el-menu-item v-for="(child, cindex) in item.children" :key="cindex" :index="item.path + child.path">
<span class="ml-20">{{ child.name }}</span>
</el-menu-item>
</el-sub-menu>
<el-menu-item v-else :index="item.path">
<SvgIcon name="HD" size="20" class="mr-10 iconscoal" />
<span>{{ item.name }}</span>
</el-menu-item>
</template>
</el-menu>
</div>
</el-aside>
<el-main class="p-0">
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<component :is="Component" :key="route.fullPath" />
</transition>
</router-view>
</el-main>
</el-container>
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router';
const route = useRoute();
const menus = [
{
name: '首页',
path: '/',
icon: 'el-icon-s-home',
},
{
name: '快速创作',
path: '/',
icon: 'el-icon-s-home',
},
{
name: 'Agent创作',
path: '/',
icon: 'el-icon-s-home',
},
{
name: '爆款复刻',
path: '/',
icon: 'el-icon-s-home',
},
{
name: 'AI视频',
path: '/',
icon: 'el-icon-s-home',
},
{
name: 'AI图片',
path: '/',
icon: 'el-icon-s-home',
},
{
name: '画布',
path: '/',
icon: 'el-icon-s-home',
},
{
name: '任务协作',
path: '/',
icon: 'el-icon-s-home',
},
{
name: '资源库',
path: '/resource',
icon: 'el-icon-s-home',
children: [
{
name: '成片管理',
path: '/clip',
},
{
name: '素材管理',
path: '/material',
},
{
name: '脚本管理',
path: '/script',
},
{
name: '图片管理',
path: '/image',
},
{
name: '音频管理',
path: '/audio',
}
],
},
];
</script>
<style lang="scss" scoped>
:deep(.el-menu-item) {
height: 44px;
margin-top: 4px;
&.is-active {
background-color: #f1e3ff;
border-radius: 10px;
}
&:hover {
border-radius: 10px;
.iconscoal {
transition: all 0.3s ease-in-out;
transform: scale(1.1);
}
}
}
:deep(.el-sub-menu__title) {
height: 44px;
margin-top: 4px;
&:hover {
border-radius: 10px;
.iconscoal {
transition: all 0.3s ease-in-out;
transform: scale(1.1);
}
}
}
</style>
import { createApp } from 'vue' import { createApp } from 'vue'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import '/public/styles/index.scss'
import '/public/styles/common.scss'
import '/public/styles/base.scss'
import App from './App.vue' import App from './App.vue'
import './styles.css' import router from './router'
import { setupRouterGuard } from './router/guard'
import SvgIcon from './components/SvgIcon/SvgIcon.vue'
createApp(App).mount('#app') const app = createApp(App)
app.use(createPinia())
app.use(router)
setupRouterGuard(router)
app.use(ElementPlus)
app.component('SvgIcon', SvgIcon)
app.mount('#app')
import type { Router } from 'vue-router'
import { useAuthStore } from '../store'
export function setupRouterGuard(router: Router) {
const authStore = useAuthStore()
authStore.restore()
router.beforeEach((to, from, next) => {
const isLoginPage = to.path === '/login'
const isAuthenticated = !!authStore.token
if (!isAuthenticated && !isLoginPage) {
return next({ path: '/login', query: { redirect: to.fullPath } })
}
if (isAuthenticated && isLoginPage) {
return next('/')
}
next()
})
}
import { createRouter, createWebHistory } from 'vue-router'
import Login from '../views/Login.vue'
import Home from '@/views/home/index.vue'
import NotFound from '../views/NotFound.vue'
import VueFlow from '@/views/vue-flow/vueFlow.vue'
import Shili from '@/views/vue-flow/shili.vue'
import video from '@/views/vue-flow/video.vue'
import VideoOutMp4 from '@/views/vue-flow/components/VideoOutMp4.vue'
import ThreeD from '@/views/vue-flow/3d.vue'
import Layout from '@/layout/index.vue'
const routes = [
{
path: '/login',
name: 'Login',
component: Login
},
{
path: '/',
component: Layout,
children: [
{
path: 'home',
alias: '',
name: 'Home',
component: Home,
meta: { title: '首页', icon: 'House', affix: true }
}
]
},
{
path: '/resource',
component: Layout,
children: [
{
path: 'clip',
name: 'Clip',
component: ()=> import('@/views/resource/clip/index.vue'),
meta: { title: '成片管理', icon: '', affix: true }
},
{
path: 'material',
name: 'Material',
component: ()=> import('@/views/resource/material/index.vue'),
meta: { title: '素材管理', icon: '', affix: true }
},
{
path: 'script',
name: 'Template',
component: ()=> import('@/views/resource/script/index.vue'),
meta: { title: '脚本管理', icon: '', affix: true }
},
{
path: 'image',
name: 'Image',
component: ()=> import('@/views/resource/image/index.vue'),
meta: { title: '图片管理', icon: '', affix: true }
},
{
path: 'audio',
name: 'Audio',
component: ()=> import('@/views/resource/audio/index.vue'),
meta: { title: '音频管理', icon: '', affix: true }
},
]
},
{
path: '/vueFlow/:projectId?',
name: 'VueFlow',
component: VueFlow
},
{
path: '/shili',
component: Layout,
children: [
{
path: '/shili',
name: 'Shili',
component: Shili
}
]
},
{
path: '/video',
component: Layout,
children: [
{
path: '/video',
name: 'video',
component: video
}
]
},
{
path: '/video1',
component: Layout,
children: [
{
path: '/video1',
name: 'VideoOutMp4',
component: VideoOutMp4
}
]
},
{
path: '/3d',
component: Layout,
children: [
{
path: '/3d',
name: 'ThreeD',
component: ThreeD
}
]
},
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: NotFound
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
<svg width="120" height="120" viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#409EFF" />
<stop offset="100%" stop-color="#67C23A" />
</linearGradient>
</defs>
<rect width="120" height="120" rx="24" fill="url(#grad)" />
<text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" fill="#ffffff" font-size="32" font-family="Arial, Helvetica, sans-serif">H5</text>
</svg>
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1784021765915" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11446" width="32" height="32" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M455.936 110.08a46.784 46.784 0 0 1 0 93.632H262.208a78.08 78.08 0 0 0-78.144 78.144l0.064 107.52c344.128 32.832 519.808 189.632 527.104 470.4h50.752a78.08 78.08 0 0 0 77.888-71.04l0.32-7.04V531.84a46.72 46.72 0 0 1 93.44 0v249.792c0 94.848-76.8 171.776-171.712 171.776H262.208a171.712 171.712 0 0 1-171.84-171.776V281.92a171.84 171.84 0 0 1 171.776-171.776h193.792zM321.92 618.816a28.352 28.352 0 0 0-28.352 28.352v56.768h-56.768v-56.768a28.352 28.352 0 1 0-56.768 0v170.24a28.416 28.416 0 0 0 56.768 0v-56.704h56.768v56.704a28.416 28.416 0 0 0 56.768 0v-170.24a28.352 28.352 0 0 0-28.416-28.352z m141.952 0H407.04a28.352 28.352 0 0 0-28.288 25.408l-0.128 2.944v170.24c0 15.744 12.672 28.416 28.416 28.416h56.768a85.248 85.248 0 0 0 85.12-85.12v-56.768a85.12 85.12 0 0 0-85.12-85.12z m0 56.768c15.616 0 28.352 12.672 28.352 28.352v56.768a28.416 28.416 0 0 1-28.352 28.352h-28.416v-113.472h28.416z m311.68-604.928a20.032 20.032 0 0 1 18.56 12.672l9.984 24.192c16.832 40.96 48.64 73.728 89.024 91.84l28.416 12.672a20.864 20.864 0 0 1 0 37.952l-30.08 13.376a172.544 172.544 0 0 0-87.744 89.024l-9.792 22.208a20.032 20.032 0 0 1-36.928 0l-9.664-22.272a172.352 172.352 0 0 0-87.744-88.96l-30.016-13.44a20.992 20.992 0 0 1 0-37.888l28.288-12.672a172.48 172.48 0 0 0 88.96-91.84l10.048-24.192a20.032 20.032 0 0 1 18.624-12.672z" p-id="11447"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1784013704681" class="icon" viewBox="0 0 1036 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3886" width="16.1875" height="16" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M967.460547 546.110294 586.925143 546.110294 586.925143 954.949983C586.925143 993.069187 555.99433 1024 517.875127 1024 479.721363 1024 448.82511 993.069187 448.82511 954.949983L448.82511 546.110294 68.255147 546.110294C30.550658 546.110294 0 515.559636 0 477.855147 0 440.150658 30.550658 409.6 68.255147 409.6L448.82511 409.6 448.82511 69.050017C448.82511 30.930813 479.721363 0 517.875127 0 555.99433 0 586.925143 30.930813 586.925143 69.050017L586.925143 409.6 967.460547 409.6C1005.165035 409.6 1035.750253 440.150658 1035.750253 477.855147 1035.750253 515.559636 1005.165035 546.110294 967.460547 546.110294Z" p-id="3887"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1784274654589" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4732" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M125.15866667 427.544a30.6 30.6 0 1 1 61.2 0c0 185.292 142.848 334.944 318.312 334.944 175.464 0 318.312-149.652 318.312-334.944a30.6 30.6 0 1 1 61.2 0c0 218.448-169.56 396.144-379.512 396.144-209.916 0-379.512-177.696-379.512-396.144z" fill="#3277FF" p-id="4733"></path><path d="M316.64266667 311.552v132.3a187.992 187.992 0 0 0 188.028 188.316 188.352 188.352 0 0 0 188.028-188.316V311.552A187.992 187.992 0 0 0 504.70666667 123.2a188.352 188.352 0 0 0-188.028 188.352z m-61.2 0C255.44266667 173.708 367.29466667 62 504.67066667 62a249.192 249.192 0 0 1 249.228 249.552v132.3c0 137.808-111.852 249.516-249.228 249.516a249.192 249.192 0 0 1-249.228-249.516V311.552z" fill="#3277FF" p-id="4734"></path><path d="M398.03866667 409.292a30.6 30.6 0 0 1 0-61.2h213.264a30.6 30.6 0 0 1 0 61.2H398.03866667zM445.77466667 500.696a30.6 30.6 0 1 1 0-61.2h117.792a30.6 30.6 0 1 1 0 61.2H445.77466667z" fill="#9ABCFF" p-id="4735"></path><path d="M308.29066667 939.968a30.6 30.6 0 1 1 0-61.2h392.76a30.6 30.6 0 0 1 0 61.2H308.29066667z" fill="#3277FF" p-id="4736"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1784023290999" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3561" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M199.30883799 860.28202599l-36.09930499-36.09930499 72.19861-71.690169 36.099305 36.099305zM305.064548 752.492552l-36.099305-36.099305 71.690169-71.69016901 36.099305 36.09930501z m107.78947401-107.78947401L379.29692199 610.129096l71.690168-71.690168 36.09930501 36.099304z m107.78947399-107.78947299l-36.099305-36.099305L559.285005 429.124131l36.09930501 36.099305z m107.789473-107.789474l-36.099305-36.09930501 71.690169-71.69016899 36.09930501 36.09930499z m107.789474-107.789474l-36.09930501-36.099304 74.74081401-71.690169 36.099305 36.099305z m110.331678-107.789473l-36.099305-36.099305 13.727905-13.72790501 36.09930499 36.09930501z" fill="#666666" p-id="3562"></path><path d="M867.90864 156.09136v762.661371a50.844091 50.844091 0 1 1-101.688183 0v-660.973188h-660.973188a50.844091 50.844091 0 0 1 0-101.688183z" fill="#666666" p-id="3563"></path><path d="M918.752731 867.90864h-762.661371v-762.661371a50.844091 50.844091 0 0 1 101.688183 0v660.973188h660.973188a50.844091 50.844091 0 0 1 0 101.688183z" fill="#666666" p-id="3564"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1784180633427" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3975" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M901.3 504.8l-76.3-150c-13.4-26.3-40-42.6-69.5-42.6H639c-1.1 0-2-0.9-2-2V120.6c0-31.1-25.3-56.3-56.3-56.3h-90c-31.1 0-56.3 25.3-56.3 56.3v189.6c0 1.1-0.9 2-2 2H315.8c-29.5 0-56.1 16.3-69.5 42.6l-76.3 150c-9.2 18.1-8.4 39.3 2.2 56.6 10.3 16.8 27.9 27 47.4 27.6-4.8 101-38.3 205.9-90.2 279.5-12.5 17.8-14.1 40.8-4.1 60.1 10 19.3 29.7 31.3 51.5 31.3h601.5c35 0 66-23.6 75.2-57.4 15.5-56.5 28.4-107.9 29.4-164.9C884 685 874 636 852.9 589c19-1.1 36.1-11.2 46.2-27.6 10.6-17.3 11.4-38.5 2.2-56.6z m-681.4 25.4l76.3-150c3.8-7.4 11.3-12 19.6-12h116.4c32 0 58-26 58-58V120.6c0-0.1 0.2-0.3 0.3-0.3h90c0.1 0 0.3 0.2 0.3 0.3v189.6c0 32 26 58 58 58h116.4c8.3 0 15.8 4.6 19.6 12l76.3 150c0.2 0.3 0.5 1-0.1 2s-1.3 1-1.7 1H221.7c-0.4 0-1.1 0-1.7-1-0.6-1-0.3-1.7-0.1-2zM827 736.6c-0.9 50.5-12.9 98.3-27.4 151.1-2.6 9.5-11.3 16.2-21.2 16.2H651.8c11.3-22.3 18.5-44 23.1-61.2 7.1-26.7 10.7-53.5 10.6-78-0.1-17.1-15.5-30.1-32.4-27.4-13.6 2.2-23.6 14-23.6 27.8 0.1 42.7-14.1 98.2-42.7 138.8H406.2c15.2-21.7 26.1-43.8 33.6-61.9 10-24.3 17.4-49.7 21.2-72.5 2.8-17-10.4-32.5-27.6-32.5-13.6 0-25.3 9.8-27.6 23.3-2.8 16.6-8.3 37.7-17.7 60.4-10.1 24.6-27.8 58.1-55.6 83.3H176.9c-0.5 0-1.2 0-1.8-1.1-0.6-1.1-0.2-1.6 0.1-2 29.7-42.1 54.8-94.5 72.5-151.4 16.2-52.1 25.7-106.9 28-160.3h514.6C816 635.6 828 684 827 736.6z" fill="" p-id="3976"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1783923934544" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8279" width="32" height="32" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M219.8016 219.8016a51.2 51.2 0 0 1 72.3968 0L512 439.6032l219.8016-219.8016a51.2 51.2 0 1 1 72.3968 72.3968L584.3968 512l219.8016 219.8016a51.2 51.2 0 0 1-72.3968 72.3968L512 584.3968l-219.8016 219.8016a51.2 51.2 0 0 1-72.3968-72.3968L439.6032 512 219.8016 292.1984a51.2 51.2 0 0 1 0-72.3968z" fill="#ffffff" p-id="8280"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1784023543352" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4681" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M853.333333 853.333333a42.666667 42.666667 0 0 1 0 85.333334H170.666667a42.666667 42.666667 0 0 1 0-85.333334h682.666666zM512 85.504a42.666667 42.666667 0 0 1 42.666667 42.666667v515.370666l204.373333-204.373333a42.666667 42.666667 0 0 1 63.914667 56.277333l-3.584 4.010667-277.376 277.546667a42.666667 42.666667 0 0 1-56.32 3.584l-4.010667-3.541334-277.12-276.650666a42.666667 42.666667 0 0 1 56.234667-63.957334l4.010666 3.541334L469.333333 644.096V128.170667a42.666667 42.666667 0 0 1 42.666667-42.666667z" fill="#000000" p-id="4682"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1784181021578" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3711" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M432.51535053 243.09851022C327.13993482 304.56318695 213.22056651 394.04167229 140.10504533 454.31537422a25.89076502 25.89076502 0 0 1-32.93305363-39.97534103c73.52977423-60.58439111 190.08999939-152.28948268 299.24546728-215.98076564 54.31882547-31.69029689 108.63765216-57.52928119 155.86240708-68.40340266 46.24090666-10.61521423 94.91554608-8.64751525 125.3113055 29.30834727 12.27222282 15.32733273 11.85797098 34.53828149 9.0617671 48.98532739-2.95154725 15.27555201-9.73492739 31.58673423-18.12353541 47.32832001a418.80902163 418.80902163 0 0 1-70.52644503 93.51744475c-51.78153127 51.72974933-128.26285155 128.26285155-204.74417304 230.22068622-36.50597925 48.67463941-45.15349451 89.42670339-40.59672059 113.19442606 2.1748243 11.39193719 7.14585125 18.22709927 12.73825658 22.11071409 5.69596799 3.93539675 14.96486283 6.83516208 29.360128 4.76390043 14.49882905-2.07126164 36.97201303-11.18481067 65.40007349-26.5121434a1204.95622281 1204.95622281 0 0 0 89.06423349-54.525952c21.54111644-14.29170252 42.66798102-28.73874963 62.24139969-42.15016653l23.61237808-16.15583763c25.01047941-16.98434253 47.63900799-32.00098608 62.44852622-39.40574457 31.06891852-15.53445925 75.60103466-22.73209245 116.56022638-15.27555201 41.73591347 7.61188503 83.98964266 31.5349523 100.81864059 82.02194488 27.3406483 81.97016295-12.42756741 156.32844208-59.08272712 209.50807347-47.12119349 53.6974471-106.72173511 92.43003259-135.14979555 106.66995439a25.89076502 25.89076502 0 1 1-23.1463443-46.34447053c23.45703348-11.70262638 77.36160711-46.60337778 119.35642904-94.44951229 42.46085571-48.36395023 67.41955318-103.45949867 48.93354667-158.96930016-9.06176831-27.23708564-31.5349523-42.15016653-60.99864295-47.4836634-30.1886329-5.48884268-63.32881184 0.25890725-84.09320653 10.61521423-11.08124801 5.54062339-30.55110281 18.27887999-56.49364976 35.93638161l-23.09456359 15.79336773c-19.72876325 13.51497955-41.42522429 28.37627851-63.38059377 42.87510755-30.91357392 20.50548622-63.22524918 40.90740978-92.99962903 56.95968355-28.99765689 15.53445925-58.35778489 28.68696769-82.64332325 32.15633067-24.44088298 3.46936298-47.53544533-0.56959644-66.12501452-13.4114169-18.64135111-12.84181925-29.87794371-32.62236445-34.17581037-54.99198578-8.388608-43.65183052 8.85464178-99.10985008 50.02095882-153.99827198 78.70792651-104.96116266 157.41585423-183.66909038 209.19738428-235.50240237l0.31068919-0.25890847c24.3373191-24.3373191 47.32831881-54.73307853 61.51645866-81.29700264 7.14585125-13.35963497 11.39193719-24.54444563 12.94538313-32.77770904a24.18197451 24.18197451 0 0 0 0.56959643-7.7154489c-11.23659261-13.20429037-32.77770903-19.41807408-72.49414281-10.25274312-39.56108917 9.06176831-88.49463584 31.79385955-141.36357925 62.65565275z" p-id="3712"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1784024265969" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3745" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M170.666667 170.666667v213.333333H85.333333V85.333333h298.666667v85.333334H170.666667z m682.666666 213.333333V170.666667h-213.333333V85.333333h298.666667v298.666667h-85.333334zM170.666667 640v213.333333h213.333333v85.333334H85.333333v-298.666667h85.333334z m682.666666 0h85.333334v298.666667h-298.666667v-85.333334h213.333333v-213.333333z" fill="#2E2F30" p-id="3746"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1784274632614" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4085" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M304.31 230.03c-53.22 0-96.32 43.28-96.32 96.32 0 53.13 43.19 96.32 96.32 96.32 53.04 0 96.23-43.28 96.23-96.32 0-53.22-43.1-96.32-96.23-96.32z m0 138.34c-23.3 0-42.11-18.82-42.11-42.02 0-23.3 18.82-42.11 42.11-42.11 23.21 0 42.02 18.82 42.02 42.11 0 23.21-18.82 42.02-42.02 42.02z m0-138.34c-53.22 0-96.32 43.28-96.32 96.32 0 53.13 43.19 96.32 96.32 96.32 53.04 0 96.23-43.28 96.23-96.32 0-53.22-43.1-96.32-96.23-96.32z m0 138.34c-23.3 0-42.11-18.82-42.11-42.02 0-23.3 18.82-42.11 42.11-42.11 23.21 0 42.02 18.82 42.02 42.11 0 23.21-18.82 42.02-42.02 42.02zM950.95 779.81c0 19.62-15.95 35.39-35.48 35.39H814.85v100.71c0 19.44-15.77 35.21-35.39 35.21-19.53 0-35.3-15.77-35.3-35.3V815.21H643.53c-19.53 0-35.3-15.77-35.3-35.39 0-19.53 15.77-35.3 35.3-35.3h100.62V643.89c0-19.53 15.77-35.3 35.3-35.3 19.62 0 35.39 15.77 35.39 35.3v100.62h100.71c19.54 0 35.4 15.77 35.4 35.3z" p-id="4086"></path><path d="M853.91 64H170.09C111.67 64 64 111.67 64 170.09v683.83C64 912.33 111.67 960 170.09 960h361.27c19.53 0 35.3-15.77 35.3-35.39 0-19.53-15.77-35.39-35.3-35.39H170.09c-19.44 0-35.39-15.86-35.39-35.3V700.97l168.18-132.16 167.01 124.36c0.09 0.09 0.27 0.18 0.36 0.27 1.88 1.34 3.94 2.6 6 3.67 0.27 0 0.45 0.09 0.63 0.18 1.08 0.54 2.15 0.9 3.23 1.25 0.99 0.36 2.06 0.63 3.05 0.9 2.69 0.63 5.47 0.99 8.24 0.99 2.69 0 5.38-0.27 8.06-0.9 1.25-0.36 2.6-0.72 3.85-1.16 1.16-0.45 2.42-0.99 3.58-1.52 0.18-0.09 0.36-0.09 0.45-0.27 1.08-0.54 2.15-1.16 3.23-1.88 0.9-0.54 1.7-1.08 2.51-1.79 0.27-0.18 0.54-0.45 0.81-0.63l0.72-0.63 374.62-309.93v145.96c0 19.53 15.86 35.39 35.39 35.39 19.62 0 35.39-15.86 35.39-35.39V170.09C960 111.67 912.33 64 853.91 64zM490.23 620.06l-166.75-124.1c-14.52-10.66-34.59-8.78-46.68 3.94l-0.09 0.09c-1.34 0.72-2.69 1.61-3.94 2.6L134.69 611.01V170.09c0-19.44 15.95-35.3 35.39-35.3h683.83c19.44 0 35.3 15.86 35.3 35.3v119.97l-398.98 330z" p-id="4087"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1784015779753" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5980" width="32" height="32" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M325.333333 155.264a74.666667 74.666667 0 0 1 74.666667 0l213.333333 123.178667a10.666667 10.666667 0 0 0 10.666667 0l170.666667-98.56c49.792-28.714667 112 7.253333 112 64.682666v448.64a74.666667 74.666667 0 0 1-37.333334 64.682667l-213.333333 123.136a74.666667 74.666667 0 0 1-74.666667 0l-218.709333-126.293333-154.624 89.344c-49.792 28.757333-112-7.168-112-64.682667V330.794667c0-26.666667 14.250667-51.328 37.333333-64.682667l192-110.848zM394.666667 699.306667l192 110.848V341.333333c0-1.536 0.128-3.072 0.341333-4.522666a75.306667 75.306667 0 0 1-5.674667-2.944L394.666667 226.133333V699.306667z m448-454.784a10.666667 10.666667 0 0 0-16-9.216l-170.666667 98.56a74.581333 74.581333 0 0 1-5.717333 2.901333c0.213333 1.450667 0.384 2.986667 0.384 4.522667v468.906666l186.666666-107.818666a10.666667 10.666667 0 0 0 5.333334-9.216V244.565333zM165.333333 321.536a10.666667 10.666667 0 0 0-5.333333 9.258667v448.597333a10.666667 10.666667 0 0 0 16 9.258667l154.666667-89.301334V226.090667L165.333333 321.536z" p-id="5981"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1784023340649" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4043" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M866.73066667 184.53333333H706.80533333c-48.58666667 0-88.11733333 39.53066667-88.11733333 88.11733334v479.75466666c0 48.58666667 39.53066667 88.11733333 88.11733333 88.11733334h159.92533334c48.58666667 0 88.11733333-39.53066667 88.11733333-88.11733334V272.65066667c0-48.58666667-39.52-88.11733333-88.11733333-88.11733334z m9.792 567.88266667c0 5.39733333-4.384 9.792-9.792 9.792H706.80533333c-5.39733333 0-9.792-4.384-9.792-9.792V272.65066667c0-5.39733333 4.384-9.792 9.792-9.792h159.92533334c5.39733333 0 9.792 4.384 9.792 9.792v479.76533333zM318.44266667 194.32533333H158.528c-48.58666667 0-88.11733333 39.53066667-88.11733333 88.11733334v479.75466666c0 48.58666667 39.53066667 88.11733333 88.11733333 88.11733334h159.91466667c48.58666667 0 88.128-39.53066667 88.128-88.11733334V282.44266667c-0.01066667-48.58666667-39.54133333-88.11733333-88.128-88.11733334z m9.78133333 567.872c0 5.39733333-4.39466667 9.792-9.792 9.792H158.528c-5.39733333 0-9.792-4.384-9.792-9.792V282.44266667c0-5.39733333 4.39466667-9.792 9.792-9.792h159.91466667c5.39733333 0 9.792 4.384 9.792 9.792v479.75466666zM512.62933333 395.04c-21.632 0-39.168 17.536-39.168 39.168v169.70666667c0 21.632 17.536 39.168 39.168 39.168 21.632 0 39.168-17.536 39.168-39.168v-169.70666667c-0.01066667-21.632-17.54666667-39.168-39.168-39.168zM512.62933333 93.16266667c-21.632 0-39.168 17.536-39.168 39.168v169.70666666c0 21.632 17.536 39.168 39.168 39.168 21.632 0 39.168-17.536 39.168-39.168v-169.70666666c-0.01066667-21.632-17.54666667-39.168-39.168-39.168zM512.62933333 683.872c-21.632 0-39.168 17.536-39.168 39.168v169.70666667c0 21.632 17.536 39.168 39.168 39.168 21.632 0 39.168-17.536 39.168-39.168v-169.70666667c-0.01066667-21.632-17.54666667-39.168-39.168-39.168z" fill="#040000" p-id="4044"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1784021652179" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7091" width="32" height="32" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M433.99424 295.68c-30.784 31.872-64.32 61.12-100.16 87.36l0.64-5.44c1.6-16 4.48-33.152 7.552-52.416 1.664-9.088 3.328-18.56 4.736-28.672 4.48-28.544 8.64-61.056 6.208-88.384-2.112-24.64-11.968-66.112-55.808-77.632a100.416 100.416 0 0 0-55.552 3.2c-17.92 5.696-37.44 15.168-58.88 28.352-42.56 25.856-97.92 68.736-169.28 132.48a40 40 0 0 0-2.688 57.152c15.36 16.576 41.408 17.728 58.24 2.624 69.952-62.72 120.768-101.504 157.184-123.52 13.824-8.832 28.672-15.872 44.224-21.056l0.64 5.248c1.472 16.64-1.024 40.32-5.568 69.184l-4.096 24.896c-3.52 20.416-7.04 41.856-8.832 60.032a222.08 222.08 0 0 0-1.28 40.448c0.832 11.712 3.712 28.544 14.4 42.88 12.544 16.576 33.088 25.344 53.952 23.04 17.408-2.368 34.112-8.576 48.768-18.176 30.464-18.24 70.784-51.968 123.648-104.192a40 40 0 0 0 12.352-39.744 40.768 40.768 0 0 0-29.888-29.44 41.6 41.6 0 0 0-40.512 11.968V295.68z m494.08-40.448a110.464 110.464 0 0 0-148.608-5.248L377.86624 590.912a166.208 166.208 0 0 0-115.84 62.72c-42.432 49.92-51.072 116.48-50.432 165.76a384 384 0 0 0 12.352 88.96c0.832 3.264 2.048 6.72 3.264 9.984 0.832 1.792 4.16 9.28 10.496 15.744a51.52 51.52 0 0 0 15.872 10.112h0.192l9.856 3.648a403.2 403.2 0 0 0 90.752 12.16c50.432 0.576 118.336-7.936 169.152-49.92 36.032-27.776 58.88-68.928 63.168-113.728l346.688-394.688a105.856 105.856 0 0 0-5.376-145.984v-0.448z m-364.8 444.096l46.272-52.608-79.424-78.08-53.504 45.568a226.048 226.048 0 0 1 86.592 85.12z m307.968-350.656l-207.808 236.608-71.04-69.568 240.768-204.224a27.2 27.2 0 0 1 37.952-0.128c10.56 10.24 10.56 27.008 0.128 37.312z m-546.048 356.288c24.64-29.12 49.792-35.584 71.552-33.344 24.32 2.24 50.24 16.192 70.784 36.416 20.608 20.224 34.56 45.44 37.056 69.312 2.048 21.44-4.48 46.08-34.176 70.784-27.968 23.04-71.36 31.36-114.816 30.72a319.68 319.68 0 0 1-55.936-5.824 266.88 266.88 0 0 1-5.76-54.784c-0.64-42.496 7.808-85.376 31.296-113.28z" p-id="7092"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1784013723665" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4900" width="16" height="16" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M117.734106 458.213254 906.212134 458.213254C925.995765 458.213254 959.972 482.279754 959.972 511.97312 959.972 541.666486 925.995765 565.732986 906.212134 565.732986L117.734106 565.732986C97.950475 565.732986 63.97424 541.666486 63.97424 511.97312 63.97424 482.279754 97.950475 458.213254 117.734106 458.213254Z" p-id="4901"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1784021869946" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="18899" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M954.9 323.2c-14.6-34.4-49.2-49.3-75.7-55.7-25.6-6.2-57.2-8.3-94.1-6-3.3-3.5-6.6-7-10-10.4-34-34-73.7-60.8-117.9-79.5-45.7-19.3-94.3-29.2-144.4-29.2-50 0-98.6 9.8-144.4 29.2-44.2 18.7-83.8 45.4-117.9 79.5S189.7 324.8 171 369c-19.3 45.7-29.2 94.3-29.2 144.4 0 6.9 0.2 13.7 0.6 20.5-28.1 25.6-49.1 50.3-62.7 73.6C66 631 52.8 666.2 67.4 700.6c14.9 35.1 50.8 50 78.2 56.3 18.4 4.2 39.7 6.3 63.7 6.3 9.1 0 18.6-0.3 28.5-0.9 5.8 6.4 11.8 12.6 18 18.6 10.8 10.3 22.3 20.1 34.2 29.1 65 48.4 142 74 223 74 50 0 98.6-9.8 144.4-29.2 44.2-18.7 83.8-45.4 117.9-79.5 34-34 60.8-73.7 79.5-117.9C874.1 611.7 884 563.1 884 513c0-8.7-0.3-17.5-0.9-26.1 25.7-23.8 45.3-46.8 58.3-68.6 14.4-23.9 28.5-60 13.5-95.1z m-653.2-21.3c56.4-56.4 131.5-87.5 211.3-87.5 79.8 0 154.9 31.1 211.3 87.5 43.1 43.1 71.5 97.2 82.4 155.9-64.3 51.9-154.6 103.8-255.9 146.9-100.4 42.7-199.5 71.6-281.1 82.1-7-9.7-13.3-19.9-19.1-30.3-2.9-5.4-5.8-10.9-8.4-16.5-1-2.2-2.1-4.5-3.1-6.7C222.4 595.4 214 555 214 513.2c0.2-79.8 31.3-154.8 87.7-211.3zM134 672.3c-1.7-4-0.4-13.7 8.3-28.7 4.1-7 9.4-14.5 15.9-22.3 4.2 13.9 9.3 27.6 15.2 41 1.2 2.8 2.5 5.6 3.8 8.4 3.1 6.6 6.4 13.2 9.9 19.6-37-3-51-13.1-53.1-18z m590.3 52.2C667.9 781 592.8 812 513 812c-65.3 0-127.3-20.7-179.3-59.8-1.1-0.8-2.2-1.7-3.3-2.5 76.4-15 162.4-42.2 248.6-78.8 87.1-37 167-80.6 231.1-125.7-7.1 67.8-37 130.5-85.8 179.3z m142-324.3c-3.4-10.6-7.3-21-11.6-31.3-5.1-12.1-10.9-23.9-17.2-35.4 9.2 0.8 17.5 2.1 24.8 3.9 16.9 4.1 24.7 9.9 26.5 14 1.9 4.7-0.4 21.2-22.5 48.8z" fill="#333333" p-id="18900"></path><path d="M340.9 507.9c14 8.1 29.9 12.1 46.2 12.1 13 0 26.4-2.6 39.3-7.7 26.7-10.7 50-31.4 65.5-58.3s21.8-57.4 17.7-85.9c-4.5-31.2-21.5-57.4-46.6-71.9-25.1-14.5-56.3-16.1-85.6-4.4-26.7 10.7-50 31.4-65.5 58.3s-21.8 57.4-17.7 85.9c4.6 31.2 21.6 57.4 46.7 71.9z m33.4-121.8c7.4-12.8 18.3-22.8 29.9-27.4 5.1-2 14.8-4.8 22.9-0.1 8 4.6 10.6 14.4 11.3 19.9 1.8 12.4-1.4 26.8-8.8 39.6s-18.3 22.8-29.9 27.4c-5.1 2-14.8 4.8-22.9 0.1-13-7.6-17.3-33.9-2.5-59.5z" fill="#333333" p-id="18901"></path><path d="M650.019364 380.589639a51.1 40.3 51.61 1 0 63.174429-50.053486 51.1 40.3 51.61 1 0-63.174429 50.053486Z" fill="#333333" p-id="18902"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1784274641702" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4401" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M222.830565 208.372093h577.488372v85.049834H222.830565z" fill="#4F4A4A" p-id="4402"></path><path d="M474.578073 258.551495h85.049834v557.076412h-85.049834z" fill="#4F4A4A" p-id="4403"></path><path d="M1022.299003 1022.299003h-1020.598006v-1020.598006h1020.598006z m-935.548172-85.049834h850.498338v-850.498338h-850.498338z" fill="#4F4A4A" p-id="4404"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1784180628538" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3805" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M581.79729167 403.865h-389.720625l189.253125-186.705c16.6753125-16.6753125 16.6753125-40.4859375 0-57.1621875s-38.9578125-17.4759375-55.70625-0.729375l-256.0284375 250.12875c-16.6753125 16.6753125-16.6753125 38.8115625 0 55.486875l256.0996875 250.4203125c16.6753125 16.6753125 38.9578125 16.6753125 55.70625-0.073125 16.6753125-16.6753125 16.6753125-38.593125 0-55.2684375l-189.32625-183.2821875h389.720625c155.8303125 0 291.7078125 121.605 291.7078125 271.9021875v38.9578125c0 22.2834375 14.1271875 38.9578125 36.40875 38.9578125s36.40875-16.6753125 36.40875-38.9578125v-38.9578125c0-194.86125-164.1309375-344.71875-364.52625-344.71875z" fill="#666666" p-id="3806"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1784274676151" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3898" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M631.89333364 841.59999969H231.25333302a160.00000031 160.00000031 0 0 1-159.99999938-159.99999938V333.12000031a160.00000031 160.00000031 0 0 1 159.99999938-160.00000031h400.64000062a160.00000031 160.00000031 0 0 1 159.99999938 160.00000031V681.60000031a160.00000031 160.00000031 0 0 1-159.99999938 159.99999938zM231.25333302 237.12000031a96 96 0 0 0-96 96V681.60000031a96 96 0 0 0 96 96h400.64000062a96 96 0 0 0 96-96V333.12000031a96 96 0 0 0-96-96z" fill="#282424" p-id="3899"></path><path d="M905.17333302 774.08a80.32000031 80.32000031 0 0 1-57.27999938-24l-103.68-106.24000031a31.99999969 31.99999969 0 0 1 0-45.12 31.99999969 31.99999969 0 0 1 45.43999969 0L893.65333302 704a15.67999969 15.67999969 0 0 0 17.60000062 3.52000031 15.67999969 15.67999969 0 0 0 9.91999969-14.72000062V320a15.67999969 15.67999969 0 0 0-9.91999969-14.71999969 16.00000031 16.00000031 0 0 0-17.60000062 3.51999938l-111.36 113.92000031a31.99999969 31.99999969 0 0 1-45.75999938-44.80000031l111.36-113.91999938A80.32000031 80.32000031 0 0 1 985.17333364 320v374.08000031A79.36000031 79.36000031 0 0 1 935.25333364 768.00000031a76.48000031 76.48000031 0 0 1-30.08000062 6.07999969zM385.17333364 387.51999969h-160.00000031a31.99999969 31.99999969 0 0 1 0-63.99999938h160.00000031a31.99999969 31.99999969 0 0 1 0 63.99999938z" fill="#282424" p-id="3900"></path></svg>
\ No newline at end of file
import { defineStore } from 'pinia'
import { ref } from 'vue'
const REDIRECT_KEY = 'logout_redirect_path'
export const useAuthStore = defineStore('auth', () => {
const token = ref('')
const username = ref('')
function login(user: string) {
username.value = user
token.value = 'demo-token'
localStorage.setItem('auth_token', token.value)
localStorage.setItem('auth_user', user)
}
function logout(currentPath?: string) {
if (currentPath) {
localStorage.setItem(REDIRECT_KEY, currentPath)
}
username.value = ''
token.value = ''
localStorage.removeItem('auth_token')
localStorage.removeItem('auth_user')
}
function restore() {
const savedToken = localStorage.getItem('auth_token')
const savedUser = localStorage.getItem('auth_user')
if (savedToken && savedUser) {
token.value = savedToken
username.value = savedUser
}
}
function getSavedRedirect() {
return localStorage.getItem(REDIRECT_KEY) || ''
}
function clearSavedRedirect() {
localStorage.removeItem(REDIRECT_KEY)
}
return { token, username, login, logout, restore, getSavedRedirect, clearSavedRedirect }
})
This diff is collapsed.
<template>
<div class="home-page">
<ElPageHeader content="首页" />
<div class="home-card">
<ElCard class="welcome-card">
<div class="welcome-header">
<div>
<h3>欢迎,{{ auth.username || '访客' }}</h3>
<p>这是一个基于 Vue 3 + Element Plus + Pinia + Vue Router 的最佳实践示例。</p>
</div>
<ElButton type="primary" @click="gotoPage('/vueFlow')">画布</ElButton>
<ElButton type="primary" @click="gotoPage('/video')">视频抽取图片</ElButton>
<ElButton type="primary" @click="gotoPage('/video1')">视频截取</ElButton>
<ElButton type="primary" @click="gotoPage('/3d')">3D效果</ElButton>
<ElButton type="primary" @click="gotoPage('/shili')">示例</ElButton>
</div>
</ElCard>
<ElCard class="request-card">
<div class="card-title">
<span>接口请求示例</span>
<ElTag type="success">{{ env }}</ElTag>
</div>
<div class="request-actions">
<ElButton type="primary" :loading="loading" @click="fetchUsers">刷新用户数据</ElButton>
</div>
<ElSkeleton :loading="loading" animated>
<template #template>
<ElSkeletonItem variant="text" style="width: 180px; margin-bottom: 16px" />
<ElSkeletonItem variant="rect" style="height: 240px" />
</template>
<template #default>
<ElTable v-if="users.length" :data="users" border size="small">
<ElTableColumn prop="id" label="ID" width="80" />
<ElTableColumn prop="name" label="姓名" />
<ElTableColumn prop="username" label="用户名" />
<ElTableColumn prop="email" label="邮箱" />
<ElTableColumn prop="website" label="网站" />
</ElTable>
<div v-else class="empty-tip">暂无用户数据,请点击“刷新用户数据”获取。</div>
</template>
</ElSkeleton>
</ElCard>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import { useAuthStore } from '../store';
import { getUsers, type User } from '../api/user';
const router = useRouter();
const auth = useAuthStore();
const users = ref<User[]>([]);
const loading = ref(false);
const env = import.meta.env.VITE_APP_ENV || 'production';
auth.restore();
async function fetchUsers() {
loading.value = true;
try {
users.value = await getUsers();
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
ElMessage.error(`获取用户失败:${message}`);
} finally {
loading.value = false;
}
}
function gotoPage(url: string) {
router.push(url);
}
function shili() {
router.push('/shili');
}
onMounted(() => {
fetchUsers();
});
</script>
<style scoped>
.home-page {
padding: 24px;
background: #f0f2f5;
}
.home-card {
max-width: 960px;
margin: 24px auto;
display: flex;
flex-direction: column;
gap: 20px;
}
.welcome-card {
padding: 20px;
}
.welcome-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 24px;
}
.request-card {
padding: 20px;
}
.card-title {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.request-actions {
margin-bottom: 18px;
}
.empty-tip {
padding: 24px 0;
text-align: center;
color: #909399;
}
.container {
/* 关键:设置透视距离 */
perspective: 600px;
perspective-origin: left center;
/* 透视原点在左边 */
}
.trapezoid {
width: 400px;
height: 200px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 8px;
/* 🎯 核心:绕 Y 轴旋转,右边会变小 */
transform: rotateY(25deg);
/* 保持 3D 空间 */
transform-style: preserve-3d;
/* 装饰 */
display: flex;
justify-content: center;
align-items: center;
color: #fff;
font-family: 'Segoe UI', sans-serif;
font-size: 24px;
font-weight: bold;
box-shadow: 0 20px 60px rgba(102, 126, 234, 0.3);
transition: transform 0.4s ease;
cursor: pointer;
}
/* 悬浮时角度变化 */
.trapezoid:hover {
transform: rotateY(30deg);
}
</style>
<template>
<div class="page-container flex items-center justify-center">
<div class="login-card round-16 p-20 bg-fff">
<div class="flex items-start justify-between gap-12 mb-20">
<div>
<h2 class="fs-18 fw-700 color-333 mb-0">登录</h2>
<p class="color-999 fs-13 mt-8">未注册手机号将自动创建账号</p>
</div>
<button v-if="false" type="button" class="w-30 h-30 border-none round-4 fs-24 pointer close-btn" @click="onClose">×</button>
</div>
<el-form :model="form" ref="loginForm" status-icon label-position="top">
<el-form-item label="手机号" prop="phone" class="mb-18">
<div class="flex items-center gap-10 w-full">
<el-input v-model="form.phone" placeholder="请输入手机号" autocomplete="tel" class="w-full" size="large">
<template #prefix>
<span class="color-aaa fs-14">+86</span>
</template>
</el-input>
</div>
</el-form-item>
<el-form-item label="验证码" prop="code" class="mb-18">
<div class="flex items-center gap-10 w-full">
<el-input v-model="form.code" placeholder="请输入验证码" maxlength="6" class="w-full" size="large" />
<el-button class="code-button px-16" size="large" @click="sendCode">获取验证码</el-button>
</div>
</el-form-item>
<el-form-item class="mb-18">
<div class="flex gap-10 color-aaa fs-13">
<el-checkbox v-model="agree">我已阅读并同意</el-checkbox>
<div class="flex flex-wrap items-center gap-6">
<a href="#" style="color: #9022fd">《用户协议》</a>
<span class="color-333"></span>
<a href="#" style="color: #9022fd">《隐私政策》</a>
</div>
</div>
</el-form-item>
<el-form-item>
<el-button type="primary" class="w-full py-12 fs-16 round-999" size="large" :disabled="!disabled" @click="onSubmit">登录</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { useAuthStore } from '../store'
const router = useRouter()
const route = useRoute()
const authStore = useAuthStore()
const loginForm = ref()
const form = ref({ phone: '', code: '' })
const agree = ref(false)
const regular = /^1[3-9]\d{9}$/
const disabled = computed(()=>{
let phoneValid = regular.test(form.value.phone)
let codeValid = form.value.code.length === 6
return phoneValid && codeValid && agree.value
})
function sendCode() {
let phoneValid = regular.test(form.value.phone)
if (!phoneValid) {
ElMessage.warning('请输入手机号后获取验证码')
return
}
ElMessage.success('验证码已发送')
}
function onSubmit() {
if (!agree.value) {
ElMessage.warning('请先同意用户协议和隐私政策')
return
}
loginForm.value.validate((valid: boolean) => {
if (!valid) return
authStore.login(form.value.phone)
const redirect = (route.query.redirect as string) || authStore.getSavedRedirect() || '/'
authStore.clearSavedRedirect()
ElMessage.success('登录成功')
router.push(redirect)
})
}
function onClose() {
router.back()
}
</script>
<style scoped lang="scss">
.page-container {
min-height: 100vh;
}
.login-card {
width: min(400px, 100%);
border: 1px solid rgba(255, 255, 255, 0.07);
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.24);
}
.close-btn {
background: rgba(255, 255, 255, 0);
color: #d1d9e6;
&:hover {
background: rgba(0, 0, 0, 0.1);
color: #333;
}
}
.el-form-item__label {
color: #d1d9e6;
font-size: 14px;
}
.phone-prefix {
min-width: 72px;
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.08);
}
</style>
<template>
<div class="not-found">
<ElResult status="404" title="404" sub-title="抱歉,页面不存在。" />
<ElButton type="primary" @click="goHome">返回首页</ElButton>
</div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
const router = useRouter()
function goHome() {
router.push('/')
}
</script>
<style scoped>
.not-found {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 16px;
background: #f5f7fa;
}
</style>
<template>
<div class="flex items-center justify-between">
<div>
<div class="fs-28 font-bold title">欢迎回到梦畅 AIGC 电商平台</div>
<div class="color-666">全链路 AI 智能化智能生成、批量裂变与广告投放协同工作台</div>
</div>
<el-button class="w-120 h-40 fs-16 round-200" size="large">消息中心</el-button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
</script>
<style lang="scss" scoped>
.title {
background: linear-gradient(120deg, #a855f7, #d946ef, #ec4899);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
</style>
\ No newline at end of file
<template>
<section class="section2-card round-12 p-16 bg-fff">
<div class="section2-head mb-24">
<div class="fs-20 fw-700 color-000">全平台资源精准搜索</div>
<p class="fs-14 color-666">支持成片、素材、脚本、图片与音频分类检索</p>
</div>
<div class="search-row flex items-center gap-12 mb-18">
<el-input v-model="keyword" placeholder="搜成片、搜素材、搜脚本、搜图片、搜音频" class="w-full input-with-select" clearable
@keyup.enter="onSearch">
<template #prepend>
<el-select v-model="typeVal" placeholder="Select" style="width: 100px">
<el-option v-for="(item, index) in options" :key="index" :label="item.label"
:value="item.value" />
</el-select>
</template>
</el-input>
<el-button type="primary" class="fs-16 px-24 h-48" @click="onSearch">搜索成片</el-button>
</div>
<div class="hot-keywords flex flex-wrap gap-12 items-center">
<span class="color-666">热搜词:</span>
<span class="keyword-tag round-4 px-10 py-2 bg-eee">美妆KOC口播</span>
<span class="keyword-tag round-4 px-10 py-2 bg-eee">高转化服装Lookbook</span>
<span class="keyword-tag round-4 px-10 py-2 bg-eee">智能抠水印示范</span>
<span class="keyword-tag round-4 px-10 py-2 bg-eee">3D数码特效</span>
<span class="keyword-tag round-4 px-10 py-2 bg-eee">爆款短视频裂变</span>
<span class="keyword-tag round-4 px-10 py-2 bg-eee">数码产品细节特写</span>
</div>
</section>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const keyword = ref('');
const typeVal = ref(1);
const options = ref([
{ value: 1, label: '成片' },
{ value: 2, label: '素材' },
{ value: 3, label: '脚本' },
{ value: 4, label: '图片' },
{ value: 5, label: '音频' },
]);
function onSearch() {
console.log('搜索关键词:', keyword.value);
}
</script>
<style lang="scss" scoped>
.search-row :deep(.el-input__inner) {
border-radius: 16px;
font-size: 14px;
}
:deep(.input-with-select) {
.el-input-group__prepend {
background-color: #fff;
}
}
:deep(.el-select__wrapper) {
height: 48px;
}
.search-row :deep(.el-input__inner::placeholder) {
color: #9fa6b8;
}
.keyword-tag {
display: inline-flex;
align-items: center;
justify-content: center;
border: 1px solid rgba(0, 0, 0, 0.08);
}
</style>
<template>
<div class="bg-fff round-12 p-16">
<div class="flex items-center justify-between">
<div class="flex items-center">
<div class="w-40 h-40 bg-main round-6 mr-10" style="background-color: rgba(var(--main-color), 0.1);"></div>
<div class="section2-head">
<div class="fs-16 fw-700 color-000">全平台资源精准搜索</div>
<p class="fs-14 color-666">支持成片、素材、脚本、图片与音频分类检索</p>
</div>
</div>
<div class="flex-items-center color-888 pointer">
<svg t="1786427654955" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="6217" width="16" height="16">
<path
d="M566.2 166.9c-11.7-11.7-30.8-11.7-42.4 0L509.7 181c-11.7 11.7-11.7 30.8 0 42.4l254.9 254.9c11.7 11.7 11.7 30.8 0 42.4L509.2 776.1c-11.7 11.7-11.7 30.8 0 42.4l14.1 14.1c11.7 11.7 30.8 11.7 42.4 0l311.1-311.1c11.7-11.7 21.1-21.3 21-21.4-0.1-0.1 0-0.4 0.2-0.6 0.2-0.2-9.1-10-20.8-21.6l-311-311z"
fill="#888888" p-id="6218"></path>
<path
d="M769 460c16.5 0 30 13.5 30 30v20c0 16.5-13.5 30-30 30H129c-16.5 0-30-13.5-30-30v-20c0-16.5 13.5-30 30-30h640z"
fill="#888888" p-id="6219"></path>
</svg>
<span>全部</span>
</div>
</div>
<div class="flex mt-20">
<el-button v-for="item in buttonList" :key="item.value" class="round-6 px-8"
:class="tabIndex === item.value ? 'active' : ''" @click="tabIndex = item.value">
<span>{{ item.label }}</span>
<span v-if="item.num > 0" class="badge round-100 color-fff px-8 py-2 fs-10 ml-4">
{{ item.num }}
</span>
</el-button>
</div>
<section class="mt-20">
<div class="round-12 px-12 flex-items-center bg-hh py-10 mt-16 mb-10">
<div>分类细项:</div>
<div class="flex-items-center gap-10">
<span v-for="item in buttonList" :key="item.value" class="round-100 px-12 py-4 fs-12 bg-eee pointer"
:class="tabIndex === item.value ? 'active1' : ''" @click="tabIndex = item.value">{{ item.label
}}</span>
</div>
</div>
<div v-for="(item, index) in list" :key="index" class="round-6 p-12 mt-10"
style="background-color: #88888810;">
<div class="flex-items-center justify-between">
<div class="fs-12">
<span class="color-main px-10 py-2 round-10 mr-10"
style="background-color: rgba(var(--main-color), 0.1);">上传视频</span>
<span class="color-999">2026-03-25 14:25:34</span>
</div>
<span class="fs-12 px-8 py-2 round-100 bg-eee" style="color: red;">未读</span>
</div>
<div class="mt-10 ellipsis">视频《冬季风衣短视频_01.mp4》上传成功,可前往创作工作台进行下一步编辑与智能渲染。</div>
</div>
</section>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const tabIndex = ref(1)
const buttonList = [
{ label: '创作', value: 1, num: 6 },
{ label: '卡审', value: 2, num: 3 },
{ label: '审核', value: 3, num: 0 },
{ label: '任务', value: 4, num: 2 },
{ label: '账号', value: 5, num: 0 },
{ label: '导出', value: 6, num: 0 },
]
const list = ref([1, 2, 3])
</script>
<style lang="scss" scoped>
.badge {
background-color: red;
}
.active {
background-color: #9022fd;
color: #fff;
.badge {
background-color: #fff;
color: #9022fd;
}
}
.active1 {
background-color: #a865ec;
color: #fff;
.badge {
background-color: #fff;
color: #9022fd;
}
}
.bg-hh {
background: #f4f6f7;
}
</style>
\ No newline at end of file
<template>
<div>
<div class="bg-fff round-12 p-16">
<div class="flex items-center justify-between">
<div class="flex items-center">
<div class="w-40 h-40 bg-main round-6 mr-10" style="background-color: rgba(var(--main-color), 0.1);"></div>
<div class="section2-head">
<div class="fs-16 fw-700 color-000">任务中心</div>
<p class="fs-14 color-666">三大类任务数量概览 (点击数字进入协作)</p>
</div>
</div>
<div class="flex-items-center color-888 pointer">
<svg t="1786427654955" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="6217" width="16" height="16">
<path
d="M566.2 166.9c-11.7-11.7-30.8-11.7-42.4 0L509.7 181c-11.7 11.7-11.7 30.8 0 42.4l254.9 254.9c11.7 11.7 11.7 30.8 0 42.4L509.2 776.1c-11.7 11.7-11.7 30.8 0 42.4l14.1 14.1c11.7 11.7 30.8 11.7 42.4 0l311.1-311.1c11.7-11.7 21.1-21.3 21-21.4-0.1-0.1 0-0.4 0.2-0.6 0.2-0.2-9.1-10-20.8-21.6l-311-311z"
fill="#888888" p-id="6218"></path>
<path
d="M769 460c16.5 0 30 13.5 30 30v20c0 16.5-13.5 30-30 30H129c-16.5 0-30-13.5-30-30v-20c0-16.5 13.5-30 30-30h640z"
fill="#888888" p-id="6219"></path>
</svg>
<span>全部</span>
</div>
</div>
<section class="mt-20">
<div v-for="(value, index) in list" :key="index" class="round-6 p-12 mt-10 flex justify-between"
style="background-color: #88888810;">
<div class="">
<span class="font-bold">给我的任务</span>
<div class="flex items-center gap-10 mt-6">
<el-tag type="warning" round>待完成2</el-tag>
<el-tag type="success" round>已达标4</el-tag>
</div>
</div>
</div>
</section>
</div>
<div class="bg-fff round-12 p-16 mt-20">
<div class="flex items-center">
<div class="w-40 h-40 bg-main round-6 mr-10" style="background-color: rgba(var(--main-color), 0.1);"></div>
<div class="section2-head">
<div class="fs-16 fw-700 color-000">帮助中心</div>
<p class="fs-14 color-666">快速查阅文档、视频、客服与反馈</p>
</div>
</div>
<section class="flex flex-wrap gap-10 mt-20">
<div v-for="(item, index) in helpItems" :key="index" class="round-6 p-12 flex-center flex-col"
style="background-color: #88888810;flex: 0 0 calc((100% - 10px*2)/3);">
<span class="font-bold">⬇️</span>
<span class="font-bold">{{ item.label }}</span>
</div>
</section>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const list = ref([1, 2])
const helpItems = [
{ label: '帮助文档', value: 1, num: 6 },
{ label: '视频教程', value: 2, num: 3 },
{ label: '意见反馈', value: 3, num: 0 },
{ label: '联系客服', value: 4, num: 2 },
{ label: '常见问题', value: 5, num: 0 },
]
</script>
<style lang="scss" scoped></style>
\ No newline at end of file
<template>
<div class="bg-fff round-12 p-16">
<div class="flex items-center justify-between">
<div class="flex items-center">
<div class="w-40 h-40 bg-main round-6 mr-10" style="background-color: rgba(var(--main-color), 0.1);"></div>
<div class="section2-head">
<div class="fs-16 fw-700 color-000">更新记录</div>
<p class="fs-14 color-666">算法迭代与功能发布日志 (点击小卡片查看完整详情)</p>
</div>
</div>
<div class="flex-items-center color-888 pointer">
<svg t="1786427654955" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="6217" width="16" height="16">
<path
d="M566.2 166.9c-11.7-11.7-30.8-11.7-42.4 0L509.7 181c-11.7 11.7-11.7 30.8 0 42.4l254.9 254.9c11.7 11.7 11.7 30.8 0 42.4L509.2 776.1c-11.7 11.7-11.7 30.8 0 42.4l14.1 14.1c11.7 11.7 30.8 11.7 42.4 0l311.1-311.1c11.7-11.7 21.1-21.3 21-21.4-0.1-0.1 0-0.4 0.2-0.6 0.2-0.2-9.1-10-20.8-21.6l-311-311z"
fill="#888888" p-id="6218"></path>
<path
d="M769 460c16.5 0 30 13.5 30 30v20c0 16.5-13.5 30-30 30H129c-16.5 0-30-13.5-30-30v-20c0-16.5 13.5-30 30-30h640z"
fill="#888888" p-id="6219"></path>
</svg>
<span>全部</span>
</div>
</div>
<section class="mt-20 flex gap-10">
<div v-for="(item, index) in list" :key="index" class="round-6 p-12"
style="background-color: #88888810; flex: 0 0 calc((100% - 10px * 3) / 4);overflow: hidden;">
<div class="flex-items-center justify-between">
<div class="fs-12 flex gap-6">
<span class="bg-000 round-4 px-6 py-2 color-fff">v1.0.0</span>
<span class="round-4 px-6 py-2 color-main"
style="background-color: rgba(var(--main-color), 0.1);">算法升级</span>
</div>
<span class="fs-12 px-6 py-2 color-999">2026-10-25</span>
</div>
<div class="mt-10 ellipsis font-bold">视频《冬季风衣短视频_01.mp4》上传成功,可前往创作工作台进行下一步编辑与智能渲染。</div>
<div class="mt-10 ellipsis color-888">
冬季风衣短视频_01.mp4》上传成功,可前往创作工作台进行下一步编辑与智能渲染冬季风衣短视频_01.mp4》上传成功,可前往创作工作台进行下一步编辑与智能渲染</div>
</div>
</section>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const tabIndex = ref(1)
const buttonList = [
{ label: '创作', value: 1, num: 6 },
{ label: '卡审', value: 2, num: 3 },
{ label: '审核', value: 3, num: 0 },
{ label: '任务', value: 4, num: 2 },
{ label: '账号', value: 5, num: 0 },
{ label: '导出', value: 6, num: 0 },
]
const list = ref([1, 2, 3, 4])
</script>
<style lang="scss" scoped>
.badge {
background-color: red;
}
.active {
background-color: #9022fd;
color: #fff;
.badge {
background-color: #fff;
color: #9022fd;
}
}
.active1 {
background-color: #a865ec;
color: #fff;
.badge {
background-color: #fff;
color: #9022fd;
}
}
.bg-hh {
background: #f4f6f7;
}
</style>
\ No newline at end of file
<template>
<div class="content">
<Section1 class="pt-20" />
<Section2 class="mt-40" />
<div class="flex gap-20 mt-40">
<Section3 style="width: 60%;" />
<Section4 style="width: 40%;" />
</div>
<Section5 class="mt-40" />
</div>
</template>
<script setup lang="ts">
import Section1 from './components/section1.vue'
import Section2 from './components/section2.vue'
import Section3 from './components/section3.vue'
import Section4 from './components/section4.vue'
import Section5 from './components/section5.vue'
</script>
<style lang="scss" scoped>
.content {
width: 1400px;
margin: 0 auto;
}
</style>
\ No newline at end of file
<template>
<div class="p-12 w-full h-full flex flex-col relative">
<el-checkbox :value="item.id" class="full-area-checkbox" :style="{ zIndex: isCheck ? 1 : -1 }" />
<div class="flex justify-between">
<div>
<div class="font-bold color-000">刘若英 - 后来</div>
<div class="color-888 fs-12">柔和 | 云音乐</div>
</div>
<span class="color-888 fs-12">25</span>
</div>
<div class="flex-items-center audio-progress-only mt-10">
<audio ref="audioRef" src="../../../../../public/houlai.mp3" @timeupdate="onTimeUpdate" @ended="onEnded"
@loadedmetadata="onLoadedMetadata"></audio>
<div @click="togglePlay" class="minw-44 h-44 round flex-center mr-16 play">{{ isPlaying ? '⏸️' : '▶' }}
</div>
<el-slider v-model="currentTime" :max="duration" :step="0.1" :show-tooltip="false" @change="onSliderChange"
@drag-start="isDragging = true" @drag-end="isDragging = false" size="small" />
<div class="flex-items-center gap-2 ml-10"><span>{{ formatTime(currentTime) }}</span>/<span
class="color-888">{{ formatTime(duration) }}</span></div>
</div>
<div class="flex-items-center justify-between pt-10 mt-10" style="border-top: 1px solid #f5f5f5;">
<div class="flex-items-center gap-4">
<div class="bg-main w-30 h-30 round"></div>
<div>张晓华</div>
</div>
<div class="fs-12 color-999">3小时前</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const props = defineProps({
item: {
type: Object,
default: () => { }
},
isCheck: {
type: Boolean,
default: false
}
})
const audioRef = ref<HTMLAudioElement | null>(null);
const currentTime = ref(0);
const duration = ref(0);
const isDragging = ref(false);
const isPlaying = ref(false);
// 切换播放
const togglePlay = async () => {
if (!audioRef.value) return;
if (isPlaying.value) {
audioRef.value.pause();
isPlaying.value = false;
} else {
try {
await audioRef.value.play();
isPlaying.value = true;
} catch (err) {
isPlaying.value = false;
return;
}
}
};
// 改变进度条
const onSliderChange = (val: number) => {
if (audioRef.value) {
audioRef.value.currentTime = val;
}
}
// 音频元数据加载完成 (获取总时长)
const onLoadedMetadata = () => {
if (audioRef.value) {
duration.value = audioRef.value.duration;
}
};
// 播放进度更新事件
const onTimeUpdate = () => {
if (!audioRef.value || isDragging.value) return; // 拖动时停止同步,防止滑块跳动
currentTime.value = audioRef.value.currentTime;
};
// 播放结束
const onEnded = () => {
isPlaying.value = false;
currentTime.value = 0;
};
// 格式化时间 (mm:ss)
const formatTime = (time: number) => {
if (!time || isNaN(time)) return '0:00';
const minutes = Math.floor(time / 60);
const seconds = Math.floor(time % 60);
return `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
};
</script>
<style lang="scss" scoped>
.play {
border: 2px solid rgba(var(--main-color), 1);
}
:deep(.el-slider__button) {
background-color: rgb(var(--main-color));
height: 14px;
width: 14px;
}
</style>
\ No newline at end of file
<template>
<div class="p-20">
<Classify />
<div class="mt-20">
<div class="mb-20 flex">
<el-button @click="switchCheck">{{ isCheck ? '取消选择' : '请选择' }}</el-button>
<el-button type="primary">上传文件</el-button>
<div class="round-6 bg-fff p-4 ml-12 flex gap-2" style="border: 1px solid #ddd;">
<div class="round-6 w-24 h-24 flex-center pointer" :class="btnIndex === 1 ? 'active' : ''" link @click="btnIndex = 1">
<SvgIcon name="HD" size="18" class="iconscoal" :color="btnIndex === 1? 'rgba(var(--main-color), 1)': ''" />
</div>
<div class="round-6 w-24 h-24 flex-center pointer" :class="btnIndex === 2 ? 'active' : ''" link @click="btnIndex = 2">
<SvgIcon name="redraw" size="18" class="iconscoal" :color="btnIndex === 2? 'rgba(var(--main-color), 1)': ''"/>
</div>
<div class="round-6 w-24 h-24 flex-center pointer" :class="btnIndex === 3 ? 'active' : ''" link @click="btnIndex = 3">
<SvgIcon name="room" size="18" class="iconscoal" :color="btnIndex === 3? 'rgba(var(--main-color), 1)': ''"/>
</div>
</div>
</div>
<div class="flex flex-wrap gap-20">
<el-checkbox-group v-model="checkedList" class="flex flex-wrap gap-20 w-full">
<div v-for="(item, index) in list" :key="index"
class="bg-fff round-12 relative hidden flex-item"
:class="{ 'is-selected': checkedList.includes(item.id) }">
<Item :item="item" :isCheck="isCheck" />
</div>
</el-checkbox-group>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import Classify from '../components/Classify.vue';
import Item from '@/views/resource/audio/components/Item.vue'
const checkedList = ref<any>([])
const btnIndex = ref(0)
const isCheck = ref(false)
const list = ref([{ id: '1' }, { id: '2' }, { id: '3' }]);
const switchCheck = () => {
isCheck.value = !isCheck.value
checkedList.value = []
}
</script>
<style scoped lang="scss">
.flex-item {
flex: 0 0 calc((100% - 20px * 5) / 6);
border-radius: 8px;
box-sizing: border-box;
}
.active {
background-color: rgba(var(--main-color), 0.1);
}
/* 1600px 以下:一行 5 个 */
@media (max-width: 2100px) {
.flex-item {
flex: 0 0 calc((100% - 20px * 4) / 5);
}
}
/* 1200px 以下:一行 4 个 */
@media (max-width: 1800px) {
.flex-item {
flex: 0 0 calc((100% - 20px * 3) / 4);
}
}
/* 900px 以下:一行 3 个 */
@media (max-width: 1400px) {
.flex-item {
flex: 0 0 calc((100% - 20px * 2) / 3);
}
}
// 复选框的样式
.is-selected {
box-shadow: 0 0 2px 2px rgb(var(--main-color));
}
.el-checkbox-group {
font-size: inherit;
line-height: inherit;
}
</style>
\ No newline at end of file
<template>
<div class="p-20">
<Classify />
<div class="mt-20">
<div class="mb-20">
<el-button @click="switchCheck">{{ isCheck ? '取消选择' : '请选择' }}</el-button>
<el-button type="primary">上传文件</el-button>
</div>
<div class="flex flex-wrap gap-20">
<el-checkbox-group v-model="checkedList" class="flex flex-wrap gap-20 w-full">
<div v-for="(item, index) in list" :key="index"
class="w-200 bg-fff round-12 relative hidden flex-item"
:class="{ 'is-selected': checkedList.includes(item.id) }">
<Item :item="item" :isCheck="isCheck" />
</div>
</el-checkbox-group>
</div>
</div>
<UploadVideoDialog />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import Classify from '../components/Classify.vue';
import Item from '@/views/resource/components/VideoItem.vue'
import UploadVideoDialog from './components/UploadVideoDialog.vue';
const checkedList = ref<any>([])
const isCheck = ref(false)
const list = ref([{ id: '1' }, { id: '2' }, { id: '3' }]);
const switchCheck = () => {
isCheck.value = !isCheck.value
checkedList.value = []
}
</script>
<style scoped lang="scss">
.flex-item {
flex: 0 0 calc((100% - 20px * 5) / 6);
border-radius: 8px;
box-sizing: border-box;
aspect-ratio: 1 / 1.5;
}
/* 1600px 以下:一行 5 个 */
@media (max-width: 2100px) {
.flex-item {
flex: 0 0 calc((100% - 20px * 4) / 5);
}
}
/* 1200px 以下:一行 4 个 */
@media (max-width: 1800px) {
.flex-item {
flex: 0 0 calc((100% - 20px * 3) / 4);
}
}
/* 900px 以下:一行 3 个 */
@media (max-width: 1400px) {
.flex-item {
flex: 0 0 calc((100% - 20px * 2) / 3);
}
}
// 复选框的样式
.is-selected {
box-shadow: 0 0 2px 2px rgb(var(--main-color));
}
.el-checkbox-group {
font-size: inherit;
line-height: inherit;
}
</style>
\ No newline at end of file
<template>
<section class="bg-fff round-12 px-12 py-20">
<div class="flex">
<div class="w-100 text-right mt-2">主类目:</div>
<div class="flex flex-wrap gap-12 flex-1">
<a href="" class="color-888 px-6 h-26 py-2" v-for="(tag, index) in tags" :key="index"
:class="tagIndex === index ? ' round-4 active color-main' : ''">{{ tag.name }}</a>
</div>
<el-select v-model="typeVal1" placeholder="Select" style="width: 200px">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-button type="primary" class="ml-12">保存</el-button>
</div>
<el-divider />
<div class="flex">
<div class="w-100 text-right mt-2">一级分类:</div>
<div class="flex flex-wrap gap-12 flex-1">
<a href="" class="color-888 px-6 py-2" v-for="(tag, index) in displayList" :key="index"
:class="tagIndex === index ? ' round-4 active color-main' : ''">{{ tag.name }}</a>
</div>
<el-button type="primary" link class="ml-12" @click="isExpanded = !isExpanded">{{ isExpanded ? '收起' : '展开'
}}</el-button>
</div>
<el-divider />
<div class="flex">
<div class="w-100 text-right mt-2">二级分类:</div>
<div class="flex-items-center flex-wrap gap-12 flex-1">
<el-input placeholder="搜索标签" style="width: 150px;" />
<a href="" class="color-888 px-6 h-26 py-2" v-for="(tag, index) in tags" :key="index"
:class="tagIndex === index ? ' round-4 active color-main' : ''">{{ tag.name }}</a>
</div>
</div>
<el-divider />
<div class="flex">
<div class="w-100 text-right mt-2">状态:</div>
<div class="flex flex-wrap gap-12 flex-1">
<a href="" class="color-888 px-6 py-2" v-for="(tag, index) in tags" :key="index"
:class="tagIndex === index ? ' round-4 active color-main' : ''">{{ tag.name }}</a>
</div>
</div>
<el-divider />
<div class="flex-items-center">
<div class="w-100 text-right">高级搜索:</div>
<div class="flex-items-center gap-10 flex-1">
<el-select v-model="typeVal1" placeholder="Select" style="width: 200px">
<template #prefix>
<span class="color-000">排序:</span>
</template>
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-select v-model="typeVal1" placeholder="Select" style="width: 200px">
<template #prefix>
<span class="color-000">广告平台标签:</span>
</template>
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-select v-model="typeVal1" placeholder="Select" style="width: 200px">
<template #prefix>
<span class="color-000">消耗:</span>
</template>
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-input placeholder="请输入内容" style="width: 200px">
<template #prefix>
<span class="color-000">作者:</span>
</template>
</el-input>
<el-select v-model="typeVal1" placeholder="Select" style="width: 140px" class="classify">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<div class="w-200 classify">
<el-date-picker v-model="datearr" type="daterange" range-separator="To" start-placeholder="开始时间"
end-placeholder="结束时间" />
</div>
</div>
<el-button type="primary" plain class="ml-12">查询</el-button>
<el-button class="ml-12">重置</el-button>
</div>
</section>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
const datearr = ref([])
const tagIndex = ref(0);
const typeVal1 = ref(1);
const tags = ref([
{ name: '全部', value: 1 },
{ name: '达人成片', value: 2 },
{ name: '草本初色内衣', value: 3 },
{ name: '短视频推广', value: 4 },
{ name: '直播', value: 5 },
])
const tags2 = ref([
{ name: '全部', value: 1 },
{ name: '达人成片', value: 2 },
{ name: '草本初色内衣', value: 3 },
{ name: '短视频推广', value: 4 },
{ name: '直播', value: 5 },
{ name: '短视频推广', value: 6 },
{ name: '直播', value: 7 },
{ name: '短视频推广', value: 8 },
{ name: '直播', value: 9 },
])
const isExpanded = ref(false)
const displayList = computed(() => {
return isExpanded.value ? tags2.value : tags2.value.slice(0, 5)
})
const options = ref([
{ value: 1, label: '黄金糕' },
{ value: 2, label: '双皮奶' },
{ value: 3, label: '蚵仔煎' },
{ value: 4, label: '龙须面' },
{ value: 5, label: '北京烤鸭' },
])
</script>
<style scoped lang="scss">
.el-divider--horizontal {
margin: 14px 0;
border-color: #f5f5f5;
}
.active {
background-color: rgba(var(--main-color), 0.1);
}
</style>
\ No newline at end of file
<template>
<div class="w-full h-full flex flex-col relative">
<el-checkbox :value="item.id" class="full-area-checkbox" :style="{ zIndex: isCheck ? 1 : -1 }" />
<div class="absolute top-0 left-0 right-0 fs-12 z-1">
<div class="flex justify-between mb-6">
<span class="px-6 py-2 color-fff bg-main round-br">成片</span>
<span class="px-6 py-2 color-fff bg-main round-bl">待审核</span>
</div>
<span class="px-6 py-2 color-fff round-4" style="background-color: rgba(0, 0, 0, 0.6);">ID:
887554622</span>
</div>
<div class="hidden flex-1 zoom-box">
<img src="../../../static/image/huaban.png" width="100%" height="100%" class="cover zoom-image">
</div>
<div class="">
<div class="p-10">
<p class="fs-14 color-333 ellipsis">全链路 AI 智能化智能生成、批量裂变与广告投放协同工作台</p>
<p class="fs-12 color-main mt-6">¥1000 / 6600</p>
<p class="fs-12 color-999 mt-6">AI画质提升</p>
</div>
<div class="flex-items-center justify-between p-10" style="border-top: 1px solid #f5f5f5;">
<div class="flex-items-center gap-4">
<div class="bg-main w-30 h-30 round"></div>
<div>张晓华</div>
</div>
<div class="fs-12 color-999">3小时前</div>
</div>
</div>
</div>
<div v-if="!isCheck" class="absolute top-0 left-0 bg-main w-full h-full video-main">
<video src="../../../../../public/aaa.mp4" height="100%" controls autoplay disablePictureInPicture></video>
<div class="absolute top-10 right-10 flex gap-4">
<div class="bg-333 w-24 h-24 round-6 pointer"></div>
<div class="bg-333 w-24 h-24 round-6 pointer"></div>
<div class="bg-333 w-24 h-24 round-6 pointer"></div>
</div>
</div>
</template>
<script setup lang="ts">
const props = defineProps({
item: {
type: Object,
default: () => { }
},
isCheck: {
type: Boolean,
default: false
}
})
</script>
<style scoped lang="scss">
.flex-item {
&:hover {
.video-main {
display: inline-block;
}
.zoom-image {
transform: scale(1.12);
}
}
}
.video-main {
display: none;
z-index: 9;
}
.zoom-image {
transition: all 0.6s;
}
.round-br {
border-bottom-right-radius: 6px;
}
.round-bl {
border-bottom-left-radius: 6px;
}
</style>
\ No newline at end of file
<template>
<div class="w-full h-full flex flex-col relative">
<el-checkbox :value="item.id" class="full-area-checkbox" :style="{ zIndex: isCheck ? 1 : -1 }" />
<div class="hidden flex-1 zoom-box">
<img src="../../../../static/image/huaban.png" width="100%" height="100%" class="cover zoom-image">
</div>
<div class="">
<div class="p-10">
<p class="fs-14 color-333 ellipsis">全链路 AI 智能化智能生成、批量裂变与广告投放协同工作台</p>
<p class="fs-12 color-main mt-6">¥1000 / 6600</p>
<p class="fs-12 color-999 mt-6">AI画质提升</p>
</div>
<div class="flex-items-center justify-between p-10" style="border-top: 1px solid #f5f5f5;">
<div class="flex-items-center gap-4">
<div class="bg-main w-30 h-30 round"></div>
<div>张晓华</div>
</div>
<div class="fs-12 color-999">3小时前</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
const props = defineProps({
item: {
type: Object,
default: () => { }
},
isCheck: {
type: Boolean,
default: false
}
})
</script>
<style scoped lang="scss">
.flex-item {
&:hover {
.video-main {
display: inline-block;
}
.zoom-image {
transform: scale(1.12);
}
}
}
.video-main {
display: none;
z-index: 9;
}
.zoom-image {
transition: all 0.6s;
}
.round-br {
border-bottom-right-radius: 6px;
}
.round-bl {
border-bottom-left-radius: 6px;
}
</style>
\ No newline at end of file
<template>
<div class="p-20">
<Classify />
<div class="mt-20">
<div class="mb-20 flex">
<el-button @click="switchCheck">{{ isCheck ? '取消选择' : '请选择' }}</el-button>
<el-button type="primary">上传文件</el-button>
<div class="round-6 bg-fff p-4 ml-12 flex gap-2" style="border: 1px solid #ddd;">
<div class="round-6 w-24 h-24 flex-center pointer" :class="btnIndex === 1 ? 'active' : ''" link @click="btnIndex = 1">
<SvgIcon name="HD" size="18" class="iconscoal" :color="btnIndex === 1? 'rgba(var(--main-color), 1)': ''" />
</div>
<div class="round-6 w-24 h-24 flex-center pointer" :class="btnIndex === 2 ? 'active' : ''" link @click="btnIndex = 2">
<SvgIcon name="redraw" size="18" class="iconscoal" :color="btnIndex === 2? 'rgba(var(--main-color), 1)': ''"/>
</div>
<div class="round-6 w-24 h-24 flex-center pointer" :class="btnIndex === 3 ? 'active' : ''" link @click="btnIndex = 3">
<SvgIcon name="room" size="18" class="iconscoal" :color="btnIndex === 3? 'rgba(var(--main-color), 1)': ''"/>
</div>
</div>
</div>
<div class="flex flex-wrap gap-20">
<el-checkbox-group v-model="checkedList" class="flex flex-wrap gap-20 w-full">
<div v-for="(item, index) in list" :key="index"
class="w-200 bg-fff round-12 relative hidden flex-item"
:class="{ 'is-selected': checkedList.includes(item.id) }">
<Item :item="item" :isCheck="isCheck" />
</div>
</el-checkbox-group>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import Classify from '../components/Classify.vue';
import Item from '@/views/resource/image/components/Item.vue'
const checkedList = ref<any>([])
const btnIndex = ref(0)
const isCheck = ref(false)
const list = ref([{ id: '1' }, { id: '2' }, { id: '3' }]);
const switchCheck = () => {
isCheck.value = !isCheck.value
checkedList.value = []
}
</script>
<style scoped lang="scss">
.flex-item {
flex: 0 0 calc((100% - 20px * 5) / 6);
border-radius: 8px;
box-sizing: border-box;
aspect-ratio: 1 / 1.1;
}
.active {
background-color: rgba(var(--main-color), 0.1);
}
/* 1600px 以下:一行 5 个 */
@media (max-width: 2100px) {
.flex-item {
flex: 0 0 calc((100% - 20px * 4) / 5);
}
}
/* 1200px 以下:一行 4 个 */
@media (max-width: 1800px) {
.flex-item {
flex: 0 0 calc((100% - 20px * 3) / 4);
}
}
/* 900px 以下:一行 3 个 */
@media (max-width: 1400px) {
.flex-item {
flex: 0 0 calc((100% - 20px * 2) / 3);
}
}
// 复选框的样式
.is-selected {
box-shadow: 0 0 2px 2px rgb(var(--main-color));
}
.el-checkbox-group {
font-size: inherit;
line-height: inherit;
}
</style>
\ No newline at end of file
<template>
<div class="p-20">
<Classify />
<div class="mt-20">
<div class="mb-20">
<el-button @click="switchCheck">{{ isCheck ? '取消选择' : '请选择' }}</el-button>
<el-button type="primary">上传文件</el-button>
</div>
<div class="flex flex-wrap gap-20">
<el-checkbox-group v-model="checkedList" class="flex flex-wrap gap-20 w-full">
<div v-for="(item, index) in list" :key="index"
class="w-200 bg-fff round-12 relative hidden flex-item"
:class="{ 'is-selected': checkedList.includes(item.id) }">
<Item :item="item" :isCheck="isCheck" />
</div>
</el-checkbox-group>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import Classify from '../components/Classify.vue';
import Item from '@/views/resource/components/VideoItem.vue'
const checkedList = ref<any>([])
const isCheck = ref(false)
const list = ref([{ id: '1' }, { id: '2' }, { id: '3' }]);
const switchCheck = () => {
isCheck.value = !isCheck.value
checkedList.value = []
}
</script>
<style scoped lang="scss">
.flex-item {
flex: 0 0 calc((100% - 20px * 5) / 6);
border-radius: 8px;
box-sizing: border-box;
aspect-ratio: 1 / 1.5;
}
/* 1600px 以下:一行 5 个 */
@media (max-width: 2100px) {
.flex-item {
flex: 0 0 calc((100% - 20px * 4) / 5);
}
}
/* 1200px 以下:一行 4 个 */
@media (max-width: 1800px) {
.flex-item {
flex: 0 0 calc((100% - 20px * 3) / 4);
}
}
/* 900px 以下:一行 3 个 */
@media (max-width: 1400px) {
.flex-item {
flex: 0 0 calc((100% - 20px * 2) / 3);
}
}
// 复选框的样式
.is-selected {
box-shadow: 0 0 2px 2px rgb(var(--main-color));
}
.el-checkbox-group {
font-size: inherit;
line-height: inherit;
}
</style>
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment