[feature] 基本功能

This commit is contained in:
lichx 2024-12-30 03:28:42 +08:00
commit 46667405ec
35 changed files with 3082 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

3
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}

5
README.md Normal file
View File

@ -0,0 +1,5 @@
# Vue 3 + TypeScript + Vite
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue + TS</title>
</head>
<body style="margin:0;">
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

26
package.json Normal file
View File

@ -0,0 +1,26 @@
{
"name": "library-sys-for-jr",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"preview": "vite preview"
},
"dependencies": {
"@vitejs/plugin-vue-jsx": "^4.1.1",
"pinia": "^2.3.0",
"tdesign-vue-next": "^1.10.5",
"vue": "^3.5.13",
"vue-router": "4"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.1",
"@vue/tsconfig": "^0.7.0",
"less": "^4.2.1",
"typescript": "~5.6.2",
"vite": "^6.0.5",
"vue-tsc": "^2.2.0"
}
}

1665
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

BIN
public/loginBackground.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

12
src/App.vue Normal file
View File

@ -0,0 +1,12 @@
<script setup lang="ts">
</script>
<template>
<main>
<RouterView/>
</main>
</template>
<style scoped>
@import "index.less";
</style>

BIN
src/assets/LOGO-dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
src/assets/LOGO.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -0,0 +1,69 @@
<script setup lang="ts">
import type {Ref} from "vue";
export interface DrawerColumn{
key: string;
value: string;
}
const data: any = defineModel('data');
const columns:Ref<DrawerColumn[],DrawerColumn[]> = defineModel('columns') as Ref<DrawerColumn[],DrawerColumn[]>;
const visible = defineModel('visible');
const title = defineModel('title');
const emit = defineEmits(['close']);
const handleClose = () => {
emit('close');
};
function getContent(x:DrawerColumn):string
{
if(x.key === "ApprovalStatus")
{
switch(data.value[x.key])
{
case 0:
return "未批准";
case 1:
return "已批准";
case 2:
return "已拒绝";
}
}
return data.value[x.key];
}
</script>
<template>
<t-drawer v-model:visible="visible" :header="title" @close="handleClose"
style="display:inline-flex;">
<t-row>
<t-col class="name">
<t-row v-for="x of columns"><p class="nameContent">{{ x.value }}:</p></t-row>
</t-col>
<t-col class="value">
<t-row v-for="x of columns"><p class="valueContent">{{ getContent(x) }}&nbsp;</p></t-row>
</t-col>
</t-row>
</t-drawer>
</template>
<style scoped>
.name {
flex: 3;
}
.value {
flex: 7;
}
p{
margin:6px 0;
}
.nameContent{
width: 100%;
padding-right: 10px;
text-align: right;
}
.valueContent{
width: 100%;
padding-left: 10px;
}
</style>

View File

@ -0,0 +1,86 @@
<script setup lang="ts">
import {computed, ref} from 'vue'
import type {TableProps} from "tdesign-vue-next";
const emit = defineEmits(['pageChange','onRowClick']);
const data = defineModel('data');
const columns = defineModel('columns');
const configurableColumns = defineModel('configurableColumns');
const indexColumns = defineModel('indexColumns');
const secondaryColumns = defineModel('secondaryColumns');
const dataColumns = defineModel('dataColumns');
const loading = defineModel('loading')
const defaultDisplayColumns = defineModel('defaultDisplayColumns')
const pagination = defineModel('pagination')
const selectedRowKeys = defineModel('selectedRowKeys');
const customText = ref("配置列");
//@ts-ignore
const columnControllerConfig = computed<TableProps['columnController']>(() => ({
//
placement: "top-right",
// //
fields: configurableColumns.value,
//
dialogProps: {
preventScrollThrough: true,
},
//
buttonProps: customText.value
? {
content: '显示列控制',
theme: 'primary',
variant: 'base',
}
: undefined,
//
groupColumns: columns.value
? [
{
label: '指标维度',
value: 'index',
columns: indexColumns.value,
},
{
label: '次要维度',
value: 'secondary',
columns: secondaryColumns.value,
},
{
label: '数据维度',
value: 'data',
columns: dataColumns.value,
},
]
: undefined,
}));
function rehandleChange() {
console.log("change");
}
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" :default-display-columns="defaultDisplayColumns" :data="data" :columns="columns"
:selected-row-keys="selectedRowKeys"
:column-controller="columnControllerConfig" :pagination="pagination"
:table-layout="'auto'" :bordered="true" :stripe=true lazy-load="" :loading="loading"
@change="rehandleChange"
@page-change="onPageChange"
@select-change="onSelectChange"
@row-click="onRowClick"
>
</t-table>
</template>

