feat: 初始化项目
This commit is contained in:
11
.env.development
Normal file
11
.env.development
Normal file
@@ -0,0 +1,11 @@
|
||||
# 环境
|
||||
VITE_ENV = development
|
||||
|
||||
# 接口地址开关控制器
|
||||
VITE_DEV_URL = 0
|
||||
|
||||
# 项目h5域名
|
||||
VITE_BASE_URL_API = https://h5.gogpay.cn/h5-test/app/love-calculator-frontend
|
||||
|
||||
# 应用ID
|
||||
VITE_APP_ID = ZN-AI
|
||||
11
.env.production
Normal file
11
.env.production
Normal file
@@ -0,0 +1,11 @@
|
||||
# 环境
|
||||
VITE_ENV = production
|
||||
|
||||
# 接口地址开关控制器
|
||||
VITE_DEV_URL = 0
|
||||
|
||||
# 项目h5域名
|
||||
VITE_BASE_URL_API = https://h5.gogpay.cn/h5-test/app/love-calculator-frontend
|
||||
|
||||
# 应用ID
|
||||
VITE_APP_ID = ZN-AI
|
||||
11
.env.staging
Normal file
11
.env.staging
Normal file
@@ -0,0 +1,11 @@
|
||||
# 环境
|
||||
VITE_ENV = staging
|
||||
|
||||
# 接口地址开关控制器
|
||||
VITE_DEV_URL = 0
|
||||
|
||||
# 项目h5域名
|
||||
VITE_BASE_URL_API = https://h5.gogpay.cn/h5-test/app/love-calculator-frontend
|
||||
|
||||
# 应用ID
|
||||
VITE_APP_ID = ZN-AI
|
||||
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal 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
3
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
||||
112
LOGIN-README.md
Normal file
112
LOGIN-README.md
Normal file
@@ -0,0 +1,112 @@
|
||||
**产品需求文档:综合登录系统**
|
||||
|
||||
**1. 文档概述**
|
||||
|
||||
- **产品名称:** 统一认证与登录系统
|
||||
- **功能模块:** 登录页 (Login Page)
|
||||
- **文档目的:** 定义登录功能的详细需求,涵盖账号密码登录、验证码校验、二维码登录及相关的用户体验细节,确保开发实现准确无误。
|
||||
- **目标用户:** 所有需要访问系统权限的注册用户(包括新用户、老用户、遗忘密码用户)。
|
||||
- **核心目标:**
|
||||
1. **安全可靠:** 保障用户账号安全,防御机器攻击。
|
||||
2. **便捷高效:** 提供多种登录方式,减少用户操作成本。
|
||||
3. **清晰引导:** 界面文案清晰,对各类情况给予明确反馈和引导。
|
||||
|
||||
**2. 功能需求详述**
|
||||
|
||||
**2.1. 账号密码登录(主流程)**
|
||||
|
||||
- **输入字段:**
|
||||
|
||||
- **用户名/邮箱/手机号:**
|
||||
- 类型:单行输入框。
|
||||
- 提示文案(Placeholder): “请输入用户名/邮箱/手机号”。
|
||||
- 规则:支持三者之一即可,后端需做兼容判断。前端可做初步格式校验(如邮箱含'@',手机号为 11 位数字),但最终有效性由后端验证。
|
||||
- **密码:**
|
||||
- 类型:密码输入框(内容默认隐藏)。
|
||||
- 提示文案:“请输入密码”。
|
||||
- 功能:右侧需提供“眼睛”图标,点击可切换明文/密文显示。
|
||||
- **图形验证码:**
|
||||
- 显示逻辑:非首次输入时触发。为了提高体验,可在用户密码输入框失焦(blur)后、或连续输错 1 次密码后,再出现验证码输入框。
|
||||
- 组件:一个输入框 + 一个实时图形验证码图片。
|
||||
- 图片:需提供“刷新”图标,点击可无刷新切换新的验证码。图片应清晰可辨,但具备一定的抗机器识别能力。
|
||||
- 提示文案:“请输入验证码”。
|
||||
|
||||
- **辅助功能:**
|
||||
|
||||
- **记住我:**
|
||||
- 组件:复选框(Checkbox)。
|
||||
- 功能:勾选后,下次访问登录页时自动填充用户名(切勿记住密码)。
|
||||
- 默认状态:不勾选。
|
||||
- **忘记密码:**
|
||||
- 组件:文字链接。
|
||||
- 位置:位于密码框右侧或下方。
|
||||
- 功能:点击后跳转至密码找回流程页。
|
||||
|
||||
- **主要操作按钮:**
|
||||
- **“登录”按钮:**
|
||||
- 状态:
|
||||
- 默认态:可点击。
|
||||
- 加载态:用户点击后,按钮变为禁用状态并显示“登录中...”加载动画,防止重复提交。
|
||||
- 功能:触发登录逻辑。
|
||||
|
||||
**2.2. 二维码登录(便捷流程)**
|
||||
|
||||
- **切换入口:**
|
||||
|
||||
- 在登录框顶部或侧边提供 Tab 页或文字链接,如“账号密码登录” | “二维码登录”,用于切换两种登录方式。
|
||||
|
||||
- **二维码显示区域:**
|
||||
|
||||
- 初始状态:页面应动态生成一个唯一的二维码(QR Code),并配有文案提示:“请使用【APP 名称】扫描二维码登录”。
|
||||
- 自动刷新:该二维码应有时效性(例如 2 分钟)。倒计时在二维码旁可视化显示(如:01:45)。过期后自动刷新生成新的二维码。
|
||||
- 手动刷新:提供“刷新”按钮,用户可手动点击更换二维码。
|
||||
|
||||
- **登录状态轮询:**
|
||||
- 一旦二维码生成,前端即开始向后台轮询(Polling)其状态(如每 2 秒一次)。
|
||||
- 状态反馈:
|
||||
- 待扫描:显示初始状态。
|
||||
- 已扫描,待确认:二维码仍显示,下方文案变为“请在手机上确认登录”。
|
||||
- 登录成功:轮询停止,前端跳转至登录后页面(或首页)。
|
||||
- 过期/失败:二维码区域变灰,显示“二维码已失效”,并自动刷新生成新的二维码。
|
||||
|
||||
**2.3. 异常与交互流程**
|
||||
|
||||
- **输入校验(前端 + 后端):**
|
||||
|
||||
- 字段为空:点击登录后,对未填写的必填字段进行红色高亮提示,并显示文案“XXX 不能为空”。
|
||||
- 格式错误:在输入框失焦(blur)时即可进行初步校验(如邮箱格式错误),即时提示“邮箱格式不正确”。
|
||||
- 验证码错误:提示“验证码错误”,并自动刷新验证码图片。
|
||||
- 账号或密码错误:切勿明确提示是“账号错误”还是“密码错误”,统一提示:“用户名或密码错误”,以防黑客枚举用户名。同时,刷新验证码。
|
||||
|
||||
- **安全限制:**
|
||||
|
||||
- 连续输错密码 N 次(如 5 次)后,除图形验证码外,应触发更严格的验证(如滑块验证、短信验证码)或直接锁定账号一段时间(如 15 分钟),并明确提示用户:“密码错误次数过多,请 15 分钟后再试”。
|
||||
|
||||
- **加载与反馈:**
|
||||
- 任何网络请求都必须有加载状态(如按钮 Loading),防止用户重复操作。
|
||||
- 所有错误提示都应清晰、友好,用红色字体在表单项附近或页面顶部固定区域显示。
|
||||
|
||||
**3. 用户体验(UX)重点**
|
||||
|
||||
1. **默认焦点:** 页面加载后,自动将光标聚焦到“用户名”输入框。
|
||||
2. **键盘操作:** 在最后一项输入框(验证码或密码)按`Enter`键应触发登录操作。
|
||||
3. **密码可见性:** “眼睛”图标是行业标准,显著降低用户输错密码的概率。
|
||||
4. **智能验证码:** 不要一开始就展示验证码,而是在系统怀疑有风险(如输错一次)时再出现,优化善良用户的体验。
|
||||
5. **状态反馈:** 二维码登录的每种状态(待扫描、已确认、已过期)都必须有明确的视觉和文案反馈,让用户知其所以然。
|
||||
6. **链路闭环:** “忘记密码”和“注册”入口必须清晰可见,为登录失败的用户提供清晰的出路。
|
||||
|
||||
**4. 非功能性需求**
|
||||
|
||||
1. **性能:** 页面加载速度快,登录接口响应时间应小于 500ms。
|
||||
2. **安全性:**
|
||||
- 密码传输必须使用 HTTPS 并加密(如 SHA256 加盐)。
|
||||
- 防暴力破解:后端需实施限流策略。
|
||||
- Token 管理:登录成功后颁发的 Token 需有有效期并支持刷新机制。
|
||||
3. **兼容性:** 支持主流浏览器(Chrome, Firefox, Safari, Edge)及移动端浏览器。
|
||||
|
||||
**5. 输出物建议**
|
||||
|
||||
1. **PRD 文档:** 即本文档,用于详细阐述逻辑和规则。
|
||||
2. **线框图(Wireframe):** 绘制登录页的布局,标注各元素和交互点。
|
||||
3. **高保真原型(High-Fidelity Mockup):** 由 UI 设计师输出,明确视觉样式、颜色、字体等。
|
||||
4. **交互原型(Interactive Prototype):** 使用 Figma、Axure 等工具制作可点击的原型,演示登录成功、失败、切换等所有流程。
|
||||
@@ -1,3 +1,6 @@
|
||||
# zn-ai
|
||||
|
||||
员工 PC 端应用
|
||||
|
||||
技术栈:Vue3 + Electron + ElementPlus + TypeScript + Vite
|
||||
包管理工具:Yarn
|
||||
|
||||
31
controller/tray.js
Normal file
31
controller/tray.js
Normal file
@@ -0,0 +1,31 @@
|
||||
// 创建系统托盘
|
||||
const { Tray, Menu } = require("electron");
|
||||
const path = require("path");
|
||||
|
||||
function createTray(app, win) {
|
||||
let tray = new Tray(path.join(__dirname, "../public/favicon.ico"));
|
||||
|
||||
tray.setToolTip("示例平台"); // 鼠标放在托盘图标上的提示信息
|
||||
|
||||
tray.on("click", (e) => {
|
||||
if (e.shiftKey) {
|
||||
app.quit();
|
||||
} else {
|
||||
win.show();
|
||||
}
|
||||
});
|
||||
|
||||
tray.setContextMenu(
|
||||
Menu.buildFromTemplate([
|
||||
{
|
||||
label: "退出",
|
||||
click: () => {
|
||||
// 先把用户的登录状态和用户的登录信息给清楚掉,再退出
|
||||
app.quit();
|
||||
},
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = createTray;
|
||||
46
electron/main.js
Normal file
46
electron/main.js
Normal file
@@ -0,0 +1,46 @@
|
||||
const { app, ipcMain, BrowserWindow } = require("electron");
|
||||
const { join } = require("path");
|
||||
const createTray = require("../controller/tray");
|
||||
|
||||
// 映射页面
|
||||
const env = app.isPackaged ? "production" : "development";
|
||||
|
||||
const createWindow = () => {
|
||||
const win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
frame: false, // 不要自带的窗口
|
||||
webPreferences: {
|
||||
preload: join(__dirname, "./preload/index.js"),
|
||||
},
|
||||
});
|
||||
|
||||
if (env === "development") {
|
||||
win.loadURL("http://localhost:5173");
|
||||
|
||||
// 打开开发工具 { mode: "detach" }
|
||||
// win.webContents.openDevTools();
|
||||
} else {
|
||||
win.loadFile("../dist/index.html");
|
||||
}
|
||||
|
||||
// 系统托盘
|
||||
createTray(app, win);
|
||||
};
|
||||
|
||||
ipcMain.handle("sent-event", (event, params) => {
|
||||
console.log(params);
|
||||
return "1111";
|
||||
});
|
||||
|
||||
app.whenReady().then(() => {
|
||||
createWindow();
|
||||
|
||||
app.on("activate", () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
||||
});
|
||||
});
|
||||
|
||||
app.on("window-all-closed", () => {
|
||||
if (process.platform !== "darwin") app.quit();
|
||||
});
|
||||
17
index.html
Normal file
17
index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!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" />
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self' 'unsafe-inline';"
|
||||
/>
|
||||
<title>Vite + Vue</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
8939
package-lock.json
generated
Normal file
8939
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
58
package.json
Normal file
58
package.json
Normal file
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "ZN-AI",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "commonjs",
|
||||
"main": "electron/main.js",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"electron": "nodemon --exec electron . --watch ./ --ext .js,.html,.css,.vue",
|
||||
"electron:dev": "npm-run-all --parallel dev electron",
|
||||
"electron:build": "vite build && electron-builder"
|
||||
},
|
||||
"build": {
|
||||
"productName": "ZN-AI",
|
||||
"appId": "ZN-AI",
|
||||
"asar": true,
|
||||
"copyright": "Copyright © 2022 zhinian",
|
||||
"directories": {
|
||||
"output": "dist_electron/${version}"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"electron"
|
||||
],
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"allowToChangeInstallationDirectory": true
|
||||
},
|
||||
"mac": {
|
||||
"category": "your.app.category.type"
|
||||
},
|
||||
"win": {
|
||||
"icon": "./electron/log.ico",
|
||||
"target": [
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": [
|
||||
"ia32"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"linux": {}
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.5.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"electron": "^38.1.2",
|
||||
"electron-builder": "^26.0.12",
|
||||
"nodemon": "^3.1.10",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"vite": "^7.1.6"
|
||||
}
|
||||
}
|
||||
11
preload/index.js
Normal file
11
preload/index.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const { contextBridge, ipcRenderer } = require("electron");
|
||||
|
||||
const handleSend = async (vue_params) => {
|
||||
let fallback = await ipcRenderer.invoke("sent-event", vue_params);
|
||||
return fallback;
|
||||
};
|
||||
|
||||
contextBridge.exposeInMainWorld("myApi", {
|
||||
handleSend: handleSend,
|
||||
// 能暴露的不仅仅是函数,我们还可以暴露变量
|
||||
});
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
1
public/vite.svg
Normal file
1
public/vite.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
38
src/App.vue
Normal file
38
src/App.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script setup>
|
||||
import { onMounted } from 'vue'
|
||||
import HelloWorld from './components/HelloWorld.vue'
|
||||
|
||||
onMounted(async () => {
|
||||
let res = await window.myApi.handleSend('liaoruiruirurirui')
|
||||
console.log(res)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<a href="https://vite.dev" target="_blank">
|
||||
<img src="/vite.svg" class="logo" alt="Vite logo" />
|
||||
</a>
|
||||
<a href="https://vuejs.org/" target="_blank">
|
||||
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
|
||||
</a>
|
||||
</div>
|
||||
<HelloWorld msg="Vite + Vue" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: filter 300ms;
|
||||
}
|
||||
|
||||
.logo:hover {
|
||||
filter: drop-shadow(0 0 2em #646cffaa);
|
||||
}
|
||||
|
||||
.logo.vue:hover {
|
||||
filter: drop-shadow(0 0 2em #42b883aa);
|
||||
}
|
||||
</style>
|
||||
1
src/assets/vue.svg
Normal file
1
src/assets/vue.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 496 B |
43
src/components/HelloWorld.vue
Normal file
43
src/components/HelloWorld.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineProps({
|
||||
msg: String,
|
||||
})
|
||||
|
||||
const count = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>{{ msg }}</h1>
|
||||
|
||||
<div class="card">
|
||||
<button type="button" @click="count++">count is {{ count }}</button>
|
||||
<p>
|
||||
Edit
|
||||
<code>components/HelloWorld.vue</code> to test HMR
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Check out
|
||||
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
|
||||
>create-vue</a
|
||||
>, the official Vue + Vite starter
|
||||
</p>
|
||||
<p>
|
||||
Learn more about IDE Support for Vue in the
|
||||
<a
|
||||
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
|
||||
target="_blank"
|
||||
>Vue Docs Scaling up Guide</a
|
||||
>.
|
||||
</p>
|
||||
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
||||
5
src/main.js
Normal file
5
src/main.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import { createApp } from 'vue'
|
||||
import './style.css'
|
||||
import App from './App.vue'
|
||||
|
||||
createApp(App).mount('#app')
|
||||
79
src/style.css
Normal file
79
src/style.css
Normal file
@@ -0,0 +1,79 @@
|
||||
:root {
|
||||
font-family: 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;
|
||||
}
|
||||
}
|
||||
17
vite.config.js
Normal file
17
vite.config.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { defineConfig } from "vite";
|
||||
import { resolve } from "path";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
base: "./",
|
||||
manifest: true, //配置后才能让编译后的vue路径被正确识别
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
optimizeDeps: {
|
||||
exclude: ["electron"], // 告诉 Vite 排除预构建 electron,不然会出现 __diranme is not defined
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user