后台前端
This commit is contained in:
parent
aec7b928f5
commit
5a4d9181cb
2
.env
2
.env
@ -8,4 +8,4 @@ VITE_OPEN = false
|
||||
VITE_OPEN_CDN = false
|
||||
|
||||
# public path 配置线上环境路径(打包)、本地通过 http-server 访问时,请置空即可
|
||||
VITE_PUBLIC_PATH = /vue-next-admin-preview/
|
||||
VITE_PUBLIC_PATH = ./
|
@ -2,4 +2,5 @@
|
||||
ENV = production
|
||||
|
||||
# 线上环境接口地址
|
||||
VITE_API_URL = https://lyt-top.gitee.io/vue-next-admin-preview/
|
||||
# VITE_API_URL = https://lyt-top.gitee.io/vue-next-admin-preview/
|
||||
VITE_API_URL = http://8.138.171.103/
|
4694
package-lock.json
generated
4694
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -47,6 +47,7 @@
|
||||
"@typescript-eslint/parser": "^7.2.0",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"@vue/compiler-sfc": "^3.4.21",
|
||||
"code-inspector-plugin": "^0.17.6",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-vue": "^9.23.0",
|
||||
"prettier": "^3.2.5",
|
||||
|
50
src/api/article/index.ts
Normal file
50
src/api/article/index.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import request from '/@/utils/request';
|
||||
import { baseUrlHost } from '../baseUrlHost';
|
||||
|
||||
/**
|
||||
* (不建议写成 request.post(xxx),因为这样 post 时,无法 params 与 data 同时传参)
|
||||
* 注意在写get请求时,参数是params,而不是data,要标注好
|
||||
*
|
||||
* 登录api接口集合
|
||||
* @method getArticleList 获取文章列表
|
||||
* @method getModuleList 获取模块列表
|
||||
* @method getLabelList 获取标签列表
|
||||
* @method deleteArticle 删除文章
|
||||
* @method getArticleDetail 获取文章详情
|
||||
*/
|
||||
|
||||
export function articleApi() {
|
||||
return {
|
||||
getArticleList: (params: object) => {
|
||||
return request({
|
||||
url: baseUrlHost + '/cpArticle',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
},
|
||||
getModuleList: () => {
|
||||
return request({
|
||||
url: baseUrlHost + '/cpModule/all',
|
||||
method: 'get',
|
||||
});
|
||||
},
|
||||
getLabelList: (moduleid: Number) => {
|
||||
return request({
|
||||
url: baseUrlHost + `/cpLabel/moduleid/${moduleid}`,
|
||||
method: 'get',
|
||||
});
|
||||
},
|
||||
deleteArticle: (id: Number) => {
|
||||
return request({
|
||||
url: baseUrlHost + `/cpArticle/${id}`,
|
||||
method: 'delete',
|
||||
});
|
||||
},
|
||||
getArticleDetail: (id: Number) => {
|
||||
return request({
|
||||
url: baseUrlHost + `/cpArticle/${id}`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
1
src/api/baseUrlHost.ts
Normal file
1
src/api/baseUrlHost.ts
Normal file
@ -0,0 +1 @@
|
||||
export const baseUrlHost = "/vueAdminApi";
|
@ -1,12 +1,16 @@
|
||||
import request from '/@/utils/request';
|
||||
import { baseUrlHost } from '../baseUrlHost';
|
||||
|
||||
/**
|
||||
* (不建议写成 request.post(xxx),因为这样 post 时,无法 params 与 data 同时传参)
|
||||
*
|
||||
* 注意在写get请求时,参数是params,而不是data,要标注好
|
||||
* 登录api接口集合
|
||||
* @method signIn 用户登录
|
||||
* @method signOut 用户退出登录
|
||||
* @method login 登录
|
||||
* @method loginOut 登出
|
||||
*/
|
||||
|
||||
export function useLoginApi() {
|
||||
return {
|
||||
signIn: (data: object) => {
|
||||
@ -23,5 +27,18 @@ export function useLoginApi() {
|
||||
data,
|
||||
});
|
||||
},
|
||||
login: (data: object) => {
|
||||
return request({
|
||||
url: baseUrlHost+'/acUser/login',
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
},
|
||||
loginOut: () => {
|
||||
return request({
|
||||
url: baseUrlHost+'/acUser/logout',
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -19,6 +19,7 @@
|
||||
:prop="item.key"
|
||||
:width="item.colWidth"
|
||||
:label="item.title"
|
||||
:formatter="item.formatter"
|
||||
>
|
||||
<template v-slot="scope">
|
||||
<template v-if="item.type === 'image'">
|
||||
|
@ -8,6 +8,10 @@ export default {
|
||||
systemUser: 'systemUser',
|
||||
systemDept: 'systemDept',
|
||||
systemDic: 'systemDic',
|
||||
article: 'article',
|
||||
articleDetail: 'articleDetail',
|
||||
addArticle: 'addArticle',
|
||||
editArticle: 'editArticle',
|
||||
limits: 'limits',
|
||||
limitsFrontEnd: 'FrontEnd',
|
||||
limitsFrontEndPage: 'FrontEndPage',
|
||||
|
@ -8,6 +8,10 @@ export default {
|
||||
systemUser: '用户管理',
|
||||
systemDept: '部门管理',
|
||||
systemDic: '字典管理',
|
||||
article: '文章管理',
|
||||
articleDetail: '文章详情',
|
||||
addArticle: '新增文章',
|
||||
editArticle: '编辑文章',
|
||||
limits: '权限管理',
|
||||
limitsFrontEnd: '前端控制',
|
||||
limitsFrontEndPage: '页面权限',
|
||||
|
@ -8,6 +8,10 @@ export default {
|
||||
systemUser: '用戶管理',
|
||||
systemDept: '部門管理',
|
||||
systemDic: '字典管理',
|
||||
article: '文章管理',
|
||||
articleDetail: '文章詳情',
|
||||
addArticle: '文章新增',
|
||||
editArticle: '文章編輯',
|
||||
limits: '許可權管理',
|
||||
limitsFrontEnd: '前端控制',
|
||||
limitsFrontEndPage: '頁面許可權',
|
||||
|
@ -97,11 +97,15 @@ import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
import other from '/@/utils/other';
|
||||
import mittBus from '/@/utils/mitt';
|
||||
import { Session, Local } from '/@/utils/storage';
|
||||
import { useLoginApi } from '/@/api/login';
|
||||
|
||||
// 引入组件
|
||||
const UserNews = defineAsyncComponent(() => import('/@/layout/navBars/topBar/userNews.vue'));
|
||||
const Search = defineAsyncComponent(() => import('/@/layout/navBars/topBar/search.vue'));
|
||||
|
||||
// 引入 api 请求接口
|
||||
const loginApi = useLoginApi();
|
||||
|
||||
// 定义变量内容
|
||||
const userNewsRef = ref();
|
||||
const userNewsBadgeRef = ref();
|
||||
@ -159,16 +163,17 @@ const onHandleCommandClick = (path: string) => {
|
||||
confirmButtonText: t('message.user.logOutConfirm'),
|
||||
cancelButtonText: t('message.user.logOutCancel'),
|
||||
buttonSize: 'default',
|
||||
beforeClose: (action, instance, done) => {
|
||||
beforeClose: async (action, instance, done) => {
|
||||
if (action === 'confirm') {
|
||||
instance.confirmButtonLoading = true;
|
||||
instance.confirmButtonText = t('message.user.logOutExit');
|
||||
setTimeout(() => {
|
||||
try {
|
||||
instance.confirmButtonLoading = true;
|
||||
instance.confirmButtonText = t('message.user.logOutExit');
|
||||
await loginApi.loginOut();
|
||||
} catch (error) {
|
||||
} finally {
|
||||
done();
|
||||
setTimeout(() => {
|
||||
instance.confirmButtonLoading = false;
|
||||
}, 300);
|
||||
}, 700);
|
||||
instance.confirmButtonLoading = false;
|
||||
}
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
@ -176,6 +181,7 @@ const onHandleCommandClick = (path: string) => {
|
||||
})
|
||||
.then(async () => {
|
||||
// 清除缓存/token等
|
||||
console.log('清除缓存/token等');
|
||||
Session.clear();
|
||||
// 使用 reload 时,不需要调用 resetRoute() 重置路由
|
||||
window.location.reload();
|
||||
|
@ -155,6 +155,68 @@ export const dynamicRoutes: Array<RouteRecordRaw> = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/article',
|
||||
name: 'article',
|
||||
component: () => import('/@/views/article/index.vue'),
|
||||
meta: {
|
||||
title: 'message.router.article',
|
||||
isLink: '',
|
||||
isHide: false,
|
||||
isKeepAlive: true,
|
||||
isAffix: false,
|
||||
isIframe: false,
|
||||
roles: ['admin', 'common'],
|
||||
icon: 'iconfont icon-jiliandongxuanzeqi',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/article/detail',
|
||||
name: 'articleDetail',
|
||||
component: () => import('/@/views/article/component/detail.vue'),
|
||||
meta: {
|
||||
title: 'message.router.articleDetail',
|
||||
isLink: '',
|
||||
isHide: true,
|
||||
isKeepAlive: false,
|
||||
isAffix: false,
|
||||
isIframe: false,
|
||||
roles: ['admin','common'],
|
||||
icon: 'ele-Document',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/article/add',
|
||||
name: 'addArticle',
|
||||
component: () => import('/@/views/article/component/upload.vue'),
|
||||
meta: {
|
||||
title: 'message.router.addArticle',
|
||||
isLink: '',
|
||||
isHide: true,
|
||||
isKeepAlive: false,
|
||||
isAffix: false,
|
||||
isIframe: false,
|
||||
roles: ['admin','common'],
|
||||
icon: 'ele-Document',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/article/edit',
|
||||
name: 'editArticle',
|
||||
component: () => import('/@/views/article/component/upload.vue'),
|
||||
meta: {
|
||||
title: 'message.router.editArticle',
|
||||
isLink: '',
|
||||
isHide: true,
|
||||
isKeepAlive: false,
|
||||
isAffix: false,
|
||||
isIframe: false,
|
||||
roles: ['admin','common'],
|
||||
icon: 'ele-Document',
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/limits',
|
||||
name: 'limits',
|
||||
|
@ -21,6 +21,7 @@ service.interceptors.request.use(
|
||||
// 在发送请求之前做些什么 token
|
||||
if (Session.get('token')) {
|
||||
config.headers!['Authorization'] = `${Session.get('token')}`;
|
||||
config.headers!['token'] = `${Session.get('token')}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
|
143
src/views/article/component/detail.vue
Normal file
143
src/views/article/component/detail.vue
Normal file
@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<div class="layout-pd">
|
||||
<el-card v-loading="state.loading" shadow="hover" header="文章详情">
|
||||
<el-form :model="state.data" size="large" label-width="100px" class="mt20 mb20">
|
||||
<el-row :gutter="35">
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb20">
|
||||
<el-form-item label="封面:">
|
||||
<el-image
|
||||
style="width: 100px;height: 100px;border-radius: 4px;"
|
||||
:src="state.data.photo"
|
||||
:zoom-rate="1.2"
|
||||
:preview-src-list="[state.data.photo]"
|
||||
preview-teleported
|
||||
fit="cover"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb20">
|
||||
<el-form-item label="模块名称:">
|
||||
{{ state.data.moduleName }}
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb20">
|
||||
<el-form-item label="标签名称:">
|
||||
{{ state.data.labelName }}
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb20">
|
||||
<el-form-item label="标题:">
|
||||
{{ state.data.title }}
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb20">
|
||||
<el-form-item label="文章类型:">
|
||||
图文
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb20">
|
||||
<el-form-item label="是否置顶:">
|
||||
<el-tag type="success" v-if="state.data.top===1">是</el-tag>
|
||||
<el-tag type="error" v-else>否</el-tag>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb20">
|
||||
<el-form-item label="浏览数:">
|
||||
{{ state.data.view }}
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb20">
|
||||
<el-form-item label="创建日期:">
|
||||
{{ dateFormatter() }}
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- <div class="mt5 mb20 ml10">文章内容:</div> -->
|
||||
<Editor v-model:get-html="state.data.text" v-model:get-text="state.data.text" :disable="true" />
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="articleDetail">
|
||||
import { defineAsyncComponent, onMounted, reactive, ref } from 'vue';
|
||||
import { articleApi } from '/@/api/article';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
// 引入组件
|
||||
const Editor = defineAsyncComponent(() => import('/@/components/editor/index.vue'));
|
||||
|
||||
// 引入 api 请求接口
|
||||
const artApi = articleApi();
|
||||
|
||||
// 模块列表
|
||||
const moduleList = ref([]);
|
||||
|
||||
// 获取模块名称和标签名称
|
||||
const route = useRoute();
|
||||
const moduleName:any = route.query.moduleName;
|
||||
const labelName:any = route.query.labelName;
|
||||
|
||||
const state = reactive({
|
||||
data: {
|
||||
labelName: '',
|
||||
moduleName: '',
|
||||
title: '',
|
||||
articletype: 0,
|
||||
createtime: '',
|
||||
photo: '',
|
||||
text: '',
|
||||
top: 0,
|
||||
view: 0,
|
||||
},
|
||||
loading: false,
|
||||
})
|
||||
|
||||
// 获取文章详情
|
||||
const getArticleDetail = async() => {
|
||||
try {
|
||||
state.loading = true;
|
||||
let res = await artApi.getArticleList({
|
||||
moduleName: moduleName,
|
||||
labelName: labelName,
|
||||
size: 1,
|
||||
current: 1
|
||||
});
|
||||
if(res?.success) {
|
||||
state.data = res.data.records[0];
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error('获取文章详情失败');
|
||||
} finally {
|
||||
state.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取模块列表
|
||||
const getModuleList = async() => {
|
||||
try {
|
||||
let res = await artApi.getModuleList();
|
||||
if(res?.success){
|
||||
moduleList.value = res.data;
|
||||
}else{
|
||||
ElMessage.error('模块列表获取失败!');
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error('模块列表获取失败!');
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
// 日期格式化
|
||||
function dateFormatter(){
|
||||
if(!state.data.createtime) return '';
|
||||
let date = new Date(state.data.createtime);
|
||||
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getArticleDetail();
|
||||
getModuleList();
|
||||
})
|
||||
|
||||
</script>
|
234
src/views/article/component/upload.vue
Normal file
234
src/views/article/component/upload.vue
Normal file
@ -0,0 +1,234 @@
|
||||
<template>
|
||||
<div class="layout-pd">
|
||||
<el-card v-loading="state.loading" shadow="hover" header="文章详情">
|
||||
<el-form ref="articleFormRef" :model="state.data" :rules="state.rules" size="large" label-width="100px" class="mt20 mb20">
|
||||
<el-row :gutter="35">
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb20">
|
||||
<el-form-item label="封面:" class="coverImg">
|
||||
<!-- 上传文件 -->
|
||||
<el-upload ref="uploadRef" class="h100 personal-user-left-upload" action="#" list-type="picture-card"
|
||||
:auto-upload="false" :limit="1" :class="{ hide: state.coverHide }" @change="handleUploadChange">
|
||||
<template #default>
|
||||
<el-icon><Plus /></el-icon>
|
||||
</template>
|
||||
<template #file="{ file }">
|
||||
<div>
|
||||
<img class="el-upload-list__item-thumbnail" :src="file.url" alt="" />
|
||||
<span class="el-upload-list__item-actions">
|
||||
<span
|
||||
class="el-upload-list__item-preview"
|
||||
@click="handlePictureCardPreview(file)"
|
||||
>
|
||||
<el-icon><zoom-in /></el-icon>
|
||||
</span>
|
||||
<span
|
||||
v-if="!disabled"
|
||||
class="el-upload-list__item-delete"
|
||||
@click="handleDownload(file)"
|
||||
>
|
||||
<el-icon><Download /></el-icon>
|
||||
</span>
|
||||
<span
|
||||
v-if="!disabled"
|
||||
class="el-upload-list__item-delete"
|
||||
@click="handleRemove(file)"
|
||||
>
|
||||
<el-icon><Delete /></el-icon>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb20">
|
||||
<el-form-item label="模块名称:" prop="moduleName">
|
||||
<el-select size="default" v-model="state.data.moduleName" placeholder="请选择模块" clearable>
|
||||
<el-option :data-op="item.id" v-for="(item,index) in moduleList" :key="index" :label="item.moduleName" :value="item.moduleName"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb20">
|
||||
<el-form-item label="标签名称:" prop="labelName">
|
||||
<el-input v-model="state.data.labelName" placeholder="请输入标签名称" clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb20">
|
||||
<el-form-item label="标题:" prop="title">
|
||||
<el-input v-model="state.data.title" placeholder="请输入标题" clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb20">
|
||||
<el-form-item label="文章类型:" prop="articletype">
|
||||
<el-select size="default" v-model="state.data.articletype" placeholder="请选择文章类型" clearable>
|
||||
<el-option label="图文" :value="0"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb20">
|
||||
<el-form-item label="是否置顶:" prop="top">
|
||||
<el-switch v-model="state.data.top" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item prop="text" label-width="0px">
|
||||
<Editor v-model:get-html="state.data.text" v-model:get-text="state.data.text" :disable="true" />
|
||||
</el-form-item>
|
||||
<el-row class="flex mt15">
|
||||
<div class="flex-margin" style="width: 100%;display: flex;justify-content: flex-end;">
|
||||
<el-button size="larger" type="primary" @click="onSubmitForm">
|
||||
<SvgIcon name="iconfont icon-shuxing" />
|
||||
保存
|
||||
</el-button>
|
||||
</div>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<!-- 封面预览弹窗 -->
|
||||
<el-dialog v-model="dialogVisible">
|
||||
<img w-full :src="dialogImageUrl" alt="预览" />
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="articleDetail">
|
||||
import { defineAsyncComponent, onMounted, reactive, ref } from 'vue';
|
||||
import { articleApi } from '/@/api/article';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { ElMessage, UploadFile, UploadFiles } from 'element-plus';
|
||||
import { Delete, Download, Plus, ZoomIn } from '@element-plus/icons-vue'
|
||||
|
||||
// 引入组件
|
||||
const Editor = defineAsyncComponent(() => import('/@/components/editor/index.vue'));
|
||||
|
||||
// 引入 api 请求接口
|
||||
const artApi = articleApi();
|
||||
|
||||
// 模块列表
|
||||
const moduleList = ref([]);
|
||||
|
||||
// 获取文章id
|
||||
const route = useRoute();
|
||||
const moduleName:any = route.query.moduleName;
|
||||
const labelName:any = route.query.labelName;
|
||||
|
||||
|
||||
const state = reactive({
|
||||
data: {
|
||||
labelName: '',
|
||||
moduleName: '',
|
||||
title: '',
|
||||
articletype: '',
|
||||
photo: '',
|
||||
text: '',
|
||||
top: false,// 是否置顶,这里提交的时候要换成0或1
|
||||
},
|
||||
loading: false,
|
||||
rules: {
|
||||
moduleName: { required: true, message: '请选择模块', trigger: 'blur' },
|
||||
labelName: { required: true, message: '请输入标签名称', trigger: 'blur' },
|
||||
title: { required: true, message: '请输入标题', trigger: 'blur' },
|
||||
articletype: { required: true, message: '请选择文章类型', trigger: 'blur' },
|
||||
photo: { required: true, message: '请上传图片', trigger: 'blur' },
|
||||
top: { required: true, message: '请选择是否置顶', trigger: 'blur' },
|
||||
text: { required: true, message: '请输入文章内容', trigger: 'blur' },
|
||||
},
|
||||
coverFile: {},
|
||||
coverHide: false
|
||||
})
|
||||
|
||||
// 获取文章详情
|
||||
const getArticleDetail = async() => {
|
||||
try {
|
||||
state.loading = true;
|
||||
let res = await artApi.getArticleList({
|
||||
moduleName: moduleName,
|
||||
labelName: labelName,
|
||||
size: 1,
|
||||
current: 1
|
||||
});
|
||||
if(res?.success) {
|
||||
state.data = res.data.records[0];
|
||||
// 下面这里处理一下,因为后端返回的是0和1,而前端需要的是true和false,到时候提交的时候再转换回来
|
||||
// 这个报错没关系的,只是ts的语法检查,不影响运行
|
||||
state.data.top = state.data.top === 1;
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error('获取文章详情失败');
|
||||
} finally {
|
||||
state.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取模块列表
|
||||
const getModuleList = async() => {
|
||||
try {
|
||||
let res = await artApi.getModuleList();
|
||||
if(res?.success){
|
||||
moduleList.value = res.data;
|
||||
}else{
|
||||
ElMessage.error('模块列表获取失败!');
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error('模块列表获取失败!');
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
const articleFormRef = ref();
|
||||
|
||||
// 保存文章
|
||||
const onSubmitForm = () => {
|
||||
articleFormRef.value.validate((valid: boolean) => {
|
||||
if (valid){
|
||||
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 封面上传禁用
|
||||
const disabled = ref(false);
|
||||
// 封面弹窗
|
||||
const dialogImageUrl = ref('');
|
||||
const dialogVisible = ref(false);
|
||||
|
||||
const uploadRef = ref();
|
||||
|
||||
// 封面删除
|
||||
const handleRemove = (file: UploadFile) => {
|
||||
state.coverFile = {};
|
||||
state.coverHide = false;
|
||||
uploadRef.value.clearFiles();
|
||||
}
|
||||
|
||||
// 封面预览
|
||||
const handlePictureCardPreview = (file: UploadFile) => {
|
||||
dialogImageUrl.value = file.url!
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 封面下载
|
||||
const handleDownload = (file: UploadFile) => {
|
||||
console.log(file)
|
||||
}
|
||||
|
||||
// 封面图片更改
|
||||
const handleUploadChange = (uploadFile: UploadFile, uploadFiles: UploadFiles) => {
|
||||
const file:any = uploadFile.raw;
|
||||
state.coverFile = file;
|
||||
state.coverHide = true;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if(moduleName && labelName && route.path == '/article/edit')
|
||||
getArticleDetail();
|
||||
getModuleList();
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.coverImg :deep(.hide .el-upload--picture-card) {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
244
src/views/article/index.vue
Normal file
244
src/views/article/index.vue
Normal file
@ -0,0 +1,244 @@
|
||||
<template>
|
||||
<div class="system-dept-container layout-padding">
|
||||
<el-card shadow="hover" class="layout-padding-auto">
|
||||
<div class="system-dept-search mb15">
|
||||
<el-select @change="handleChangeModule" size="default" v-model="state.tableData.param.moduleName" placeholder="请选择模块" clearable style="max-width: 180px">
|
||||
<el-option :data-op="item.id" v-for="(item,index) in moduleList" :key="index" :label="item.moduleName" :value="item.moduleName"></el-option>
|
||||
</el-select>
|
||||
<el-select size="default" v-model="state.tableData.param.labelName" placeholder="请选择标签" clearable class="ml10" style="max-width: 180px">
|
||||
<el-option v-for="(item,index) in labelList" :key="index" :label="item.name" :value="item.name"></el-option>
|
||||
</el-select>
|
||||
<el-input size="default" placeholder="请输入标题" v-model="state.tableData.param.title" class="ml10" style="max-width: 180px"> </el-input>
|
||||
<el-button @click="getTableData" size="default" type="primary" class="ml10">
|
||||
<el-icon>
|
||||
<ele-Search />
|
||||
</el-icon>
|
||||
查询
|
||||
</el-button>
|
||||
<el-button @click="reset" size="default" type="primary" class="ml10">
|
||||
<el-icon>
|
||||
<ele-RefreshRight />
|
||||
</el-icon>
|
||||
重置
|
||||
</el-button>
|
||||
<el-button @click="toAddArticle" size="default" type="success" class="ml10">
|
||||
<el-icon>
|
||||
<ele-FolderAdd />
|
||||
</el-icon>
|
||||
新增文章
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table :data="state.tableData.data" v-loading="state.tableData.loading" style="width: 100%">
|
||||
<el-table-column type="index" label="序号" width="100"/>
|
||||
<el-table-column prop="moduleName" label="模块名称" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="labelName" label="标签名称" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="title" label="标题" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="articletype" label="文章类型" :formatter="typeFormatter" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="置顶" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<el-tag type="success" v-if="scope.row.top===1">是</el-tag>
|
||||
<el-tag type="error" v-else>否</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createtime" label="创建时间" :formatter="dateFormatter" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="操作" width="200">
|
||||
<template #default="scope">
|
||||
<el-button size="small" text type="primary" @click="toShowDetail(scope.row)">查看详情</el-button>
|
||||
<el-button size="small" text type="primary" @click="toEditArticle(scope.row)">修改</el-button>
|
||||
<el-button size="small" text type="primary" @click="deleteArticle(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
@size-change="onHandleSizeChange"
|
||||
@current-change="onHandleCurrentChange"
|
||||
class="mt15"
|
||||
:pager-count="5"
|
||||
:page-sizes="[10, 20, 30]"
|
||||
v-model:current-page="state.tableData.param.current"
|
||||
background
|
||||
v-model:page-size="state.tableData.param.size"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="state.tableData.total"
|
||||
>
|
||||
</el-pagination>
|
||||
</el-card>
|
||||
<!-- <DeptDialog ref="deptDialogRef" @refresh="getTableData()" /> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="articleIndex">
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { articleApi } from '/@/api/article';
|
||||
import { ElMessage, ElMessageBox, TableColumnCtx } from 'element-plus';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
// 表格数据
|
||||
const state = reactive({
|
||||
tableData: {
|
||||
data: [],
|
||||
total: 0,
|
||||
loading: false,
|
||||
param: {
|
||||
labelName: '',
|
||||
moduleName: '',
|
||||
title: '',
|
||||
current: 1,
|
||||
size: 10,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 模块列表
|
||||
const moduleList = ref([]);
|
||||
|
||||
// 标签列表
|
||||
const labelList = ref([]);
|
||||
|
||||
// 引入 api 请求接口
|
||||
const artApi = articleApi();
|
||||
|
||||
// 获取表格数据
|
||||
const getTableData = async() => {
|
||||
try {
|
||||
state.tableData.loading = true;
|
||||
let res = await artApi.getArticleList(state.tableData.param);
|
||||
if(res?.success){
|
||||
state.tableData.data = res.data.records;
|
||||
state.tableData.total = res.data.total;
|
||||
}else{
|
||||
ElMessage.error('文章列表获取失败!');
|
||||
}
|
||||
} catch (error) {
|
||||
} finally {
|
||||
state.tableData.loading = false;
|
||||
}
|
||||
};
|
||||
|
||||
const getModuleList = async() => {
|
||||
try {
|
||||
let res = await artApi.getModuleList();
|
||||
if(res?.success){
|
||||
moduleList.value = res.data;
|
||||
}else{
|
||||
ElMessage.error('模块列表获取失败!');
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error('模块列表获取失败!');
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
// 模块选择
|
||||
const handleChangeModule = async(val: any) => {
|
||||
try {
|
||||
state.tableData.param.labelName = '';
|
||||
if(!val){labelList.value=[]; return;}
|
||||
const op:any = event?.currentTarget;
|
||||
let res = await artApi.getLabelList(op.dataset.op);
|
||||
if(res?.success){
|
||||
labelList.value = res.data;
|
||||
}else{
|
||||
ElMessage.error('标签列表获取失败!');
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error('标签列表获取失败!');
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
// 重置
|
||||
const reset = () =>{
|
||||
state.tableData.param = {
|
||||
labelName: '',
|
||||
moduleName: '',
|
||||
title: '',
|
||||
current: 1,
|
||||
size: 10,
|
||||
}
|
||||
getTableData();
|
||||
}
|
||||
|
||||
// 添加文章
|
||||
const toAddArticle = () => {
|
||||
router.push({ path: '/article/add' });
|
||||
}
|
||||
|
||||
// 添加文章
|
||||
const toEditArticle = (row: any) => {
|
||||
router.push({ path: '/article/edit', query: { moduleName: row.moduleName, labelName: row.labelName } });
|
||||
}
|
||||
|
||||
// 文章详情
|
||||
const toShowDetail = (row: any) => {
|
||||
router.push({ path: '/article/detail' , query: { moduleName: row.moduleName, labelName: row.labelName } });
|
||||
}
|
||||
|
||||
// 文章删除
|
||||
const deleteArticle = (id: number) => {
|
||||
ElMessageBox.confirm('确定要删除该文章吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(async() => {
|
||||
try {
|
||||
state.tableData.loading = true;
|
||||
let res = await artApi.deleteArticle(id);
|
||||
if(res?.success){
|
||||
await getTableData();
|
||||
ElMessage.success('文章删除成功!');
|
||||
} else ElMessage.error('文章删除失败!');
|
||||
} catch(e) {
|
||||
ElMessage.error('处理失败!');
|
||||
} finally {
|
||||
state.tableData.loading = false;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 日期格式化
|
||||
const dateFormatter = (row: any, column: TableColumnCtx<String>) => {
|
||||
let date = new Date(row.createtime);
|
||||
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
|
||||
}
|
||||
|
||||
// 文章类型格式化
|
||||
const typeFormatter = (row: any, column: TableColumnCtx<String>) => {
|
||||
return row.articletype === 1 ? '外链' : '图文';
|
||||
}
|
||||
|
||||
// 分页改变
|
||||
const onHandleSizeChange = (val: number) => {
|
||||
state.tableData.param.size = val;
|
||||
getTableData();
|
||||
};
|
||||
|
||||
// 分页改变
|
||||
const onHandleCurrentChange = (val: number) => {
|
||||
state.tableData.param.current = val;
|
||||
getTableData();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getTableData();
|
||||
getModuleList();
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.system-dept-container {
|
||||
:deep(.el-card__body) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
.el-table {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<el-form size="large" class="login-content-form">
|
||||
<el-form-item class="login-animation1">
|
||||
<el-input text :placeholder="$t('message.account.accountPlaceholder1')" v-model="state.ruleForm.userName" clearable autocomplete="off">
|
||||
<el-input text :placeholder="$t('message.account.accountPlaceholder1')" v-model="state.ruleForm.username" clearable autocomplete="off">
|
||||
<template #prefix>
|
||||
<el-icon class="el-input__icon"><ele-User /></el-icon>
|
||||
</template>
|
||||
@ -68,6 +68,7 @@ import { initBackEndControlRoutes } from '/@/router/backEnd';
|
||||
import { Session } from '/@/utils/storage';
|
||||
import { formatAxis } from '/@/utils/formatTime';
|
||||
import { NextLoading } from '/@/utils/loading';
|
||||
import { useLoginApi } from '/@/api/login';
|
||||
|
||||
// 定义变量内容
|
||||
const { t } = useI18n();
|
||||
@ -78,14 +79,17 @@ const router = useRouter();
|
||||
const state = reactive({
|
||||
isShowPassword: false,
|
||||
ruleForm: {
|
||||
userName: 'admin',
|
||||
username: 'admin1',
|
||||
password: '123456',
|
||||
code: '1234',
|
||||
usertype: 1
|
||||
},
|
||||
loading: {
|
||||
signIn: false,
|
||||
},
|
||||
});
|
||||
// 引入 api 请求接口
|
||||
const loginApi = useLoginApi();
|
||||
|
||||
// 时间获取
|
||||
const currentTime = computed(() => {
|
||||
@ -93,21 +97,29 @@ const currentTime = computed(() => {
|
||||
});
|
||||
// 登录
|
||||
const onSignIn = async () => {
|
||||
state.loading.signIn = true;
|
||||
// 存储 token 到浏览器缓存
|
||||
Session.set('token', Math.random().toString(36).substr(0));
|
||||
// 模拟数据,对接接口时,记得删除多余代码及对应依赖的引入。用于 `/src/stores/userInfo.ts` 中不同用户登录判断(模拟数据)
|
||||
Cookies.set('userName', state.ruleForm.userName);
|
||||
if (!themeConfig.value.isRequestRoutes) {
|
||||
// 前端控制路由,2、请注意执行顺序
|
||||
const isNoPower = await initFrontEndControlRoutes();
|
||||
signInSuccess(isNoPower);
|
||||
} else {
|
||||
// 模拟后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
|
||||
// 添加完动态路由,再进行 router 跳转,否则可能报错 No match found for location with path "/"
|
||||
const isNoPower = await initBackEndControlRoutes();
|
||||
// 执行完 initBackEndControlRoutes,再执行 signInSuccess
|
||||
signInSuccess(isNoPower);
|
||||
try {
|
||||
state.loading.signIn = true;
|
||||
let res = await loginApi.login(state.ruleForm);
|
||||
if(res?.success){
|
||||
// 存储 token 到浏览器缓存
|
||||
Session.set('token', 'fbc545a91cc94fe89296828a25a7e08e@9085553879028596738');
|
||||
// 模拟数据,对接接口时,记得删除多余代码及对应依赖的引入。用于 `/src/stores/userInfo.ts` 中不同用户登录判断(模拟数据)
|
||||
Cookies.set('userName', res.data.user.realname);
|
||||
if (!themeConfig.value.isRequestRoutes) {
|
||||
// 前端控制路由,2、请注意执行顺序
|
||||
const isNoPower = await initFrontEndControlRoutes();
|
||||
signInSuccess(isNoPower);
|
||||
} else {
|
||||
// 模拟后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
|
||||
// 添加完动态路由,再进行 router 跳转,否则可能报错 No match found for location with path "/"
|
||||
const isNoPower = await initBackEndControlRoutes();
|
||||
// 执行完 initBackEndControlRoutes,再执行 signInSuccess
|
||||
signInSuccess(isNoPower);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
} finally {
|
||||
state.loading.signIn = false;
|
||||
}
|
||||
};
|
||||
// 登录成功后的跳转
|
||||
|
@ -43,7 +43,7 @@
|
||||
|
||||
/* Module Resolution Options */
|
||||
"moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
|
||||
"baseUrl": "." /* Base directory to resolve non-absolute module names. */,
|
||||
"baseUrl": "http://8.138.171.103:8081" /* Base directory to resolve non-absolute module names. */,
|
||||
"paths": {
|
||||
"/@/*": ["src/*"]
|
||||
} /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */,
|
||||
|
@ -4,6 +4,7 @@ import { defineConfig, loadEnv, ConfigEnv } from 'vite';
|
||||
import vueSetupExtend from 'vite-plugin-vue-setup-extend-plus';
|
||||
import viteCompression from 'vite-plugin-compression';
|
||||
import { buildConfig } from './src/utils/build';
|
||||
import { codeInspectorPlugin } from 'code-inspector-plugin';
|
||||
|
||||
const pathResolve = (dir: string) => {
|
||||
return resolve(__dirname, '.', dir);
|
||||
@ -16,8 +17,9 @@ const alias: Record<string, string> = {
|
||||
|
||||
const viteConfig = defineConfig((mode: ConfigEnv) => {
|
||||
const env = loadEnv(mode.mode, process.cwd());
|
||||
|
||||
return {
|
||||
plugins: [vue(), vueSetupExtend(), viteCompression(), JSON.parse(env.VITE_OPEN_CDN) ? buildConfig.cdn() : null],
|
||||
plugins: [vue(), vueSetupExtend(), viteCompression(), JSON.parse(env.VITE_OPEN_CDN) ? buildConfig.cdn() : null, codeInspectorPlugin({bundler: 'vite'})],
|
||||
root: process.cwd(),
|
||||
resolve: { alias },
|
||||
base: mode.command === 'serve' ? './' : env.VITE_PUBLIC_PATH,
|
||||
@ -28,6 +30,12 @@ const viteConfig = defineConfig((mode: ConfigEnv) => {
|
||||
open: JSON.parse(env.VITE_OPEN),
|
||||
hmr: true,
|
||||
proxy: {
|
||||
'/vueAdminApi': {
|
||||
target: 'http://8.138.171.103:8081',
|
||||
ws: true,
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/vueAdminApi/, ''),
|
||||
},
|
||||
'/gitee': {
|
||||
target: 'https://gitee.com',
|
||||
ws: true,
|
||||
|
Loading…
Reference in New Issue
Block a user