first commit

This commit is contained in:
编码猿
2024-09-27 02:06:13 +08:00
commit 852d94fbb9
36760 changed files with 3274413 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router'
</script>
<template>
<RouterView />
</template>
<style lang="less">
@import "@pub/clear.less";
@import "@pub/reset.less";
</style>

View File

@@ -0,0 +1,86 @@
import axios, { AxiosResponse } from "axios";
import { config } from "@/config";
import { resultType } from "@/types/response/result";
import { showLoadingToast, closeToast, showFailToast } from 'vant';
import 'vant/es/toast/style';
import { VagueOject } from "@/types/common";
interface requestParams {
baseUrl: string,
url: string,
data: VagueOject,
header: VagueOject
}
const instance = axios.create({
timeout: 10000,
headers: {
"Content-Type": "application/json"
}
});
instance.interceptors.request.use(
function (config) {
// showLoadingToast({
// duration: 0,
// forbidClick: true,
// message: '加载中....',
// })
return config;
},
function (error) {
return Promise.reject(error);
}
);
instance.interceptors.response.use(
function (response) {
// closeToast()
return ResponseCodeCheck(response)
},
function (error) {
return Promise.reject(error);
}
);
const ResponseCodeCheck = (response: AxiosResponse<resultType<any>>) => {
switch (response.data.code) {
case 200:
return response.data.data
break;
case 404:
showFailToast({
message: response.data.msg
});
throw new Error(response.data.msg)
break;
default:
break;
}
}
export const GET = async <T> (params: requestParams): Promise<T> => {
return await instance.get<T>(params.url, {
baseURL: params.baseUrl,
params: params.data,
headers: Object.assign(params.header, {
"token": localStorage.getItem("token")
})
})
}
export const POST = async <T> (params: requestParams): Promise<T> => {
return await instance.post<T>(params.url, params.data, {
headers: Object.assign(params.header, {
"token": localStorage.getItem("token")
}) ,
baseURL: params.baseUrl,
})
}
export default {
GET, POST
}

View File

@@ -0,0 +1,11 @@
.home {
.list {
li {
padding-bottom: 30px;
margin-bottom: 30px;
.line(bottom);
}
}
}

View File

@@ -0,0 +1,2 @@
@import "../pub/mixins.less";
@import "../pub/vars.less";

View File

