Frontend created and rewritten a few times, with some backend fixes #1
2
cista-front/components.d.ts
vendored
2
cista-front/components.d.ts
vendored
|
@ -16,6 +16,7 @@ declare module 'vue' {
|
||||||
AImage: typeof import('ant-design-vue/es')['Image']
|
AImage: typeof import('ant-design-vue/es')['Image']
|
||||||
AInput: typeof import('ant-design-vue/es')['Input']
|
AInput: typeof import('ant-design-vue/es')['Input']
|
||||||
AInputSearch: typeof import('ant-design-vue/es')['InputSearch']
|
AInputSearch: typeof import('ant-design-vue/es')['InputSearch']
|
||||||
|
AModal: typeof import('ant-design-vue/es')['Modal']
|
||||||
APageHeader: typeof import('ant-design-vue/es')['PageHeader']
|
APageHeader: typeof import('ant-design-vue/es')['PageHeader']
|
||||||
APopover: typeof import('ant-design-vue/es')['Popover']
|
APopover: typeof import('ant-design-vue/es')['Popover']
|
||||||
AppNavigation: typeof import('./src/components/AppNavigation.vue')['default']
|
AppNavigation: typeof import('./src/components/AppNavigation.vue')['default']
|
||||||
|
@ -27,6 +28,7 @@ declare module 'vue' {
|
||||||
FileExplorer: typeof import('./src/components/FileExplorer.vue')['default']
|
FileExplorer: typeof import('./src/components/FileExplorer.vue')['default']
|
||||||
FileViewer: typeof import('./src/components/FileViewer.vue')['default']
|
FileViewer: typeof import('./src/components/FileViewer.vue')['default']
|
||||||
HeaderMain: typeof import('./src/components/HeaderMain.vue')['default']
|
HeaderMain: typeof import('./src/components/HeaderMain.vue')['default']
|
||||||
|
LoginModal: typeof import('./src/components/LoginModal.vue')['default']
|
||||||
NotificationLoading: typeof import('./src/components/NotificationLoading.vue')['default']
|
NotificationLoading: typeof import('./src/components/NotificationLoading.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
--table-background: #535353;
|
--table-background: #535353;
|
||||||
--primary-color: #ffffff;
|
--primary-color: #ffffff;
|
||||||
--secondary-color: #ccc;
|
--secondary-color: #ccc;
|
||||||
--blue-color: #66ffeb
|
--blue-color: #66ffeb;
|
||||||
|
--red-color: #ff4d4f;
|
||||||
}
|
}
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
:root {
|
:root {
|
||||||
|
@ -15,7 +16,8 @@
|
||||||
--table-background: #535353;
|
--table-background: #535353;
|
||||||
--primary-color: #ffffff;
|
--primary-color: #ffffff;
|
||||||
--secondary-color: #ccc;
|
--secondary-color: #ccc;
|
||||||
--blue-color: #66ffeb
|
--blue-color: #66ffeb;
|
||||||
|
--red-color: #ff4d4f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
|
@ -93,5 +95,11 @@ body {
|
||||||
.ant-empty-description{
|
.ant-empty-description{
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
.ant-modal .ant-modal-content{
|
||||||
|
background-color: var(--secondary-background);
|
||||||
|
}
|
||||||
|
.ant-modal .ant-modal-close-x{
|
||||||
|
color: var(--font-color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<main>
|
||||||
|
<context-holder />
|
||||||
<!-- <h2 v-if="!documentStore.loading && documentStore.error"> {{ documentStore.error }} </h2> -->
|
<!-- <h2 v-if="!documentStore.loading && documentStore.error"> {{ documentStore.error }} </h2> -->
|
||||||
<div class="carousel-container" v-if="!documentStore.loading && documentStore.mainDocument[0] && documentStore.mainDocument[0].type === 'file'">
|
<div class="carousel-container" v-if="!documentStore.loading && documentStore.mainDocument[0] && documentStore.mainDocument[0].type === 'file'">
|
||||||
<FileCarousel></FileCarousel>
|
<FileCarousel></FileCarousel>
|
||||||
|
@ -32,13 +33,27 @@
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="more-action">
|
<div class="more-action">
|
||||||
<div class="action-container">
|
<div class="action-container">
|
||||||
<a-button type="text" class="action-button" :icon="h(ImportOutlined)"/>Open
|
<a :href="`${record.type === 'folder'? linkBasePath+'#' : filesBasePath}/${record.name}`">
|
||||||
|
<a-button type="text" class="action-button" :icon="h(ImportOutlined)"/>
|
||||||
|
</a>
|
||||||
|
Open
|
||||||
|
</div>
|
||||||
|
<div v-if="record.type === 'folder-file'" class="action-container">
|
||||||
|
<a :href="`${filesBasePath}/${record.name}`" download>
|
||||||
|
<a-button type="text" class="action-button" :icon="h(DownloadOutlined)"/>
|
||||||
|
</a>
|
||||||
|
Download
|
||||||
</div>
|
</div>
|
||||||
<div class="action-container">
|
<div class="action-container">
|
||||||
<a-button type="text" class="action-button" :icon="h(EditOutlined)"/> Rename
|
<a-button type="text" class="action-button" @click="edit(record.key)" :icon="h(EditOutlined)"/> Rename
|
||||||
</div>
|
</div>
|
||||||
<div class="action-container">
|
<div class="action-container">
|
||||||
<a-button type="text" class="action-button" :icon="h(LinkOutlined)"/> Share
|
<a-button
|
||||||
|
type="text"
|
||||||
|
class="action-button"
|
||||||
|
@click="share(`${record.type === 'folder'? linkBasePath+'/#' : filesBasePath}/${record.name}`)"
|
||||||
|
:icon="h(LinkOutlined)"
|
||||||
|
/> Share
|
||||||
</div>
|
</div>
|
||||||
<div class="action-container">
|
<div class="action-container">
|
||||||
<a-button type="text" class="action-button" :icon="h(CopyOutlined)"/> Copy
|
<a-button type="text" class="action-button" :icon="h(CopyOutlined)"/> Copy
|
||||||
|
@ -69,14 +84,14 @@
|
||||||
import { EditOutlined, ImportOutlined, CheckOutlined,CopyOutlined, ScissorOutlined, LinkOutlined, DownloadOutlined, DeleteOutlined, EllipsisOutlined } from '@ant-design/icons-vue'
|
import { EditOutlined, ImportOutlined, CheckOutlined,CopyOutlined, ScissorOutlined, LinkOutlined, DownloadOutlined, DeleteOutlined, EllipsisOutlined } from '@ant-design/icons-vue'
|
||||||
import type { TableColumnsType } from 'ant-design-vue';
|
import type { TableColumnsType } from 'ant-design-vue';
|
||||||
import Router from '@/router/index';
|
import Router from '@/router/index';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
import type { Document, FolderDocument } from '@/repositories/Document';
|
import type { Document, FolderDocument } from '@/repositories/Document';
|
||||||
import FileCarousel from './FileCarousel.vue';
|
import FileCarousel from './FileCarousel.vue';
|
||||||
|
|
||||||
|
const [messageApi, contextHolder] = message.useMessage();
|
||||||
|
|
||||||
type Key = string | number;
|
type Key = string | number;
|
||||||
const documentStore = useDocumentStore()
|
const documentStore = useDocumentStore()
|
||||||
watchEffect(()=>{
|
|
||||||
console.log(documentStore.mainDocument)
|
|
||||||
})
|
|
||||||
const editableData: UnwrapRef<Record<string, Document>> = reactive({});
|
const editableData: UnwrapRef<Record<string, Document>> = reactive({});
|
||||||
const state = reactive<{
|
const state = reactive<{
|
||||||
selectedRowKeys: Key[];
|
selectedRowKeys: Key[];
|
||||||
|
@ -143,7 +158,10 @@
|
||||||
Object.assign(documentStore.mainDocument.filter(item => key === item.key)[0], editableData[key]);
|
Object.assign(documentStore.mainDocument.filter(item => key === item.key)[0], editableData[key]);
|
||||||
delete editableData[key];
|
delete editableData[key];
|
||||||
};
|
};
|
||||||
|
const share = async (url : string) => {
|
||||||
|
await navigator.clipboard.writeText(location.origin + url)
|
||||||
|
messageApi.success("Link successfully copied to the clipboard");
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -30,11 +30,6 @@ import { url_document_get } from '@/repositories/Document';
|
||||||
|
|
||||||
const dataURL = ref('')
|
const dataURL = ref('')
|
||||||
watchEffect(()=>{
|
watchEffect(()=>{
|
||||||
console.log('😎😎😎😎')
|
|
||||||
console.log(url_document_get)
|
|
||||||
console.log(Router.currentRoute)
|
|
||||||
console.log(Router.currentRoute.value.path)
|
|
||||||
console.log('----------')
|
|
||||||
dataURL.value = new URL(
|
dataURL.value = new URL(
|
||||||
url_document_get + Router.currentRoute.value.path,
|
url_document_get + Router.currentRoute.value.path,
|
||||||
location.origin
|
location.origin
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useDocumentStore } from '@/stores/documents'
|
import { useDocumentStore } from '@/stores/documents'
|
||||||
|
import LoginModal from '@/components/LoginModal.vue'
|
||||||
import UploadButton from '@/components/UploadButton.vue'
|
import UploadButton from '@/components/UploadButton.vue'
|
||||||
import { InfoCircleOutlined, SettingOutlined, PlusSquareOutlined, SearchOutlined, DeleteOutlined, DownloadOutlined, FileAddFilled, LinkOutlined, FolderAddFilled, FileFilled, FolderFilled } from '@ant-design/icons-vue'
|
import { InfoCircleOutlined, SettingOutlined, PlusSquareOutlined, SearchOutlined, DeleteOutlined, DownloadOutlined, FileAddFilled, LinkOutlined, FolderAddFilled, FileFilled, FolderFilled } from '@ant-design/icons-vue'
|
||||||
import { h, ref, defineProps } from 'vue';
|
import { h, ref } from 'vue';
|
||||||
|
|
||||||
const documentStore = useDocumentStore()
|
const documentStore = useDocumentStore()
|
||||||
const searchQuery = ref<string>('')
|
const searchQuery = ref<string>('')
|
||||||
|
@ -94,8 +95,8 @@ function download(){
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="actions-list">
|
<div class="actions-list">
|
||||||
|
<LoginModal></LoginModal>
|
||||||
<template v-if="showSearchInput">
|
<template v-if="showSearchInput">
|
||||||
<a-input-search
|
<a-input-search
|
||||||
v-model="searchQuery"
|
v-model="searchQuery"
|
||||||
|
|
90
cista-front/src/components/LoginModal.vue
Normal file
90
cista-front/src/components/LoginModal.vue
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
<template>
|
||||||
|
<a-tooltip title="Login">
|
||||||
|
<template v-if="DocumentStore.isUserLogged">
|
||||||
|
<a-button @click="logout" type="text" class="action-button" :icon="h(UserDeleteOutlined)" />
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-button @click="showModal" type="text" class="action-button" :icon="h(UserOutlined)" />
|
||||||
|
</template>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-modal v-model:open="DocumentStore.user.isOpenLoginModal" :confirm-loading="confirmLoading" okText="Login" @ok="login">
|
||||||
|
<div class="login-container">
|
||||||
|
<a-form :model="loginForm">
|
||||||
|
<a-form-item label="Username" prop="username" :rules="[{ required: true, message: 'Please input your username!' }]">
|
||||||
|
<a-input v-model:value="loginForm.username" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Password" prop="password" :rules="[{ required: true, message: 'Please input your password!' }]">
|
||||||
|
<a-input type="password" v-model:value="loginForm.password" />
|
||||||
|
</a-form-item>
|
||||||
|
<h3 v-if="loginForm.error.length > 0" class="error-text">{{loginForm.error}}</h3>
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, h } from 'vue';
|
||||||
|
import { UserOutlined, UserDeleteOutlined } from '@ant-design/icons-vue';
|
||||||
|
import { useDocumentStore } from '@/stores/documents';
|
||||||
|
import { loginUser, logoutUser } from '@/repositories/User';
|
||||||
|
import type { ISimpleError } from '@/repositories/Client';
|
||||||
|
|
||||||
|
const DocumentStore = useDocumentStore();
|
||||||
|
const confirmLoading = ref<boolean>(false);
|
||||||
|
|
||||||
|
const showModal = () => {
|
||||||
|
DocumentStore.user.isOpenLoginModal = true;
|
||||||
|
};
|
||||||
|
const logout = async () => {
|
||||||
|
try {
|
||||||
|
await logoutUser();
|
||||||
|
} catch (error) {} finally {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const loginForm = ref({
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
error: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const login = async () => {
|
||||||
|
try {
|
||||||
|
loginForm.value.error = '';
|
||||||
|
confirmLoading.value = true;
|
||||||
|
const user = await loginUser(loginForm.value.username, loginForm.value.password);
|
||||||
|
if(user){
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const httpError = error as ISimpleError
|
||||||
|
if(httpError.name){
|
||||||
|
loginForm.value.error = httpError.message
|
||||||
|
}
|
||||||
|
}finally{
|
||||||
|
confirmLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.login-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 30vh;
|
||||||
|
}
|
||||||
|
.button-login {
|
||||||
|
background-color: var(--secondary-color);
|
||||||
|
color: var(--secondary-background);
|
||||||
|
}
|
||||||
|
.ant-btn-primary:not(:disabled):hover{
|
||||||
|
background-color: var(--blue-color);
|
||||||
|
|
||||||
|
}
|
||||||
|
.error-text {
|
||||||
|
color :var(--red-color)
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import axios from 'axios'
|
import axios, {AxiosError} from 'axios'
|
||||||
|
|
||||||
/* Base domain for all request */
|
/* Base domain for all request */
|
||||||
export const baseURL = import.meta.env.VITE_URL_DOCUMENT
|
export const baseURL = import.meta.env.VITE_URL_DOCUMENT
|
||||||
|
@ -17,9 +17,10 @@ Client.interceptors.response.use(
|
||||||
// Do something with response data
|
// Do something with response data
|
||||||
return response
|
return response
|
||||||
},
|
},
|
||||||
(error) => {
|
(error: AxiosError<any>) => {
|
||||||
const msg = error.response?.data.message ?? 'Unexpected error'
|
const msg = error.response && error.response.data && error.response.data.error ?
|
||||||
const code = error.code ? Number(error.code) : 500
|
error.response.data.error.message : 'Unexpected error'
|
||||||
|
const code = error.code ? Number(error.response?.status) : 500
|
||||||
const standardizedError = new SimpleError(code, msg)
|
const standardizedError = new SimpleError(code, msg)
|
||||||
|
|
||||||
return Promise.reject(standardizedError)
|
return Promise.reject(standardizedError)
|
||||||
|
|
|
@ -23,6 +23,14 @@ export type FileDocument = BaseDocument & {
|
||||||
data: string;
|
data: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type errorEvent = {
|
||||||
|
error: {
|
||||||
|
code : number;
|
||||||
|
message: string;
|
||||||
|
redirect: string;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export type Document = FolderDocument | FileDocument;
|
export type Document = FolderDocument | FileDocument;
|
||||||
|
|
||||||
|
|
||||||
|
@ -44,19 +52,36 @@ export class DocumentHandler {
|
||||||
case !!msg.update:
|
case !!msg.update:
|
||||||
this.handleUpdateMessage(msg);
|
this.handleUpdateMessage(msg);
|
||||||
break;
|
break;
|
||||||
|
case !!msg.error:
|
||||||
|
this.handleError(msg);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleRootMessage({ root }: { root: FileStructure }) {
|
private handleRootMessage({ root }: { root: FileStructure }) {
|
||||||
if (this.store && this.store.root) this.store.root = root;
|
if (this.store && this.store.root) {
|
||||||
|
this.store.user.isLoggedIn = true;
|
||||||
|
this.store.root = root;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleUpdateMessage(updateData: { update: FileStructure[] }) {
|
private handleUpdateMessage(updateData: { update: FileStructure[] }) {
|
||||||
const root = updateData.update[0]
|
const root = updateData.update[0]
|
||||||
if(root) this.store.root = root
|
if(root) {
|
||||||
|
this.store.user.isLoggedIn = true;
|
||||||
|
this.store.root = root
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private handleError(msg: errorEvent){
|
||||||
|
if(msg.error.code === 401){
|
||||||
|
this.store.user.isOpenLoginModal = true;
|
||||||
|
this.store.user.isLoggedIn = false;
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DocumentUploadHandler {
|
export class DocumentUploadHandler {
|
||||||
constructor( private store: DocumentStore = useDocumentStore() ) {
|
constructor( private store: DocumentStore = useDocumentStore() ) {
|
||||||
this.handleWebSocketMessage = this.handleWebSocketMessage.bind(this);
|
this.handleWebSocketMessage = this.handleWebSocketMessage.bind(this);
|
||||||
|
|
23
cista-front/src/repositories/User.ts
Normal file
23
cista-front/src/repositories/User.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
|
||||||
|
import Client from '@/repositories/Client'
|
||||||
|
export const url_login = '/login'
|
||||||
|
export const url_logout = '/logout '
|
||||||
|
|
||||||
|
export async function loginUser(username : string, password: string){
|
||||||
|
try {
|
||||||
|
const user = await Client.post(url_login, {
|
||||||
|
username, password
|
||||||
|
})
|
||||||
|
return user;
|
||||||
|
} catch (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export async function logoutUser(){
|
||||||
|
try {
|
||||||
|
const data = await Client.post(url_logout)
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||||
import ExplorerView from '../views/ExplorerView.vue'
|
import ExplorerView from '../views/ExplorerView.vue'
|
||||||
import LoginView from '../views/LoginView.vue'
|
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHashHistory(import.meta.env.BASE_URL),
|
history: createWebHashHistory(import.meta.env.BASE_URL),
|
||||||
|
|
|
@ -10,6 +10,10 @@ type DirectoryData = {
|
||||||
[filename: string]: FileData;
|
[filename: string]: FileData;
|
||||||
};
|
};
|
||||||
export type FileStructure = {id: string, mtime: number, size: number, dir: DirectoryData};
|
export type FileStructure = {id: string, mtime: number, size: number, dir: DirectoryData};
|
||||||
|
type User = {
|
||||||
|
isOpenLoginModal: boolean,
|
||||||
|
isLoggedIn : boolean,
|
||||||
|
}
|
||||||
|
|
||||||
export type DocumentStore = {
|
export type DocumentStore = {
|
||||||
root: FileStructure,
|
root: FileStructure,
|
||||||
|
@ -20,6 +24,7 @@ export type DocumentStore = {
|
||||||
wsWatch: WebSocket | undefined,
|
wsWatch: WebSocket | undefined,
|
||||||
wsUpload: WebSocket | undefined,
|
wsUpload: WebSocket | undefined,
|
||||||
selectedDocuments: Document[],
|
selectedDocuments: Document[],
|
||||||
|
user: User,
|
||||||
error: string,
|
error: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +40,7 @@ export const useDocumentStore = defineStore({
|
||||||
wsUpload: undefined,
|
wsUpload: undefined,
|
||||||
selectedDocuments: [] as Document[],
|
selectedDocuments: [] as Document[],
|
||||||
error: '' as string,
|
error: '' as string,
|
||||||
|
user: { isLoggedIn: false, isOpenLoginModal: false } as User
|
||||||
}),
|
}),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
@ -140,7 +146,7 @@ export const useDocumentStore = defineStore({
|
||||||
for (const d of this.document) {
|
for (const d of this.document) {
|
||||||
if ("mtime" in d) d.modified = formatUnixDate(d.mtime)
|
if ("mtime" in d) d.modified = formatUnixDate(d.mtime)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
mainDocument(): Document[] {
|
mainDocument(): Document[] {
|
||||||
|
@ -151,6 +157,9 @@ export const useDocumentStore = defineStore({
|
||||||
},
|
},
|
||||||
rootMain(): DirectoryData | undefined {
|
rootMain(): DirectoryData | undefined {
|
||||||
if(this.root) return this.root.dir
|
if(this.root) return this.root.dir
|
||||||
|
},
|
||||||
|
isUserLogged(): boolean{
|
||||||
|
return this.user.isLoggedIn
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="login-container">
|
|
||||||
<a-form :model="loginForm">
|
|
||||||
<a-form-item label="Username" prop="username" :rules="[{ required: true, message: 'Please input your username!' }]">
|
|
||||||
<a-input v-model:value="loginForm.username" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="Password" prop="password" :rules="[{ required: true, message: 'Please input your password!' }]">
|
|
||||||
<a-input type="password" v-model:value="loginForm.password" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item>
|
|
||||||
<a-button type="primary" @click="login" class="button-login">Login</a-button>
|
|
||||||
</a-form-item>
|
|
||||||
</a-form>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { ref } from 'vue';
|
|
||||||
|
|
||||||
const loginForm = ref({
|
|
||||||
username: '',
|
|
||||||
password: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
const login = () => {
|
|
||||||
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.login-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
.button-login {
|
|
||||||
background-color: var(--secondary-color);
|
|
||||||
color: var(--secondary-background);
|
|
||||||
}
|
|
||||||
.ant-btn-primary:not(:disabled):hover{
|
|
||||||
background-color: var(--blue-color);
|
|
||||||
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
1
cista/wwwroot/assets/index-213aec14.css
Normal file
1
cista/wwwroot/assets/index-213aec14.css
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -5,8 +5,8 @@
|
||||||
<link rel="icon" href="/favicon.ico">
|
<link rel="icon" href="/favicon.ico">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
<title>Vite Vasanko</title>
|
<title>Vite Vasanko</title>
|
||||||
<script type="module" crossorigin src="/assets/index-10851222.js"></script>
|
<script type="module" crossorigin src="/assets/index-25722397.js"></script>
|
||||||
<link rel="stylesheet" href="/assets/index-ee545ab1.css">
|
<link rel="stylesheet" href="/assets/index-213aec14.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user