41
src/index.less Normal file
View File

@ -0,0 +1,41 @@
@font-color: black;
@don-t-highlight-font-color: rgba(0, 0, 0, 0.6);
@blur-background-color: rgba(255, 255, 255, 0.5);
@anim-duration-slow: 0.28s;
@anim-time-fn-easing: cubic-bezier(0.38, 0, 0.24, 1);
.light-mode{
--basic-color: #ffffff;
}
.dark-mode{
--basic-color: #242424;
}
.content {
margin: 5px;
padding: 10px;
border-radius: 20px;
}
.tdesign-demo__select-input-ul-autocomplete {
padding: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.tdesign-demo__select-input-ul-autocomplete > li {
display: block;
border-radius: 3px;
line-height: 22px;
cursor: pointer;
padding: 3px 8px;
color: var(--td-text-color-primary);
transition: background-color 0.2s linear;
white-space: nowrap;
word-wrap: normal;
overflow: hidden;
text-overflow: ellipsis;
}
.tdesign-demo__select-input-ul-autocomplete > li:hover {
background-color: var(--td-bg-color-container-hover);
}

13
src/main.ts Normal file
View File

@ -0,0 +1,13 @@
import { createApp } from 'vue'
// import './style.css'
import App from './App.vue'
import router from "./router";
import {createPinia} from "pinia";
import TDesign from 'tdesign-vue-next'
import 'tdesign-vue-next/es/style/index.css'
createApp(App)
.use(TDesign)
.use(router)
.use(createPinia())
.mount('#app')

169
src/pages/base/index.vue Normal file
View File

@ -0,0 +1,169 @@
<script setup lang="ts">
import {useUserStore} from "@/store";
import {storeToRefs} from "pinia";
let store = useUserStore();
const {user, theme} = storeToRefs(store);
function changeTheme(newTheme: string) {
theme.value = newTheme;
document.getElementById('app')!.className = newTheme + "-theme"
document.documentElement.setAttribute('theme-mode', newTheme);
}
</script>
<template>
<t-layout :class="{layout:true}">
<t-header>
<t-head-menu value="item1" height="120px">
<template #logo>
<img style="width: 240px" class="logo"
:src="theme=='light'?'/src/assets/LOGO.png':'/src/assets/LOGO-dark.png'" alt="logo"/>
</template>
<div id="info-bar">
<div id="info">
<p>欢迎回来{{ user }}</p>
</div>
</div>
<template #operations>
<t-icon class="t-menu__operations-icon" :name="theme==='light'?'sunny':'moon'"
@click="changeTheme(theme==='light'?'dark':'light')"/>
<!-- <a href="/#/base/homepage">-->
<!-- <t-icon class="t-menu__operations-icon" name="home"/>-->
<!-- </a>-->
<a href="/#/base/settings">
<t-icon class="t-menu__operations-icon" name="setting"/>
</a>
<a href="/#/login">
<t-icon class="t-menu__operations-icon" name="poweroff"/>
</a>
</template>
</t-head-menu>
</t-header>
<t-layout>
<t-aside id="aside">
<t-menu theme="light" default-value="homepage" style="margin-right: 50px">
<!-- <t-menu-item value="homepage" :to="'/base/homepage'">-->
<!-- <template #icon>-->
<!-- <t-icon name="dashboard"/>-->
<!-- </template>-->
<!-- 主页-->
<!-- </t-menu-item>-->
<t-menu-item value="list" :to="'/base/list'">
<template #icon>
<t-icon name="search"/>
</template>
图书列表
</t-menu-item>
<t-menu-item value="reservation"
:to="'/base/reservation'">
<template #icon>
<t-icon name="precise-monitor"/>
</template>
预定列表
</t-menu-item>
<t-menu-item value="record"
:to="'/base/record'">
<template #icon>
<t-icon name="edit-1"/>
</template>
借阅记录
</t-menu-item>
</t-menu>
</t-aside>
<div class="baseContent">
<t-layout>
<t-content>
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<component :is="Component"/>
</transition>
</router-view>
</t-content>
<t-footer style="text-align: center"> Copyright @ 2024-2024 AlexJR?. All Rights Reserved<br>由TDesign强力驱动
</t-footer>
</t-layout>
</div>
</t-layout>
</t-layout>
</template>
<style lang="less" scoped>
@import "@/index.less";
@basic-color: var(--basic-color);
#aside {
border-top: 1px solid @basic-color;
}
.layout {
height: calc(100vh - 56px);
}
#info-bar {
* {
display: inline;
}
width: 100%;
text-align: right;
padding-right: 40px;
}
#info {
text-align: right;
margin-right: 50px;
}
#identities {
display: inline;
width: 388px;
* {
margin-right: 5px;
display: inline;
}
}
.fade-leave-active,
.fade-enter-active {
transition: opacity @anim-duration-slow @anim-time-fn-easing;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
.baseContent {
height: calc(100vh - 56px);
width: 100%;
overflow: auto;
}
/* 设置滚动条的宽度和高度 */
::-webkit-scrollbar {
width: 12px;
height: 12px;
}
/* 设置滚动条的背景色 */
::-webkit-scrollbar-track {
background: @basic-color;
}
/* 设置滚动条滑块的颜色 */
::-webkit-scrollbar-thumb {
background: #777;
border-radius: 6px;
}
/* 设置滚动条滑块在悬停时的颜色 */
::-webkit-scrollbar-thumb:hover {
background: #555;
}
</style>

28
src/pages/list/index.vue Normal file
View File

@ -0,0 +1,28 @@
<script setup lang="ts">
import ListCards from "@/pages/list/modules/listCards.vue";
//import Borrow from "@/pages/list/modules/borrow.vue";
import {useUserStore} from "@/store";
import {storeToRefs} from "pinia";
import {computed} from "vue";
const {theme} = storeToRefs(useUserStore());
const lightTheme = computed(() => theme.value === 'light');
</script>
<template>
<div class="search content" :class="{'light-mode':lightTheme,'dark-mode':!lightTheme}">
<!-- <t-typography-title level="h2">仓储查询</t-typography-title>-->
<t-input placeholder="请输入书名"></t-input>
<list-cards/>
<!-- <borrow/>-->
</div>
</template>
<style lang="less" scoped>
@import '@/index.less';
@basic-color: var(--basic-color);
.search {
background-color: @basic-color;
}
</style>

View File

@ -0,0 +1,53 @@
<script setup lang="ts">
import {useListStore} from "@/store";
import BasicDrawer, {type DrawerColumn} from "@/components/basic-drawer.vue";
import {storeToRefs} from "pinia";
let infos = useListStore().infos;
/* "title": "",
"author": "加西亚·马尔克斯",
"publisher": "南海出版公司",
"year": 1967,
"isbn": "9787544253993",
"genre": "魔幻现实主义",
"available_copies": 12*/
let columns: DrawerColumn[] = [
{key: 'title', value: '书名'},
{key: 'author', value: '作者'},
{key: 'publisher', value: '出版社'},
{key: 'year', value: '出版年份'},
{key: 'isbn', value: 'ISBN'},
{key: 'genre', value: '类型'},
{key: 'available_copies', value: '可用副本'},
]
let {drawerVisible} = storeToRefs(useListStore());
</script>
<template>
<div class="list-cards">
<t-card class="card" v-for="t of infos" :title="t.title" :bordered="true" hover-shadow="true"
:style="{ width: '400px' }" @click="drawerVisible = true">
<div>
作者{{ t.author }}<br>
出版社{{ t.publisher }}<br>
类型{{ t.genre }}<br>
可用副本{{ t.available_copies }}<br>
</div>
</t-card>
</div>
<basic-drawer :size="'medium'" :columns="columns" :data="useListStore().getCurrentData()" :footer="false" :visible="drawerVisible" :title="'书籍详情'" @close="drawerVisible = false"/>
</template>
<style scoped>
.list-cards {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
}
.card {
margin: 10px;
text-align: left;
}
</style>

139
src/pages/login/index.vue Normal file
View File

@ -0,0 +1,139 @@
<script setup lang="ts">
import {ref} from 'vue';
import Login from "@/pages/login/modules/login.vue";
import Register from "@/pages/login/modules/register.vue";
const type = ref('login');
function switchType(val: string) {
type.value = val;
}
</script>
<template>
<div id="login">
<div style="background-color:transparent;height:46px">
<div id="header">
<img id="icon" src="/src/assets/LOGO.png" alt="logo"/>
</div>
</div>
<div id="content">
<div id="placeholder"></div>
<div class="login-container">
<div class="title-container">
<h1 class="title margin-no">登录到</h1>
<h1 class="title">图书管理系统</h1>
<div class="sub-title">
<p class="tip">{{
type == 'register' ? '已有账号?' : '没有账号吗?'
}}</p>
<p class="tip clickTip" @click="switchType(type == 'register' ? 'login' : 'register')">
{{ type == 'register' ? '登录' : '注册新账号' }}
</p>
</div>
</div>
</div>
<login v-if="type==='login'"/>
<register v-else/>
</div>
</div>
</template>
<style scoped lang="less">
@import "../../index.less";
#login {
height: 100vh;
background-image: url("/loginBackground.jpg");
background-repeat: no-repeat;
background-position: center center;
background-attachment: fixed;
background-size: cover;
background-color: white;
}
#header {
backdrop-filter: blur(10px);
position: relative;
background-color: @blur-background-color;
color: transparent;
height: 46px;
* {
display: inline;
}
}
#icon {
margin-left: 9px;
margin-top: 4px;
width: 220px;
//height: 30px;
}
#title {
position: absolute;
left: 41px;
top: 3px;
margin-left: 20px;
}
#content {
width: 70vh;
height: calc(100vh - 46px);
backdrop-filter: blur(10px);
background-color: @blur-background-color;
margin-left: 5vh;
}
.form {
margin-right: 10vh;
}
.login-container {
margin-left: 100px;
}
.title {
font-size: 36px;
margin: 5px 0;
padding-top: 8px;
height: 35px;
}
.margin-no {
margin: 0;
}
.tip {
display: inline;
font-size: 14px;
margin-right: 8px;
color: @don-t-highlight-font-color;
}
.clickTip {
cursor: pointer;
color: @font-color;
}
.sub-title {
margin-top: 40px;
margin-bottom: 60px;
font-size: 14px;
justify-content: center;
align-items: center;
height: 20px;
}
#placeholder {
height: 150px;
}
</style>

