feat: 新增窗口头部组件
This commit is contained in:
7
env.d.ts
vendored
7
env.d.ts
vendored
@@ -1,7 +0,0 @@
|
|||||||
declare module "@store/*";
|
|
||||||
declare module "@modules/*";
|
|
||||||
declare module "@utils/*";
|
|
||||||
declare module "@assets/images/*";
|
|
||||||
declare module "@constant/*";
|
|
||||||
declare module "@remixicon/vue";
|
|
||||||
declare module "vue-router";
|
|
||||||
37
global.d.ts
vendored
37
global.d.ts
vendored
@@ -49,11 +49,12 @@ declare global {
|
|||||||
external: {
|
external: {
|
||||||
open: (url: string) => void
|
open: (url: string) => void
|
||||||
},
|
},
|
||||||
window: {
|
minimizeWindow: () => void,
|
||||||
minimize: () => void,
|
maximizeWindow: () => void,
|
||||||
maximize: () => void,
|
closeWindow: () => void,
|
||||||
close: () => void
|
onWindowMaximized: (callback: (isMaximized: boolean) => void) => void,
|
||||||
},
|
isWindowMaximized: () => Promise<boolean>,
|
||||||
|
viewIsReady: () => void
|
||||||
app: {
|
app: {
|
||||||
setFrameless: (route?: string) => void
|
setFrameless: (route?: string) => void
|
||||||
},
|
},
|
||||||
@@ -69,10 +70,34 @@ declare global {
|
|||||||
on: (event: 'tab-updated' | 'tab-created' | 'tab-closed' | 'tab-switched', handler: (payload: any) => void) => void
|
on: (event: 'tab-updated' | 'tab-created' | 'tab-closed' | 'tab-switched', handler: (payload: any) => void) => void
|
||||||
},
|
},
|
||||||
readFile: (filePath: string) => Promise<{success: boolean, data?: string, error?: string}>,
|
readFile: (filePath: string) => Promise<{success: boolean, data?: string, error?: string}>,
|
||||||
logToMain: (logLevel: string, message: string) => void,
|
logger: {
|
||||||
|
debug: (message: string, ...meta?: any[]) => void;
|
||||||
|
info: (message: string, ...meta?: any[]) => void;
|
||||||
|
warn: (message: string, ...meta?: any[]) => void;
|
||||||
|
error: (message: string, ...meta?: any[]) => void;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
declare interface Window {
|
declare interface Window {
|
||||||
api: WindowApi;
|
api: WindowApi;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module "@store/*";
|
||||||
|
declare module "@modules/*";
|
||||||
|
declare module "@utils/*";
|
||||||
|
declare module "@assets/images/*";
|
||||||
|
declare module "@constant/*";
|
||||||
|
declare module "@remixicon/vue";
|
||||||
|
declare module "vue-router";
|
||||||
|
declare module '@iconify/vue' {
|
||||||
|
import { DefineComponent } from 'vue'
|
||||||
|
export const Icon: DefineComponent<{
|
||||||
|
icon: string
|
||||||
|
width?: string | number
|
||||||
|
height?: string | number
|
||||||
|
color?: string
|
||||||
|
flip?: string
|
||||||
|
rotate?: number
|
||||||
|
}>
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta
|
<meta
|
||||||
http-equiv="Content-Security-Policy"
|
http-equiv="Content-Security-Policy"
|
||||||
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' http://8.138.234.141; connect-src 'self' http://8.138.234.141"
|
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' http://8.138.234.141; connect-src 'self' http://8.138.234.141 https://api.iconify.design"
|
||||||
/>
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
32
package-lock.json
generated
32
package-lock.json
generated
@@ -9,6 +9,8 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@iconify-json/material-symbols": "^1.2.50",
|
||||||
|
"@iconify/vue": "^5.0.0",
|
||||||
"@remixicon/vue": "^4.7.0",
|
"@remixicon/vue": "^4.7.0",
|
||||||
"@types/js-cookie": "^3.0.6",
|
"@types/js-cookie": "^3.0.6",
|
||||||
"@vueuse/core": "^14.1.0",
|
"@vueuse/core": "^14.1.0",
|
||||||
@@ -1533,6 +1535,36 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@iconify-json/material-symbols": {
|
||||||
|
"version": "1.2.50",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@iconify-json/material-symbols/-/material-symbols-1.2.50.tgz",
|
||||||
|
"integrity": "sha512-71tjHR70h46LHtBFab3fAd2V/wPTO7JMV5lKnRn3IcF303LaFgAlO0BZeTJDcmCv9d0snRZmnoLZAJVD7/eisw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@iconify/types": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@iconify/types": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@iconify/types/-/types-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@iconify/vue": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@iconify/vue/-/vue-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-C+KuEWIF5nSBrobFJhT//JS87OZ++QDORB6f2q2Wm6fl2mueSTpFBeBsveK0KW9hWiZ4mNiPjsh6Zs4jjdROSg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@iconify/types": "^2.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/cyberalien"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": ">=3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@inquirer/checkbox": {
|
"node_modules/@inquirer/checkbox": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-3.0.1.tgz",
|
||||||
|
|||||||
@@ -46,6 +46,8 @@
|
|||||||
"vite": "^7.1.9"
|
"vite": "^7.1.9"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@iconify-json/material-symbols": "^1.2.50",
|
||||||
|
"@iconify/vue": "^5.0.0",
|
||||||
"@remixicon/vue": "^4.7.0",
|
"@remixicon/vue": "^4.7.0",
|
||||||
"@types/js-cookie": "^3.0.6",
|
"@types/js-cookie": "^3.0.6",
|
||||||
"@vueuse/core": "^14.1.0",
|
"@vueuse/core": "^14.1.0",
|
||||||
|
|||||||
@@ -25,7 +25,14 @@ export enum IPC_EVENTS {
|
|||||||
FILE_WRITE = 'file:write',
|
FILE_WRITE = 'file:write',
|
||||||
GET_WINDOW_ID='get-window-id',
|
GET_WINDOW_ID='get-window-id',
|
||||||
CUSTOM_EVENT ='custom:event',
|
CUSTOM_EVENT ='custom:event',
|
||||||
TIME_UPDATE = 'time:update'
|
TIME_UPDATE = 'time:update',
|
||||||
|
RENDERER_IS_READY = 'renderer-ready',
|
||||||
|
|
||||||
|
// 发送日志
|
||||||
|
LOG_DEBUG = 'log-debug',
|
||||||
|
LOG_INFO = 'log-info',
|
||||||
|
LOG_WARN = 'log-warn',
|
||||||
|
LOG_ERROR = 'log-error',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MAIN_WIN_SIZE = {
|
export const MAIN_WIN_SIZE = {
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ class AppMain {
|
|||||||
resizable: true,
|
resizable: true,
|
||||||
maximizable: true,
|
maximizable: true,
|
||||||
minimizable: true,
|
minimizable: true,
|
||||||
|
titleBarStyle: 'hidden',
|
||||||
|
title: 'NIANXX',
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
devTools: this.isDev,
|
devTools: this.isDev,
|
||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
|
|||||||
@@ -8,11 +8,12 @@ const api: WindowApi = {
|
|||||||
open: (url: string) => ipcRenderer.invoke('external-open', url)
|
open: (url: string) => ipcRenderer.invoke('external-open', url)
|
||||||
},
|
},
|
||||||
|
|
||||||
window: {
|
closeWindow: () => ipcRenderer.send(IPC_EVENTS.WINDOW_CLOSE),
|
||||||
minimize: () => ipcRenderer.send(IPC_EVENTS.WINDOW_MINIMIZE),
|
minimizeWindow: () => ipcRenderer.send(IPC_EVENTS.WINDOW_MINIMIZE),
|
||||||
maximize: () => ipcRenderer.send(IPC_EVENTS.WINDOW_MAXIMIZE),
|
maximizeWindow: () => ipcRenderer.send(IPC_EVENTS.WINDOW_MAXIMIZE),
|
||||||
close: () => ipcRenderer.send(IPC_EVENTS.WINDOW_CLOSE)
|
onWindowMaximized: (callback: (isMaximized: boolean) => void) => ipcRenderer.on(IPC_EVENTS.WINDOW_MAXIMIZE + 'back', (_, isMaximized) => callback(isMaximized)),
|
||||||
},
|
isWindowMaximized: () => ipcRenderer.invoke(IPC_EVENTS.WINDOW_MAXIMIZE),
|
||||||
|
viewIsReady: () => ipcRenderer.send(IPC_EVENTS.RENDERER_IS_READY),
|
||||||
|
|
||||||
app: {
|
app: {
|
||||||
setFrameless: (route?: string) => ipcRenderer.invoke(IPC_EVENTS.APP_SET_FRAMELESS, route)
|
setFrameless: (route?: string) => ipcRenderer.invoke(IPC_EVENTS.APP_SET_FRAMELESS, route)
|
||||||
@@ -63,7 +64,12 @@ const api: WindowApi = {
|
|||||||
getCurrentWindowId: () => ipcRenderer.sendSync(IPC_EVENTS.GET_WINDOW_ID),
|
getCurrentWindowId: () => ipcRenderer.sendSync(IPC_EVENTS.GET_WINDOW_ID),
|
||||||
|
|
||||||
// 发送日志
|
// 发送日志
|
||||||
logToMain: (logLevel: string, message: string) => ipcRenderer.send(IPC_EVENTS.LOG_TO_MAIN, logLevel, message),
|
logger: {
|
||||||
|
debug: (message: string, ...meta: any[]) => ipcRenderer.send(IPC_EVENTS.LOG_DEBUG, message, ...meta),
|
||||||
|
info: (message: string, ...meta: any[]) => ipcRenderer.send(IPC_EVENTS.LOG_INFO, message, ...meta),
|
||||||
|
warn: (message: string, ...meta: any[]) => ipcRenderer.send(IPC_EVENTS.LOG_WARN, message, ...meta),
|
||||||
|
error: (message: string, ...meta: any[]) => ipcRenderer.send(IPC_EVENTS.LOG_ERROR, message, ...meta),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('api', api)
|
contextBridge.exposeInMainWorld('api', api)
|
||||||
76
src/renderer/components/HeaderBar/index.vue
Normal file
76
src/renderer/components/HeaderBar/index.vue
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<template>
|
||||||
|
<header class="title-bar flex items-start justify-between h-[40px]">
|
||||||
|
<div class="title-bar-main flex-auto">
|
||||||
|
<slot>{{ title ?? '' }}</slot>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="title-bar-controls w-[168px] flex items-center justify-end text-tx-secondary">
|
||||||
|
<native-tooltip content="最小化">
|
||||||
|
<button v-show="isMinimizable" class="flex items-center justify-center cursor-pointer w-[40px] h-[40px]"
|
||||||
|
@click="minimizeWindow">
|
||||||
|
<iconify-icon icon="material-symbols:check-indeterminate-small" color="#ffffff" :width="btnSize"
|
||||||
|
:height="btnSize" />
|
||||||
|
</button>
|
||||||
|
</native-tooltip>
|
||||||
|
<native-tooltip :content="isMaximized ? '还原' : '最大化'">
|
||||||
|
<button v-show="isMaximizable" class="flex items-center justify-center cursor-pointer w-[40px] h-[40px]"
|
||||||
|
@click="maximizeWindow">
|
||||||
|
<iconify-icon icon="material-symbols:chrome-maximize-outline-sharp" color="#ffffff" :width="btnSize"
|
||||||
|
:height="btnSize" v-show="!isMaximized" />
|
||||||
|
<iconify-icon icon="material-symbols:chrome-restore-outline-sharp" color="#ffffff" :width="btnSize"
|
||||||
|
:height="btnSize" v-show="isMaximized" />
|
||||||
|
</button>
|
||||||
|
</native-tooltip>
|
||||||
|
<native-tooltip content="关闭">
|
||||||
|
<button v-show="isClosable" class="flex items-center justify-center cursor-pointer w-[40px] h-[40px]"
|
||||||
|
@click="handleClose">
|
||||||
|
<iconify-icon icon="material-symbols:close" color="#ffffff" :width="btnSize" :height="btnSize" />
|
||||||
|
</button>
|
||||||
|
</native-tooltip>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Icon as IconifyIcon } from '@iconify/vue'
|
||||||
|
import { useWinManager } from '@hooks/useWinManager'
|
||||||
|
|
||||||
|
import NativeTooltip from '@components/NativeTooltip/index.vue'
|
||||||
|
|
||||||
|
interface HeaderBarProps {
|
||||||
|
title?: string;
|
||||||
|
isMaximizable?: boolean;
|
||||||
|
isMinimizable?: boolean;
|
||||||
|
isClosable?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineOptions({ name: 'HeaderBar' })
|
||||||
|
|
||||||
|
withDefaults(defineProps<HeaderBarProps>(), {
|
||||||
|
isMaximizable: true,
|
||||||
|
isMinimizable: true,
|
||||||
|
isClosable: true,
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['close']);
|
||||||
|
// const { t } = useI18n();
|
||||||
|
|
||||||
|
const btnSize = 16;
|
||||||
|
|
||||||
|
const {
|
||||||
|
isMaximized,
|
||||||
|
closeWindow,
|
||||||
|
minimizeWindow,
|
||||||
|
maximizeWindow
|
||||||
|
} = useWinManager();
|
||||||
|
|
||||||
|
function handleClose() {
|
||||||
|
emit('close');
|
||||||
|
closeWindow();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.title-bar {
|
||||||
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
43
src/renderer/components/NativeTooltip/index.vue
Normal file
43
src/renderer/components/NativeTooltip/index.vue
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<template>
|
||||||
|
<template v-if="slots.default()[0].el">
|
||||||
|
<slot></slot>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<span :title="content">
|
||||||
|
<slot></slot>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { logger } from '@utils/logger'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineOptions({ name: 'NativeTooltip' });
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
const slots = defineSlots()
|
||||||
|
|
||||||
|
if (slots?.default?.().length > 1) {
|
||||||
|
logger.warn('NativeTooltip only support one slot.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateTooltipContent = (content: string) => {
|
||||||
|
const defaultSlot = slots?.default?.();
|
||||||
|
|
||||||
|
if (defaultSlot) {
|
||||||
|
const slotElement = defaultSlot[0]?.el
|
||||||
|
|
||||||
|
if (slotElement && slotElement instanceof HTMLElement) {
|
||||||
|
slotElement.title = content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => updateTooltipContent(props.content))
|
||||||
|
|
||||||
|
watch(() => props.content, (val: string) => updateTooltipContent(val));
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
export function useWinManager() {
|
||||||
|
const isMaximized = ref(false)
|
||||||
|
|
||||||
|
function closeWindow() {
|
||||||
|
window.api.closeWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
function minimizeWindow() {
|
||||||
|
window.api.minimizeWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
function maximizeWindow() {
|
||||||
|
window.api.maximizeWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await nextTick();
|
||||||
|
window.api.viewIsReady();
|
||||||
|
isMaximized.value = await window.api.isWindowMaximized();
|
||||||
|
window.api.onWindowMaximized((_isMaximized: boolean) => isMaximized.value = _isMaximized);
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
isMaximized,
|
||||||
|
closeWindow,
|
||||||
|
minimizeWindow,
|
||||||
|
maximizeWindow
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useWinManager;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createApp } from "vue";
|
import { createApp, type Plugin } from "vue";
|
||||||
import { createPinia } from "pinia";
|
import { createPinia } from "pinia";
|
||||||
import router from "./router";
|
import router from "./router";
|
||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
@@ -10,6 +10,15 @@ import locale from 'element-plus/es/locale/lang/zh-cn'
|
|||||||
import "./styles/index.css";
|
import "./styles/index.css";
|
||||||
import 'element-plus/dist/index.css'
|
import 'element-plus/dist/index.css'
|
||||||
|
|
||||||
|
// 引入全局组件
|
||||||
|
import HeaderBar from './components/HeaderBar/index.vue'
|
||||||
|
import DragRegion from './components/DragRegion/index.vue'
|
||||||
|
|
||||||
|
const components: Plugin = (app) => {
|
||||||
|
app.component('HeaderBar', HeaderBar);
|
||||||
|
app.component('DragRegion', DragRegion);
|
||||||
|
}
|
||||||
|
|
||||||
// 创建 Vue 应用实例
|
// 创建 Vue 应用实例
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
@@ -20,6 +29,7 @@ app.use(createPinia());
|
|||||||
// 使用 Vue Router
|
// 使用 Vue Router
|
||||||
app.use(router);
|
app.use(router);
|
||||||
app.use(ElementPlus, { locale })
|
app.use(ElementPlus, { locale })
|
||||||
|
app.use(components)
|
||||||
|
|
||||||
// 挂载应用到 DOM
|
// 挂载应用到 DOM
|
||||||
app.mount("#app");
|
app.mount("#app");
|
||||||
|
|||||||
12
src/renderer/utils/logger.ts
Normal file
12
src/renderer/utils/logger.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
export const logger = window.api.logger ?? console;
|
||||||
|
|
||||||
|
if (window.api.logger) {
|
||||||
|
console.debug = logger.debug;
|
||||||
|
console.log = logger.info;
|
||||||
|
console.info = logger.info;
|
||||||
|
console.warn = logger.warn;
|
||||||
|
console.error = logger.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default logger;
|
||||||
@@ -1,86 +1,92 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="h-screen box-border p-[8px] login-bg flex items-center justify-center">
|
<div class="h-screen login-bg flex flex-col">
|
||||||
<div class="w-[836px] h-full bg-white rounded-2xl p-[32px] flex flex-col">
|
<header-bar>
|
||||||
<div class="flex items-center">
|
<drag-region class="w-full" />
|
||||||
<img class="w-[48px] h-[48px]" src="@assets/images/login/blue_logo.png" />
|
</header-bar>
|
||||||
|
|
||||||
<span class="ml-auto text-[14px] text-gray-600">没有账号?</span>
|
<main class="box-border p-[8px] flex flex-auto items-center justify-center">
|
||||||
<button
|
|
||||||
class="bg-sky-50 rounded-[8px] text-[14px] text-sky-600 px-[12px] py-[6px] focus-visible:outline-none">注册</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-col items-center justify-center mb-[24px] box-border pt-[108px]">
|
<div class="w-[836px] h-full bg-white rounded-2xl p-[32px] flex flex-col">
|
||||||
<img class="w-[80px] h-[80px] mb-[12px]" src="@assets/images/login/user_icon.png" />
|
<div class="flex items-center">
|
||||||
<div class="text-[24px] font-500 text-gray-800 line-height-[32px] mb-[4px]">登录</div>
|
<img class="w-[48px] h-[48px]" src="@assets/images/login/blue_logo.png" />
|
||||||
<div class="text-[16px] text-gray-500 line-height-[24px]">24小时在岗,从不打烊的数字员工</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-form class="w-[392px] ml-auto mr-auto" ref="formRef" :rules="rules" :model="form" label-position="top"
|
<span class="ml-auto text-[14px] text-gray-600">没有账号?</span>
|
||||||
@keyup.enter="onSubmit">
|
<button
|
||||||
<el-form-item prop="username">
|
class="bg-sky-50 rounded-[8px] text-[14px] text-sky-600 px-[12px] py-[6px] focus-visible:outline-none">注册</button>
|
||||||
<div class="text-[14px] text-gray-600">账号</div>
|
</div>
|
||||||
<el-input v-model.trim="form.username" placeholder="请输入账号" clearable autocomplete="off">
|
|
||||||
<template #prefix>
|
|
||||||
<RiUser3Fill size="20px" color="#99A0AE" />
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item prop="password">
|
|
||||||
<div class="text-[14px] text-gray-600">密码</div>
|
|
||||||
<el-input v-model.trim="form.password" placeholder="请输入密码" clearable autocomplete="off">
|
|
||||||
<template #prefix>
|
|
||||||
<RiKey2Fill size="20px" color="#99A0AE" />
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item prop="code">
|
<div class="flex flex-col items-center justify-center mb-[24px] box-border pt-[108px]">
|
||||||
<span class="text-[14px] text-gray-600">验证码</span>
|
<img class="w-[80px] h-[80px] mb-[12px]" src="@assets/images/login/user_icon.png" />
|
||||||
<el-input v-model.trim="form.code" placeholder="请输入验证码" clearable autocomplete="off">
|
<div class="text-[24px] font-500 text-gray-800 line-height-[32px] mb-[4px]">登录</div>
|
||||||
<template #suffix>
|
<div class="text-[16px] text-gray-500 line-height-[24px]">24小时在岗,从不打烊的数字员工</div>
|
||||||
<img class="w-[80px] h-[38px] cursor-pointer" :src="imgSrc" @click="getVerifyCode" />
|
</div>
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<!-- 记住密码|忘记密码 -->
|
<el-form class="w-[392px] ml-auto mr-auto" ref="formRef" :rules="rules" :model="form" label-position="top"
|
||||||
<div class="flex items-center justify-between mb-[24px] mt-[24px]">
|
@keyup.enter="onSubmit">
|
||||||
<div class="flex items-center gap-2 cursor-pointer">
|
<el-form-item prop="username">
|
||||||
<input type="checkbox" v-model="showPwd" class="w-[14px] h-[14px] rounded-[4px]" />
|
<div class="text-[14px] text-gray-600">账号</div>
|
||||||
<span class="text-[14px] text-gray-600">记住密码</span>
|
<el-input v-model.trim="form.username" placeholder="请输入账号" clearable autocomplete="off">
|
||||||
|
<template #prefix>
|
||||||
|
<RiUser3Fill size="20px" color="#99A0AE" />
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="password">
|
||||||
|
<div class="text-[14px] text-gray-600">密码</div>
|
||||||
|
<el-input v-model.trim="form.password" placeholder="请输入密码" clearable autocomplete="off">
|
||||||
|
<template #prefix>
|
||||||
|
<RiKey2Fill size="20px" color="#99A0AE" />
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item prop="code">
|
||||||
|
<span class="text-[14px] text-gray-600">验证码</span>
|
||||||
|
<el-input v-model.trim="form.code" placeholder="请输入验证码" clearable autocomplete="off">
|
||||||
|
<template #suffix>
|
||||||
|
<img class="w-[80px] h-[38px] cursor-pointer" :src="imgSrc" @click="getVerifyCode" />
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<!-- 记住密码|忘记密码 -->
|
||||||
|
<div class="flex items-center justify-between mb-[24px] mt-[24px]">
|
||||||
|
<div class="flex items-center gap-2 cursor-pointer">
|
||||||
|
<input type="checkbox" v-model="showPwd" class="w-[14px] h-[14px] rounded-[4px]" />
|
||||||
|
<span class="text-[14px] text-gray-600">记住密码</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-[14px] text-sky-600">忘记密码?</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-[14px] text-sky-600">忘记密码?</span>
|
|
||||||
|
<!-- 登录按钮 -->
|
||||||
|
<button class="w-full py-2 bg-blue-600 text-white rounded-[8px] hover:bg-blue-700 disabled:bg-blue-300"
|
||||||
|
@click="onSubmit">
|
||||||
|
{{ loading ? '登录中…' : '登录' }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- 同意协议 -->
|
||||||
|
<div class="flex items-center justify-center gap-2 mt-[24px]">
|
||||||
|
<input type="checkbox" v-model="isAgree" class="w-[14px] h-[14px] rounded-[4px]" />
|
||||||
|
<span class="text-[14px] text-gray-600">我已同意</span>
|
||||||
|
<span class="text-[14px] text-sky-600">《使用协议》</span>
|
||||||
|
<span class="text-[14px] text-gray-600">和</span>
|
||||||
|
<span class="text-[14px] text-sky-600">《隐私协议》</span>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<!-- Copy Right -->
|
||||||
|
<div class="text-[14px] text-gray-500 text-center mt-auto">
|
||||||
|
© 2025 贵州智念科技服务有限公司 版权所有
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 登录按钮 -->
|
|
||||||
<button class="w-full py-2 bg-blue-600 text-white rounded-[8px] hover:bg-blue-700 disabled:bg-blue-300"
|
|
||||||
@click="onSubmit">
|
|
||||||
{{ loading ? '登录中…' : '登录' }}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- 同意协议 -->
|
|
||||||
<div class="flex items-center justify-center gap-2 mt-[24px]">
|
|
||||||
<input type="checkbox" v-model="isAgree" class="w-[14px] h-[14px] rounded-[4px]" />
|
|
||||||
<span class="text-[14px] text-gray-600">我已同意</span>
|
|
||||||
<span class="text-[14px] text-sky-600">《使用协议》</span>
|
|
||||||
<span class="text-[14px] text-gray-600">和</span>
|
|
||||||
<span class="text-[14px] text-sky-600">《隐私协议》</span>
|
|
||||||
</div>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<!-- Copy Right -->
|
|
||||||
<div class="text-[14px] text-gray-500 text-center mt-auto">
|
|
||||||
© 2025 贵州智念科技服务有限公司 版权所有
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<img class="w-[570px]" src="@assets/images/login/logo.png" />
|
<img class="w-[570px]" src="@assets/images/login/logo.png" />
|
||||||
|
</main>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive } from "vue";
|
|
||||||
import { useRouter } from "vue-router";
|
|
||||||
import { authOauth2TokenUsingPost } from "@renderer/api";
|
import { authOauth2TokenUsingPost } from "@renderer/api";
|
||||||
import { RiUser3Fill, RiKey2Fill } from '@remixicon/vue'
|
import { RiUser3Fill, RiKey2Fill } from '@remixicon/vue'
|
||||||
import { generateUUID } from "@utils/generateUUID";
|
import { generateUUID } from "@utils/generateUUID";
|
||||||
|
|||||||
@@ -34,9 +34,11 @@
|
|||||||
"@utils/*": ["src/renderer/utils/*"],
|
"@utils/*": ["src/renderer/utils/*"],
|
||||||
"@common/*": ["src/common/*"],
|
"@common/*": ["src/common/*"],
|
||||||
"@modules/*": ["src/main/modules/*"],
|
"@modules/*": ["src/main/modules/*"],
|
||||||
"@locales/*": ["locales/*"]
|
"@locales/*": ["locales/*"],
|
||||||
|
"@hooks/*": ["src/renderer/hooks/*"],
|
||||||
|
"@components/*": ["src/renderer/components/*"],
|
||||||
},
|
},
|
||||||
"types": ["element-plus/global", "vue"]
|
"types": []
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"forge.env.d.ts",
|
"forge.env.d.ts",
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ export default defineConfig(async () => {
|
|||||||
"@assets": resolve(__dirname, "./src/assets"),
|
"@assets": resolve(__dirname, "./src/assets"),
|
||||||
'@common': resolve(__dirname, './src/common'),
|
'@common': resolve(__dirname, './src/common'),
|
||||||
"@constant": resolve(__dirname, "./src/renderer/constant"),
|
"@constant": resolve(__dirname, "./src/renderer/constant"),
|
||||||
|
"@components": resolve(__dirname, "./src/renderer/components"),
|
||||||
|
"@hooks": resolve(__dirname, "./src/renderer/hooks"),
|
||||||
"@store": resolve(__dirname, "./src/renderer/store"),
|
"@store": resolve(__dirname, "./src/renderer/store"),
|
||||||
"@utils": resolve(__dirname, "./src/renderer/utils"),
|
"@utils": resolve(__dirname, "./src/renderer/utils"),
|
||||||
"@shared": resolve(__dirname, "./src/renderer/shared"),
|
"@shared": resolve(__dirname, "./src/renderer/shared"),
|
||||||
|
|||||||
Reference in New Issue
Block a user