@@ -0,0 +1,403 @@
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
/* Document
========================================================================== */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in iOS.
*/
body {
font-size: 0;
}
html {
line-height: 1;
-webkit-text-size-adjust: 100%;
/* 2 */
}
/* Sections
========================================================================== */
/**
* Remove the margin in all browsers.
*/
body,
ul,
li,
p,
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 0;
padding: 0;
list-style: none;
}
::-webkit-scrollbar {
width: 0;
height: 0;
}
/**
* Render the `main` element consistently in IE.
*/
main {
display: block;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/* Grouping content
========================================================================== */
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box;
/* 1 */
height: 0;
/* 1 */
overflow: visible;
/* 2 */
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
pre {
font-family: monospace, monospace;
/* 1 */
font-size: 1em;
/* 2 */
}
/* Text-level semantics
========================================================================== */
/**
* Remove the gray background on active links in IE 10.
*/
a {
background-color: transparent;
text-decoration: none;
color: #000;
}
/**
* 1. Remove the bottom border in Chrome 57-
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
border-bottom: none;
/* 1 */
text-decoration: underline;
/* 2 */
text-decoration: underline dotted;
/* 2 */
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp {
font-family: monospace, monospace;
/* 1 */
font-size: 1em;
/* 2 */
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Remove the border on images inside links in IE 10.
*/
img {
border-style: none;
width: 100%;
}
/* Forms
========================================================================== */
/**
* 1. Change the font styles in all browsers.
* 2. Remove the margin in Firefox and Safari.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit;
/* 1 */
font-size: 100%;
/* 1 */
line-height: 1.15;
/* 1 */
margin: 0;
/* 2 */
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
button,
input {
/* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
button,
select {
/* 1 */
text-transform: none;
}
/**
* Correct the inability to style clickable types in iOS and Safari.
*/
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Correct the padding in Firefox.
*/
fieldset {
padding: 0.35em 0.75em 0.625em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
legend {
box-sizing: border-box;
/* 1 */
color: inherit;
/* 2 */
display: table;
/* 1 */
max-width: 100%;
/* 1 */
padding: 0;
/* 3 */
white-space: normal;
/* 1 */
}
/**
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
vertical-align: baseline;
}
/**
* Remove the default vertical scrollbar in IE 10+.
*/
textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10.
* 2. Remove the padding in IE 10.
*/
[type="checkbox"],
[type="radio"] {
box-sizing: border-box;
/* 1 */
padding: 0;
/* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type="search"] {
-webkit-appearance: textfield;
/* 1 */
outline-offset: -2px;
/* 2 */
}
/**
* Remove the inner padding in Chrome and Safari on macOS.
*/
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button;
/* 1 */
font: inherit;
/* 2 */
}
/* Interactive
========================================================================== */
/*
* Add the correct display in Edge, IE 10+, and Firefox.
*/
details {
display: block;
}
/*
* Add the correct display in all browsers.
*/
summary {
display: list-item;
}
/* Misc
========================================================================== */
/**
* Add the correct display in IE 10+.
*/
template {
display: none;
}
/**
* Add the correct display in IE 10.
*/
[hidden] {
display: none;
}

View File

@@ -0,0 +1,51 @@
.line (@position: "left") {
border-@{position}: 1px solid #f0f0f0;
}
.clearfix() {
&:after,
&:before {
content: " ";
display: table;
}
&:after {
clear: both;
}
}
// No Scrollbar
.no-scrollbar() {
&::-webkit-scrollbar {
display: none !important;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
opacity: 0 !important;
}
}
.ellipsis() {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
word-wrap: break-word;
}
.flex(@align: center, @justify: center) {
display: flex;
align-items: @align;
justify-content: @justify;
}
//超出显示省略号
.ellipsis() {
overflow: hidden; //超出的文本隐藏
text-overflow: ellipsis; //溢出用省略号显示
white-space: nowrap; //溢出不换行
}

View File

@@ -0,0 +1,37 @@
.app {
.ant-layout {
background: none;
.ant-layout-header {
background: #fff;
}
}
.comment {}
}
.admin {
.el-header {
width: 100%;
padding: 0;
position: fixed;
z-index: 999;
}
.el-aside {
height: calc(100vh - 60px);
position: fixed;
top: 60px;
left: 0;
z-index: 999;
.el-menu {
height: 100%;
}
}
.el-main {
margin-left: 200px;
margin-top: 60px;
}
}

View File

@@ -0,0 +1,304 @@
@red50: #ffebee;
@red100: #ffcdd2;
@red200: #ef9a9a;
@red300: #e57373;
@red400: #ef5350;
@red500: #f44336;
@red600: #e53935;
@red700: #d32f2f;
@red800: #c62828;
@red900: #b71c1c;
@redA100: #ff8a80;
@redA200: #ff5252;
@redA400: #ff1744;
@redA700: #d50000;
@red: @red500;
@pink50: #fce4ec;
@pink100: #f8bbd0;
@pink200: #f48fb1;
@pink300: #f06292;
@pink400: #ec407a;
@pink500: #e91e63;
@pink600: #d81b60;
@pink700: #c2185b;
@pink800: #ad1457;
@pink900: #880e4f;
@pinkA100: #ff80ab;
@pinkA200: #ff4081;
@pinkA400: #f50057;
@pinkA700: #c51162;
@pink: @pink500;
@purple50: #f3e5f5;
@purple100: #e1bee7;
@purple200: #ce93d8;
@purple300: #ba68c8;
@purple400: #ab47bc;
@purple500: #9c27b0;
@purple600: #8e24aa;
@purple700: #7b1fa2;
@purple800: #6a1b9a;
@purple900: #4a148c;
@purpleA100: #ea80fc;
@purpleA200: #e040fb;
@purpleA400: #d500f9;
@purpleA700: #aa00ff;
@purple: @purple500;
@deepPurple50: #ede7f6;
@deepPurple100: #d1c4e9;
@deepPurple200: #b39ddb;
@deepPurple300: #9575cd;
@deepPurple400: #7e57c2;
@deepPurple500: #673ab7;
@deepPurple600: #5e35b1;
@deepPurple700: #512da8;
@deepPurple800: #4527a0;
@deepPurple900: #311b92;
@deepPurpleA100: #b388ff;
@deepPurpleA200: #7c4dff;
@deepPurpleA400: #651fff;
@deepPurpleA700: #6200ea;
@deepPurple: @deepPurple500;
@indigo50: #e8eaf6;
@indigo100: #c5cae9;
@indigo200: #9fa8da;
@indigo300: #7986cb;
@indigo400: #5c6bc0;
@indigo500: #3f51b5;
@indigo600: #3949ab;
@indigo700: #303f9f;
@indigo800: #283593;
@indigo900: #1a237e;
@indigoA100: #8c9eff;
@indigoA200: #536dfe;
@indigoA400: #3d5afe;
@indigoA700: #304ffe;
@indigo: @indigo500;
@blue50: #e3f2fd;
@blue100: #bbdefb;
@blue200: #90caf9;
@blue300: #64b5f6;
@blue400: #42a5f5;
@blue500: #2196f3;
@blue600: #1e88e5;
@blue700: #1976d2;
@blue800: #1565c0;
@blue900: #0d47a1;
@blueA100: #82b1ff;
@blueA200: #448aff;
@blueA400: #2979ff;
@blueA700: #2962ff;
@blue: @blue500;
@lightBlue50: #e1f5fe;
@lightBlue100: #b3e5fc;
@lightBlue200: #81d4fa;
@lightBlue300: #4fc3f7;
@lightBlue400: #29b6f6;
@lightBlue500: #03a9f4;
@lightBlue600: #039be5;
@lightBlue700: #0288d1;
@lightBlue800: #0277bd;
@lightBlue900: #01579b;
@lightBlueA100: #80d8ff;
@lightBlueA200: #40c4ff;
@lightBlueA400: #00b0ff;
@lightBlueA700: #0091ea;
@lightBlue: @lightBlue500;
@cyan50: #e0f7fa;
@cyan100: #b2ebf2;
@cyan200: #80deea;
@cyan300: #4dd0e1;
@cyan400: #26c6da;
@cyan500: #00bcd4;
@cyan600: #00acc1;
@cyan700: #0097a7;
@cyan800: #00838f;
@cyan900: #006064;
@cyanA100: #84ffff;
@cyanA200: #18ffff;
@cyanA400: #00e5ff;
@cyanA700: #00b8d4;
@cyan: @cyan500;
@teal50: #e0f2f1;
@teal100: #b2dfdb;
@teal200: #80cbc4;
@teal300: #4db6ac;
@teal400: #26a69a;
@teal500: #009688;
@teal600: #00897b;
@teal700: #00796b;
@teal800: #00695c;
@teal900: #004d40;
@tealA100: #a7ffeb;
@tealA200: #64ffda;
@tealA400: #1de9b6;
@tealA700: #00bfa5;
@teal: @teal500;
@green50: #e8f5e9;
@green100: #c8e6c9;
@green200: #a5d6a7;
@green300: #81c784;
@green400: #66bb6a;
@green500: #4caf50;
@green600: #43a047;
@green700: #388e3c;
@green800: #2e7d32;
@green900: #1b5e20;
@greenA100: #b9f6ca;
@greenA200: #69f0ae;
@greenA400: #00e676;
@greenA700: #00c853;
@green: @green500;
@lightGreen50: #f1f8e9;
@lightGreen100: #dcedc8;
@lightGreen200: #c5e1a5;
@lightGreen300: #aed581;
@lightGreen400: #9ccc65;
@lightGreen500: #8bc34a;
@lightGreen600: #7cb342;
@lightGreen700: #689f38;
@lightGreen800: #558b2f;
@lightGreen900: #33691e;
@lightGreenA100: #ccff90;
@lightGreenA200: #b2ff59;
@lightGreenA400: #76ff03;
@lightGreenA700: #64dd17;
@lightGreen: @lightGreen500;
@lime50: #f9fbe7;
@lime100: #f0f4c3;
@lime200: #e6ee9c;
@lime300: #dce775;
@lime400: #d4e157;
@lime500: #cddc39;
@lime600: #c0ca33;
@lime700: #afb42b;
@lime800: #9e9d24;
@lime900: #827717;
@limeA100: #f4ff81;
@limeA200: #eeff41;
@limeA400: #c6ff00;
@limeA700: #aeea00;
@lime: @lime500;
@yellow50: #fffde7;
@yellow100: #fff9c4;
@yellow200: #fff59d;
@yellow300: #fff176;
@yellow400: #ffee58;
@yellow500: #ffeb3b;
@yellow600: #fdd835;
@yellow700: #fbc02d;
@yellow800: #f9a825;
@yellow900: #f57f17;
@yellowA100: #ffff8d;
@yellowA200: #ffff00;
@yellowA400: #ffea00;
@yellowA700: #ffd600;
@yellow: @yellow500;
@amber50: #fff8e1;
@amber100: #ffecb3;
@amber200: #ffe082;
@amber300: #ffd54f;
@amber400: #ffca28;
@amber500: #ffc107;
@amber600: #ffb300;
@amber700: #ffa000;
@amber800: #ff8f00;
@amber900: #ff6f00;
@amberA100: #ffe57f;
@amberA200: #ffd740;
@amberA400: #ffc400;
@amberA700: #ffab00;
@amber: @amber500;
@orange50: #fff3e0;
@orange100: #ffe0b2;
@orange200: #ffcc80;
@orange300: #ffb74d;
@orange400: #ffa726;
@orange500: #ff9800;
@orange600: #fb8c00;
@orange700: #f57c00;
@orange800: #ef6c00;
@orange900: #e65100;
@orangeA100: #ffd180;
@orangeA200: #ffab40;
@orangeA400: #ff9100;
@orangeA700: #ff6d00;
@orange: @orange500;
@deepOrange50: #fbe9e7;
@deepOrange100: #ffccbc;
@deepOrange200: #ffab91;
@deepOrange300: #ff8a65;
@deepOrange400: #ff7043;
@deepOrange500: #ff5722;
@deepOrange600: #f4511e;
@deepOrange700: #e64a19;
@deepOrange800: #d84315;
@deepOrange900: #bf360c;
@deepOrangeA100: #ff9e80;
@deepOrangeA200: #ff6e40;
@deepOrangeA400: #ff3d00;
@deepOrangeA700: #dd2c00;
@deepOrange: @deepOrange500;
@brown50: #efebe9;
@brown100: #d7ccc8;
@brown200: #bcaaa4;
@brown300: #a1887f;
@brown400: #8d6e63;
@brown500: #795548;
@brown600: #6d4c41;
@brown700: #5d4037;
@brown800: #4e342e;
@brown900: #3e2723;
@brown: @brown500;
@blueGrey50: #eceff1;
@blueGrey100: #cfd8dc;
@blueGrey200: #b0bec5;
@blueGrey300: #90a4ae;
@blueGrey400: #78909c;
@blueGrey500: #607d8b;
@blueGrey600: #546e7a;
@blueGrey700: #455a64;
@blueGrey800: #37474f;
@blueGrey900: #263238;
@blueGrey: @blueGrey500;
@grey50: #fafafa;
@grey100: #f5f5f5;
@grey200: #eeeeee;
@grey300: #e0e0e0;
@grey400: #bdbdbd;
@grey500: #9e9e9e;
@grey600: #757575;
@grey700: #616161;
@grey800: #424242;
@grey900: #212121;
@grey: @grey500;
@black: #000000;
@white: #ffffff;
@transparent: rgba(0, 0, 0, 0);
@fullBlack: rgba(0, 0, 0, 1);
@darkBlack: rgba(0, 0, 0, 0.87);
@lightBlack: rgba(0, 0, 0, 0.54);
@minBlack: rgba(0, 0, 0, 0.26);
@faintBlack: rgba(0, 0, 0, 0.12);
@fullWhite: rgba(255, 255, 255, 1);
@darkWhite: rgba(255, 255, 255, 0.87);
@lightWhite: rgba(255, 255, 255, 0.54);

View File

@@ -0,0 +1,80 @@
<template>
<el-menu router class="el-menu-vertical-demo" :default-active="route().fullPath" @open="handleOpen" @close="handleClose">
<template v-for="item, index in MenuStore.getMenuList" :key="index">
<el-menu-item v-if="item.menu_id == -1" :index="item.menu_href">
<template #title>
<el-icon>
<component :is="item.menu_icon"></component>
</el-icon>
<span>{{ item.menu_text }}</span>
</template>
</el-menu-item>
<el-sub-menu v-else :index="`${index}`">
<template #title>
<el-icon>
<component :is="item.menu_icon"></component>
</el-icon>
<span>{{ item.menu_text }}</span>
</template>
<el-menu-item v-for="v, i in item.children" :index="'/admin/' + item.menu_href + v.menu_href">
<template #title>
<el-icon>
<component :is="v.menu_icon"></component>
</el-icon>
<span>{{ v.menu_text }}</span>
</template>
</el-menu-item>
</el-sub-menu>
</template>
</el-menu>
</template>
<script lang="ts" setup>
import http, { setItem, getItem, firstToUpper, createdRouterConfig } from "@/utils";
import { Menu } from "@/types/response/Menu";
import { useRouterAction } from "@/utils/router";
import { defaultMenu } from "@/router";
import { onMounted, reactive } from "vue";
import { useMenuStore } from "@/state/menu";
const modules = import.meta.glob('@/views/admin/**/*.vue')
const { router, route } = useRouterAction()
const MenuStore = useMenuStore()
const state: {
} = reactive({
})
onMounted(() => {
getAllMenu();
})
const getAllMenu = async () => {
await MenuStore.httpMenuList();
if (router.getRoutes().length <= 12) {
createdRouterConfig(router)
}
}
const handleOpen = (key: string, keyPath: string[]) => {
console.log(key, keyPath)
}
const handleClose = (key: string, keyPath: string[]) => {
console.log(key, keyPath)
}
</script>

View File

@@ -0,0 +1,22 @@
<template>
<el-page-header class="page_header" @back="back()">
<template #content>
<span class="text-large font-600 mr-3"> {{title}} </span>
</template>
</el-page-header>
</template>
<script lang="ts" setup>
import { back } from "@/router";
withDefaults(defineProps<{
title: string
}>(), {
title: "默认标题"
})
</script>
<style lang="less" scoped>
.page_header {
margin-bottom: 30px;
}
</style>

View File

@@ -0,0 +1,10 @@
<template>
<el-row>
<el-col :span="21">
<slot name="input"></slot>
</el-col>
<el-col :span="2" :offset="1">
<slot name="button"></slot>
</el-col>
</el-row>
</template>

View File

@@ -0,0 +1,44 @@
<template>
<el-menu
:default-active="activeIndex"
class="el-menu-demo"
mode="horizontal"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b"
:ellipsis="false"
@select="handleSelect"
>
<el-menu-item index="0">LOGO</el-menu-item>
<div class="flex-grow" />
<el-menu-item index="1">Processing Center</el-menu-item>
<el-sub-menu index="2">
<template #title>Workspace</template>
<el-menu-item index="2-1">item one</el-menu-item>
<el-menu-item index="2-2">item two</el-menu-item>
<el-menu-item index="2-3">item three</el-menu-item>
<el-sub-menu index="2-4">
<template #title>item four</template>
<el-menu-item index="2-4-1">item one</el-menu-item>
<el-menu-item index="2-4-2">item two</el-menu-item>
<el-menu-item index="2-4-3">item three</el-menu-item>
</el-sub-menu>
</el-sub-menu>
</el-menu>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const activeIndex = ref('1')
const handleSelect = (key: string, keyPath: string[]) => {
console.log(key, keyPath)
}
</script>
<style lang="less" scoped>
.flex-grow {
flex-grow: 1;
}
</style>

View File

@@ -0,0 +1,44 @@
<template>
<div style="border: 1px solid #ccc">
<Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef" :defaultConfig="toolbarConfig" mode="default" />
<Editor style="height: 500px; overflow-y: hidden;" v-model="valueHtml" :defaultConfig="editorConfig" mode="default"
@onCreated="handleCreated"
@onChange="onBlur" />
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, onBeforeUnmount, shallowRef, onMounted } from 'vue'
import '@wangeditor/editor/dist/css/style.css';
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef()
const emits = defineEmits(["html"])
// 内容 HTML
const valueHtml = ref('')
const toolbarConfig = {}
const editorConfig = { placeholder: '请输入内容...' }
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
const editor = editorRef.value
if (editor == null) return
editor.destroy()
})
const onBlur = (editor: any) => emits("html", editor.getHtml())
const handleCreated = (editor: any) => {
editorRef.value = editor // 记录 editor 实例,重要!
}
defineExpose({
editorRef
})
</script>