View File

@ -0,0 +1,82 @@
<script setup lang="ts">
import {ref, reactive} from 'vue';
import {MessagePlugin} from 'tdesign-vue-next';
import type {FormProps, FormInstanceFunctions, InputProps} from 'tdesign-vue-next';
import {useUserStore} from "@/store";
import router from "@/router";
const FORM_RULES: FormProps['rules'] = {
code: [
{
required: true,
message: '账号必填',
}
],
password: [
{
required: true,
message: '密码必填',
}
]
};
const formData: FormProps['data'] = reactive({
code: '',
password: '',
});
const form = ref<FormInstanceFunctions>(null!);
const rememberLogin = ref(false);
const userStore = useUserStore();
userStore.logout();
const logIn = ref(false);
const onSubmit: FormProps['onSubmit'] = async ({validateResult, firstError}) => {
logIn.value = true;
if (validateResult === true) {
await login();
} else {
console.log('Validate Errors: ', firstError, validateResult);
await MessagePlugin.warning(firstError!);
}
logIn.value = false;
};
async function login() {
useUserStore().user = formData!.code;
useUserStore().login();
await MessagePlugin.success('登录成功');
await router.replace({path: '/base'})
}
// Input Enter submit
const onEnter: InputProps['onEnter'] = (_, {e}) => {
e.preventDefault();
};
</script>
<template>
<t-form :class="{form:true}" ref="form" :rules="FORM_RULES" :data="formData" :colon="true" :required-mark=false
@submit="onSubmit">
<t-form-item name="code">
<t-input v-model="formData.code" size='large' placeholder="请输入账号" @enter="onEnter"></t-input>
</t-form-item>
<t-form-item name="password">
<t-input v-model="formData.password" size='large' placeholder="请输入密码" type="password"/>
</t-form-item>
<t-form-item>
<t-checkbox :model="rememberLogin">记住账号</t-checkbox>
</t-form-item>
<t-form-item>
<t-space size="small">
<t-button :disabled="logIn" theme="primary" type="submit" size="large">登录</t-button>
</t-space>
</t-form-item>
</t-form>
</template>
<style scoped>
.form {
margin-right: 10vh;
}
</style>

