first commit
9
.editorconfig
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
33
.gitignore
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
.DS_Store
|
||||||
|
.thumbs.db
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Quasar core related directories
|
||||||
|
.quasar
|
||||||
|
/dist
|
||||||
|
/quasar.config.*.temporary.compiled*
|
||||||
|
|
||||||
|
# Cordova related directories and files
|
||||||
|
/src-cordova/node_modules
|
||||||
|
/src-cordova/platforms
|
||||||
|
/src-cordova/plugins
|
||||||
|
/src-cordova/www
|
||||||
|
|
||||||
|
# Capacitor related directories and files
|
||||||
|
/src-capacitor/www
|
||||||
|
/src-capacitor/node_modules
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
|
||||||
|
# local .env files
|
||||||
|
.env.local*
|
5
.npmrc
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# pnpm-related options
|
||||||
|
shamefully-hoist=true
|
||||||
|
strict-peer-dependencies=false
|
||||||
|
# to get the latest compatible packages when creating the project https://github.com/pnpm/pnpm/issues/6463
|
||||||
|
resolution-mode=highest
|
13
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"editorconfig.editorconfig",
|
||||||
|
"vue.volar",
|
||||||
|
"wayou.vscode-todo-highlight"
|
||||||
|
],
|
||||||
|
"unwantedRecommendations": [
|
||||||
|
"octref.vetur",
|
||||||
|
"hookyqr.beautify",
|
||||||
|
"dbaeumer.jshint",
|
||||||
|
"ms-vscode.vscode-typescript-tslint-plugin"
|
||||||
|
]
|
||||||
|
}
|
4
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"editor.bracketPairColorization.enabled": true,
|
||||||
|
"editor.guides.bracketPairs": true
|
||||||
|
}
|
24
README.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# stableDiffusion (stablediffusion)
|
||||||
|
|
||||||
|
A Quasar Project
|
||||||
|
|
||||||
|
## Install the dependencies
|
||||||
|
```bash
|
||||||
|
yarn
|
||||||
|
# or
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Start the app in development mode (hot-code reloading, error reporting, etc.)
|
||||||
|
```bash
|
||||||
|
quasar dev
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Build the app for production
|
||||||
|
```bash
|
||||||
|
quasar build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Customize the configuration
|
||||||
|
See [Configuring quasar.config.js](https://v2.quasar.dev/quasar-cli-vite/quasar-config-js).
|
21
index.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title><%= productName %></title>
|
||||||
|
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="description" content="<%= productDescription %>">
|
||||||
|
<meta name="format-detection" content="telephone=no">
|
||||||
|
<meta name="msapplication-tap-highlight" content="no">
|
||||||
|
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width<% if (ctx.mode.cordova || ctx.mode.capacitor) { %>, viewport-fit=cover<% } %>">
|
||||||
|
|
||||||
|
<link rel="icon" type="image/png" sizes="128x128" href="icons/favicon-128x128.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="96x96" href="icons/favicon-96x96.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png">
|
||||||
|
<link rel="icon" type="image/ico" href="favicon.ico">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- quasar:entry-point -->
|
||||||
|
</body>
|
||||||
|
</html>
|
39
jsconfig.json
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"src/*": [
|
||||||
|
"src/*"
|
||||||
|
],
|
||||||
|
"app/*": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"components/*": [
|
||||||
|
"src/components/*"
|
||||||
|
],
|
||||||
|
"layouts/*": [
|
||||||
|
"src/layouts/*"
|
||||||
|
],
|
||||||
|
"pages/*": [
|
||||||
|
"src/pages/*"
|
||||||
|
],
|
||||||
|
"assets/*": [
|
||||||
|
"src/assets/*"
|
||||||
|
],
|
||||||
|
"boot/*": [
|
||||||
|
"src/boot/*"
|
||||||
|
],
|
||||||
|
"stores/*": [
|
||||||
|
"src/stores/*"
|
||||||
|
],
|
||||||
|
"vue$": [
|
||||||
|
"node_modules/vue/dist/vue.runtime.esm-bundler.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"dist",
|
||||||
|
".quasar",
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
8672
package-lock.json
generated
Normal file
39
package.json
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "stablediffusion",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "A Quasar Project",
|
||||||
|
"productName": "stableDiffusion",
|
||||||
|
"author": "scout <1134087124@qq.com>",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"No test specified\" && exit 0",
|
||||||
|
"dev": "quasar dev",
|
||||||
|
"build": "quasar build"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@formkit/auto-animate": "^0.8.1",
|
||||||
|
"@quasar/extras": "^1.16.4",
|
||||||
|
"animate.css": "^4.1.1",
|
||||||
|
"axios": "^1.2.1",
|
||||||
|
"moment": "^2.30.1",
|
||||||
|
"quasar": "^2.8.0",
|
||||||
|
"vue": "^3.4.18",
|
||||||
|
"vue-i18n": "^9.0.0",
|
||||||
|
"vue-router": "^4.0.12",
|
||||||
|
"vue3-lazyload": "^0.3.8"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@intlify/vite-plugin-vue-i18n": "^3.3.1",
|
||||||
|
"@quasar/app-vite": "^1.8.0",
|
||||||
|
"autoprefixer": "^10.4.19",
|
||||||
|
"naive-ui": "^2.38.1",
|
||||||
|
"postcss": "^8.4.38",
|
||||||
|
"unplugin-auto-import": "^0.17.5",
|
||||||
|
"vfonts": "^0.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^20 || ^18 || ^16",
|
||||||
|
"npm": ">= 6.13.4",
|
||||||
|
"yarn": ">= 1.21.1"
|
||||||
|
}
|
||||||
|
}
|
27
postcss.config.cjs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
// https://github.com/michael-ciniawsky/postcss-load-config
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
plugins: [
|
||||||
|
// https://github.com/postcss/autoprefixer
|
||||||
|
require('autoprefixer')({
|
||||||
|
overrideBrowserslist: [
|
||||||
|
'last 4 Chrome versions',
|
||||||
|
'last 4 Firefox versions',
|
||||||
|
'last 4 Edge versions',
|
||||||
|
'last 4 Safari versions',
|
||||||
|
'last 4 Android versions',
|
||||||
|
'last 4 ChromeAndroid versions',
|
||||||
|
'last 4 FirefoxAndroid versions',
|
||||||
|
'last 4 iOS versions'
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
|
||||||
|
// https://github.com/elchininet/postcss-rtlcss
|
||||||
|
// If you want to support RTL css, then
|
||||||
|
// 1. yarn/npm install postcss-rtlcss
|
||||||
|
// 2. optionally set quasar.config.js > framework > lang to an RTL language
|
||||||
|
// 3. uncomment the following line:
|
||||||
|
// require('postcss-rtlcss')
|
||||||
|
]
|
||||||
|
}
|
BIN
public/favicon.ico
Normal file
After Width: | Height: | Size: 63 KiB |
BIN
public/icons/favicon-128x128.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/icons/favicon-16x16.png
Normal file
After Width: | Height: | Size: 859 B |
BIN
public/icons/favicon-32x32.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
public/icons/favicon-96x96.png
Normal file
After Width: | Height: | Size: 9.4 KiB |
233
quasar.config.js
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
/* eslint-env node */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file runs in a Node context (it's NOT transpiled by Babel), so use only
|
||||||
|
* the ES6 features that are supported by your Node version. https://node.green/
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Configuration for your app
|
||||||
|
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js
|
||||||
|
|
||||||
|
const { configure } = require("quasar/wrappers");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
module.exports = configure(function (/* ctx */) {
|
||||||
|
return {
|
||||||
|
// https://v2.quasar.dev/quasar-cli-vite/prefetch-feature
|
||||||
|
// preFetch: true,
|
||||||
|
|
||||||
|
// app boot file (/src/boot)
|
||||||
|
// --> boot files are part of "main.js"
|
||||||
|
// https://v2.quasar.dev/quasar-cli-vite/boot-files
|
||||||
|
boot: ["i18n", "axios", "naiveUI"],
|
||||||
|
|
||||||
|
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
|
||||||
|
css: ["app.scss"],
|
||||||
|
|
||||||
|
// https://github.com/quasarframework/quasar/tree/dev/extras
|
||||||
|
extras: [
|
||||||
|
// 'ionicons-v4',
|
||||||
|
// 'mdi-v7',
|
||||||
|
// 'fontawesome-v6',
|
||||||
|
// 'eva-icons',
|
||||||
|
// 'themify',
|
||||||
|
// 'line-awesome',
|
||||||
|
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
|
||||||
|
|
||||||
|
"roboto-font", // optional, you are not bound to it
|
||||||
|
"material-icons", // optional, you are not bound to it
|
||||||
|
],
|
||||||
|
|
||||||
|
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#build
|
||||||
|
build: {
|
||||||
|
target: {
|
||||||
|
browser: ["es2019", "edge88", "firefox78", "chrome87", "safari13.1"],
|
||||||
|
node: "node20",
|
||||||
|
},
|
||||||
|
|
||||||
|
vueRouterMode: "hash", // available values: 'hash', 'history'
|
||||||
|
// vueRouterBase,
|
||||||
|
// vueDevtools,
|
||||||
|
// vueOptionsAPI: false,
|
||||||
|
|
||||||
|
// rebuildCache: true, // rebuilds Vite/linter/etc cache on startup
|
||||||
|
|
||||||
|
// publicPath: '/',
|
||||||
|
// analyze: true,
|
||||||
|
// env: {},
|
||||||
|
// rawDefine: {}
|
||||||
|
// ignorePublicFolder: true,
|
||||||
|
// minify: false,
|
||||||
|
// polyfillModulePreload: true,
|
||||||
|
// distDir
|
||||||
|
|
||||||
|
// extendViteConf (viteConf) {},
|
||||||
|
// viteVuePluginOptions: {},
|
||||||
|
|
||||||
|
vitePlugins: [
|
||||||
|
[
|
||||||
|
"@intlify/vite-plugin-vue-i18n",
|
||||||
|
{
|
||||||
|
// if you want to use Vue I18n Legacy API, you need to set `compositionOnly: false`
|
||||||
|
// compositionOnly: false,
|
||||||
|
|
||||||
|
// if you want to use named tokens in your Vue I18n messages, such as 'Hello {name}',
|
||||||
|
// you need to set `runtimeOnly: false`
|
||||||
|
// runtimeOnly: false,
|
||||||
|
|
||||||
|
// you need to set i18n resource including paths !
|
||||||
|
include: path.resolve(__dirname, "./src/i18n/**"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
|
||||||
|
devServer: {
|
||||||
|
// https: true
|
||||||
|
open: true, // opens browser window automatically
|
||||||
|
},
|
||||||
|
|
||||||
|
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#framework
|
||||||
|
framework: {
|
||||||
|
config: {},
|
||||||
|
|
||||||
|
// iconSet: 'material-icons', // Quasar icon set
|
||||||
|
lang: 'zh-CN', // Quasar language pack
|
||||||
|
|
||||||
|
// For special cases outside of where the auto-import strategy can have an impact
|
||||||
|
// (like functional components as one of the examples),
|
||||||
|
// you can manually specify Quasar components/directives to be available everywhere:
|
||||||
|
//
|
||||||
|
// components: [],
|
||||||
|
// directives: [],
|
||||||
|
|
||||||
|
// Quasar plugins
|
||||||
|
plugins: ["LocalStorage", "SessionStorage", "Notify","Dialog"],
|
||||||
|
},
|
||||||
|
|
||||||
|
// animations: 'all', // --- includes all animations
|
||||||
|
// https://v2.quasar.dev/options/animations
|
||||||
|
animations: 'all',
|
||||||
|
|
||||||
|
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#property-sourcefiles
|
||||||
|
// sourceFiles: {
|
||||||
|
// rootComponent: 'src/App.vue',
|
||||||
|
// router: 'src/router/index',
|
||||||
|
// store: 'src/store/index',
|
||||||
|
// registerServiceWorker: 'src-pwa/register-service-worker',
|
||||||
|
// serviceWorker: 'src-pwa/custom-service-worker',
|
||||||
|
// pwaManifestFile: 'src-pwa/manifest.json',
|
||||||
|
// electronMain: 'src-electron/electron-main',
|
||||||
|
// electronPreload: 'src-electron/electron-preload'
|
||||||
|
// },
|
||||||
|
|
||||||
|
// https://v2.quasar.dev/quasar-cli-vite/developing-ssr/configuring-ssr
|
||||||
|
ssr: {
|
||||||
|
// ssrPwaHtmlFilename: "sd.html", // do NOT use index.html as name!
|
||||||
|
// will mess up SSR
|
||||||
|
|
||||||
|
// extendSSRWebserverConf(esbuildConf) {},
|
||||||
|
// extendPackageJson(json) {},
|
||||||
|
|
||||||
|
pwa: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自己提供手动处理 store 中 state 序列化的方法
|
||||||
|
* as window.__INITIAL_STATE__ to the client-side (through a <script> tag)
|
||||||
|
* 通过<script>标签注入到客户端的 window.__INITIAL_STATE__
|
||||||
|
* (需要 @quasar/app-vite v1.0.0-beta.14+)
|
||||||
|
*/
|
||||||
|
manualStoreSerialization: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手动注入 store 的 state 到 ssrContext.state
|
||||||
|
* (需要 @quasar/app-vite v1.0.0-beta.14+)
|
||||||
|
*/
|
||||||
|
manualStoreSsrContextInjection: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手动处理 store 的水化(hydration)过程而代替 Quasar CLI 的水化过程
|
||||||
|
* For Pinia: store.state.value = window.__INITIAL_STATE__
|
||||||
|
* For Vuex: store.replaceState(window.__INITIAL_STATE__)
|
||||||
|
*/
|
||||||
|
manualStoreHydration: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手动调用$q.onSSRHydrated(),代替 Quasar CLI 做的
|
||||||
|
* 这宣布客户端代码应该接管。
|
||||||
|
* This announces that client-side code should takeover.
|
||||||
|
*/
|
||||||
|
manualPostHydrationTrigger: false,
|
||||||
|
|
||||||
|
prodPort: 3000, // 生产环境下 server 默认使用的端口号
|
||||||
|
// (如果在运行时指定了 ({}).PORT,则被取代)
|
||||||
|
|
||||||
|
middlewares: [
|
||||||
|
"render", // 添加自己的中间件时,保证'render'为最后一个
|
||||||
|
// keep this as last one
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
// https://v2.quasar.dev/quasar-cli-vite/developing-pwa/configuring-pwa
|
||||||
|
pwa: {
|
||||||
|
workboxMode: "generateSW", // or 'injectManifest'
|
||||||
|
injectPwaMetaTags: true,
|
||||||
|
swFilename: "sw.js",
|
||||||
|
manifestFilename: "manifest.json",
|
||||||
|
useCredentialsForManifestTag: false,
|
||||||
|
// useFilenameHashes: true,
|
||||||
|
// extendGenerateSWOptions (cfg) {}
|
||||||
|
// extendInjectManifestOptions (cfg) {},
|
||||||
|
// extendManifestJson (json) {}
|
||||||
|
// extendPWACustomSWConf (esbuildConf) {}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-cordova-apps/configuring-cordova
|
||||||
|
cordova: {
|
||||||
|
// noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing
|
||||||
|
},
|
||||||
|
|
||||||
|
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-capacitor-apps/configuring-capacitor
|
||||||
|
capacitor: {
|
||||||
|
hideSplashscreen: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-electron-apps/configuring-electron
|
||||||
|
electron: {
|
||||||
|
// extendElectronMainConf (esbuildConf)
|
||||||
|
// extendElectronPreloadConf (esbuildConf)
|
||||||
|
|
||||||
|
// specify the debugging port to use for the Electron app when running in development mode
|
||||||
|
inspectPort: 5858,
|
||||||
|
|
||||||
|
bundler: "packager", // 'packager' or 'builder'
|
||||||
|
|
||||||
|
packager: {
|
||||||
|
// https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options
|
||||||
|
// OS X / Mac App Store
|
||||||
|
// appBundleId: '',
|
||||||
|
// appCategoryType: '',
|
||||||
|
// osxSign: '',
|
||||||
|
// protocol: 'myapp://path',
|
||||||
|
// Windows only
|
||||||
|
// win32metadata: { ... }
|
||||||
|
},
|
||||||
|
|
||||||
|
builder: {
|
||||||
|
// https://www.electron.build/configuration/configuration
|
||||||
|
|
||||||
|
appId: "stablediffusion",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-browser-extensions/configuring-bex
|
||||||
|
bex: {
|
||||||
|
contentScripts: ["my-content-script"],
|
||||||
|
|
||||||
|
// extendBexScriptsConf (esbuildConf) {}
|
||||||
|
// extendBexManifestJson (json) {}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
58
src-ssr/middlewares/render.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { ssrMiddleware } from 'quasar/wrappers'
|
||||||
|
|
||||||
|
// This middleware should execute as last one
|
||||||
|
// since it captures everything and tries to
|
||||||
|
// render the page with Vue
|
||||||
|
|
||||||
|
export default ssrMiddleware(({ app, resolve, render, serve }) => {
|
||||||
|
// we capture any other Express route and hand it
|
||||||
|
// over to Vue and Vue Router to render our page
|
||||||
|
app.get(resolve.urlPath('*'), (req, res) => {
|
||||||
|
res.setHeader('Content-Type', 'text/html')
|
||||||
|
|
||||||
|
render(/* the ssrContext: */ { req, res })
|
||||||
|
.then(html => {
|
||||||
|
// now let's send the rendered html to the client
|
||||||
|
res.send(html)
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
// oops, we had an error while rendering the page
|
||||||
|
|
||||||
|
// we were told to redirect to another URL
|
||||||
|
if (err.url) {
|
||||||
|
if (err.code) {
|
||||||
|
res.redirect(err.code, err.url)
|
||||||
|
} else {
|
||||||
|
res.redirect(err.url)
|
||||||
|
}
|
||||||
|
} else if (err.code === 404) {
|
||||||
|
// hmm, Vue Router could not find the requested route
|
||||||
|
|
||||||
|
// Should reach here only if no "catch-all" route
|
||||||
|
// is defined in /src/routes
|
||||||
|
res.status(404).send('404 | Page Not Found')
|
||||||
|
} else if (process.env.DEV) {
|
||||||
|
// well, we treat any other code as error;
|
||||||
|
// if we're in dev mode, then we can use Quasar CLI
|
||||||
|
// to display a nice error page that contains the stack
|
||||||
|
// and other useful information
|
||||||
|
|
||||||
|
// serve.error is available on dev only
|
||||||
|
serve.error({ err, req, res })
|
||||||
|
} else {
|
||||||
|
// we're in production, so we should have another method
|
||||||
|
// to display something to the client when we encounter an error
|
||||||
|
// (for security reasons, it's not ok to display the same wealth
|
||||||
|
// of information as we do in development)
|
||||||
|
|
||||||
|
// Render Error Page on production or
|
||||||
|
// create a route (/src/routes) for an error page and redirect to it
|
||||||
|
res.status(500).send('500 | Internal Server Error')
|
||||||
|
|
||||||
|
if (process.env.DEBUGGING) {
|
||||||
|
console.error(err.stack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
136
src-ssr/server.js
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
/**
|
||||||
|
* More info about this file:
|
||||||
|
* https://v2.quasar.dev/quasar-cli-vite/developing-ssr/ssr-webserver
|
||||||
|
*
|
||||||
|
* Runs in Node context.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure to yarn add / npm install (in your project root)
|
||||||
|
* anything you import here (except for express and compression).
|
||||||
|
*/
|
||||||
|
import express from 'express'
|
||||||
|
import compression from 'compression'
|
||||||
|
import {
|
||||||
|
ssrClose,
|
||||||
|
ssrCreate,
|
||||||
|
ssrListen,
|
||||||
|
ssrRenderPreloadTag,
|
||||||
|
ssrServeStaticContent
|
||||||
|
} from 'quasar/wrappers'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create your webserver and return its instance.
|
||||||
|
* If needed, prepare your webserver to receive
|
||||||
|
* connect-like middlewares.
|
||||||
|
*
|
||||||
|
* Should NOT be async!
|
||||||
|
*/
|
||||||
|
export const create = ssrCreate((/* { ... } */) => {
|
||||||
|
const app = express()
|
||||||
|
|
||||||
|
// attackers can use this header to detect apps running Express
|
||||||
|
// and then launch specifically-targeted attacks
|
||||||
|
app.disable('x-powered-by')
|
||||||
|
|
||||||
|
// place here any middlewares that
|
||||||
|
// absolutely need to run before anything else
|
||||||
|
if (process.env.PROD) {
|
||||||
|
app.use(compression())
|
||||||
|
}
|
||||||
|
|
||||||
|
return app
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* You need to make the server listen to the indicated port
|
||||||
|
* and return the listening instance or whatever you need to
|
||||||
|
* close the server with.
|
||||||
|
*
|
||||||
|
* The "listenResult" param for the "close()" definition below
|
||||||
|
* is what you return here.
|
||||||
|
*
|
||||||
|
* For production, you can instead export your
|
||||||
|
* handler for serverless use or whatever else fits your needs.
|
||||||
|
*/
|
||||||
|
export const listen = ssrListen(async ({ app, port, isReady }) => {
|
||||||
|
await isReady()
|
||||||
|
return app.listen(port, () => {
|
||||||
|
if (process.env.PROD) {
|
||||||
|
console.log('Server listening at port ' + port)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should close the server and free up any resources.
|
||||||
|
* Will be used on development only when the server needs
|
||||||
|
* to be rebooted.
|
||||||
|
*
|
||||||
|
* Should you need the result of the "listen()" call above,
|
||||||
|
* you can use the "listenResult" param.
|
||||||
|
*
|
||||||
|
* Can be async.
|
||||||
|
*/
|
||||||
|
export const close = ssrClose(({ listenResult }) => {
|
||||||
|
return listenResult.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
const maxAge = process.env.DEV
|
||||||
|
? 0
|
||||||
|
: 1000 * 60 * 60 * 24 * 30
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should return middleware that serves the indicated path
|
||||||
|
* with static content.
|
||||||
|
*/
|
||||||
|
export const serveStaticContent = ssrServeStaticContent((path, opts) => {
|
||||||
|
return express.static(path, {
|
||||||
|
maxAge,
|
||||||
|
...opts
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const jsRE = /\.js$/
|
||||||
|
const cssRE = /\.css$/
|
||||||
|
const woffRE = /\.woff$/
|
||||||
|
const woff2RE = /\.woff2$/
|
||||||
|
const gifRE = /\.gif$/
|
||||||
|
const jpgRE = /\.jpe?g$/
|
||||||
|
const pngRE = /\.png$/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should return a String with HTML output
|
||||||
|
* (if any) for preloading indicated file
|
||||||
|
*/
|
||||||
|
export const renderPreloadTag = ssrRenderPreloadTag((file) => {
|
||||||
|
if (jsRE.test(file) === true) {
|
||||||
|
return `<link rel="modulepreload" href="${file}" crossorigin>`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cssRE.test(file) === true) {
|
||||||
|
return `<link rel="stylesheet" href="${file}">`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (woffRE.test(file) === true) {
|
||||||
|
return `<link rel="preload" href="${file}" as="font" type="font/woff" crossorigin>`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (woff2RE.test(file) === true) {
|
||||||
|
return `<link rel="preload" href="${file}" as="font" type="font/woff2" crossorigin>`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gifRE.test(file) === true) {
|
||||||
|
return `<link rel="preload" href="${file}" as="image" type="image/gif">`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jpgRE.test(file) === true) {
|
||||||
|
return `<link rel="preload" href="${file}" as="image" type="image/jpeg">`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pngRE.test(file) === true) {
|
||||||
|
return `<link rel="preload" href="${file}" as="image" type="image/png">`
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
|
})
|
10
src-ssr/ssr-flag.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
// THIS FEATURE-FLAG FILE IS AUTOGENERATED,
|
||||||
|
// REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING
|
||||||
|
import "quasar/dist/types/feature-flag";
|
||||||
|
|
||||||
|
declare module "quasar/dist/types/feature-flag" {
|
||||||
|
interface QuasarFeatureFlags {
|
||||||
|
ssr: true;
|
||||||
|
}
|
||||||
|
}
|
13
src/App.vue
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<n-config-provider :locale="zhCN">
|
||||||
|
<router-view />
|
||||||
|
</n-config-provider>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { NConfigProvider } from "naive-ui";
|
||||||
|
import { zhCN } from "naive-ui";
|
||||||
|
defineOptions({
|
||||||
|
name: "App",
|
||||||
|
});
|
||||||
|
</script>
|
103
src/api/index.js
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
// src/api/index.js
|
||||||
|
import { api } from "../boot/axios";
|
||||||
|
|
||||||
|
export class UserApi {
|
||||||
|
static async login(data) {
|
||||||
|
return api.post("/api/ai/user/login", data);
|
||||||
|
}
|
||||||
|
// 获取注册验证码
|
||||||
|
static async getRegisterCode(data) {
|
||||||
|
return api.post("/api/user/send/msg", data);
|
||||||
|
}
|
||||||
|
// 注册
|
||||||
|
static async register(data) {
|
||||||
|
return api.post("/api/ai/user/register", data);
|
||||||
|
}
|
||||||
|
// 获取登录验证码
|
||||||
|
static async getLoginCode(data) {
|
||||||
|
return api.post("/api/ai/user/login/msg", data);
|
||||||
|
}
|
||||||
|
// 重置密码
|
||||||
|
static async resetPassword(data) {
|
||||||
|
return api.post("/api/ai/user/forget/pwd", data);
|
||||||
|
}
|
||||||
|
// 忘记密码验证码
|
||||||
|
static async getForgetPwdCode(data) {
|
||||||
|
return api.post("/api/ai/user/forget/msg", data);
|
||||||
|
}
|
||||||
|
// 获取用户信息
|
||||||
|
static async getUserInfo(data) {
|
||||||
|
return api.post("/api/ai/user/info", data);
|
||||||
|
}
|
||||||
|
// 修改昵称 和 头像 密码
|
||||||
|
static async updateUserInfo(data) {
|
||||||
|
return api.post("/api/ai/user/update", data);
|
||||||
|
}
|
||||||
|
// 修改手机号验证码
|
||||||
|
static async getUpdatePhoneCode(data) {
|
||||||
|
return api.post("/api/ai/user/phone/update/send", data);
|
||||||
|
}
|
||||||
|
// 修改手机号
|
||||||
|
static async updatePhone(data) {
|
||||||
|
return api.post("/api/ai/user/phone/update", data);
|
||||||
|
}
|
||||||
|
// 提交反馈
|
||||||
|
static async submitFeedBack(data) {
|
||||||
|
return api.post("/api/feedback/create", data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 模型相关接口
|
||||||
|
export class ModelApi {
|
||||||
|
// 获取模型列表
|
||||||
|
static async getModelList(data) {
|
||||||
|
return api.post("/api/ai/modules/v2", data);
|
||||||
|
}
|
||||||
|
// 文生图
|
||||||
|
static async txt2img(data) {
|
||||||
|
return api.post("/api/ai/txt2img", data);
|
||||||
|
}
|
||||||
|
// 翻译
|
||||||
|
static async translate(data) {
|
||||||
|
return api.post("/api/ai/gpt/txt/translate", data);
|
||||||
|
}
|
||||||
|
// 美化
|
||||||
|
static async beautify(data) {
|
||||||
|
return api.post("/api/ai/gpt/txt/beaut", data);
|
||||||
|
}
|
||||||
|
// 上传图片
|
||||||
|
static async uploadImage(data) {
|
||||||
|
return api.post("/api/ai/upload-file", data);
|
||||||
|
}
|
||||||
|
// 图生图
|
||||||
|
static async img2img(data) {
|
||||||
|
return api.post("/api/ai/img2img", data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 历史相关
|
||||||
|
export class HistoryApi {
|
||||||
|
// 获取历史记录
|
||||||
|
static async getHistoryList(data) {
|
||||||
|
return api.post("/api/ai/my/images", data);
|
||||||
|
}
|
||||||
|
// 删除历史记录
|
||||||
|
static async deleteHistory(data) {
|
||||||
|
return api.post("/api/ai/image/del/batch", data);
|
||||||
|
}
|
||||||
|
// 批量下载
|
||||||
|
static async downloadHistory(data) {
|
||||||
|
return api.post("/api/ai/image/download/batch", data, {
|
||||||
|
responseType: "blob",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 积分相关
|
||||||
|
export class ScoreApi {
|
||||||
|
// 消费积分列表
|
||||||
|
static async getScore(data) {
|
||||||
|
return api.post("/api/ai/coin/custom/list", data);
|
||||||
|
}
|
||||||
|
// 奖励积分列表
|
||||||
|
static async getScoreList(data) {
|
||||||
|
return api.post("/api/ai/coin/acquire/list", data);
|
||||||
|
}
|
||||||
|
}
|
BIN
src/assets/fonts/aiFonts.ttf
Normal file
BIN
src/assets/image/ai/169.png
Normal file
After Width: | Height: | Size: 817 B |
BIN
src/assets/image/ai/34.png
Normal file
After Width: | Height: | Size: 819 B |
BIN
src/assets/image/ai/43.png
Normal file
After Width: | Height: | Size: 852 B |
BIN
src/assets/image/ai/cirl.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/image/ai/close.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
src/assets/image/ai/img-bg.png
Normal file
After Width: | Height: | Size: 225 KiB |
BIN
src/assets/image/ai/message.png
Normal file
After Width: | Height: | Size: 517 B |
BIN
src/assets/image/ai/plane.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
src/assets/image/ai/plus.png
Normal file
After Width: | Height: | Size: 480 B |
BIN
src/assets/image/ai/ptp.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
src/assets/image/ai/ptpact.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
src/assets/image/ai/run.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
src/assets/image/ai/star.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
src/assets/image/ai/ttp.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
src/assets/image/ai/ttpact.png
Normal file
After Width: | Height: | Size: 9.6 KiB |
BIN
src/assets/image/aicg/bg.png
Normal file
After Width: | Height: | Size: 267 KiB |
BIN
src/assets/image/aicg/box.png
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
src/assets/image/ast.png
Normal file
After Width: | Height: | Size: 1.6 MiB |
BIN
src/assets/image/coin.png
Normal file
After Width: | Height: | Size: 961 B |
BIN
src/assets/image/exit.png
Normal file
After Width: | Height: | Size: 370 B |
BIN
src/assets/image/glass.png
Normal file
After Width: | Height: | Size: 782 KiB |
BIN
src/assets/image/henshin.png
Normal file
After Width: | Height: | Size: 113 KiB |
BIN
src/assets/image/login/forgetpwd.png
Normal file
After Width: | Height: | Size: 53 KiB |
BIN
src/assets/image/login/regist.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
src/assets/image/login/success.png
Normal file
After Width: | Height: | Size: 56 KiB |
BIN
src/assets/image/login/succicon.png
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
src/assets/image/login/title.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
src/assets/image/login/warning.png
Normal file
After Width: | Height: | Size: 951 B |
BIN
src/assets/image/logo.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
src/assets/image/mine-bg.png
Normal file
After Width: | Height: | Size: 395 KiB |
BIN
src/assets/image/oldman.png
Normal file
After Width: | Height: | Size: 4.5 MiB |
BIN
src/assets/image/patato.png
Normal file
After Width: | Height: | Size: 3.1 MiB |
BIN
src/assets/image/per.png
Normal file
After Width: | Height: | Size: 723 B |
BIN
src/assets/image/train.png
Normal file
After Width: | Height: | Size: 179 KiB |
15
src/assets/quasar-logo-vertical.svg
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 356 360">
|
||||||
|
<path
|
||||||
|
d="M43.4 303.4c0 3.8-2.3 6.3-7.1 6.3h-15v-22h14.4c4.3 0 6.2 2.2 6.2 5.2 0 2.6-1.5 4.4-3.4 5 2.8.4 4.9 2.5 4.9 5.5zm-8-13H24.1v6.9H35c2.1 0 4-1.3 4-3.8 0-2.2-1.3-3.1-3.7-3.1zm5.1 12.6c0-2.3-1.8-3.7-4-3.7H24.2v7.7h11.7c3.4 0 4.6-1.8 4.6-4zm36.3 4v2.7H56v-22h20.6v2.7H58.9v6.8h14.6v2.3H58.9v7.5h17.9zm23-5.8v8.5H97v-8.5l-11-13.4h3.4l8.9 11 8.8-11h3.4l-10.8 13.4zm19.1-1.8V298c0-7.9 5.2-10.7 12.7-10.7 7.5 0 13 2.8 13 10.7v1.4c0 7.9-5.5 10.8-13 10.8s-12.7-3-12.7-10.8zm22.7 0V298c0-5.7-3.9-8-10-8-6 0-9.8 2.3-9.8 8v1.4c0 5.8 3.8 8.1 9.8 8.1 6 0 10-2.3 10-8.1zm37.2-11.6v21.9h-2.9l-15.8-17.9v17.9h-2.8v-22h3l15.6 18v-18h2.9zm37.9 10.2v1.3c0 7.8-5.2 10.4-12.4 10.4H193v-22h11.2c7.2 0 12.4 2.8 12.4 10.3zm-3 0c0-5.3-3.3-7.6-9.4-7.6h-8.4V307h8.4c6 0 9.5-2 9.5-7.7V298zm50.8-7.6h-9.7v19.3h-3v-19.3h-9.7v-2.6h22.4v2.6zm34.4-2.6v21.9h-3v-10.1h-16.8v10h-2.8v-21.8h2.8v9.2H296v-9.2h2.9zm34.9 19.2v2.7h-20.7v-22h20.6v2.7H316v6.8h14.5v2.3H316v7.5h17.8zM24 340.2v7.3h13.9v2.4h-14v9.6H21v-22h20v2.7H24zm41.5 11.4h-9.8v7.9H53v-22h13.3c5.1 0 8 1.9 8 6.8 0 3.7-2 6.3-5.6 7l6 8.2h-3.3l-5.8-8zm-9.8-2.6H66c3.1 0 5.3-1.5 5.3-4.7 0-3.3-2.2-4.1-5.3-4.1H55.7v8.8zm47.9 6.2H89l-2 4.3h-3.2l10.7-22.2H98l10.7 22.2h-3.2l-2-4.3zm-1-2.3l-6.3-13-6 13h12.2zm46.3-15.3v21.9H146v-17.2L135.7 358h-2.1l-10.2-15.6v17h-2.8v-21.8h3l11 16.9 11.3-17h3zm35 19.3v2.6h-20.7v-22h20.6v2.7H166v6.8h14.5v2.3H166v7.6h17.8zm47-19.3l-8.3 22h-3l-7.1-18.6-7 18.6h-3l-8.2-22h3.3L204 356l6.8-18.5h3.4L221 356l6.6-18.5h3.3zm10 11.6v-1.4c0-7.8 5.2-10.7 12.7-10.7 7.6 0 13 2.9 13 10.7v1.4c0 7.9-5.4 10.8-13 10.8-7.5 0-12.7-3-12.7-10.8zm22.8 0v-1.4c0-5.7-4-8-10-8s-9.9 2.3-9.9 8v1.4c0 5.8 3.8 8.2 9.8 8.2 6.1 0 10-2.4 10-8.2zm28.3 2.4h-9.8v7.9h-2.8v-22h13.2c5.2 0 8 1.9 8 6.8 0 3.7-2 6.3-5.6 7l6 8.2h-3.3l-5.8-8zm-9.8-2.6h10.2c3 0 5.2-1.5 5.2-4.7 0-3.3-2.1-4.1-5.2-4.1h-10.2v8.8zm40.3-1.5l-6.8 5.6v6.4h-2.9v-22h2.9v12.3l15.2-12.2h3.7l-9.9 8.1 10.3 13.8h-3.6l-8.9-12z" />
|
||||||
|
<path fill="#050A14"
|
||||||
|
d="M188.4 71.7a10.4 10.4 0 01-20.8 0 10.4 10.4 0 1120.8 0zM224.2 45c-2.2-3.9-5-7.5-8.2-10.7l-12 7c-3.7-3.2-8-5.7-12.6-7.3a49.4 49.4 0 00-9.7 13.9 59 59 0 0140.1 14l7.6-4.4a57 57 0 00-5.2-12.5zM178 125.1c4.5 0 9-.6 13.4-1.7v-14a40 40 0 0012.5-7.2 47.7 47.7 0 00-7.1-15.3 59 59 0 01-32.2 27.7v8.7c4.4 1.2 8.9 1.8 13.4 1.8zM131.8 45c-2.3 4-4 8.1-5.2 12.5l12 7a40 40 0 000 14.4c5.7 1.5 11.3 2 16.9 1.5a59 59 0 01-8-41.7l-7.5-4.3c-3.2 3.2-6 6.7-8.2 10.6z" />
|
||||||
|
<path fill="#00B4FF"
|
||||||
|
d="M224.2 98.4c2.3-3.9 4-8 5.2-12.4l-12-7a40 40 0 000-14.5c-5.7-1.5-11.3-2-16.9-1.5a59 59 0 018 41.7l7.5 4.4c3.2-3.2 6-6.8 8.2-10.7zm-92.4 0c2.2 4 5 7.5 8.2 10.7l12-7a40 40 0 0012.6 7.3c4-4.1 7.3-8.8 9.7-13.8a59 59 0 01-40-14l-7.7 4.4c1.2 4.3 3 8.5 5.2 12.4zm46.2-80c-4.5 0-9 .5-13.4 1.7V34a40 40 0 00-12.5 7.2c1.5 5.7 4 10.8 7.1 15.4a59 59 0 0132.2-27.7V20a53.3 53.3 0 00-13.4-1.8z" />
|
||||||
|
<path fill="#00B4FF"
|
||||||
|
d="M178 9.2a62.6 62.6 0 11-.1 125.2A62.6 62.6 0 01178 9.2m0-9.2a71.7 71.7 0 100 143.5A71.7 71.7 0 00178 0z" />
|
||||||
|
<path fill="#050A14"
|
||||||
|
d="M96.6 212v4.3c-9.2-.8-15.4-5.8-15.4-17.8V180h4.6v18.4c0 8.6 4 12.6 10.8 13.5zm16-31.9v18.4c0 8.9-4.3 12.8-10.9 13.5v4.4c9.2-.7 15.5-5.6 15.5-18v-18.3h-4.7zM62.2 199v-2.2c0-12.7-8.8-17.4-21-17.4-12.1 0-20.7 4.7-20.7 17.4v2.2c0 12.8 8.6 17.6 20.7 17.6 1.5 0 3-.1 4.4-.3l11.8 6.2 2-3.3-8.2-4-6.4-3.1a32 32 0 01-3.6.2c-9.8 0-16-3.9-16-13.3v-2.2c0-9.3 6.2-13.1 16-13.1 9.9 0 16.3 3.8 16.3 13.1v2.2c0 5.3-2.1 8.7-5.6 10.8l4.8 2.4c3.4-2.8 5.5-7 5.5-13.2zM168 215.6h5.1L156 179.7h-4.8l17 36zM143 205l7.4-15.7-2.4-5-15.1 31.4h5.1l3.3-7h18.3l-1.8-3.7H143zm133.7 10.7h5.2l-17.3-35.9h-4.8l17 36zm-25-10.7l7.4-15.7-2.4-5-15.1 31.4h5.1l3.3-7h18.3l-1.7-3.7h-14.8zm73.8-2.5c6-1.2 9-5.4 9-11.4 0-8-4.5-10.9-12.9-10.9h-21.4v35.5h4.6v-31.3h16.5c5 0 8.5 1.4 8.5 6.7 0 5.2-3.5 7.7-8.5 7.7h-11.4v4.1h10.7l9.3 12.8h5.5l-9.9-13.2zm-117.4 9.9c-9.7 0-14.7-2.5-18.6-6.3l-2.2 3.8c5.1 5 11 6.7 21 6.7 1.6 0 3.1-.1 4.6-.3l-1.9-4h-3zm18.4-7c0-6.4-4.7-8.6-13.8-9.4l-10.1-1c-6.7-.7-9.3-2.2-9.3-5.6 0-2.5 1.4-4 4.6-5l-1.8-3.8c-4.7 1.4-7.5 4.2-7.5 8.9 0 5.2 3.4 8.7 13 9.6l11.3 1.2c6.4.6 8.9 2 8.9 5.4 0 2.7-2.1 4.7-6 5.8l1.8 3.9c5.3-1.6 8.9-4.7 8.9-10zm-20.3-21.9c7.9 0 13.3 1.8 18.1 5.7l1.8-3.9a30 30 0 00-19.6-5.9c-2 0-4 .1-5.7.3l1.9 4 3.5-.2z" />
|
||||||
|
<path fill="#00B4FF"
|
||||||
|
d="M.5 251.9c29.6-.5 59.2-.8 88.8-1l88.7-.3 88.7.3 44.4.4 44.4.6-44.4.6-44.4.4-88.7.3-88.7-.3a7981 7981 0 01-88.8-1z" />
|
||||||
|
<path fill="none" d="M-565.2 324H-252v15.8h-313.2z" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.4 KiB |
0
src/boot/.gitkeep
Normal file
90
src/boot/axios.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import { boot } from "quasar/wrappers";
|
||||||
|
import axios from "axios";
|
||||||
|
import { LocalStorage, SessionStorage } from "quasar";
|
||||||
|
import router from "src/router";
|
||||||
|
import { Notify } from "quasar";
|
||||||
|
// Be careful when using SSR for cross-request state pollution
|
||||||
|
// due to creating a Singleton instance here;
|
||||||
|
// If any client changes this (global) instance, it might be a
|
||||||
|
// good idea to move this instance creation inside of the
|
||||||
|
// "export default () => {}" function below (which runs individually
|
||||||
|
// for each client)
|
||||||
|
const api = axios.create({
|
||||||
|
baseURL: "http://192.168.1.244:8085/", //接口统一域名
|
||||||
|
timeout: 60 * 60 * 10000,
|
||||||
|
authToken: true,
|
||||||
|
responseType: "json", // 请求数据格式
|
||||||
|
});
|
||||||
|
|
||||||
|
export default boot(({ app, router, ssrContext }) => {
|
||||||
|
// for use inside Vue files (Options API) through this.$axios and this.$api
|
||||||
|
api.interceptors.request.use(
|
||||||
|
(config) => {
|
||||||
|
// set header Content-Type
|
||||||
|
if (config.method === "get") {
|
||||||
|
// || config.method === "delete"
|
||||||
|
config.headers["Content-Type"] = "application/x-www-form-urlencoded";
|
||||||
|
} else {
|
||||||
|
config.headers["Content-Type"] = "application/json";
|
||||||
|
}
|
||||||
|
if (config.isFormData) {
|
||||||
|
config.headers["Content-Type"] = "multipart/form-data";
|
||||||
|
}
|
||||||
|
config.headers.Authorization = LocalStorage.getItem("sd-token") || "";
|
||||||
|
console.log(config);
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
api.interceptors.response.use(
|
||||||
|
(response) => {
|
||||||
|
// 对响应数据做些事
|
||||||
|
let isResourse = response.config.responseType;
|
||||||
|
if (response && response.data.status === 401) {
|
||||||
|
Notify.create({
|
||||||
|
message: "登录过期,请重新登录",
|
||||||
|
type: "negative",
|
||||||
|
position: "top",
|
||||||
|
});
|
||||||
|
// 删除Localstorage
|
||||||
|
LocalStorage.remove("sd-token");
|
||||||
|
LocalStorage.remove("sd-accountInfo");
|
||||||
|
router.push("/login");
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
response.status === 200 ||
|
||||||
|
response.status === 201 ||
|
||||||
|
response.status === 204
|
||||||
|
) {
|
||||||
|
return isResourse == "blob" ? response : response.data;
|
||||||
|
} else {
|
||||||
|
Notify.create({
|
||||||
|
message: "系统繁忙,请稍后",
|
||||||
|
type: "negative",
|
||||||
|
position: "top",
|
||||||
|
});
|
||||||
|
Promise.reject(response);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
Notify.create({
|
||||||
|
message: "系统繁忙,请稍后",
|
||||||
|
type: "negative",
|
||||||
|
position: "top",
|
||||||
|
});
|
||||||
|
// 响应错误时做些事
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
app.config.globalProperties.$axios = axios;
|
||||||
|
// ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form)
|
||||||
|
// so you won't necessarily have to import axios in each vue file
|
||||||
|
|
||||||
|
app.config.globalProperties.$api = api;
|
||||||
|
// ^ ^ ^ this will allow you to use this.$api (for Vue Options API form)
|
||||||
|
// so you can easily perform requests against your app's API
|
||||||
|
});
|
||||||
|
|
||||||
|
export { api };
|
14
src/boot/i18n.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { boot } from 'quasar/wrappers'
|
||||||
|
import { createI18n } from 'vue-i18n'
|
||||||
|
import messages from 'src/i18n'
|
||||||
|
|
||||||
|
export default boot(({ app }) => {
|
||||||
|
const i18n = createI18n({
|
||||||
|
locale: 'zh-CN',
|
||||||
|
globalInjection: true,
|
||||||
|
messages
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set i18n instance on app
|
||||||
|
app.use(i18n)
|
||||||
|
})
|
12
src/boot/lazyLoad.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { boot } from 'quasar/wrappers'
|
||||||
|
import Lazyload from "vue3-lazyload";
|
||||||
|
|
||||||
|
export default boot(({ app }) => {
|
||||||
|
|
||||||
|
|
||||||
|
// Set i18n instance on app
|
||||||
|
app.use(Lazyload, {
|
||||||
|
loading: "@/assets/images/default.png",//可以指定加载中的图像
|
||||||
|
error: "@/assets/images/err.png",//可以指定加载失败的图像
|
||||||
|
});
|
||||||
|
})
|
53
src/boot/naiveUI.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { boot } from "quasar/wrappers";
|
||||||
|
import {
|
||||||
|
// create naive ui
|
||||||
|
create,
|
||||||
|
NAvatar,
|
||||||
|
// component
|
||||||
|
NButton,
|
||||||
|
NCard,
|
||||||
|
NConfigProvider,
|
||||||
|
NDataTable,
|
||||||
|
NDatePicker,
|
||||||
|
NFlex,
|
||||||
|
NGrid,
|
||||||
|
NGridItem,
|
||||||
|
NInput,
|
||||||
|
NInputNumber,
|
||||||
|
NMenu,
|
||||||
|
NMessageProvider,
|
||||||
|
NModal,
|
||||||
|
NPagination,
|
||||||
|
NPopover,
|
||||||
|
NSelect,
|
||||||
|
NSpin,
|
||||||
|
NUpload,
|
||||||
|
NUploadDragger,
|
||||||
|
} from "naive-ui";
|
||||||
|
|
||||||
|
export default boot(({ app }) => {
|
||||||
|
const naive = create({
|
||||||
|
components: [
|
||||||
|
NButton,
|
||||||
|
NInput,
|
||||||
|
NInputNumber,
|
||||||
|
NSelect,
|
||||||
|
NSpin,
|
||||||
|
NMessageProvider,
|
||||||
|
NPopover,
|
||||||
|
NUpload,
|
||||||
|
NUploadDragger,
|
||||||
|
NModal,
|
||||||
|
NCard,
|
||||||
|
NGrid,
|
||||||
|
NGridItem,
|
||||||
|
NDatePicker,
|
||||||
|
NConfigProvider,
|
||||||
|
NMenu,
|
||||||
|
NAvatar,
|
||||||
|
NDataTable,
|
||||||
|
NPagination
|
||||||
|
],
|
||||||
|
});
|
||||||
|
app.use(naive);
|
||||||
|
});
|
48
src/components/EssentialLink.vue
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<template>
|
||||||
|
<q-item
|
||||||
|
clickable
|
||||||
|
tag="a"
|
||||||
|
target="_blank"
|
||||||
|
:href="props.link"
|
||||||
|
>
|
||||||
|
<q-item-section
|
||||||
|
v-if="props.icon"
|
||||||
|
avatar
|
||||||
|
>
|
||||||
|
<q-icon :name="props.icon" />
|
||||||
|
</q-item-section>
|
||||||
|
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>{{ props.title }}</q-item-label>
|
||||||
|
<q-item-label caption>{{ props.caption }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
defineOptions({
|
||||||
|
name: 'EssentialLink'
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
|
||||||
|
caption: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
|
||||||
|
link: {
|
||||||
|
type: String,
|
||||||
|
default: '#'
|
||||||
|
},
|
||||||
|
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
63
src/css/app.scss
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
.q-notification {
|
||||||
|
background: linear-gradient(260deg, #c448b8, #591df5) !important;
|
||||||
|
}
|
||||||
|
.n-popover-arrow {
|
||||||
|
background: #383838 !important;
|
||||||
|
}
|
||||||
|
.n-popover {
|
||||||
|
background: #383838 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
font-weight: bold !important;
|
||||||
|
}
|
||||||
|
.n-modal {
|
||||||
|
height: 820px;
|
||||||
|
background: #212121 !important;
|
||||||
|
}
|
||||||
|
.n-card > .n-card-header .n-card-header__main {
|
||||||
|
color: #fff !important;
|
||||||
|
font-weight: bold !important;
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
.n-card-header {
|
||||||
|
border-bottom: 1px solid !important;
|
||||||
|
margin-left: 40px;
|
||||||
|
margin-right: 40px;
|
||||||
|
}
|
||||||
|
.n-date-panel {
|
||||||
|
background: #19191a !important;
|
||||||
|
}
|
||||||
|
.n-date-panel-date {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
.n-date-panel-weekdays__day {
|
||||||
|
color: #b9b9b9 !important;
|
||||||
|
}
|
||||||
|
.n-date-panel
|
||||||
|
.n-date-panel-dates
|
||||||
|
.n-date-panel-date.n-date-panel-date--covered:not(
|
||||||
|
.n-date-panel-date--excluded
|
||||||
|
)::before {
|
||||||
|
background-color: rgba(195, 72, 184, 0.2) !important;
|
||||||
|
}
|
||||||
|
.n-date-panel
|
||||||
|
.n-date-panel-dates
|
||||||
|
.n-date-panel-date.n-date-panel-date--selected::after {
|
||||||
|
background-color: rgba(195, 72, 184, 1);
|
||||||
|
}
|
||||||
|
.v-vl-items {
|
||||||
|
background: #19191a !important;
|
||||||
|
}
|
||||||
|
.n-base-select-option__content {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
.n-select-menu {
|
||||||
|
--n-option-color-pending: gray !important;
|
||||||
|
--n-option-color-active: gray !important;
|
||||||
|
--n-option-color-active-pending: gray !important;
|
||||||
|
}
|
||||||
|
.n-input {
|
||||||
|
--n-color-focus: #383838 !important;
|
||||||
|
}
|
||||||
|
.n-base-selection-label {
|
||||||
|
--n-color-active: #383838 !important;
|
||||||
|
}
|
26
src/css/quasar.variables.scss
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Quasar SCSS (& Sass) Variables
|
||||||
|
// --------------------------------------------------
|
||||||
|
// To customize the look and feel of this app, you can override
|
||||||
|
// the Sass/SCSS variables found in Quasar's source Sass/SCSS files.
|
||||||
|
|
||||||
|
// Check documentation for full list of Quasar variables
|
||||||
|
|
||||||
|
// Your own variables (that are declared here) and Quasar's own
|
||||||
|
// ones will be available out of the box in your .vue/.scss/.sass files
|
||||||
|
|
||||||
|
// It's highly recommended to change the default colors
|
||||||
|
// to match your app's branding.
|
||||||
|
// Tip: Use the "Theme Builder" on Quasar's documentation website.
|
||||||
|
|
||||||
|
$primary : #1976D2;
|
||||||
|
$secondary : #26A69A;
|
||||||
|
$accent : #9C27B0;
|
||||||
|
|
||||||
|
$dark : #1D1D1D;
|
||||||
|
$dark-page : #121212;
|
||||||
|
|
||||||
|
$positive : #21BA45;
|
||||||
|
$negative : #C10015;
|
||||||
|
$info : #31CCEC;
|
||||||
|
$warning : #F2C037;
|
||||||
|
|
7
src/i18n/en-US/index.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// This is just an example,
|
||||||
|
// so you can safely delete all default props below
|
||||||
|
|
||||||
|
export default {
|
||||||
|
failed: 'Action failed',
|
||||||
|
success: 'Action was successful'
|
||||||
|
}
|
5
src/i18n/index.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import enUS from './en-US'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
'en-US': enUS
|
||||||
|
}
|
226
src/layouts/MainLayout.vue
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
<template>
|
||||||
|
<q-layout view="lhh LpR lff" class="bg-grey-1">
|
||||||
|
<q-header :reveal="true" class="text-white" style="background: #212121">
|
||||||
|
<q-toolbar class="q-py-sm q-px-md">
|
||||||
|
<img class="logo" src="../assets/image/logo.png" />
|
||||||
|
<div
|
||||||
|
class="GL__toolbar-link q-ml-xs q-gutter-md text-body2 text-weight-bold row items-center no-wrap"
|
||||||
|
>
|
||||||
|
<q-tabs inline-label class="q-pl-xl q-pr-xl aiFonts" align="justify">
|
||||||
|
<q-route-tab to="/" label="AI生图" />
|
||||||
|
<q-route-tab to="/history" name="history" label="生成历史" />
|
||||||
|
<q-route-tab to="/aicg" name="acg" label="AIGC认证" />
|
||||||
|
</q-tabs>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<q-space />
|
||||||
|
|
||||||
|
<div class="q-pl-sm q-gutter-sm row items-center no-wrap">
|
||||||
|
<div
|
||||||
|
class="q-pl-sm q-gutter-sm row items-center no-wrap cursor-pointer"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
style="width: 17px; height: auto"
|
||||||
|
src="../assets/image/ai/message.png"
|
||||||
|
/>
|
||||||
|
<span @click="goFeedBack" class="msg">留言反馈</span>
|
||||||
|
</div>
|
||||||
|
<q-btn dense flat no-wrap>
|
||||||
|
<q-avatar size="40px">
|
||||||
|
<img :src="user.avatar" />
|
||||||
|
</q-avatar>
|
||||||
|
<q-icon name="arrow_drop_down" size="16px" />
|
||||||
|
<q-menu auto-close>
|
||||||
|
<div class="pop-box">
|
||||||
|
<div class="userInfo">
|
||||||
|
<img :src="user.avatar" />
|
||||||
|
<div style="margin-bottom: 10px">
|
||||||
|
<div class="user-name">{{ user.nickName }}</div>
|
||||||
|
<div class="phone">{{ user.telNum }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<n-button class="create" quaternary>
|
||||||
|
<div style="color: #fff">
|
||||||
|
剩余积分<span style="margin-left: 30px">{{
|
||||||
|
user.coin
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
</n-button>
|
||||||
|
<div class="list" @click="goDetail">
|
||||||
|
<div style="display: flex; align-items: center">
|
||||||
|
<img src="../assets/image/coin.png" alt="设置" />
|
||||||
|
<span style="margin-left: 5px">积分明细</span>
|
||||||
|
</div>
|
||||||
|
<span> > </span>
|
||||||
|
</div>
|
||||||
|
<div class="list" @click="goMine">
|
||||||
|
<div style="display: flex; align-items: center">
|
||||||
|
<img src="../assets/image/per.png" alt="设置" />
|
||||||
|
<span style="margin-left: 5px">个人中心</span>
|
||||||
|
</div>
|
||||||
|
<span> > </span>
|
||||||
|
</div>
|
||||||
|
<div class="log-out" @click="logOut">
|
||||||
|
<img src="../assets/image/exit.png" alt="设置" />
|
||||||
|
<span style="margin-left: 5px">退出登录</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-menu>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
</q-toolbar>
|
||||||
|
</q-header>
|
||||||
|
|
||||||
|
<q-page-container>
|
||||||
|
<router-view />
|
||||||
|
</q-page-container>
|
||||||
|
</q-layout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { LocalStorage, SessionStorage } from "quasar";
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
const tab = ref("ai");
|
||||||
|
const router = useRouter();
|
||||||
|
const user = LocalStorage.getItem("sd-accountInfo") || {};
|
||||||
|
const logOut = () => {
|
||||||
|
LocalStorage.remove("sd-accountInfo");
|
||||||
|
LocalStorage.remove("sd-token");
|
||||||
|
window.location.href = "/#/login";
|
||||||
|
};
|
||||||
|
const goMine = () => {
|
||||||
|
router.push("/mine?active=mineCenter");
|
||||||
|
};
|
||||||
|
const goDetail = () => {
|
||||||
|
router.push("/mine?active=pointDetail");
|
||||||
|
};
|
||||||
|
const goFeedBack = () => {
|
||||||
|
router.push("/feedback");
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@font-face {
|
||||||
|
font-family: "aiFonts";
|
||||||
|
src: url("../assets/fonts/aiFonts.ttf") format("truetype");
|
||||||
|
font-weight: lighter !important;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
.aiFonts {
|
||||||
|
font-family: "aiFonts";
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
height: 40px;
|
||||||
|
width: auto;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
.q-tab {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
.msg {
|
||||||
|
font-size: 14px;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
.pop-box {
|
||||||
|
width: 310px;
|
||||||
|
padding: 0px 27px 15px 27px;
|
||||||
|
height: 330px;
|
||||||
|
background: #19191a;
|
||||||
|
.userInfo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
img {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
.user-name {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #fff;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.phone {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #bebebe;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
img {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #bebebe;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.log-out {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 1px solid #707070;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
img {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #C348B8;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.create {
|
||||||
|
margin-top: 10px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
height: 44px;
|
||||||
|
background: linear-gradient(260deg, #c448b8, #591df5);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: bolder;
|
||||||
|
font-size: 18px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
:deep(.q-tab--active .q-tab__indicator) {
|
||||||
|
opacity: 1;
|
||||||
|
width: 40px;
|
||||||
|
left: 39%;
|
||||||
|
height: 5px;
|
||||||
|
background: linear-gradient(to right, #be45ba, #5d1ef1);
|
||||||
|
}
|
||||||
|
:deep(.q-tab--active .q-tab__label) {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: lighter;
|
||||||
|
text-shadow: 0px 3px 4px #de53ab;
|
||||||
|
}
|
||||||
|
:deep(.q-tab--inactive .q-tab__label) {
|
||||||
|
color: #bebebe;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: lighter;
|
||||||
|
}
|
||||||
|
:deep(.q-menu) {
|
||||||
|
left: 1586px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
29
src/pages/ErrorNotFound.vue
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<template>
|
||||||
|
<div class="fullscreen bg-blue text-white text-center q-pa-md flex flex-center">
|
||||||
|
<div>
|
||||||
|
<div style="font-size: 30vh">
|
||||||
|
404
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-h2" style="opacity:.4">
|
||||||
|
Oops. Nothing here...
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
class="q-mt-xl"
|
||||||
|
color="white"
|
||||||
|
text-color="blue"
|
||||||
|
unelevated
|
||||||
|
to="/"
|
||||||
|
label="Go Home"
|
||||||
|
no-caps
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
defineOptions({
|
||||||
|
name: 'ErrorNotFound'
|
||||||
|
});
|
||||||
|
</script>
|
21
src/pages/IndexPage.vue
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<template>
|
||||||
|
<q-page class="flex flex-center">
|
||||||
|
<h5>Click emojis to remove them.</h5>
|
||||||
|
<ul v-auto-animate>
|
||||||
|
<li v-for="item in items" :key="item" @click="removeItem(item)">
|
||||||
|
{{ item }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</q-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
const items = ref(["😏","😐","😑","😒","😕" ])
|
||||||
|
function removeItem(toRemove) {
|
||||||
|
items.value = items.value.filter((item) => item !== toRemove)
|
||||||
|
}
|
||||||
|
defineOptions({
|
||||||
|
name: 'IndexPage'
|
||||||
|
});
|
||||||
|
</script>
|
958
src/pages/Login.vue
Normal file
@ -0,0 +1,958 @@
|
|||||||
|
<template>
|
||||||
|
<div class="fullscreen">
|
||||||
|
<div class="fullscreen out-box">
|
||||||
|
<div class="bg">
|
||||||
|
<div
|
||||||
|
class="carousel"
|
||||||
|
:id="'silder' + (index + 1)"
|
||||||
|
v-for="(item, index) in 7"
|
||||||
|
>
|
||||||
|
<div class="carousel-track-container">
|
||||||
|
<ul class="carousel-track" :id="'silder-track' + (index + 1)">
|
||||||
|
<li class="carousel-slide">
|
||||||
|
<img src="../assets/image/train.png" />
|
||||||
|
</li>
|
||||||
|
<li class="carousel-slide">
|
||||||
|
<img src="../assets/image/glass.png" />
|
||||||
|
</li>
|
||||||
|
<li class="carousel-slide">
|
||||||
|
<img src="../assets/image/ast.png" />
|
||||||
|
</li>
|
||||||
|
<li class="carousel-slide">
|
||||||
|
<img src="../assets/image/oldman.png" />
|
||||||
|
</li>
|
||||||
|
<li class="carousel-slide">
|
||||||
|
<img src="../assets/image/patato.png" />
|
||||||
|
</li>
|
||||||
|
<li class="carousel-slide">
|
||||||
|
<img src="../assets/image/henshin.png" />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div :class="{ 're-item': true, 'is-flipped': isFlipped }">
|
||||||
|
<div class="login-box">
|
||||||
|
<img class="title" src="../assets/image/login/title.png" />
|
||||||
|
<div class="sub-title">登录得积分,畅享免费素材</div>
|
||||||
|
<q-tabs
|
||||||
|
v-model="tab"
|
||||||
|
inline-label
|
||||||
|
class="bg-black q-pl-xl q-pr-xl"
|
||||||
|
align="justify"
|
||||||
|
>
|
||||||
|
<q-tab name="acaount" label="账号登录" />
|
||||||
|
<q-tab name="code" label="验证码登录" />
|
||||||
|
</q-tabs>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
v-show="isErr"
|
||||||
|
style="display: flex; align-items: center; justify-content: center"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
style="width: 22px; height: 18px; margin-right: 5px"
|
||||||
|
src="../assets/image/login/warning.png"
|
||||||
|
/>
|
||||||
|
<span style="color: #e0378c">{{ errMsg }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="tab === 'acaount'">
|
||||||
|
<div class="account-box">
|
||||||
|
<span class="account">账号</span>
|
||||||
|
<q-input
|
||||||
|
class="input-box"
|
||||||
|
v-model="telNum"
|
||||||
|
:dense="true"
|
||||||
|
placeholder="请输入账号"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="account-box">
|
||||||
|
<span class="account">密码</span>
|
||||||
|
<q-input
|
||||||
|
:type="isPwd ? 'password' : 'text'"
|
||||||
|
maxlength="8"
|
||||||
|
class="input-box"
|
||||||
|
v-model="password"
|
||||||
|
:dense="true"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
>
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon
|
||||||
|
color="grey-13"
|
||||||
|
:name="isPwd ? 'visibility_off' : 'visibility'"
|
||||||
|
class="cursor-pointer"
|
||||||
|
@click="isPwd = !isPwd"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
<div class="forget" @click="forgetPwd">忘记密码?</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="tab === 'code'">
|
||||||
|
<div class="account-box">
|
||||||
|
<span class="account" style="transform: translateX(-100%)"
|
||||||
|
>手机号</span
|
||||||
|
>
|
||||||
|
<q-input
|
||||||
|
class="input-box"
|
||||||
|
v-model="telNum"
|
||||||
|
:dense="true"
|
||||||
|
placeholder="请输入账号"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="account-box">
|
||||||
|
<span class="account" style="transform: translateX(-100%)"
|
||||||
|
>验证码</span
|
||||||
|
>
|
||||||
|
<q-input
|
||||||
|
style="width: 250px"
|
||||||
|
:type="isPwd ? 'password' : 'text'"
|
||||||
|
class="input-box"
|
||||||
|
v-model="code"
|
||||||
|
:dense="true"
|
||||||
|
placeholder="请输入账号"
|
||||||
|
>
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon
|
||||||
|
color="grey-13"
|
||||||
|
:name="isPwd ? 'visibility_off' : 'visibility'"
|
||||||
|
class="cursor-pointer"
|
||||||
|
@click="isPwd = !isPwd"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
<q-btn
|
||||||
|
class="code-btn"
|
||||||
|
@click="getLoginCode"
|
||||||
|
:loading="codeLoading"
|
||||||
|
:disabled="loginRestSec < 60"
|
||||||
|
>
|
||||||
|
<div v-if="loginRestSec === 60 || loginRestSec < 1">
|
||||||
|
获取验证码
|
||||||
|
</div>
|
||||||
|
<div v-else>{{ loginRestSec }}</div>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
<div class="forget"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="read">
|
||||||
|
<q-checkbox v-model="readed" />
|
||||||
|
<a>我已阅读并同意</a>
|
||||||
|
<span style="color: #c348b8"> 《用户协议》</span>
|
||||||
|
<span style="color: #c348b8">《隐私政策》</span>
|
||||||
|
</div>
|
||||||
|
<q-btn
|
||||||
|
label="登录"
|
||||||
|
class="login-btn"
|
||||||
|
:loading="loading"
|
||||||
|
text-color="white"
|
||||||
|
@click="login"
|
||||||
|
/>
|
||||||
|
<div class="regist">
|
||||||
|
<a>去</a>
|
||||||
|
<span style="color: #c348b8" @click="toggleFlip">注册</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="regist-box">
|
||||||
|
<div v-if="registType === 1">
|
||||||
|
<img class="title" src="../assets/image/login/regist.png" />
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
v-show="isErr"
|
||||||
|
style="
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
style="width: 22px; height: 18px; margin-right: 5px"
|
||||||
|
src="../assets/image/login/warning.png"
|
||||||
|
/>
|
||||||
|
<span style="color: #e0378c">{{ errMsg }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="account-box">
|
||||||
|
<span class="account" style="transform: translateX(-100%)"
|
||||||
|
>手机号</span
|
||||||
|
>
|
||||||
|
<q-input
|
||||||
|
class="input-box"
|
||||||
|
v-model="telNum"
|
||||||
|
:dense="true"
|
||||||
|
placeholder="请输入账号"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="account-box">
|
||||||
|
<span class="account" style="transform: translateX(-100%)"
|
||||||
|
>验证码</span
|
||||||
|
>
|
||||||
|
<q-input
|
||||||
|
style="width: 238px"
|
||||||
|
:type="isPwd ? 'password' : 'text'"
|
||||||
|
class="input-box"
|
||||||
|
v-model="code"
|
||||||
|
:dense="true"
|
||||||
|
placeholder="请输入验证码"
|
||||||
|
>
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon
|
||||||
|
color="grey-13"
|
||||||
|
:name="isPwd ? 'visibility_off' : 'visibility'"
|
||||||
|
class="cursor-pointer"
|
||||||
|
@click="isPwd = !isPwd"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
<q-btn
|
||||||
|
class="code-btn"
|
||||||
|
:loading="codeLoading"
|
||||||
|
:disabled="restSec < 60"
|
||||||
|
@click="getCode"
|
||||||
|
style="margin-right: 10px"
|
||||||
|
>
|
||||||
|
<div v-if="restSec === 60 || restSec < 1">获取验证码</div>
|
||||||
|
<div v-else>{{ restSec }}</div>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
<div class="account-box">
|
||||||
|
<span class="account" style="transform: translateX(-160%)"
|
||||||
|
>密码</span
|
||||||
|
>
|
||||||
|
<q-input
|
||||||
|
:type="isPwd ? 'password' : 'text'"
|
||||||
|
class="input-box"
|
||||||
|
v-model="password"
|
||||||
|
:dense="true"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
maxlength="8"
|
||||||
|
style="margin-left: 10px"
|
||||||
|
>
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon
|
||||||
|
color="grey-13"
|
||||||
|
:name="isPwd ? 'visibility_off' : 'visibility'"
|
||||||
|
class="cursor-pointer"
|
||||||
|
@click="isPwd = !isPwd"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
<div class="account-box">
|
||||||
|
<span class="account" style="transform: translateX(-45%)"
|
||||||
|
>确认密码</span
|
||||||
|
>
|
||||||
|
<q-input
|
||||||
|
:type="isPwd ? 'password' : 'text'"
|
||||||
|
class="input-box"
|
||||||
|
v-model="verfyPassword"
|
||||||
|
:dense="true"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
maxlength="8"
|
||||||
|
style="margin-right: 18px"
|
||||||
|
>
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon
|
||||||
|
color="grey-13"
|
||||||
|
:name="isPwd ? 'visibility_off' : 'visibility'"
|
||||||
|
class="cursor-pointer"
|
||||||
|
@click="isPwd = !isPwd"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<q-btn
|
||||||
|
:loading="loading"
|
||||||
|
label="确定"
|
||||||
|
class="login-btn"
|
||||||
|
text-color="white"
|
||||||
|
@click="goRegist"
|
||||||
|
style="margin-top: 40px"
|
||||||
|
/>
|
||||||
|
<div class="regist">
|
||||||
|
<a>去</a>
|
||||||
|
<span style="color: #c348b8" @click="toggleFlip">登录</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="registType === 2"
|
||||||
|
style="
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
flex-direction: column;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
style="width: 170px; height: 60px; margin-top: 20px"
|
||||||
|
src="../assets/image/login/success.png"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
style="width: 200px; height: 200px; margin-top: 80px"
|
||||||
|
src="../assets/image/login/succicon.png"
|
||||||
|
/>
|
||||||
|
<q-btn
|
||||||
|
label="返回登录页"
|
||||||
|
class="login-btn"
|
||||||
|
text-color="white"
|
||||||
|
@click="backLogin"
|
||||||
|
style="margin-top: 80px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-if="registType === 3">
|
||||||
|
<img
|
||||||
|
class="title"
|
||||||
|
style="width: 140px"
|
||||||
|
src="../assets/image/login/forgetpwd.png"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
v-show="isErr"
|
||||||
|
style="
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
style="width: 22px; height: 18px; margin-right: 5px"
|
||||||
|
src="../assets/image/login/warning.png"
|
||||||
|
/>
|
||||||
|
<span style="color: #e0378c">{{ errMsg }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="account-box">
|
||||||
|
<span class="account" style="transform: translateX(-100%)"
|
||||||
|
>手机号</span
|
||||||
|
>
|
||||||
|
<q-input
|
||||||
|
class="input-box"
|
||||||
|
v-model="telNum"
|
||||||
|
:dense="true"
|
||||||
|
placeholder="请输入账号"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="account-box">
|
||||||
|
<span class="account" style="transform: translateX(-100%)"
|
||||||
|
>验证码</span
|
||||||
|
>
|
||||||
|
<q-input
|
||||||
|
style="width: 232px"
|
||||||
|
:type="isPwd ? 'password' : 'text'"
|
||||||
|
class="input-box"
|
||||||
|
v-model="code"
|
||||||
|
:dense="true"
|
||||||
|
placeholder="请输入验证码"
|
||||||
|
>
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon
|
||||||
|
color="grey-13"
|
||||||
|
:name="isPwd ? 'visibility_off' : 'visibility'"
|
||||||
|
class="cursor-pointer"
|
||||||
|
@click="isPwd = !isPwd"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
<q-btn
|
||||||
|
class="code-btn"
|
||||||
|
:loading="codeLoading"
|
||||||
|
:disabled="restSec < 60"
|
||||||
|
@click="getRestCode"
|
||||||
|
style="margin-right: 10px"
|
||||||
|
>
|
||||||
|
<div v-if="restSec === 60 || restSec < 1">获取验证码</div>
|
||||||
|
<div v-else>{{ restSec }}</div>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
<div class="account-box">
|
||||||
|
<span class="account" style="transform: translateX(-90%)"
|
||||||
|
>新密码</span
|
||||||
|
>
|
||||||
|
<q-input
|
||||||
|
:type="isPwd ? 'password' : 'text'"
|
||||||
|
class="input-box"
|
||||||
|
v-model="password"
|
||||||
|
:dense="true"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
style="margin-left: 5px"
|
||||||
|
maxlength="8"
|
||||||
|
>
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon
|
||||||
|
color="grey-13"
|
||||||
|
:name="isPwd ? 'visibility_off' : 'visibility'"
|
||||||
|
class="cursor-pointer"
|
||||||
|
@click="isPwd = !isPwd"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
<div class="account-box">
|
||||||
|
<span class="account" style="transform: translateX(-20%)"
|
||||||
|
>确认新密码</span
|
||||||
|
>
|
||||||
|
<q-input
|
||||||
|
:type="isPwd ? 'password' : 'text'"
|
||||||
|
class="input-box"
|
||||||
|
v-model="verfyPassword"
|
||||||
|
:dense="true"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
style="margin-right: 25px"
|
||||||
|
maxlength="8"
|
||||||
|
>
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon
|
||||||
|
color="grey-13"
|
||||||
|
:name="isPwd ? 'visibility_off' : 'visibility'"
|
||||||
|
class="cursor-pointer"
|
||||||
|
@click="isPwd = !isPwd"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<q-btn
|
||||||
|
:loading="loading"
|
||||||
|
label="确定"
|
||||||
|
class="login-btn"
|
||||||
|
text-color="white"
|
||||||
|
@click="goResetPwd"
|
||||||
|
style="margin-top: 40px"
|
||||||
|
/>
|
||||||
|
<div class="regist">
|
||||||
|
<a>去</a>
|
||||||
|
<span style="color: #c348b8" @click="toggleFlip">登录</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from "vue";
|
||||||
|
import { LocalStorage, SessionStorage } from "quasar";
|
||||||
|
import { processError, processSuccess } from "../utils/message";
|
||||||
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
import { UserApi } from "src/api";
|
||||||
|
const router = useRouter();
|
||||||
|
const tab = ref("acaount");
|
||||||
|
const telNum = ref("");
|
||||||
|
const password = ref("");
|
||||||
|
const verfyPassword = ref("");
|
||||||
|
const loading = ref(false);
|
||||||
|
const codeLoading = ref(false);
|
||||||
|
const code = ref("");
|
||||||
|
const restSec = ref(60);
|
||||||
|
const loginRestSec = ref(60);
|
||||||
|
const isErr = ref(false);
|
||||||
|
const errMsg = ref("");
|
||||||
|
const isPwd = ref(true);
|
||||||
|
const readed = ref(false);
|
||||||
|
const registType = ref(1);
|
||||||
|
const isFlipped = ref(false);
|
||||||
|
// 滚动逻辑
|
||||||
|
function startScrolling(carousel, track) {
|
||||||
|
const slides = Array.from(track.children);
|
||||||
|
slides.forEach((slide) => {
|
||||||
|
track.appendChild(slide.cloneNode(true));
|
||||||
|
});
|
||||||
|
let scrollInterval;
|
||||||
|
const scrollSpeed = 1;
|
||||||
|
scrollInterval = setInterval(() => {
|
||||||
|
if (carousel.scrollTop >= track.scrollHeight / 2) {
|
||||||
|
carousel.scrollTop = 0;
|
||||||
|
} else {
|
||||||
|
carousel.scrollTop += scrollSpeed;
|
||||||
|
}
|
||||||
|
}, 20);
|
||||||
|
}
|
||||||
|
// 登录
|
||||||
|
const login = async () => {
|
||||||
|
// 如果没有勾选同意协议
|
||||||
|
if (!readed.value) {
|
||||||
|
errMsg.value = "请先同意用户协议";
|
||||||
|
isErr.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 如果是账号登录,且账号密码为空
|
||||||
|
if (tab.value === "acaount" && (!telNum.value || !password.value)) {
|
||||||
|
errMsg.value = "账号或密码不能为空";
|
||||||
|
isErr.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 如果是验证码登录,且手机号或验证码为空
|
||||||
|
if (tab.value === "code" && (!telNum.value || !password.value)) {
|
||||||
|
errMsg.value = "手机号或验证码不能为空";
|
||||||
|
isErr.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isErr.value = false;
|
||||||
|
errMsg.value = "";
|
||||||
|
loading.value = true;
|
||||||
|
const data = {
|
||||||
|
telNum: telNum.value,
|
||||||
|
};
|
||||||
|
if (tab.value === "acaount") {
|
||||||
|
data.password = password.value;
|
||||||
|
} else {
|
||||||
|
data.code = code.value;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await UserApi.login(data).then((res) => {
|
||||||
|
if (res.status === 0) {
|
||||||
|
loading.value = false;
|
||||||
|
LocalStorage.set("sd-token", res.data.token);
|
||||||
|
LocalStorage.set("sd-accountInfo", res.data.accountInfo);
|
||||||
|
router.push("/");
|
||||||
|
} else {
|
||||||
|
loading.value = false;
|
||||||
|
processError(res.msg);
|
||||||
|
errMsg.value = res.msg;
|
||||||
|
isErr.value = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
loading.value = false;
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 获取登录验证码
|
||||||
|
const getLoginCode = async () => {
|
||||||
|
if (!telNum.value) {
|
||||||
|
errMsg.value = "手机号不能为空";
|
||||||
|
isErr.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isErr.value = false;
|
||||||
|
errMsg.value = "";
|
||||||
|
codeLoading.value = true;
|
||||||
|
const data = {
|
||||||
|
telNum: telNum.value,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const res = await UserApi.getLoginCode(data).then((res) => {
|
||||||
|
if (res.status === 0) {
|
||||||
|
codeLoading.value = false;
|
||||||
|
let countdown = 60;
|
||||||
|
const countdownTimer = setInterval(() => {
|
||||||
|
countdown--;
|
||||||
|
loginRestSec.value = countdown;
|
||||||
|
if (countdown === 0) {
|
||||||
|
clearInterval(countdownTimer);
|
||||||
|
loginRestSec.value = 60;
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
codeLoading.value = false;
|
||||||
|
errMsg.value = res.msg;
|
||||||
|
isErr.value = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
codeLoading.value = false;
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 获取注册验证码
|
||||||
|
const getCode = async () => {
|
||||||
|
if (!telNum.value) {
|
||||||
|
errMsg.value = "手机号不能为空";
|
||||||
|
isErr.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isErr.value = false;
|
||||||
|
errMsg.value = "";
|
||||||
|
codeLoading.value = true;
|
||||||
|
const data = {
|
||||||
|
telNum: telNum.value,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await UserApi.getRegisterCode(data).then((res) => {
|
||||||
|
if (res.status === 0) {
|
||||||
|
codeLoading.value = false;
|
||||||
|
let countdown = 60;
|
||||||
|
const countdownTimer = setInterval(() => {
|
||||||
|
countdown--;
|
||||||
|
restSec.value = countdown;
|
||||||
|
if (countdown === 0) {
|
||||||
|
clearInterval(countdownTimer);
|
||||||
|
restSec.value = 60;
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
codeLoading.value = false;
|
||||||
|
errMsg.value = res.msg;
|
||||||
|
isErr.value = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
codeLoading.value = false;
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 获取忘记密码验证码
|
||||||
|
const getRestCode = async () => {
|
||||||
|
if (!telNum.value) {
|
||||||
|
errMsg.value = "手机号不能为空";
|
||||||
|
isErr.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isErr.value = false;
|
||||||
|
errMsg.value = "";
|
||||||
|
codeLoading.value = true;
|
||||||
|
const data = {
|
||||||
|
telNum: telNum.value,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await UserApi.getForgetPwdCode(data).then((res) => {
|
||||||
|
console.log(res);
|
||||||
|
if (res.status === 0) {
|
||||||
|
codeLoading.value = false;
|
||||||
|
let countdown = 60;
|
||||||
|
const countdownTimer = setInterval(() => {
|
||||||
|
countdown--;
|
||||||
|
restSec.value = countdown;
|
||||||
|
if (countdown === 0) {
|
||||||
|
clearInterval(countdownTimer);
|
||||||
|
restSec.value = 60;
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
codeLoading.value = false;
|
||||||
|
errMsg.value = res.msg;
|
||||||
|
isErr.value = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
codeLoading.value = false;
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 重置密码
|
||||||
|
const goResetPwd = async () => {
|
||||||
|
if (!telNum.value || !code.value || !password.value || !verfyPassword.value) {
|
||||||
|
errMsg.value = "请填写完整信息";
|
||||||
|
isErr.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (password.value !== verfyPassword.value) {
|
||||||
|
errMsg.value = "两次密码不一致";
|
||||||
|
isErr.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 密码不允许有空格
|
||||||
|
if (password.value.indexOf(" ") !== -1) {
|
||||||
|
errMsg.value = "密码不允许有空格";
|
||||||
|
isErr.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isErr.value = false;
|
||||||
|
errMsg.value = "";
|
||||||
|
loading.value = true;
|
||||||
|
const data = {
|
||||||
|
telNum: telNum.value,
|
||||||
|
code: code.value,
|
||||||
|
password: password.value,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const res = await UserApi.resetPassword(data).then((res) => {
|
||||||
|
if (res.status === 0) {
|
||||||
|
loading.value = false;
|
||||||
|
code.value = "";
|
||||||
|
registType.value = 2;
|
||||||
|
} else {
|
||||||
|
loading.value = false;
|
||||||
|
errMsg.value = res.msg;
|
||||||
|
isErr.value = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
loading.value = false;
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
function toggleFlip() {
|
||||||
|
isFlipped.value = !isFlipped.value;
|
||||||
|
code.value = "";
|
||||||
|
registType.value = 1;
|
||||||
|
}
|
||||||
|
const forgetPwd = () => {
|
||||||
|
isFlipped.value = true;
|
||||||
|
password.value = "";
|
||||||
|
registType.value = 3;
|
||||||
|
};
|
||||||
|
// 注册
|
||||||
|
const goRegist = async () => {
|
||||||
|
if (!telNum.value || !code.value || !password.value || !verfyPassword.value) {
|
||||||
|
errMsg.value = "请填写完整信息";
|
||||||
|
isErr.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (password.value !== verfyPassword.value) {
|
||||||
|
errMsg.value = "两次密码不一致";
|
||||||
|
isErr.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 密码不允许有空格
|
||||||
|
if (password.value.indexOf(" ") !== -1) {
|
||||||
|
errMsg.value = "密码不允许有空格";
|
||||||
|
isErr.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isErr.value = false;
|
||||||
|
errMsg.value = "";
|
||||||
|
loading.value = true;
|
||||||
|
const data = {
|
||||||
|
telNum: telNum.value,
|
||||||
|
code: code.value,
|
||||||
|
password: password.value,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const res = await UserApi.register(data).then((res) => {
|
||||||
|
if (res.status === 0) {
|
||||||
|
loading.value = false;
|
||||||
|
code.value = "";
|
||||||
|
registType.value = 2;
|
||||||
|
} else {
|
||||||
|
loading.value = false;
|
||||||
|
errMsg.value = res.msg;
|
||||||
|
isErr.value = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
loading.value = false;
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 返回登陆
|
||||||
|
const backLogin = () => {
|
||||||
|
isFlipped.value = false;
|
||||||
|
};
|
||||||
|
onMounted(() => {
|
||||||
|
const carousels = document.querySelectorAll(".carousel");
|
||||||
|
carousels.forEach((carousel) => {
|
||||||
|
const track = carousel.querySelector(".carousel-track");
|
||||||
|
startScrolling(carousel, track);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.out-box {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.carousel {
|
||||||
|
overflow: hidden;
|
||||||
|
height: 100%;
|
||||||
|
background: #000000;
|
||||||
|
}
|
||||||
|
.bg {
|
||||||
|
display: flex;
|
||||||
|
transform: rotate(30deg);
|
||||||
|
height: 197%;
|
||||||
|
position: relative;
|
||||||
|
top: -437px;
|
||||||
|
width: 113%;
|
||||||
|
left: -133px;
|
||||||
|
}
|
||||||
|
/* 创建一个伪元素作为黑色透明蒙版 */
|
||||||
|
.bg::before {
|
||||||
|
content: ""; /* 伪元素内容为空 */
|
||||||
|
display: block; /* 或者使用 inline-block,根据需要 */
|
||||||
|
position: absolute; /* 相对于容器进行绝对定位 */
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%; /* 蒙版覆盖整个容器宽度 */
|
||||||
|
height: 100%; /* 蒙版覆盖整个容器高度 */
|
||||||
|
background-color: rgba(0, 0, 0, 0.7); /* 设置黑色背景且透明度为0.7 */
|
||||||
|
z-index: 1; /* 设置层级确保蒙版出现在内容之上(根据需要调整) */
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-track {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-slide {
|
||||||
|
margin: 10px 5px 10px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-slide img {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
-moz-user-drag: none;
|
||||||
|
}
|
||||||
|
.login-box {
|
||||||
|
backface-visibility: hidden;
|
||||||
|
position: absolute;
|
||||||
|
width: 911px;
|
||||||
|
height: 591px;
|
||||||
|
background: #000000;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-align: center;
|
||||||
|
z-index: 99;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
width: 180px;
|
||||||
|
height: 67px;
|
||||||
|
}
|
||||||
|
.sub-title {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #c348b8;
|
||||||
|
font-weight: bolder;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.account-box {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-top: 30px;
|
||||||
|
|
||||||
|
.account {
|
||||||
|
color: #bebebe;
|
||||||
|
transform: translateX(-120%);
|
||||||
|
}
|
||||||
|
.input-box {
|
||||||
|
width: 370px;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.forget {
|
||||||
|
color: #c348b8;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-top: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
transform: translateX(19%);
|
||||||
|
}
|
||||||
|
.code-btn {
|
||||||
|
margin-left: 20px;
|
||||||
|
background: linear-gradient(to right, #591df5, #c448b8);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: bold;
|
||||||
|
border: 8px;
|
||||||
|
height: 45px;
|
||||||
|
min-width: 105px;
|
||||||
|
}
|
||||||
|
.read {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 30px;
|
||||||
|
color: #bebebe;
|
||||||
|
font-size: 15px;
|
||||||
|
span {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.login-btn {
|
||||||
|
margin-top: 10px;
|
||||||
|
background: linear-gradient(to right, #591df5, #c448b8);
|
||||||
|
width: 436px;
|
||||||
|
height: 45px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.regist {
|
||||||
|
margin-top: 20px;
|
||||||
|
color: #bebebe;
|
||||||
|
font-size: 16px;
|
||||||
|
span {
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.regist-box {
|
||||||
|
backface-visibility: hidden;
|
||||||
|
transform: rotateY(180deg);
|
||||||
|
position: absolute;
|
||||||
|
width: 911px;
|
||||||
|
height: 591px;
|
||||||
|
background: #000000;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-align: center;
|
||||||
|
z-index: 99;
|
||||||
|
.title {
|
||||||
|
width: 90px;
|
||||||
|
height: 57px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.re-item {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 99;
|
||||||
|
transition: transform 0.8s;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
perspective: 1000px;
|
||||||
|
}
|
||||||
|
.is-flipped {
|
||||||
|
transform: rotateY(180deg);
|
||||||
|
}
|
||||||
|
:deep(.q-tab--active .q-tab__indicator) {
|
||||||
|
opacity: 1;
|
||||||
|
width: 50px;
|
||||||
|
left: 44%;
|
||||||
|
height: 5px;
|
||||||
|
background: linear-gradient(to right, #be45ba, #5d1ef1);
|
||||||
|
}
|
||||||
|
:deep(.q-tab--active .q-tab__label) {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-shadow: 0px 3px 4px #de53ab;
|
||||||
|
}
|
||||||
|
:deep(.q-tab--inactive .q-tab__label) {
|
||||||
|
color: #bebebe;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
:deep(.q-field__inner) {
|
||||||
|
background: #212121;
|
||||||
|
|
||||||
|
padding: 0 10px 0 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
.q-field__native {
|
||||||
|
color: #7e7e7e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:deep(.q-field__inner::before) {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: -1;
|
||||||
|
margin: -4px;
|
||||||
|
border-radius: inherit; /*important*/
|
||||||
|
background: linear-gradient(270deg, #e44ea8 0%, #5f33f3 49%, #61d2ef 100%);
|
||||||
|
}
|
||||||
|
:deep(.q-field--standard .q-field__control:after) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
:deep(.q-checkbox__svg) {
|
||||||
|
color: #c348b8;
|
||||||
|
}
|
||||||
|
:deep(.q-checkbox__bg) {
|
||||||
|
background: none;
|
||||||
|
border: 2px solid #c348b8;
|
||||||
|
}
|
||||||
|
</style>
|
24
src/pages/aicg/index.vue
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<template>
|
||||||
|
<q-page>
|
||||||
|
<div class="main">
|
||||||
|
<img src="../../assets/image/aicg/box.png" alt="logo" />
|
||||||
|
</div>
|
||||||
|
</q-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.q-page {
|
||||||
|
background: rgb(25, 25, 26);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-image: url("../../assets/image/ai/img-bg.png");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
72
src/pages/create/components/imageDetail.vue
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<template>
|
||||||
|
<div class="detail-dialog">
|
||||||
|
<n-modal
|
||||||
|
v-model:show="showModal"
|
||||||
|
@close="closeModal"
|
||||||
|
:mask-closable="false"
|
||||||
|
>
|
||||||
|
<n-card
|
||||||
|
style="width: 600px;text-align: center;"
|
||||||
|
title="查看图片"
|
||||||
|
:bordered="false"
|
||||||
|
size="huge"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
>
|
||||||
|
<div class="content-box">
|
||||||
|
<img :src="props.detailSrc" alt="图片" />
|
||||||
|
</div>
|
||||||
|
<n-button @click="closeModal" class="create" quaternary> <div style="color: #fff;">确定</div> </n-button>
|
||||||
|
</n-card>
|
||||||
|
|
||||||
|
</n-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, defineProps, computed } from "vue";
|
||||||
|
const props = defineProps({
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
detailSrc: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const emit = defineEmits("update:show", "close-dialog");
|
||||||
|
const showModal = computed({
|
||||||
|
get: () => props.show,
|
||||||
|
set: (value) => {
|
||||||
|
emit("update:show", value);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// 关闭弹窗
|
||||||
|
const closeModal = () => {
|
||||||
|
emit("close-dialog", false);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.content-box {
|
||||||
|
display: flex;
|
||||||
|
height: 85%;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
img {
|
||||||
|
max-width: 450px;
|
||||||
|
max-height: 600px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.create {
|
||||||
|
margin-top: 50px;
|
||||||
|
width: 260px;
|
||||||
|
height: 55px;
|
||||||
|
background: linear-gradient(260deg, #c448b8, #591df5);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 18px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
186
src/pages/create/components/modeList.vue
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
<template>
|
||||||
|
<div class="detail-dialog">
|
||||||
|
<n-modal v-model:show="showModal" @close="closeModal">
|
||||||
|
<n-card
|
||||||
|
style="width: 1300px"
|
||||||
|
title="选择模型"
|
||||||
|
:bordered="false"
|
||||||
|
size="huge"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
>
|
||||||
|
<template #header-extra>
|
||||||
|
<img
|
||||||
|
src="../../../assets/image/ai/close.png"
|
||||||
|
alt="关闭"
|
||||||
|
@click="closeModal"
|
||||||
|
style="cursor: pointer"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<q-scroll-area style="height: 100%">
|
||||||
|
<n-grid :x-gap="12" :y-gap="14" :cols="4">
|
||||||
|
<n-grid-item v-for="(item, index) in modelList" :key="index">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card2" @click="chooseModal(item)">
|
||||||
|
<img :src="item.replaceImg" alt="图片" />
|
||||||
|
<div class="info-box">
|
||||||
|
<div class="name">{{ item.subTitle }}</div>
|
||||||
|
<div class="desc">
|
||||||
|
<span
|
||||||
|
style="margin-right: 10px; color: #bebebe"
|
||||||
|
v-for="(i, d) in item.replaceTag"
|
||||||
|
:key="d"
|
||||||
|
>{{ i }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="author-info">
|
||||||
|
<img :src="item.replaceAuthorImg" alt="图片" />
|
||||||
|
<div class="author">{{ item.replaceAuthor }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-grid-item>
|
||||||
|
</n-grid>
|
||||||
|
</q-scroll-area>
|
||||||
|
</n-card>
|
||||||
|
</n-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, defineProps, computed, watch } from "vue";
|
||||||
|
import { ModelApi } from "src/api";
|
||||||
|
import { processError, processSuccess } from "../../../utils/message";
|
||||||
|
const props = defineProps({
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const emit = defineEmits("update:show", "handCloseDialog", "selectModal");
|
||||||
|
const modelList = ref([]);
|
||||||
|
const showModal = computed({
|
||||||
|
get: () => props.show,
|
||||||
|
set: (value) => {
|
||||||
|
emit("update:show", value);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// 关闭弹窗
|
||||||
|
const closeModal = () => {
|
||||||
|
emit("handCloseDialog", false);
|
||||||
|
};
|
||||||
|
// 选择模型
|
||||||
|
const chooseModal = (item) => {
|
||||||
|
emit("selectModal", item);
|
||||||
|
closeModal();
|
||||||
|
processSuccess("选择模型成功");
|
||||||
|
};
|
||||||
|
// 获取模型列表
|
||||||
|
const getModelList = async () => {
|
||||||
|
try {
|
||||||
|
await ModelApi.getModelList({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 100,
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.status === 0) {
|
||||||
|
modelList.value = res.data.list;
|
||||||
|
} else {
|
||||||
|
processError(res.msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
watch(
|
||||||
|
() => props.show,
|
||||||
|
(val) => {
|
||||||
|
if (val) {
|
||||||
|
getModelList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.card {
|
||||||
|
width: 270px;
|
||||||
|
height: 374px;
|
||||||
|
background-image: linear-gradient(
|
||||||
|
163deg,
|
||||||
|
#61d2ef 0%,
|
||||||
|
#5f33f3 50%,
|
||||||
|
#e44ea8 100%
|
||||||
|
);
|
||||||
|
border-radius: 20px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card2 {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 270px;
|
||||||
|
padding: 10px;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 374px;
|
||||||
|
position: absolute;
|
||||||
|
background-color: #19191a;
|
||||||
|
border-radius: 20px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
img {
|
||||||
|
height: 280px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
.info-box {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #fff;
|
||||||
|
margin-top: 5px;
|
||||||
|
// 超出一行显示省略号
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.desc {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #fff;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
.author-info {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 5px;
|
||||||
|
img {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
.author {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #7e7e7e;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card2:hover {
|
||||||
|
transform: scale(0.98);
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
box-shadow: 0px 0px 30px 1px rgba(0, 255, 117, 0.1);
|
||||||
|
}
|
||||||
|
:deep(.n-card__content) {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
1329
src/pages/create/index.vue
Normal file
279
src/pages/feedback/index.vue
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
<template>
|
||||||
|
<q-page >
|
||||||
|
<div class="top-img animate__animated animate__fadeIn">
|
||||||
|
<img src="../../assets/image/mine-bg.png" alt="mine" />
|
||||||
|
<div class="txt">
|
||||||
|
<div style="font-size: 20px">Comment Feedback</div>
|
||||||
|
<div style="font-size: 60px">留言反馈</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info animate__animated animate__fadeIn">
|
||||||
|
<div class="item">
|
||||||
|
<div class="txt">
|
||||||
|
<span class="tx1">姓名</span>
|
||||||
|
<span class="tx2">
|
||||||
|
<n-input
|
||||||
|
style="width: 300px"
|
||||||
|
v-model:value="name"
|
||||||
|
type="text"
|
||||||
|
placeholder="请输入真实姓名"
|
||||||
|
/></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item">
|
||||||
|
<div class="txt">
|
||||||
|
<span class="tx1">手机号</span>
|
||||||
|
<span class="tx2"
|
||||||
|
><n-input
|
||||||
|
style="width: 300px"
|
||||||
|
v-model:value="phone"
|
||||||
|
type="text"
|
||||||
|
placeholder="请输入真实手机号,以便我们联系您"
|
||||||
|
/></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<div class="txt" style="height: 200px">
|
||||||
|
<span class="tx1" style="height: 170px"
|
||||||
|
>留言内容 </span
|
||||||
|
>
|
||||||
|
<span class="tx2"
|
||||||
|
><n-input
|
||||||
|
style="width: 780px; height: 170px"
|
||||||
|
type="textarea"
|
||||||
|
maxlength="200"
|
||||||
|
v-model:value="content"
|
||||||
|
show-count
|
||||||
|
/></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<n-button @click="submit" class="create" quaternary>
|
||||||
|
<div style="color: #fff">提交</div>
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
<!-- 修改用户名 -->
|
||||||
|
<n-modal
|
||||||
|
v-model:show="showModal"
|
||||||
|
@close="closeModal"
|
||||||
|
:mask-closable="false"
|
||||||
|
>
|
||||||
|
<n-card
|
||||||
|
style="width: 900px; text-align: center; height: 450px"
|
||||||
|
:title="'提交成功'"
|
||||||
|
:bordered="false"
|
||||||
|
size="huge"
|
||||||
|
role="dialog"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
|
||||||
|
style="
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
flex-direction: column;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
|
||||||
|
<img
|
||||||
|
style="width: 200px; height: 200px; margin-top: 20px"
|
||||||
|
src="../../assets/image/login/succicon.png"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<n-button @click="closeModal" class="create" quaternary>
|
||||||
|
<div style="color: #fff">确定</div>
|
||||||
|
</n-button>
|
||||||
|
</n-card>
|
||||||
|
</n-modal>
|
||||||
|
</q-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, defineProps, computed, onBeforeMount, watch } from "vue";
|
||||||
|
import { LocalStorage, SessionStorage } from "quasar";
|
||||||
|
import { processError, processSuccess } from "../../utils/message";
|
||||||
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
import { UserApi } from "src/api";
|
||||||
|
const router = useRouter();
|
||||||
|
const showModal = ref(false);
|
||||||
|
const title = ref("");
|
||||||
|
const errMsg = ref("");
|
||||||
|
const name = ref("");
|
||||||
|
const phone = ref("");
|
||||||
|
const content = ref("");
|
||||||
|
const closeModal = () => {
|
||||||
|
showModal.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 修改手机号
|
||||||
|
const submit = async () => {
|
||||||
|
errMsg.value = "";
|
||||||
|
const data = {
|
||||||
|
domain: "fontree",
|
||||||
|
name: name.value,
|
||||||
|
phone: phone.value,
|
||||||
|
content: content.value,
|
||||||
|
feedbackType: "comment_feedback",
|
||||||
|
};
|
||||||
|
await UserApi.submitFeedBack(data).then((res) => {
|
||||||
|
if (res.status === 0) {
|
||||||
|
processSuccess("提交成功");
|
||||||
|
showModal.value = true;
|
||||||
|
// 清空数据
|
||||||
|
name.value = "";
|
||||||
|
phone.value = "";
|
||||||
|
content.value = "";
|
||||||
|
} else {
|
||||||
|
processError(res.msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onBeforeMount(() => {});
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.q-page {
|
||||||
|
background: #19191a;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.top-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 340px;
|
||||||
|
position: relative;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.txt {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0%;
|
||||||
|
left: 43%;
|
||||||
|
font-weight: bolder;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
div {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.create {
|
||||||
|
margin-top: 40px;
|
||||||
|
width: 260px;
|
||||||
|
height: 55px;
|
||||||
|
background: linear-gradient(260deg, #c448b8, #591df5);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: bolder;
|
||||||
|
font-size: 18px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
background: linear-gradient(to bottom, #39273c, #000000);
|
||||||
|
.item {
|
||||||
|
margin: 26px 0 0 32px;
|
||||||
|
width: 977px;
|
||||||
|
min-height: 60px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #212121;
|
||||||
|
font-size: 18px;
|
||||||
|
|
||||||
|
.txt {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
span {
|
||||||
|
color: #bebebe;
|
||||||
|
}
|
||||||
|
.tx1 {
|
||||||
|
padding-right: 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 120px;
|
||||||
|
border-right: 1px solid #707070;
|
||||||
|
}
|
||||||
|
.tx2 {
|
||||||
|
flex: 1;
|
||||||
|
padding-left: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
color: #c348b8;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.content-box {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 18px;
|
||||||
|
margin-top: 65px;
|
||||||
|
color: #bebebe;
|
||||||
|
}
|
||||||
|
.input-box {
|
||||||
|
width: 370px;
|
||||||
|
margin-left: 35px;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
.account-box {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-top: 30px;
|
||||||
|
|
||||||
|
.account {
|
||||||
|
color: #bebebe;
|
||||||
|
transform: translateX(-120%);
|
||||||
|
}
|
||||||
|
.input-box {
|
||||||
|
width: 370px;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.code-btn {
|
||||||
|
margin-left: 20px;
|
||||||
|
background: linear-gradient(to right, #591df5, #c448b8);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: bold;
|
||||||
|
border: 8px;
|
||||||
|
height: 45px;
|
||||||
|
min-width: 125px;
|
||||||
|
}
|
||||||
|
:deep(.n-input) {
|
||||||
|
background-color: #212121;
|
||||||
|
--n-border-focus: #212121 !important;
|
||||||
|
--n-border-hover: #212121 !important;
|
||||||
|
--n-caret-color: #212121 !important;
|
||||||
|
}
|
||||||
|
:deep(.n-input__suffix) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
:deep(.n-input .n-input__border) {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
// 更改placeholder的颜色
|
||||||
|
:deep(.n-input__placeholder) {
|
||||||
|
color: #7e7e7e;
|
||||||
|
}
|
||||||
|
:deep(.n-input__textarea-el) {
|
||||||
|
color: #7e7e7e;
|
||||||
|
}
|
||||||
|
:deep(.n-input__input-el) {
|
||||||
|
color: #7e7e7e;
|
||||||
|
}
|
||||||
|
</style>
|
188
src/pages/history/components/detail.vue
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
<template>
|
||||||
|
<div class="detail-dialog">
|
||||||
|
<n-modal
|
||||||
|
v-model:show="showModal"
|
||||||
|
@close="closeModal"
|
||||||
|
:mask-closable="false"
|
||||||
|
>
|
||||||
|
<n-card
|
||||||
|
style="width: 1300px; text-align: center"
|
||||||
|
title="查看图片"
|
||||||
|
:bordered="false"
|
||||||
|
size="huge"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
>
|
||||||
|
<template #header-extra>
|
||||||
|
<img
|
||||||
|
src="../../../assets/image/ai/close.png"
|
||||||
|
alt="关闭"
|
||||||
|
@click="closeModal"
|
||||||
|
style="cursor: pointer"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<div class="content-box">
|
||||||
|
<img :src="detailData.image" alt="" />
|
||||||
|
<div class="detail-info">
|
||||||
|
<div class="title">图片详情</div>
|
||||||
|
<div class="info">
|
||||||
|
<span>生成时间</span>
|
||||||
|
<span>{{ detailData.createdAt }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
<span>生成模式</span>
|
||||||
|
<span>{{
|
||||||
|
detailData.type === "ai" ? "AI生图" : "AI生图PLUS"
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
<span>提示词</span>
|
||||||
|
<span>{{ detailData.realPrompt }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
<span>反向词</span>
|
||||||
|
<span>{{ detailData.realNegativePrompt }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
<span>尺寸</span>
|
||||||
|
<span>{{ detailData.width }}*{{ detailData.height }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
<span>订单号</span>
|
||||||
|
<span>{{ detailData.orderNum }}</span>
|
||||||
|
</div>
|
||||||
|
<n-button
|
||||||
|
style="background: #373737"
|
||||||
|
@click="del()"
|
||||||
|
class="create"
|
||||||
|
quaternary
|
||||||
|
>
|
||||||
|
<div style="color: #fff">删除</div>
|
||||||
|
</n-button>
|
||||||
|
<n-button @click="downLoad()" class="create" quaternary>
|
||||||
|
<div style="color: #fff">下载</div>
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-card>
|
||||||
|
</n-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, defineProps, computed } from "vue";
|
||||||
|
import { HistoryApi } from "src/api";
|
||||||
|
import { processError, processSuccess } from "../../../utils/message";
|
||||||
|
import { downLoadByblob, downLoadByUrl } from "../../../utils/downLoad";
|
||||||
|
import { useQuasar } from "quasar";
|
||||||
|
const $q = useQuasar();
|
||||||
|
const props = defineProps({
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
detailData: {
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const emit = defineEmits("update:show", "close-dialog");
|
||||||
|
const showModal = computed({
|
||||||
|
get: () => props.show,
|
||||||
|
set: (value) => {
|
||||||
|
emit("update:show", value);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// 下载
|
||||||
|
const downLoad = async () => {
|
||||||
|
downLoadByUrl(props.detailData.image, null, "图片");
|
||||||
|
};
|
||||||
|
// 删除
|
||||||
|
const del = async () => {
|
||||||
|
$q.dialog({
|
||||||
|
dark: true,
|
||||||
|
title: "删除",
|
||||||
|
message: "是否删除" +"图片",
|
||||||
|
cancel: "取消",
|
||||||
|
ok: "确定",
|
||||||
|
persistent: true,
|
||||||
|
})
|
||||||
|
.onOk(async () => {
|
||||||
|
|
||||||
|
try {
|
||||||
|
await HistoryApi.deleteHistory({
|
||||||
|
ids: [props.detailData.ID],
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.status === 0) {
|
||||||
|
processSuccess("删除成功");
|
||||||
|
closeModal()
|
||||||
|
} else {
|
||||||
|
processError(res.msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onCancel(() => {
|
||||||
|
// console.log('>>>> Cancel')
|
||||||
|
})
|
||||||
|
.onDismiss(() => {
|
||||||
|
// console.log('I am triggered on both OK and Cancel')
|
||||||
|
});
|
||||||
|
console.log(selectIds.value);
|
||||||
|
};
|
||||||
|
// 关闭弹窗
|
||||||
|
const closeModal = () => {
|
||||||
|
emit("close-dialog", false);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.content-box {
|
||||||
|
display: flex;
|
||||||
|
height: 85%;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
img {
|
||||||
|
max-width: 600px;
|
||||||
|
max-height: 650px;
|
||||||
|
}
|
||||||
|
.detail-info {
|
||||||
|
padding-left: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-top: 310px;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: left;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #fff;
|
||||||
|
border-bottom: 1px solid #707070;
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
span {
|
||||||
|
color: #fff;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.create {
|
||||||
|
margin-top: 50px;
|
||||||
|
width: 280px;
|
||||||
|
margin-left: 13px;
|
||||||
|
height: 55px;
|
||||||
|
background: linear-gradient(260deg, #c448b8, #591df5);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 18px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
485
src/pages/history/index.vue
Normal file
@ -0,0 +1,485 @@
|
|||||||
|
<template>
|
||||||
|
<q-page>
|
||||||
|
<div class="main">
|
||||||
|
<div class="search-box">
|
||||||
|
<div style="display: flex; align-items: center">
|
||||||
|
生成时间
|
||||||
|
<n-date-picker
|
||||||
|
class="date-picker"
|
||||||
|
:actions="null"
|
||||||
|
v-model:value="createdTime"
|
||||||
|
start-placeholder="开始时间"
|
||||||
|
end-placeholder="结束时间"
|
||||||
|
type="daterange"
|
||||||
|
format="yyyy-MM-dd"
|
||||||
|
clearable
|
||||||
|
:close-on-select="true"
|
||||||
|
:update-value-on-close="true"
|
||||||
|
:on-update:value="handleDateChange"
|
||||||
|
/>
|
||||||
|
生成模式
|
||||||
|
|
||||||
|
<n-select
|
||||||
|
style="width: 200px; margin-left: 20px"
|
||||||
|
v-model:value="type"
|
||||||
|
:options="options"
|
||||||
|
:on-update:value="changeType"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="btn-group">
|
||||||
|
<span v-if="isEdit"
|
||||||
|
>已选择
|
||||||
|
<span style="margin: 0 10px 0 10px; color: #bc44b8">{{
|
||||||
|
imageList.length
|
||||||
|
}}</span>
|
||||||
|
张图片</span
|
||||||
|
>
|
||||||
|
<n-button
|
||||||
|
v-if="isEdit"
|
||||||
|
style="background: #5b1df1"
|
||||||
|
@click="del(selectIds)"
|
||||||
|
class="create"
|
||||||
|
quaternary
|
||||||
|
>
|
||||||
|
<div style="color: #fff">删除</div>
|
||||||
|
</n-button>
|
||||||
|
<n-button
|
||||||
|
v-if="isEdit"
|
||||||
|
style="background: #bd45b9"
|
||||||
|
@click="downLoad(selectIds, imageList[0].image)"
|
||||||
|
class="create"
|
||||||
|
quaternary
|
||||||
|
>
|
||||||
|
<div style="color: #fff">下载</div>
|
||||||
|
</n-button>
|
||||||
|
<n-button @click="editAll" class="create" quaternary>
|
||||||
|
<div v-if="!isEdit" style="color: #fff">批量管理</div>
|
||||||
|
<div v-else style="color: #fff">退出管理</div>
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<q-scroll-area style="height: 90%">
|
||||||
|
<n-grid :x-gap="12" :y-gap="14" :cols="6" style="margin-top: 20px">
|
||||||
|
<n-grid-item
|
||||||
|
class="animate__animated animate__slideInUp"
|
||||||
|
v-for="(item, index) in historyList"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<n-spin :show="!item.image">
|
||||||
|
<div class="card">
|
||||||
|
<div
|
||||||
|
class="card2"
|
||||||
|
@mouseover="item.showBtn = true"
|
||||||
|
@mouseleave="item.showBtn = false"
|
||||||
|
@click="checkDetail(item)"
|
||||||
|
>
|
||||||
|
<q-parallax :src="item.image"> </q-parallax>
|
||||||
|
<div class="act-btn" v-if="item.showBtn">
|
||||||
|
<n-button
|
||||||
|
style="background: #5c1ef2; width: 125px"
|
||||||
|
@click.stop="del([item.ID])"
|
||||||
|
class="create"
|
||||||
|
quaternary
|
||||||
|
>
|
||||||
|
<div style="color: #fff">删除</div>
|
||||||
|
</n-button>
|
||||||
|
<n-button
|
||||||
|
style="background: #bd45b9; width: 125px"
|
||||||
|
@click.stop="downLoad([item.ID], item.image)"
|
||||||
|
class="create"
|
||||||
|
quaternary
|
||||||
|
>
|
||||||
|
<div style="color: #fff">下载</div>
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
<div class="checkbox-wrapper-16">
|
||||||
|
<label class="checkbox-wrapper">
|
||||||
|
<input
|
||||||
|
v-model="selectIds"
|
||||||
|
:value="item.ID"
|
||||||
|
class="checkbox-input"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
v-if="isEdit"
|
||||||
|
@click="selectedImage(item)"
|
||||||
|
class="checkbox-tile"
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-spin>
|
||||||
|
</n-grid-item>
|
||||||
|
</n-grid>
|
||||||
|
</q-scroll-area>
|
||||||
|
</div>
|
||||||
|
</q-page>
|
||||||
|
<detail
|
||||||
|
:show="showModal"
|
||||||
|
:detailData="selectedModel"
|
||||||
|
@close-dialog="closeModal"
|
||||||
|
></detail>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, defineProps, computed, onBeforeMount } from "vue";
|
||||||
|
import { HistoryApi } from "src/api";
|
||||||
|
import { processError, processSuccess } from "../../utils/message";
|
||||||
|
import { downLoadByblob, downLoadByUrl } from "../../utils/downLoad";
|
||||||
|
import { useQuasar } from "quasar";
|
||||||
|
import detail from "./components/detail.vue";
|
||||||
|
const $q = useQuasar();
|
||||||
|
const isEdit = ref(false);
|
||||||
|
const showModal = ref(false);
|
||||||
|
const createdTime = ref("");
|
||||||
|
const createdAtStart = ref("");
|
||||||
|
const createdAtEnd = ref("");
|
||||||
|
const type = ref("");
|
||||||
|
const historyList = ref([]);
|
||||||
|
const selectIds = ref([]);
|
||||||
|
const selectedModel = ref({});
|
||||||
|
const options = [
|
||||||
|
{ label: "全部", value: "" },
|
||||||
|
{ label: "AI生图", value: "ai" },
|
||||||
|
{ label: "AI生图PLUS", value: "plus" },
|
||||||
|
];
|
||||||
|
const imageList = ref([]);
|
||||||
|
const closeModal = () => {
|
||||||
|
showModal.value = false;
|
||||||
|
getHistoryList();
|
||||||
|
};
|
||||||
|
const checkDetail = (item) => {
|
||||||
|
// 如果在编辑状态下,则不弹出详情
|
||||||
|
if (isEdit.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showModal.value = true;
|
||||||
|
selectedModel.value = item;
|
||||||
|
};
|
||||||
|
const selectedImage = (item) => {
|
||||||
|
// 将选中的图片存储到imageList中,如果再次点击则取消选中
|
||||||
|
if (imageList.value.includes(item)) {
|
||||||
|
imageList.value = imageList.value.filter((i) => i !== item);
|
||||||
|
} else {
|
||||||
|
imageList.value.push(item);
|
||||||
|
}
|
||||||
|
console.log(imageList.value);
|
||||||
|
};
|
||||||
|
const changeType = (val) => {
|
||||||
|
type.value = val;
|
||||||
|
getHistoryList();
|
||||||
|
};
|
||||||
|
// 下载
|
||||||
|
const downLoad = async (val, url) => {
|
||||||
|
console.log(imageList.value);
|
||||||
|
if (!val.length) {
|
||||||
|
processError("请选择图片");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (val.length === 1) {
|
||||||
|
processSuccess("下载成功");
|
||||||
|
downLoadByUrl(url, null, "图片");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await HistoryApi.downloadHistory({
|
||||||
|
ids: val,
|
||||||
|
}).then((res) => {
|
||||||
|
// 如果是单个下载
|
||||||
|
|
||||||
|
downLoadByblob(res.data, "图片.zip");
|
||||||
|
|
||||||
|
processSuccess("下载成功");
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 删除
|
||||||
|
const del = async (val) => {
|
||||||
|
$q.dialog({
|
||||||
|
dark: true,
|
||||||
|
title: "删除",
|
||||||
|
message: "是否删除" + val.length + "张图片",
|
||||||
|
cancel: "取消",
|
||||||
|
ok: "确定",
|
||||||
|
persistent: true,
|
||||||
|
})
|
||||||
|
.onOk(async () => {
|
||||||
|
if (!val.length) {
|
||||||
|
processError("请选择图片");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await HistoryApi.deleteHistory({
|
||||||
|
ids: val,
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.status === 0) {
|
||||||
|
processSuccess("删除成功");
|
||||||
|
imageList.value = [];
|
||||||
|
selectIds.value = [];
|
||||||
|
isEdit.value = false;
|
||||||
|
getHistoryList();
|
||||||
|
} else {
|
||||||
|
processError(res.msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onCancel(() => {
|
||||||
|
// console.log('>>>> Cancel')
|
||||||
|
})
|
||||||
|
.onDismiss(() => {
|
||||||
|
// console.log('I am triggered on both OK and Cancel')
|
||||||
|
});
|
||||||
|
console.log(selectIds.value);
|
||||||
|
};
|
||||||
|
const handleDateChange = (val, formattedValue) => {
|
||||||
|
if (formattedValue) {
|
||||||
|
createdAtStart.value = formattedValue[0];
|
||||||
|
createdAtEnd.value = formattedValue[1];
|
||||||
|
} else {
|
||||||
|
createdAtStart.value = "";
|
||||||
|
createdAtEnd.value = "";
|
||||||
|
}
|
||||||
|
getHistoryList();
|
||||||
|
};
|
||||||
|
const editAll = () => {
|
||||||
|
isEdit.value = !isEdit.value;
|
||||||
|
imageList.value = [];
|
||||||
|
selectIds.value = [];
|
||||||
|
};
|
||||||
|
const getHistoryList = async () => {
|
||||||
|
try {
|
||||||
|
await HistoryApi.getHistoryList({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 1000,
|
||||||
|
modelId: "",
|
||||||
|
createdAtStart: createdAtStart.value,
|
||||||
|
createdAtEnd: createdAtEnd.value,
|
||||||
|
type: type.value,
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.status === 0) {
|
||||||
|
historyList.value = res.data.list;
|
||||||
|
} else {
|
||||||
|
processError(res.msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
onBeforeMount(() => {
|
||||||
|
getHistoryList();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.q-page {
|
||||||
|
background: rgb(25, 25, 26);
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.main {
|
||||||
|
width: 100%;
|
||||||
|
background: #212121;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin: 14px 24px 14px 24px;
|
||||||
|
padding: 28px;
|
||||||
|
.search-box {
|
||||||
|
color: #ffffff;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
.date-picker {
|
||||||
|
margin-left: 20px;
|
||||||
|
width: 300px;
|
||||||
|
margin-right: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.create {
|
||||||
|
width: 150px;
|
||||||
|
height: 44px;
|
||||||
|
background: linear-gradient(260deg, #c448b8, #591df5);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-left: 15px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.act-btn {
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
width: 290px;
|
||||||
|
height: 319px;
|
||||||
|
background-image: linear-gradient(
|
||||||
|
163deg,
|
||||||
|
#61d2ef 0%,
|
||||||
|
#5f33f3 50%,
|
||||||
|
#e44ea8 100%
|
||||||
|
);
|
||||||
|
border-radius: 20px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card2 {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 290px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 319px;
|
||||||
|
position: relative;
|
||||||
|
background-color: #19191a;
|
||||||
|
border-radius: 20px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card2:hover {
|
||||||
|
transform: scale(0.98);
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
box-shadow: 0px 0px 30px 1px rgba(0, 255, 117, 0.1);
|
||||||
|
}
|
||||||
|
.checkbox-wrapper-16 *,
|
||||||
|
.checkbox-wrapper-16 *:after,
|
||||||
|
.checkbox-wrapper-16 *:before {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-wrapper-16 .checkbox-input {
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
-webkit-clip-path: inset(100%);
|
||||||
|
clip-path: inset(100%);
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-wrapper-16 .checkbox-input:checked + .checkbox-tile:before {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
background-color: #c348b8;
|
||||||
|
border-color: #c348b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-wrapper-16 .checkbox-input:focus + .checkbox-tile:before {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-wrapper-16 .checkbox-tile {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
|
border-radius: 20px;
|
||||||
|
transition: 0.15s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
top: 10px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-wrapper-16 .checkbox-tile:before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
border: 2px solid #b5bfd9;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
top: 0.25rem;
|
||||||
|
left: 0.25rem;
|
||||||
|
margin-left: 10px;
|
||||||
|
transition: 0.25s ease;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='://www.w3.org/2000/svg' width='192' height='192' fill='%23FFFFFF' viewBox='0 0 256 256'%3E%3Crect width='256' height='256' fill='none'%3E%3C/rect%3E%3Cpolyline points='216 72.005 104 184 48 128.005' fill='none' stroke='%23FFFFFF' stroke-linecap='round' stroke-linejoin='round' stroke-width='32'%3E%3C/polyline%3E%3C/svg%3E");
|
||||||
|
background-size: 12px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: 50% 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-wrapper-16 .checkbox-tile:hover {
|
||||||
|
border-color: #c348b8;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-wrapper-16 .checkbox-tile:hover:before {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-wrapper-16 .checkbox-icon {
|
||||||
|
transition: 0.375s ease;
|
||||||
|
color: #494949;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-wrapper-16 .checkbox-icon svg {
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-wrapper-16 .checkbox-label {
|
||||||
|
color: #707070;
|
||||||
|
transition: 0.375s ease;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
:deep(.n-card__content) {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
:deep(.n-input) {
|
||||||
|
background: #383838;
|
||||||
|
|
||||||
|
border: 1px solid #7e7e7e;
|
||||||
|
}
|
||||||
|
:deep(.n-input__input-el) {
|
||||||
|
color: #bebebe;
|
||||||
|
}
|
||||||
|
:deep(.n-input .n-input__border) {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
// 更改placeholder的颜色
|
||||||
|
:deep(.n-input__placeholder) {
|
||||||
|
color: #7e7e7e;
|
||||||
|
}
|
||||||
|
:deep(.n-input__textarea-el) {
|
||||||
|
color: #7e7e7e;
|
||||||
|
}
|
||||||
|
:deep(.n-input__input-el) {
|
||||||
|
color: #7e7e7e;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-base-selection-input__content) {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
:deep(.n-base-selection-label) {
|
||||||
|
height: 36px;
|
||||||
|
background-color: #383838;
|
||||||
|
width: 200px;
|
||||||
|
color: #7e7e7e;
|
||||||
|
}
|
||||||
|
:deep(.n-base-selection .n-base-selection__border) {
|
||||||
|
border: 1px solid #7e7e7e;
|
||||||
|
}
|
||||||
|
:deep(.n-base-selection-input__content) {
|
||||||
|
color: #7e7e7e;
|
||||||
|
}
|
||||||
|
</style>
|
266
src/pages/mine/components/cost.vue
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
<template>
|
||||||
|
<q-page>
|
||||||
|
<div class="main-box">
|
||||||
|
<div class="search-box">
|
||||||
|
<div style="display: flex; align-items: center">
|
||||||
|
订单编号
|
||||||
|
<n-input
|
||||||
|
style="width: 390px; margin-right: 60px; margin-left: 20px"
|
||||||
|
v-model:value="orderNum"
|
||||||
|
type="text"
|
||||||
|
@keydown.enter="getCostList"
|
||||||
|
placeholder="请输入订单号"
|
||||||
|
/>
|
||||||
|
消费时间
|
||||||
|
<n-date-picker
|
||||||
|
class="date-picker"
|
||||||
|
:actions="null"
|
||||||
|
v-model:value="createdTime"
|
||||||
|
start-placeholder="开始时间"
|
||||||
|
end-placeholder="结束时间"
|
||||||
|
type="daterange"
|
||||||
|
format="yyyy-MM-dd"
|
||||||
|
clearable
|
||||||
|
:close-on-select="true"
|
||||||
|
:update-value-on-close="true"
|
||||||
|
:on-update:value="handleDateChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<n-data-table
|
||||||
|
style="margin-top: 40px"
|
||||||
|
:bordered="false"
|
||||||
|
:columns="columns"
|
||||||
|
:data="tableData"
|
||||||
|
:loading="loading"
|
||||||
|
/>
|
||||||
|
<div class="page">
|
||||||
|
共{{ total }}条数据
|
||||||
|
<n-pagination
|
||||||
|
style="margin-left: 20px"
|
||||||
|
v-model:page="page"
|
||||||
|
:page-count="pageCount"
|
||||||
|
size="medium"
|
||||||
|
show-quick-jumper
|
||||||
|
:on-update:page="pageChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {
|
||||||
|
ref,
|
||||||
|
defineProps,
|
||||||
|
computed,
|
||||||
|
onBeforeMount,
|
||||||
|
watch,
|
||||||
|
reactive,
|
||||||
|
h,
|
||||||
|
} from "vue";
|
||||||
|
import { LocalStorage, SessionStorage } from "quasar";
|
||||||
|
import { processError, processSuccess } from "../../../utils/message";
|
||||||
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
import { ScoreApi } from "src/api";
|
||||||
|
const createdTime = ref("");
|
||||||
|
const createdAtStart = ref("");
|
||||||
|
const createdAtEnd = ref("");
|
||||||
|
const orderNum = ref("");
|
||||||
|
const loading = ref(false);
|
||||||
|
const page = ref(1);
|
||||||
|
const tableData = ref([]);
|
||||||
|
const pageCount = ref(0);
|
||||||
|
const total = ref(0);
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: "消费时间",
|
||||||
|
key: "createAt",
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "订单号",
|
||||||
|
key: "orderNum",
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "消费积分 ",
|
||||||
|
key: "coin",
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "订单状态",
|
||||||
|
key: "status",
|
||||||
|
width: 200,
|
||||||
|
// 如果是0,显示红色字失败,否则显示绿色字成功
|
||||||
|
render: (row) => {
|
||||||
|
return h(
|
||||||
|
"div",
|
||||||
|
{ style: { color: row.status === 0 ? "#C348B8" : "#61C8EF" } },
|
||||||
|
row.status === 0 ? "失败" : "成功"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const pageChange = (val) => {
|
||||||
|
page.value = val;
|
||||||
|
getCostList();
|
||||||
|
};
|
||||||
|
// 获取消费积分列表
|
||||||
|
const getCostList = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
const params = {
|
||||||
|
page: page.value,
|
||||||
|
pageSize: 10,
|
||||||
|
startCreatedAt: createdAtStart.value,
|
||||||
|
endCreatedAt: createdAtEnd.value,
|
||||||
|
orderNum: orderNum.value,
|
||||||
|
};
|
||||||
|
await ScoreApi.getScore(params).then((res) => {
|
||||||
|
if (res.code === 200) {
|
||||||
|
loading.value = false;
|
||||||
|
total.value = res.data.count;
|
||||||
|
tableData.value = res.data.list || [];
|
||||||
|
pageCount.value = Math.ceil(res.data.count / 10);
|
||||||
|
} else {
|
||||||
|
loading.value = false;
|
||||||
|
processError(res);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const handleDateChange = (val, formattedValue) => {
|
||||||
|
if (formattedValue) {
|
||||||
|
createdAtStart.value = formattedValue[0];
|
||||||
|
createdAtEnd.value = formattedValue[1];
|
||||||
|
} else {
|
||||||
|
createdAtStart.value = "";
|
||||||
|
createdAtEnd.value = "";
|
||||||
|
}
|
||||||
|
getCostList();
|
||||||
|
};
|
||||||
|
onBeforeMount(() => {
|
||||||
|
getCostList();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.q-page {
|
||||||
|
background: #19191a;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.main-box {
|
||||||
|
padding: 28px;
|
||||||
|
background: #212121;
|
||||||
|
margin: 15px;
|
||||||
|
height: 98%;
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 98%;
|
||||||
|
}
|
||||||
|
.search-box {
|
||||||
|
color: #ffffff;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
.date-picker {
|
||||||
|
margin-left: 20px;
|
||||||
|
width: 300px;
|
||||||
|
margin-right: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.page {
|
||||||
|
display: flex;
|
||||||
|
justify-content: end;
|
||||||
|
margin-top: 20px;
|
||||||
|
align-items: center;
|
||||||
|
color: #7e7e7e;
|
||||||
|
}
|
||||||
|
:deep(.n-input) {
|
||||||
|
background: #383838;
|
||||||
|
border: 1px solid #7e7e7e;
|
||||||
|
}
|
||||||
|
:deep(.n-input__input-el) {
|
||||||
|
color: #bebebe;
|
||||||
|
}
|
||||||
|
:deep(.n-input .n-input__border) {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
// 更改placeholder的颜色
|
||||||
|
:deep(.n-input__placeholder) {
|
||||||
|
color: #7e7e7e;
|
||||||
|
}
|
||||||
|
:deep(.n-input__textarea-el) {
|
||||||
|
color: #7e7e7e;
|
||||||
|
}
|
||||||
|
:deep(.n-input__input-el) {
|
||||||
|
color: #7e7e7e;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-base-selection-input__content) {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
:deep(.n-base-selection-label) {
|
||||||
|
height: 36px;
|
||||||
|
background-color: #383838;
|
||||||
|
width: 200px;
|
||||||
|
color: #7e7e7e;
|
||||||
|
}
|
||||||
|
:deep(.n-base-selection .n-base-selection__border) {
|
||||||
|
border: 1px solid #7e7e7e;
|
||||||
|
}
|
||||||
|
:deep(.n-base-selection-input__content) {
|
||||||
|
color: #7e7e7e;
|
||||||
|
}
|
||||||
|
:deep(
|
||||||
|
.n-pagination
|
||||||
|
.n-pagination-item.n-pagination-item--disabled.n-pagination-item--button
|
||||||
|
) {
|
||||||
|
background-color: #19191a;
|
||||||
|
}
|
||||||
|
:deep(
|
||||||
|
.n-pagination
|
||||||
|
.n-pagination-item:not(
|
||||||
|
.n-pagination-item--disabled
|
||||||
|
).n-pagination-item--active
|
||||||
|
) {
|
||||||
|
background-color: #c348b8;
|
||||||
|
color: #fff;
|
||||||
|
border: 1px solid #c348b8;
|
||||||
|
}
|
||||||
|
:deep(.n-pagination .n-pagination-item) {
|
||||||
|
color: #7e7e7e;
|
||||||
|
}
|
||||||
|
:deep(.n-data-table-td) {
|
||||||
|
--n-th-color-hover: #383838;
|
||||||
|
background-color: #000000;
|
||||||
|
text-align: center;
|
||||||
|
color: #7e7e7e;
|
||||||
|
border-bottom: 1px solid #383838;
|
||||||
|
}
|
||||||
|
:deep(.n-data-table-th) {
|
||||||
|
border: 0;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ffffff;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
:deep(.n-data-table) {
|
||||||
|
--n-th-color: #c448b8 !important;
|
||||||
|
--n-td-color-hover: #383838 !important;
|
||||||
|
--n-loading-color: #c448b8 !important;
|
||||||
|
--n-td-padding: 17px !important;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
:deep(
|
||||||
|
.n-data-table.n-data-table--bottom-bordered
|
||||||
|
.n-data-table-td.n-data-table-td--last-row
|
||||||
|
) {
|
||||||
|
border: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-scrollbar-container){
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
543
src/pages/mine/components/mineInfo.vue
Normal file
@ -0,0 +1,543 @@
|
|||||||
|
<template>
|
||||||
|
<q-page>
|
||||||
|
<div class="top-img">
|
||||||
|
<img src="../../../assets/image/mine-bg.png" alt="mine" />
|
||||||
|
<div class="txt">
|
||||||
|
<div style="font-size: 20px">Personal Center</div>
|
||||||
|
<div style="font-size: 60px">个人中心</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info">
|
||||||
|
<div class="item">
|
||||||
|
<div class="txt">
|
||||||
|
<span class="tx1">用户名</span>
|
||||||
|
<span class="tx2">{{ user.nickName }}</span>
|
||||||
|
</div>
|
||||||
|
<span @click="editUserName">编辑</span>
|
||||||
|
</div>
|
||||||
|
<div class="item" style="height: 120px">
|
||||||
|
<div class="txt">
|
||||||
|
<span class="tx1">头像 </span>
|
||||||
|
<n-avatar
|
||||||
|
style="margin-left: 20px"
|
||||||
|
round
|
||||||
|
:size="88"
|
||||||
|
:src="user.avatar"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span @click="editAvatar">修改</span>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<div class="txt">
|
||||||
|
<span class="tx1">手机号</span>
|
||||||
|
<span class="tx2">{{ user.telNum }}</span>
|
||||||
|
</div>
|
||||||
|
<span @click="editTel">修改</span>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<div class="txt">
|
||||||
|
<span class="tx1">密码 </span>
|
||||||
|
<span class="tx2">●●●●●●</span>
|
||||||
|
</div>
|
||||||
|
<span @click="editPwd">修改</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 修改用户名 -->
|
||||||
|
<n-modal
|
||||||
|
v-model:show="showModal"
|
||||||
|
@close="closeModal"
|
||||||
|
:mask-closable="false"
|
||||||
|
>
|
||||||
|
<n-card
|
||||||
|
style="width: 900px; text-align: center; height: 420px"
|
||||||
|
:title="title"
|
||||||
|
:bordered="false"
|
||||||
|
size="huge"
|
||||||
|
role="dialog"
|
||||||
|
>
|
||||||
|
<template #header-extra>
|
||||||
|
<img
|
||||||
|
src="../../../assets/image/ai/close.png"
|
||||||
|
alt="关闭"
|
||||||
|
@click="closeModal"
|
||||||
|
style="cursor: pointer"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<div class="content-box" v-if="title === '修改用户名'">
|
||||||
|
用户名
|
||||||
|
<q-input
|
||||||
|
class="input-box"
|
||||||
|
maxlength="10"
|
||||||
|
v-model="nickName"
|
||||||
|
:dense="true"
|
||||||
|
placeholder="请输入用户名"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="title === '修改用户名'"
|
||||||
|
style="color: #bebebe; margin-left: 80px; margin-top: 10px"
|
||||||
|
>
|
||||||
|
用户名请输入1-10个字符,可包括中文,英文和数字
|
||||||
|
</div>
|
||||||
|
<n-upload
|
||||||
|
v-if="title === '修改头像'"
|
||||||
|
class="content-box"
|
||||||
|
list-type="image-card"
|
||||||
|
:max="1"
|
||||||
|
:on-finish="handleChange"
|
||||||
|
action="http://192.168.1.244:8085/api/ai/upload-file"
|
||||||
|
>
|
||||||
|
<n-upload-dragger>
|
||||||
|
<div>
|
||||||
|
<img src="../../../assets/image/ai/plus.png" />
|
||||||
|
</div>
|
||||||
|
</n-upload-dragger>
|
||||||
|
</n-upload>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="content-box"
|
||||||
|
style="flex-direction: column; margin-top: 20px"
|
||||||
|
v-if="title === '修改密码'"
|
||||||
|
>
|
||||||
|
<div style="display: flex; align-items: center">
|
||||||
|
旧密码
|
||||||
|
<q-input
|
||||||
|
style="margin-left: 70px"
|
||||||
|
class="input-box"
|
||||||
|
v-model="oldPwd"
|
||||||
|
maxlength="8"
|
||||||
|
:dense="true"
|
||||||
|
placeholder="请输入旧密码"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; margin-top: 20px">
|
||||||
|
新密码
|
||||||
|
<q-input
|
||||||
|
style="margin-left: 70px"
|
||||||
|
class="input-box"
|
||||||
|
v-model="password"
|
||||||
|
:dense="true"
|
||||||
|
maxlength="8"
|
||||||
|
placeholder="请输入新密码"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; margin-top: 20px">
|
||||||
|
确认新密码
|
||||||
|
<q-input
|
||||||
|
class="input-box"
|
||||||
|
v-model="confirmPwd"
|
||||||
|
:dense="true"
|
||||||
|
maxlength="8"
|
||||||
|
placeholder="请输入新密码"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="content-box"
|
||||||
|
style="flex-direction: column; margin-top: 20px"
|
||||||
|
v-if="title === '修改手机号'"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="display: flex; align-items: center; flex-direction: column"
|
||||||
|
>
|
||||||
|
<div style="display: flex; align-items: center; margin-top: 20px">
|
||||||
|
新手机号
|
||||||
|
<q-input
|
||||||
|
style="margin-left: 10px"
|
||||||
|
class="input-box"
|
||||||
|
v-model="newTelNum"
|
||||||
|
:dense="true"
|
||||||
|
placeholder="请输入新手机号"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="account-box">
|
||||||
|
<span class="account" style="transform: translateX(0)"
|
||||||
|
>验证码</span
|
||||||
|
>
|
||||||
|
<q-input
|
||||||
|
style="width: 212px"
|
||||||
|
class="input-box"
|
||||||
|
v-model="code"
|
||||||
|
:dense="true"
|
||||||
|
placeholder="请输入验证码"
|
||||||
|
>
|
||||||
|
</q-input>
|
||||||
|
<q-btn
|
||||||
|
class="code-btn"
|
||||||
|
:loading="codeLoading"
|
||||||
|
:disabled="restSec < 60"
|
||||||
|
@click="getRestCode"
|
||||||
|
style="margin-right: 10px"
|
||||||
|
>
|
||||||
|
<div v-if="restSec === 60 || restSec < 1">获取验证码</div>
|
||||||
|
<div v-else>{{ restSec }}</div>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-show="isErr"
|
||||||
|
style="
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 10px;
|
||||||
|
left: 45%;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
style="width: 22px; height: 18px; margin-right: 5px"
|
||||||
|
src="../../../assets/image/login/warning.png"
|
||||||
|
/>
|
||||||
|
<span style="color: #e0378c; margin-top: 2px">{{ errMsg }}</span>
|
||||||
|
</div>
|
||||||
|
<n-button @click="doAction" class="create" quaternary>
|
||||||
|
<div style="color: #fff">确定</div>
|
||||||
|
</n-button>
|
||||||
|
</n-card>
|
||||||
|
</n-modal>
|
||||||
|
</q-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, defineProps, computed, onBeforeMount, watch } from "vue";
|
||||||
|
import { LocalStorage, SessionStorage } from "quasar";
|
||||||
|
import { processError, processSuccess } from "../../../utils/message";
|
||||||
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
import { UserApi } from "src/api";
|
||||||
|
const router = useRouter();
|
||||||
|
const user = LocalStorage.getItem("sd-accountInfo") || {};
|
||||||
|
const showModal = ref(false);
|
||||||
|
const title = ref("");
|
||||||
|
const nickName = ref("");
|
||||||
|
const avatar = ref("");
|
||||||
|
const password = ref("");
|
||||||
|
const confirmPwd = ref("");
|
||||||
|
const oldPwd = ref("");
|
||||||
|
const isErr = ref(false);
|
||||||
|
const codeLoading = ref(false);
|
||||||
|
const restSec = ref(60);
|
||||||
|
const code = ref("");
|
||||||
|
const errMsg = ref("");
|
||||||
|
const newTelNum = ref("");
|
||||||
|
const closeModal = () => {
|
||||||
|
showModal.value = false;
|
||||||
|
isErr.value = false;
|
||||||
|
errMsg.value = "";
|
||||||
|
nickName.value = "";
|
||||||
|
avatar.value = "";
|
||||||
|
password.value = "";
|
||||||
|
oldPwd.value = "";
|
||||||
|
confirmPwd.value = "";
|
||||||
|
};
|
||||||
|
// 编辑用户名
|
||||||
|
const editUserName = () => {
|
||||||
|
title.value = "修改用户名";
|
||||||
|
showModal.value = true;
|
||||||
|
};
|
||||||
|
// 编辑头像
|
||||||
|
const editAvatar = () => {
|
||||||
|
title.value = "修改头像";
|
||||||
|
showModal.value = true;
|
||||||
|
};
|
||||||
|
// 编辑手机号
|
||||||
|
const editTel = () => {
|
||||||
|
title.value = "修改手机号";
|
||||||
|
showModal.value = true;
|
||||||
|
};
|
||||||
|
// 编辑密码
|
||||||
|
const editPwd = () => {
|
||||||
|
title.value = "修改密码";
|
||||||
|
showModal.value = true;
|
||||||
|
};
|
||||||
|
// 上传完毕
|
||||||
|
const handleChange = (file, List) => {
|
||||||
|
let resData = JSON.parse(file.event.currentTarget.response);
|
||||||
|
if (resData.status === 0) {
|
||||||
|
processSuccess("上传成功");
|
||||||
|
avatar.value = resData.data.ori_url;
|
||||||
|
} else {
|
||||||
|
processError(resData.msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 获取验证码
|
||||||
|
const getRestCode = async () => {
|
||||||
|
if (!newTelNum.value) {
|
||||||
|
errMsg.value = "手机号不能为空";
|
||||||
|
isErr.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isErr.value = false;
|
||||||
|
errMsg.value = "";
|
||||||
|
codeLoading.value = true;
|
||||||
|
const data = {
|
||||||
|
tel: newTelNum.value,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await UserApi.getUpdatePhoneCode(data).then((res) => {
|
||||||
|
console.log(res);
|
||||||
|
if (res.status === 0) {
|
||||||
|
codeLoading.value = false;
|
||||||
|
let countdown = 60;
|
||||||
|
const countdownTimer = setInterval(() => {
|
||||||
|
countdown--;
|
||||||
|
restSec.value = countdown;
|
||||||
|
if (countdown === 0) {
|
||||||
|
clearInterval(countdownTimer);
|
||||||
|
restSec.value = 60;
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
codeLoading.value = false;
|
||||||
|
errMsg.value = res.msg;
|
||||||
|
isErr.value = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
codeLoading.value = false;
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 修改手机号
|
||||||
|
const editTelNum = async () => {
|
||||||
|
errMsg.value = "";
|
||||||
|
const data = {
|
||||||
|
newTelNum: newTelNum.value,
|
||||||
|
code: code.value,
|
||||||
|
};
|
||||||
|
await UserApi.updatePhone(data).then((res) => {
|
||||||
|
if (res.status === 0) {
|
||||||
|
processSuccess("修改成功");
|
||||||
|
|
||||||
|
LocalStorage.clear();
|
||||||
|
router.push("/login");
|
||||||
|
|
||||||
|
closeModal();
|
||||||
|
} else {
|
||||||
|
processError(res.msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 修改用户名,头像
|
||||||
|
const editUserInfo = async () => {
|
||||||
|
errMsg.value = "";
|
||||||
|
const data = {
|
||||||
|
nickName: nickName.value,
|
||||||
|
avatar: avatar.value,
|
||||||
|
password: password.value,
|
||||||
|
oldPwd: oldPwd.value,
|
||||||
|
};
|
||||||
|
await UserApi.updateUserInfo(data).then((res) => {
|
||||||
|
if (res.status === 0) {
|
||||||
|
processSuccess("修改成功");
|
||||||
|
LocalStorage.set("sd-accountInfo", res.data);
|
||||||
|
// 如果是修改密码或者修改手机号,需要重新登录
|
||||||
|
if (title.value === "修改密码") {
|
||||||
|
LocalStorage.clear();
|
||||||
|
router.push("/login");
|
||||||
|
} else {
|
||||||
|
// 刷新页面
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
closeModal();
|
||||||
|
} else {
|
||||||
|
processError(res.msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 判断操作
|
||||||
|
const doAction = () => {
|
||||||
|
if (title.value === "修改用户名") {
|
||||||
|
// 判断用户名是否为空
|
||||||
|
if (!nickName.value) {
|
||||||
|
isErr.value = true;
|
||||||
|
errMsg.value = "用户名不能为空";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 判断用户名是否合法
|
||||||
|
if (!/^[\u4e00-\u9fa5a-zA-Z0-9]{1,10}$/.test(nickName.value)) {
|
||||||
|
isErr.value = true;
|
||||||
|
errMsg.value = "用户名格式错误";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editUserInfo();
|
||||||
|
} else if (title.value === "修改头像") {
|
||||||
|
if (!avatar.value) {
|
||||||
|
isErr.value = true;
|
||||||
|
errMsg.value = "请上传头像";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editUserInfo();
|
||||||
|
} else if (title.value === "修改密码") {
|
||||||
|
if (!oldPwd.value) {
|
||||||
|
isErr.value = true;
|
||||||
|
errMsg.value = "旧密码不能为空";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!password.value) {
|
||||||
|
isErr.value = true;
|
||||||
|
errMsg.value = "新密码不能为空";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!confirmPwd.value) {
|
||||||
|
isErr.value = true;
|
||||||
|
errMsg.value = "确认密码不能为空";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (password.value !== confirmPwd.value) {
|
||||||
|
isErr.value = true;
|
||||||
|
errMsg.value = "两次密码不一致";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editUserInfo();
|
||||||
|
} else if (title.value === "修改手机号") {
|
||||||
|
if (!newTelNum.value) {
|
||||||
|
isErr.value = true;
|
||||||
|
errMsg.value = "手机号不能为空";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!code.value) {
|
||||||
|
isErr.value = true;
|
||||||
|
errMsg.value = "验证码不能为空";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editTelNum();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
onBeforeMount(() => {});
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.q-page {
|
||||||
|
background: #19191a;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.top-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 340px;
|
||||||
|
position: relative;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.txt {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0%;
|
||||||
|
left: 2%;
|
||||||
|
font-weight: bolder;
|
||||||
|
color: #fff;
|
||||||
|
text-align: left div {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.create {
|
||||||
|
margin-top: 60px;
|
||||||
|
width: 260px;
|
||||||
|
height: 55px;
|
||||||
|
background: linear-gradient(260deg, #c448b8, #591df5);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: bolder;
|
||||||
|
font-size: 18px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(to bottom, #39273c, #000000);
|
||||||
|
.item {
|
||||||
|
margin: 26px 0 0 32px;
|
||||||
|
width: 977px;
|
||||||
|
min-height: 60px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #212121;
|
||||||
|
font-size: 18px;
|
||||||
|
|
||||||
|
.txt {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
span {
|
||||||
|
color: #bebebe;
|
||||||
|
}
|
||||||
|
.tx1 {
|
||||||
|
padding-right: 30px;
|
||||||
|
border-right: 1px solid #707070;
|
||||||
|
}
|
||||||
|
.tx2 {
|
||||||
|
padding-left: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
color: #c348b8;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.content-box {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 18px;
|
||||||
|
margin-top: 65px;
|
||||||
|
color: #bebebe;
|
||||||
|
}
|
||||||
|
.input-box {
|
||||||
|
width: 370px;
|
||||||
|
margin-left: 35px;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
.account-box {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-top: 30px;
|
||||||
|
|
||||||
|
.account {
|
||||||
|
color: #bebebe;
|
||||||
|
transform: translateX(-120%);
|
||||||
|
}
|
||||||
|
.input-box {
|
||||||
|
width: 370px;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.code-btn {
|
||||||
|
margin-left: 20px;
|
||||||
|
background: linear-gradient(to right, #591df5, #c448b8);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: bold;
|
||||||
|
border: 8px;
|
||||||
|
height: 45px;
|
||||||
|
min-width: 125px;
|
||||||
|
}
|
||||||
|
:deep(.q-field__inner) {
|
||||||
|
background: #212121;
|
||||||
|
padding: 0 10px 0 10px;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-clip: padding-box, border-box;
|
||||||
|
background-origin: padding-box, border-box;
|
||||||
|
background-image: linear-gradient(to right, #222, #222),
|
||||||
|
linear-gradient(to right, #61d2ef, #e44ea8);
|
||||||
|
.q-field__native {
|
||||||
|
color: #7e7e7e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:deep(.n-upload-dragger) {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
:deep(.q-field--standard .q-field__control:after) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
334
src/pages/mine/components/reward.vue
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
<template>
|
||||||
|
<q-page>
|
||||||
|
<div class="main-box">
|
||||||
|
<div class="search-box">
|
||||||
|
<div style="display: flex; align-items: center">
|
||||||
|
转入时间
|
||||||
|
<n-date-picker
|
||||||
|
class="date-picker"
|
||||||
|
:actions="null"
|
||||||
|
v-model:value="createdTime"
|
||||||
|
start-placeholder="开始时间"
|
||||||
|
end-placeholder="结束时间"
|
||||||
|
type="daterange"
|
||||||
|
format="yyyy-MM-dd"
|
||||||
|
clearable
|
||||||
|
:close-on-select="true"
|
||||||
|
:update-value-on-close="true"
|
||||||
|
:on-update:value="handleDateChange"
|
||||||
|
/>
|
||||||
|
到期时间
|
||||||
|
<n-date-picker
|
||||||
|
class="date-picker"
|
||||||
|
:actions="null"
|
||||||
|
v-model:value="expireTime"
|
||||||
|
start-placeholder="开始时间"
|
||||||
|
end-placeholder="结束时间"
|
||||||
|
type="daterange"
|
||||||
|
format="yyyy-MM-dd"
|
||||||
|
clearable
|
||||||
|
:close-on-select="true"
|
||||||
|
:update-value-on-close="true"
|
||||||
|
:on-update:value="handleExDateChange"
|
||||||
|
/>
|
||||||
|
状态
|
||||||
|
|
||||||
|
<n-select
|
||||||
|
style="width: 200px; margin-left: 20px"
|
||||||
|
v-model:value="type"
|
||||||
|
:options="options"
|
||||||
|
:on-update:value="changeType"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<n-data-table
|
||||||
|
style="margin-top: 40px"
|
||||||
|
:bordered="false"
|
||||||
|
:columns="columns"
|
||||||
|
:data="tableData"
|
||||||
|
:loading="loading"
|
||||||
|
:on-update:sorter="sorter"
|
||||||
|
/>
|
||||||
|
<div class="page">
|
||||||
|
共{{ total }}条数据
|
||||||
|
<n-pagination
|
||||||
|
style="margin-left: 20px"
|
||||||
|
v-model:page="page"
|
||||||
|
:page-count="pageCount"
|
||||||
|
size="medium"
|
||||||
|
show-quick-jumper
|
||||||
|
:on-update:page="pageChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {
|
||||||
|
ref,
|
||||||
|
defineProps,
|
||||||
|
computed,
|
||||||
|
onBeforeMount,
|
||||||
|
watch,
|
||||||
|
reactive,
|
||||||
|
h,
|
||||||
|
} from "vue";
|
||||||
|
import { LocalStorage, SessionStorage } from "quasar";
|
||||||
|
import { processError, processSuccess } from "../../../utils/message";
|
||||||
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
import { ScoreApi } from "src/api";
|
||||||
|
const createdTime = ref("");
|
||||||
|
const createdAtStart = ref("");
|
||||||
|
const createdAtEnd = ref("");
|
||||||
|
const expireTime = ref("");
|
||||||
|
const expireAtStart = ref("");
|
||||||
|
const expireAtEnd = ref("");
|
||||||
|
const orderBy = ref("");
|
||||||
|
const loading = ref(false);
|
||||||
|
const page = ref(1);
|
||||||
|
const tableData = ref([]);
|
||||||
|
const pageCount = ref(0);
|
||||||
|
const total = ref(0);
|
||||||
|
const type = ref(0);
|
||||||
|
const options = [
|
||||||
|
{ label: "全部", value: 0 },
|
||||||
|
{ label: "未过期", value: 1 },
|
||||||
|
{ label: "已过期", value: 2 },
|
||||||
|
];
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: "转入时间",
|
||||||
|
key: "createdAt",
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "订单号",
|
||||||
|
key: "orderNum",
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "转入积分 ",
|
||||||
|
key: "coin",
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "来源",
|
||||||
|
key: "scene",
|
||||||
|
width: 200,
|
||||||
|
render: (row) => {
|
||||||
|
return h(
|
||||||
|
"div",
|
||||||
|
|
||||||
|
row.scene === "register" ? "新用户注册奖励" : "每日登录奖励"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "到期时间",
|
||||||
|
key: "expiredAt",
|
||||||
|
width: 200,
|
||||||
|
sorter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "订单状态",
|
||||||
|
key: "isExpired",
|
||||||
|
width: 200,
|
||||||
|
// 如果是0,显示红色字失败,否则显示绿色字成功
|
||||||
|
render: (row) => {
|
||||||
|
return h(
|
||||||
|
"div",
|
||||||
|
{ style: { color: row.isExpired ? "#C348B8" : "#61C8EF" } },
|
||||||
|
row.isExpired ? "已过期" : "未过期"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const sorter = (sort) => {
|
||||||
|
console.log(sort);
|
||||||
|
let txt = sort.order === "ascend" ? "asc" : "desc";
|
||||||
|
orderBy.value ="expired_at "+ txt;
|
||||||
|
console.log(orderBy.value);
|
||||||
|
getCostList();
|
||||||
|
};
|
||||||
|
const changeType = (val) => {
|
||||||
|
type.value = val;
|
||||||
|
getCostList();
|
||||||
|
};
|
||||||
|
const pageChange = (val) => {
|
||||||
|
page.value = val;
|
||||||
|
getCostList();
|
||||||
|
};
|
||||||
|
// 获取积分列表
|
||||||
|
const getCostList = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
const params = {
|
||||||
|
page: page.value,
|
||||||
|
pageSize: 10,
|
||||||
|
startCreatedAt: createdAtStart.value,
|
||||||
|
endCreatedAt: createdAtEnd.value,
|
||||||
|
startExpireAt: expireAtStart.value,
|
||||||
|
endExpireAt: expireAtEnd.value,
|
||||||
|
scene: "daily_login",
|
||||||
|
status: type.value,
|
||||||
|
orderBy: orderBy.value,
|
||||||
|
};
|
||||||
|
await ScoreApi.getScoreList(params).then((res) => {
|
||||||
|
if (res.code === 200) {
|
||||||
|
loading.value = false;
|
||||||
|
total.value = res.data.count;
|
||||||
|
tableData.value = res.data.list || [];
|
||||||
|
pageCount.value = Math.ceil(res.data.count / 10);
|
||||||
|
} else {
|
||||||
|
loading.value = false;
|
||||||
|
processError(res);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const handleDateChange = (val, formattedValue) => {
|
||||||
|
if (formattedValue) {
|
||||||
|
createdAtStart.value = formattedValue[0];
|
||||||
|
createdAtEnd.value = formattedValue[1];
|
||||||
|
} else {
|
||||||
|
createdAtStart.value = "";
|
||||||
|
createdAtEnd.value = "";
|
||||||
|
}
|
||||||
|
getCostList();
|
||||||
|
};
|
||||||
|
const handleExDateChange = (val, formattedValue) => {
|
||||||
|
if (formattedValue) {
|
||||||
|
expireAtStart.value = formattedValue[0];
|
||||||
|
expireAtEnd.value = formattedValue[1];
|
||||||
|
} else {
|
||||||
|
expireAtStart.value = "";
|
||||||
|
expireAtEnd.value = "";
|
||||||
|
}
|
||||||
|
getCostList();
|
||||||
|
};
|
||||||
|
onBeforeMount(() => {
|
||||||
|
getCostList();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.q-page {
|
||||||
|
background: #19191a;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.main-box {
|
||||||
|
padding: 28px;
|
||||||
|
background: #212121;
|
||||||
|
margin: 15px;
|
||||||
|
height: 98%;
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 98%;
|
||||||
|
}
|
||||||
|
.search-box {
|
||||||
|
color: #ffffff;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
.date-picker {
|
||||||
|
margin-left: 20px;
|
||||||
|
width: 300px;
|
||||||
|
margin-right: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.page {
|
||||||
|
display: flex;
|
||||||
|
justify-content: end;
|
||||||
|
margin-top: 20px;
|
||||||
|
align-items: center;
|
||||||
|
color: #7e7e7e;
|
||||||
|
}
|
||||||
|
:deep(.n-input) {
|
||||||
|
background: #383838;
|
||||||
|
border: 1px solid #7e7e7e;
|
||||||
|
}
|
||||||
|
:deep(.n-input__input-el) {
|
||||||
|
color: #bebebe;
|
||||||
|
}
|
||||||
|
:deep(.n-input .n-input__border) {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
// 更改placeholder的颜色
|
||||||
|
:deep(.n-input__placeholder) {
|
||||||
|
color: #7e7e7e;
|
||||||
|
}
|
||||||
|
:deep(.n-input__textarea-el) {
|
||||||
|
color: #7e7e7e;
|
||||||
|
}
|
||||||
|
:deep(.n-input__input-el) {
|
||||||
|
color: #7e7e7e;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-base-selection-input__content) {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
:deep(.n-base-selection-label) {
|
||||||
|
height: 36px;
|
||||||
|
background-color: #383838;
|
||||||
|
width: 200px;
|
||||||
|
color: #7e7e7e;
|
||||||
|
}
|
||||||
|
:deep(.n-base-selection .n-base-selection__border) {
|
||||||
|
border: 1px solid #7e7e7e;
|
||||||
|
}
|
||||||
|
:deep(.n-base-selection-input__content) {
|
||||||
|
color: #7e7e7e;
|
||||||
|
}
|
||||||
|
:deep(
|
||||||
|
.n-pagination
|
||||||
|
.n-pagination-item.n-pagination-item--disabled.n-pagination-item--button
|
||||||
|
) {
|
||||||
|
background-color: #19191a;
|
||||||
|
}
|
||||||
|
:deep(
|
||||||
|
.n-pagination
|
||||||
|
.n-pagination-item:not(
|
||||||
|
.n-pagination-item--disabled
|
||||||
|
).n-pagination-item--active
|
||||||
|
) {
|
||||||
|
background-color: #c348b8;
|
||||||
|
color: #fff;
|
||||||
|
border: 1px solid #c348b8;
|
||||||
|
}
|
||||||
|
:deep(.n-pagination .n-pagination-item) {
|
||||||
|
color: #7e7e7e;
|
||||||
|
}
|
||||||
|
:deep(.n-data-table-td) {
|
||||||
|
--n-th-color-hover: #383838;
|
||||||
|
background-color: #000000;
|
||||||
|
text-align: center;
|
||||||
|
color: #7e7e7e;
|
||||||
|
border-bottom: 1px solid #383838;
|
||||||
|
}
|
||||||
|
:deep(.n-data-table-th) {
|
||||||
|
border: 0;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ffffff;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
:deep(.n-data-table) {
|
||||||
|
--n-th-color: #c448b8 !important;
|
||||||
|
--n-td-color-hover: #000000 !important;
|
||||||
|
--n-loading-color: #c448b8 !important;
|
||||||
|
--n-th-color-hover: #c448b8 !important;
|
||||||
|
--n-td-padding: 17px !important;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
:deep(
|
||||||
|
.n-data-table.n-data-table--bottom-bordered
|
||||||
|
.n-data-table-td.n-data-table-td--last-row
|
||||||
|
) {
|
||||||
|
border: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-scrollbar-container) {
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
153
src/pages/mine/index.vue
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
<template>
|
||||||
|
<q-page>
|
||||||
|
<div class="silder">
|
||||||
|
<div class="menu-box">
|
||||||
|
<n-menu
|
||||||
|
ref="menuInstRef"
|
||||||
|
:options="menuOptions"
|
||||||
|
:value="selectedKeys"
|
||||||
|
@update:value="handleUpdateExpandedKeys"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; width: 89%">
|
||||||
|
<mineInfo
|
||||||
|
v-if="selectedKeys === 'mineCenter'"
|
||||||
|
class="animate__animated animate__fadeIn"
|
||||||
|
/>
|
||||||
|
<cost
|
||||||
|
v-if="selectedKeys === 'pointDetail'"
|
||||||
|
class="animate__animated animate__fadeIn"
|
||||||
|
/>
|
||||||
|
<reward
|
||||||
|
v-if="selectedKeys === 'rewardDetail'"
|
||||||
|
class="animate__animated animate__fadeIn"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, defineProps, computed, onBeforeMount, watch, h } from "vue";
|
||||||
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
import mineInfo from "./components/mineInfo.vue";
|
||||||
|
import cost from "./components/cost.vue";
|
||||||
|
import reward from "./components/reward.vue";
|
||||||
|
// 创建一个图片vNode
|
||||||
|
|
||||||
|
const mineIcon = () => {
|
||||||
|
return h("img", {
|
||||||
|
src: "https://cdns.fontree.cn/blockchain-main-test/dev/image/default/ai/3e237fcc-78a3-48dc-94d5-dee25b753f0b.png",
|
||||||
|
alt: "coin",
|
||||||
|
style: {
|
||||||
|
width: "20px",
|
||||||
|
height: "20px",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const pointIcon = () => {
|
||||||
|
return h("img", {
|
||||||
|
src: "https://cdns.fontree.cn/blockchain-main-test/dev/image/default/ai/23163654-3fb2-45f0-97ac-a48aada80dcd.png",
|
||||||
|
alt: "coin",
|
||||||
|
style: {
|
||||||
|
width: "20px",
|
||||||
|
height: "20px",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const router = useRouter();
|
||||||
|
const selectedKeys = ref("");
|
||||||
|
const menuInstRef = ref("");
|
||||||
|
const menuOptions = [
|
||||||
|
{
|
||||||
|
label: "积分明细",
|
||||||
|
key: "point",
|
||||||
|
icon: pointIcon,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: "消费积分明细",
|
||||||
|
key: "pointDetail",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "奖励积分明细",
|
||||||
|
key: "rewardDetail",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
label: "个人中心",
|
||||||
|
key: "mineCenter",
|
||||||
|
icon: mineIcon,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const handleUpdateExpandedKeys = (keys) => {
|
||||||
|
selectedKeys.value = keys;
|
||||||
|
// 路由参数变化
|
||||||
|
router.push({
|
||||||
|
query: {
|
||||||
|
active: keys,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 监听路由参数变化
|
||||||
|
watch(
|
||||||
|
() => router.currentRoute.value.query.active,
|
||||||
|
(val) => {
|
||||||
|
selectedKeys.value = val;
|
||||||
|
menuInstRef.value?.showOption(val);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
onBeforeMount(() => {
|
||||||
|
selectedKeys.value = router.currentRoute.value.query.active;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.q-page {
|
||||||
|
background: #19191a;
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.silder {
|
||||||
|
width: 220px;
|
||||||
|
background: #212121;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
.menu-box {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: 30px;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:deep(.n-menu) {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
:deep(.n-menu-item-content-header) {
|
||||||
|
color: #fff !important;
|
||||||
|
text-align: center;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
:deep(.n-menu) {
|
||||||
|
--n-item-color-hover: #be45ba !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-menu .n-menu-item-content.n-menu-item-content--selected::before) {
|
||||||
|
background: linear-gradient(to right, #be45ba, #5d1ef1);
|
||||||
|
}
|
||||||
|
:deep(.n-menu-item-content__arrow) {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
:deep(.--n-item-color-hover) {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
30
src/router/index.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { route } from 'quasar/wrappers'
|
||||||
|
import { createRouter, createMemoryHistory, createWebHistory, createWebHashHistory } from 'vue-router'
|
||||||
|
import routes from './routes'
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If not building with SSR mode, you can
|
||||||
|
* directly export the Router instantiation;
|
||||||
|
*
|
||||||
|
* The function below can be async too; either use
|
||||||
|
* async/await or return a Promise which resolves
|
||||||
|
* with the Router instance.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default route(function (/* { store, ssrContext } */) {
|
||||||
|
const createHistory = process.env.SERVER
|
||||||
|
? createMemoryHistory
|
||||||
|
: (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory)
|
||||||
|
|
||||||
|
const Router = createRouter({
|
||||||
|
scrollBehavior: () => ({ left: 0, top: 0 }),
|
||||||
|
routes,
|
||||||
|
|
||||||
|
// Leave this as is and make changes in quasar.conf.js instead!
|
||||||
|
// quasar.conf.js -> build -> vueRouterMode
|
||||||
|
// quasar.conf.js -> build -> publicPath
|
||||||
|
history: createHistory(process.env.VUE_ROUTER_BASE)
|
||||||
|
})
|
||||||
|
|
||||||
|
return Router
|
||||||
|
})
|
56
src/router/routes.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: () => import('layouts/MainLayout.vue'),
|
||||||
|
children: [
|
||||||
|
{ path: '', component: () => import('pages/create/index.vue') }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// 登录
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
children: [
|
||||||
|
{ path: '', component: () => import('pages/Login.vue') }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// 历史
|
||||||
|
{
|
||||||
|
path: '/history',
|
||||||
|
component: () => import('layouts/MainLayout.vue'),
|
||||||
|
children: [
|
||||||
|
{ path: '', component: () => import('pages/history/index.vue') }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// AICG
|
||||||
|
{
|
||||||
|
path: '/aicg',
|
||||||
|
component: () => import('layouts/MainLayout.vue'),
|
||||||
|
children: [
|
||||||
|
{ path: '', component: () => import('pages/aicg/index.vue') }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// 个人中心
|
||||||
|
{
|
||||||
|
path: '/mine',
|
||||||
|
component: () => import('layouts/MainLayout.vue'),
|
||||||
|
children: [
|
||||||
|
{ path: '', component: () => import('pages/mine/index.vue') }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// 留言反馈
|
||||||
|
{
|
||||||
|
path: '/feedback',
|
||||||
|
component: () => import('layouts/MainLayout.vue'),
|
||||||
|
children: [
|
||||||
|
{ path: '', component: () => import('pages/feedback/index.vue') }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// Always leave this as last one,
|
||||||
|
// but you can also remove it
|
||||||
|
{
|
||||||
|
path: '/:catchAll(.*)*',
|
||||||
|
component: () => import('pages/ErrorNotFound.vue')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export default routes
|
54
src/utils/auto-update.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
|
||||||
|
|
||||||
|
let lastSrcs;
|
||||||
|
|
||||||
|
const scriptReg = /\<script.*src=["'](?<src>[^"']+)/gm;
|
||||||
|
|
||||||
|
// 获取最新的 script src
|
||||||
|
async function extractNewScripts() {
|
||||||
|
const html = await fetch("/?_timestamp=" + Date.now()).then((res) =>
|
||||||
|
res.text()
|
||||||
|
);
|
||||||
|
scriptReg.lastIndex = 0;
|
||||||
|
let result = [];
|
||||||
|
let match;
|
||||||
|
while ((match = scriptReg.exec(html))) {
|
||||||
|
result.push(match.groups.src);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否需要更新
|
||||||
|
async function needUpdate() {
|
||||||
|
const newScripts = await extractNewScripts();
|
||||||
|
if (!lastSrcs) {
|
||||||
|
lastSrcs = newScripts;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let result = false;
|
||||||
|
if (newScripts.length !== lastSrcs.length) {
|
||||||
|
result = true;
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < newScripts.length; i++) {
|
||||||
|
if (newScripts[i] !== lastSrcs[i]) {
|
||||||
|
result = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastSrcs = newScripts;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
// 间隔五分钟
|
||||||
|
const DURATION = 1000 * 60 * 5;
|
||||||
|
function autoRefresh() {
|
||||||
|
setTimeout(async () => {
|
||||||
|
const willUpdate = await needUpdate();
|
||||||
|
if (willUpdate) {
|
||||||
|
|
||||||
|
}
|
||||||
|
autoRefresh();
|
||||||
|
}, DURATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
autoRefresh();
|
43
src/utils/downLoad.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
export const downLoadByblob = (blob, fileName) => {
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = url;
|
||||||
|
link.download = fileName;
|
||||||
|
link.style.display = "none";
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
};
|
||||||
|
export const downLoadByUrl = (url, rowData, filename, timeflag = true) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
// 这里加 ?v= 能绕过 跨域????
|
||||||
|
if (timeflag && !url.includes("?v=")) {
|
||||||
|
url = url + "?v=" + new Date().getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(url, { mode: "cors" }).then((res) => {
|
||||||
|
res.blob().then((blob) => {
|
||||||
|
const blobUrl = window.URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = blobUrl;
|
||||||
|
if (Array.isArray(filename)) {
|
||||||
|
let newName = "";
|
||||||
|
filename.forEach((fi) => {
|
||||||
|
newName = newName + " " + (rowData[fi] || fi);
|
||||||
|
});
|
||||||
|
filename = newName;
|
||||||
|
}
|
||||||
|
// a.download必须有值
|
||||||
|
a.download = filename || new Date().getTime();
|
||||||
|
a.click();
|
||||||
|
window.URL.revokeObjectURL(blobUrl);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
22
src/utils/message.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { Notify } from "quasar";
|
||||||
|
export const processSuccess = (res, time = 3) => {
|
||||||
|
Notify.create({
|
||||||
|
message: res,
|
||||||
|
type: "positive",
|
||||||
|
position: "top",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export const processError = (res, time = 3) => {
|
||||||
|
Notify.create({
|
||||||
|
message: res,
|
||||||
|
type: "negative",
|
||||||
|
position: "top",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export const processWarning = (res, time = 3) => {
|
||||||
|
Notify.create({
|
||||||
|
message: res,
|
||||||
|
type: "warning",
|
||||||
|
position: "top",
|
||||||
|
});
|
||||||
|
};
|
181
src/utils/scroll-out.min.js
vendored
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
export const ScrollOut = (function () {
|
||||||
|
"use strict";
|
||||||
|
function w() {}
|
||||||
|
var E,
|
||||||
|
t = [],
|
||||||
|
S = [];
|
||||||
|
function y() {
|
||||||
|
S.slice().forEach(function (e) {
|
||||||
|
return e();
|
||||||
|
});
|
||||||
|
var e = t;
|
||||||
|
(t = []),
|
||||||
|
e.forEach(function (e) {
|
||||||
|
return e.f.apply(0, e.a);
|
||||||
|
}),
|
||||||
|
(E = S.length ? requestAnimationFrame(y) : 0);
|
||||||
|
}
|
||||||
|
function D(e) {
|
||||||
|
return (
|
||||||
|
(e = e || w),
|
||||||
|
function () {
|
||||||
|
e.apply(0, arguments);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function L(e, t, n) {
|
||||||
|
return e < t ? t : n < e ? n : e;
|
||||||
|
}
|
||||||
|
function P(e) {
|
||||||
|
return (0 < e) - (e < 0);
|
||||||
|
}
|
||||||
|
var n = {};
|
||||||
|
function b(e) {
|
||||||
|
return n[e] || (n[e] = e.replace(/([A-Z])/g, r));
|
||||||
|
}
|
||||||
|
function r(e) {
|
||||||
|
return "-" + e[0].toLowerCase();
|
||||||
|
}
|
||||||
|
var A = window,
|
||||||
|
H = document.documentElement;
|
||||||
|
function O(e, t) {
|
||||||
|
return e && 0 != e.length
|
||||||
|
? e.nodeName
|
||||||
|
? [e]
|
||||||
|
: [].slice.call(e[0].nodeName ? e : (t || H).querySelectorAll(e))
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
var x = D(function (e, t) {
|
||||||
|
for (var n in t) e.setAttribute("data-" + b(n), t[n]);
|
||||||
|
}),
|
||||||
|
W = "scroll",
|
||||||
|
N = "resize",
|
||||||
|
T = "addEventListener",
|
||||||
|
$ = "removeEventListener",
|
||||||
|
_ = 0;
|
||||||
|
return function (h) {
|
||||||
|
var o,
|
||||||
|
i,
|
||||||
|
c,
|
||||||
|
l,
|
||||||
|
d,
|
||||||
|
p,
|
||||||
|
t,
|
||||||
|
s = D((h = h || {}).onChange),
|
||||||
|
f = D(h.onHidden),
|
||||||
|
u = D(h.onShown),
|
||||||
|
a = h.cssProps
|
||||||
|
? ((o = h.cssProps),
|
||||||
|
D(function (e, t) {
|
||||||
|
for (var n in t)
|
||||||
|
(1 == o || o[n]) &&
|
||||||
|
e.style.setProperty(
|
||||||
|
"--" + b(n),
|
||||||
|
((r = t[n]), Math.round(1e4 * r) / 1e4)
|
||||||
|
);
|
||||||
|
var r;
|
||||||
|
}))
|
||||||
|
: w,
|
||||||
|
e = h.scrollingElement,
|
||||||
|
g = e ? O(e)[0] : A,
|
||||||
|
m = e ? O(e)[0] : H,
|
||||||
|
r = ++_,
|
||||||
|
v = function (e, t, n) {
|
||||||
|
return e[t + r] != (e[t + r] = JSON.stringify(n));
|
||||||
|
},
|
||||||
|
n = function () {
|
||||||
|
l = !0;
|
||||||
|
},
|
||||||
|
X = function () {
|
||||||
|
var a = m.clientWidth,
|
||||||
|
v = m.clientHeight,
|
||||||
|
e = P(-d + (d = m.scrollLeft || A.pageXOffset)),
|
||||||
|
t = P(-p + (p = m.scrollTop || A.pageYOffset)),
|
||||||
|
n = m.scrollLeft / (m.scrollWidth - a || 1),
|
||||||
|
r = m.scrollTop / (m.scrollHeight - v || 1);
|
||||||
|
(i = {
|
||||||
|
scrollDirX: e,
|
||||||
|
scrollDirY: t,
|
||||||
|
scrollPercentX: n,
|
||||||
|
scrollPercentY: r,
|
||||||
|
}),
|
||||||
|
l &&
|
||||||
|
((l = !1),
|
||||||
|
(c = O(h.targets || "[data-scroll]", O(h.scope || m)[0]).map(
|
||||||
|
function (e) {
|
||||||
|
return { $: e, ctx: {} };
|
||||||
|
}
|
||||||
|
))),
|
||||||
|
c.forEach(function (e) {
|
||||||
|
for (
|
||||||
|
var t = e.$, n = t, r = 0, o = 0;
|
||||||
|
(r += n.offsetLeft),
|
||||||
|
(o += n.offsetTop),
|
||||||
|
(n = n.offsetParent) && n != g;
|
||||||
|
|
||||||
|
);
|
||||||
|
var i = t.clientWidth,
|
||||||
|
c = t.clientHeight,
|
||||||
|
l = (L(r + i, d, d + a) - L(r, d, d + a)) / i,
|
||||||
|
s = (L(o + c, p, p + v) - L(o, p, p + v)) / c,
|
||||||
|
f = L((d - (i / 2 + r - a / 2)) / (a / 2), -1, 1),
|
||||||
|
u = L((p - (c / 2 + o - v / 2)) / (v / 2), -1, 1);
|
||||||
|
e.ctx = {
|
||||||
|
elementHeight: c,
|
||||||
|
elementWidth: i,
|
||||||
|
intersectX: 1 == l ? 0 : P(r - d),
|
||||||
|
intersectY: 1 == s ? 0 : P(o - p),
|
||||||
|
offsetX: r,
|
||||||
|
offsetY: o,
|
||||||
|
viewportX: f,
|
||||||
|
viewportY: u,
|
||||||
|
visibleX: l,
|
||||||
|
visibleY: s,
|
||||||
|
visible: +(h.offset ? h.offset <= p : (h.threshold || 0) < l * s),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
Y =
|
||||||
|
((t = function () {
|
||||||
|
if (c) {
|
||||||
|
v(m, "_S", i) &&
|
||||||
|
(x(m, { scrollDirX: i.scrollDirX, scrollDirY: i.scrollDirY }),
|
||||||
|
a(m, i));
|
||||||
|
for (var e = c.length - 1; -1 < e; e--) {
|
||||||
|
var t = c[e],
|
||||||
|
n = t.$,
|
||||||
|
r = t.ctx,
|
||||||
|
o = r.visible;
|
||||||
|
v(n, "_SO", r) && a(n, r),
|
||||||
|
v(n, "_SV", o) &&
|
||||||
|
(x(n, { scroll: o ? "in" : "out" }),
|
||||||
|
s(n, r, m),
|
||||||
|
(o ? u : f)(n, r, m)),
|
||||||
|
o && h.once && c.splice(e, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
S.push(t),
|
||||||
|
E || y(),
|
||||||
|
function () {
|
||||||
|
!(S = S.filter(function (e) {
|
||||||
|
return e != t;
|
||||||
|
})).length &&
|
||||||
|
E &&
|
||||||
|
cancelAnimationFrame(E);
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
n(),
|
||||||
|
X(),
|
||||||
|
A[T](N, n),
|
||||||
|
g[T](W, X),
|
||||||
|
{
|
||||||
|
index: n,
|
||||||
|
update: X,
|
||||||
|
teardown: function () {
|
||||||
|
Y(), A[$](N, n), g[$](W, X);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
})();
|
53
src/utils/storage.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* window.localStorage 浏览器永久缓存
|
||||||
|
* @method set 设置永久缓存
|
||||||
|
* @method get 获取永久缓存
|
||||||
|
* @method remove 移除永久缓存
|
||||||
|
* @method clear 移除全部永久缓存
|
||||||
|
*/
|
||||||
|
export const Local = {
|
||||||
|
// 设置永久缓存
|
||||||
|
set (key, val) {
|
||||||
|
window.localStorage.setItem(key, JSON.stringify(val));
|
||||||
|
},
|
||||||
|
// 获取永久缓存
|
||||||
|
get (key) {
|
||||||
|
let json = window.localStorage.getItem(key);
|
||||||
|
return json !== 'undefined' ? JSON.parse(json) : '';
|
||||||
|
},
|
||||||
|
// 移除永久缓存
|
||||||
|
remove (key) {
|
||||||
|
window.localStorage.removeItem(key);
|
||||||
|
},
|
||||||
|
// 移除全部永久缓存
|
||||||
|
clear () {
|
||||||
|
window.localStorage.clear();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* window.sessionStorage 浏览器临时缓存
|
||||||
|
* @method set 设置临时缓存
|
||||||
|
* @method get 获取临时缓存
|
||||||
|
* @method remove 移除临时缓存
|
||||||
|
* @method clear 移除全部临时缓存
|
||||||
|
*/
|
||||||
|
export const Session = {
|
||||||
|
// 设置临时缓存
|
||||||
|
set (key, val) {
|
||||||
|
window.sessionStorage.setItem(key, JSON.stringify(val));
|
||||||
|
},
|
||||||
|
// 获取临时缓存
|
||||||
|
get (key) {
|
||||||
|
let json = window.sessionStorage.getItem(key);
|
||||||
|
return JSON.parse(json);
|
||||||
|
},
|
||||||
|
// 移除临时缓存
|
||||||
|
remove (key) {
|
||||||
|
window.sessionStorage.removeItem(key);
|
||||||
|
},
|
||||||
|
// 移除全部临时缓存
|
||||||
|
clear () {
|
||||||
|
window.sessionStorage.clear();
|
||||||
|
},
|
||||||
|
};
|
28
vite.config.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import AutoImport from 'unplugin-auto-import/vite'
|
||||||
|
import Components from 'unplugin-vue-components/vite'
|
||||||
|
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
AutoImport({
|
||||||
|
imports: [
|
||||||
|
'vue',
|
||||||
|
{
|
||||||
|
'naive-ui': [
|
||||||
|
'useDialog',
|
||||||
|
'useMessage',
|
||||||
|
'useNotification',
|
||||||
|
'useLoadingBar'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
Components({
|
||||||
|
resolvers: [NaiveUiResolver()]
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|