View File

@@ -0,0 +1,140 @@
<template>
<div class="comment">
<div class="comment_fa">
<a-textarea @change="test" :allowClear="true" class="push_comment" v-model:value="state.params.comment_text"
:placeholder="state.placeholderText" :rows="4" @focus="isLoginAction"/>
<a-button class="button" @click="sendComment">发 送</a-button>
</div>
<a-comment v-for="item, index in data.article_comment" :key="index">
<template #actions>
<span key="comment-basic-reply-to" @click="replyTo(item, item)">回复1</span>
</template>
<template #author><a>{{ item.user_name }}</a></template>
<template #avatar>
<a-avatar :src="item.user_img" :alt="item.user_name" />
</template>
<template #content>
<p>
{{ item.comment_text }}
</p>
</template>
<a-comment v-for="v, i in item.comment_children
" :key="i">
<template #actions>
<span @click="replyTo(item, v)">回复2</span>
</template>
<template #author>
<a>{{ v.user_name }} 回复 {{ v.user_reply_name }}</a>
</template>
<template #avatar>
<a-avatar :src="v.user_img" :alt="v.user_name" />
</template>
<template #content>
<p>
{{ v.comment_text }}
</p>
</template>
</a-comment>
<a-skeleton v-if="item.loading" avatar :paragraph="{ rows: 2 }" />
<div class="more" @click="getMore(item)">展开更多</div>
</a-comment>
<a-spin v-if="data.loading" />
<div v-else class="test" @click="loadMore">加载更多..</div>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from "vue";
import { ArticleInfoType, comment } from "@/types/response/home";
import http,{ getItem, isLoginAction } from "@/utils";
import { onMounted } from "vue";
const props = defineProps<{
data: ArticleInfoType
}>()
const emits = defineEmits(["refresh"])
const state: any = reactive({
loading: false,
placeholderText: "善语结善缘,恶言伤人心",
params: {
comment_text: "",
comment_father_id: 0,
article_id: props.data.article_id,
user_reply_id: 0
}
})
onMounted(() => {
})
const test = () => {
if (state.params.comment_text.length == 0) {
state.params.comment_father_id = 0;
state.params.user_reply_id = 0;
state.placeholderText = "善语结善缘,恶言伤人心"
}
}
const replyTo = (item: comment, v: comment) => {
state.params.comment_father_id = item.comment_id
state.params.user_reply_id = v.user_id;
state.params.comment_text = "";
state.placeholderText = `${getItem("userInfo").user_name} 回复 ${v.user_name}`
}
const sendComment = async () => {
await http.pushComment(state.params)
state.params.comment_text = "";
test()
emits("refresh")
}
const loadMore = () => {
console.log("data: ", props.data);
props.data.loading = true;
setTimeout(() => {
props.data.loading = false;
}, 4000)
}
const getMore = (item: comment) => {
item.loading = true
setTimeout(() => {
item.loading = false;
}, 4000)
}
</script>
<style lang="less" scoped>
.comment {
/deep/ .anticon.ant-input-clear-icon-hidden {
visibility: inherit;
}
.comment_fa {
margin-bottom: 20px;
.button {
margin-top: 10px;
}
.push_comment {
resize: none;
}
}
}
</style>

View File

@@ -0,0 +1,61 @@
<template>
<a-menu theme="light" v-model:selectedKeys="current" mode="horizontal" @click="handelClick">
<a-menu-item :key="item.key" v-for="item, index in menu">
<template #icon>
<component :is="item.icon"></component>
</template>
{{ item.text }}
</a-menu-item>
<a-menu-item v-if="!isLoginStaus()" @click="push({ name: 'appuser' })">
登录 / 注册
</a-menu-item>
<a-sub-menu v-else>
<template #icon>
<a-avatar :src="user.getUserInfo.user_img" />
</template>
<template #title>{{ user.getUserInfo.user_name }}</template>
<a-menu-item-group>
<a-menu-item>自定义展示的下拉信息....</a-menu-item>
<a-menu-item key="/admin/home" v-if="user.getUserInfo.user_type == 1">进入管理后台</a-menu-item>
</a-menu-item-group>
</a-sub-menu>
</a-menu>
</template>
<script lang="ts" setup>
import { storeToRefs } from 'pinia'
import { ref, reactive, onMounted } from 'vue';
import { MailOutlined } from '@ant-design/icons-vue';
import { VagueOject } from '@/types/common';
import { push } from "@/router";
import { isLoginStaus, getItem } from "@/utils";
import { useUserStore } from "@/state/user";
// const { getUserInfo } = storeToRefs(useUserStore())
// const { setUserInfo } = useUserStore()
const user = useUserStore()
withDefaults(defineProps<{
menu: VagueOject[]
}>(), {
menu: () => []
})
onMounted(() => {
// TODO 这行在登录写完后需要删除
user.setUserInfo(getItem("userinfo"))
console.log("getUserInfo: ", user.getUserInfo);
})
const handelClick = (item: any) => {
push(item.key)
}
const current = ref<string[]>(['mail']);
</script>

View File

@@ -0,0 +1,25 @@
<template>
<div class="page" :style="{ marginTop: `${top}px`, marginBottom: `${bottom}px` }">
<slot></slot>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from "vue";
withDefaults(defineProps<{
top: number,
bottom: number
}>(), {
top: 45,
bottom: 50
})
</script>
<style lang="less" scoped>
.page {
width: 1200px;
margin: 0 auto;
}
</style>

View File

@@ -0,0 +1,37 @@
import { VagueOject, UrlModule, appUrl } from "@/types/common";
interface ConfigType {
baseUrl: UrlModule<appUrl>
apiList: UrlModule<VagueOject>,
aliyunOss: { bucketUrl: string }
}
export const config: ConfigType = {
aliyunOss: {
bucketUrl: 'https://testcodename.oss-cn-shanghai.aliyuncs.com'
},
baseUrl: {
app: {
devBaseUrl: "http://127.0.0.1:3000/app",
prodBaseUrl: "http://127.0.0.1:3000/api/json/prod",
},
admin: {
devBaseUrl: "http://127.0.0.1:3000/admin",
prodBaseUrl: "http://127.0.0.1:3000/api/json/prod",
}
},
apiList: {
app: {
articleList: "/article/articleList",
articleInfo: "/article/info",
pushComment: "/article/push_comment",
linkList: "/link/list",
categoriesList: "/article/categories_list",
},
admin: {
menuList: '/menu/list',
menuAdd: '/menu/add',
aliyunToken: '/lib/aliyunToken'
}
}
}

View File

@@ -0,0 +1,32 @@
import "lib-flexible"
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import { createPinia } from 'pinia'
import page from './components/app/page.vue'
import comment from './components/app/comment.vue'
import pageHeader from './components/admin/pageHeader.vue'
import pageSearch from './components/admin/pageSearch.vue'
const app = createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
const pinia = createPinia()
app.use(pinia)
app.component(page)
app.component(comment)
app.component(pageHeader)
app.component(pageSearch)
app.use(router)
app.mount('#app')

View File

@@ -0,0 +1,130 @@
import { createRouter, createWebHistory } from 'vue-router'
import { createdRouterConfig } from '@/utils/index'
// 默认显示在左侧的菜单
export const defaultMenu = [
{
menu_id: -1,
menu_text: "首页",
menu_icon: "House",
menu_href: "/admin/home",
menu_type: 0,
menu_father_id: 0,
menu_status: 0,
children: [],
}
]
export const config = [
{
path: '/admin',
name: 'admin',
component: () => import('../views/admin/_layout.vue'),
children: [
{
path: 'home',
name: 'admin_Home',
component: () => import('../views/admin/Home.vue'),
meta: {
title: '首页',
}
}
]
},
{
path: '/admin/account',
name: 'adminAccount',
component: () => import('../views/admin/User/Account.vue'),
meta: {
}
},
// ---------------------------------------
{
path: '/',
name: 'app',
component: () => import('../views/app/_layout.vue'),
children: [
{
path: 'home',
name: 'appHome',
component: () => import('../views/app/Home.vue'),
meta: {
isSider: true
}
},
{
path: 'article',
name: 'appArticle',
component: () => import('../views/app/Article.vue'),
meta: {
isSider: true
}
},
{
path: 'classification',
name: 'appClassification',
component: () => import('../views/app/Classification.vue'),
meta: {
isSider: true
}
},
{
path: 'tags',
name: 'appTags',
component: () => import('../views/app/Tags.vue'),
meta: {
isSider: true
}
},
{
path: 'search',
name: 'appSearch',
component: () => import('../views/app/Search.vue'),
meta: {
isSider: false
}
},
{
path: 'about',
name: 'app_About',
component: () => import('../views/app/About.vue'),
meta: {
isSider: false
}
},
{
path: 'info',
name: 'app_info',
component: () => import('../views/app/Info.vue'),
meta: {
isSider: false
}
}
]
},
{
path: '/user',
name: 'appuser',
component: () => import('../views/app/User.vue'),
meta: {
isSider: true
}
},
]
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: config
})
createdRouterConfig(router)
export const { push, replace, back, go } = router
export default router