View File

@ -0,0 +1,153 @@
<script setup lang="ts">
import {ref, reactive} from 'vue';
import {MessagePlugin, FormProps, FormInstanceFunctions, InputProps, CustomValidateObj} from 'tdesign-vue-next';
import {useUserStore} from "@/store";
function passwordValidator(val: any): CustomValidateObj {
if (val as string !== formData!.password)
return {result: false, message: '两次密码不一致', type: 'error'};
return {result: true, message: '', type: 'success'};
}
const FORM_RULES: FormProps['rules'] = {
code: [
{
required: true,
message: '账号必填',
}
],
password: [
{
required: true,
message: '密码必填',
}
],
anotherPassword: [
{
required: true,
message: '密码必填',
},
{
validator: passwordValidator
}
],
department: [
{
required: true,
message: '单位必填',
}
],
name: [
{
required: true,
message: '名字必填',
}
]
};
const formData: FormProps['data'] = reactive({
code: '',
password: '',
anotherPassword: '',
department: '',
name: '',
identity: []
});
const form = ref<FormInstanceFunctions>(null!);
const userStore = useUserStore();
const onSubmit: FormProps['onSubmit'] = async ({validateResult, firstError}) => {
if (validateResult === true) {
await register();
} else {
console.log('Validate Errors: ', firstError, validateResult);
await MessagePlugin.warning(firstError!);
}
};
async function register() {
let identity = 0;
formData!.identity.forEach((value:string) => {
switch (value) {
case '借用人':
identity |= 1;
break;
case '购买者':
identity |= 2;
break;
case '管理员':
identity |= 4;
break;
case '批准者':
identity |= 8;
break;
}
});
console.log(identity);
userStore.user = {
Id: "",
Code: formData!.code,
Password: formData!.password,
Name: formData!.name,
Department: formData!.department,
Identity: identity,
ApprovalStatus: 0,
ApproverId: "",
Approver: undefined,
MiscellaneousConfiguration: ""
}
try {
await userStore.register();
MessagePlugin.info('已提交审核');
} catch (e: any) {
console.log(e);
MessagePlugin.error(e.message);
return;
}
}
// Input Enter submit
const onEnter: InputProps['onEnter'] = (_, {e}) => {
e.preventDefault();
};
</script>
<template>
<t-form :class="{form:true}" ref="form" :rules="FORM_RULES" :data="formData" :colon="true" :required-mark=false
@submit="onSubmit">
<t-form-item name="code">
<t-input v-model="formData.code" size='large' placeholder="请输入编号" @enter="onEnter"/>
</t-form-item>
<t-form-item name="password">
<t-input v-model="formData.password" size='large' placeholder="请输入密码" @enter="onEnter"
type="password"/>
</t-form-item>
<t-form-item name="anotherPassword">
<t-input v-model="formData.anotherPassword" size='large' placeholder="请再次输入密码" @enter="onEnter"
type="password"/>
</t-form-item>
<t-form-item name="department">
<t-input v-model="formData.department" size='large' placeholder="请输入部门" @enter="onEnter"/>
</t-form-item>
<t-form-item name="name">
<t-input v-model="formData.name" size='large' placeholder="请输入姓名" @enter="onEnter"/>
</t-form-item>
<t-form-item>
<t-checkbox-group v-model="formData.identity" :options="['借用人','购买者','管理员','批准者']">
<t-checkbox >1</t-checkbox>
<t-checkbox >管理员</t-checkbox>
</t-checkbox-group>
</t-form-item>
<t-form-item>
<t-space size="small">
<t-button theme="primary" type="submit" size="large">注册</t-button>
</t-space>
</t-form-item>
</t-form>
</template>
<style scoped>
.form {
margin-right: 10vh;
}
</style>

