This commit is contained in:
六如
2024-10-05 21:13:29 +08:00
parent c08fec74c9
commit d9fb25dc0a
31 changed files with 695 additions and 226 deletions

View File

@@ -6,9 +6,7 @@ import gradientString from "gradient-string";
import boxen, { type Options as BoxenOptions } from "boxen";
dayjs.extend(duration);
const welcomeMessage = gradientString("cyan", "magenta").multiline(
`您好! 欢迎使用 pure-admin 开源项目\n我们为您精心准备了下面两个贴心的保姆级文档\nhttps://pure-admin.github.io/pure-admin-doc\nhttps://pure-admin-utils.netlify.app`
);
const welcomeMessage = gradientString("cyan", "magenta").multiline(`SOP Admin`);
const boxenOptions: BoxenOptions = {
padding: 0.5,

View File

@@ -93,7 +93,7 @@ export default defineFlatConfig([
"@typescript-eslint/prefer-as-const": "warn",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-import-type-side-effects": "error",
"@typescript-eslint/no-import-type-side-effects": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/consistent-type-imports": [
"error",

View File

@@ -8,7 +8,7 @@
name="viewport"
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
/>
<title>pure-admin-thin</title>
<title>SOP Admin</title>
<link rel="icon" href="/favicon.ico" />
<script>
window.process = {};

View File

@@ -5,7 +5,7 @@ import { defineFakeRoute } from "vite-plugin-fake-server/client";
* 服务管理
*/
const apiRouter = {
path: "/serviceManage",
path: "/serve",
meta: {
title: "服务管理",
icon: "ri:server-line",
@@ -13,7 +13,7 @@ const apiRouter = {
},
children: [
{
path: "/serviceManage/apiManage/index",
path: "/serve/api/index",
name: "ApiManage",
meta: {
title: "接口管理",

View File

@@ -1,5 +1,5 @@
{
"name": "pure-admin-thin",
"name": "SOP Admin",
"version": "5.8.0",
"private": true,
"type": "module",
@@ -55,6 +55,7 @@
"@vueuse/motion": "2.2.3",
"animate.css": "4.1.1",
"axios": "1.7.4",
"crypto-js": "4.2.0",
"dayjs": "1.11.12",
"echarts": "5.5.1",
"element-plus": "2.8.0",

View File

@@ -29,6 +29,9 @@ importers:
axios:
specifier: 1.7.4
version: 1.7.4
crypto-js:
specifier: 4.2.0
version: 4.2.0
dayjs:
specifier: 1.11.12
version: 1.11.12
@@ -1605,6 +1608,9 @@ packages:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
crypto-js@4.2.0:
resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
css-declaration-sorter@6.4.1:
resolution: {integrity: sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==}
engines: {node: ^10 || ^12 || >=14}
@@ -5390,6 +5396,8 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
crypto-js@4.2.0: {}
css-declaration-sorter@6.4.1(postcss@8.4.41):
dependencies:
postcss: 8.4.41

View File

@@ -1,6 +1,6 @@
{
"Version": "5.8.0",
"Title": "PureAdmin",
"Title": "SOP Admin",
"FixedHeader": true,
"HiddenSideBar": false,
"MultiTagsCache": false,
@@ -12,8 +12,8 @@
"OverallStyle": "light",
"Grey": false,
"Weak": false,
"HideTabs": false,
"HideFooter": false,
"HideTabs": true,
"HideFooter": true,
"Stretch": false,
"SidebarStatus": true,
"EpThemeColor": "#409EFF",

View File

@@ -48,9 +48,10 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
<LayNavMix v-if="layout === 'mix'" />
<div v-if="layout === 'vertical'" class="vertical-header-right">
<!-- 菜单搜索 -->
<!-- 菜单搜索
<LaySearch id="header-search" />
<!-- 国际化 -->
-->
<!-- 国际化
<el-dropdown id="header-translation" trigger="click">
<GlobalizationIcon
class="navbar-bg-hover w-[40px] h-[48px] p-[11px] cursor-pointer outline-none"
@@ -82,14 +83,19 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
</el-dropdown-menu>
</template>
</el-dropdown>
<!-- 全屏 -->
-->
<!-- 全屏
<LaySidebarFullScreen id="full-screen" />
<!-- 消息通知 -->
-->
<!-- 消息通知
<LayNotice id="header-notice" />
-->
<!-- 退出登录 -->
<el-dropdown trigger="click">
<span class="el-dropdown-link navbar-bg-hover select-none">
<!-- 头像
<img :src="userAvatar" :style="avatarsStyle" />
-->
<p v-if="username" class="dark:text-white">{{ username }}</p>
</span>
<template #dropdown>
@@ -104,6 +110,7 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
</el-dropdown-menu>
</template>
</el-dropdown>
<!-- 设置
<span
class="set-icon navbar-bg-hover"
:title="t('buttons.pureOpenSystemSet')"
@@ -111,6 +118,7 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
>
<IconifyIconOffline :icon="Setting" />
</span>
-->
</div>
</div>
</template>

View File

@@ -0,0 +1,14 @@
export type Result = {
success: boolean;
data: object;
};
export type PageResult = {
success: boolean;
msg: "";
code: "";
data: {
total: 0;
list: Array<any>;
};
};

View File

@@ -37,7 +37,7 @@ import {
* 如何排除文件请看https://cn.vitejs.dev/guide/features.html#negative-patterns
*/
const modules: Record<string, any> = import.meta.glob(
["./modules/**/*.ts", "!./modules/**/remaining.ts"],
["./modules/**/*.ts", "!./modules/**/remaining.ts", "!./modules/**/error.ts"],
{
eager: true
}

View File

@@ -22,6 +22,7 @@ import globalization from "@/assets/svg/globalization.svg?component";
import Lock from "@iconify-icons/ri/lock-fill";
import Check from "@iconify-icons/ep/check";
import User from "@iconify-icons/ri/user-3-fill";
import CryptoJS from "crypto-js";
defineOptions({
name: "Login"
@@ -40,8 +41,8 @@ const { title, getDropdownItemStyle, getDropdownItemClass } = useNav();
const { locale, translationCh, translationEn } = useTranslationLang();
const ruleForm = reactive({
username: "admin",
password: "admin123"
username: "",
password: ""
});
const onLogin = async (formEl: FormInstance | undefined) => {
@@ -49,8 +50,10 @@ const onLogin = async (formEl: FormInstance | undefined) => {
await formEl.validate((valid, fields) => {
if (valid) {
loading.value = true;
const pwd = ruleForm.password;
const hash = CryptoJS.SHA256(pwd).toString();
useUserStoreHook()
.loginByUsername({ username: ruleForm.username, password: "admin123" })
.loginByUsername({ username: ruleForm.username, password: hash })
.then(res => {
if (res.success) {
// 获取后端路由

View File

@@ -3,8 +3,8 @@ import type { FormRules } from "element-plus";
import { $t, transformI18n } from "@/plugins/i18n";
/** 密码正则密码格式应为8-18位数字、字母、符号的任意两种组合 */
export const REGEXP_PWD =
/^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z)]|[()])+$)(?!^.*[\u4E00-\u9FA5].*$)([^(0-9a-zA-Z)]|[()]|[a-z]|[A-Z]|[0-9]){8,18}$/;
// export const REGEXP_PWD =
// /^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z)]|[()])+$)(?!^.*[\u4E00-\u9FA5].*$)([^(0-9a-zA-Z)]|[()]|[a-z]|[A-Z]|[0-9]){8,18}$/;
/** 登录校验 */
const loginRules = reactive(<FormRules>{
@@ -13,8 +13,6 @@ const loginRules = reactive(<FormRules>{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error(transformI18n($t("login.purePassWordReg"))));
} else if (!REGEXP_PWD.test(value)) {
callback(new Error(transformI18n($t("login.purePassWordRuleReg"))));
} else {
callback();
}

View File

@@ -0,0 +1,315 @@
import { computed, ref } from "vue";
import {
type PlusColumn,
useTable,
type PageInfo,
type FieldValues,
type ButtonsCallBackParams
} from "plus-pro-components";
import { type PageResult } from "@/model";
import { http } from "@/utils/http";
import { ElMessage } from "element-plus";
// 后端请求接口
const apiUrl = {
page: "/serve/api/page",
add: "/serve/api/add",
update: "/serve/api/update",
del: "/serve/api/delete"
};
// 查询表单对象
export const searchFormData = ref({
apiName: "",
pageIndex: 1,
pageSize: 10
});
// 表格对象
export const {
tableData,
total,
pageInfo,
buttons: actionButtons
} = useTable<any[]>();
// 默认每页条数,默认10
pageInfo.value.pageSize = 10;
// 查询表单字段定义
export const searchFormColumns: PlusColumn[] = [
{
label: "接口名称",
prop: "apiName"
}
];
// 表格字段定义
export const tableColumns: PlusColumn[] = [
{
label: "应用名称",
prop: "application"
},
{
label: "接口名称",
prop: "apiName"
},
{
label: "版本号",
prop: "apiVersion",
width: 80
},
{
label: "接口描述",
prop: "description"
},
{
label: "需要授权",
prop: "isPermission",
width: 100,
valueType: "select",
options: [
{
label: "否",
value: 0,
color: "red"
},
{
label: "是",
value: 1,
color: "green"
}
]
},
{
label: "需要token",
prop: "isNeedToken",
width: 100,
valueType: "select",
options: [
{
label: "否",
value: 0,
color: "red"
},
{
label: "是",
value: 1,
color: "green"
}
]
},
{
label: "状态",
prop: "status",
width: 80,
valueType: "select",
options: [
{
label: "禁用",
value: 0,
color: "red"
},
{
label: "启用",
value: 1,
color: "green"
}
]
},
{
label: "添加时间",
prop: "addTime"
}
];
actionButtons.value = [
{
// 修改
text: "修改",
code: "edit",
props: {
type: "primary"
},
show: computed(() => true),
onClick(params: ButtonsCallBackParams) {
if (params?.formRefs) {
// isEdit v0.1.8 新增
const isEdit = params.formRefs[0].isEdit.value;
isEdit
? params.formRefs[0]?.stopCellEdit()
: params.formRefs[0]?.startCellEdit();
}
}
},
{
// 删除
text: "删除",
code: "delete",
props: {
type: "danger"
},
confirm: {
options: { draggable: true }
},
onClick(params: ButtonsCallBackParams) {
console.log(params, "onClick");
},
onConfirm(params: ButtonsCallBackParams) {
console.log(params, "onConfirm");
},
onCancel(params: ButtonsCallBackParams) {
console.log(params, "onCancel");
}
}
];
// 弹窗表单
export const dlgShow = ref(false);
export const editFormData = ref<FieldValues>({
application: "",
status: 1,
isPermission: 0,
isNeedToken: 0
});
export const editFormRules = {
application: [{ required: true, message: "请输入应用名称" }],
apiName: [{ required: true, message: "请输入接口名称" }],
apiVersion: [{ required: true, message: "请输入版本号" }],
description: [{ required: true, message: "请输入接口描述" }]
};
export const handleDlgOpen = () => {
dlgShow.value = true;
};
export const handleSave = () => {
http
.post<PageResult, any>(apiUrl.add, { data: editFormData.value })
.then(resp => {
if (resp.success) {
ElMessage({
message: "保存成功",
type: "success"
});
dlgShow.value = false;
search();
} else {
console.log(resp);
ElMessage.error(`保存失败:${resp.msg}`);
}
});
};
export const editFormColumns: PlusColumn[] = [
{
label: "应用名称",
prop: "application",
valueType: "copy"
},
{
label: "接口名称",
prop: "apiName",
valueType: "copy"
},
{
label: "版本号",
prop: "apiVersion",
valueType: "copy"
},
{
label: "接口描述",
prop: "description",
valueType: "copy"
},
{
label: "备注",
prop: "remark",
valueType: "textarea",
fieldProps: {
maxlength: 10,
showWordLimit: true,
autosize: { minRows: 2, maxRows: 4 }
}
},
{
label: "需要授权",
prop: "isPermission",
valueType: "radio",
options: [
{
label: "否",
value: 0,
color: "red"
},
{
label: "是",
value: 1,
color: "green"
}
]
},
{
label: "需要token",
prop: "isNeedToken",
valueType: "radio",
options: [
{
label: "否",
value: 0,
color: "red"
},
{
label: "是",
value: 1,
color: "green"
}
]
},
{
label: "状态",
prop: "status",
valueType: "radio",
options: [
{
label: "禁用",
value: 0,
color: "red"
},
{
label: "启用",
value: 1,
color: "green"
}
]
}
];
// 点击查询按钮
export const handleSearch = () => {
pageInfo.value.page = 1;
search();
};
// 查询
const search = async () => {
try {
const { data } = await doSearch();
tableData.value = data.list;
total.value = data.total;
} catch (error) {}
};
// 请求接口
const doSearch = async () => {
// 查询参数
const data = searchFormData.value;
// 添加分页参数
data.pageIndex = pageInfo.value.page;
data.pageSize = pageInfo.value.pageSize;
return http.get<PageResult, any>(apiUrl.page, { params: data });
};
// 分页事件
export const handlePaginationChange = (_pageInfo: PageInfo): void => {
pageInfo.value = _pageInfo;
search();
};
// 页面加载
search();

View File

@@ -0,0 +1,61 @@
<script setup lang="ts">
import {
actionButtons,
dlgShow,
editFormColumns,
editFormData,
editFormRules,
handleDlgOpen,
handlePaginationChange,
handleSave,
handleSearch,
pageInfo,
searchFormColumns,
searchFormData,
tableColumns,
tableData,
total
} from "./index";
</script>
<template>
<el-card shadow="never">
<template #header>
<PlusSearch
v-model="searchFormData"
:columns="searchFormColumns"
:show-number="2"
label-position="right"
:has-reset="false"
@search="handleSearch"
/>
</template>
<PlusTable
:columns="tableColumns"
:table-data="tableData"
:action-bar="{ buttons: actionButtons, width: 120 }"
:pagination="{
total,
modelValue: pageInfo,
pageSizeList: [10, 20, 50, 100],
align: 'right'
}"
@paginationChange="handlePaginationChange"
>
<template #title>
<el-button type="primary" @click="handleDlgOpen">新增接口</el-button>
</template>
</PlusTable>
<PlusDialogForm
v-model:visible="dlgShow"
v-model="editFormData"
label-width="200px"
:form="{
columns: editFormColumns,
rules: editFormRules,
labelWidth: '100px',
labelPosition: 'right'
}"
@confirm="handleSave"
/>
</el-card>
</template>

View File

@@ -1,151 +0,0 @@
<script setup lang="ts">
import { ref } from "vue";
import { type PlusColumn, useTable, PageInfo } from "plus-pro-components";
const state = ref({
status: "0",
time: new Date().toString()
});
const columns: PlusColumn[] = [
{
label: "接口名称",
prop: "name"
}
];
const handleChange = (values: any) => {
console.log(values, "change");
};
const handleSearch = (values: any) => {
console.log(values, "search");
};
const handleRest = () => {
console.log("handleRest");
};
interface TableRow {
id: number;
name: string;
status: string;
tag: string;
time: Date;
}
const TestServe = {
getList: async () => {
const data = Array.from({ length: 3 }).map((item, index) => {
return {
id: index,
name: index + "name",
status: String(index % 3),
tag: "success",
time: new Date()
};
});
return {
data: data as TableRow[],
total: data.length
};
}
};
const { tableData, total, pageInfo } = useTable<TableRow[]>();
pageInfo.value.pageSize = 10;
const tableConfig: PlusColumn[] = [
{
label: "名称",
prop: "name",
disabledHeaderFilter: true,
tooltip: "名称",
tableColumnProps: {
align: "center",
showOverflowTooltip: true
}
},
{
label: "状态",
prop: "status",
valueType: "select",
options: [
{
label: "未解决",
value: "0",
color: "red"
},
{
label: "已解决",
value: "1",
color: "blue"
},
{
label: "解决中",
value: "2",
color: "yellow"
},
{
label: "失败",
value: "3",
color: "red"
}
]
},
{
label: "标签",
prop: "tag",
valueType: "tag",
fieldProps: (value: string) => {
return { type: value };
}
},
{
label: "时间",
prop: "time",
valueType: "date-picker"
}
];
const getList = async () => {
try {
const { data, total: dataTotal } = await TestServe.getList();
tableData.value = data;
total.value = dataTotal;
} catch (error) {}
};
const handlePaginationChange = (_pageInfo: PageInfo): void => {
pageInfo.value = _pageInfo;
getList();
};
getList();
</script>
<template>
<el-card>
<PlusSearch
v-model="state"
:columns="columns"
:show-number="2"
label-position="right"
:has-reset="false"
@change="handleChange"
@search="handleSearch"
@reset="handleRest"
/>
<el-divider />
<PlusTable
:columns="tableConfig"
:table-data="tableData"
:pagination="{
total,
modelValue: pageInfo,
pageSizeList: [10, 20, 50, 100],
align: 'right'
}"
>
<template #title>
<el-button type="primary">新增接口</el-button>
</template>
</PlusTable>
</el-card>
</template>

View File

@@ -5,5 +5,5 @@ defineOptions({
</script>
<template>
<h1>Pure-Admin-Thin国际化版本</h1>
<h1>欢迎使用SOP Admin</h1>
</template>

View File

@@ -24,7 +24,14 @@ export default ({ mode }: ConfigEnv): UserConfigExport => {
port: VITE_PORT,
host: "0.0.0.0",
// 本地跨域代理 https://cn.vitejs.dev/config/server-options.html#server-proxy
proxy: {},
proxy: {
"/api": {
// 这里填写后端地址
target: "http://127.0.0.1:8082",
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, "")
}
},
// 预热文件以提前转换和缓存结果,降低启动期间的初始页面加载时长并防止转换瀑布
warmup: {
clientFiles: ["./index.html", "./src/{views,components}/*"]