View File

@@ -0,0 +1,12 @@
import home from "@/serve/home";
import menu from "@/serve/menu";
import categories from "@/serve/categories";
import { VagueOject } from "@/types/common";
const serve: VagueOject = {
...home,
...menu,
...categories
}
export default serve

View File

@@ -0,0 +1,7 @@
import { VagueOject } from "@/types/common";
let methods = async (data: VagueOject = {}, header: VagueOject = {}): Promise<any> => {}
export default {
get_app_categoriesList: methods
}

View File

@@ -0,0 +1,30 @@
import { VagueOject } from "@/types/common";
import { ArticleType, ArticleInfoType } from "@/types/response/home";
let methods = async (data: VagueOject = {}, header: VagueOject = {}): Promise<any> => {
}
export default {
get_app_articleList: methods,
post_app_pushComment: methods,
get_app_linkList: methods,
get_app_articleInfo_handle: async (data: VagueOject | ArticleInfoType = {}, header: VagueOject = {}): Promise<ArticleInfoType> => {
data.loading = false
data.page = 1
data.total_page = 2
data.article_comment.forEach((v: any) => {
v.loading = false
v.page = 1
v.total_page = 3
});
return <ArticleInfoType>data
}
}

View File

@@ -0,0 +1,9 @@
import { VagueOject } from "@/types/common";
let methods = async (data: VagueOject = {}, header: VagueOject = {}): Promise<any> => {}
export default {
get_admin_menuList: methods,
post_admin_menuAdd: methods,
get_admin_aliyunToken: methods
}

View File

@@ -0,0 +1,30 @@
import { defineStore } from "pinia"
import { reactive, computed } from "vue"
import { Menu } from "@/types/response/Menu"
import http, { setItem, getItem } from "@/utils";
import { defaultMenu } from "@/router";
export const useMenuStore = defineStore('menu', () => {
const state: {
menuList: Menu[]
} = reactive({
menuList: []
})
const getMenuList = computed(() => {
return state.menuList
})
const setMenuList = (info: Menu[]) => {
state.menuList = info
}
const httpMenuList = async () => {
let res = await http.menuList();
res.unshift(...defaultMenu)
setMenuList(res)
setItem("menu", getMenuList.value)
}
return { getMenuList, setMenuList, httpMenuList }
})

View File

@@ -0,0 +1,21 @@
import { defineStore } from "pinia"
import { reactive, computed } from "vue"
import { UserType } from "@/types/response/user"
export const useUserStore = defineStore('user', () => {
const state: {
UserInfo: UserType
} = reactive({
UserInfo: <UserType>{}
})
const getUserInfo = computed(() => {
return state.UserInfo
})
function setUserInfo(info: UserType) {
state.UserInfo = info
}
return { getUserInfo, setUserInfo }
})

View File