View File

@ -0,0 +1,26 @@
<script setup lang="ts">
import {useUserStore} from "@/store";
import {storeToRefs} from "pinia";
import {computed} from "vue";
import RecordTable from "@/pages/record/modules/recordTable.vue";
const {theme} = storeToRefs(useUserStore());
const lightTheme = computed(() => theme.value === 'light');
</script>
<template>
<div class="search content" :class="{'light-mode':lightTheme,'dark-mode':!lightTheme}">
<t-input placeholder="请输入书名"></t-input>
<record-table/>
<!-- <borrow/>-->
</div>
</template>
<style lang="less" scoped>
@import '@/index.less';
@basic-color: var(--basic-color);
.search {
background-color: @basic-color;
}
</style>

View File

@ -0,0 +1,68 @@
<script setup lang="tsx">
import SuperTable from "@/components/super-table.vue";
import type {TableProps} from "tdesign-vue-next";
import {ref} from "vue";
import {useRecordStore} from "@/store/modules/record.ts";
/*
{
"title": "活着",
"borrow_date": "2024-12-08",
"due_date": "2024-12-22",
"status": "在借"
},
*/
const data = useRecordStore().infos;
const columns = ref<TableProps['columns']>([
{
colKey: "title",
title: "书名",
},
{
colKey: "borrow_date",
title: "借书日期",
},
{
colKey: "due_date",
title: "归还日期",
},
{
colKey: "status",
title: "状态",
cell: (_, row: any) => GetTypeHtml(row!)
}
]);
function GetTypeHtml({row: {status}}: { row: { status: string } }) {
switch (status) {
case '在借':
return <t-tag shape="round" theme="success" variant="light-outline">
<span>在借</span>
</t-tag>
case '已归还':
return <t-tag shape="round" theme="primary" variant="light-outline">
<span>已归还</span>
</t-tag>
case '逾期':
return <t-tag shape="round" theme="danger" variant="light-outline">
<span>逾期</span>
</t-tag>
}
}
const configurableColumns = ref(['title', 'borrow_date', 'due_date', 'status']);
const indexColumns = ref(['title', 'borrow_date', 'due_date', 'status']);
const defaultDisplayColumns = ref([...configurableColumns.value]);
const pagination = ref<TableProps['pagination']>({
defaultPageSize: 10,
total: 10,
defaultCurrent: 1,
})
</script>
<template>
<super-table :data="data" :columns="columns" :configurable-columns="configurableColumns" :index-columns="indexColumns" />
</template>
<style scoped>
</style>

