feat: 新增组件

This commit is contained in:
DEV_DSW
2025-09-23 16:59:32 +08:00
parent 3c4f14be7f
commit c77c5350aa
14 changed files with 739 additions and 155 deletions

View File

@@ -0,0 +1,26 @@
const { ipcMain, BrowserWindow } = require("electron");
// 最小化
ipcMain.on("window-min", (event) => {
const webContent = event.sender;
const win = BrowserWindow.fromWebContents(webContent);
win.minimize();
});
// 最大化
ipcMain.on("window-max", (event) => {
const webContent = event.sender;
const win = BrowserWindow.fromWebContents(webContent);
if (win.isMaximized()) {
win.restore();
} else {
win.maximize();
}
});
// 关闭
ipcMain.on("window-close", (event) => {
const webContent = event.sender;
const win = BrowserWindow.fromWebContents(webContent);
win.close();
});

250
package-lock.json generated
View File

@@ -8,7 +8,10 @@
"name": "ZN-AI", "name": "ZN-AI",
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.3.2",
"element-plus": "^2.11.3",
"pinia": "^3.0.3", "pinia": "^3.0.3",
"tailwindcss": "^4.1.13",
"vue": "^3.5.21", "vue": "^3.5.21",
"vue-router": "^4.5.1" "vue-router": "^4.5.1"
}, },
@@ -67,6 +70,15 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@ctrl/tinycolor": {
"version": "3.6.1",
"resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
"integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/@develar/schema-utils": { "node_modules/@develar/schema-utils": {
"version": "2.6.5", "version": "2.6.5",
"resolved": "https://registry.npmmirror.com/@develar/schema-utils/-/schema-utils-2.6.5.tgz", "resolved": "https://registry.npmmirror.com/@develar/schema-utils/-/schema-utils-2.6.5.tgz",
@@ -785,6 +797,15 @@
"node": ">= 10.0.0" "node": ">= 10.0.0"
} }
}, },
"node_modules/@element-plus/icons-vue": {
"version": "2.3.2",
"resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz",
"integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==",
"license": "MIT",
"peerDependencies": {
"vue": "^3.2.0"
}
},
"node_modules/@esbuild/aix-ppc64": { "node_modules/@esbuild/aix-ppc64": {
"version": "0.25.10", "version": "0.25.10",
"resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz",
@@ -1227,6 +1248,31 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@floating-ui/core": {
"version": "1.7.3",
"resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.7.3.tgz",
"integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
"license": "MIT",
"dependencies": {
"@floating-ui/utils": "^0.2.10"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.7.4",
"resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.4.tgz",
"integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==",
"license": "MIT",
"dependencies": {
"@floating-ui/core": "^1.7.3",
"@floating-ui/utils": "^0.2.10"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.2.10",
"resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.10.tgz",
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
"license": "MIT"
},
"node_modules/@gar/promisify": { "node_modules/@gar/promisify": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmmirror.com/@gar/promisify/-/promisify-1.1.3.tgz", "resolved": "https://registry.npmmirror.com/@gar/promisify/-/promisify-1.1.3.tgz",
@@ -1561,6 +1607,17 @@
"node": ">=14" "node": ">=14"
} }
}, },
"node_modules/@popperjs/core": {
"name": "@sxzz/popperjs-es",
"version": "2.11.7",
"resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz",
"integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@rolldown/pluginutils": { "node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.29", "version": "1.0.0-beta.29",
"resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.29.tgz", "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.29.tgz",
@@ -1969,6 +2026,21 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/lodash": {
"version": "4.17.20",
"resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==",
"license": "MIT"
},
"node_modules/@types/lodash-es": {
"version": "4.17.12",
"resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz",
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
"license": "MIT",
"dependencies": {
"@types/lodash": "*"
}
},
"node_modules/@types/ms": { "node_modules/@types/ms": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmmirror.com/@types/ms/-/ms-2.1.0.tgz", "resolved": "https://registry.npmmirror.com/@types/ms/-/ms-2.1.0.tgz",
@@ -2016,6 +2088,12 @@
"license": "MIT", "license": "MIT",
"optional": true "optional": true
}, },
"node_modules/@types/web-bluetooth": {
"version": "0.0.16",
"resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
"integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==",
"license": "MIT"
},
"node_modules/@types/yauzl": { "node_modules/@types/yauzl": {
"version": "2.10.3", "version": "2.10.3",
"resolved": "https://registry.npmmirror.com/@types/yauzl/-/yauzl-2.10.3.tgz", "resolved": "https://registry.npmmirror.com/@types/yauzl/-/yauzl-2.10.3.tgz",
@@ -2177,6 +2255,94 @@
"integrity": "sha512-+2k1EQpnYuVuu3N7atWyG3/xoFWIVJZq4Mz8XNOdScFI0etES75fbny/oU4lKWk/577P1zmg0ioYvpGEDZ3DLw==", "integrity": "sha512-+2k1EQpnYuVuu3N7atWyG3/xoFWIVJZq4Mz8XNOdScFI0etES75fbny/oU4lKWk/577P1zmg0ioYvpGEDZ3DLw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@vueuse/core": {
"version": "9.13.0",
"resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz",
"integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==",
"license": "MIT",
"dependencies": {
"@types/web-bluetooth": "^0.0.16",
"@vueuse/metadata": "9.13.0",
"@vueuse/shared": "9.13.0",
"vue-demi": "*"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/core/node_modules/vue-demi": {
"version": "0.14.10",
"resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
"hasInstallScript": true,
"license": "MIT",
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/@vueuse/metadata": {
"version": "9.13.0",
"resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.13.0.tgz",
"integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/shared": {
"version": "9.13.0",
"resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.13.0.tgz",
"integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==",
"license": "MIT",
"dependencies": {
"vue-demi": "*"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/shared/node_modules/vue-demi": {
"version": "0.14.10",
"resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
"hasInstallScript": true,
"license": "MIT",
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/@xmldom/xmldom": { "node_modules/@xmldom/xmldom": {
"version": "0.8.11", "version": "0.8.11",
"resolved": "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.11.tgz", "resolved": "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.11.tgz",
@@ -2559,6 +2725,12 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/async-validator": {
"version": "4.2.5",
"resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz",
"integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==",
"license": "MIT"
},
"node_modules/asynckit": { "node_modules/asynckit": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
@@ -3530,6 +3702,12 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/dayjs": {
"version": "1.11.18",
"resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.18.tgz",
"integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==",
"license": "MIT"
},
"node_modules/debug": { "node_modules/debug": {
"version": "4.4.3", "version": "4.4.3",
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz",
@@ -4166,6 +4344,32 @@
"node": ">=6 <7 || >=8" "node": ">=6 <7 || >=8"
} }
}, },
"node_modules/element-plus": {
"version": "2.11.3",
"resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.11.3.tgz",
"integrity": "sha512-769xsjLR4B9Vf9cl5PDXnwTEdmFJvMgAkYtthdJKPhjVjU3hdAwTJ+gXKiO+PUyo2KWFwOYKZd4Ywh6PHfkbJg==",
"license": "MIT",
"dependencies": {
"@ctrl/tinycolor": "^3.4.1",
"@element-plus/icons-vue": "^2.3.1",
"@floating-ui/dom": "^1.0.1",
"@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7",
"@types/lodash": "^4.14.182",
"@types/lodash-es": "^4.17.6",
"@vueuse/core": "^9.1.0",
"async-validator": "^4.2.5",
"dayjs": "^1.11.13",
"escape-html": "^1.0.3",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"lodash-unified": "^1.0.2",
"memoize-one": "^6.0.0",
"normalize-wheel-es": "^1.2.0"
},
"peerDependencies": {
"vue": "^3.2.0"
}
},
"node_modules/emoji-regex": { "node_modules/emoji-regex": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -4429,6 +4633,12 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT"
},
"node_modules/escape-string-regexp": { "node_modules/escape-string-regexp": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -5943,9 +6153,25 @@
"version": "4.17.21", "version": "4.17.21",
"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"license": "MIT"
},
"node_modules/lodash-unified": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz",
"integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==",
"license": "MIT",
"peerDependencies": {
"@types/lodash-es": "*",
"lodash": "*",
"lodash-es": "*"
}
},
"node_modules/log-symbols": { "node_modules/log-symbols": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmmirror.com/log-symbols/-/log-symbols-4.1.0.tgz", "resolved": "https://registry.npmmirror.com/log-symbols/-/log-symbols-4.1.0.tgz",
@@ -6162,6 +6388,12 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/memoize-one": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz",
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
"license": "MIT"
},
"node_modules/memorystream": { "node_modules/memorystream": {
"version": "0.3.1", "version": "0.3.1",
"resolved": "https://registry.npmmirror.com/memorystream/-/memorystream-0.3.1.tgz", "resolved": "https://registry.npmmirror.com/memorystream/-/memorystream-0.3.1.tgz",
@@ -6569,6 +6801,12 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/normalize-wheel-es": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
"integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==",
"license": "BSD-3-Clause"
},
"node_modules/npm-run-all": { "node_modules/npm-run-all": {
"version": "4.1.5", "version": "4.1.5",
"resolved": "https://registry.npmmirror.com/npm-run-all/-/npm-run-all-4.1.5.tgz", "resolved": "https://registry.npmmirror.com/npm-run-all/-/npm-run-all-4.1.5.tgz",
@@ -6992,7 +7230,7 @@
}, },
"node_modules/pinia": { "node_modules/pinia": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.3.tgz", "resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.3.tgz",
"integrity": "sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==", "integrity": "sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -8231,6 +8469,12 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/tailwindcss": {
"version": "4.1.13",
"resolved": "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-4.1.13.tgz",
"integrity": "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==",
"license": "MIT"
},
"node_modules/tar": { "node_modules/tar": {
"version": "6.2.1", "version": "6.2.1",
"resolved": "https://registry.npmmirror.com/tar/-/tar-6.2.1.tgz", "resolved": "https://registry.npmmirror.com/tar/-/tar-6.2.1.tgz",
@@ -8766,7 +9010,7 @@
}, },
"node_modules/vue-router": { "node_modules/vue-router": {
"version": "4.5.1", "version": "4.5.1",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.1.tgz", "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.5.1.tgz",
"integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==", "integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {

View File

@@ -45,7 +45,10 @@
"linux": {} "linux": {}
}, },
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.3.2",
"element-plus": "^2.11.3",
"pinia": "^3.0.3", "pinia": "^3.0.3",
"tailwindcss": "^4.1.13",
"vue": "^3.5.21", "vue": "^3.5.21",
"vue-router": "^4.5.1" "vue-router": "^4.5.1"
}, },

View File

@@ -8,4 +8,18 @@ const handleSend = async (vue_params) => {
contextBridge.exposeInMainWorld("myApi", { contextBridge.exposeInMainWorld("myApi", {
handleSend: handleSend, handleSend: handleSend,
// 能暴露的不仅仅是函数,我们还可以暴露变量 // 能暴露的不仅仅是函数,我们还可以暴露变量
// 最小化
windowMin: () => {
ipcRenderer.send("window-min");
},
// 最大化
windowMax: () => {
ipcRenderer.send("window-max");
},
// 关闭窗口
windowClose: () => {
ipcRenderer.send("window-close");
},
}); });

View File

@@ -1,6 +1,5 @@
<script setup> <script setup>
import { onMounted } from "vue"; import { onMounted } from "vue";
import HelloWorld from "./components/HelloWorld.vue";
import { useMainStore } from "./store"; import { useMainStore } from "./store";
const store = useMainStore(); const store = useMainStore();
@@ -11,32 +10,7 @@ onMounted(() => {
</script> </script>
<template> <template>
<HelloWorld />
<!-- Pinia 状态管理示例 -->
<div style="margin: 20px; padding: 20px; border: 1px solid #ccc">
<h3>计数器: {{ store.count }}</h3>
<button @click="store.increment()">+1</button>
<button @click="store.decrement()">-1</button>
</div>
<!-- 路由视图 -->
<router-view /> <router-view />
</template> </template>
<style scoped> <style scoped></style>
.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>

View File

@@ -1,53 +0,0 @@
<script setup lang="ts">
// import TheWelcome from '../components/TheWelcome.vue'
import { onMounted } from "vue";
onMounted(async () => {
let res = await window.myApi.handleSend("liaoruiruirurirui");
console.log(res);
});
const toMin = () => {
window.myApi.windowMin();
};
const toBig = () => {
window.myApi.windowMax();
};
const toClose = () => {
window.myApi.windowClose();
};
</script>
<template>
<main>
<div class="hearder">
<span @click="toMin">最小化</span>
<span @click="toBig">最大化</span>
<span @click="toClose">关闭</span>
</div>
<div class="main">主要内容</div>
<!-- <TheWelcome /> -->
</main>
</template>
<style scoped>
.hearder {
-webkit-app-region: drag;
background-color: #ccc;
height: 40px;
width: 100%;
display: flex;
justify-content: flex-end;
align-items: center;
}
.hearder span {
margin: 0 16px;
border: 1px solid rgb(35, 34, 34);
cursor: pointer;
-webkit-app-region: no-drag;
}
.main {
height: calc(100vh - 40px);
display: flex;
align-items: center;
justify-content: center;
}
</style>

View File

@@ -1,8 +1,8 @@
import { createApp } from "vue"; import { createApp } from "vue";
import "./style.css";
import App from "./App.vue"; import App from "./App.vue";
import router from "./router"; import router from "./router";
import pinia from "./store"; import pinia from "./store";
import "./style.css";
const app = createApp(App); const app = createApp(App);
app.use(router); app.use(router);

View File

@@ -1,16 +1,26 @@
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from "vue-router";
const routes = [ const routes = [
{ {
path: '/', path: "/",
name: 'Home', name: "login",
component: () => import('../App.vue') component: () => import("../views/login/index.vue"),
} },
] {
path: "/home",
name: "home",
component: () => import("../views/home/index.vue"),
},
{
path: "/about",
name: "about",
component: () => import("../views/about/index.vue"),
},
];
const router = createRouter({ const router = createRouter({
history: createWebHistory(), history: createWebHistory(import.meta.env.BASE_URL),
routes routes,
}) });
export default router export default router;

View File

@@ -1,79 +1,25 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root { :root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5; line-height: 1.5;
font-weight: 400; font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none; font-synthesis: none;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body { body {
margin: 0; margin: 0;
display: flex; padding: 0;
place-items: center;
min-width: 320px;
min-height: 100vh; 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 { #app {
max-width: 1280px; width: 100%;
margin: 0 auto; min-height: 100vh;
padding: 2rem;
text-align: center;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
} }

View File

@@ -0,0 +1,7 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>
<style scoped></style>

107
src/views/home/index.vue Normal file
View File

@@ -0,0 +1,107 @@
<template>
<main class="h-screen flex flex-col">
<!-- 标题栏 -->
<div class="header bg-gray-300 h-10 w-full flex justify-end items-center" style="-webkit-app-region: drag;">
<span @click="toMin"
class="mx-4 px-2 py-1 border border-gray-800 cursor-pointer hover:bg-gray-400 transition-colors"
style="-webkit-app-region: no-drag;">最小化</span>
<span @click="toBig"
class="mx-4 px-2 py-1 border border-gray-800 cursor-pointer hover:bg-gray-400 transition-colors"
style="-webkit-app-region: no-drag;">最大化</span>
<span @click="toClose"
class="mx-4 px-2 py-1 border border-gray-800 cursor-pointer hover:bg-red-400 transition-colors"
style="-webkit-app-region: no-drag;">关闭</span>
</div>
<!-- 主要内容区域 -->
<div class="flex-1 flex flex-col items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-100 p-8">
<h1 class="text-4xl font-bold text-gray-800 mb-8">Tailwind CSS 示例</h1>
<!-- 卡片示例 -->
<div class="max-w-md w-full bg-white rounded-lg shadow-lg p-6 mb-6">
<h2 class="text-2xl font-semibold text-gray-700 mb-4">卡片组件</h2>
<p class="text-gray-600 mb-4">这是一个使用 Tailwind CSS 样式的卡片组件示例</p>
<button class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded transition-colors">
点击按钮
</button>
</div>
<!-- 网格布局示例 -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 w-full max-w-4xl">
<div class="bg-red-100 p-4 rounded-lg text-center">
<div class="text-red-600 text-2xl mb-2">🎨</div>
<h3 class="font-semibold text-red-800">设计</h3>
<p class="text-red-600 text-sm">美观的界面设计</p>
</div>
<div class="bg-green-100 p-4 rounded-lg text-center">
<div class="text-green-600 text-2xl mb-2"></div>
<h3 class="font-semibold text-green-800">性能</h3>
<p class="text-green-600 text-sm">快速响应体验</p>
</div>
<div class="bg-purple-100 p-4 rounded-lg text-center">
<div class="text-purple-600 text-2xl mb-2">🔧</div>
<h3 class="font-semibold text-purple-800">功能</h3>
<p class="text-purple-600 text-sm">丰富的功能特性</p>
</div>
</div>
<!-- 响应式按钮组 -->
<div class="flex flex-wrap gap-2 mt-6">
<button
class="bg-indigo-500 hover:bg-indigo-600 text-white px-4 py-2 rounded-md text-sm font-medium transition-colors">
主要按钮
</button>
<button
class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded-md text-sm font-medium transition-colors">
次要按钮
</button>
<button
class="border border-indigo-500 text-indigo-500 hover:bg-indigo-50 px-4 py-2 rounded-md text-sm font-medium transition-colors">
边框按钮
</button>
</div>
</div>
</main>
</template>
<script setup>
import { onMounted } from "vue";
// 检查是否在Electron环境中
const isElectron = typeof window !== 'undefined' && window.myApi;
onMounted(async () => {
if (isElectron) {
let res = await window.myApi.handleSend("liaoruiruirurirui");
console.log(res);
} else {
console.log('在浏览器环境中运行Electron API不可用');
}
});
const toMin = () => {
if (isElectron) {
window.myApi.windowMin();
} else {
console.log('最小化功能仅在Electron环境中可用');
}
};
const toBig = () => {
if (isElectron) {
window.myApi.windowMax();
} else {
console.log('最大化功能仅在Electron环境中可用');
}
};
const toClose = () => {
if (isElectron) {
window.myApi.windowClose();
} else {
console.log('关闭功能仅在Electron环境中可用');
}
};
</script>
<style scoped></style>

295
src/views/login/index.vue Normal file
View File

@@ -0,0 +1,295 @@
<template>
<div class="min-h-screen bg-gradient-to-br from-purple-50 to-blue-50 flex items-center justify-center p-4">
<div class="w-full max-w-md">
<!-- 登录卡片 -->
<div class="bg-white rounded-2xl shadow-xl p-8">
<!-- 头部 -->
<div class="text-center mb-8">
<div
class="w-16 h-16 bg-gradient-to-r from-purple-500 to-blue-500 rounded-full mx-auto mb-4 flex items-center justify-center">
<div class="w-8 h-8 bg-white rounded-full flex items-center justify-center">
<div class="w-4 h-4 bg-gradient-to-r from-purple-500 to-blue-500 rounded-full"></div>
</div>
</div>
<h1 class="text-2xl font-bold text-gray-800 mb-2">欢迎来到 Fellou</h1>
<p class="text-gray-500 text-sm">您的隐私对我们很重要我们确保您的数据安全可靠</p>
</div>
<!-- 登录方式切换 -->
<div class="flex mb-6">
<button @click="loginType = 'code'" :class="[
'flex-1 py-2 px-4 text-sm font-medium border-b-2 transition-colors',
loginType === 'code'
? 'text-blue-600 border-blue-600'
: 'text-gray-500 border-transparent hover:text-gray-700'
]">
验证码登录
</button>
<button @click="loginType = 'password'" :class="[
'flex-1 py-2 px-4 text-sm font-medium border-b-2 transition-colors',
loginType === 'password'
? 'text-blue-600 border-blue-600'
: 'text-gray-500 border-transparent hover:text-gray-700'
]">
密码登录
</button>
</div>
<!-- 登录表单 -->
<el-form ref="loginFormRef" :model="loginForm" :rules="loginRules" @submit.prevent="handleLogin"
class="space-y-4">
<!-- 邮箱输入框 -->
<el-form-item prop="email" class="mb-4">
<el-input v-model="loginForm.email" type="email" placeholder="请输入邮箱" size="large" class="w-full"
:prefix-icon="User" @blur="validateField('email')" />
</el-form-item>
<!-- 验证码登录 -->
<div v-if="loginType === 'code'" class="space-y-4">
<el-form-item prop="verifyCode" class="mb-4">
<div class="flex space-x-2">
<el-input v-model="loginForm.verifyCode" placeholder="请输入4位验证码" size="large" class="flex-1"
:prefix-icon="Shield" maxlength="4" @blur="validateField('verifyCode')" />
<el-button type="primary" size="large" :disabled="!isEmailValid || countdown > 0"
@click="sendVerifyCode" class="px-6">
{{ countdown > 0 ? `${countdown}s` : '发送验证码' }}
</el-button>
</div>
</el-form-item>
</div>
<!-- 密码登录 -->
<div v-if="loginType === 'password'">
<el-form-item prop="password" class="mb-4">
<el-input v-model="loginForm.password" :type="showPassword ? 'text' : 'password'" placeholder="请输入密码"
size="large" class="w-full" :prefix-icon="Lock" @blur="validateField('password')">
<template #suffix>
<el-icon @click="showPassword = !showPassword"
class="cursor-pointer text-gray-400 hover:text-gray-600">
<View v-if="showPassword" />
<Hide v-else />
</el-icon>
</template>
</el-input>
</el-form-item>
<!-- 忘记密码 -->
<div class="text-right mb-4">
<el-link type="primary" :underline="false" @click="handleForgotPassword">
忘记密码
</el-link>
</div>
</div>
<!-- 协议同意 -->
<el-form-item prop="agreed" class="mb-6">
<el-checkbox v-model="loginForm.agreed" @change="validateField('agreed')">
<span class="text-sm text-gray-600">
我已阅读并同意 Fellou
<el-link type="primary" :underline="false" @click="handlePrivacyPolicy">隐私政策</el-link>
<el-link type="primary" :underline="false" @click="handleServiceTerms">平台服务条款</el-link>
</span>
</el-checkbox>
</el-form-item>
<!-- 登录按钮 -->
<el-form-item>
<el-button type="primary" size="large" :loading="loading" @click="handleLogin"
class="w-full bg-gradient-to-r from-purple-600 to-blue-600 border-0 hover:from-purple-700 hover:to-blue-700">
{{ loading ? '登录中...' : '注册 / 登录' }}
</el-button>
</el-form-item>
</el-form>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { User, Lock, Shield, View, Hide } from '@element-plus/icons-vue'
// 登录类型
const loginType = ref('code')
// 表单引用
const loginFormRef = ref()
// 表单数据
const loginForm = reactive({
email: '',
password: '',
verifyCode: '',
agreed: false
})
// 表单验证规则
const loginRules = reactive({
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, message: '密码长度不能少于6位', trigger: 'blur' }
],
verifyCode: [
{ required: true, message: '请输入验证码', trigger: 'blur' },
{ len: 4, message: '验证码必须为4位', trigger: 'blur' }
],
agreed: [
{
validator: (rule, value, callback) => {
if (!value) {
callback(new Error('请同意隐私政策和服务条款'))
} else {
callback()
}
},
trigger: 'change'
}
]
})
// 状态管理
const loading = ref(false)
const showPassword = ref(false)
const countdown = ref(0)
// 计算属性
const isEmailValid = computed(() => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return emailRegex.test(loginForm.email)
})
// 验证码倒计时
const startCountdown = () => {
countdown.value = 60
const timer = setInterval(() => {
countdown.value--
if (countdown.value <= 0) {
clearInterval(timer)
}
}, 1000)
}
// 发送验证码
const sendVerifyCode = async () => {
if (!isEmailValid.value) {
ElMessage.error('请先输入正确的邮箱地址')
return
}
try {
// 这里调用发送验证码的API
ElMessage.success('验证码已发送到您的邮箱')
startCountdown()
} catch (error) {
ElMessage.error('验证码发送失败,请重试')
}
}
// 验证单个字段
const validateField = (field) => {
loginFormRef.value?.validateField(field)
}
// 处理登录
const handleLogin = async () => {
if (!loginFormRef.value) return
try {
const valid = await loginFormRef.value.validate()
if (!valid) return
loading.value = true
// 根据登录类型构建请求数据
const loginData = {
email: loginForm.email,
agreed: loginForm.agreed
}
if (loginType.value === 'password') {
loginData.password = loginForm.password
} else {
loginData.verifyCode = loginForm.verifyCode
}
// 这里调用登录API
console.log('登录数据:', loginData)
// 模拟登录请求
await new Promise(resolve => setTimeout(resolve, 2000))
ElMessage.success('登录成功')
// 登录成功后的跳转逻辑
// router.push('/home')
} catch (error) {
console.error('登录失败:', error)
ElMessage.error('登录失败,请重试')
} finally {
loading.value = false
}
}
// 忘记密码
const handleForgotPassword = () => {
ElMessage.info('忘记密码功能开发中')
}
// 隐私政策
const handlePrivacyPolicy = () => {
ElMessage.info('隐私政策页面开发中')
}
// 服务条款
const handleServiceTerms = () => {
ElMessage.info('服务条款页面开发中')
}
// 页面加载时自动聚焦到邮箱输入框
onMounted(() => {
// 可以在这里添加自动聚焦逻辑
})
</script>
<style scoped>
/* Element Plus 样式覆盖 */
:deep(.el-input__wrapper) {
border-radius: 8px;
box-shadow: 0 0 0 1px #e5e7eb;
transition: all 0.2s;
}
:deep(.el-input__wrapper:hover) {
box-shadow: 0 0 0 1px #d1d5db;
}
:deep(.el-input.is-focus .el-input__wrapper) {
box-shadow: 0 0 0 2px #3b82f6;
}
:deep(.el-button--primary) {
border-radius: 8px;
font-weight: 500;
}
:deep(.el-checkbox__label) {
font-size: 14px;
}
/* 自定义渐变按钮样式 */
.el-button--primary.bg-gradient-to-r {
background: linear-gradient(to right, #9333ea, #2563eb) !important;
border: none !important;
}
.el-button--primary.bg-gradient-to-r:hover {
background: linear-gradient(to right, #7c3aed, #1d4ed8) !important;
}
</style>

11
tailwind.config.js Normal file
View File

@@ -0,0 +1,11 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}