@@ -0,0 +1,553 @@
/**
* 覆盖axios get post 请求返回数据不合理的地方
*/
declare module "axios" {
// TypeScript Version: 4.7
export type AxiosHeaderValue = AxiosHeaders | string | string[] | number | boolean | null;
interface RawAxiosHeaders {
[key: string]: AxiosHeaderValue;
}
type MethodsHeaders = Partial<{
[Key in Method as Lowercase<Key>]: AxiosHeaders;
} & { common: AxiosHeaders }>;
type AxiosHeaderMatcher = (this: AxiosHeaders, value: string, name: string, headers: RawAxiosHeaders) => boolean;
export class AxiosHeaders {
constructor(
headers?: RawAxiosHeaders | AxiosHeaders
);
[key: string]: any;
set(headerName?: string, value?: AxiosHeaderValue, rewrite?: boolean | AxiosHeaderMatcher): AxiosHeaders;
set(headers?: RawAxiosHeaders | AxiosHeaders, rewrite?: boolean): AxiosHeaders;
get(headerName: string, parser: RegExp): RegExpExecArray | null;
get(headerName: string, matcher?: true | AxiosHeaderMatcher): AxiosHeaderValue;
has(header: string, matcher?: true | AxiosHeaderMatcher): boolean;
delete(header: string | string[], matcher?: AxiosHeaderMatcher): boolean;
clear(matcher?: AxiosHeaderMatcher): boolean;
normalize(format: boolean): AxiosHeaders;
concat(...targets: Array<AxiosHeaders | RawAxiosHeaders | string | undefined | null>): AxiosHeaders;
toJSON(asStrings?: boolean): RawAxiosHeaders;
static from(thing?: AxiosHeaders | RawAxiosHeaders | string): AxiosHeaders;
static accessor(header: string | string[]): AxiosHeaders;
static concat(...targets: Array<AxiosHeaders | RawAxiosHeaders | string | undefined | null>): AxiosHeaders;
setContentType(value: ContentType, rewrite?: boolean | AxiosHeaderMatcher): AxiosHeaders;
getContentType(parser?: RegExp): RegExpExecArray | null;
getContentType(matcher?: AxiosHeaderMatcher): AxiosHeaderValue;
hasContentType(matcher?: AxiosHeaderMatcher): boolean;
setContentLength(value: AxiosHeaderValue, rewrite?: boolean | AxiosHeaderMatcher): AxiosHeaders;
getContentLength(parser?: RegExp): RegExpExecArray | null;
getContentLength(matcher?: AxiosHeaderMatcher): AxiosHeaderValue;
hasContentLength(matcher?: AxiosHeaderMatcher): boolean;
setAccept(value: AxiosHeaderValue, rewrite?: boolean | AxiosHeaderMatcher): AxiosHeaders;
getAccept(parser?: RegExp): RegExpExecArray | null;
getAccept(matcher?: AxiosHeaderMatcher): AxiosHeaderValue;
hasAccept(matcher?: AxiosHeaderMatcher): boolean;
setUserAgent(value: AxiosHeaderValue, rewrite?: boolean | AxiosHeaderMatcher): AxiosHeaders;
getUserAgent(parser?: RegExp): RegExpExecArray | null;
getUserAgent(matcher?: AxiosHeaderMatcher): AxiosHeaderValue;
hasUserAgent(matcher?: AxiosHeaderMatcher): boolean;
setContentEncoding(value: AxiosHeaderValue, rewrite?: boolean | AxiosHeaderMatcher): AxiosHeaders;
getContentEncoding(parser?: RegExp): RegExpExecArray | null;
getContentEncoding(matcher?: AxiosHeaderMatcher): AxiosHeaderValue;
hasContentEncoding(matcher?: AxiosHeaderMatcher): boolean;
setAuthorization(value: AxiosHeaderValue, rewrite?: boolean | AxiosHeaderMatcher): AxiosHeaders;
getAuthorization(parser?: RegExp): RegExpExecArray | null;
getAuthorization(matcher?: AxiosHeaderMatcher): AxiosHeaderValue;
hasAuthorization(matcher?: AxiosHeaderMatcher): boolean;
[Symbol.iterator](): IterableIterator<[string, AxiosHeaderValue]>;
}
type CommonRequestHeadersList = 'Accept' | 'Content-Length' | 'User-Agent' | 'Content-Encoding' | 'Authorization';
type ContentType = AxiosHeaderValue | 'text/html' | 'text/plain' | 'multipart/form-data' | 'application/json' | 'application/x-www-form-urlencoded' | 'application/octet-stream';
export type RawAxiosRequestHeaders = Partial<RawAxiosHeaders & {
[Key in CommonRequestHeadersList]: AxiosHeaderValue;
} & {
'Content-Type': ContentType
}>;
export type AxiosRequestHeaders = RawAxiosRequestHeaders & AxiosHeaders;
type CommonResponseHeadersList = 'Server' | 'Content-Type' | 'Content-Length' | 'Cache-Control' | 'Content-Encoding';
type RawCommonResponseHeaders = {
[Key in CommonResponseHeadersList]: AxiosHeaderValue;
} & {
"set-cookie": string[];
};
export type RawAxiosResponseHeaders = Partial<RawAxiosHeaders & RawCommonResponseHeaders>;
export type AxiosResponseHeaders = RawAxiosResponseHeaders & AxiosHeaders;
export interface AxiosRequestTransformer {
(this: InternalAxiosRequestConfig, data: any, headers: AxiosRequestHeaders): any;
}
export interface AxiosResponseTransformer {
(this: InternalAxiosRequestConfig, data: any, headers: AxiosResponseHeaders, status?: number): any;
}
export interface AxiosAdapter {
(config: InternalAxiosRequestConfig): AxiosPromise;
}
export interface AxiosBasicCredentials {
username: string;
password: string;
}
export interface AxiosProxyConfig {
host: string;
port: number;
auth?: {
username: string;
password: string;
};
protocol?: string;
}
export enum HttpStatusCode {
Continue = 100,
SwitchingProtocols = 101,
Processing = 102,
EarlyHints = 103,
Ok = 200,
Created = 201,
Accepted = 202,
NonAuthoritativeInformation = 203,
NoContent = 204,
ResetContent = 205,
PartialContent = 206,
MultiStatus = 207,
AlreadyReported = 208,
ImUsed = 226,
MultipleChoices = 300,
MovedPermanently = 301,
Found = 302,
SeeOther = 303,
NotModified = 304,
UseProxy = 305,
Unused = 306,
TemporaryRedirect = 307,
PermanentRedirect = 308,
BadRequest = 400,
Unauthorized = 401,
PaymentRequired = 402,
Forbidden = 403,
NotFound = 404,
MethodNotAllowed = 405,
NotAcceptable = 406,
ProxyAuthenticationRequired = 407,
RequestTimeout = 408,
Conflict = 409,
Gone = 410,
LengthRequired = 411,
PreconditionFailed = 412,
PayloadTooLarge = 413,
UriTooLong = 414,
UnsupportedMediaType = 415,
RangeNotSatisfiable = 416,
ExpectationFailed = 417,
ImATeapot = 418,
MisdirectedRequest = 421,
UnprocessableEntity = 422,
Locked = 423,
FailedDependency = 424,
TooEarly = 425,
UpgradeRequired = 426,
PreconditionRequired = 428,
TooManyRequests = 429,
RequestHeaderFieldsTooLarge = 431,
UnavailableForLegalReasons = 451,
InternalServerError = 500,
NotImplemented = 501,
BadGateway = 502,
ServiceUnavailable = 503,
GatewayTimeout = 504,
HttpVersionNotSupported = 505,
VariantAlsoNegotiates = 506,
InsufficientStorage = 507,
LoopDetected = 508,
NotExtended = 510,
NetworkAuthenticationRequired = 511,
}
export type Method =
| 'get' | 'GET'
| 'delete' | 'DELETE'
| 'head' | 'HEAD'
| 'options' | 'OPTIONS'
| 'post' | 'POST'
| 'put' | 'PUT'
| 'patch' | 'PATCH'
| 'purge' | 'PURGE'
| 'link' | 'LINK'
| 'unlink' | 'UNLINK';
export type ResponseType =
| 'arraybuffer'
| 'blob'
| 'document'
| 'json'
| 'text'
| 'stream';
export type responseEncoding =
| 'ascii' | 'ASCII'
| 'ansi' | 'ANSI'
| 'binary' | 'BINARY'
| 'base64' | 'BASE64'
| 'base64url' | 'BASE64URL'
| 'hex' | 'HEX'
| 'latin1' | 'LATIN1'
| 'ucs-2' | 'UCS-2'
| 'ucs2' | 'UCS2'
| 'utf-8' | 'UTF-8'
| 'utf8' | 'UTF8'
| 'utf16le' | 'UTF16LE';
export interface TransitionalOptions {
silentJSONParsing?: boolean;
forcedJSONParsing?: boolean;
clarifyTimeoutError?: boolean;
}
export interface GenericAbortSignal {
readonly aborted: boolean;
onabort?: ((...args: any) => any) | null;
addEventListener?: (...args: any) => any;
removeEventListener?: (...args: any) => any;
}
export interface FormDataVisitorHelpers {
defaultVisitor: SerializerVisitor;
convertValue: (value: any) => any;
isVisitable: (value: any) => boolean;
}
export interface SerializerVisitor {
(
this: GenericFormData,
value: any,
key: string | number,
path: null | Array<string | number>,
helpers: FormDataVisitorHelpers
): boolean;
}
export interface SerializerOptions {
visitor?: SerializerVisitor;
dots?: boolean;
metaTokens?: boolean;
indexes?: boolean | null;
}
// tslint:disable-next-line
export interface FormSerializerOptions extends SerializerOptions {
}
export interface ParamEncoder {
(value: any, defaultEncoder: (value: any) => any): any;
}
export interface CustomParamsSerializer {
(params: Record<string, any>, options?: ParamsSerializerOptions): string;
}
export interface ParamsSerializerOptions extends SerializerOptions {
encode?: ParamEncoder;
serialize?: CustomParamsSerializer;
}
type MaxUploadRate = number;
type MaxDownloadRate = number;
type BrowserProgressEvent = any;
export interface AxiosProgressEvent {
loaded: number;
total?: number;
progress?: number;
bytes: number;
rate?: number;
estimated?: number;
upload?: boolean;
download?: boolean;
event?: BrowserProgressEvent;
}
type Milliseconds = number;
type AxiosAdapterName = 'xhr' | 'http' | string;
type AxiosAdapterConfig = AxiosAdapter | AxiosAdapterName;
export interface AxiosRequestConfig<D = any> {
url?: string;
method?: Method | string;
baseURL?: string;
transformRequest?: AxiosRequestTransformer | AxiosRequestTransformer[];
transformResponse?: AxiosResponseTransformer | AxiosResponseTransformer[];
headers?: (RawAxiosRequestHeaders & MethodsHeaders) | AxiosHeaders;
params?: any;
paramsSerializer?: ParamsSerializerOptions | CustomParamsSerializer;
data?: D;
timeout?: Milliseconds;
timeoutErrorMessage?: string;
withCredentials?: boolean;
adapter?: AxiosAdapterConfig | AxiosAdapterConfig[];
auth?: AxiosBasicCredentials;
responseType?: ResponseType;
responseEncoding?: responseEncoding | string;
xsrfCookieName?: string;
xsrfHeaderName?: string;
onUploadProgress?: (progressEvent: AxiosProgressEvent) => void;
onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void;
maxContentLength?: number;
validateStatus?: ((status: number) => boolean) | null;
maxBodyLength?: number;
maxRedirects?: number;
maxRate?: number | [MaxUploadRate, MaxDownloadRate];
beforeRedirect?: (options: Record<string, any>, responseDetails: { headers: Record<string, string> }) => void;
socketPath?: string | null;
transport?: any;
httpAgent?: any;
httpsAgent?: any;
proxy?: AxiosProxyConfig | false;
cancelToken?: CancelToken;
decompress?: boolean;
transitional?: TransitionalOptions;
signal?: GenericAbortSignal;
insecureHTTPParser?: boolean;
env?: {
FormData?: new (...args: any[]) => object;
};
formSerializer?: FormSerializerOptions;
family?: 4 | 6 | undefined;
lookup?: ((hostname: string, options: object, cb: (err: Error | null, address: string, family: number) => void) => void) |
((hostname: string, options: object) => Promise<[address: string, family: number] | string>);
}
// Alias
export type RawAxiosRequestConfig<D = any> = AxiosRequestConfig<D>;
export interface InternalAxiosRequestConfig<D = any> extends AxiosRequestConfig<D> {
headers: AxiosRequestHeaders;
}
export interface HeadersDefaults {
common: RawAxiosRequestHeaders;
delete: RawAxiosRequestHeaders;
get: RawAxiosRequestHeaders;
head: RawAxiosRequestHeaders;
post: RawAxiosRequestHeaders;
put: RawAxiosRequestHeaders;
patch: RawAxiosRequestHeaders;
options?: RawAxiosRequestHeaders;
purge?: RawAxiosRequestHeaders;
link?: RawAxiosRequestHeaders;
unlink?: RawAxiosRequestHeaders;
}
export interface AxiosDefaults<D = any> extends Omit<AxiosRequestConfig<D>, 'headers'> {
headers: HeadersDefaults;
}
export interface CreateAxiosDefaults<D = any> extends Omit<AxiosRequestConfig<D>, 'headers'> {
headers?: RawAxiosRequestHeaders | AxiosHeaders | Partial<HeadersDefaults>;
}
export interface AxiosResponse<T = any, D = any> {
data: T;
status: number;
statusText: string;
headers: RawAxiosResponseHeaders | AxiosResponseHeaders;
config: InternalAxiosRequestConfig<D>;
request?: any;
}
export class AxiosError<T = unknown, D = any> extends Error {
constructor(
message?: string,
code?: string,
config?: InternalAxiosRequestConfig<D>,
request?: any,
response?: AxiosResponse<T, D>
);
config?: InternalAxiosRequestConfig<D>;
code?: string;
request?: any;
response?: AxiosResponse<T, D>;
isAxiosError: boolean;
status?: number;
toJSON: () => object;
cause?: Error;
static from<T = unknown, D = any>(
error: Error | unknown,
code?: string,
config?: InternalAxiosRequestConfig<D>,
request?: any,
response?: AxiosResponse<T, D>,
customProps?: object,
): AxiosError<T, D>;
static readonly ERR_FR_TOO_MANY_REDIRECTS = "ERR_FR_TOO_MANY_REDIRECTS";
static readonly ERR_BAD_OPTION_VALUE = "ERR_BAD_OPTION_VALUE";
static readonly ERR_BAD_OPTION = "ERR_BAD_OPTION";
static readonly ERR_NETWORK = "ERR_NETWORK";
static readonly ERR_DEPRECATED = "ERR_DEPRECATED";
static readonly ERR_BAD_RESPONSE = "ERR_BAD_RESPONSE";
static readonly ERR_BAD_REQUEST = "ERR_BAD_REQUEST";
static readonly ERR_NOT_SUPPORT = "ERR_NOT_SUPPORT";
static readonly ERR_INVALID_URL = "ERR_INVALID_URL";
static readonly ERR_CANCELED = "ERR_CANCELED";
static readonly ECONNABORTED = "ECONNABORTED";
static readonly ETIMEDOUT = "ETIMEDOUT";
}
export class CanceledError<T> extends AxiosError<T> {
}
export type AxiosPromise<T = any> = Promise<AxiosResponse<T>>;
export interface CancelStatic {
new(message?: string): Cancel;
}
export interface Cancel {
message: string | undefined;
}
export interface Canceler {
(message?: string, config?: AxiosRequestConfig, request?: any): void;
}
export interface CancelTokenStatic {
new(executor: (cancel: Canceler) => void): CancelToken;
source(): CancelTokenSource;
}
export interface CancelToken {
promise: Promise<Cancel>;
reason?: Cancel;
throwIfRequested(): void;
}
export interface CancelTokenSource {
token: CancelToken;
cancel: Canceler;
}
export interface AxiosInterceptorOptions {
synchronous?: boolean;
runWhen?: (config: InternalAxiosRequestConfig) => boolean;
}
export interface AxiosInterceptorManager<V> {
use(onFulfilled?: ((value: V) => V | Promise<V>) | null, onRejected?: ((error: any) => any) | null, options?: AxiosInterceptorOptions): number;
eject(id: number): void;
clear(): void;
}
export class Axios {
constructor(config?: AxiosRequestConfig);
defaults: AxiosDefaults;
interceptors: {
request: AxiosInterceptorManager<InternalAxiosRequestConfig>;
response: AxiosInterceptorManager<AxiosResponse>;
};
getUri(config?: AxiosRequestConfig): string;
request<T = any, R = AxiosResponse<T>, D = any>(config: AxiosRequestConfig<D>): Promise<R>;
get<T = any, R = AxiosResponse<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<T>;
delete<T = any, R = AxiosResponse<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
head<T = any, R = AxiosResponse<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
options<T = any, R = AxiosResponse<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
post<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<T>;
put<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
patch<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
postForm<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
putForm<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
patchForm<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
}
export interface AxiosInstance extends Axios {
<T = any, R = AxiosResponse<T>, D = any>(config: AxiosRequestConfig<D>): Promise<R>;
<T = any, R = AxiosResponse<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
defaults: Omit<AxiosDefaults, 'headers'> & {
headers: HeadersDefaults & {
[key: string]: AxiosHeaderValue
}
};
}
export interface GenericFormData {
append(name: string, value: any, options?: any): any;
}
export interface GenericHTMLFormElement {
name: string;
method: string;
submit(): void;
}
export function toFormData(sourceObj: object, targetFormData?: GenericFormData, options?: FormSerializerOptions): GenericFormData;
export function formToJSON(form: GenericFormData | GenericHTMLFormElement): object;
export function isAxiosError<T = any, D = any>(payload: any): payload is AxiosError<T, D>;
export function spread<T, R>(callback: (...args: T[]) => R): (array: T[]) => R;
export function isCancel(value: any): value is Cancel;
export function all<T>(values: Array<T | Promise<T>>): Promise<T[]>;
export interface AxiosStatic extends AxiosInstance {
create(config?: CreateAxiosDefaults): AxiosInstance;
Cancel: CancelStatic;
CancelToken: CancelTokenStatic;
Axios: typeof Axios;
AxiosError: typeof AxiosError;
HttpStatusCode: typeof HttpStatusCode;
readonly VERSION: string;
isCancel: typeof isCancel;
all: typeof all;
spread: typeof spread;
isAxiosError: typeof isAxiosError;
toFormData: typeof toFormData;
formToJSON: typeof formToJSON;
CanceledError: typeof CanceledError;
AxiosHeaders: typeof AxiosHeaders;
}
declare const axios: AxiosStatic;
export default axios;
}

