Commit bc0bcd4b authored by 黄同智's avatar 黄同智

页面布局,部分交互

parent 3cd16618
#请求api
#VITE_API_BASE_URL='http://192.168.0.138:9004/adminapi'
VITE_API_BASE_URL='http://121.196.162.232:3000/api'
#基础 URL
VITE_APP_BASE_URL = '/'
#请求api
VITE_API_BASE_URL=''
#基础 URL
VITE_APP_BASE_URL = '/web/'
node_modules
dist
.vite
.DS_Store
# Vue 3 + TypeScript + Vite 项目
## 安装
```bash
npm install
```
## 开发运行
```bash
npm run dev
```
## 打包
```bash
npm run build
```
## 说明
- `src/router/index.ts` 配置 Vue Router,包括懒加载、登录页、保护路由和 404 页面
- `src/services/http.ts` 配置 Axios 实例并集成请求/响应拦截器
- `src/services/api.ts` 封装后端请求示例
- `src/stores/useAuth.ts` 提供简单登录状态管理
- `src/views/Login.vue` 登录页面示例
- `src/views/Home.vue` 演示 API 请求
- `src/views/About.vue` 演示受保护路由
- `src/views/NotFound.vue` 404 页面
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue 3 + TS H5 项目</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
This diff is collapsed.
{
"name": "yaoai-h5-vue",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"typecheck": "vue-tsc --noEmit"
},
"dependencies": {
"axios": "^1.5.0",
"element-plus": "^2.14.0",
"nprogress": "^0.2.0",
"vue": "^3.4.0",
"vue-router": "^4.3.0"
},
"devDependencies": {
"@types/node": "20.11.0",
"@types/nprogress": "^0.2.3",
"@vitejs/plugin-vue": "4.4.0",
"@vue/compiler-sfc": "3.5.34",
"sass": "^1.99.0",
"typescript": "5.9.3",
"vite": "4.4.9",
"vue-tsc": "1.8.27"
}
}
<template>
<div id="app" class="flex flex-col h-100vh">
<Header />
<section class="flex flex-1 overflow-y-auto">
<Aside />
<main class="flex-1 bg-F8F9FA">
<router-view />
</main>
</section>
</div>
</template>
<script setup lang="ts">
import Header from './components/Header.vue';
import Aside from './components/Aside.vue';
</script>
<style>
#app {
margin: 0 auto;
font-family: "Helvetica Neue", Arial, sans-serif;
}
.bg-F8F9FA {
background-color: #F8F9FA;
}
</style>
import http from '../services/http';
export function loginApi(params: any) {
return http.post('/auth/login', params)
}
<template>
<nav v-if="isShow" class="navbar w-200 px-8">
<div class="flex-center pt-28">
<img src="@/static/imgs/logo.png" class="w-100 h-22" alt="Logo">
</div>
<div class="mt-22 h-36 flex-center relative selectbox">
<el-icon class="absolute top-1 left-16" size="20" style="z-index: 1;">
<Help />
</el-icon>
<el-select v-model="groupValue">
<el-option v-for="item in groupList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<div class="mt-28">
<div class="ml-16 mt-16 mb-4" style="color: #00000066;">创作</div>
<div class="flex-align-center gap-8 mt-12 cursor-pointer pl-16 h-36 rounded-4"
:class="indexvalue == 0 ? 'active' : ''" @click="switchTab(0)">
<el-icon>
<VideoCameraFilled />
</el-icon>
<span>剧本生成短剧</span>
</div>
</div>
<div class="mt-10">
<div class="ml-16 mt-16 mb-4" style="color: #00000066;">我的</div>
<div class="flex-align-center gap-8 mt-12 cursor-pointer pl-16 h-36 rounded-4"
:class="indexvalue == 1 ? 'active' : ''" @click="switchTab(1)">
<el-icon>
<Files />
</el-icon>
<span>项目</span>
</div>
<div class="flex-align-center gap-8 mt-12 cursor-pointer pl-16 h-36 rounded-4"
:class="indexvalue == 2 ? 'active' : ''" @click="switchTab(2)">
<el-icon>
<PieChart />
</el-icon>
<span>资产</span>
</div>
</div>
<div class="mt-50">
<div class="ml-16 mt-16 mb-4" style="color: #00000066;">企业配置</div>
<div class="flex-align-center gap-8 mt-12 cursor-pointer pl-16 h-36 rounded-4"
:class="indexvalue == 3 ? 'active' : ''" @click="switchTab(3)">
<el-icon>
<Files />
</el-icon>
<span>项目</span>
</div>
<div class="flex-align-center gap-8 mt-12 cursor-pointer pl-16 h-36 rounded-4"
:class="indexvalue == 4 ? 'active' : ''" @click="switchTab(4)">
<el-icon>
<PieChart />
</el-icon>
<span>用量统计</span>
</div>
<div class="flex-align-center gap-8 mt-12 cursor-pointer pl-16 h-36 rounded-4"
:class="indexvalue == 5 ? 'active' : ''" @click="switchTab(5)">
<el-icon>
<PieChart />
</el-icon>
<span>AI生成水印设置</span>
</div>
</div>
</nav>
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import { useAuth } from '../stores/useAuth';
import { VideoCameraFilled, Files, PieChart, Help } from '@element-plus/icons-vue'
import { useRouter, useRoute } from 'vue-router';
const auth = useAuth();
const router = useRouter();
const route = useRoute();
const routeIndexMap: Record<string, number> = {
'/': 0,
'/projectlist': 1,
'/asset': 2,
'/enterprise/project': 3,
'/enterprise/usage': 4,
'/enterprise/watermark': 5,
};
const indexvalue = ref(routeIndexMap[route.path] ?? 0);
const isShow = ref(true)
watch(
() => route.path,
(path) => {
indexvalue.value = routeIndexMap[path] ?? 0;
isShow.value = !!routeIndexMap[path]
},
{ immediate: true }
);
const groupList = [{
value: 'group1',
label: '哈哈哈大萨达',
},
{
value: 'group2',
label: '地址都干啥',
},
];
const groupValue = ref(groupList[0].value);
const tabRouter:any = {
0: '/',
1: '/projectlist',
2: '/asset',
3: '/enterprise/project',
4: '/enterprise/usage',
5: '/enterprise/watermark',
}
const switchTab = (index: number) => {
indexvalue.value = index;
router.push(tabRouter[index]);
};
</script>
<style scoped>
.navbar {
border-right: 1px solid #E6EAED;
border-top: 1px solid #E6EAED;
background-color: #F8F9FA;
}
.active {
background-color: #F3EEFE;
color: #5337C7;
}
.selectbox {
border: 1px solid #E6EAED;
border-radius: 3px;
overflow: hidden;
:deep(.el-select__wrapper) {
box-shadow: none;
padding-left: 44px;
line-height: normal;
}
}
</style>
<template>
<nav class="flex-between-center h-48 px-26 bg-fff navbar">
<div class="flex-align-center gap-16">
<el-icon :size="20" color="#888">
<Operation />
</el-icon>
<img src="@/static/imgs/logo.png" class="w-130 h-26 mb-2 cursor-pointer" alt="Logo" @click="router.push('/')">
</div>
<div class="flex gap-26">
<el-input
v-model="search"
class="responsive-input"
placeholder="搜索产品和文档"
:prefix-icon="Search"
/>
<el-button type="primary" class="nav-button">
<span class="pt-2">企业</span><el-icon class="el-icon--right" size="12">
<ArrowDownBold />
</el-icon>
</el-button>
<el-button type="primary" class="nav-button">
<span class="pt-2">工具</span><el-icon class="el-icon--right" size="12">
<ArrowDownBold />
</el-icon>
</el-button>
<el-button type="primary" class="nav-button">
<span class="pt-2">费用</span><el-icon class="el-icon--right" size="12">
<ArrowDownBold />
</el-icon>
</el-button>
<el-button type="primary" class="nav-button">
<span class="pt-2">支持</span><el-icon class="el-icon--right" size="12">
<ArrowDownBold />
</el-icon>
</el-button>
<el-button type="primary" class="nav-button pt-2">备案</el-button>
<el-button type="primary" class="nav-button pt-2">文档</el-button>
<el-badge :value="12" class="mt-6 cursor-pointer">
<el-icon size="18"><Bell /></el-icon>
</el-badge>
<el-button type="primary" link class="nav-button fw-bold color">中文</el-button>
<div class="w-30 h-30 flex-center rounded-circle headimg fs-12 mt-2"></div>
</div>
</nav>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { Operation } from '@element-plus/icons-vue'
import { ArrowDownBold, Bell, Search } from '@element-plus/icons-vue'
import { useRouter } from 'vue-router';
const router = useRouter();
const search = ref('');
</script>
<style scoped lang="scss">
.navbar {
box-shadow: 0 3px 5px #eee;
z-index: 1;
}
.responsive-input {
width: 240px;
height: 36px;
border-radius: 8px !important;
border: 1px solid #E5E7EB;
overflow: hidden;
:deep(.el-input__wrapper) {
box-shadow: none !important;
background-color: #F9FAFB;
}
}
.headimg {
background-color: #EFF6FF;
border: 1px solid #DBEAFE;
color: #193CB8;
}
.nav-link,
.nav-button {
color: #333;
text-decoration: none;
font-weight: 600;
background: none;
border: none;
cursor: pointer;
padding: 0;
}
.pt-2 {
padding-top: 2px;
}
.color {
color: #155DFC;
}
.nav-button:hover,
.nav-link:hover {
color: #409eff;
}
.active {
color: #409eff;
}
</style>
<template>
<nav class="navbar">
<router-link to="/" class="nav-link" active-class="active" exact-active-class="active">首页</router-link>
<router-link to="/about" class="nav-link" active-class="active" exact-active-class="active">关于</router-link>
<router-link v-if="!isLoggedIn" to="/login" class="nav-link" active-class="active">登录</router-link>
<button v-else type="button" class="nav-button" @click="logout">退出</button>
</nav>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { useAuth } from '../stores/useAuth';
const auth = useAuth();
const isLoggedIn = computed(() => auth.isLoggedIn.value);
const logout = () => {
auth.logout();
};
</script>
<style scoped>
/* .navbar {
display: flex;
gap: 16px;
border-bottom: 1px solid #dfe6ec;
padding-bottom: 12px;
}
.nav-link,
.nav-button {
color: #333;
text-decoration: none;
font-weight: 600;
background: none;
border: none;
cursor: pointer;
padding: 0;
}
.nav-button:hover,
.nav-link:hover {
color: #409eff;
}
.active {
color: #409eff;
} */
</style>
import { createApp } from 'vue';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import App from './App.vue';
import router from './router';
import './style.css';
import zhCn from 'element-plus/es/locale/lang/zh-cn'
const app = createApp(App);
app.use(ElementPlus, { locale: zhCn });
app.use(router);
app.mount('#app');
import { createRouter, createWebHistory } from 'vue-router';
import { useAuth } from '../stores/useAuth';
import NProgress from "nprogress";
import "nprogress/nprogress.css";
const Home = () => import('@/views/Home.vue');
const About = () => import('@/views/About.vue');
const Login = () => import('@/views/Login.vue');
const NotFound = () => import('@/views/NotFound.vue');
const Projectlist = () => import('@/views/project/projectlist.vue');
const Asset = () => import('@/views/project/asset.vue');
const EnterpriseProject = () => import('@/views/enterprise/project.vue');
const EnterpriseUsage = () => import('@/views/enterprise/usage.vue');
const EnterpriseWatermark = () => import('@/views/enterprise/watermark.vue');
const Drama = () => import('@/views/project/drama.vue');
const routes = [
{
path: '/login',
name: 'Login',
component: Login
},
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/projectlist',
name: 'Projectlist',
component: Projectlist,
meta: { authRequired: true }
},
{
path: '/drama',
name: 'Drama',
component: Drama,
meta: { authRequired: true }
},
{
path: '/asset',
name: 'Asset',
component: Asset,
meta: { authRequired: true }
},
{
path: '/enterprise/project',
name: 'EnterpriseProject',
component: EnterpriseProject,
meta: { authRequired: true }
},
{
path: '/enterprise/usage',
name: 'EnterpriseUsage',
component: EnterpriseUsage,
meta: { authRequired: true }
},
{
path: '/enterprise/watermark',
name: 'EnterpriseWatermark',
component: EnterpriseWatermark,
meta: { authRequired: true }
},
{
path: '/about',
name: 'About',
component: About,
meta: { authRequired: true }
},
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: NotFound
}
];
const router = createRouter({
history: createWebHistory(),
routes
});
router.beforeEach(async (to) => {
const auth = useAuth();
const hasToken = auth.isLoggedIn.value;
if (hasToken) {
if (to.path === "/login") {
// 如果已登录,则重定向到首页
NProgress.done();
return { path: '/' };
}
} else if (to.path !== '/login') {
return { path: '/login' };
}
});
router.afterEach(() => {
NProgress.done();
});
export default router;
import http from './http';
export interface Todo {
userId: number;
id: number;
title: string;
completed: boolean;
}
export async function fetchTodo(id: number): Promise<Todo> {
const response = await http.get<Todo>(`/todos/${id}`);
return response.data;
}
export default http;
import axios from 'axios';
import { useAuth } from '../stores/useAuth';
const http = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
});
http.interceptors.request.use((config: any) => {
const auth = useAuth();
if (auth.token.value) {
config.headers = {
...config.headers,
Authorization: `Bearer ${auth.token.value}`
};
}
return config;
});
http.interceptors.response.use(
(response) => {
return response.data
},
(error) => {
const status = error.response?.status;
if (status === 401 || status === 403) {
const auth = useAuth();
auth.logout();
}
return Promise.reject(error);
}
);
export default http;
$numbers: (
0: 0,
2: 2px,
4: 4px,
6: 6px,
8: 8px,
9: 9px,
10: 10px,
12: 12px,
14: 14px,
16: 16px,
18: 18px,
20: 20px,
22: 22px,
24: 24px,
26: 26px,
28: 28px,
30: 30px,
32: 32px,
34: 34px,
36: 36px,
38: 38px,
40: 40px,
42: 42px,
44: 44px,
46: 46px,
48: 48px,
50: 50px,
52: 52px,
54: 54px,
56: 56px,
58: 58px,
60: 60px,
62: 62px,
64: 64px,
66: 66px,
68: 68px,
70: 70px,
72: 72px,
74: 74px,
76: 76px,
78: 78px,
80: 80px,
82: 82px,
84: 84px,
86: 86px,
88: 88px,
90: 90px,
92: 92px,
94: 94px,
96: 96px,
98: 98px,
100: 100px,
102: 102px,
104: 104px,
106: 106px,
108: 108px,
110: 110px,
112: 112px,
114: 114px,
116: 116px,
118: 118px,
120: 120px,
122: 122px,
124: 124px,
126: 126px,
128: 128px,
130: 130px,
132: 132px,
134: 134px,
136: 136px,
138: 138px,
140: 140px,
150: 150px,
160: 160px,
170: 170px,
180: 180px,
190: 190px,
200: 200px,
300: 300px,
400: 400px,
500: 500px,
600: 600px,
700: 700px,
800: 800px,
);
// 生成 margin 工具类
@each $key, $value in $numbers {
// margin
.m-#{$key} {
margin: $value;
}
.mt-#{$key} {
margin-top: $value;
}
.mr-#{$key} {
margin-right: $value;
}
.mb-#{$key} {
margin-bottom: $value;
}
.ml-#{$key} {
margin-left: $value;
}
.mx-#{$key} {
margin-left: $value;
margin-right: $value;
}
.my-#{$key} {
margin-top: $value;
margin-bottom: $value;
}
// padding
.p-#{$key} {
padding: $value;
}
.pt-#{$key} {
padding-top: $value;
}
.pr-#{$key} {
padding-right: $value;
}
.pb-#{$key} {
padding-bottom: $value;
}
.pl-#{$key} {
padding-left: $value;
}
.px-#{$key} {
padding-left: $value;
padding-right: $value;
}
.py-#{$key} {
padding-top: $value;
padding-bottom: $value;
}
.top-#{$key} {
top: $value;
}
.right-#{$key} {
right: $value;
}
.bottom-#{$key} {
bottom: $value;
}
.left-#{$key} {
left: $value;
}
// 字体大小
.fs-#{$key} {
font-size: $value;
}
// 别名
.fz-#{$key} {
font-size: $value;
}
// 宽度
.w-#{$key} {
width: $value;
}
// 高度
.h-#{$key} {
height: $value;
}
.min-w-#{$key} {
min-width: $value;
}
.min-h-#{$key} {
min-height: $value;
}
.max-w-#{$key} {
max-width: $value;
}
.max-h-#{$key} {
max-height: $value;
}
// 圆角
.rounded-#{$key} {
border-radius: $value;
}
.rounded-t-#{$key} {
border-top-left-radius: $value;
border-top-right-radius: $value;
}
.rounded-r-#{$key} {
border-top-right-radius: $value;
border-bottom-right-radius: $value;
}
.rounded-b-#{$key} {
border-bottom-right-radius: $value;
border-bottom-left-radius: $value;
}
.rounded-l-#{$key} {
border-top-left-radius: $value;
border-bottom-left-radius: $value;
}
.rounded-tl-#{$key} {
border-top-left-radius: $value;
}
.rounded-tr-#{$key} {
border-top-right-radius: $value;
}
.rounded-br-#{$key} {
border-bottom-right-radius: $value;
}
.rounded-bl-#{$key} {
border-bottom-left-radius: $value;
}
// 行高
.line-h#{$key} {
line-height: $value;
}
}
.rounded-circle {
border-radius: 50%;
}
// 特殊的 margin: auto
.m-auto {
margin: auto;
}
.mt-auto {
margin-top: auto;
}
.mr-auto {
margin-right: auto;
}
.mb-auto {
margin-bottom: auto;
}
.ml-auto {
margin-left: auto;
}
.mx-auto {
margin-left: auto;
margin-right: auto;
}
.my-auto {
margin-top: auto;
margin-bottom: auto;
}
.fw-400 {
font-weight: 400;
}
// Regular (Normal)
.fw-500 {
font-weight: 500;
}
// Medium
.fw-600 {
font-weight: 600;
}
// Semi Bold
.fw-700 {
font-weight: 700;
}
// Bold
.fw-800 {
font-weight: 800;
}
// Extra Bold
.fw-900 {
font-weight: 900;
}
// 常用别名
.fw-bold {
font-weight: bold;
}
.w-auto {
width: auto;
}
// 视口宽度
.w-100vw {
width: 100vw;
}
.w-b100 {
width: 100%;
}
.h-100vh {
height: 100vh;
}
.h-b100 {
height: 100%;
}
// 文本对齐
.text-left {
text-align: left;
}
.text-center {
text-align: center;
}
.text-right {
text-align: right;
}
.text-justify {
text-align: justify;
}
// 文本颜色(示例,可以扩展)
@each $color, $value in (primary: #1890ff,
success: #52c41a,
warning: #faad14,
error: #ff4d4f,
fff: #ffffff,
f5: #f5f5f5,
'000': #000000,
'333': #333333,
'444': #444444,
'555': #555555,
'666': #666666,
'888': #888888,
'999': #999999) {
.text-#{$color} {
color: $value;
}
.bg-#{$color} {
background-color: $value;
}
}
// 行高
.line-height-1 {
line-height: 1;
}
.line-height-1-2 {
line-height: 1.2;
}
.line-height-1-5 {
line-height: 1.5;
}
.line-height-2 {
line-height: 2;
}
.line-height-normal {
line-height: normal;
}
// 显示方式
.block {
display: block;
}
.inline-block {
display: inline-block;
}
.inline {
display: inline;
}
.flex {
display: flex;
}
.inline-flex {
display: inline-flex;
}
.grid {
display: grid;
}
.inline-grid {
display: inline-grid;
}
.hidden {
display: none;
}
// 位置
.relative {
position: relative;
}
.absolute {
position: absolute;
}
.fixed {
position: fixed;
}
.sticky {
position: sticky;
}
.static {
position: static;
}
// 溢出
.overflow-auto {
overflow: auto;
}
.overflow-hidden {
overflow: hidden;
}
.overflow-visible {
overflow: visible;
}
.overflow-scroll {
overflow: scroll;
}
.overflow-x-auto {
overflow-x: auto;
}
.overflow-x-hidden {
overflow-x: hidden;
}
.overflow-y-auto {
overflow-y: auto;
}
.overflow-y-hidden {
overflow-y: hidden;
}
// 光标样式
.cursor-pointer {
cursor: pointer;
}
.cursor-default {
cursor: default;
}
.cursor-not-allowed {
cursor: not-allowed;
}
.cursor-move {
cursor: move;
}
.cursor-wait {
cursor: wait;
}
// 用户选择
.select-none {
user-select: none;
}
.select-all {
user-select: all;
}
.select-auto {
user-select: auto;
}
// // 不透明度
// @each $value in 0, 10, 20, 25, 30, 40, 50, 60, 70, 75, 80, 90, 100 {
// .opacity-#{$value} {
// opacity: #{$value / 100};
// }
// }
// ============================================
// 8. Flex 布局工具类
// ============================================
.flex-row {
flex-direction: row;
}
.flex-col {
flex-direction: column;
}
.flex-row-reverse {
flex-direction: row-reverse;
}
.flex-col-reverse {
flex-direction: column-reverse;
}
.justify-start {
justify-content: flex-start;
}
.justify-end {
justify-content: flex-end;
}
.justify-center {
justify-content: center;
}
.justify-between {
justify-content: space-between;
}
.justify-around {
justify-content: space-around;
}
.justify-evenly {
justify-content: space-evenly;
}
.items-start {
align-items: flex-start;
}
.items-end {
align-items: flex-end;
}
.items-center {
align-items: center;
}
.items-baseline {
align-items: baseline;
}
.items-stretch {
align-items: stretch;
}
.flex-wrap {
flex-wrap: wrap;
}
.flex-nowrap {
flex-wrap: nowrap;
}
.flex-wrap-reverse {
flex-wrap: wrap-reverse;
}
.flex-center {
display: flex;
justify-content: center;
align-items: center;
}
.flex-justify-center {
display: flex;
justify-content: center;
}
.flex-align-center {
display: flex;
align-items: center;
}
.flex-between-center {
display: flex;
justify-content: space-between;
align-items: center;
}
.flex-1 {
flex: 1;
}
.flex-2 {
flex: 2;
}
.flex-3 {
flex: 3;
}
.flex-4 {
flex: 4;
}
.gap-0 {
gap: 0;
}
@each $key, $value in $numbers {
.gap-#{$key} {
gap: $value;
}
}
.text-over-1 {
width: 200px;
/* 必须设置宽度 */
white-space: nowrap;
/* 强制文本不换行 */
overflow: hidden;
/* 隐藏溢出内容 */
text-overflow: ellipsis;
/* 显示省略号 */
}
.text-over-2 {
display: -webkit-box;
/* 弹性盒模型 */
-webkit-line-clamp: 2;
/* 限制显示行数 */
-webkit-box-orient: vertical;
/* 垂直排列 */
overflow: hidden;
/* 隐藏溢出 */
word-break: break-word;
/* 长单词换行(可选) */
}
.hide-scrollbar {
&::-webkit-scrollbar {
display: none;
}
}
\ No newline at end of file
import { ref, readonly } from 'vue';
import { loginApi } from '../api/login';
const TOKEN_KEY = 'yaoai-h5-access-token';
const storedToken = localStorage.getItem(TOKEN_KEY);
const token = ref<string | null>(storedToken);
const isLoggedIn = ref(!!storedToken);
function saveToken(value: string | null) {
token.value = value;
isLoggedIn.value = !!value;
if (value) {
localStorage.setItem(TOKEN_KEY, value);
} else {
localStorage.removeItem(TOKEN_KEY);
}
}
function setToken(value: string | null) {
saveToken(value);
}
function logout() {
saveToken(null);
}
function login(userinfo: any) {
return new Promise((resolve, reject) => {
// loginApi(userinfo).then((res) => {
// const { data } = res;
// if (data) {
// setToken(data)
// }
// resolve(res)
// }).catch((err) => {
// reject(err)
// })
setToken('123')
resolve({
data: {
token: '123'
}
})
})
}
export function useAuth() {
return {
token: readonly(token),
isLoggedIn: readonly(isLoggedIn),
login,
setToken,
logout
};
}
/* 引入公共的css文件 */
@import './static/styles/common.scss';
:root {
--el-color-primary: #785BE5; /* 修改主色 */
}
html, body {
margin: 0;
padding: 0;
min-height: 100%;
font-size: 14px;
/* background-color: #fff; */
}
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
button {
font: inherit;
}
<template>
<section>
<h1>关于</h1>
<p>这个页面展示了 Vue Router 的多页面导航能力,以及 Axios 处理后端 API 请求的示例。</p>
<p>你可以通过导航栏在“首页”和“关于”之间切换。</p>
</section>
</template>
<script setup lang="ts">
</script>
<style scoped>
section {
line-height: 1.75;
}
</style>
This diff is collapsed.
<template>
<section class="login-page">
<h1>登录</h1>
<el-form
ref="loginFormRef"
:model="loginForm"
:rules="rules"
label-position="top"
class="login-form"
>
<el-form-item label="用户名" prop="email">
<el-input v-model="loginForm.email" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input
v-model="loginForm.password"
type="password"
placeholder="请输入密码"
></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm()">登录</el-button>
</el-form-item>
</el-form>
</section>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { useAuth } from '../stores/useAuth';
import type { FormInstance, FormRules } from 'element-plus';
import { ElMessage } from 'element-plus'
interface LoginForm {
email: string;
password: string;
}
const auth = useAuth();
const router = useRouter();
const loginForm = ref<LoginForm>({
email: '',
password: ''
});
const loginFormRef = ref<FormInstance>();
const rules: FormRules = {
email: [
{ required: true, message: '请输入用户名', trigger: 'change' },
{ min: 3, max: 20, message: '用户名长度 3~20 个字符', trigger: 'change' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'change' },
{ min: 6, message: '密码至少 6 位', trigger: 'change' }
]
};
const submitForm = () => {
loginFormRef.value?.validate((valid) => {
if (!valid) {
return;
}
auth.login(loginForm.value).then((res) => {
loginSuccess()
}).catch(res => loginFail(res));
});
};
const loginSuccess = () => {
router.push({ path: "/" });
};
const loginFail = (res: any) => {
ElMessage.error(`${res?.message || '登录失败,请重试'}`);
};
</script>
<style scoped>
.login-page {
max-width: 420px;
margin: 0 auto;
}
.login-form {
background: #ffffff;
padding: 24px;
border-radius: 12px;
box-shadow: 0 12px 30px rgba(62, 94, 148, 0.08);
}
.el-form-item {
margin-bottom: 18px;
}
.error-alert {
margin-top: 16px;
}
</style>
<template>
<section class="not-found">
<h1>页面未找到</h1>
<p>抱歉,你访问的页面不存在。</p>
<router-link to="/" class="nav-link">返回首页</router-link>
</section>
</template>
<script setup lang="ts">
</script>
<style scoped>
.not-found {
text-align: center;
}
.nav-link {
color: #409eff;
text-decoration: none;
font-weight: 600;
}
</style>
<template>
<div></div>
</template>
<script setup lang="ts"></script>
<style lang="scss" scoped></style>
\ No newline at end of file
<template>
<div></div>
</template>
<script setup lang="ts"></script>
<style lang="scss" scoped></style>
\ No newline at end of file
<template>
<div></div>
</template>
<script setup lang="ts"></script>
<style lang="scss" scoped></style>
\ No newline at end of file
<template>
<div></div>
</template>
<script setup lang="ts"></script>
<style lang="scss" scoped></style>
\ No newline at end of file
<template>
<div>
<section class="h-72 head flex-between-center px-20">
<section class="flex-align-center">
<div class="flex-center cursor-pointer w-28 h-28" @click="router.back()">
<el-icon>
<ArrowLeftBold />
</el-icon>
</div>
<div class="ml-10">
<div>新剧本策划</div>
<div class="flex-align-center gap-10 line-h20 text-888 mt-2">
<span>16:9</span>
<div class="line-r"></div>
<span>720p</span>
<div class="line-r"></div>
<span>写实通用</span>
<el-button link style="color: #595dff;">查看剧本</el-button>
</div>
</div>
</section>
<section class="flex-align-center gap-20 w-500">
<el-button v-for="(item, index) in steps" :key="index" class="px-24 box"
:class="{ active: index == indexVal, noactive: index != indexVal, weiel: index < steps.length - 1 }"
@click="changeTab(index)">
<div class="flex-align-center flex-col">
<el-icon v-if="item.name == '剧本'" size="16">
<Notebook />
</el-icon>
<el-icon v-if="item.name == '设定'" size="16">
<Reading />
</el-icon>
<el-icon v-if="item.name == '分镜'" size="16">
<Film />
</el-icon>
<el-icon v-if="item.name == '视频'" size="16">
<VideoCamera />
</el-icon>
<span class="fs-12 span">{{ item.name }}</span>
</div>
</el-button>
</section>
<section class="">
<div class="flex-align-center h-32 px-10 rounded-4 jinbi">
<svg t="1779335788481" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="9953" width="20" height="20">
<path
d="M408.8832 353.9968c40.7552-87.1424 61.0304-130.7648 92.7744-136.192a61.44 61.44 0 0 1 20.6848 0c31.744 5.4272 52.0192 49.0496 92.7744 136.192 7.8848 16.9984 11.8784 25.6 18.0224 32.3584a61.44 61.44 0 0 0 4.5056 4.5056c6.8608 6.144 15.36 10.1376 32.3584 18.0224 87.1424 40.7552 130.7648 61.0304 136.192 92.7744a61.44 61.44 0 0 1 0 20.6848c-5.4272 31.744-49.0496 52.0192-136.192 92.7744-16.9984 7.8848-25.6 11.8784-32.3584 18.0224a61.44 61.44 0 0 0-4.5056 4.5056c-6.144 6.8608-10.1376 15.36-18.0224 32.3584-40.7552 87.1424-61.0304 130.7648-92.7744 136.192a61.44 61.44 0 0 1-20.6848 0c-31.744-5.4272-52.0192-49.0496-92.7744-136.192-7.8848-16.9984-11.8784-25.6-18.0224-32.3584a61.44 61.44 0 0 0-4.5056-4.5056c-6.8608-6.144-15.36-10.1376-32.3584-18.0224-87.1424-40.7552-130.7648-61.0304-136.192-92.7744a61.44 61.44 0 0 1 0-20.6848c5.4272-31.744 49.0496-52.0192 136.192-92.7744 16.9984-7.8848 25.6-11.8784 32.3584-18.0224a61.44 61.44 0 0 0 4.5056-4.5056c6.144-6.8608 10.1376-15.36 18.0224-32.3584z"
p-id="9954" fill="#5252ff"></path>
</svg>
<span class="text-555 ml-4">882</span>
</div>
</section>
</section>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { ArrowLeftBold, Notebook, Reading, Film, VideoCamera } from '@element-plus/icons-vue'
import router from '@/router';
const emit = defineEmits(['changeTab'])
const props = defineProps({
indexVal: {
type: Number,
default: 0
}
})
const steps = ref([
{ name: '剧本', active: true, icon: 'Notebook', finished: true },
{ name: '设定', active: false, icon: '', finished: false },
{ name: '分镜', active: false, icon: '', finished: false },
{ name: '视频', active: false, icon: '', finished: false },
])
const changeTab = (index: number) => {
emit('changeTab', index)
}
defineExpose({
changeTab
})
</script>
<style lang="scss" scoped>
.head {
border-bottom: 1px solid #eee;
.line-r {
height: 12px;
width: 1px;
background: #ddd;
}
.box {
border: 1px solid #eee;
border-radius: 100px;
height: 44px;
margin: 0;
.span {
margin: 0;
margin-top: 6px;
}
}
.active {
background-color: #f3f3ff;
border-color: #595dff80;
color: #595dff;
}
.noactive {
&:hover {
background-color: #f5f5f5;
}
}
.weiel {
position: relative;
&::after {
content: '';
position: absolute;
right: -21px;
top: 50%;
transform: translateY(-50%);
width: 20px;
height: 1px;
background: #cfcfcf;
}
}
.jinbi {
background-color: #efeff7;
}
}
</style>
\ No newline at end of file
<template>
<el-dialog v-model="dialogVisible" :title="title" width="580" align-center :before-close="handleClose"
modal-class="modalstyle">
<div class="mb-20 mt-10">
<span v-if="modalType === '2'">确定要删除这个项目吗?</span>
<el-input v-else v-model="inputValue" size="large" placeholder="请输入项目名称" />
</div>
<template #footer>
<div class="dialog-footer">
<el-button type="info" dashed @click="dialogVisible = false" style="border: none;">取消</el-button>
<el-button type="primary" @click="dialogVisible = false" style="background-color: #5252ff;border: none;">
确定
</el-button>
</div>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const dialogVisible = ref(false)
const inputValue = ref('')
const modalType = ref('1')
const title = ref('编辑项目名称')
const open = (item:any, type: string) => {
modalType.value = type
title.value = type === '1' ? '编辑项目名称' : '删除项目'
dialogVisible.value = true
}
const handleClose = (done: () => void) => {
done()
}
defineExpose({
open
})
</script>
<style lang="scss">
.modalstyle {
.el-dialog {
border-radius: 20px;
}
}
</style>
<style scoped>
:deep(.el-input__wrapper) {
border-radius: 8px;
}
</style>
\ No newline at end of file
<template>
<div class="bg-f6f7fa rounded-8 flex-center flex-col py-20">
<svg t="1779438123738" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="36427" width="30" height="30">
<path
d="M862.151111 471.836444c32.426667 42.837333 15.132444 82.488889-40.106667 89.543112l-131.413333 16.725333-71.566222 113.152-2.616889 3.925333c-30.435556 44.088889-72.988444 39.936-96.426667-10.296889l-56.718222-121.457777-128.227555-33.564445-4.266667-1.251555c-51.313778-15.416889-60.984889-57.969778-20.423111-96.369778l96.540444-91.306667-7.964444-133.973333-0.227556-4.323556c-1.308444-53.873778 35.896889-75.889778 84.764445-48.810666l116.053333 64.284444 123.164444-48.753778 3.982223-1.536c50.744889-17.976889 83.512889 10.467556 73.102222 65.194667l-25.031111 131.754667 84.593778 103.537777 2.787555 3.527111zM229.944889 176.696889l20.707555 37.944889c1.137778 2.048 2.844444 3.754667 4.835556 4.835555l37.888 20.707556a12.060444 12.060444 0 0 1 0 21.105778l-37.888 20.878222a12.060444 12.060444 0 0 0-4.778667 4.778667l-20.821333 37.831111a12.060444 12.060444 0 0 1-21.162667 0l-20.707555-37.831111a12.060444 12.060444 0 0 0-4.835556-4.835556l-37.944889-20.821333a12.060444 12.060444 0 0 1 0-21.162667l37.944889-20.650667a12.060444 12.060444 0 0 0 4.835556-4.835555l20.764444-37.944889a12.060444 12.060444 0 0 1 21.162667 0zM193.991111 793.827556l194.332445-194.389334a32.256 32.256 0 1 1 45.624888 45.624889l-194.389333 194.332445a32.312889 32.312889 0 1 1-45.568-45.568z m631.466667-97.109334l17.180444 31.516445c1.080889 1.991111 2.730667 3.697778 4.778667 4.835555l31.345778 17.066667a12.060444 12.060444 0 0 1 0 21.219555l-31.345778 17.180445a12.060444 12.060444 0 0 0-4.835556 4.778667l-17.180444 31.345777a12.060444 12.060444 0 0 1-21.162667 0l-17.180444-31.345777a12.003556 12.003556 0 0 0-4.778667-4.835556l-31.402667-17.123556a12.060444 12.060444 0 0 1 0-21.162666l31.402667-17.180445a12.060444 12.060444 0 0 0 4.778667-4.835555l17.180444-31.459556a12.060444 12.060444 0 0 1 21.219556 0zM511.772444 778.808889l13.824 25.372444a8.021333 8.021333 0 0 0 3.242667 3.185778l25.258667 13.824a8.021333 8.021333 0 0 1 0 14.108445l-25.372445 13.824a8.021333 8.021333 0 0 0-3.185777 3.242666l-13.767112 25.258667a8.021333 8.021333 0 0 1-14.108444 0l-13.880889-25.315556a8.021333 8.021333 0 0 0-3.185778-3.185777l-25.315555-13.824a8.021333 8.021333 0 0 1 0-14.108445l25.372444-13.824a8.021333 8.021333 0 0 0 3.128889-3.242667l13.880889-25.315555a8.078222 8.078222 0 0 1 14.108444 0z"
fill="#cdcdcd" p-id="36428"></path>
</svg>
<span class="fs-12 text-999">{{ text }}</span>
</div>
</template>
<script setup lang="ts">
const props = defineProps({
text: {
type: String,
default: '暂无数据'
}
})
</script>
<style lang="scss" scoped>
.bg-f6f7fa {
background-color: #f6f7fa;
}
</style>
\ No newline at end of file
<template>
<section class="box rounded-16 p-10" style="min-width: 1110px;">
<div class="flex-align-center justify-between">
<div class="flex-align-center gap-4">
<el-icon color="#333" size="12">
<Plus />
</el-icon>
<span class="fs-12">分镜1</span>
<span class="fs-10 py-2 px-4 rounded-4 bg-e3e9ff">ID</span>
<div class="flex-align-center ml-10 bg-f4f6ff py-6 px-14 rounded-8 btnsborder gap-4">
<el-icon color="#5252ff" size="12">
<Plus />
</el-icon>
<span class="fs-12">全能参考生视频</span>
</div>
</div>
<div class="">
<el-button text :icon="Plus" circle />
<el-button text :icon="CopyDocument" circle />
<el-button text :icon="Delete" circle />
</div>
</div>
<div class="flex gap-10 h-b100">
<section class="w-400 p-14 bg-fff rounded-16 mt-10">
<div>分镜信息</div>
<div class="h-548 hide-scrollbar">
<div class="mt-10 text-666 mb-6">分镜描述</div>
<el-input v-model="fenjDesc" placeholder="请输入分镜描述" type="textarea" clearable />
<div class="mt-20">
<div class="flex-between-center">
<span>出镜角色</span>
<el-button text :icon="Plus" circle />
</div>
<div class="mt-6">
<div v-for="(item, index) in 2" :key="index" class="flex-align-center gap-4 mt-10">
<img class="h-40 w-40 rounded-8"
src="https://res.volccdn.com/obj/volc-console-fe/dramart/static/image/PIC3.22e42366.png" alt="">
<el-select v-model="aiTypeVal" placeholder="未配置角色" style="width: 160px;">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-select v-model="aiTypeVal" placeholder="未配置音色" style="width: 120px;">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-button :icon="MoreFilled" style="padding-left: 8px;padding-right: 8px;border-radius: 8px;" />
</div>
<Empty v-if="1 == 2" text="暂未选择出镜角色" />
</div>
</div>
<div class="mt-20">
<div class="flex-between-center">
<span>分镜场景</span>
<el-button text :icon="Plus" circle />
</div>
<div class="flex gap-10 flex-wrap mt-10">
<div v-for="(item, index) in 4" :key="index" class="child mb-6">
<img class="rounded-8 w-b100 h-100"
src="https://res.volccdn.com/obj/volc-console-fe/dramart/static/image/PIC3.22e42366.png" alt="">
<div class="flex-align-center gap-4 mt-4">
<el-select v-model="aiTypeVal" placeholder="未配置角色">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-button :icon="MoreFilled" style="padding-left: 8px;padding-right: 8px;border-radius: 8px;" />
</div>
</div>
<Empty v-if="1 == 2" text="暂未选择分镜场景" />
</div>
</div>
<div class="mt-20">
<div class="flex-between-center">
<span>场景道具</span>
<el-button text :icon="Plus" circle />
</div>
<div class="mt-6">
<div v-for="(item, index) in 2" :key="index" class="flex-align-center gap-4 mt-10">
<img class="h-40 w-40 rounded-8"
src="https://res.volccdn.com/obj/volc-console-fe/dramart/static/image/PIC3.22e42366.png" alt="">
<el-select v-model="aiTypeVal" placeholder="未配置角色">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-button :icon="MoreFilled" style="padding-left: 8px;padding-right: 8px;border-radius: 8px;" />
</div>
<Empty v-if="1 == 2" text="暂未选择场景道具" />
</div>
</div>
</div>
</section>
<section class="flex-1 flex flex-col bg-fff rounded-16 p-10 mt-10 right">
<div class="flex-between-center">
<span>分镜视频生成</span>
<el-icon color="#888" size="12">
<DocumentCopy />
</el-icon>
</div>
<div class="flex h-b100 mt-10 gap-10">
<div class="flex-3 flex flex-col rounded-16 p-10 card1">
<div class="flex gap-4">
<div v-if="fileList.length > 0" class="relative w-40 h-40 rounded-4 overflow-hidden imgbox" v-for="(item, index) in fileList" :key="index" >
<img :src="item.url" class="w-b100 h-b100" />
<el-icon class="absolute right-2 top-2 del cursor-pointer" @click="delImg(index)"><CircleCloseFilled /></el-icon>
</div>
<el-upload
v-model:file-list="fileList"
action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
list-type="picture-card"
class="custom-upload"
:show-file-list="false"
>
<el-icon size="18"><Plus /></el-icon>
</el-upload>
</div>
<div class="flex-1"></div>
<div class="">不阿斯卡斯卡</div>
</div>
<div class="flex-2 flex flex-col rounded-12 card2 overflow-hidden" style="background: #fafbfd">
<video src="@/static/imgs/mp4.mp4" class="w-b100 bg-2b303a flex-1" controls></video>
<div class="flex-center gap-4 px-20 mt-10">
<div v-for="(item, index) in 2" :key="index" class="fjitem flex rounded-8 cursor-pointer" @click="changeVideo(item, index)" :class="videoIndex === index ? 'active': ''">
<img class="h-36 w-50 rounded-6"
src="https://res.volccdn.com/obj/volc-console-fe/dramart/static/image/PIC3.22e42366.png" alt="">
</div>
</div>
</div>
</div>
</section>
</div>
</section>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { Plus, DocumentCopy, CopyDocument,CircleCloseFilled, MoreFilled, Delete } from '@element-plus/icons-vue'
import Empty from './Empty.vue';
const fenjDesc = ref('')
const aiTypeVal = ref('')
const options = [
{
value: '1',
label: '林霞',
}
]
const fileList = ref([
{
name: 'food.jpeg',
url: 'https://res.volccdn.com/obj/volc-console-fe/dramart/static/image/PIC3.22e42366.png',
},
{
name: 'food1.jpeg',
url: 'https://res.volccdn.com/obj/volc-console-fe/dramart/static/image/PIC3.22e42366.png',
}
])
const delImg = (index: number) => {
fileList.value.splice(index, 1)
}
const videoIndex = ref(0)
const changeVideo = (item: any, index: number) => {
videoIndex.value = index
}
</script>
<style lang="scss" scoped>
.box {
border: 1px solid #ddd;
.bg-f4f6ff {
background-color: #f4f6ff;
}
.bg-e3e9ff {
background-color: #e3e9ff;
}
.btnsborder {
border: 1px solid #eeeeff;
}
.child {
flex: 0 0 calc(50% - 5px);
}
.h-548 {
height: 548px;
overflow-y: auto;
}
.right {
.card1 {
border: 1px solid #5252ff;
box-shadow: 0px 0px 3px 1px rgba(177, 177, 255, 1.3);
}
.fjitem {
border: 1px solid #ddd;
padding: 1px;
&.active {
border: 1px solid #5252ff;
}
}
}
.imgbox {
.del {
display: none;
}
&:hover{
.del{
display: inline-block;
}
}
}
}
.bg-2b303a {
background-color: #2b303a;
}
:deep(.el-select__wrapper) {
border-radius: 8px;
}
/* 控制上传卡片的大小 */
.custom-upload :deep(.el-upload--picture-card) {
width: 40px;
height: 40px;
--el-upload-picture-card-size: 40px; /* 修改 Element Plus 变量 */
}
</style>
\ No newline at end of file
<template>
<div></div>
</template>
<script setup lang="ts"></script>
<style lang="scss" scoped></style>
\ No newline at end of file
This diff is collapsed.
<template>
<section class="flex h-b100">
<section class="aside flex-align-center flex-col">
<span class="py-16">集数</span>
<div class="flex-1 flex-center flex-col gap-10 overflow-y-auto hide-scrollbar">
<div v-for="(item, index) in 100" :key="index"
class="cursor-pointer w-36 min-h-36 rounded-4 flex-center text-888 jj"
:class="indexVal === index ? 'active' : ''">
{{ index + 1 }}</div>
</div>
</section>
<section class="flex flex-col w-b100 box">
<div class="flex-between-center py-20 px-36">
<span class="bg-fff py-6 px-14 rounded-4 boxshadow-1">分镜表</span>
<div class="flex gap-10">
<el-select v-model="aiTypeVal" 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 :icon="DocumentCopy" class="hover">批量生成</el-button>
</div>
</div>
<div class="flex-1 overflow-y-auto px-36 hide-scrollbar">
<div class="fw-bold">第1集 凌晨的咖啡与雨</div>
<div class="fs-12 text-666 mt-4">
林夏在雨夜来到便利店,与店员阿哲交谈。她倾诉人际关系的疲惫与困惑,阿哲以温和的话语给予慰藉。
<el-popover placement="bottom" :width="400">
<template #reference>
<el-button text ><span class="fs-12">详情</span></el-button>
</template>
<div class="max-h-200 overflow-y-auto">
<p>林夏在雨夜来到便利店,与店员阿哲交谈。她倾诉人际关系的疲惫与困惑,阿哲以温和的话语给予慰藉。</p>
<p>林夏在雨夜来到便利店,与店员阿哲交谈。她倾诉人际关系的疲惫与困惑,阿哲以温和的话语给予慰藉。</p>
<p>林夏在雨夜来到便利店,与店员阿哲交谈。她倾诉人际关系的疲惫与困惑,阿哲以温和的话语给予慰藉。</p>
</div>
</el-popover>
</div>
<div v-for="(item, index) in 2" :key="index" class="pb-20">
<Episode />
</div>
</div>
</section>
</section>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { DocumentCopy } from '@element-plus/icons-vue'
import Episode from './Episode.vue';
const indexVal = ref(0)
const aiTypeVal = ref('')
const options = [
{
value: '1',
label: 'DouBao-hahyha-1.0',
},
{
value: '2',
label: 'DouBao-hahyha-12.0'
},
]
</script>
<style lang="scss" scoped>
.aside {
width: 60px;
border-right: 1px solid #eee;
.jj {
border: 1px solid #ddd;
&.active {
border: 1px solid #5252ff;
background: #5252ff;
color: #fff;
}
}
}
.box {
.boxshadow-1 {
box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.1);
}
}
</style>
\ No newline at end of file
This diff is collapsed.
<template>
<div class="flex flex-col h-b100">
<DramaHeader @change-tab="changeTab" :indexVal="indexVal" />
<div class="flex-1 overflow-y-auto">
<StepOne v-if="indexVal === 0" @next="next" />
<StepTwo v-if="indexVal === 1" @next="next" />
<StepThree v-if="indexVal === 2" />
<StepFour v-if="indexVal === 3" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import DramaHeader from './components/DramaHeader.vue';
import StepOne from './components/StepOne.vue';
import StepTwo from './components/StepTwo.vue';
import StepThree from './components/StepThree.vue';
import StepFour from './components/StepFour.vue';
const indexVal = ref(2);
const changeTab = (val: number) => {
indexVal.value = val;
}
const next = (val: number) => {
indexVal.value = val;
}
</script>
<style lang="scss" scoped>
</style>
\ No newline at end of file
<template>
<div class="px-70 pt-34">
<div class="fs-18">剧本生短剧</div>
<div class="flex-center gap-10 mt-26">
<el-input v-model="searchValue" class="responsive-input" placeholder="搜索短剧项目名称" :prefix-icon="Search" />
<el-date-picker v-model="value1" type="daterange" single-panel start-placeholder="开始日期" end-placeholder="结束日期" />
<el-select v-model="selectValue" placeholder="Select" style="width: 240px">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-button type="primary" color="#785BE5" :dark="true">
<img src="@/static/imgs/icon1.png" alt="">
<span class="ml-4">创建项目</span>
</el-button>
</div>
<section>
<div class="flex flex-wrap gap-30 mt-30">
<div v-for="(item, index) in 8" :key="index" class="child cursor-pointer" @click="gotoDetail(item)">
<div class="bgjb rounded-12 flex-center">
<img src="@/static/imgs/icon1.png" class="w-30" alt="">
</div>
<div class="flex-align-center mt-12">
<span class="fs-16">大炎神工:我靠折纸镇万妖</span>
<span class="bg-EAEAF9">创作中</span>
</div>
<div class="flex-between-center mt-14">
<div class="text-999 fs-12">2020-10-22 13:20:30 创建</div>
<el-dropdown>
<el-button text :icon="MoreFilled" class="morebtn" />
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="openModal(item, '1')">编辑名称</el-dropdown-item>
<el-dropdown-item @click="openModal(item, '2')">删除</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</div>
</section>
<section class="mt-20 flex justify-end">
<el-pagination v-model:current-page="pages.current" v-model:page-size="pages.pageSize"
:page-sizes="[10, 20, 40, 60]" size="small" :background="false" layout="total, sizes, prev, pager, next, jumper"
:total="pages.total" @size-change="handleSizeChange" @current-change="handleCurrentChange" />
</section>
<EditProjectNameDialog ref="editProjectNameRef" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { Search, MoreFilled } from '@element-plus/icons-vue'
import EditProjectNameDialog from './components/EditProjectNameDialog.vue';
import { useRouter } from 'vue-router';
const searchValue = ref('');
const value1 = ref();
const selectValue = ref('1');
const router = useRouter();
const pages = ref({
current: 1,
pageSize: 10,
total: 100
})
const handleSizeChange = (val: number) => {
console.log(`${val} items per page`)
}
const handleCurrentChange = (val: number) => {
console.log(`current page: ${val}`)
}
const options = [
{
value: '1',
label: '所有成员',
},
{
value: '2',
label: '由我创建',
},
]
const editProjectNameRef: any = ref(null)
const openModal = (item: any, type: string) => {
editProjectNameRef.value?.open(item, type)
}
const gotoDetail = (item: any) => {
router.push({ path: '/drama' })
}
</script>
<style scoped lang="scss">
.child {
flex: 0 0 calc(20% - 24px);
/* (4个间隙 × 30px) / 5 = 15px */
.bg-EAEAF9 {
background-color: #EAEAF9;
color: #785BE5;
padding: 2px 4px;
border-radius: 4px;
font-size: 12px;
margin-left: 10px;
}
.bgjb {
width: 100%;
height: 187px;
background: url(@/static/imgs/bg1.png) no-repeat 50%;
background-size: cover;
}
.morebtn {
opacity: 0;
}
&:hover {
.morebtn {
opacity: 0.9;
}
}
}
:deep(.el-pager) {
gap: 6px;
}
:deep(.el-pager li) {
background-color: transparent;
border: 1px solid #EAEAF9;
border-radius: 4px;
&.is-active {
border-color: #785BE5;
}
}
:deep(.btn-prev),
:deep(.btn-next) {
background-color: transparent;
border: 1px solid #EAEAF9;
border-radius: 4px;
margin-right: 6px;
margin-left: 6px;
}
:deep(.el-button.is-text) {
outline: none !important;
&:hover {
background-color: #eee;
}
}
/* 1800px以下:一行4个 */
@media (max-width: 1800px) {
.child {
flex: 0 0 calc(25% - 22.5px);
/* (3个间隙 × 30px) / 4 = 15px */
}
}
/* 1400px以下:一行3个 */
@media (max-width: 1400px) {
.child {
flex: 0 0 calc(33.333% - 20px);
/* (2个间隙 × 30px) / 3 ≈ 20px */
}
}
</style>
\ No newline at end of file
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"types": ["vite/client"],
"ignoreDeprecations": "5.0",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}
{
"compilerOptions": {
"module": "ESNext",
"composite": true,
"moduleResolution": "bundler",
"types": ["node"]
},
"include": ["vite.config.ts"]
}
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { fileURLToPath, URL } from 'node:url';
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server: {
port: 4173
}
});
This source diff could not be displayed because it is too large. You can view the blob instead.
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