View File

@ -0,0 +1,13 @@
<script setup lang="ts">
</script>
<template>
<div>
<t-typography-title level="h2">预定列表</t-typography-title>
</div>
</template>
<style scoped>
</style>

43
src/router/index.ts Normal file
View File

@ -0,0 +1,43 @@
import {createRouter, createWebHashHistory} from "vue-router";
import type {RouteRecordRaw} from "vue-router";
const defaultRouterList: Array<RouteRecordRaw> = [
{
path: '/',
redirect: '/base/homepage'
},
{
path: '/login',
name: 'login',
component: () => import('@/pages/login/index.vue'),
},
{
path: '/base',
redirect: '/base/list',
component: () => import('@/pages/base/index.vue'),
children: [
{
path: 'list',
name: 'list',
component: () => import('@/pages/list/index.vue'),
},
{
path: 'reservation',
name: 'reservation',
component: () => import('@/pages/reservation/index.vue'),
},
{
path: 'record',
name: 'record',
component: () => import('@/pages/record/index.vue'),
},
]
}
]
const allRoutes = [...defaultRouterList]
const router = createRouter({
history: createWebHashHistory(),
routes: allRoutes
});
export default router;

8
src/store/index.ts Normal file
View File

@ -0,0 +1,8 @@
import {createPinia} from "pinia";
const store = createPinia();
export * from './modules/user';
export * from './modules/list';
export {store};
export default store;

105
src/store/modules/list.ts Normal file
View File