View File

@@ -0,0 +1,12 @@
export type VagueOject = { [key: string]: string | any };
export type PrefixType = 'app' | 'admin';
export type UrlModule<T> = {
app: T;
admin: T
}
export type appUrl = {
devBaseUrl: string;
prodBaseUrl: string;
}

View File

@@ -0,0 +1,10 @@
export interface Menu {
menu_id: number;
menu_text: string;
menu_icon: string;
menu_href: string;
menu_type: number;
menu_father_id: number;
menu_status: number;
children: Menu[];
}

View File

@@ -0,0 +1,48 @@
export interface ArticleType<T,D> {
article_backimg: string;
article_id: number;
article_text: string;
article_time: number;
article_title: string;
article_user: number;
article_view: number;
categories_id: T;
tag_id: D;
}
export interface ArticleInfoType extends ArticleType<categories[],tag[]>{
article_comment: comment[]
loading: boolean
page: number
total_page: number
}
export interface comment {
article_id: number
comment_children: comment[]
comment_father_id: number
comment_id: number
comment_text: string
comment_time: number | null
loading: boolean
page: number
total_page: number
user_id: number
user_img: string
user_name: string
user_reply_id: number
user_reply_name: string
}
export interface categories {
categories_id: number;
categories_title: string;
}
export interface tag {
tag_id: number;
tag_title: string;
}

View File

@@ -0,0 +1,5 @@
export interface resultType<T extends any> {
code: number,
msg: string,
data: T
}

View File

@@ -0,0 +1,8 @@
export interface UserType {
user_id: number;
user_name: string;
user_img: string;
user_type: number;
user_addtime: number;
}

View File

@@ -0,0 +1,6 @@
declare module '*.vue' {
// import Vue from 'vue'
export default ComputedOptions
// export default Vue
}

View File

@@ -0,0 +1,123 @@
import { config } from "@/config";
import api from "@/api";
import { PrefixType, VagueOject } from "@/types/common";
import serve from "@/serve/_";
import { push } from "@/router";
import moment from 'moment'
import { Menu } from "@/types/response/Menu";
const modules = import.meta.glob('@/views/admin/**/*.vue')
export const setItem = (key: string, value: any): void => {
localStorage.setItem(
key,
value.constructor == Object || value.constructor == Array
? JSON.stringify(value)
: value
)
}
export const getItem = (key: string): [] | {} | any => {
if (localStorage.getItem(key) == null) {
return false
} else {
let Storage = <string>localStorage.getItem(key)
try {
return JSON.parse(Storage)
} catch {
return Storage
}
}
}
export const isPhone = (phone: string): boolean => {
let myreg = /^1[3-9]\d{9}$/;
return myreg.test(phone)
}
export const format = (time: Date | number | string, fmt: string): string => {
return moment(time).format(fmt);
}
export const isLoginAction = (): void => {
if (!isLoginStaus()) {
push({
path: "/user"
})
}
}
export const isLoginStaus = (): boolean => {
return getItem("token") ? true : false
}
export const firstToUpper = (str: string) => {
return str.trim().toLowerCase().replace(str[0], str[0].toUpperCase());
}
export const createdRouterConfig = (router: any) => {
if (getItem("menu")) {
getItem("menu").forEach((v: Menu) => {
v.children.forEach(j => {
let path = firstToUpper(v.menu_href)
let c = {
path: v.menu_href + j.menu_href,
name: `admin_${v.menu_href}_${j.menu_href.split("/")[1]}`,
component: modules[`/src/views/admin/${path}${j.menu_href}.vue`],
meta: {
isLogin: true
}
}
router.addRoute("admin", c)
})
});
}
}
export const inject = () => {
let ProxyServe: VagueOject = {}
for (const key in serve) {
// get_app_articleList
// ['get','app','articleList']
let splitArr: string[] = key.split("_");
// 请求方法 GET POST
let methodsType: 'GET' | 'POST' = <'GET' | 'POST'>splitArr[0].toLocaleUpperCase()
// 请求基地址的key
let BaseUrlPrefix: PrefixType = <PrefixType>splitArr[1]
let baseUrl: string = import.meta.env.MODE == "development"
? config.baseUrl[BaseUrlPrefix].devBaseUrl
: config.baseUrl[BaseUrlPrefix].prodBaseUrl
// 请求短地址的key
let UrlShort: string = splitArr[2]
// 是否需要处理响应结果
let ishandle: string = splitArr[3]
if (ishandle != undefined && ishandle != "handle") {
throw new Error(`方法名${key}中的参数${ishandle}需要为 handle`)
}
ProxyServe[UrlShort] = new Proxy(serve[key], {
apply: async (target, ctx, args) => {
let res = await api[methodsType]<any>({
baseUrl,
url: config.apiList[BaseUrlPrefix][UrlShort],
data: args[0] == undefined ? {} : args[0],
header: args[1] == undefined ? {} : args[1]
})
return ishandle == undefined ? res : target(res)
}
})
}
return ProxyServe
}
export default inject()
// function isValidKey(key: string | number | symbol, object: object): key is keyof typeof object {
// return key in object;
// }

View File

@@ -0,0 +1,3 @@
export const md5 = () => {
console.log("md5");
}

View File

@@ -0,0 +1,25 @@
import { useRouter, RouteLocationRaw, useRoute } from 'vue-router'
export function useRouterAction() {
const router = useRouter()
function push(to: RouteLocationRaw){
router.push(to)
}
function replace(to: RouteLocationRaw){
router.replace(to)
}
function back(){
router.back()
}
function route() {
return useRoute()
}
return {
push, replace, route, router, back
}
}

View File

