2
0

Merge remote-tracking branch 'origin/wfc-modules-user'

This commit is contained in:
lai
2024-11-29 18:32:00 +08:00
70 changed files with 1286 additions and 3960 deletions

9
.env
View File

@@ -1,8 +1,6 @@
VITE_BASE_URL=/
VITE_APP_TITLE=Vue-AntD-Web
VITE_APP_DESC=Vue-AntD-Web
VITE_APP_TITLE="WANFi Platform"
# the prefix of the icon name
VITE_ICON_PREFIX=icon
@@ -12,10 +10,7 @@ VITE_ICON_PREFIX=icon
VITE_ICON_LOCAL_PREFIX=icon-local
# auth route mode: static dynamic
VITE_AUTH_ROUTE_MODE=dynamic
# static auth route home
VITE_ROUTE_HOME=manage_user
VITE_AUTH_ROUTE_MODE=static
# default menu icon
VITE_MENU_ICON=mdi:menu

21
LICENSE
View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2024 SoybeanJS
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,6 +1,4 @@
# Front-End at CRM
## Wfc-Cloud-Vue-AntD-Web(SoybeanAdmin AntDesign)
# WANFI 用户平台
## 使用
@@ -20,6 +18,7 @@ npm install -g pnpm
npm install -g pnpm
pnpm i
```
> 由于本项目采用了 pnpm monorepo 的管理方式,因此请不要使用 npm 或 yarn 来安装依赖。
**启动项目**

View File

@@ -2,7 +2,6 @@ import type { PluginOption } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import progress from 'vite-plugin-progress';
import { setupElegantRouter } from './router';
import { setupUnocss } from './unocss';
import { setupUnplugin } from './unplugin';
@@ -14,7 +13,6 @@ export function setupVitePlugins(viteEnv: Env.ImportMeta) {
}
}),
vueJsx(),
setupElegantRouter(),
setupUnocss(viteEnv),
...setupUnplugin(viteEnv),
progress()

View File

@@ -1,44 +0,0 @@
import type { RouteMeta } from 'vue-router';
import ElegantVueRouter from '@elegant-router/vue/vite';
import type { RouteKey } from '@elegant-router/types';
export function setupElegantRouter() {
return ElegantVueRouter({
layouts: {
base: 'src/layouts/base-layout/index.vue',
blank: 'src/layouts/blank-layout/index.vue'
},
customRoutes: {
names: ['exception_403', 'exception_404', 'exception_500']
},
routePathTransformer(routeName, routePath) {
const key = routeName as RouteKey;
if (key === 'login') {
const modules: UnionKey.LoginModule[] = ['pwd-login', 'code-login', 'register', 'reset-pwd', 'bind-wechat'];
const moduleReg = modules.join('|');
return `/login/:module(${moduleReg})?`;
}
return routePath;
},
onRouteMetaGen(routeName) {
const key = routeName as RouteKey;
const constantRoutes: RouteKey[] = ['login', '403', '404', '500'];
const meta: Partial<RouteMeta> = {
title: key,
i18nKey: `route.${key}` as App.I18n.I18nKey
};
if (constantRoutes.includes(key)) {
meta.constant = true;
}
return meta;
}
});
}

View File

@@ -7,7 +7,6 @@
"build:test": "vite build --mode test",
"dev": "vite --mode test",
"dev:prod": "vite --mode prod",
"gen-route": "sa gen-route",
"lint": "eslint . --fix",
"preview": "vite preview",
"typecheck": "vue-tsc --noEmit --skipLibCheck"
@@ -17,9 +16,7 @@
"@iconify/vue": "4.1.2",
"@sa/axios": "workspace:*",
"@sa/color-palette": "workspace:*",
"@sa/fetch": "workspace:*",
"@sa/hooks": "workspace:*",
"@sa/materials": "workspace:*",
"@sa/utils": "workspace:*",
"@vueuse/core": "10.10.0",
"ant-design-vue": "4.2.2",
@@ -37,7 +34,6 @@
"devDependencies": {
"@elegant-router/vue": "0.3.7",
"@iconify/json": "2.2.217",
"@sa/scripts": "workspace:*",
"@sa/uno-preset": "workspace:*",
"@soybeanjs/eslint-config": "1.3.6",
"@types/lodash-es": "4.17.12",

View File

@@ -1,39 +0,0 @@
import process from 'node:process';
import path from 'node:path';
import { defineConfig } from 'vitepress';
export default defineConfig({
title: 'Soybean Admin',
description: '一个优雅、清新、漂亮的中后台模版',
head: [
['meta', { name: 'author', content: 'Soybean' }],
[
'meta',
{
name: 'keywords',
content: 'soybean, soybean-admin, vite, vue, vue3, soybean-admin docs'
}
],
['link', { rel: 'icon', type: 'image/svg+xml', href: '/logo.svg' }],
[
'meta',
{
name: 'viewport',
content: 'width=device-width,initial-scale=1,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no'
}
],
['link', { rel: 'icon', href: '/favicon.ico' }]
],
srcDir: path.join(process.cwd(), 'packages/docs/src'),
themeConfig: {
logo: '/logo.svg',
socialLinks: [{ icon: 'github', link: 'https://github.com/honghuangdc/soybean-admin' }],
algolia: {
appId: '98WN1RY04S',
apiKey: '13e9f5767b774422a5880723d9c23265',
indexName: 'soybean'
},
nav: [],
sidebar: {}
}
});

View File

@@ -1,32 +0,0 @@
export const qqSvg = `
<svg height="2500" viewBox="-1.94 0 124.879 145.085" width="2101" xmlns="http://www.w3.org/2000/svg">
<path
d="m60.503 142.237c-12.533 0-24.038-4.195-31.445-10.46-3.762 1.124-8.574 2.932-11.61 5.175-2.6 1.918-2.275 3.874-1.807 4.663 2.056 3.47 35.273 2.216 44.862 1.136zm0 0c12.535 0 24.039-4.195 31.447-10.46 3.76 1.124 8.573 2.932 11.61 5.175 2.598 1.918 2.274 3.874 1.805 4.663-2.056 3.47-35.272 2.216-44.862 1.136zm0 0"
fill="#faab07"
/>
<path
d="m60.576 67.119c20.698-.14 37.286-4.147 42.907-5.683 1.34-.367 2.056-1.024 2.056-1.024.005-.189.085-3.37.085-5.01 0-27.634-13.044-55.401-45.124-55.402-32.08.001-45.125 27.769-45.125 55.401 0 1.642.08 4.822.086 5.01 0 0 .583.615 1.65.913 5.19 1.444 22.09 5.65 43.312 5.795zm56.245 23.02c-1.283-4.129-3.034-8.944-4.808-13.568 0 0-1.02-.126-1.537.023-15.913 4.623-35.202 7.57-49.9 7.392h-.153c-14.616.175-33.774-2.737-49.634-7.315-.606-.175-1.802-.1-1.802-.1-1.774 4.624-3.525 9.44-4.808 13.568-6.119 19.69-4.136 27.838-2.627 28.02 3.239.392 12.606-14.821 12.606-14.821 0 15.459 13.957 39.195 45.918 39.413h.848c31.96-.218 45.917-23.954 45.917-39.413 0 0 9.368 15.213 12.607 14.822 1.508-.183 3.491-8.332-2.627-28.021"
/>
<path
d="m49.085 40.824c-4.352.197-8.07-4.76-8.304-11.063-.236-6.305 3.098-11.576 7.45-11.773 4.347-.195 8.064 4.76 8.3 11.065.238 6.306-3.097 11.577-7.446 11.771m31.133-11.063c-.233 6.302-3.951 11.26-8.303 11.063-4.35-.195-7.684-5.465-7.446-11.77.236-6.305 3.952-11.26 8.3-11.066 4.352.197 7.686 5.468 7.449 11.773"
fill="#fff"
/>
<path
d="m87.952 49.725c-1.162-2.575-12.875-5.445-27.374-5.445h-.156c-14.5 0-26.212 2.87-27.375 5.446a.863.863 0 0 0 -.085.367c0 .186.063.352.16.496.98 1.427 13.985 8.487 27.3 8.487h.156c13.314 0 26.319-7.058 27.299-8.487a.873.873 0 0 0 .16-.498.856.856 0 0 0 -.085-.365"
fill="#faab07"
/>
<path
d="m54.434 29.854c.199 2.49-1.167 4.702-3.046 4.943-1.883.242-3.568-1.58-3.768-4.07-.197-2.492 1.167-4.704 3.043-4.944 1.886-.244 3.574 1.58 3.771 4.07m11.956.833c.385-.689 3.004-4.312 8.427-2.993 1.425.347 2.084.857 2.223 1.057.205.296.262.718.053 1.286-.412 1.126-1.263 1.095-1.734.875-.305-.142-4.082-2.66-7.562 1.097-.24.257-.668.346-1.073.04-.407-.308-.574-.93-.334-1.362"
/>
<path
d="m60.576 83.08h-.153c-9.996.12-22.116-1.204-33.854-3.518-1.004 5.818-1.61 13.132-1.09 21.853 1.316 22.043 14.407 35.9 34.614 36.1h.82c20.208-.2 33.298-14.057 34.616-36.1.52-8.723-.087-16.035-1.092-21.854-11.739 2.315-23.862 3.64-33.86 3.518"
fill="#fff"
/>
<g fill="#eb1923">
<path d="m32.102 81.235v21.693s9.937 2.004 19.893.616v-20.009c-6.307-.357-13.109-1.152-19.893-2.3" />
<path
d="m105.539 60.412s-19.33 6.102-44.963 6.275h-.153c-25.591-.172-44.896-6.255-44.962-6.275l-6.474 16.158c16.193 4.882 36.261 8.028 51.436 7.845h.153c15.175.183 35.242-2.963 51.437-7.845zm0 0"
/>
</g>
</svg>
`;

View File

@@ -1,4 +0,0 @@
import Theme from 'vitepress/theme';
import './style.css';
export default Theme;

View File

@@ -1,86 +0,0 @@
/**
* Customize default theme styling by overriding CSS variables:
* https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css
*/
/**
* Colors
* -------------------------------------------------------------------------- */
:root {
--vp-c-brand: #646cff;
--vp-c-brand-light: #747bff;
--vp-c-brand-lighter: #9499ff;
--vp-c-brand-lightest: #bcc0ff;
--vp-c-brand-dark: #535bf2;
--vp-c-brand-darker: #454ce1;
--vp-c-brand-dimm: rgba(100, 108, 255, 0.08);
}
/**
* Component: Button
* -------------------------------------------------------------------------- */
:root {
--vp-button-brand-border: var(--vp-c-brand-light);
--vp-button-brand-text: var(--vp-c-white);
--vp-button-brand-bg: var(--vp-c-brand);
--vp-button-brand-hover-border: var(--vp-c-brand-light);
--vp-button-brand-hover-text: var(--vp-c-white);
--vp-button-brand-hover-bg: var(--vp-c-brand-light);
--vp-button-brand-active-border: var(--vp-c-brand-light);
--vp-button-brand-active-text: var(--vp-c-white);
--vp-button-brand-active-bg: var(--vp-button-brand-bg);
}
/**
* Component: Home
* -------------------------------------------------------------------------- */
:root {
--vp-home-hero-name-color: transparent;
--vp-home-hero-name-background: -webkit-linear-gradient(
120deg,
var(--vp-c-brand-lightest) 30%,
var(--vp-c-brand-darker)
);
--vp-home-hero-image-background-image: linear-gradient(-45deg, var(--vp-c-brand-lightest) 30%, var(--vp-c-brand) 50%);
--vp-home-hero-image-filter: blur(40px);
}
@media (min-width: 640px) {
:root {
--vp-home-hero-image-filter: blur(56px);
}
}
@media (min-width: 960px) {
:root {
--vp-home-hero-image-filter: blur(72px);
}
}
/**
* Component: Custom Block
* -------------------------------------------------------------------------- */
:root {
--vp-custom-block-tip-border: var(--vp-c-brand);
--vp-custom-block-tip-text: var(--vp-c-brand-darker);
--vp-custom-block-tip-bg: var(--vp-c-brand-dimm);
}
.dark {
--vp-custom-block-tip-border: var(--vp-c-brand);
--vp-custom-block-tip-text: var(--vp-c-brand-lightest);
--vp-custom-block-tip-bg: var(--vp-c-brand-dimm);
}
/**
* Component: Algolia
* -------------------------------------------------------------------------- */
.DocSearch {
--docsearch-primary-color: var(--vp-c-brand) !important;
}

View File

@@ -1,12 +0,0 @@
{
"name": "@sa/docs",
"version": "1.0.0",
"scripts": {
"build": "vitepress build",
"dev": "vitepress dev",
"serve": "vitepress serve"
},
"devDependencies": {
"vitepress": "1.0.0-rc.36"
}
}

View File

@@ -1,44 +0,0 @@
---
layout: home
title: Soybean Admin
titleTemplate: 一个清新优雅的中后台模版
hero:
name: Soybean Admin
text: 清新优雅的中后台模版
tagline: 基于 Vue3 + Vite3 + TS + NaiveUI + UnoCSS
image:
src: /logo.svg
alt: Soybean Admin
actions:
- theme: brand
text: 开始
link: /guide/
- theme: alt
text: 介绍
link: /guide/introduction
- theme: alt
text: 在 GitHub 上查看
link: https://github.com/honghuangdc/soybean-admin
features:
- icon: 🆕
title: 最新流行技术栈
details: 基于Vue3、Vite3、TS、NaiveUI和UnoCSS等最新技术栈开发
- icon: 🦋
title: 极高水准的代码规范
details: 代码规范完善,代码结构清晰
- icon: 🛠️
title: 丰富的插件
details: 常见的Web端插件示例实现
- icon: 🔩
title: 主题配置
details: 丰富的主题配置及暗黑主题适配
- icon: 🔗
title: 基于文件的路由系统
details: 自动生成路由声明、路由导入和路由模块
- icon: 🔑
title: 权限管理
details: 完善的前后端权限管理方案
---

View File

@@ -1,15 +0,0 @@
{
"name": "@sa/fetch",
"version": "1.0.0",
"exports": {
".": "./src/index.ts"
},
"typesVersions": {
"*": {
"*": ["./src/*"]
}
},
"dependencies": {
"ofetch": "1.3.4"
}
}

View File

@@ -1,10 +0,0 @@
import { ofetch } from 'ofetch';
import type { FetchOptions } from 'ofetch';
export function createRequest(options: FetchOptions) {
const request = ofetch.create(options);
return request;
}
export default createRequest;

View File

@@ -1,20 +0,0 @@
{
"compilerOptions": {
"target": "ESNext",
"jsx": "preserve",
"lib": ["DOM", "ESNext"],
"baseUrl": ".",
"module": "ESNext",
"moduleResolution": "node",
"resolveJsonModule": true,
"types": ["node"],
"strict": true,
"strictNullChecks": true,
"noUnusedLocals": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -1,3 +0,0 @@
#!/usr/bin/env tsx
import './src/index.ts';

View File

@@ -1,27 +0,0 @@
{
"name": "@sa/scripts",
"version": "1.0.0",
"bin": {
"sa": "./bin.ts"
},
"exports": {
".": "./src/index.ts"
},
"typesVersions": {
"*": {
"*": ["./src/*"]
}
},
"devDependencies": {
"@soybeanjs/changelog": "0.3.22",
"bumpp": "9.4.0",
"c12": "1.10.0",
"cac": "6.7.14",
"consola": "3.2.3",
"enquirer": "^2.4.1",
"execa": "8.0.1",
"kolorist": "1.8.0",
"npm-check-updates": "16.14.18",
"rimraf": "5.0.5"
}
}

View File

@@ -1,10 +0,0 @@
import { generateChangelog, generateTotalChangelog } from '@soybeanjs/changelog';
import type { ChangelogOption } from '@soybeanjs/changelog';
export async function genChangelog(options?: Partial<ChangelogOption>, total = false) {
if (total) {
await generateTotalChangelog(options);
} else {
await generateChangelog(options);
}
}

View File

@@ -1,5 +0,0 @@
import { rimraf } from 'rimraf';
export async function cleanup(paths: string[]) {
await rimraf(paths, { glob: true });
}

View File

@@ -1,86 +0,0 @@
import path from 'node:path';
import { readFileSync } from 'node:fs';
import { prompt } from 'enquirer';
import { bgRed, green, red, yellow } from 'kolorist';
import { execCommand } from '../shared';
import type { CliOption } from '../types';
interface PromptObject {
types: string;
scopes: string;
description: string;
}
/**
* Git commit with Conventional Commits standard
*
* @param gitCommitTypes
* @param gitCommitScopes
*/
export async function gitCommit(
gitCommitTypes: CliOption['gitCommitTypes'],
gitCommitScopes: CliOption['gitCommitScopes']
) {
const typesChoices = gitCommitTypes.map(([value, msg]) => {
const nameWithSuffix = `${value}:`;
const message = `${nameWithSuffix.padEnd(12)}${msg}`;
return {
name: value,
message
};
});
const scopesChoices = gitCommitScopes.map(([value, msg]) => ({
name: value,
message: `${value.padEnd(30)} (${msg})`
}));
const result = await prompt<PromptObject>([
{
name: 'types',
type: 'select',
message: 'Please select a type',
choices: typesChoices
},
{
name: 'scopes',
type: 'select',
message: 'Please select a scope',
choices: scopesChoices
},
{
name: 'description',
type: 'text',
message: `Please enter a description (add prefix ${yellow('!')} to indicate breaking change)`
}
]);
const breaking = result.description.startsWith('!') ? '!' : '';
const description = result.description.replace(/^!/, '').trim();
const commitMsg = `${result.types}(${result.scopes})${breaking}: ${description}`;
await execCommand('git', ['commit', '-m', commitMsg], { stdio: 'inherit' });
}
/** Git commit message verify */
export async function gitCommitVerify() {
const gitPath = await execCommand('git', ['rev-parse', '--show-toplevel']);
const gitMsgPath = path.join(gitPath, '.git', 'COMMIT_EDITMSG');
const commitMsg = readFileSync(gitMsgPath, 'utf8').trim();
const REG_EXP = /(?<type>[a-z]+)(?:\((?<scope>.+)\))?(?<breaking>!)?: (?<description>.+)/i;
if (!REG_EXP.test(commitMsg)) {
throw new Error(
`${bgRed(' ERROR ')} ${red('git commit message must match the Conventional Commits standard!')}\n\n${green(
'Recommended to use the command `pnpm commit` to generate Conventional Commits compliant commit information.\nGet more info about Conventional Commits, follow this link: https://conventionalcommits.org'
)}`
);
}
}

View File

@@ -1,6 +0,0 @@
export * from './git-commit';
export * from './cleanup';
export * from './update-pkg';
export * from './changelog';
export * from './release';
export * from './router';

View File

@@ -1,12 +0,0 @@
import { versionBump } from 'bumpp';
export async function release(execute = 'pnpm sa changelog', push = true) {
await versionBump({
files: ['**/package.json', '!**/node_modules'],
execute,
all: true,
tag: true,
commit: 'chore(projects): release v%s',
push
});
}

View File

@@ -1,90 +0,0 @@
import process from 'node:process';
import path from 'node:path';
import { writeFile } from 'node:fs/promises';
import { existsSync, mkdirSync } from 'node:fs';
import { prompt } from 'enquirer';
import { green, red } from 'kolorist';
interface PromptObject {
routeName: string;
addRouteParams: boolean;
routeParams: string;
}
/** generate route */
export async function generateRoute() {
const result = await prompt<PromptObject>([
{
name: 'routeName',
type: 'text',
message: 'please enter route name',
initial: 'demo-route_child'
},
{
name: 'addRouteParams',
type: 'confirm',
message: 'add route params?',
initial: false
}
]);
if (result.addRouteParams) {
const answers = await prompt<PromptObject>({
name: 'routeParams',
type: 'text',
message: 'please enter route params',
initial: 'id'
});
Object.assign(result, answers);
}
const PAGE_DIR_NAME_PATTERN = /^[\w-]+[0-9a-zA-Z]+$/;
if (!PAGE_DIR_NAME_PATTERN.test(result.routeName)) {
throw new Error(`${red('route name is invalid, it only allow letters, numbers, "-" or "_"')}.
For example:
(1) one level route: ${green('demo-route')}
(2) two level route: ${green('demo-route_child')}
(3) multi level route: ${green('demo-route_child_child')}
(4) group route: ${green('_ignore_demo-route')}'
`);
}
const PARAM_REG = /^\w+$/g;
if (result.routeParams && !PARAM_REG.test(result.routeParams)) {
throw new Error(red('route params is invalid, it only allow letters, numbers or "_".'));
}
const cwd = process.cwd();
const [dir, ...rest] = result.routeName.split('_') as string[];
let routeDir = path.join(cwd, 'src', 'views', dir);
if (rest.length) {
routeDir = path.join(routeDir, rest.join('_'));
}
if (!existsSync(routeDir)) {
mkdirSync(routeDir, { recursive: true });
} else {
throw new Error(red('route already exists'));
}
const fileName = result.routeParams ? `[${result.routeParams}].vue` : 'index.vue';
const vueTemplate = `<script setup lang="ts"></script>
<template>
<div>${result.routeName}</div>
</template>
<style scoped></style>
`;
const filePath = path.join(routeDir, fileName);
await writeFile(filePath, vueTemplate);
}

View File

@@ -1,5 +0,0 @@
import { execCommand } from '../shared';
export async function updatePkg(args: string[] = ['--deep', '-u']) {
execCommand('npx', ['ncu', ...args], { stdio: 'inherit' });
}

View File

@@ -1,53 +0,0 @@
import process from 'node:process';
import { loadConfig } from 'c12';
import type { CliOption } from '../types';
const defaultOptions: CliOption = {
cwd: process.cwd(),
cleanupDirs: [
'**/dist',
'**/package-lock.json',
'**/yarn.lock',
'**/pnpm-lock.yaml',
'**/node_modules',
'!node_modules/**'
],
gitCommitTypes: [
['feat', 'A new feature'],
['fix', 'A bug fix'],
['docs', 'Documentation only changes'],
['style', 'Changes that do not affect the meaning of the code'],
['refactor', 'A code change that neither fixes a bug nor adds a feature'],
['perf', 'A code change that improves performance'],
['test', 'Adding missing tests or correcting existing tests'],
['build', 'Changes that affect the build system or external dependencies'],
['ci', 'Changes to our CI configuration files and scripts'],
['chore', "Other changes that don't modify src or test files"],
['revert', 'Reverts a previous commit']
],
gitCommitScopes: [
['projects', 'project'],
['components', 'components'],
['hooks', 'hook functions'],
['utils', 'utils functions'],
['types', 'TS declaration'],
['styles', 'style'],
['deps', 'project dependencies'],
['release', 'release project'],
['other', 'other changes']
],
ncuCommandArgs: ['--deep', '-u'],
changelogOptions: {}
};
export async function loadCliOptions(overrides?: Partial<CliOption>, cwd = process.cwd()) {
const { config } = await loadConfig<Partial<CliOption>>({
name: 'soybean',
defaults: defaultOptions,
overrides,
cwd,
packageJson: true
});
return config as CliOption;
}

View File

@@ -1,101 +0,0 @@
import cac from 'cac';
import { blue, lightGreen } from 'kolorist';
import { version } from '../package.json';
import { cleanup, genChangelog, generateRoute, gitCommit, gitCommitVerify, release, updatePkg } from './commands';
import { loadCliOptions } from './config';
type Command = 'cleanup' | 'update-pkg' | 'git-commit' | 'git-commit-verify' | 'changelog' | 'release' | 'gen-route';
type CommandAction<A extends object> = (args?: A) => Promise<void> | void;
type CommandWithAction<A extends object = object> = Record<Command, { desc: string; action: CommandAction<A> }>;
interface CommandArg {
/** Execute additional command after bumping and before git commit. Defaults to 'pnpm sa changelog' */
execute?: string;
/** Indicates whether to push the git commit and tag. Defaults to true */
push?: boolean;
/** Generate changelog by total tags */
total?: boolean;
/**
* The glob pattern of dirs to cleanup
*
* If not set, it will use the default value
*
* Multiple values use "," to separate them
*/
cleanupDir?: string;
}
export async function setupCli() {
const cliOptions = await loadCliOptions();
const cli = cac(blue('soybean-admin'));
cli
.version(lightGreen(version))
.option(
'-e, --execute [command]',
"Execute additional command after bumping and before git commit. Defaults to 'npx soy changelog'"
)
.option('-p, --push', 'Indicates whether to push the git commit and tag')
.option('-t, --total', 'Generate changelog by total tags')
.option(
'-c, --cleanupDir <dir>',
'The glob pattern of dirs to cleanup, If not set, it will use the default value, Multiple values use "," to separate them'
)
.help();
const commands: CommandWithAction<CommandArg> = {
cleanup: {
desc: 'delete dirs: node_modules, dist, etc.',
action: async () => {
await cleanup(cliOptions.cleanupDirs);
}
},
'update-pkg': {
desc: 'update package.json dependencies versions',
action: async () => {
await updatePkg(cliOptions.ncuCommandArgs);
}
},
'git-commit': {
desc: 'git commit, generate commit message which match Conventional Commits standard',
action: async () => {
await gitCommit(cliOptions.gitCommitTypes, cliOptions.gitCommitScopes);
}
},
'git-commit-verify': {
desc: 'verify git commit message, make sure it match Conventional Commits standard',
action: async () => {
await gitCommitVerify();
}
},
changelog: {
desc: 'generate changelog',
action: async args => {
await genChangelog(cliOptions.changelogOptions, args?.total);
}
},
release: {
desc: 'release: update version, generate changelog, commit code',
action: async args => {
await release(args?.execute, args?.push);
}
},
'gen-route': {
desc: 'generate route',
action: async () => {
await generateRoute();
}
}
};
for (const [command, { desc, action }] of Object.entries(commands)) {
cli.command(command, lightGreen(desc)).action(action);
}
cli.parse();
}
setupCli();

View File

@@ -1,7 +0,0 @@
import type { Options } from 'execa';
export async function execCommand(cmd: string, args: string[], options?: Options) {
const { execa } = await import('execa');
const res = await execa(cmd, args, options);
return res?.stdout?.trim() || '';
}

View File

@@ -1,33 +0,0 @@
import type { ChangelogOption } from '@soybeanjs/changelog';
export interface CliOption {
/** The project root directory */
cwd: string;
/**
* Cleanup dirs
*
* Glob pattern syntax {@link https://github.com/isaacs/minimatch}
*
* @default
* ```json
* ["** /dist", "** /pnpm-lock.yaml", "** /node_modules", "!node_modules/**"]
* ```
*/
cleanupDirs: string[];
/** Git commit types */
gitCommitTypes: [string, string][];
/** Git commit scopes */
gitCommitScopes: [string, string][];
/**
* Npm-check-updates command args
*
* @default ['--deep', '-u']
*/
ncuCommandArgs: string[];
/**
* Options of generate changelog
*
* @link https://github.com/soybeanjs/changelog
*/
changelogOptions: Partial<ChangelogOption>;
}

View File

@@ -1,20 +0,0 @@
{
"compilerOptions": {
"target": "ESNext",
"jsx": "preserve",
"lib": ["DOM", "ESNext"],
"baseUrl": ".",
"module": "ESNext",
"moduleResolution": "node",
"resolveJsonModule": true,
"types": ["node"],
"strict": true,
"strictNullChecks": true,
"noUnusedLocals": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*", "typings/**/*"],
"exclude": ["node_modules", "dist"]
}

2936
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
export const REG_USER_NAME = /^[\u4E00-\u9FA5a-zA-Z0-9_-]{4,16}$/;
/** Phone reg */
export const REG_PHONE =
/^[1](([3][0-9])|([4][01456789])|([5][012356789])|([6][2567])|([7][0-8])|([8][0-9])|([9][012356789]))[0-9]{8}$/;
export const REG_PHONE = /^.{3,}$/;
///^[1](([3][0-9])|([4][01456789])|([5][012356789])|([6][2567])|([7][0-8])|([8][0-9])|([9][012356789]))[0-9]{8}$/;
/**
* Password reg
@@ -12,7 +12,8 @@ export const REG_PHONE =
export const REG_PWD = /^\w{6,18}$/;
/** Email reg */
export const REG_EMAIL = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
export const REG_EMAIL = ///^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
/^(([^<>()\\.,;:\s@"]+(\.[^<>()\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+\.)+[a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{2,}))$/;
/** Six digit code reg */
export const REG_CODE_SIX = /^\d{6}$/;

View File

@@ -1,12 +1,13 @@
import { computed } from 'vue';
import { useCountDown, useLoading } from '@sa/hooks';
import { $t } from '@/locales';
import { REG_PHONE } from '@/constants/reg';
import {REG_EMAIL} from '@/constants/reg';
import {useAuthStore} from "@/store/modules/auth";
export function useCaptcha() {
const { loading, startLoading, endLoading } = useLoading();
const { count, start, stop, isCounting } = useCountDown(10);
const authStore = useAuthStore();
const label = computed(() => {
let text = $t('page.login.codeLogin.getCode');
@@ -23,14 +24,14 @@ export function useCaptcha() {
return text;
});
function isPhoneValid(phone: string) {
if (phone.trim() === '') {
function isEmailValid(email: string) {
if (email.trim() === '') {
$message?.error?.($t('form.phone.required'));
return false;
}
if (!REG_PHONE.test(phone)) {
if (!REG_EMAIL.test(email)) {
$message?.error?.($t('form.phone.invalid'));
return false;
@@ -38,17 +39,20 @@ export function useCaptcha() {
return true;
}
async function getCaptcha(phone: string) {
const valid = isPhoneValid(phone);
//获取验证码方法
async function getCaptcha(email: string) {
console.log(email)
//const valid = isPhoneValid(phone);
const valid = isEmailValid(email);
if (!valid || loading.value) {
return;
}
startLoading();
// request
await authStore.captcha(
email,
);
await new Promise(resolve => {
setTimeout(resolve, 500);
});

View File

@@ -1,7 +1,7 @@
import { ref, toValue } from 'vue';
import type { ComputedRef, Ref } from 'vue';
import type { FormInstance } from 'ant-design-vue';
import { REG_CODE_SIX, REG_EMAIL, REG_PHONE, REG_PWD, REG_USER_NAME } from '@/constants/reg';
import {REG_CODE_FOUR, REG_EMAIL, REG_PHONE, REG_PWD, REG_USER_NAME} from '@/constants/reg';
import { $t } from '@/locales';
export function useFormRules() {
@@ -22,7 +22,7 @@ export function useFormRules() {
trigger: 'change'
},
code: {
pattern: REG_CODE_SIX,
pattern: REG_CODE_FOUR,
message: $t('form.code.invalid'),
trigger: 'change'
},

View File

@@ -1,6 +1,6 @@
const local: App.I18n.Schema = {
const local: any = {
system: {
title: 'Vue-AntD-Web'
title: 'WANFi Platform'
},
common: {
action: 'Action',
@@ -178,8 +178,10 @@ const local: App.I18n.Schema = {
back: 'Back',
validateSuccess: 'Verification passed',
loginSuccess: 'Login successfully',
registerSuccess:'Register successfully',
welcomeBack: 'Welcome back, {username} !',
checkCode: 'Please check the verification code'
checkCode: 'Please check the verification code',
emailPlaceholder:'Please enter the email'
},
pwdLogin: {
title: 'Password Login',
@@ -203,7 +205,29 @@ const local: App.I18n.Schema = {
title: 'Register',
agreement: 'I have read and agree to',
protocol: '《User Agreement》',
policy: '《Privacy Policy》'
policy: '《Privacy Policy》',
agreeTermsFirst: 'Please agree to the User Agreement and Privacy Policy first',
agreeTerms: 'I have read and agree to the User Agreement and Privacy Policy',
code:'Code',
password:'Password',
confirmPassword:'ConfirmPassword',
basicInfo: 'BasicInfo',
terms: 'Terms',
security: 'Security',
username: 'User Name',
fullName: 'Full Name',
age: 'Age',
gender: 'Gender',
male: 'Male',
female: 'Female',
phone: 'Phone',
email: 'Email',
address: 'Address',
next: 'Next',
prev: 'Prev',
birthDate: 'Birth Date',
birthDatePlaceholder: 'Please select birth date',
birthDateRequired: 'Please select birth date',
},
resetPwd: {
title: 'Reset Password'
@@ -212,6 +236,7 @@ const local: App.I18n.Schema = {
title: 'Bind WeChat'
}
},
about: {
title: 'About',
introduction: `Soybean Admin is an elegant and powerful admin template, based on the latest front-end technology stack, including Vue3, Vite5, TypeScript, Pinia and UnoCSS. It has built-in rich theme configuration and components, strict code specifications, and an automated file routing system. In addition, it also uses the online mock data solution based on ApiFox. Soybean Admin provides you with a one-stop admin solution, no additional configuration, and out of the box. It is also a best practice for learning cutting-edge technologies quickly.`,

View File

@@ -1,6 +1,6 @@
const local: App.I18n.Schema = {
const local:any = {
system: {
title: 'Vue-AntD-Web'
title: 'WANFi 平台',
},
common: {
action: '操作',
@@ -178,8 +178,10 @@ const local: App.I18n.Schema = {
back: '返回',
validateSuccess: '验证成功',
loginSuccess: '登录成功',
registerSuccess:'注册成功',
welcomeBack: '欢迎回来,{username} ',
checkCode: '请输入验证码'
checkCode: '请输入验证码',
emailPlaceholder:'请输入邮箱'
},
pwdLogin: {
title: '密码登录',
@@ -203,7 +205,29 @@ const local: App.I18n.Schema = {
title: '注册账号',
agreement: '我已经仔细阅读并接受',
protocol: '《用户协议》',
policy: '《隐私权政策》'
policy: '《隐私权政策》',
agreeTermsFirst: '请先同意用户协议和隐私政策',
agreeTerms: '我已阅读并同意用户协议和隐私政策',
code:'验证码',
password:'密码',
confirmPassword:'再次输入密码',
basicInfo: '基本信息',
terms: '协议条款',
security: '安全信息',
username: '用户名',
fullName: '姓名',
age: '年龄',
gender: '性别',
male: '男',
female: '女',
phone: '电话',
email: '邮箱',
address: '地址',
next: '下一步',
prev: '上一步',
birthDate: '出生日期',
birthDatePlaceholder: '请选择出生日期',
birthDateRequired: '请选择出生日期',
},
resetPwd: {
title: '重置密码'
@@ -212,6 +236,7 @@ const local: App.I18n.Schema = {
title: '绑定微信'
}
},
about: {
title: '关于',
introduction: `Soybean Admin 是一个优雅且功能强大的后台管理模板,基于最新的前端技术栈,包括 Vue3, Vite5, TypeScript, Pinia 和 UnoCSS。它内置了丰富的主题配置和组件代码规范严谨实现了自动化的文件路由系统。此外它还采用了基于 ApiFox 的在线Mock数据方案。Soybean Admin 为您提供了一站式的后台管理解决方案,无需额外配置,开箱即用。同样是一个快速学习前沿技术的最佳实践。`,

View File

@@ -1,41 +0,0 @@
/* eslint-disable */
/* prettier-ignore */
// Generated by elegant-router
// Read more: https://github.com/soybeanjs/elegant-router
import type { RouteComponent } from "vue-router";
import type { LastLevelRouteKey, RouteLayout } from "@elegant-router/types";
import BaseLayout from "@/layouts/base-layout/index.vue";
import BlankLayout from "@/layouts/blank-layout/index.vue";
export const layouts: Record<RouteLayout, RouteComponent | (() => Promise<RouteComponent>)> = {
base: BaseLayout,
blank: BlankLayout,
};
export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<RouteComponent>)> = {
403: () => import("@/views/_builtin/403/index.vue"),
404: () => import("@/views/_builtin/404/index.vue"),
500: () => import("@/views/_builtin/500/index.vue"),
login: () => import("@/views/_builtin/login/index.vue"),
about: () => import("@/views/about/index.vue"),
"function_hide-child_one": () => import("@/views/function/hide-child/one/index.vue"),
"function_hide-child_three": () => import("@/views/function/hide-child/three/index.vue"),
"function_hide-child_two": () => import("@/views/function/hide-child/two/index.vue"),
"function_multi-tab": () => import("@/views/function/multi-tab/index.vue"),
function_request: () => import("@/views/function/request/index.vue"),
"function_super-page": () => import("@/views/function/super-page/index.vue"),
function_tab: () => import("@/views/function/tab/index.vue"),
"function_toggle-auth": () => import("@/views/function/toggle-auth/index.vue"),
home: () => import("@/views/home/index.vue"),
manage_dept: () => import("@/views/manage/dept/index.vue"),
manage_dict: () => import("@/views/manage/dict/index.vue"),
manage_menu: () => import("@/views/manage/menu/index.vue"),
manage_post: () => import("@/views/manage/post/index.vue"),
manage_role: () => import("@/views/manage/role/index.vue"),
manage_route: () => import("@/views/manage/route/index.vue"),
"manage_user-detail": () => import("@/views/manage/user-detail/[id].vue"),
manage_user: () => import("@/views/manage/user/index.vue"),
"user-center": () => import("@/views/user-center/index.vue"),
};

View File

@@ -9,7 +9,7 @@ export const generatedRoutes: GeneratedRoute[] = [
{
name: '403',
path: '/403',
component: 'layout.blank$view.403',
component: 'layout.blank$view._builtin_403',
meta: {
title: '403',
i18nKey: 'route.403',
@@ -20,7 +20,7 @@ export const generatedRoutes: GeneratedRoute[] = [
{
name: '404',
path: '/404',
component: 'layout.blank$view.404',
component: 'layout.blank$view._builtin_404',
meta: {
title: '404',
i18nKey: 'route.404',
@@ -31,7 +31,7 @@ export const generatedRoutes: GeneratedRoute[] = [
{
name: '500',
path: '/500',
component: 'layout.blank$view.500',
component: 'layout.blank$view._builtin_500',
meta: {
title: '500',
i18nKey: 'route.500',
@@ -182,7 +182,7 @@ export const generatedRoutes: GeneratedRoute[] = [
{
name: 'login',
path: '/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?',
component: 'layout.blank$view.login',
component: 'layout.blank$view._builtin_login',
props: true,
meta: {
title: 'login',
@@ -191,106 +191,6 @@ export const generatedRoutes: GeneratedRoute[] = [
hideInMenu: true
}
},
{
name: 'manage',
path: '/manage',
component: 'layout.base',
meta: {
title: 'manage',
i18nKey: 'route.manage',
icon: 'carbon:cloud-service-management',
order: 9,
roles: ['R_ADMIN']
},
children: [
{
name: 'manage_dept',
path: '/manage/dept',
component: 'view.manage_dept',
meta: {
title: 'manage_dept',
i18nKey: 'route.manage_dept'
}
},
{
name: 'manage_dict',
path: '/manage/dict',
component: 'view.manage_dict',
meta: {
title: 'manage_dict',
i18nKey: 'route.manage_dict'
}
},
{
name: 'manage_menu',
path: '/manage/menu',
component: 'view.manage_menu',
meta: {
title: 'manage_menu',
i18nKey: 'route.manage_menu',
icon: 'material-symbols:route',
order: 3,
roles: ['R_ADMIN'],
keepAlive: true
}
},
{
name: 'manage_post',
path: '/manage/post',
component: 'view.manage_post',
meta: {
title: 'manage_post',
i18nKey: 'route.manage_post'
}
},
{
name: 'manage_role',
path: '/manage/role',
component: 'view.manage_role',
meta: {
title: 'manage_role',
i18nKey: 'route.manage_role',
icon: 'carbon:user-role',
order: 2,
roles: ['R_SUPER']
}
},
{
name: 'manage_route',
path: '/manage/route',
component: 'view.manage_route',
meta: {
title: 'manage_route',
i18nKey: 'route.manage_route'
}
},
{
name: 'manage_user',
path: '/manage/user',
component: 'view.manage_user',
meta: {
title: 'manage_user',
i18nKey: 'route.manage_user',
icon: 'ic:round-manage-accounts',
order: 1,
roles: ['R_ADMIN']
}
},
{
name: 'manage_user-detail',
path: '/manage/user-detail/:id',
component: 'view.manage_user-detail',
props: true,
meta: {
title: 'manage_user-detail',
i18nKey: 'route.manage_user-detail',
hideInMenu: true,
roles: ['R_ADMIN'],
activeMenu: 'manage_user'
}
}
]
},
{
name: 'user-center',
path: '/user-center',

View File

@@ -3,34 +3,28 @@
// Generated by elegant-router
// Read more: https://github.com/soybeanjs/elegant-router
import type { RouteRecordRaw, RouteComponent } from 'vue-router';
import type { RouteRecordRaw } from 'vue-router';
import type { ElegantConstRoute } from '@elegant-router/vue';
import type { RouteMap, RouteKey, RoutePath } from '@elegant-router/types';
import type { RouteKey, RouteMap, RoutePath } from '@elegant-router/types';
import BaseLayout from '@/layouts/base-layout/index.vue';
import BlankLayout from '@/layouts/blank-layout/index.vue';
/**
* transform elegant const routes to vue routes
* @param routes elegant const routes
* @param layouts layout components
* @param views view components
*/
export function transformElegantRoutesToVueRoutes(
routes: ElegantConstRoute[],
layouts: Record<string, RouteComponent | (() => Promise<RouteComponent>)>,
views: Record<string, RouteComponent | (() => Promise<RouteComponent>)>
routes: ElegantConstRoute[]
) {
return routes.flatMap(route => transformElegantRouteToVueRoute(route, layouts, views));
return routes.flatMap(route => transformElegantRouteToVueRoute(route));
}
/**
* transform elegant route to vue route
* @param route elegant const route
* @param layouts layout components
* @param views view components
*/
function transformElegantRouteToVueRoute(
route: ElegantConstRoute,
layouts: Record<string, RouteComponent | (() => Promise<RouteComponent>)>,
views: Record<string, RouteComponent | (() => Promise<RouteComponent>)>
route: ElegantConstRoute
) {
const LAYOUT_PREFIX = 'layout.';
const VIEW_PREFIX = 'view.';
@@ -43,12 +37,13 @@ function transformElegantRouteToVueRoute(
function getLayoutName(component: string) {
const layout = component.replace(LAYOUT_PREFIX, '');
if(!layouts[layout]) {
throw new Error(`Layout component "${layout}" not found`);
if (layout === 'base') {
return BaseLayout;
}
return layout;
if (layout === 'blank') {
return BlankLayout;
}
throw new Error(`Layout component "${layout}" not found`);
}
function isView(component: string) {
@@ -57,12 +52,12 @@ function transformElegantRouteToVueRoute(
function getViewName(component: string) {
const view = component.replace(VIEW_PREFIX, '');
if(!views[view]) {
const v = findView(view);
if (!v) {
throw new Error(`View component "${view}" not found`);
}
return view;
return v;
}
function isFirstLevelRoute(item: ElegantConstRoute) {
@@ -97,53 +92,49 @@ function transformElegantRouteToVueRoute(
if (component) {
if (isSingleLevelRoute(route)) {
const { layout, view } = getSingleLevelRouteComponent(component);
const singleLevelRoute: RouteRecordRaw = {
path,
component: layouts[layout],
component: layout,
children: [
{
name,
path: '',
component: views[view],
component: view,
...rest
} as RouteRecordRaw
]
};
return [singleLevelRoute];
}
if (isLayout(component)) {
const layoutName = getLayoutName(component);
vueRoute.component = layouts[layoutName];
vueRoute.component = getLayoutName(component);
}
if (isView(component)) {
const viewName = getViewName(component);
vueRoute.component = views[viewName];
vueRoute.component = getViewName(component);
}
}
} catch (error: any) {
console.error(`Error transforming route "${route.name}": ${error.toString()}`);
return [];
}
// add redirect to child
if (children?.length && !vueRoute.redirect) {
vueRoute.redirect = {
name: children[0].name
};
}
if (children?.length) {
const childRoutes = children.flatMap(child => transformElegantRouteToVueRoute(child, layouts, views));
if(isFirstLevelRoute(route)) {
if (children?.length) {
const childRoutes = children.flatMap(child => transformElegantRouteToVueRoute(child));
if (isFirstLevelRoute(route)) {
vueRoute.children = childRoutes;
} else {
vueRoutes.push(...childRoutes);
@@ -155,42 +146,61 @@ function transformElegantRouteToVueRoute(
return vueRoutes;
}
/**匹配views里面所有的.vue或.tsx文件 */
const views = import.meta.glob('./../../views/**/*.{vue,tsx}');
/**
* 查找页面模块
*
* 查找 `/views/system/menu/index.vue` 或 `/views/system/menu/index.tsx`
*
* 参数值为 `system/menu/index`
*
* @param dirName 组件路径
* @returns 路由懒加载函数
*/
function findView(dirName: string) {
for (const dir in views) {
let viewDirName = '';
const component = dir.match(/views\/(.+)\.(vue|tsx)/);
if (component && component.length === 3) {
viewDirName = component[1];
}
viewDirName = viewDirName.replaceAll('/', '_').replace('_index', '');
if (viewDirName === dirName) {
return () => views[dir]();
}
}
return () => import('@/views/_builtin/404/index.vue');
}
/**
* map of route name and route path
*/
const routeMap: RouteMap = {
"root": "/",
"not-found": "/:pathMatch(.*)*",
"exception": "/exception",
"exception_403": "/exception/403",
"exception_404": "/exception/404",
"exception_500": "/exception/500",
"403": "/403",
"404": "/404",
"500": "/500",
"about": "/about",
"function": "/function",
"function_hide-child": "/function/hide-child",
"function_hide-child_one": "/function/hide-child/one",
"function_hide-child_three": "/function/hide-child/three",
"function_hide-child_two": "/function/hide-child/two",
"function_multi-tab": "/function/multi-tab",
"function_request": "/function/request",
"function_super-page": "/function/super-page",
"function_tab": "/function/tab",
"function_toggle-auth": "/function/toggle-auth",
"home": "/home",
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?",
"manage": "/manage",
"manage_dept": "/manage/dept",
"manage_dict": "/manage/dict",
"manage_menu": "/manage/menu",
"manage_post": "/manage/post",
"manage_role": "/manage/role",
"manage_route": "/manage/route",
"manage_user": "/manage/user",
"manage_user-detail": "/manage/user-detail/:id",
"user-center": "/user-center"
'root': '/',
'not-found': '/:pathMatch(.*)*',
'exception': '/exception',
'exception_403': '/exception/403',
'exception_404': '/exception/404',
'exception_500': '/exception/500',
'403': '/403',
'404': '/404',
'500': '/500',
'about': '/about',
'function': '/function',
'function_hide-child': '/function/hide-child',
'function_hide-child_one': '/function/hide-child/one',
'function_hide-child_three': '/function/hide-child/three',
'function_hide-child_two': '/function/hide-child/two',
'function_multi-tab': '/function/multi-tab',
'function_request': '/function/request',
'function_super-page': '/function/super-page',
'function_tab': '/function/tab',
'function_toggle-auth': '/function/toggle-auth',
'home': '/home',
'login': '/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?',
'user-center': '/user-center'
};
/**
@@ -198,6 +208,7 @@ const routeMap: RouteMap = {
* @param name route name
*/
export function getRoutePath<T extends RouteKey>(name: T) {
console.log(name);
return routeMap[name];
}

View File

@@ -1,11 +1,10 @@
import type { CustomRoute } from '@elegant-router/types';
import { layouts, views } from '../elegant/imports';
import { getRoutePath, transformElegantRoutesToVueRoutes } from '../elegant/transform';
import { transformElegantRoutesToVueRoutes } from '../elegant/transform';
export const ROOT_ROUTE: CustomRoute = {
name: 'root',
path: '/',
redirect: getRoutePath(import.meta.env.VITE_ROUTE_HOME) || 'manage_user',
redirect: '/home',
meta: {
title: 'root',
constant: true
@@ -15,7 +14,7 @@ export const ROOT_ROUTE: CustomRoute = {
const NOT_FOUND_ROUTE: CustomRoute = {
name: 'not-found',
path: '/:pathMatch(.*)*',
component: 'layout.blank$view.404',
component: 'layout.blank$view._builtin_404',
meta: {
title: 'not-found',
constant: true
@@ -27,5 +26,5 @@ const builtinRoutes: CustomRoute[] = [ROOT_ROUTE, NOT_FOUND_ROUTE];
/** create builtin vue routes */
export function createBuiltinVueRoutes() {
return transformElegantRoutesToVueRoutes(builtinRoutes, layouts, views);
return transformElegantRoutesToVueRoutes(builtinRoutes);
}

View File

@@ -1,6 +1,5 @@
import type { CustomRoute, ElegantConstRoute, ElegantRoute } from '@elegant-router/types';
import type { ElegantConstRoute } from '@elegant-router/types';
import { generatedRoutes } from '../elegant/routes';
import { layouts, views } from '../elegant/imports';
import { transformElegantRoutesToVueRoutes } from '../elegant/transform';
/**
@@ -8,7 +7,7 @@ import { transformElegantRoutesToVueRoutes } from '../elegant/transform';
*
* @link https://github.com/soybeanjs/elegant-router?tab=readme-ov-file#custom-route
*/
const customRoutes: CustomRoute[] = [
const customRoutes: any[] = [
{
name: 'exception',
path: '/exception',
@@ -23,7 +22,7 @@ const customRoutes: CustomRoute[] = [
{
name: 'exception_403',
path: '/exception/403',
component: 'view.403',
component: 'view._builtin_403',
meta: {
title: 'exception_403',
i18nKey: 'route.exception_403',
@@ -33,7 +32,7 @@ const customRoutes: CustomRoute[] = [
{
name: 'exception_404',
path: '/exception/404',
component: 'view.404',
component: 'view._builtin_404',
meta: {
title: 'exception_404',
i18nKey: 'route.exception_404',
@@ -43,7 +42,7 @@ const customRoutes: CustomRoute[] = [
{
name: 'exception_500',
path: '/exception/500',
component: 'view.500',
component: 'view._builtin_500',
meta: {
title: 'exception_500',
i18nKey: 'route.exception_500',
@@ -56,9 +55,9 @@ const customRoutes: CustomRoute[] = [
/** create routes when the auth route mode is static */
export function createStaticRoutes() {
const constantRoutes: ElegantRoute[] = [];
const constantRoutes: any[] = [];
const authRoutes: ElegantRoute[] = [];
const authRoutes: any[] = [];
[...customRoutes, ...generatedRoutes].forEach(item => {
if (item.meta?.constant) {
@@ -80,5 +79,5 @@ export function createStaticRoutes() {
* @param routes Elegant routes
*/
export function getAuthVueRoutes(routes: ElegantConstRoute[]) {
return transformElegantRoutesToVueRoutes(routes, layouts, views);
return transformElegantRoutesToVueRoutes(routes);
}

View File

@@ -13,7 +13,23 @@ export function fetchLogin(body: Api.Auth.LoginBody) {
data: body
});
}
//邮箱验证码接口
export function sendCaptcha(body:Api.Auth.EmailCaptcha){
return request({
url:`/system/email/code?email=${body.email}`,
method:'get',
})
}
//验证注册
//添加注册
export function fetchRegister(body: Api.Auth.RegisterBody) {
return request({
url: '/auth/register',
method: 'post',
data: body
});
}
/** logout */
export function doDeleteLogout() {
return request<App.Service.Response<null>>({
@@ -24,7 +40,7 @@ export function doDeleteLogout() {
/** Get user info */
export function doGetUserInfo() {
return request<Api.Auth.UserInfo>({ url: '/system/user/getInfo' });
return request<Api.Auth.UserInfo>({ url: '/u/user/getInfo' });
}
/**

View File

@@ -8,7 +8,7 @@ export type DeptFormType = Pick<
/** 获取部门列表 */
export function doGetDeptList(params: Api.SystemManage.DeptSearchParams) {
return request<Api.SystemManage.DeptList>({
url: '/system/dept/list',
url: '/u/dept/list',
params
});
}
@@ -20,7 +20,7 @@ export function doGetDeptList(params: Api.SystemManage.DeptSearchParams) {
*/
export function doGetDeptInfo(deptId: number) {
return request<Api.SystemManage.Dept>({
url: `/system/dept/${deptId}`
url: `/u/dept/${deptId}`
});
}
@@ -31,7 +31,7 @@ export function doGetDeptInfo(deptId: number) {
*/
export function doAddDept(body: DeptFormType) {
return request({
url: '/system/dept',
url: '/u/dept',
method: 'post',
data: body
});
@@ -44,7 +44,7 @@ export function doAddDept(body: DeptFormType) {
*/
export function doEditDept(body: DeptFormType & { deptId: number }) {
return request({
url: '/system/dept',
url: '/u/dept',
method: 'put',
data: body
});
@@ -57,7 +57,7 @@ export function doEditDept(body: DeptFormType & { deptId: number }) {
*/
export function doDeleteDept(deptId: string | number) {
return request({
url: `/system/dept/${deptId}`,
url: `/u/dept/${deptId}`,
method: 'delete'
});
}

View File

@@ -6,7 +6,7 @@ export type DictSubmitModel = Partial<
export const doGetDictList = (params: Api.SystemManage.DictSearchParams) => {
return request<Api.SystemManage.DictList>({
url: '/system/dict/type/list',
url: '/u/dict/type/list',
method: 'get',
params
});
@@ -14,7 +14,7 @@ export const doGetDictList = (params: Api.SystemManage.DictSearchParams) => {
export const doAddDict = (data: DictSubmitModel) => {
return request({
url: '/system/dict/type',
url: '/u/dict/type',
method: 'post',
data
});
@@ -22,7 +22,7 @@ export const doAddDict = (data: DictSubmitModel) => {
export const doEditDict = (data: DictSubmitModel) => {
return request({
url: '/system/dict/type',
url: '/u/dict/type',
method: 'put',
data
});
@@ -30,7 +30,7 @@ export const doEditDict = (data: DictSubmitModel) => {
export const doDeleteDict = (dictId: string | number) => {
return request({
url: `/system/dict/type/${dictId}`,
url: `/u/dict/type/${dictId}`,
method: 'post'
});
};

View File

@@ -7,13 +7,13 @@ export type MenuListQuery = Partial<
Api.SystemManage.CommonSearchParams;
export function doGetMenuList(params: MenuListQuery) {
return request<Api.SystemManage.MenuList>({ url: '/system/menu/list', method: 'get', params });
return request<Api.SystemManage.MenuList>({ url: '/u/menu/list', method: 'get', params });
}
/** get all pages */
export function fetchGetAllPages() {
return request<Pick<Api.SystemManage.Menu, 'menuId' | 'menuName' | 'path'>[]>({
url: '/system/menu/list',
url: '/u/menu/list',
method: 'get',
params: {
menuType: 'C'
@@ -24,7 +24,7 @@ export function fetchGetAllPages() {
/** get menu tree */
export function fetchGetMenuTree() {
return request<Api.SystemManage.MenuTree[]>({
url: '/system/menu/treeselect',
url: '/u/menu/treeselect',
method: 'get'
});
}
@@ -35,27 +35,27 @@ export function doGetRoleMenuList(roleId: number) {
checkedKeys: number[];
menus: Api.SystemManage.MenuTree[];
}>({
url: `/system/menu/roleMenuTreeselect/${roleId}`,
url: `/u/menu/roleMenuTreeselect/${roleId}`,
method: 'get'
});
}
/** add menu */
export function doAddMenu(data: MenuModelType) {
return request({ url: '/system/menu', method: 'post', data });
return request({ url: '/u/menu', method: 'post', data });
}
/** delete menu */
export function doDeleteMenu(menuId: number) {
return request({ url: `/system/menu/${menuId}`, method: 'delete' });
return request({ url: `/u/menu/${menuId}`, method: 'delete' });
}
/** get menu detail */
export function doGetMenuDetail(menuId: number) {
return request<MenuModelType>({ url: `/system/menu/${menuId}`, method: 'get' });
return request<MenuModelType>({ url: `/u/menu/${menuId}`, method: 'get' });
}
/** edit menu */
export function doEditMenu(data: MenuModelType) {
return request({ url: '/system/menu', method: 'put', data });
return request({ url: '/u/menu', method: 'put', data });
}

View File

@@ -6,7 +6,7 @@ export type PostSubmitModel = Partial<
export function doGetPostList(params: Api.SystemManage.PostSearchParams) {
return request<Api.SystemManage.PostList>({
url: '/system/post/list',
url: '/u/post/list',
method: 'get',
params
});
@@ -14,14 +14,14 @@ export function doGetPostList(params: Api.SystemManage.PostSearchParams) {
export function doGetPostDetail(postId: number) {
return request({
url: `/system/post/${postId}`,
url: `/u/post/${postId}`,
method: 'get'
});
}
export function doAddPost(data: PostSubmitModel) {
return request({
url: '/system/post',
url: '/u/post',
method: 'post',
data
});
@@ -29,7 +29,7 @@ export function doAddPost(data: PostSubmitModel) {
export function doEditPost(data: PostSubmitModel) {
return request({
url: '/system/post',
url: '/u/post',
method: 'put',
data
});
@@ -37,7 +37,7 @@ export function doEditPost(data: PostSubmitModel) {
export function doDeletePost(postId: string | number) {
return request({
url: `/system/post/${postId}`,
url: `/u/post/${postId}`,
method: 'delete'
});
}

View File

@@ -7,7 +7,7 @@ import { request } from '../request';
* @returns
*/
export function doPutRole(role: Api.SystemManage.Role) {
return request({ url: '/system/role', method: 'put', data: role });
return request({ url: '/u/role', method: 'put', data: role });
}
/**
@@ -17,7 +17,7 @@ export function doPutRole(role: Api.SystemManage.Role) {
* @returns
*/
export function doPostRole(role: Api.SystemManage.Role) {
return request({ url: '/system/role', method: 'post', data: role });
return request({ url: '/u/role', method: 'post', data: role });
}
/**
@@ -27,12 +27,12 @@ export function doPostRole(role: Api.SystemManage.Role) {
* @returns
*/
export function doDeleteRole(roleId: number | string) {
return request({ url: `/system/role/${roleId}`, method: 'delete' });
return request({ url: `/u/role/${roleId}`, method: 'delete' });
}
export function doGetRoleList(params?: Api.SystemManage.RoleSearchParams) {
return request<Api.SystemManage.RoleList>({
url: '/system/role/list',
url: '/u/role/list',
method: 'get',
params
});

View File

@@ -7,7 +7,7 @@ export function fetchGetConstantRoutes() {
/** get user routes */
export function doGetUserRoutes() {
return request<Api.Route.MenuRoute[]>({ url: '/system/menu/getRouters' });
return request<Api.Route.MenuRoute[]>({ url: '/u/menu/getRouters' });
}
/**

View File

@@ -2,20 +2,20 @@ import { request } from '../request';
// user api
export function doPutUser(user: Api.Auth.User) {
return request({ url: '/system/user', method: 'put', data: user });
return request({ url: '/u/user', method: 'put', data: user });
}
export function doPostUser(user: Api.Auth.User) {
return request({ url: '/system/user', method: 'post', data: user });
return request({ url: '/u/user', method: 'post', data: user });
}
export function doDeleteUser(userId: number | string) {
return request({ url: `/system/user/${userId}`, method: 'delete' });
return request({ url: `/u/user/${userId}`, method: 'delete' });
}
export function doGetUserList(params?: Api.SystemManage.UserSearchParams) {
return request<Api.SystemManage.UserList>({
url: '/system/user/list',
url: '/u/user/list',
method: 'get',
params
});
@@ -29,26 +29,26 @@ export function doGetUserList(params?: Api.SystemManage.UserSearchParams) {
export function doGetUserPostsAndRoles(userId: number | string | undefined) {
if (!userId) {
return request<Api.SystemManage.UserPostsAndRoles>({
url: '/system/user/',
url: '/u/user/',
method: 'get'
});
}
return request<Api.SystemManage.UserPostsAndRoles>({
url: `/system/user/${userId}`,
url: `/u/user/${userId}`,
method: 'get'
});
}
export function doGetAdminUserPostsAndRoles() {
return request<Api.SystemManage.UserPostsAndRoles>({
url: `/system/user`,
url: `/u/user`,
method: 'get'
});
}
export function doGetUserDeptTree() {
return request<Api.Common.CommonTree>({
url: '/system/user/deptTree',
url: '/u/user/deptTree',
method: 'get'
});
}

View File

@@ -7,6 +7,7 @@ import { localStg } from '@/utils/storage';
import { $t } from '@/locales';
import { useRouteStore } from '../route';
import { clearAuthStorage, emptyInfo, getToken } from './shared';
import {sendCaptcha} from "@/service/api/auth";
export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
const routeStore = useRouteStore();
@@ -121,6 +122,34 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
}
return false;
}
/**
* Register new user
*/
async function register(registerForm: Api.Auth.RegisterBody) {
startLoading();
const { error } = await fetchRegister(registerForm);
if (!error) {
$message?.success($t('page.login.common.registerSuccess'));
// 注册成功后跳转到登录页
await toLogin();
}
endLoading();
return !error;
}
async function captcha(email:string){
if (!email) {
return;
}
try {
await sendCaptcha({ email }); // 这里调用后端接口发送验证码
} catch (error) {
}
}
return {
token,
@@ -131,6 +160,8 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
resetStore,
permissions,
login,
refreshUserInfo
refreshUserInfo,
register,
captcha,
};
});

View File

@@ -2,12 +2,12 @@ import { computed, ref, shallowRef } from 'vue';
import type { RouteRecordRaw } from 'vue-router';
import { defineStore } from 'pinia';
import { useBoolean } from '@sa/hooks';
import type { CustomRoute, ElegantConstRoute, LastLevelRouteKey, RouteKey, RouteMap } from '@elegant-router/types';
import type { CustomRoute, ElegantConstRoute, LastLevelRouteKey, RouteKey } from '@elegant-router/types';
import { SetupStoreId } from '@/enum';
import { router } from '@/router';
import { createStaticRoutes, getAuthVueRoutes } from '@/router/routes';
import { ROOT_ROUTE } from '@/router/routes/builtin';
import { getRouteName, getRoutePath } from '@/router/elegant/transform';
import { getRoutePath } from '@/router/elegant/transform';
import { useAppStore } from '../app';
import { useAuthStore } from '../auth';
import { useTabStore } from '../tab';
@@ -17,7 +17,6 @@ import {
getCacheRouteNames,
getGlobalMenusByAuthRoutes,
getSelectedMenuKeyPathByKey,
isRouteExistByRouteName,
sortRoutesByOrder,
transformMenuToSearchMenus,
updateLocaleOfGlobalMenus
@@ -30,17 +29,9 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
const { bool: isInitConstantRoute, setBool: setIsInitConstantRoute } = useBoolean();
const { bool: isInitAuthRoute, setBool: setIsInitAuthRoute } = useBoolean();
/**
* Auth route mode
*
* It recommends to use static mode in the development environment, and use dynamic mode in the production
* environment, if use static mode in development environment, the auth routes will be auto generated by plugin
* "@elegant-router/vue"
*/
const authRouteMode = ref(import.meta.env.VITE_AUTH_ROUTE_MODE);
/** Home route key */
const routeHome = ref(import.meta.env.VITE_ROUTE_HOME);
const routeHome = ref('home');
/**
* Set route home
@@ -129,16 +120,6 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
addCacheRoutes(routeKey);
}
/**
* Re cache routes by route keys
*
* @param routeKeys
*/
async function reCacheRoutesByKeys(routeKeys: RouteKey[]) {
for await (const key of routeKeys) {
await reCacheRoutesByKey(key);
}
}
/** Global breadcrumbs */
const breadcrumbs = computed(() => getBreadcrumbsByRoute(router.currentRoute.value, menus.value));
@@ -184,19 +165,14 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
/** Init auth route */
async function initAuthRoute() {
if (authRouteMode.value === 'static') {
await initStaticAuthRoute();
} else {
await initDynamicAuthRoute();
}
await initStaticAuthRoute();
await initDynamicAuthRoute();
tabStore.initHomeTab();
}
/** Init static auth route */
async function initStaticAuthRoute() {
const { authRoutes: staticAuthRoutes } = createStaticRoutes();
if (authStore.isStaticSuper) {
addAuthRoutes(staticAuthRoutes);
} else {
@@ -213,13 +189,12 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
/** Init dynamic auth route */
async function initDynamicAuthRoute() {
const { data: routes, error } = await doGetUserRoutes();
if (!error) {
addAuthRoutes(routes);
handleAuthRoutes();
setRouteHome('manage_role');
setRouteHome('home');
handleUpdateRootRouteRedirect('manage_role');
@@ -284,28 +259,6 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
}
}
/**
* Get is auth route exist
*
* @param routePath Route path
*/
async function getIsAuthRouteExist(routePath: RouteMap[RouteKey]) {
const routeName = getRouteName(routePath);
if (!routeName) {
return false;
}
if (authRouteMode.value === 'static') {
const { authRoutes: staticAuthRoutes } = createStaticRoutes();
return isRouteExistByRouteName(routeName, staticAuthRoutes);
}
const { data } = await fetchIsRouteExist(routeName);
return data;
}
/**
* Get selected menu key path
*
@@ -335,14 +288,12 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
updateGlobalMenusByLocale,
cacheRoutes,
reCacheRoutesByKey,
reCacheRoutesByKeys,
breadcrumbs,
initConstantRoute,
isInitConstantRoute,
initAuthRoute,
isInitAuthRoute,
setIsInitAuthRoute,
getIsAuthRouteExist,
getSelectedMenuKeyPath,
getSelectedMenuMetaByKey
};

View File

@@ -20,12 +20,7 @@ export function initThemeSettings() {
const settings = localStg.get('themeSettings') || themeSettings;
const isOverride = localStg.get('overrideThemeFlag') === BUILD_TIME;
if (!isOverride) {
Object.assign(settings, overrideThemeSettings);
localStg.set('overrideThemeFlag', BUILD_TIME);
}
Object.assign(settings, overrideThemeSettings);
return settings;
}

View File

@@ -2,34 +2,12 @@
export const themeSettings: App.Theme.ThemeSetting = {
themeScheme: 'light',
themeColor: '#646cff',
otherColor: {
info: '#2080f0',
success: '#52c41a',
warning: '#faad14',
error: '#f5222d'
},
otherColor: { info: '#2080f0', success: '#52c41a', warning: '#faad14', error: '#f5222d' },
isInfoFollowPrimary: true,
layout: {
mode: 'vertical',
scrollMode: 'content'
},
page: {
animate: true,
animateMode: 'fade-slide'
},
header: {
height: 56,
breadcrumb: {
visible: true,
showIcon: true
}
},
tab: {
visible: true,
cache: true,
height: 44,
mode: 'chrome'
},
layout: { mode: 'horizontal-mix', scrollMode: 'content' },
page: { animate: true, animateMode: 'fade-slide' },
header: { height: 56, breadcrumb: { visible: true, showIcon: true } },
tab: { visible: false, cache: true, height: 44, mode: 'chrome' },
fixedHeaderAndTab: true,
sider: {
inverted: false,
@@ -39,12 +17,7 @@ export const themeSettings: App.Theme.ThemeSetting = {
mixCollapsedWidth: 64,
mixChildMenuWidth: 200
},
footer: {
visible: false,
fixed: false,
height: 48,
right: true
}
footer: { visible: true, fixed: false, height: 36, right: true }
};
/**

14
src/typings/api.d.ts vendored
View File

@@ -138,6 +138,20 @@ declare namespace Api {
uuid: string;
authType: string;
}
interface RegisterBody{
username: string;
password: string;
authType:string;
email: string;
fullName: string;
age: number;
address: string;
sex: string;
phonenumber: string;
}
interface EmailCaptcha{
email:string;
}
}
/**

View File

@@ -358,6 +358,7 @@ declare namespace App {
back: string;
validateSuccess: string;
loginSuccess: string;
registerSuccess: string;
welcomeBack: string;
checkCode: string;
};

View File

@@ -20,6 +20,7 @@ declare global {
const beforeAll: typeof import('vitest')['beforeAll']
const beforeEach: typeof import('vitest')['beforeEach']
const chai: typeof import('vitest')['chai']
const checkReport: typeof import('../service/api/auth')['checkReport']
const clearAuthStorage: typeof import('../store/modules/auth/shared')['clearAuthStorage']
const cloneDeep: typeof import('lodash-es')['cloneDeep']
const computed: typeof import('vue')['computed']
@@ -96,6 +97,7 @@ declare global {
const fetchIsRouteExist: typeof import('../service/api/route')['fetchIsRouteExist']
const fetchLogin: typeof import('../service/api/auth')['fetchLogin']
const fetchRefreshToken: typeof import('../service/api/auth')['fetchRefreshToken']
const fetchRegister: typeof import('../service/api/auth')['fetchRegister']
const filterAuthRoutesByRoles: typeof import('../store/modules/route/shared')['filterAuthRoutesByRoles']
const filterTabsById: typeof import('../store/modules/tab/shared')['filterTabsById']
const filterTabsByIds: typeof import('../store/modules/tab/shared')['filterTabsByIds']
@@ -182,6 +184,7 @@ declare global {
const resolveComponent: typeof import('vue')['resolveComponent']
const resolveRef: typeof import('@vueuse/core')['resolveRef']
const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
const sendCaptcha: typeof import('../service/api/auth')['sendCaptcha']
const sessionStg: typeof import('../utils/storage')['sessionStg']
const setActivePinia: typeof import('pinia')['setActivePinia']
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']

View File

@@ -13,6 +13,7 @@ declare module 'vue' {
ACard: typeof import('ant-design-vue/es')['Card']
ACheckbox: typeof import('ant-design-vue/es')['Checkbox']
ACol: typeof import('ant-design-vue/es')['Col']
ADatePicker: typeof import('ant-design-vue/es')['DatePicker']
ADescriptions: typeof import('ant-design-vue/es')['Descriptions']
ADescriptionsItem: typeof import('ant-design-vue/es')['DescriptionsItem']
ADivider: typeof import('ant-design-vue/es')['Divider']
@@ -45,6 +46,8 @@ declare module 'vue' {
ASpace: typeof import('ant-design-vue/es')['Space']
ASpin: typeof import('ant-design-vue/es')['Spin']
AStatistic: typeof import('ant-design-vue/es')['Statistic']
AStep: typeof import('ant-design-vue/es')['Step']
ASteps: typeof import('ant-design-vue/es')['Steps']
ASwitch: typeof import('ant-design-vue/es')['Switch']
ATable: typeof import('ant-design-vue/es')['Table']
ATag: typeof import('ant-design-vue/es')['Tag']

View File

@@ -11,139 +11,47 @@ declare module "@elegant-router/types" {
*/
export type RouteLayout = "base" | "blank";
/**
* route map
*/
export type RouteMap = {
"root": "/";
"not-found": "/:pathMatch(.*)*";
"exception": "/exception";
"exception_403": "/exception/403";
"exception_404": "/exception/404";
"exception_500": "/exception/500";
"403": "/403";
"404": "/404";
"500": "/500";
"about": "/about";
"function": "/function";
"function_hide-child": "/function/hide-child";
"function_hide-child_one": "/function/hide-child/one";
"function_hide-child_three": "/function/hide-child/three";
"function_hide-child_two": "/function/hide-child/two";
"function_multi-tab": "/function/multi-tab";
"function_request": "/function/request";
"function_super-page": "/function/super-page";
"function_tab": "/function/tab";
"function_toggle-auth": "/function/toggle-auth";
"home": "/home";
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?";
"manage": "/manage";
"manage_dept": "/manage/dept";
"manage_dict": "/manage/dict";
"manage_menu": "/manage/menu";
"manage_post": "/manage/post";
"manage_role": "/manage/role";
"manage_route": "/manage/route";
"manage_user": "/manage/user";
"manage_user-detail": "/manage/user-detail/:id";
"user-center": "/user-center";
};
/**
* route key
*/
export type RouteKey = keyof RouteMap;
export type RouteKey = string;
/**
* route path
*/
export type RoutePath = RouteMap[RouteKey];
export type RoutePath = string;
export type RouteMap =Record<string,string>;
/**
* custom route key
*/
export type CustomRouteKey = Extract<
RouteKey,
| "root"
| "not-found"
| "exception"
| "exception_403"
| "exception_404"
| "exception_500"
>;
*/
export type CustomRouteKey = string;
/**
* the generated route key
*/
*/
export type GeneratedRouteKey = Exclude<RouteKey, CustomRouteKey>;
/**
* the first level route key, which contain the layout of the route
*/
export type FirstLevelRouteKey = Extract<
RouteKey,
| "403"
| "404"
| "500"
| "about"
| "function"
| "home"
| "login"
| "manage"
| "user-center"
>;
export type FirstLevelRouteKey = string;
/**
* the custom first level route key
*/
export type CustomFirstLevelRouteKey = Extract<
CustomRouteKey,
| "root"
| "not-found"
| "exception"
>;
export type CustomFirstLevelRouteKey = string;
/**
* the last level route key, which has the page file
*/
export type LastLevelRouteKey = Extract<
RouteKey,
| "403"
| "404"
| "500"
| "login"
| "about"
| "function_hide-child_one"
| "function_hide-child_three"
| "function_hide-child_two"
| "function_multi-tab"
| "function_request"
| "function_super-page"
| "function_tab"
| "function_toggle-auth"
| "home"
| "manage_dept"
| "manage_dict"
| "manage_menu"
| "manage_post"
| "manage_role"
| "manage_route"
| "manage_user-detail"
| "manage_user"
| "user-center"
>;
export type LastLevelRouteKey = string
/**
* the custom last level route key
*/
export type CustomLastLevelRouteKey = Extract<
CustomRouteKey,
| "root"
| "not-found"
| "exception_403"
| "exception_404"
| "exception_500"
>;
export type CustomLastLevelRouteKey = string;
/**
* the single level route key
@@ -190,8 +98,10 @@ declare module "@elegant-router/types" {
type SingleLevelRoute<K extends SingleLevelRouteKey = SingleLevelRouteKey> = K extends string
? Omit<ElegantConstRoute, 'children'> & {
name: K;
path: RouteMap[K];
component: `layout.${RouteLayout}$view.${K}`;
path: K;
meta?: Record<K, any>;
component?: `layout.${RouteLayout}$view.${K}` | `layout.${RouteLayout}` | `view.${K}` ;
children?: SingleLevelRoute[];
}
: never;
@@ -201,18 +111,18 @@ declare module "@elegant-router/types" {
type LastLevelRoute<K extends GeneratedRouteKey> = K extends LastLevelRouteKey
? Omit<ElegantConstRoute, 'children'> & {
name: K;
path: RouteMap[K];
path: string;
component: `view.${K}`;
}
: never;
/**
* the center level route
*/
type CenterLevelRoute<K extends GeneratedRouteKey> = K extends CenterLevelRouteKey
? Omit<ElegantConstRoute, 'component'> & {
name: K;
path: RouteMap[K];
path: string;
children: (CenterLevelRoute<GetChildRouteKey<K>> | LastLevelRoute<GetChildRouteKey<K>>)[];
}
: never;
@@ -223,19 +133,19 @@ declare module "@elegant-router/types" {
type MultiLevelRoute<K extends FirstLevelRouteNotSingleKey = FirstLevelRouteNotSingleKey> = K extends string
? ElegantConstRoute & {
name: K;
path: RouteMap[K];
path: K;
component: `layout.${RouteLayout}`;
children: (CenterLevelRoute<GetChildRouteKey<K>> | LastLevelRoute<GetChildRouteKey<K>>)[];
}
: never;
/**
* the custom first level route
*/
type CustomSingleLevelRoute<K extends CustomFirstLevelRouteKey = CustomFirstLevelRouteKey> = K extends string
? Omit<ElegantConstRoute, 'children'> & {
name: K;
path: RouteMap[K];
path: string;
component?: `layout.${RouteLayout}$view.${LastLevelRouteKey}`;
}
: never;
@@ -246,7 +156,7 @@ declare module "@elegant-router/types" {
type CustomLastLevelRoute<K extends CustomRouteKey> = K extends CustomLastLevelRouteKey
? Omit<ElegantConstRoute, 'children'> & {
name: K;
path: RouteMap[K];
path: string;
component?: `view.${LastLevelRouteKey}`;
}
: never;
@@ -257,7 +167,7 @@ declare module "@elegant-router/types" {
type CustomCenterLevelRoute<K extends CustomRouteKey> = K extends CustomCenterLevelRouteKey
? Omit<ElegantConstRoute, 'component'> & {
name: K;
path: RouteMap[K];
path: string;
children: (CustomCenterLevelRoute<GetChildRouteKey<K>> | CustomLastLevelRoute<GetChildRouteKey<K>>)[];
}
: never;
@@ -269,7 +179,7 @@ declare module "@elegant-router/types" {
K extends string
? ElegantConstRoute & {
name: K;
path: RouteMap[K];
path: string;
component: `layout.${RouteLayout}`;
children: (CustomCenterLevelRoute<GetChildRouteKey<K>> | CustomLastLevelRoute<GetChildRouteKey<K>>)[];
}
@@ -278,7 +188,7 @@ declare module "@elegant-router/types" {
/**
* the custom route
*/
type CustomRoute = CustomSingleLevelRoute | CustomMultiLevelRoute;
type CustomRoute = CustomSingleLevelRoute | CustomMultiLevelRoute | any;
/**
* the generated route

View File

@@ -13,8 +13,6 @@ declare namespace Env {
readonly VITE_BASE_URL: string;
/** The title of the application */
readonly VITE_APP_TITLE: string;
/** The description of the application */
readonly VITE_APP_DESC: string;
/** The router history mode */
readonly VITE_ROUTER_HISTORY_MODE?: RouterHistoryMode;
/** The prefix of the iconify icon */
@@ -78,13 +76,6 @@ declare namespace Env {
* - Dynamic: the auth routes is generated in back-end
*/
readonly VITE_AUTH_ROUTE_MODE: 'static' | 'dynamic';
/**
* The home route key
*
* It only has effect when the auth route mode is static, if the route mode is dynamic, the home route key is
* defined in the back-end
*/
readonly VITE_ROUTE_HOME: import('@elegant-router/types').LastLevelRouteKey;
/**
* Default menu icon if menu icon is not set
*

View File

@@ -20,6 +20,3 @@ interface Document {
interface ImportMeta {
readonly env: Env.ImportMeta;
}
/** Build time of the project */
declare const BUILD_TIME: string;

View File

@@ -2,7 +2,6 @@
import { computed } from 'vue';
import type { Component } from 'vue';
import { getColorPalette, mixColor } from '@sa/utils';
import { $t } from '@/locales';
import { useThemeStore } from '@/store/modules/theme';
import { loginModuleRecord } from '@/constants/app';
import PwdLogin from './modules/pwd-login.vue';
@@ -10,14 +9,16 @@ import CodeLogin from './modules/code-login.vue';
import Register from './modules/register.vue';
import ResetPwd from './modules/reset-pwd.vue';
import BindWechat from './modules/bind-wechat.vue';
import { WifiOutlined } from '@ant-design/icons-vue';
import {useI18n} from "vue-i18n";
//import { $t } from '@/locales';
interface Props {
/** The login module */
module?: UnionKey.LoginModule;
}
const props = defineProps<Props>();
const {t} = useI18n();
const themeStore = useThemeStore();
interface LoginModule {
@@ -54,12 +55,12 @@ const bgColor = computed(() => {
<ACard class="relative z-4">
<div class="w-400px lt-sm:w-300px">
<header class="flex-y-center justify-between">
<SystemLogo class="text-64px text-primary lt-sm:text-48px" />
<h3 class="text-28px text-primary font-500 lt-sm:text-22px">{{ $t('system.title') }}</h3>
<WifiOutlined class="text-64px text-primary lt-sm:text-48px" style="margin-left: 9px"/>
<h3 class="text-18px text-primary font-medium">{{ t(activeModule.label) }}</h3>
</header>
<main class="pt-24px">
<h3 class="text-18px text-primary font-medium">{{ $t(activeModule.label) }}</h3>
<div class="animation-slide-in-left pt-24px">
<main >
<h3 class="text-28px text-primary font-500 lt-sm:text-22px">{{ "WANFi" }}</h3>
<div class="animation-slide-in-left ">
<Transition :name="themeStore.page.animateMode" mode="out-in" appear>
<component :is="activeModule.component" />
</Transition>

View File

@@ -1,25 +1,27 @@
<script setup lang="ts">
import { $t } from '@/locales';
import { loginModuleRecord } from '@/constants/app';
import { useRouterPush } from '@/hooks/common/router';
import { useAntdForm, useFormRules } from '@/hooks/common/form';
import { useAuthStore } from '@/store/modules/auth';
import {useI18n} from "vue-i18n";
defineOptions({
name: 'PwdLogin'
});
const { t } = useI18n();
const authStore = useAuthStore();
const { toggleLoginModule } = useRouterPush();
const { formRef, validate } = useAntdForm();
const { patternRules } = useFormRules();
const codeImg = ref('');
getCheckCode();
const model = reactive({
username: 'ryadmin',
password: 'admin123',
username: '123456',
password: '123456',
code: '',
uuid: '',
authType: 'sys'
@@ -31,11 +33,11 @@ const rules = {
};
async function handleSubmit() {
await validate();
await validate();//验证表单内容
await authStore.login({
loginForm: model,
loginForm: model,//发送表单的数据
onError() {
getCheckCode();
getCheckCode();//重新获取验证码
}
});
}
@@ -45,6 +47,9 @@ async function getCheckCode() {
if (!error) {
codeImg.value = `data:image/png;base64,${data.img}`;
model.uuid = data.uuid;
if (data?.text) {
model.code = data.text;
}
}
}
</script>
@@ -73,18 +78,18 @@ async function getCheckCode() {
<ASpace direction="vertical" size="large" class="w-full">
<div class="flex-y-center justify-between">
<ACheckbox>{{ $t('page.login.pwdLogin.rememberMe') }}</ACheckbox>
<AButton type="text" @click="toggleLoginModule('reset-pwd')">{{ $t('page.login.pwdLogin.forgetPassword') }}</AButton>
<AButton type="text" @click="toggleLoginModule('reset-pwd')">{{ t('page.login.pwdLogin.forgetPassword') }}</AButton>
</div>
<AButton type="primary" block size="large" shape="round" :loading="authStore.loginLoading" @click="handleSubmit">
{{ $t('common.confirm') }}
</AButton>
<div class="flex-y-center justify-between">
<AButton class="h-34px flex-1" block @click="toggleLoginModule('code-login')">
{{ $t(loginModuleRecord['code-login']) }}
</AButton>
<!-- <AButton class="h-34px flex-1" block @click="toggleLoginModule('code-login')">-->
<!-- {{ t(loginModuleRecord['code-login']) }}-->
<!-- </AButton>-->
<div class="w-12px"></div>
<AButton class="h-34px flex-1" block @click="toggleLoginModule('register')">
{{ $t(loginModuleRecord.register) }}
{{ t(loginModuleRecord.register) }}
</AButton>
</div>
</ASpace>

View File

@@ -1,86 +1,445 @@
<script setup lang="ts">
import { computed, reactive } from 'vue';
import { $t } from '@/locales';
import { useRouterPush } from '@/hooks/common/router';
import { useAntdForm, useFormRules } from '@/hooks/common/form';
import { useCaptcha } from '@/hooks/business/captcha';
import {computed, reactive, ref} from 'vue';
import {useI18n} from 'vue-i18n'; // 添加这行
import {useAuthStore} from '@/store/modules/auth';
import {useRouterPush} from '@/hooks/common/router';
import {useAntdForm, useFormRules} from '@/hooks/common/form';
import {useCaptcha} from '@/hooks/business/captcha';
import {useWindowSize} from '@vueuse/core';
import {registerTerms} from '@/views/_builtin/login/modules/terms';
import dayjs from 'dayjs';
import type {Rule} from 'ant-design-vue/es/form';
const { t } = useI18n();
const authStore = useAuthStore();
defineOptions({
name: 'CodeLogin'
name: 'Register'
});
const { toggleLoginModule } = useRouterPush();
const { formRef, validate } = useAntdForm();
const { label, isCounting, loading, getCaptcha } = useCaptcha();
interface FormModel {
const { width } = useWindowSize();
const isMobile = computed(() => width.value <= 640);
// 当前步骤
const currentStep = ref(0);
// 是否同意协议
const agreeTerms = ref(false);
// 定义一个统一的数据模型
interface RegisterModel {
username: string;
password: string;
email: string;
fullName: string;
age:0,
gender: string;
phone: string;
address: string;
code: string;
authType: string;
}
// 使用一个统一的 model
const model = reactive<RegisterModel>({
username: '',
password: '',
email: '',
fullName: '',
age:0,
gender: '',
phone: '',
address: '',
code: '',
authType: 'u'
});
// 第一步表单数据
interface BasicFormModel {
username: string;
fullName: string;
birthDate: string;
gender: string;
phone: string;
email: string;
address: string;
}
const basicModel = reactive<BasicFormModel>({
username: '',
fullName: '',
birthDate: '',
gender: '',
phone: '',
email: '',
address: ''
});
// 第三表单数据
interface SecurityFormModel {
email: string;
code: string;
password: string;
confirmPassword: string;
}
const model: FormModel = reactive({
phone: '',
const securityModel = reactive<SecurityFormModel>({
email: '',
code: '',
password: '',
confirmPassword: ''
});
const rules = computed<Record<keyof FormModel, App.Global.FormRule[]>>(() => {
const { formRules, createConfirmPwdRule } = useFormRules();
// 第一步表单验证规则
const basicRules = computed<Record<string, Rule | Rule[]>>(() => {
const { formRules } = useFormRules();
return {
phone: formRules.phone,
code: formRules.code,
password: formRules.pwd,
confirmPassword: createConfirmPwdRule(model.password)
username: formRules.username,
email: formRules.email,
birthDate: [{ required: true, message: t('page.login.register.birthDateRequired') }],
phone: [{ pattern: /^1[3-9]\d{9}$/, message: t('form.phone.invalid'), trigger: 'blur' }]
};
});
async function handleSubmit() {
await validate();
// request to register
$message?.success($t('page.login.common.validateSuccess'));
// 第三步表单验证规则
const securityRules = computed<Record<string, Rule | Rule[]>>(() => {
const { formRules, createConfirmPwdRule } = useFormRules();
return {
code: formRules.code,
password: formRules.pwd,
confirmPassword: createConfirmPwdRule(securityModel.password)
};
});
// 协议内容
const terms = computed(() => {
const locale = useI18n().locale.value;
return registerTerms[locale === 'zh-CN' ? 'zh-CN' : 'en-US'];
});
// 步骤控制函数
async function nextStep() {
if (currentStep.value === 0) {
await validate();
// 复制电话号码到第三步
securityModel.email = basicModel.email;
}
if (currentStep.value === 1) {
if (!agreeTerms.value) {
window.$message?.error(t('page.login.register.agreeTermsFirst'));
return;
}
}
currentStep.value += 1;
}
function prevStep() {
currentStep.value -= 1;
}
//注册按钮
async function handleSubmit() {
try {
await validate();
// 整合表单数据
model.username = basicModel.username;
model.password = securityModel.password;
model.email = securityModel.email;
model.fullName = basicModel.fullName;
model.gender = basicModel.gender; // 直接使用 gender 值
model.phone = basicModel.phone;
model.address = basicModel.address;
model.code = securityModel.code;
const success = await authStore.register({
...model,
age: dayjs().diff(dayjs(basicModel.birthDate), 'year'),
sex: model.gender, // 直接使用 gender 值,不需要转换
phonenumber: model.phone,
});
if (success) {
window.$message?.success(t('page.login.register.registerSuccess'));
toggleLoginModule('pwd-login');
}
} catch (error) {
console.error('Form validation failed:', error);
}
}
// 在script setup部分添加一个新的计算属性
const showSteps = computed(() => !isMobile.value);
</script>
<template>
<AForm ref="formRef" :model="model" :rules="rules">
<AFormItem name="phone">
<AInput v-model:value="model.phone" size="large" :placeholder="$t('page.login.common.phonePlaceholder')" />
</AFormItem>
<AFormItem name="code">
<div class="w-full flex-y-center gap-16px">
<AInput v-model:value="model.code" size="large" :placeholder="$t('page.login.common.codePlaceholder')" />
<AButton size="large" :disabled="isCounting" :loading="loading" @click="getCaptcha(model.phone)">
{{ label }}
</AButton>
<ASteps
v-if="showSteps"
:current="currentStep"
size="small"
class="max-w-full mb-16px"
direction="horizontal"
:responsive="false"
>
<AStep :title="t('page.login.register.basicInfo')" />
<AStep :title="t('page.login.register.terms')" />
<AStep :title="t('page.login.register.security')" />
</ASteps>
<div v-else class="mobile-step-indicator mb-16px text-center">
{{ currentStep + 1 }}/3: {{
currentStep === 0
? t('page.login.register.basicInfo')
: currentStep === 1
? t('page.login.register.terms')
: t('page.login.register.security')
}}
</div>
<div class="step-content">
<!-- 第一步基本信息 -->
<div v-if="currentStep === 0">
<AForm
ref="formRef"
:model="basicModel"
:rules="basicRules"
:label-wrap="true"
:label-col="{ span: 8 }"
:wrapper-col="{ span: 16 }"
>
<ARow :gutter="[8,2]">
<ACol :span="24" :lg="24">
<AFormItem name="username" :label="t('page.login.register.username')">
<AInput v-model:value="basicModel.username" />
</AFormItem>
</ACol>
<ACol :span="24" :lg="24">
<AFormItem name="fullName" :label="t('page.login.register.fullName')">
<AInput v-model:value="basicModel.fullName" />
</AFormItem>
</ACol>
<ACol :xs="12" :sm="12" :lg="24">
<AFormItem name="birthDate" :label="t('page.login.register.birthDate')">
<ADatePicker
v-model:value="basicModel.birthDate"
class="!w-full birth-date-picker"
:placeholder="t('page.login.register.birthDatePlaceholder')"
:disabled-date="(current: dayjs.Dayjs) => current && current.isAfter(dayjs())"
/>
</AFormItem>
</ACol>
<ACol :xs="12" :sm="12" :lg="24">
<AFormItem name="gender" :label="t('page.login.register.gender')">
<ASelect v-model:value="basicModel.gender">
<ASelectOption value="male">{{ t('page.login.register.male') }}</ASelectOption>
<ASelectOption value="female">{{ t('page.login.register.female') }}</ASelectOption>
</ASelect>
</AFormItem>
</ACol>
<ACol :lg="24" :span="24">
<AFormItem name="phone" :label="t('page.login.register.phone')">
<AInput v-model:value="basicModel.phone" />
</AFormItem>
</ACol>
<ACol :lg="24" :span="24">
<AFormItem name="email" :label="t('page.login.register.email')">
<AInput v-model:value="basicModel.email" />
</AFormItem>
</ACol>
<ACol :lg="24" :span="24">
<AFormItem name="address" :label="t('page.login.register.address')">
<ATextarea v-model:value="basicModel.address" :rows="2" />
</AFormItem>
</ACol>
<ACol :lg="24" :span="24">
<ASpace direction="vertical" size="small" class="w-full">
<AButton type="primary" block size="small" @click="nextStep">
{{ t('page.login.register.next') }}
</AButton>
<AButton block size="small" @click="toggleLoginModule('pwd-login')">
{{ t('page.login.common.back') }}
</AButton>
</ASpace>
</ACol>
</ARow>
</AForm>
</div>
<!-- 第二步:协议 -->
<div v-if="currentStep === 1">
<ATextarea
:value="terms"
:rows="12"
readonly
class="mb-16px"
size="small"
:style="{ fontSize: '14px', lineHeight: '1.6' }"
/>
<div class="mb-16px">
<ACheckbox
v-model:checked="agreeTerms"
class="terms-checkbox"
>
{{ t('page.login.register.agreeTerms') }}
</ACheckbox>
</div>
</AFormItem>
<AFormItem name="password">
<AInputPassword
v-model:value="model.password"
size="large"
:placeholder="$t('page.login.common.passwordPlaceholder')"
/>
</AFormItem>
<AFormItem name="confirmPassword">
<AInputPassword
v-model:value="model.confirmPassword"
size="large"
:placeholder="$t('page.login.common.confirmPasswordPlaceholder')"
/>
</AFormItem>
<ASpace direction="vertical" size="large" class="w-full">
<AButton type="primary" block size="large" shape="round" @click="handleSubmit">
{{ $t('common.confirm') }}
</AButton>
<AButton block size="large" shape="round" @click="toggleLoginModule('pwd-login')">
{{ $t('page.login.common.back') }}
</AButton>
</ASpace>
</AForm>
<ASpace direction="vertical" size="small" class="w-full">
<AButton type="primary" block size="small" :disabled="!agreeTerms" @click="nextStep">
{{ t('page.login.register.next') }}
</AButton>
<AButton block size="small" @click="prevStep">
{{ t('page.login.register.prev') }}
</AButton>
</ASpace>
</div>
<!-- 第三步:安全信息 -->
<div v-if="currentStep === 2">
<AForm
ref="formRef"
:model="securityModel"
:rules="securityRules"
:label-wrap="true"
:label-col="{ span: 8 }"
:wrapper-col="{ span: 16 }"
class="compact-form"
>
<AFormItem name="email" :label="t('page.login.register.email')">
<AInput
v-model:value="securityModel.email"
:placeholder="t('page.login.common.emailPlaceholder')"
/>
</AFormItem>
<AFormItem name="code" :label="t('page.login.register.code')">
<div class="w-full flex-y-center gap-8px">
<AInput
v-model:value="securityModel.code"
:placeholder="t('page.login.common.codePlaceholder')"
/>
<AButton
size="small"
:disabled="isCounting"
:loading="loading"
@click="getCaptcha(securityModel.email)"
>
{{ label }}
</AButton>
</div>
</AFormItem>
<AFormItem name="password" :label="t('page.login.register.password')">
<AInputPassword
v-model:value="securityModel.password"
:placeholder="t('page.login.common.passwordPlaceholder')"
/>
</AFormItem>
<AFormItem name="confirmPassword" :label="t('page.login.register.confirmPassword')">
<AInputPassword
v-model:value="securityModel.confirmPassword"
:placeholder="t('page.login.common.confirmPasswordPlaceholder')"
/>
</AFormItem>
<AFormItem :wrapper-col="{ xs: { span: 24 }, sm: { span: 24 } }">
<ASpace direction="vertical" size="small" class="w-full">
<AButton type="primary" block size="small" @click="handleSubmit">
{{ t('common.confirm') }}
</AButton>
<AButton block size="small" @click="prevStep">
{{ t('page.login.register.prev') }}
</AButton>
</ASpace>
</AFormItem>
</AForm>
</div>
</div>
</template>
<style scoped></style>
<style scoped>
@media (max-width: 640px) {
:deep(.ant-form) {
.ant-form-item {
margin-bottom: 4px !important;
}
.ant-form-item-label {
padding: 0 !important;
line-height: 28px !important;
> label {
font-size: 13px !important;
height: 28px !important;
padding-bottom: 0 !important;
}
}
.ant-input,
.ant-input-password,
.ant-select,
.ant-input-number,
.ant-btn {
font-size: 13px !important;
height: 28px !important;
line-height: 28px !important;
padding-top: 0 !important;
padding-bottom: 0 !important;
}
.ant-select-selector {
height: 28px !important;
padding: 0 8px !important;
.ant-select-selection-item {
line-height: 26px !important;
}
}
.ant-input-number {
height: 32px !important;
input {
height: 30px !important;
padding: 0 8px !important;
}
}
}
.mobile-step-indicator {
font-size: 13px;
padding: 4px 8px;
margin-bottom: 8px;
text-align: center;
width: 100%;
border-radius: 4px;
}
:deep(.ant-space) {
gap: 4px !important;
}
}
:deep(.terms-checkbox) {
.ant-checkbox + span {
font-size: 14px;
color: rgba(0, 0, 0, 0.65);
}
}
:deep(.ant-input[readonly]) {
background-color: #f5f5f5;
cursor: default;
}
:deep(.ant-form-item-label) {
white-space: normal;
text-align: left;
> label {
height: auto !important;
padding-bottom: 4px;
}
}
.birth-date-picker {
margin-top: 2px !important;
}
</style>

View File

@@ -1,25 +1,27 @@
<script setup lang="ts">
import { computed, reactive } from 'vue';
import { $t } from '@/locales';
import { useRouterPush } from '@/hooks/common/router';
import { useAntdForm, useFormRules } from '@/hooks/common/form';
import {useCaptcha} from "@/hooks/business/captcha";
import {useI18n} from "vue-i18n";
defineOptions({
name: 'ResetPwd'
});
const { t } = useI18n();
const { toggleLoginModule } = useRouterPush();
const { formRef, validate } = useAntdForm();
const { label, isCounting, loading, getCaptcha } = useCaptcha();
interface FormModel {
phone: string;
email: string;
code: string;
password: string;
confirmPassword: string;
}
const model: FormModel = reactive({
phone: '',
email: '',
code: '',
password: '',
confirmPassword: ''
@@ -31,7 +33,9 @@ const rules = computed<RuleRecord>(() => {
const { formRules, createConfirmPwdRule } = useFormRules();
return {
phone: formRules.phone,
//phone: formRules.phone,
email:formRules.email,
code:formRules.code,
password: formRules.pwd,
confirmPassword: createConfirmPwdRule(model.password)
};
@@ -40,38 +44,48 @@ const rules = computed<RuleRecord>(() => {
async function handleSubmit() {
await validate();
// request to reset password
$message?.success($t('page.login.common.validateSuccess'));
$message?.success(t('page.login.common.validateSuccess'));
}
</script>
<template>
<AForm ref="formRef" :model="model" :rules="rules">
<AFormItem name="phone">
<AInput v-model:value="model.phone" size="large" :placeholder="$t('page.login.common.phonePlaceholder')" />
<AFormItem name="email">
<AInput v-model:value="model.email" size="large" :placeholder="t('page.login.common.emailPlaceholder')" />
</AFormItem>
<AFormItem name="code">
<AInput v-model:value="model.code" size="large" :placeholder="$t('page.login.common.codePlaceholder')" />
<AFormItem name="code" >
<div class="w-full flex-y-center gap-8px">
<AInput v-model:value="model.code" size="large" :placeholder="t('page.login.common.codePlaceholder')" />
<AButton
size="small"
:disabled="isCounting"
:loading="loading"
@click="getCaptcha(model.email)"
>
{{ label }}
</AButton>
</div>
</AFormItem>
<AFormItem name="password">
<AInputPassword
v-model:value="model.password"
size="large"
:placeholder="$t('page.login.common.passwordPlaceholder')"
:placeholder="t('page.login.common.passwordPlaceholder')"
/>
</AFormItem>
<AFormItem name="confirmPassword">
<AInputPassword
v-model:value="model.confirmPassword"
size="large"
:placeholder="$t('page.login.common.confirmPasswordPlaceholder')"
:placeholder="t('page.login.common.confirmPasswordPlaceholder')"
/>
</AFormItem>
<ASpace direction="vertical" size="large" class="w-full">
<AButton type="primary" block size="large" shape="round" @click="handleSubmit">
{{ $t('common.confirm') }}
{{ t('common.confirm') }}
</AButton>
<AButton block size="large" shape="round" @click="toggleLoginModule('pwd-login')">
{{ $t('page.login.common.back') }}
{{ t('page.login.common.back') }}
</AButton>
</ASpace>
</AForm>

View File

@@ -0,0 +1,50 @@
export const registerTerms = {
'zh-CN': `
用户协议和隐私政策
1. 总则
本协议是您与我们之间关于使用我们的服务所订立的协议。您注册成为用户,表示您已充分阅读、理解并同意接受本协议的全部内容。
2. 服务内容
2.1 我们将为您提供安全、可靠的服务。
2.2 我们保留随时修改或中断服务的权利。
3. 用户义务
3.1 用户应当遵守相关法律法规。
3.2 用户应当遵守社区规范和道德准则。
3.3 用户应当保护账号安全,不得将账号借给他人使用。
4. 隐私保护
4.1 我们将依法保护您的个人信息安全。
4.2 未经您的同意,我们不会向第三方披露您的个人信息。
4.3 我们采用业界标准的安全技术来保护您的个人信息。
5. 免责声明
5.1 因不可抗力导致的服务中断,本平台不承担责任。
5.2 用户因违反本协议造成的损失由用户自行承担。
`,
'en-US': `
User Agreement and Privacy Policy
1. General Provisions
This agreement is established between you and us regarding the use of our services. By registering as a user, you indicate that you have fully read, understood and agreed to accept all contents of this agreement.
2. Service Content
2.1 We will provide you with secure and reliable services.
2.2 We reserve the right to modify or interrupt services at any time.
3. User Obligations
3.1 Users shall comply with relevant laws and regulations.
3.2 Users shall comply with community standards and ethical guidelines.
3.3 Users shall protect account security and not lend their accounts to others.
4. Privacy Protection
4.1 We will protect your personal information security in accordance with law.
4.2 We will not disclose your personal information to third parties without your consent.
4.3 We use industry-standard security technology to protect your personal information.
5. Disclaimer
5.1 The platform is not liable for service interruptions caused by force majeure.
5.2 Users shall bear losses caused by their violation of this agreement.
`
};

View File

@@ -31,7 +31,7 @@ const pkgJson: PkgJson = {
devDependencies: Object.entries(devDependencies).map(item => transformVersionData(item))
};
const latestBuildTime = BUILD_TIME;
const latestBuildTime = "----";
</script>
<template>

View File

@@ -1,6 +1,4 @@
<script lang="ts" setup>
import type {} from 'ant-design-vue';
import { generatedRoutes } from '@/router/elegant/routes';
import { $t } from '@/locales';
import type { MenuModelType } from './form';
import { formRules, menuStatusOptions, menuTypeOptions, resetAddForm } from './form';
@@ -30,24 +28,6 @@ const title = computed(() => {
return titles[props.operateType];
});
// TODO: 根据菜单类型动态加载组件路径、目录类型只允许选择带有子元素的,菜单类型只允许选择没有子元素的
const componentOptions = computed(() => {
const excludePaths = ['/404', '/403', '/500'];
function transformRoutes(routes: any[]): any[] {
return routes.filter(route => {
if (route.children) {
route.children = transformRoutes(route.children);
return true;
}
if (!route.hideInMenu && !excludePaths.includes(route.path) && !route.path.startsWith('/login')) {
return true;
}
return false;
});
}
return transformRoutes(generatedRoutes);
});
watch(visible, val => {
if (val) {
@@ -95,10 +75,6 @@ async function getTreeData() {
}
}
function handleTreeSelect(node: any) {
model.value.component = node.component;
model.value.name = node.name;
}
getTreeData();
</script>
@@ -139,20 +115,9 @@ getTreeData();
</ARadioGroup>
</AFormItem>
<AFormItem v-if="model.menuType !== 'F'" label="菜单路径" name="path">
<div>
<!-- @vue-ignore -->
<ATreeSelect
v-if="model.isFrame === '1'"
v-model:value="model.path"
show-search
:field-names="{ value: 'path', label: 'path' }"
allow-clear
:tree-data="componentOptions"
tree-node-filter-prop="label"
@select="(_val, node) => handleTreeSelect(node)"
/>
<AInput v-else v-model:value="model.path" />
</div>
path <AInput v-model:value="model.path" />
component <AInput v-model:value="model.component" />
name <AInput v-model:value="model.name" />
</AFormItem>
<AFormItem v-if="model.menuType === 'C'" label="隐藏菜单" name="hideInMenu">
<ASwitch v-model:checked="model.hideInMenu" checked-value="0" un-checked-value="1" />

View File

@@ -0,0 +1,7 @@
<script setup lang="ts"></script>
<template>
<div>user_vip</div>
</template>
<style scoped></style>

View File

@@ -4,7 +4,7 @@ import transformerVariantGroup from '@unocss/transformer-variant-group';
import presetUno from '@unocss/preset-uno';
import type { Theme } from '@unocss/preset-uno';
import { presetSoybeanAdmin } from '@sa/uno-preset';
import presetAttributify from '@unocss/preset-attributify';
import presetAttributive from '@unocss/preset-attributify';
import { themeVars } from './src/theme/vars';
export default defineConfig<Theme>({
@@ -29,5 +29,5 @@ export default defineConfig<Theme>({
},
transformers: [transformerDirectives(), transformerVariantGroup()],
// @ts-expect-error presetUno is not compatible with the new API
presets: [presetUno({ dark: 'class' }), presetSoybeanAdmin(), presetAttributify()]
presets: [presetUno({ dark: 'class' }), presetSoybeanAdmin(), presetAttributive()]
});

View File

@@ -1,15 +1,12 @@
import process from 'node:process';
import { URL, fileURLToPath } from 'node:url';
import { defineConfig, loadEnv } from 'vite';
import dayjs from 'dayjs';
import { setupVitePlugins } from './build/plugins';
import { createViteProxy } from './build/config';
export default defineConfig(configEnv => {
const viteEnv = loadEnv(configEnv.mode, process.cwd()) as unknown as Env.ImportMeta;
const buildTime = dayjs().format('YYYY-MM-DD HH:mm:ss');
return {
base: viteEnv.VITE_BASE_URL,
resolve: {
@@ -26,12 +23,9 @@ export default defineConfig(configEnv => {
}
},
plugins: setupVitePlugins(viteEnv),
define: {
BUILD_TIME: JSON.stringify(buildTime)
},
server: {
host: '0.0.0.0',
port: 9527,
port: 8085,
open: false,
proxy: createViteProxy(viteEnv, configEnv.command === 'serve'),
fs: {

View File

@@ -1,7 +1,5 @@
import { defineConfig } from 'vitest/config'
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
}
})
test: {}
});