@ -0,0 +1,105 @@
import {defineStore} from "pinia";
export const useListStore = defineStore('List', {
state: () => ({
infos: [
{
"title": "红楼梦",
"author": "曹雪芹",
"publisher": "人民文学出版社",
"year": 1791,
"isbn": "9787020002207",
"genre": "古典文学",
"available_copies": 10
},
// {
// "title": "三国演义",
// "author": "罗贯中",
// "publisher": "中华书局",
// "year": 1522,
// "isbn": "9787101003048",
// "genre": "历史小说",
// "available_copies": 8
// },
{
"title": "西游记",
"author": "吴承恩",
"publisher": "人民文学出版社",
"year": 1592,
"isbn": "9787020002208",
"genre": "古典文学",
"available_copies": 12
},
{
"title": "水浒传",
"author": "施耐庵",
"publisher": "人民文学出版社",
"year": 1589,
"isbn": "9787020002209",
"genre": "古典文学",
"available_copies": 7
},
{
"title": "围城",
"author": "钱钟书",
"publisher": "人民文学出版社",
"year": 1947,
"isbn": "9787020002210",
"genre": "现代文学",
"available_copies": 15
},
{
"title": "小王子",
"author": "安托万·德·圣-埃克苏佩里",
"publisher": "译林出版社",
"year": 1943,
"isbn": "9787544752311",
"genre": "童话",
"available_copies": 9
},
{
"title": "平凡的世界",
"author": "路遥",
"publisher": "人民文学出版社",
"year": 1986,
"isbn": "9787020024755",
"genre": "现代文学",
"available_copies": 5
},
{
"title": "活着",
"author": "余华",
"publisher": "作家出版社",
"year": 1993,
"isbn": "9787506365437",
"genre": "现代文学",
"available_copies": 8
},
{
"title": "百年孤独",
"author": "加西亚·马尔克斯",
"publisher": "南海出版公司",
"year": 1967,
"isbn": "9787544253993",
"genre": "魔幻现实主义",
"available_copies": 12
},
{
"title": "白夜行",
"author": "东野圭吾",
"publisher": "南海出版公司",
"year": 1999,
"isbn": "9787544258608",
"genre": "悬疑",
"available_copies": 7
}
],
currentDataIndex: 0,
drawerVisible: false,
}),
actions: {
getCurrentData(){
return this.infos[this.currentDataIndex];
}
}
})

View File

@ -0,0 +1,69 @@
import {defineStore} from "pinia";
export const useRecordStore = defineStore('Record', {
state: () => ({
infos: [
{
"title": "红楼梦",
"borrow_date": "2024-12-01",
"due_date": "2024-12-15",
"status": "已归还"
},
{
"title": "三国演义",
"borrow_date": "2024-12-05",
"due_date": "2024-12-19",
"status": "在借"
},
{
"title": "西游记",
"borrow_date": "2024-12-10",
"due_date": "2024-12-24",
"status": "逾期"
},
{
"title": "水浒传",
"borrow_date": "2024-12-15",
"due_date": "2024-12-29",
"status": "在借"
},
{
"title": "围城",
"borrow_date": "2024-11-20",
"due_date": "2024-12-04",
"status": "已归还"
},
{
"title": "小王子",
"borrow_date": "2024-12-18",
"due_date": "2025-01-01",
"status": "在借"
},
{
"title": "平凡的世界",
"borrow_date": "2024-12-02",
"due_date": "2024-12-16",
"status": "逾期"
},
{
"title": "活着",
"borrow_date": "2024-12-08",
"due_date": "2024-12-22",
"status": "在借"
},
{
"title": "百年孤独",
"borrow_date": "2024-11-30",
"due_date": "2024-12-14",
"status": "已归还"
},
{
"title": "白夜行",
"borrow_date": "2024-12-12",
"due_date": "2024-12-26",
"status": "在借"
}
]
}),
actions: {}
})

24
src/store/modules/user.ts Normal file
View File

@ -0,0 +1,24 @@
import {defineStore} from "pinia";
interface UserState {
user:string;
theme: string;
isLogin: boolean;
}
export const useUserStore = defineStore('User', {
state: (): UserState => ({
user: "AlexJR",
theme: 'light',
isLogin: false,
}),
actions: {
async login() {
this.isLogin = true;
},
async logout() {
this.isLogin = false;
}
}
});

79
src/style.css Normal file
View File

@ -0,0 +1,79 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
.card {
padding: 2em;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

1
src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

21
tsconfig.app.json Normal file
View File

@ -0,0 +1,21 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true,
"baseUrl": "./",
"paths": {
"@/*": [
"./src/*"
]
}
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}

7
tsconfig.json Normal file
View File

@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}

24
tsconfig.node.json Normal file
View File

@ -0,0 +1,24 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}

13
vite.config.ts Normal file
View File

@ -0,0 +1,13 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
// https://vite.dev/config/
export default defineConfig({
plugins: [vue(),vueJsx()],
resolve: {
alias: {
'@': '/src'
}
},
})