@@ -0,0 +1,70 @@
<template>
<div class="div">
<pageHeader title="文章列表"></pageHeader>
<pageSearch>
<template #input>
<el-input v-model="state.input" placeholder="输入查询条件" clearable />
</template>
<template #button>
<el-button type="primary" @click="SearchArticle">查 询</el-button>
</template>
</pageSearch>
<el-table :data="state.ArticleList" stripe style="width: 100%">
<el-table-column prop="article_id" label="ID" width="80" />
<el-table-column prop="article_title" label="标题" width="180" />
<el-table-column prop="article_time" label="时间" />
<el-table-column prop="article_view" label="浏览数量" />
<el-table-column label="操作">
<template #default="{ $index, row }">
<el-button size="small" @click="updateArticle(row)">修 改</el-button>
<el-button size="small" type="danger" @click="deleteArticle(row)">删 除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination :page-size="3" :pager-count="2" layout="prev, pager, next" :total="state.ArticleList.length" />
</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, toRefs, ref } from "vue";
import http, { format, setItem, getItem } from "@/utils";
import { useRouterAction } from "@/utils/router";
import { useUserStore } from "@/state/user";
import { showSuccessToast, showFailToast } from 'vant';
const { back, push } = useRouterAction()
import { ArticleType } from "@/types/response/home";
const state: {
input: string,
ArticleList: ArticleType<string, string>[]
} = reactive({
input: "",
ArticleList: []
})
onMounted(() => {
SearchArticle()
})
const SearchArticle = async () => {
state.ArticleList = await http.articleList({
text: state.input
})
console.log("ArticleList: ", state.ArticleList);
}
const updateArticle = (row: ArticleType<string, string>) => {
console.log(row);
}
const deleteArticle = (row: ArticleType<string, string>) => {
console.log(row);
}
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,184 @@
<template>
<pageHeader title="文章列表"></pageHeader>
<el-form ref="ruleFormRef" :model="Article" :rules="rules" label-width="120px" class="demo-ruleForm" :size="formSize"
status-icon>
<el-form-item label="文章标题" prop="article_title">
<el-input v-model="Article.article_title" />
</el-form-item>
<el-form-item label="背景图" prop="article_backimg">
<el-upload class="upload-demo" :class="state.hidden ? 'none' : ''" list-type="picture-card"
:data="state.aliyunData" :before-upload="beforeUpload" :on-remove="() => state.hidden = false" :limit="1"
:on-preview="() => state.showPreview = true" :action="config.aliyunOss.bucketUrl">
<el-icon>
<Plus />
</el-icon>
</el-upload>
<el-dialog v-model="state.showPreview">
<img w-full :src="Article.article_backimg" alt="Preview Image" />
</el-dialog>
</el-form-item>
<el-form-item label="分类" prop="categories_id">
<el-select multiple v-model="Article.categories_id" placeholder="请选择文章分类">
<el-option v-for="item, index in state.categoriesList" :key="index" :label="item.categories_title"
:value="item.categories_id" />
</el-select>
</el-form-item>
<el-form-item label="标签" prop="tag_id">
<!-- <el-select-v2 v-model="Article.tag_id" placeholder="Activity count" /> -->
</el-form-item>
<el-form-item label="文章内容" prop="article_text">
<wangeditor ref="wangEditor" @html="setHtml"></wangeditor>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm(ruleFormRef)">
发 布
</el-button>
<el-button @click="resetForm(ruleFormRef)">重 置</el-button>
</el-form-item>
</el-form>
</template>
<script lang="ts" setup>
import { reactive, ref, onBeforeUnmount, shallowRef, onMounted } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
import wangeditor from '@/components/admin/wangeditor.vue'
import type { UploadProps, UploadUserFile } from 'element-plus'
import { ElMessage } from 'element-plus'
import { VagueOject } from '@/types/common'
import http, { } from "@/utils";
import { config } from "@/config";
import 'element-plus/dist/index.css'
interface ArticleType {
article_backimg: string;
article_text: string;
article_title: string;
categories_id: string[];
tag_id: string[];
}
const formSize = ref('default')
const ruleFormRef = ref<FormInstance>()
const wangEditor = ref();
const Article = reactive<ArticleType>({
article_backimg: "",
article_text: "",
article_title: "",
categories_id: [],
tag_id: [],
})
const state: {
aliyunData: VagueOject,
hidden: boolean,
showPreview: boolean,
categoriesList: VagueOject[]
// fileList: UploadUserFile[]
} = reactive({
aliyunData: {},
hidden: false,
showPreview: false,
categoriesList: []
// fileList: []
})
onMounted(() => {
ElMessage({
message: 'this is a message.'
})
getCategoriesList()
})
const getCategoriesList = async () => {
state.categoriesList = await http.categoriesList();
}
const rules = reactive<FormRules<ArticleType>>({
article_title: [
{ required: true, message: '请输入标题', trigger: 'blur' },
{ min: 1, max: 100, message: '标题1~100间', trigger: 'blur' },
],
article_text: [
{
required: true,
message: '请输入内容',
trigger: 'change',
},
],
article_backimg: [
{
required: true,
message: '请选择图片',
trigger: 'change',
},
],
categories_id: [
{
required: true,
message: '请选择分类',
trigger: 'change',
},
],
tag_id: [
{
required: true,
message: '请选择标签',
trigger: 'change',
},
],
})
const beforeUpload: UploadProps['beforeUpload'] = async (rawFile) => {
state.hidden = true;
let res = await http.aliyunToken()
res.key = rawFile.name
state.aliyunData = res;
Article.article_backimg = `${config.aliyunOss.bucketUrl}/${rawFile.name}`
// state.fileList.push({
// name: rawFile.name,
// url: Article.article_backimg
// })
}
const setHtml = (content: string) => {
Article.article_text = content
}
const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid, fields) => {
if (valid && Article.article_text != "<p><br></p>") {
console.log('submit!')
} else {
console.log('error submit!', fields)
}
})
}
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
wangEditor.value.editorRef.setHtml("<p><br></p>")
}
</script>
<style lang="less" scoped>
.upload-demo {
width: 50%;
}
/deep/ .none {
.el-upload--picture-card {
display: none !important;
}
}
</style>

View File

@@ -0,0 +1,13 @@
<template>
<div class="div">评论列表</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, toRefs } from "vue";
</script>
<style lang="less" scoped>
</style>

View File

@@ -0,0 +1,17 @@
<template>
<div class="admin_home">
<el-button type="primary">Primary</el-button>
<ul>
<li v-for="i in 10">测试</li>
</ul>
</div>
</template>
<script setup lang="ts">
</script>
<style lang="less" scoped>
</style>

View File

@@ -0,0 +1,163 @@
<template>
<pageHeader title="菜单设置"></pageHeader>
<el-link @click="addOneLevelMenu" :underline="false">
<el-icon>
<Plus />
</el-icon>
</el-link>
<div class="div">
<a-tree v-if="MenuStore.getMenuList.length != 0" :default-expand-all="true" :tree-data="MenuStore.getMenuList">
<template #title="{ menu_id, menu_text, menu_icon, menu_type, menu_href }">
<a-dropdown :trigger="['contextmenu']">
<span>
<el-icon>
<component :is="menu_icon"></component>
</el-icon>
{{ menu_text }}
</span>
<template #overlay>
<a-menu @click="({ key }) => onContextMenuClick(menu_text, menu_id, key, menu_href)">
<a-menu-item key="update">修改</a-menu-item>
<a-menu-item key="delete">删除</a-menu-item>
<a-menu-item v-if="menu_type == 1" key="add">增加</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</template>
</a-tree>
<el-dialog destroy-on-close :show-close="false" v-model="state.isShowAdd" title="增加菜单" width="30%"
:before-close="handleClose">
<div class="content">
<el-input v-model="state.addMenuParams.text" placeholder="输入菜单名称">
<template #prepend>
<el-link @click="state.isShowMenuSelect = true" :underline="false">
<el-icon>
<component :is="state.addMenuParams.icon"></component>
</el-icon>
</el-link>
</template>
</el-input>
<el-input v-model="state.addMenuParams.path" placeholder="输入菜单地址">
<template #prepend>{{ state.defaultPath }}</template>
</el-input>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="state.isShowAdd = false">取消</el-button>
<el-button type="primary" @click="addMenuAction">确定</el-button>
</span>
</template>
</el-dialog>
<el-dialog v-model="state.isShowMenuSelect" title="选择菜单图标" width="60%">
<div class="content">
<el-link @click="selectClick(key)" :underline="false" v-for="key in ElementPlusIconsVue" :key="key">
<el-icon :size="30">
<component :is="key"></component>
</el-icon>
</el-link>
</div>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, toRefs, watch, ref } from "vue";
import http, { setItem, getItem, firstToUpper, createdRouterConfig } from "@/utils";
import { Menu } from "@/types/response/Menu";
import 'ant-design-vue/dist/reset.css';
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import { useMenuStore } from "@/state/menu";
const MenuStore = useMenuStore()
const state: {
menu: Menu[],
isShowAdd: boolean,
isShowMenuSelect: boolean,
toastOkStatus: string,
defaultPath: string,
addMenuParams: {
icon: string,
text: string,
path: string,
type: number,
father_id: number
}
} = reactive({
menu: [],
isShowAdd: false,
isShowMenuSelect: false,
toastOkStatus: '',
defaultPath: '',
addMenuParams: {
icon: "QuestionFilled",
text: "",
path: "",
type: 0,
father_id: 0
}
})
onMounted(() => {
})
const addOneLevelMenu = async () => {
state.addMenuParams.type = 1
state.addMenuParams.father_id = 0
state.isShowAdd = true
}
const addMenuAction = async () => {
if (state.toastOkStatus == "update") {
} else {
state.addMenuParams.path = state.addMenuParams.type == 1 ? state.addMenuParams.path : `/${state.addMenuParams.path}`
await http.menuAdd(state.addMenuParams)
await MenuStore.httpMenuList();
}
state.isShowAdd = false
}
const selectClick = (icon: any) => {
state.addMenuParams.icon = icon.name
state.isShowMenuSelect = false
}
const onContextMenuClick = (name: string | number, id: number, action: string, path: string) => {
console.log(`name: ${name}, id : ${id}, action: ${JSON.stringify(action)}`);
switch (action) {
case "update":
state.toastOkStatus = "update"
break;
case "delete":
break;
case "add":
state.toastOkStatus = "add"
state.addMenuParams.type = 0
state.addMenuParams.father_id = id
state.defaultPath = `/${path}/`
state.isShowAdd = true
console.log("addMenuParams: ", state.addMenuParams);
break;
default:
break;
}
};
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,12 @@
<template>
<div class="div">标签列表</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, toRefs } from "vue";
</script>
<style lang="less" scoped>
</style>

View File

@@ -0,0 +1,13 @@
<template>
<div class="div">发布标签</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, toRefs } from "vue";
</script>
<style lang="less" scoped>
</style>

View File

@@ -0,0 +1,10 @@
<template>
<div class="account">
<h1>管理后台account</h1>
</div>
</template>
<style>
</style>

View File

@@ -0,0 +1,24 @@
<template>
<div class="admin">
<el-container>
<el-header>
<topMenu></topMenu>
</el-header>
<el-container>
<el-aside width="200px">
<leftMenu></leftMenu>
</el-aside>
<el-main>
<RouterView></RouterView>
</el-main>
</el-container>
</el-container>
</div>
</template>
<script lang="ts" setup>
import { RouterLink, RouterView } from 'vue-router'
import topMenu from '@/components/admin/topMenu.vue'
import leftMenu from '@/components/admin/leftMenu.vue'
</script>
<style></style>

View File

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

View File

@@ -0,0 +1,10 @@
<template>
<div class="article">
文章
</div>
</template>
<style>
</style>

View File

