🎉 项目基础架构完成

This commit is contained in:
li-chx
2025-04-23 17:43:14 +08:00
commit 15c0a1de0e
40 changed files with 2645 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
<script setup lang="ts">
const colorMode = useColorMode();
onMounted(() => {
watchEffect(() => {
if (colorMode.value === 'dark') {
document.documentElement.setAttribute('theme-mode', 'dark');
} else {
document.documentElement.removeAttribute('theme-mode');
}
});
});
</script>
+40
View File
@@ -0,0 +1,40 @@
<script setup lang="ts">
import type { TableProps, TableRowData } from 'tdesign-vue-next';
const emit = defineEmits(['pageChange', 'onRowClick']);
const data = defineModel<unknown>('data');
const columns = defineModel<TableProps['columns']>('columns');
const loading = defineModel<boolean>('loading');
const pagination = defineModel<TableProps['pagination']>('pagination');
async function onPageChange(pageInfo: { current: number; pageSize: number }) {
console.log('Page change');
console.log(pageInfo);
emit('pageChange', pageInfo);
}
function onRowClick(x: any) {
emit('onRowClick', x);
}
function onSelectChange() {
console.log('Select change');
}
</script>
<template>
<t-table
row-key="index"
:data="data as TableRowData[]"
:columns="columns"
:pagination="pagination"
:table-layout="'auto'"
:bordered="true"
:stripe="true"
:lazy-load="true"
:loading="loading"
@page-change="onPageChange"
@select-change="onSelectChange"
@row-click="onRowClick"
>
</t-table>
</template>
+31
View File
@@ -0,0 +1,31 @@
type Theme = 'default' | 'test' | 'self';
const themeStorageKey = 'tdesign-vue-next-theme';
export const useThemeStore = defineStore('theme', () => {
const theme = ref<Theme>('default');
const setTheme = (_theme: Theme) => {
theme.value = _theme;
};
onMounted(() => {
const storageValue = localStorage.getItem(themeStorageKey);
if (storageValue) theme.value = storageValue as Theme;
const styleElement = document.createElement('link');
watch(theme, () => {
localStorage.setItem(themeStorageKey, theme.value);
styleElement.type = 'text/css';
styleElement.rel = 'stylesheet';
styleElement.href = `/styles/tdesign-theme-${theme.value}.css?t=${Date.now()}`;
if (styleElement.parentElement === document.head) {
document.head.removeChild(styleElement);
}
document.head.appendChild(styleElement);
});
});
return { theme, setTheme };
});
+7
View File
@@ -0,0 +1,7 @@
<template>
<div
class="w-screen h-screen bg-white dark:bg-black dark:text-white transition-all"
>
<slot />
</div>
</template>
+259
View File
@@ -0,0 +1,259 @@
<script setup lang="ts">
import resolveConfig from 'tailwindcss/resolveConfig';
import tailwindConfig from '~/tailwind.config';
import type { Config } from 'tailwindcss';
import { useWindowSize } from '@vueuse/core';
const theme = useThemeStore();
const config = resolveConfig(tailwindConfig as Config);
const { collapsed, manualCollapsed } = storeToRefs(useSystemStore());
const lg = parseFloat(
config.theme.screens.lg.substring(0, config.theme.screens.lg.length - 2),
);
const modeName = ref('sunny');
// watch()
// computed(())
watch(useWindowSize().width, (val) => {
console.log(val);
console.log(collapsed.value);
console.log(manualCollapsed.value);
setCollapsed(val);
});
function setCollapsed(val: number) {
if ((val > lg && !collapsed.value) || (val < lg && collapsed.value)) {
manualCollapsed.value = false;
return;
}
if (val > lg && collapsed.value) {
if (manualCollapsed.value) return;
collapsed.value = false;
} else if (val < lg && !collapsed.value) {
if (manualCollapsed.value) return;
collapsed.value = true;
}
}
function changeCollapsed() {
collapsed.value = !collapsed.value;
manualCollapsed.value = true;
}
const { currentPage } = storeToRefs(useSystemStore());
function changeMode() {
const x = useColorMode();
console.log(x.preference);
console.log(x.value);
if (x.preference === 'light') {
x.preference = 'dark';
modeName.value = 'moon';
} else if (x.preference === 'dark') {
x.preference = 'sepia';
modeName.value = 'tea';
} else {
x.preference = 'light';
modeName.value = 'sunny';
}
}
function changeTheme() {
console.log(theme.theme);
if (theme.theme === 'default') {
theme.setTheme('test');
} else if (theme.theme === 'test') {
theme.setTheme('self');
} else if (theme.theme === 'self') {
theme.setTheme('default');
}
}
function backToLogin() {
const router = useRouter();
router.push('/login');
}
watch(currentPage, (val) => {
const router = useRouter();
router.push('/base/' + val);
});
async function sendTest() {
try {
// const response = await $fetch('/api/login/test', {
// method: 'GET',
// });
$fetch('/api/file/test', { method: 'GET' }).catch(() => {
console.log('error');
$fetch('/api/login/login', {
method: 'POST',
body: {
username: 'admin',
password: 'password',
},
}).then(async () => {
const result = await $fetch('/api/file/test', { method: 'GET' });
console.log(result);
});
});
// const response = await $fetch('/api/login/login', {
// method: 'POST',
// body: {
// username: 'admin',
// password: 'password',
// },
// });
// console.log(response);
} catch (err) {
console.log(err);
}
}
onMounted(() => setCollapsed(useWindowSize().width.value));
</script>
<template>
<t-layout class="h-full">
<t-header>
<t-head-menu value="item1" height="120px">
<span class="text-xl">李晨鑫 Web 实验</span>
<template #operations>
<a @click="changeMode">
<t-icon class="t-menu__operations-icon" :name="modeName" />
</a>
<a @click="changeTheme">
<t-icon class="t-menu__operations-icon" name="fill-color" />
</a>
<a @click="backToLogin">
<t-icon class="t-menu__operations-icon" name="rollback" />
</a>
<t-button @click="sendTest" />
</template>
<!-- <template #logo>-->
<!-- <img-->
<!-- width="136"-->
<!-- class="logo"-->
<!-- src="https://www.tencent.com/img/index/menu_logo_hover.png"-->
<!-- alt="logo"-->
<!-- />-->
<!-- </template>-->
<!-- <t-menu-item value="item1"> 已选内容 </t-menu-item>-->
<!-- <t-menu-item value="item2"> 菜单内容一 </t-menu-item>-->
<!-- <t-menu-item value="item3"> 菜单内容二 </t-menu-item>-->
<!-- <t-menu-item value="item4" :disabled="true"> 菜单内容三 </t-menu-item>-->
<!-- <template #operations>-->
<!-- <a href="javascript:;"-->
<!-- ><t-icon class="t-menu__operations-icon" name="search"-->
<!-- /></a>-->
<!-- <a href="javascript:;"-->
<!-- ><t-icon class="t-menu__operations-icon" name="notification-filled"-->
<!-- /></a>-->
<!-- <a href="javascript:;"-->
<!-- ><t-icon class="t-menu__operations-icon" name="index"-->
<!-- /></a>-->
<!-- </template>-->
</t-head-menu>
</t-header>
<t-layout :class="{ collapsed: collapsed, 'non-collapsed': !collapsed }">
<t-aside class="border-t border-gray-100 dark:border-gray-700">
<t-menu
v-model="currentPage"
theme="light"
value="dashboard"
height="550px"
:collapsed="collapsed"
>
<t-menu-item value="home">
<template #icon>
<t-icon name="home" />
</template>
首页
</t-menu-item>
<t-submenu value="resource" title="功能菜单">
<template #icon>
<t-icon name="server" />
</template>
<t-menu-item value="peopleManage">
<span>用户管理</span>
</t-menu-item>
<t-menu-item value="fileManage">
<span>文件管理</span>
</t-menu-item>
</t-submenu>
<t-menu-item value="root">
<template #icon>
<t-icon name="root-list" />
</template>
导航三
</t-menu-item>
<t-menu-item value="control-platform">
<template #icon>
<t-icon name="control-platform" />
</template>
导航四
</t-menu-item>
<t-menu-item value="precise-monitor">
<template #icon>
<t-icon name="precise-monitor" />
</template>
导航五
</t-menu-item>
<t-menu-item value="mail">
<template #icon>
<t-icon name="mail" />
</template>
导航六
</t-menu-item>
<t-menu-item value="user-circle">
<template #icon>
<t-icon name="user-circle" />
</template>
导航七
</t-menu-item>
<t-menu-item value="play-circle">
<template #icon>
<t-icon name="play-circle" />
</template>
导航八
</t-menu-item>
<t-menu-item value="edit1">
<template #icon>
<t-icon name="edit-1" />
</template>
导航九
</t-menu-item>
<template #operations>
<t-button
class="t-demo-collapse-btn"
variant="text"
shape="square"
@click="changeCollapsed"
>
<template #icon>
<t-icon name="view-list" />
</template>
</t-button>
</template>
</t-menu>
</t-aside>
<t-layout>
<t-content class="overflow-auto">
<NuxtPage />
</t-content>
<t-footer class="m-auto">
Copyright @ 2019-{{ new Date().getFullYear() }} Tencent. All Rights
Reserved
</t-footer>
</t-layout>
</t-layout>
</t-layout>
</template>
<style scoped lang="less">
.collapsed {
:deep(.t-layout__sider) {
@apply w-20;
}
}
.non-collapsed {
:deep(.t-layout__sider) {
@apply w-60;
}
}
</style>
+30
View File
@@ -0,0 +1,30 @@
<script setup lang="ts">
const x = ref();
</script>
<template>
<div class="p-6">
<div class="flex md:flex-row flex-col">
<div class="flex-[1] m-2 max-h-80">
<div class="flex flex-row md:flex-col justify-center">
<span class="text-2xl ml-auto mr-auto md:mt-6 mt-20 mb-6"
>欢迎 {{ useUserStore().userName }}
</span>
<t-image
src="/avatar.jpg"
alt="头像"
shape="circle"
:fit="'contain'"
class="max-h-80 max-w-80 m-auto"
/>
</div>
</div>
<div class="flex-[2] m-2">
<t-calendar> </t-calendar>
</div>
</div>
</div>
</template>
<style scoped lang="less"></style>
@@ -0,0 +1,87 @@
<script setup lang="tsx">
import SuperTable from '~/components/SuperTable.vue';
import type { TableProps } from 'tdesign-vue-next';
const pagination = ref<TableProps['pagination']>({
defaultPageSize: 10,
total: 100,
defaultCurrent: 1,
});
const columns = ref<TableProps['columns']>([
{
colKey: 'index',
title: '序号',
},
{
colKey: 'date',
title: '日期',
},
{
colKey: 'name',
title: '姓名',
},
{
colKey: 'province',
title: '省份',
},
{
colKey: 'city',
title: '市区',
},
{
colKey: 'address',
title: '地址',
},
{
colKey: '',
title: '邮编',
},
{
colKey: 'operator',
title: '操作',
cell: (_, { row }) => (
<div>
<t-button onClick={() => editUser(row)}>编辑</t-button>
<t-button onClick={() => deleteUser(row)}>删除</t-button>
</div>
),
},
]);
const data = ref([]);
for (let i = 0; i < 30; i++) {
data.value.push({
index: i + 1,
date: '2023-10-01',
name: 'John Doe',
province: 'Province A',
city: 'City A',
address: 'Address A',
postalCode: '123456',
} as never);
}
function editUser(row: never) {}
function deleteUser(row: never) {}
</script>
<template>
<div class="p-6">
<div class="flex justify-between p-4">
<t-button>新增</t-button>
<t-input placeholder="搜索" clearable class="max-w-60">
<template #suffixIcon>
<search-icon :style="{ cursor: 'pointer' }" />
</template>
</t-input>
</div>
<super-table
:data="data"
:columns="columns"
:loading="false"
:pagination="pagination"
></super-table>
</div>
</template>
<style scoped lang="less"></style>
+69
View File
@@ -0,0 +1,69 @@
<template>
<div class="flex flex-col items-center px-4 py-10 gap-y-8 h-full">
<h1 class="text-[26px]">Nuxt & TDesign Vue Next</h1>
<div class="space-x-2">
<TButton
ghost
@click="
$colorMode.preference =
$colorMode.preference === 'light' ? 'dark' : 'light'
"
>
<template #icon>
<TIcon
class="mr-1"
:name="$colorMode.preference === 'light' ? 'heart' : 'heart-filled'"
/>
当前主题{{ $colorMode.preference === 'light' ? 'Light' : 'Dark' }}
</template>
</TButton>
</div>
<div class="flex items-center gap-x-4">
<TButton @click="theme.setTheme('default')">默认主题</TButton>
<TButton @click="theme.setTheme('test')">主题 1</TButton>
<TButton @click="theme.setTheme('self')">主题 2</TButton>
</div>
<div class="grid grid-cols-2 sm:flex flex-row items-center gap-2">
<TButton>按钮示例</TButton>
<TButton theme="warning" loading>按钮示例</TButton>
<TButton theme="danger" disabled>按钮示例</TButton>
<TButton theme="success" variant="outline">按钮示例</TButton>
</div>
<div class="flex flex-col sm:flex-row items-center gap-4">
<!-- tag-name TagName 写法都可以 -->
<t-switch v-model="isLoading" class="flex-none" size="large">
<template #label> 加载中 </template>
</t-switch>
<TTagInput v-model="tags" drag-sort clearable>
<template #label> 你喜欢的前端框架 </template>
</TTagInput>
</div>
<TAlert :theme="alertTheme" close>{{ date }}</TAlert>
</div>
</template>
<script setup lang="ts">
const theme = useThemeStore();
const tags = ref(['Vue', 'React', 'Angular']);
const isLoading = ref(true);
const alertTheme = ref<'success' | 'warning' | 'error' | 'info'>('success');
let date: Date;
onMounted(() => {
date = new Date();
setInterval(() => {
isLoading.value = !isLoading.value;
if (alertTheme.value === 'success') {
alertTheme.value = 'warning';
} else if (alertTheme.value === 'warning') {
alertTheme.value = 'error';
} else if (alertTheme.value === 'error') {
alertTheme.value = 'info';
} else {
alertTheme.value = 'success';
}
}, 2000);
});
</script>
+74
View File
@@ -0,0 +1,74 @@
<script setup lang="ts">
import { PageType } from '~/stores/login';
const store = useLoginStore();
const { currentType, loginForm } = storeToRefs(store);
</script>
<template>
<div class="w-full">
<div
class="flex md:flex-row items-center md:justify-center w-full flex-col gap-4"
>
<div class="flex flex-col items-center gap-4">
<button class="w-56 h-10 bg-qqColor text-white rounded-md"
><img class="max-h-7 inline" src="/QQ.png" alt="QQ logo" />
<span class="text-white">QQ登录</span>
</button>
<button class="w-56 h-10 bg-wechatColor rounded-md"
><img
class="max-h-7 inline"
src="public/微信.png"
alt="wechat logo"
/>
<span class="text-white">微信登录</span>
</button>
<button class="w-56 h-10 bg-alipayColor rounded-md"
><img
class="max-h-7 inline"
src="public/支付,支付宝.png"
alt="alipay logo"
/>
<span class="text-black">支付宝登录</span>
</button>
</div>
<div
class="relative hidden md:flex flex-col items-center gap-4 w-2/5 max-w-24 select-none"
>
<div class="w-1 h-44 bg-gray-200"></div>
<div
class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full border-4 border-gray-200 w-10 h-10 bg-loginBG"
>
<span
class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-1"
>or
</span>
</div>
</div>
<div class="flex flex-col items-center gap-4 mt-3 md:mt-0">
<TInput
v-model="loginForm.username"
class="w-56"
placeholder="请输入用户名"
/>
<TInput
v-model="loginForm.password"
class="w-56"
type="password"
placeholder="请输入密码"
/>
<TButton class="w-56" @click="store.login">登录</TButton>
</div>
</div>
<div
class="flex flex-row items-center bg-gray-600 w-full h-10 justify-around rounded-b-md mt-4"
>
<button class="text-white" @click="currentType = PageType.register"
>注册
</button>
<button class="text-white" @click="currentType = PageType.reset"
>忘记密码
</button>
</div>
</div>
</template>
+140
View File
@@ -0,0 +1,140 @@
<script setup lang="ts">
import type {
FormProps,
UploadInstanceFunctions,
FormInstanceFunctions,
} from 'tdesign-vue-next';
import type { IAPIResponse } from '~/utils/APIResponse';
const store = useLoginStore();
const { registerForm, currentType } = storeToRefs(store);
const rules: FormProps['rules'] = {
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ email: true, message: '请输入邮箱', trigger: 'change' },
],
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
passwordRepeat: [
{ required: true, message: '请再次输入密码', trigger: 'blur' },
{
validator: (val) => val === registerForm.value.password,
message: '两次输入密码不一致',
trigger: 'change',
},
],
birth: [{ required: true, message: '请输入出生日期', trigger: 'change' }],
};
const uploadImage = ref<UploadInstanceFunctions>();
const formInstance = ref<FormInstanceFunctions>();
async function submitForm() {
console.log(formInstance.value);
if (!formInstance.value) return;
const result = await formInstance.value.validate();
if (typeof result === 'boolean' && result) {
formInstance.value.submit();
}
}
</script>
<template>
<div class="w-full">
<div class="flex md:flex-row flex-col items-center gap-4 justify-around">
<div class="flex flex-col items-center gap-4 max-w-96">
<t-form
ref="formInstance"
:label-width="10"
:data="registerForm"
:rules="rules"
@submit="() => store.register()"
>
<t-form-item name="email">
<TInput v-model="registerForm.email" placeholder="请输入邮箱" />
</t-form-item>
<t-form-item name="username">
<TInput
v-model="registerForm.username"
placeholder="请输入用户名"
/>
</t-form-item>
<t-form-item name="password">
<TInput
v-model="registerForm.password"
placeholder="请输入密码"
type="password"
/>
</t-form-item>
<t-form-item name="passwordRepeat">
<TInput
v-model="registerForm.passwordRepeat"
placeholder="请再次输入密码"
type="password"
/>
</t-form-item>
<t-form-item name="birth">
<TDatePicker
v-model="registerForm.birth"
placeholder="请输入出生日期"
/>
</t-form-item>
</t-form>
</div>
<TUpload
ref="uploadImage"
action="/api/file/uploadAvatar"
class="set-width-length bg-white bg-opacity-70"
theme="custom"
accept="image/*"
:draggable="true"
:format-response="
(res) => {
res = res as IAPIResponse<string>;
if (res.code === 200) {
registerForm.avatarURL = res.data;
return { url: res.data };
}
return { fail: res.message };
}
"
>
<div v-if="registerForm.avatarURL" class="aspect-square h-full">
<t-image
:src="registerForm.avatarURL"
class="w-40 h-40 object-cover rounded-md"
fit="contain"
/>
</div>
<div v-else>
<span class="text-blue-400"> 点击上传头像</span>
<span> / 拖拽到此区域 </span>
</div>
</TUpload>
</div>
<div
class="flex flex-row items-center bg-gray-600 w-full h-10 justify-around rounded-b-md mt-4"
>
<button class="text-white" @click="submitForm">注册用户</button>
<button class="text-white" @click="currentType = PageType.login"
>返回登录
</button>
</div>
</div>
</template>
<style scoped lang="less">
:deep(.t-upload__dragger),
.set-width-length {
@apply w-64;
@apply h-40;
@apply p-0;
@apply border-0;
}
// {
// @apply w-40;
// @apply h-40;
//}
:deep(.t-date-picker) {
@apply w-full;
}
</style>
+47
View File
@@ -0,0 +1,47 @@
<script setup lang="ts">
const store = useLoginStore();
const { currentType } = storeToRefs(store);
</script>
<template>
<div class="w-full">
<div
class="flex md:flex-row items-center md:justify-center w-full flex-col gap-4"
>
<div class="flex flex-col items-center gap-4">
<!-- <TInput class="w-56" placeholder="请输入邮箱" />-->
<!-- <TButton class="w-56">发送验证码</TButton>-->
<!-- <TInput class="w-56" placeholder="请输入验证码" />-->
<!-- <TInput class="w-56" type="password" placeholder="请输入新密码" />-->
<!-- <TButton class="w-56">确认</TButton>-->
<TInput class="w-56" placeholder="用户名" />
<TInput class="w-56" placeholder="旧密码" />
</div>
<div
class="hidden md:flex flex-col items-center gap-4 w-2/5 max-w-24 select-none"
>
<div class="w-1 h-44 bg-gray-200"></div>
<!-- <div-->
<!-- class="hidden absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full border-4 border-gray-200 w-10 h-10 bg-loginBG"-->
<!-- >-->
<!--&lt;!&ndash; <span&ndash;&gt;-->
<!--&lt;!&ndash; class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-1"&ndash;&gt;-->
<!--&lt;!&ndash; >or&ndash;&gt;-->
<!--&lt;!&ndash; </span>&ndash;&gt;-->
<!-- </div>-->
</div>
<div class="flex flex-col items-center gap-4">
<TInput class="w-56" placeholder="新密码" />
<TInput class="w-56" placeholder="确认新密码" />
</div>
</div>
<div
class="flex flex-row items-center bg-gray-600 w-full h-10 justify-around rounded-b-md mt-4"
>
<button class="text-white" @click="store.register()">修改密码</button>
<button class="text-white" @click="currentType = PageType.login"
>返回登录
</button>
</div>
</div>
</template>
+32
View File
@@ -0,0 +1,32 @@
<script setup lang="ts">
import LoginPart from '~/pages/login/components/LoginPart.vue';
import RegisterPart from '~/pages/login/components/RegisterPart.vue';
import ResetPart from '~/pages/login/components/ResetPart.vue';
import { PageType } from '~/stores/login';
const store = useLoginStore();
const { currentType } = storeToRefs(store);
// watch(currentType, (val) => {
// console.log(val);
// });
onMounted(() => {
// useThemeStore().setTheme('test');
// useThemeStore().setTheme('self');
});
</script>
<template>
<div
class="flex justify-center items-center min-h-screen bg-[url('public/fll.jpg')] bg-cover bg-no-repeat bg-fixed bg-center"
>
<div
class="flex flex-col items-center justify-center gap-4 w-3/5 md:w-4/5 max-w-2xl bg-loginBG pt-4 rounded-md bg-opacity-30 dark:bg-darkLoginBG dark:bg-opacity-40"
>
<h1 class="text-3xl font-bold">{{ store.getTypeName }}</h1>
<LoginPart v-if="currentType === PageType.login" />
<register-part v-else-if="currentType === PageType.register" />
<reset-part v-else-if="currentType === PageType.reset" />
</div>
</div>
</template>
+82
View File
@@ -0,0 +1,82 @@
interface RegisterData {
email: string;
username: string;
password: string;
passwordRepeat: string;
birth: string;
avatarURL: string;
}
interface LoginData {
username: string;
password: string;
}
interface resetData {
username: string;
oldPassword: string;
password: string;
passwordRepeat: string;
}
export enum PageType {
'login',
'register',
'reset',
}
export interface LoginStore {
loginForm: LoginData;
registerForm: RegisterData;
resetForm: resetData;
currentType: PageType;
}
export const useLoginStore = defineStore('Login', {
state: (): LoginStore => ({
loginForm: { username: '', password: '' },
registerForm: {
email: '',
username: '',
password: '',
passwordRepeat: '',
birth: '',
avatarURL: '',
},
resetForm: {
username: '',
oldPassword: '',
password: '',
passwordRepeat: '',
},
currentType: PageType.login,
}),
actions: {
async login() {
console.log(this.loginForm);
const router = useRouter();
await router.push('/base/home');
},
async register() {
console.log(this.registerForm);
},
async reset() {
console.log(this.resetForm);
},
},
getters: {
getTypeName: (state) => {
switch (state.currentType) {
case PageType.login:
return '登录';
case PageType.register:
return '注册';
case PageType.reset:
return '重置密码';
default:
console.log('未知');
return '未知';
}
},
},
});
+12
View File
@@ -0,0 +1,12 @@
export interface SystemStore {
currentPage: string;
collapsed: boolean;
manualCollapsed: boolean;
}
export const useSystemStore = defineStore('System', {
state: (): SystemStore => ({
currentPage: 'dashboard',
collapsed: false,
manualCollapsed: false,
}),
});
+14
View File
@@ -0,0 +1,14 @@
export interface UserState {
userId: string;
userName: string;
userEmail: string;
userAvatar: string;
}
export const useUserStore = defineStore('User', {
state: (): UserState => ({
userId: '',
userName: '李晨鑫',
userEmail: '',
userAvatar: '',
}),
});
+52
View File
@@ -0,0 +1,52 @@
// tailwind config file
import plugin from 'tailwindcss/plugin';
export default {
mode: 'jit',
darkMode: 'class',
theme: {
extend: {
colors: {
wechatColor: '#79b542',
qqColor: '#2f85c4',
alipayColor: '#e9ebea',
loginBG: '#f2f2f2',
darkLoginBG: '#1a1a1a',
},
letterSpacing: {
doublewidest: '.2em',
},
},
screens: {
sm: '640px',
md: '800px',
lg: '1000px',
xl: '1280px',
'2xl': '1536px',
},
},
plugins: [
plugin(function ({ addUtilities }) {
addUtilities({
'.scrollbar-hide': {
/* IE and Edge */
'-ms-overflow-style': 'none',
/* Firefox */
'scrollbar-width': 'none',
/* Safari and Chrome */
'&::-webkit-scrollbar': {
display: 'none',
},
},
});
}),
],
content: [
'./app.vue',
'./components/**/*.{vue,js,ts}',
'./layouts/**/*.vue',
'./pages/**/*.vue',
],
};
+151
View File
@@ -0,0 +1,151 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
// biome-ignore lint: disable
export {}
declare global {
const APIResponse: typeof import('../utils/APIResponse')['APIResponse']
const ApolloError: (typeof import('@apollo/client/core'))['ApolloError']
const EffectScope: (typeof import('vue'))['EffectScope']
const NetworkStatus: (typeof import('@apollo/client/core'))['NetworkStatus']
const PageType: typeof import('../stores/login')['PageType']
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
const computed: (typeof import('vue'))['computed']
const createApp: (typeof import('vue'))['createApp']
const createPinia: typeof import('pinia')['createPinia']
const customRef: (typeof import('vue'))['customRef']
const defineAsyncComponent: (typeof import('vue'))['defineAsyncComponent']
const defineComponent: (typeof import('vue'))['defineComponent']
const defineStore: typeof import('pinia')['defineStore']
const effectScope: (typeof import('vue'))['effectScope']
const getActivePinia: typeof import('pinia')['getActivePinia']
const getCurrentInstance: (typeof import('vue'))['getCurrentInstance']
const getCurrentScope: (typeof import('vue'))['getCurrentScope']
const h: (typeof import('vue'))['h']
const inject: (typeof import('vue'))['inject']
const isProxy: (typeof import('vue'))['isProxy']
const isReactive: (typeof import('vue'))['isReactive']
const isReadonly: (typeof import('vue'))['isReadonly']
const isRef: (typeof import('vue'))['isRef']
const mapActions: typeof import('pinia')['mapActions']
const mapGetters: typeof import('pinia')['mapGetters']
const mapState: typeof import('pinia')['mapState']
const mapStores: typeof import('pinia')['mapStores']
const mapWritableState: typeof import('pinia')['mapWritableState']
const markRaw: (typeof import('vue'))['markRaw']
const nextTick: (typeof import('vue'))['nextTick']
const onActivated: (typeof import('vue'))['onActivated']
const onAddToFavorites: (typeof import('@dcloudio/uni-app'))['onAddToFavorites']
const onBackPress: (typeof import('@dcloudio/uni-app'))['onBackPress']
const onBeforeMount: (typeof import('vue'))['onBeforeMount']
const onBeforeUnmount: (typeof import('vue'))['onBeforeUnmount']
const onBeforeUpdate: (typeof import('vue'))['onBeforeUpdate']
const onDeactivated: (typeof import('vue'))['onDeactivated']
const onError: (typeof import('@dcloudio/uni-app'))['onError']
const onErrorCaptured: (typeof import('vue'))['onErrorCaptured']
const onHide: (typeof import('@dcloudio/uni-app'))['onHide']
const onLaunch: (typeof import('@dcloudio/uni-app'))['onLaunch']
const onLoad: (typeof import('@dcloudio/uni-app'))['onLoad']
const onMounted: (typeof import('vue'))['onMounted']
const onNavigationBarButtonTap: (typeof import('@dcloudio/uni-app'))['onNavigationBarButtonTap']
const onNavigationBarSearchInputChanged: (typeof import('@dcloudio/uni-app'))['onNavigationBarSearchInputChanged']
const onNavigationBarSearchInputClicked: (typeof import('@dcloudio/uni-app'))['onNavigationBarSearchInputClicked']
const onNavigationBarSearchInputConfirmed: (typeof import('@dcloudio/uni-app'))['onNavigationBarSearchInputConfirmed']
const onNavigationBarSearchInputFocusChanged: (typeof import('@dcloudio/uni-app'))['onNavigationBarSearchInputFocusChanged']
const onPageNotFound: (typeof import('@dcloudio/uni-app'))['onPageNotFound']
const onPageScroll: (typeof import('@dcloudio/uni-app'))['onPageScroll']
const onPullDownRefresh: (typeof import('@dcloudio/uni-app'))['onPullDownRefresh']
const onReachBottom: (typeof import('@dcloudio/uni-app'))['onReachBottom']
const onReady: (typeof import('@dcloudio/uni-app'))['onReady']
const onRenderTracked: (typeof import('vue'))['onRenderTracked']
const onRenderTriggered: (typeof import('vue'))['onRenderTriggered']
const onResize: (typeof import('@dcloudio/uni-app'))['onResize']
const onScopeDispose: (typeof import('vue'))['onScopeDispose']
const onServerPrefetch: (typeof import('vue'))['onServerPrefetch']
const onShareAppMessage: (typeof import('@dcloudio/uni-app'))['onShareAppMessage']
const onShareTimeline: (typeof import('@dcloudio/uni-app'))['onShareTimeline']
const onShow: (typeof import('@dcloudio/uni-app'))['onShow']
const onTabItemTap: (typeof import('@dcloudio/uni-app'))['onTabItemTap']
const onThemeChange: (typeof import('@dcloudio/uni-app'))['onThemeChange']
const onUnhandledRejection: (typeof import('@dcloudio/uni-app'))['onUnhandledRejection']
const onUnload: (typeof import('@dcloudio/uni-app'))['onUnload']
const onUnmounted: (typeof import('vue'))['onUnmounted']
const onUpdated: (typeof import('vue'))['onUpdated']
const produce: (typeof import('immer'))['produce']
const provide: (typeof import('vue'))['provide']
const reactive: (typeof import('vue'))['reactive']
const readonly: (typeof import('vue'))['readonly']
const ref: (typeof import('vue'))['ref']
const resolveComponent: (typeof import('vue'))['resolveComponent']
const setActivePinia: typeof import('pinia')['setActivePinia']
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
const shallowReactive: (typeof import('vue'))['shallowReactive']
const shallowReadonly: (typeof import('vue'))['shallowReadonly']
const shallowRef: (typeof import('vue'))['shallowRef']
const storeToRefs: typeof import('pinia')['storeToRefs']
const toRaw: (typeof import('vue'))['toRaw']
const toRef: (typeof import('vue'))['toRef']
const toRefs: (typeof import('vue'))['toRefs']
const toValue: (typeof import('vue'))['toValue']
const triggerRef: (typeof import('vue'))['triggerRef']
const unref: (typeof import('vue'))['unref']
const useApolloClient: (typeof import('@vue/apollo-composable'))['useApolloClient']
const useAttrs: (typeof import('vue'))['useAttrs']
const useCssModule: (typeof import('vue'))['useCssModule']
const useCssVars: (typeof import('vue'))['useCssVars']
const useLazyQuery: (typeof import('@vue/apollo-composable'))['useLazyQuery']
const useLoginStore: typeof import('../stores/login')['useLoginStore']
const useMutation: (typeof import('@vue/apollo-composable'))['useMutation']
const useQuery: (typeof import('@vue/apollo-composable'))['useQuery']
const useSlots: (typeof import('vue'))['useSlots']
const useSystemStore: typeof import('../stores/system')['useSystemStore']
const useThemeStore: typeof import('../hooks/useTheme')['useThemeStore']
const useUserStore: typeof import('../stores/user')['useUserStore']
const watch: (typeof import('vue'))['watch']
const watchEffect: (typeof import('vue'))['watchEffect']
const watchPostEffect: (typeof import('vue'))['watchPostEffect']
const watchSyncEffect: (typeof import('vue'))['watchSyncEffect']
}
// for type re-export
declare global {
// @ts-ignore
export type { PageType, LoginStore } from '../stores/login'
import('../stores/login')
// @ts-ignore
export type { SystemStore } from '../stores/system'
import('../stores/system')
// @ts-ignore
export type { UserState } from '../stores/user'
import('../stores/user')
// @ts-ignore
export type { APIResponse, IAPIResponse } from '../utils/APIResponse'
import('../utils/APIResponse')
}
// for vue template auto import
import { UnwrapRef } from 'vue'
declare module 'vue' {
interface GlobalComponents {}
interface ComponentCustomProperties {
readonly APIResponse: UnwrapRef<typeof import('../utils/APIResponse')['APIResponse']>
readonly PageType: UnwrapRef<typeof import('../stores/login')['PageType']>
readonly acceptHMRUpdate: UnwrapRef<typeof import('pinia')['acceptHMRUpdate']>
readonly createPinia: UnwrapRef<typeof import('pinia')['createPinia']>
readonly defineStore: UnwrapRef<typeof import('pinia')['defineStore']>
readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
readonly mapActions: UnwrapRef<typeof import('pinia')['mapActions']>
readonly mapGetters: UnwrapRef<typeof import('pinia')['mapGetters']>
readonly mapState: UnwrapRef<typeof import('pinia')['mapState']>
readonly mapStores: UnwrapRef<typeof import('pinia')['mapStores']>
readonly mapWritableState: UnwrapRef<typeof import('pinia')['mapWritableState']>
readonly setActivePinia: UnwrapRef<typeof import('pinia')['setActivePinia']>
readonly setMapStoreSuffix: UnwrapRef<typeof import('pinia')['setMapStoreSuffix']>
readonly storeToRefs: UnwrapRef<typeof import('pinia')['storeToRefs']>
readonly useLoginStore: UnwrapRef<typeof import('../stores/login')['useLoginStore']>
readonly useSystemStore: UnwrapRef<typeof import('../stores/system')['useSystemStore']>
readonly useThemeStore: UnwrapRef<typeof import('../hooks/useTheme')['useThemeStore']>
readonly useUserStore: UnwrapRef<typeof import('../stores/user')['useUserStore']>
}
}
+14
View File
@@ -0,0 +1,14 @@
/* eslint-disable */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
// biome-ignore lint: disable
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}
}
+10
View File
@@ -0,0 +1,10 @@
export interface IAPIResponse<T> {
code: number;
message: string;
data: T;
}
export class APIResponse<T> implements IAPIResponse<T> {
code: number = 200;
message: string = '';
data: T = undefined as T;
}