@@ -0,0 +1,10 @@
<template>
<div class="classification">
分类
</div>
</template>
<style>
</style>

View File

@@ -0,0 +1,60 @@
<template>
<div class="home">
<ul class="list">
<li v-for="item, index in state.ArticleList" :key="index">
<a-typography-paragraph>
<a-image width="100%" :src="item.article_backimg" />
</a-typography-paragraph>
<router-link :to="{ name: 'app_info', query: { id: item.article_id } }">
<a-typography-title :level="3">{{ item.article_title }}</a-typography-title>
<a-typography-paragraph type="secondary">{{ item.article_text }}</a-typography-paragraph>
<a-typography-paragraph>
<a-typography-text strong>
发布时间: {{ format(item.article_time, 'YYYY-MM-DD HH:mm:ss') }}
浏览数: {{ item.article_view }}
</a-typography-text>
</a-typography-paragraph>
</router-link>
</li>
</ul>
</div>
</template>
<script setup lang="ts">
import http, { format } from "@/utils";
import { RouterLink } from 'vue-router';
import { ArticleType } from "@/types/response/home";
import { onMounted, reactive, toRefs } from "vue";
const state: {
ArticleList: ArticleType<string, string>[]
} = reactive({
ArticleList: []
})
onMounted(() => {
getArticleList()
// getLinkList()
})
let getArticleList = async () => {
state.ArticleList = await http.articleList()
console.log("ArticleList: ",state.ArticleList);
}
let getLinkList = async () => {
let res = await http.linkList();
console.log("res: ", res);
}
</script>
<style lang="less" scoped>
@import "@page/Home.less";
</style>

View File

@@ -0,0 +1,76 @@
<template>
<div class="info">
<a-image width="100%" :src="state.info.article_backimg" />
<a-typography-title :level="3">{{ state.info.article_title }}</a-typography-title>
<a-typography-paragraph>
<a-typography-text strong>
发布时间: {{ format(state.info.article_time, 'YYYY-MM-DD HH:mm:ss') }}
浏览数: {{ state.info.article_view }}
</a-typography-text>
</a-typography-paragraph>
<div class="categories">
<span :style="{ marginRight: '8px' }">分类:</span>
<template v-for="item, index in state.info.categories_id" :key="index">
<a-checkable-tag>
{{ item.categories_title }}
</a-checkable-tag>
</template>
</div>
<div class="tag">
<span :style="{ marginRight: '8px' }">标签:</span>
<template v-for="item, index in state.info.tag_id" :key="index">
<a-checkable-tag>
{{ item.tag_title }}
</a-checkable-tag>
</template>
</div>
<a-typography-paragraph class="text" v-html="state.info.article_text"></a-typography-paragraph>
<a-typography-title :level="4">全部评论</a-typography-title>
<comment v-if="Object.keys(state.info).length != 0" @refresh="getInfo" :data="state.info"></comment>
</div>
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router';
import { onMounted, reactive, toRefs } from "vue";
import http, { format } from "@/utils";
import { ArticleInfoType } from "@/types/response/home";
const route = useRoute()
const state: {
info: ArticleInfoType
} = reactive({
info: <ArticleInfoType>{},
})
onMounted(() => {
getInfo()
})
const getInfo = async () => {
state.info = await http.articleInfo({
id: route.query.id
});
console.log("res: ", state.info);
}
</script>
<style lang="less" scoped>
.info {
.text {
font-size: 18px;
}
}
</style>

View File

@@ -0,0 +1,10 @@
<template>
<div class="search">
搜索
</div>
</template>
<style>
</style>

View File

@@ -0,0 +1,10 @@
<template>
<div class="tags">
标签
</div>
</template>
<style>
</style>

View File

@@ -0,0 +1,185 @@
<template>
<div class="user">
<div class="pane">
<div class="pane_div">
<div class="pane_div_zi" @click="userdata.userbool = true"
:class="userdata.userbool == true ? 'actcolor' : ''">登录
</div>
<div class="pane_div_zi" @click="userdata.userbool = false"
:class="userdata.userbool == true ? '' : 'actcolor'">注册
</div>
</div>
<div class="login" v-if="userdata.userbool">
<el-input class="user_input" v-model="userdata.login.name" :clearable="true" placeholder="输入账号"
:minlength="3" :maxlength="10">
<template #prefix>
<el-icon>
<UserFilled />
</el-icon>
</template>
</el-input>
<!-- <div v-if="userdata.boolsd">账号长度小于3</div> -->
<el-input class="user_input" v-model="userdata.login.pwd" :clearable="true" type="password" :minlength="6"
:maxlength="20" placeholder="输入密码" show-password>
<template #prefix>
<el-icon>
<Lock />
</el-icon>
</template>
</el-input>
<el-button style="width: 100%; height:40px;" @click="loginmethod()" type="primary" plain>登录</el-button>
</div>
<div class="enroll" v-else>
<el-input class="user_input" v-model="userdata.enroll.name" :clearable="true" :minlength="3" :maxlength="10"
placeholder="输入账号">
<template #prefix>
<el-icon>
<UserFilled />
</el-icon>
</template>
</el-input>
<el-input class="user_input" v-model="userdata.enroll.pwd" :clearable="true" :minlength="6" :maxlength="20"
type="password" placeholder="输入密码" show-password>
<template #prefix>
<el-icon>
<Lock />
</el-icon>
</template>
</el-input>
<el-button style="width: 100%; height:40px;" @click="enrollmethod()" type="primary" plain>注册</el-button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, toRefs } from "vue";
import http, { format, setItem ,getItem} from "@/utils";
import { useRouterAction } from "@/utils/router";
import { useUserStore } from "@/state/user";
import { showSuccessToast, showFailToast } from 'vant';
const { back, push } = useRouterAction()
const { setUserInfo } = reactive(useUserStore());
const userdata: {
login: { name: string, pwd: string }, enroll: { name: string, pwd: string }, userbool: boolean, boolsd: boolean,
under: boolean, above: boolean
} = reactive({
login: {
name: "",
pwd: ""
},
enroll: {
name: "",
pwd: ""
},
userbool: true,
boolsd: false,
})
//登录
const loginmethod = async () => {
if (judgment(userdata.login)) {
let res = await http.user({ name: userdata.login.name, pwd: userdata.login.pwd })
setItem("token", res)
userdv("登录成功")
}
}
//注册
const enrollmethod = async () => {
if (judgment(userdata.enroll)) {
let ret = await http.add({
name: userdata.enroll.name,
pwd: userdata.enroll.pwd
})
let res = await http.user({name: userdata.enroll.name, pwd: userdata.enroll.pwd})
setItem("token", res)
await userdv("注册成功")
console.log("res",localStorage.getItem("token"))
showSuccessToast("showSuccessToast")
}
}
//存储用户信息加跳转
const userdv = async (text: string) => {
let ret = await http.info()
setUserInfo(ret)
setItem("userinfo", ret)
showSuccessToast(text);
push({ name: "appHome" })
}
//判断是否为空
const judgment = (user: { name: string, pwd: string }) => {
if (user.name.length >= 3) {
if (user.pwd.length >= 6) {
return true
} else {
showFailToast("密码长度小于6")
return false
}
return true
} else {
showFailToast("账号长度小于3")
return false
}
}
</script>
<style lang="less" scoped>
.user {
width: 100%;
height: 100vh;
background-image: url(../../../public/bj.jpg);
background-size: cover;
display: flex;
align-items: center;
.pane {
width: 400px;
//height: 400px;
//border: 1px solid #000;
margin-left: 200px;
background-color: #cac5b91a;
box-shadow: 10px 15px 10px #000;
padding: 35px;
border-radius: 15px;
/deep/.el-input__wrapper {
background: none;
}
.user_input {
height: 45px;
margin-bottom: 15px;
}
.pane_div {
display: flex;
font-size: 18px;
font-weight: 900;
color: aliceblue;
.pane_div_zi {
margin-left: 12px;
margin-bottom: 6px;
}
}
}
.actcolor {
color: blanchedalmond;
}
}
</style>

View File

@@ -0,0 +1,71 @@
<template>
<div class="app">
<a-layout>
<a-layout-header theme="light">
<div class="logo_text">BmyBlog</div>
<headerComponents :menu="state.menu"></headerComponents>
</a-layout-header>
<page :top="84">
<a-layout>
<RouterView v-slot="{ Component, route }">
<a-layout-content>
<component :is="Component" :key="route.name"/>
<!-- <RouterView></RouterView> -->
</a-layout-content>
<a-layout-sider theme="light" v-if="route.meta.isSider">
右侧菜单
</a-layout-sider>
</RouterView>
</a-layout>
</page>
<a-layout-footer>Footer</a-layout-footer>
</a-layout>
</div>
</template>
<script lang="ts" setup>
import { reactive } from 'vue';
import { VagueOject } from '@/types/common';
import { RouterLink, RouterView } from 'vue-router';
import headerComponents from "@/components/app/header.vue";
import { HomeOutlined, BarsOutlined, ApartmentOutlined, TagsFilled, SearchOutlined, TeamOutlined } from '@ant-design/icons-vue';
const state: {
menu: VagueOject[]
} = reactive({
menu: [
{ text: '首页', icon: HomeOutlined, key: '/home' },
{ text: '文章', icon: BarsOutlined, key: '/article' },
{ text: '分类', icon: ApartmentOutlined, key: '/classification' },
{ text: '标签', icon: TagsFilled, key: '/tags' },
{ text: '搜索', icon: SearchOutlined, key: '/search' },
{ text: '关于', icon: TeamOutlined, key: '/about' },
]
})
</script>
<style lang="less">
.ant-layout-header {
display: flex;
justify-content: space-between;
padding: 0 30px;
position: fixed;
width: 100%;
top: 0;
left: 0;
z-index: 999;
.logo_text {
color: #000;
font-size: 28px;
font-weight: 900;
}
.ant-menu {
flex: 1;
display: flex;
justify-content: flex-end;
}
}
</style>