配置路由:router/router.ts,在appRouters对象中copy修改就行了,注意所有路由包括父子路由的name都不能重复。
路由配置中permission是对应的后台权限名称字符串:permission:'Pages.Articles',meta》title对应的是多语言配置key:Framework.Core》Localization》Framework.xml
添加新页面:
1、添加store实体:store/entities
import Entity from './entity' export default class Article extends Entity<number>{ isActive:boolean; tenantId:number; title:string; author:number; contents:string; articleType:number; coverImg:string; }
2、添加store/modules,其中引用刚刚添加的实体类型
import {Store,Module,ActionContext} from 'vuex'
import ListModule from './list-module'
import ListState from './list-state'
import Article from '../entities/article'
import Role from '../entities/role'
import Ajax from '../../lib/ajax'
import PageResult from '@/store/entities/page-result';
import ListMutations from './list-mutations'
interface ArticleState extends ListState<Article>{
editArticle:Article
}
class ArticleMutations extends ListMutations<Article>{
}
class ArticleModule extends ListModule<ArticleState,any,Article>{
state={
totalCount:0,
currentPage:1,
pageSize:10,
list: new Array<Article>(),
loading:false,
editArticle:new Article()
}
actions={
async getAll(context:ActionContext<ArticleState,any>,payload:any){
context.state.loading=true;
let reponse=await Ajax.post('/api/services/app/Article/GetArticles',payload.data);//Ajax.get 方式,传参:{params:payload.data};Ajax.post 方式参数直接写 payload.data
context.state.loading=false;
let page=reponse.data.result as PageResult<Article>;
context.state.totalCount=page.totalCount;
context.state.list=page.items;
},
async create(context:ActionContext<ArticleState,any>,payload:any){
await Ajax.post('/api/services/app/Article/Create',payload.data);
},
async update(context:ActionContext<ArticleState,any>,payload:any){
await Ajax.put('/api/services/app/Article/Update',payload.data);
},
async delete(context:ActionContext<ArticleState,any>,payload:any){
await Ajax.delete('/api/services/app/Article/Delete?Id='+payload.data.id);
},
async get(context:ActionContext<ArticleState,any>,payload:any){
let reponse=await Ajax.get('/api/services/app/Article/Get?Id='+payload.id);
return reponse.data.result as Article;
},
};
mutations={
setCurrentPage(state:ArticleState,page:number){
state.currentPage=page;
},
setPageSize(state:ArticleState,pagesize:number){
state.pageSize=pagesize;
},
edit(state:ArticleState,article:Article){
state.editArticle=article;
}
}
}
const articleModule=new ArticleModule();
export default articleModule;
可以看到他都是用store来存储数据的。
3、添加页面views/article/index.vue,这用的是切换组件,不跳转,就不用改变路径,也不用再多配置路由
<template> <component :is="currentRouter" :operation='currentOpt' :mId="currentId" /> </template> <script> // 列表页面 import list from '@/views/article/components/list' // 编辑页面 import edit from '@/views/article/components/edit' export default { name: 'articles', components: { list, edit }, data() { return { // 当前加载的组件,默认为 list 组件(显示列表页面) currentRouter: "list", currentOpt: undefined, currentId: undefined } }, created() { } } </script>
4、添加组件views/article/components/list.vue和edit.vue
list.vue
<template>
<div>
<Card dis-hover>
<div class="page-body">
<Form ref="queryForm" :label-width="80" label-position="left" inline>
<Row :gutter="16">
<Col span="6">
<FormItem :label="L('Keyword') + ':'" style="width: 100%">
<Input
v-model="pagerequest.keyword"
:placeholder="L('ArticleTitle')"
/>
</FormItem>
</Col>
</Row>
<Row>
<Button
@click="create"
icon="android-add"
type="primary"
size="large"
>{{ L("Add") }}</Button
>
<Button
icon="ios-search"
type="primary"
size="large"
@click="getpage"
class="toolbar-btn"
>{{ L("Find") }}</Button
>
</Row>
</Form>
<div class="margin-top-10">
<Table
:loading="loading"
:columns="columns"
:no-data-text="L('NoDatas')"
border
:data="list"
>
</Table>
<Page
show-sizer
class-name="fengpage"
:total="totalCount"
class="margin-top-10"
@on-change="pageChange"
@on-page-size-change="pagesizeChange"
:page-size="pageSize"
:current="currentPage"
></Page>
</div>
</div>
</Card>
</div>
</template>
<script lang='ts'>
import { Component, Vue, Inject, Prop, Watch } from "vue-property-decorator";
import Util from "@/lib/util";
import AbpBase from "@/lib/abpbase";
import PageRequest from "@/store/entities/page-request";
class PageModelRequest extends PageRequest {
keyword: string;
isActive: boolean = null; //nullable
}
@Component({
// components: { CreateUser, EditUser },
})
export default class AbpArticles extends AbpBase {
// createModalShow: boolean = false;
// editModalShow: boolean = false;
pagerequest: PageModelRequest = new PageModelRequest();
// creationTime: Date[] = [];
edit() {
// this.editModalShow=true;
}
get list() {
return this.$store.state.article.list;
}
get loading() {
return this.$store.state.article.loading;
}
create() {
this.$parent.currentOpt = "create";
this.$parent.currentRouter = "edit";
}
isActiveChange(val: string) {
console.log(val);
if (val === "Actived") {
this.pagerequest.isActive = true;
} else if (val === "NoActive") {
this.pagerequest.isActive = false;
} else {
this.pagerequest.isActive = null;
}
}
pageChange(page: number) {
this.$store.commit("article/setCurrentPage", page);
this.getpage();
}
pagesizeChange(pagesize: number) {
this.$store.commit("article/setPageSize", pagesize);
this.getpage();
}
async getpage() {
this.pagerequest.maxResultCount = this.pageSize;
this.pagerequest.skipCount = (this.currentPage - 1) * this.pageSize;
//filters
// if (this.creationTime.length > 0) {
// this.pagerequest.from = this.creationTime[0];
// }
// if (this.creationTime.length > 1) {
// this.pagerequest.to = this.creationTime[1];
// }
await this.$store.dispatch({
type: "article/getAll",
data: this.pagerequest,
});
}
get pageSize() {
return this.$store.state.article.pageSize;
}
get totalCount() {
return this.$store.state.article.totalCount;
}
get currentPage() {
return this.$store.state.article.currentPage;
}
columns = [
{
title: this.L("ArticleTitle"),
key: "title",
},
{
title: this.L("ArticleAuthor"),
key: "author",
},
{
title: this.L("IsActive"),
render: (h: any, params: any) => {
return h("span", params.row.isActive ? this.L("Yes") : this.L("No"));
},
},
{
title: this.L("Actions"),
key: "Actions",
width: 150,
render: (h: any, params: any) => {
return h("div", [
h(
"Button",
{
props: {
type: "primary",
size: "small",
},
style: {
marginRight: "5px",
},
on: {
click: () => {
this.$store.commit("article/edit", params.row);
this.$parent.currentOpt = "edit";
this.$parent.currentRouter = "edit";
this.$parent.currentId = params.row.id;
},
},
},
this.L("Edit")
),
h(
"Button",
{
props: {
type: "error",
size: "small",
},
on: {
click: async () => {
this.$Modal.confirm({
title: this.L("Tips"),
content: this.L("DeleteUserConfirm"),
okText: this.L("Yes"),
cancelText: this.L("No"),
onOk: async () => {
await this.$store.dispatch({
type: "article/delete",
data: params.row,
});
await this.getpage();
},
});
},
},
},
this.L("Delete")
),
]);
},
},
];
async created() {
this.getpage();
}
}
</script>
edit.vue
<template>
<div>
<Form ref="subForm" label-position="top" :rules="rules" :model="formModel">
<Tabs value="detail">
<TabPane :label="L('ArticleDetails')" name="detail">
<FormItem :label="L('ArticleTitle')" prop="title">
<Input
v-model="formModel.title"
:maxlength="32"
:minlength="2"
></Input>
</FormItem>
<FormItem :label="L('ArticleType')" prop="articleType">
<Select
:placeholder="L('ArticleType')"
v-model="formModel.articleType"
>
<Option
v-for="item in articleTypeDataItems"
:key="item.id"
:label="item.name"
:value="item.id"
>{{ item.name }}</Option
>
</Select>
</FormItem>
<FormItem :label="L('ArticleCoverImg')" prop="coverImg">
<Input v-model="formModel.coverImg"></Input>
<Upload
name="coverImg"
ref="upload"
:show-upload-list="false"
:format="['jpg', 'jpeg', 'png']"
:max-size="2048"
:on-format-error="handleFormatError"
:on-exceeded-size="handleMaxSize"
:before-upload="handleBeforeUpload"
:on-success="handleSuccess"
:multiple="false"
type="drag"
action="http://localhost:21021/api/FileCommon/UploadFile"
>
<Button icon="ios-cloud-upload-outline">{{
L("UploadFiles")
}}</Button>
</Upload>
</FormItem>
<FormItem :label="L('ArticleAuthor')" prop="author">
<Input v-model="formModel.author" :maxlength="32"></Input>
</FormItem>
<FormItem>
<Checkbox v-model="formModel.isActive">{{
L("IsActive")
}}</Checkbox>
</FormItem>
<FormItem :label="L('ArticleContents')" prop="contents">
<quill-editor
v-model="formModel.contents"
ref="myQuillEditor"
style="height: 500px;margin-bottom:30px;"
:options="editorOption"
>
<!-- 自定义toolar -->
<div id="toolbar" slot="toolbar">
<!-- Add a bold button -->
<button class="ql-bold" title="加粗">Bold</button>
<button class="ql-italic" title="斜体">Italic</button>
<button class="ql-underline" title="下划线">underline</button>
<button class="ql-strike" title="删除线">strike</button>
<button class="ql-blockquote" title="引用"></button>
<button class="ql-code-block" title="代码"></button>
<button class="ql-header" value="1" title="标题1"></button>
<button class="ql-header" value="2" title="标题2"></button>
<!--Add list -->
<button
class="ql-list"
value="ordered"
title="有序列表"
></button>
<button
class="ql-list"
value="bullet"
title="无序列表"
></button>
<!-- Add font size dropdown -->
<select class="ql-header" title="段落格式">
<option selected>段落</option>
<option value="1">标题1</option>
<option value="2">标题2</option>
<option value="3">标题3</option>
<option value="4">标题4</option>
<option value="5">标题5</option>
<option value="6">标题6</option>
</select>
<select class="ql-size" title="字体大小">
<option value="10px">10px</option>
<option value="12px">12px</option>
<option value="14px">14px</option>
<option value="16px" selected>16px</option>
<option value="18px">18px</option>
<option value="20px">20px</option>
</select>
<select class="ql-font" title="字体">
<option value="SimSun">宋体</option>
<option value="SimHei">黑体</option>
<option value="Microsoft-YaHei">微软雅黑</option>
<option value="KaiTi">楷体</option>
<option value="FangSong">仿宋</option>
<option value="Arial">Arial</option>
</select>
<!-- Add subscript and superscript buttons -->
<select
class="ql-color"
value="color"
title="字体颜色"
></select>
<select
class="ql-background"
value="background"
title="背景颜色"
></select>
<select class="ql-align" value="align" title="对齐"></select>
<button class="ql-clean" title="还原"></button>
<!-- You can also add your own -->
</div>
</quill-editor>
</FormItem>
</TabPane>
</Tabs>
</Form>
<div slot="footer" >
<Button @click="cancel">{{ L("Cancel") }}</Button>
<Button @click="save" type="primary">{{ L("OK") }}</Button>
</div>
</div>
</template>
<script lang="ts">
import { Quill, quillEditor } from "vue-quill-editor";
import "quill/dist/quill.core.css";
import "quill/dist/quill.snow.css";
import "quill/dist/quill.bubble.css";
//引入font.css
import "@/assets/css/font.css";
// 自定义字体大小
let Size = Quill.import("attributors/style/size");
Size.whitelist = ["10px", "12px", "14px", "16px", "18px", "20px"];
Quill.register(Size, true);
// 自定义字体类型
var fonts = [
"SimSun",
"SimHei",
"Microsoft-YaHei",
"KaiTi",
"FangSong",
"Arial",
"Times-New-Roman",
"sans-serif",
"宋体",
"黑体",
];
var Font = Quill.import("formats/font");
Font.whitelist = fonts;
Quill.register(Font, true);
import { ArticleTypeDataItems } from "@/lib/constData";
import { Component, Vue, Inject, Prop, Watch } from "vue-property-decorator";
import Util from "../../../lib/util";
import AbpBase from "../../../lib/abpbase";
import Article from "@/store/entities/article";
@Component({
components: { quillEditor },
})
export default class EditArticle extends AbpBase {
@Prop({ type: Boolean, default: false }) value: boolean;
formModel: Article = new Article();
articleTypeDataItems = ArticleTypeDataItems;
uploadFormat = [".jpg", ".png", ".jpeg"];
editorOption = {
placeholder: "请输入",
theme: "snow", // or 'bubble'
modules: {
toolbar: {
container: "#toolbar",
},
},
};
handleSuccess(res, file) {
console.log(res.data);
file.url = res.data.fileUrl;
file.name = res.data.fileUrl;
this.formModel.coverImg = res.data.fileUrl;
}
handleFormatError(file) {
this.$Notice.warning({
title: "The file format is incorrect",
desc:
"File format of " +
file.name +
" is incorrect, please select jpg or png.",
});
}
handleMaxSize(file) {
this.$Notice.warning({
title: "Exceeding file size limit",
desc: "File " + file.name + " is too large, no more than 2M.",
});
}
handleBeforeUpload() {
//上传文件之前的逻辑处理,return false 阻止上传
const check = false;
if (!check) {
this.$Notice.warning({
title: "error,can't upload",
});
}
return check;
}
created() {
let editModel = null;
if (this.$store.state.article.editArticle) {
editModel = this.$store.state.article.editArticle;
}
this.formModel = Util.extend(true, {}, editModel);
}
// articleTypeChange(val: string) {
// this.formModel.articleType = parseInt(val);
// }
save() {
(this.$refs.subForm as any).validate(async (valid: boolean) => {
if (valid) {
let typeName = "article/create";
if (this && this.formModel && this.formModel.id) {
typeName = "article/update";
}
await this.$store.dispatch({
type: typeName,
data: this.formModel,
});
(this.$refs.subForm as any).resetFields();
this.$emit("save-success");
this.$emit("input", false);
this.$Notice.success({
title: "tips",
desc: "success",
});
}
});
}
cancel() {
this.$parent.currentOpt = "list";
this.$parent.currentRouter = "list";
this.$parent.currentId = 0;
}
rules = {
title: [
{
required: true,
message: this.L("FieldIsRequired", undefined, this.L("ArticleTitle")),
trigger: "blur",
},
],
author: [
{
required: true,
message: this.L("FieldIsRequired", undefined, this.L("ArticleAuthor")),
trigger: "blur",
},
],
contents: [
{
required: true,
message: this.L(
"FieldIsRequired",
undefined,
this.L("ArticleContents")
),
trigger: "blur",
},
],
articleType: [
{
required: true,
message: this.L("FieldIsRequired", undefined, this.L("ArticleType")),
trigger: "change",
type: "number",
},
],
};
}
</script>
list.vue使用ElementUI版本,main.ts中增加引用和基本样式:import ElementUI from 'element-ui';import 'element-ui/lib/theme-chalk/index.css';这个样式路径下面还有其他相关样式,需要可以自己引用一下。
<template>
<div>
<div class="app-container">
<div class="filter-container">
<el-row :gutter="15">
<el-col :md="6">
<el-input
v-model="pagerequest.keyword"
:placeholder="L('ArticleTitle')"
class="filter-item"
size="mini"
/>
</el-col>
<el-col :md="6">
<el-button
@click="create"
icon="android-add"
type="primary"
size="mini"
>{{ L("Add") }}</el-button
>
<el-button
class="filter-item"
type="primary"
icon="el-icon-search"
@click="getpage"
size="mini"
>{{ L("Find") }}</el-button
>
</el-col>
</el-row>
</div>
<el-table
v-loading="loading"
:data="list"
fit
highlight-current-row
style="width: 100%"
class="table-scroll-x"
>
<el-table-column label="ID" prop="id"></el-table-column>
<el-table-column label="标题" prop="title"></el-table-column>
<el-table-column label="作者" prop="author"></el-table-column>
<el-table-column label="操作" class-name="small-padding">
<template slot-scope="{ row }">
<span class="link-type" @click="handleEdit(row)">编辑</span>
<span class="link-type" @click="handleDelete(row)">删除</span>
</template>
</el-table-column>
</el-table>
<pagination
v-show="totalCount > 0"
:total="totalCount"
:page="currentPage"
:limit="pageSize"
@pagination="pageChange"
/>
</div>
</div>
</template>
<script lang='ts'>
import { Component, Vue, Inject, Prop, Watch } from "vue-property-decorator";
import Pagination from "@/components/Pagination/index";
import Util from "@/lib/util";
import AbpBase from "@/lib/abpbase";
import PageRequest from "@/store/entities/page-request";
class PageModelRequest extends PageRequest {
keyword: string;
isActive: boolean = null; //nullable
}
@Component({
components: { Pagination },
})
export default class AbpArticles extends AbpBase {
// createModalShow: boolean = false;
// editModalShow: boolean = false;
pagerequest: PageModelRequest = new PageModelRequest();
// creationTime: Date[] = [];
handleEdit(row) {
this.$store.commit("article/edit", row);
this.$parent.currentOpt = "edit";
this.$parent.currentRouter = "edit";
this.$parent.currentId = row.id;
}
handleDelete(row) {
this.$Modal.confirm({
title: this.L("Tips"),
content: this.L("DeleteUserConfirm"),
okText: this.L("Yes"),
cancelText: this.L("No"),
onOk: async () => {
await this.$store.dispatch({
type: "article/delete",
data: row,
});
await this.getpage();
},
});
}
edit() {
// this.editModalShow=true;
}
get list() {
return this.$store.state.article.list;
}
get loading() {
return this.$store.state.article.loading;
}
create() {
this.$parent.currentOpt = "create";
this.$parent.currentRouter = "edit";
}
isActiveChange(val: string) {
if (val === "Actived") {
this.pagerequest.isActive = true;
} else if (val === "NoActive") {
this.pagerequest.isActive = false;
} else {
this.pagerequest.isActive = null;
}
}
pageChange(page: number, pagesize: number) {
this.$store.commit("article/setCurrentPage", page);
this.$store.commit("article/setPageSize", pagesize);
this.getpage();
}
pagesizeChange(pagesize: number) {
this.$store.commit("article/setPageSize", pagesize);
this.getpage();
}
async getpage() {
this.pagerequest.maxResultCount = this.pageSize;
this.pagerequest.skipCount = (this.currentPage - 1) * this.pageSize;
//filters
// if (this.creationTime.length > 0) {
// this.pagerequest.from = this.creationTime[0];
// }
// if (this.creationTime.length > 1) {
// this.pagerequest.to = this.creationTime[1];
// }
await this.$store.dispatch({
type: "article/getAll",
data: this.pagerequest,
});
}
get pageSize() {
return this.$store.state.article.pageSize;
}
get totalCount() {
return this.$store.state.article.totalCount;
}
get currentPage() {
return this.$store.state.article.currentPage;
}
columns = [
{
title: this.L("ArticleTitle"),
key: "title",
},
{
title: this.L("ArticleAuthor"),
key: "author",
},
{
title: this.L("IsActive"),
render: (h: any, params: any) => {
return h("span", params.row.isActive ? this.L("Yes") : this.L("No"));
},
},
{
title: this.L("Actions"),
key: "Actions",
width: 150,
render: (h: any, params: any) => {
return h("div", [
h(
"Button",
{
props: {
type: "primary",
size: "small",
},
style: {
marginRight: "5px",
},
on: {
click: () => {
this.$store.commit("article/edit", params.row);
this.$parent.currentOpt = "edit";
this.$parent.currentRouter = "edit";
this.$parent.currentId = params.row.id;
},
},
},
this.L("Edit")
),
h(
"Button",
{
props: {
type: "error",
size: "small",
},
on: {
click: async () => {
this.$Modal.confirm({
title: this.L("Tips"),
content: this.L("DeleteUserConfirm"),
okText: this.L("Yes"),
cancelText: this.L("No"),
onOk: async () => {
await this.$store.dispatch({
type: "article/delete",
data: params.row,
});
await this.getpage();
},
});
},
},
},
this.L("Delete")
),
]);
},
},
];
async created() {
this.getpage();
}
}
</script>
edit.vue使用ElementUI版本
<template> <div class="app-container"> <el-form :model="formModel" :rules="rules" ref="subForm" label-width="150px" class="form-container" > <div class="createPost-main-container"> <!-- <el-row :gutter="rowGutter"> <el-divider content-position="left"> <span style="color: rgba(41, 155, 255, 0.67);">{{L('ArticleDetails')}}</span> </el-divider> </el-row> --> <el-row :gutter="rowGutter"> <el-col :span="8"> <el-form-item :label="L('ArticleTitle')" prop="title" class="postInfo-container-item" > <el-input :maxlength="32" :minlength="2" v-model="formModel.title" /> </el-form-item> </el-col> <el-col :span="8"> <el-form-item :label="L('ArticleType')" prop="articleType"> <el-select size="mini" v-model="formModel.articleType" clearable class="filter-item" > <el-option v-for="item in articleTypeDataItems" :key="item.id" :label="item.name" :value="item.id" /> </el-select> </el-form-item> </el-col> </el-row> <el-row :gutter="rowGutter"> <el-col :span="8"> <el-form-item :label="L('ArticleCoverImg')" prop="coverImg"> <el-input v-model="formModel.coverImg" /> </el-form-item> </el-col> <el-col :span="8"> <el-form-item> <el-button type="primary" class="filter-item" size="mini"> <label for="file-upload"> {{ L("UploadFiles") }} <input type="file" id="file-upload" style="display: none" accept=".png, .jpg, .jpeg" @change="uploadFiles" /> </label> </el-button> </el-form-item> </el-col> </el-row> <el-row :gutter="rowGutter"> <el-col :span="8"> <el-form-item :label="L('ArticleAuthor')" prop="author"> <el-input :maxlength="32" v-model="formModel.author" /> </el-form-item> </el-col> <el-col :span="8"> <el-form-item prop="isActive"> <el-checkbox v-model="formModel.isActive">{{ L("IsActive") }}</el-checkbox> </el-form-item> </el-col> </el-row> <el-row :gutter="rowGutter"> <el-col :span="24"> <el-form-item :label="L('ArticleContents')" prop="contents"> <quill-editor v-model="formModel.contents" ref="myQuillEditor" style="height: 500px; margin-bottom: 30px" :options="editorOption" > <!-- 自定义toolar --> <div id="toolbar" slot="toolbar"> <!-- Add a bold button --> <button class="ql-bold" title="加粗">Bold</button> <button class="ql-italic" title="斜体">Italic</button> <button class="ql-underline" title="下划线">underline</button> <button class="ql-strike" title="删除线">strike</button> <button class="ql-blockquote" title="引用"></button> <button class="ql-code-block" title="代码"></button> <button class="ql-header" value="1" title="标题1"></button> <button class="ql-header" value="2" title="标题2"></button> <!--Add list --> <button class="ql-list" value="ordered" title="有序列表" ></button> <button class="ql-list" value="bullet" title="无序列表" ></button> <!-- Add font size dropdown --> <select class="ql-header" title="段落格式"> <option selected>段落</option> <option value="1">标题1</option> <option value="2">标题2</option> <option value="3">标题3</option> <option value="4">标题4</option> <option value="5">标题5</option> <option value="6">标题6</option> </select> <select class="ql-size" title="字体大小"> <option value="10px">10px</option> <option value="12px">12px</option> <option value="14px">14px</option> <option value="16px" selected>16px</option> <option value="18px">18px</option> <option value="20px">20px</option> </select> <select class="ql-font" title="字体"> <option value="SimSun">宋体</option> <option value="SimHei">黑体</option> <option value="Microsoft-YaHei">微软雅黑</option> <option value="KaiTi">楷体</option> <option value="FangSong">仿宋</option> <option value="Arial">Arial</option> </select> <!-- Add subscript and superscript buttons --> <select class="ql-color" value="color" title="字体颜色" ></select> <select class="ql-background" value="background" title="背景颜色" ></select> <select class="ql-align" value="align" title="对齐"></select> <button class="ql-clean" title="还原"></button> <!-- You can also add your own --> </div> </quill-editor> </el-form-item> </el-col> </el-row> <el-row :gutter="rowGutter"> <el-col :span="8"> <el-form-item> <el-button @click="cancel">{{ L("Back") }}</el-button> <el-button @click="save" type="primary">{{ L("OK") }}</el-button> </el-form-item> </el-col> </el-row> </div> </el-form> </div> </template> <script lang="ts"> import Axios from "axios"; import { Quill, quillEditor } from "vue-quill-editor"; import "quill/dist/quill.core.css"; import "quill/dist/quill.snow.css"; import "quill/dist/quill.bubble.css"; //引入font.css import "@/assets/css/font.css"; import appconst from "@/lib/appconst"; // 自定义字体大小 let Size = Quill.import("attributors/style/size"); Size.whitelist = ["10px", "12px", "14px", "16px", "18px", "20px"]; Quill.register(Size, true); // 自定义字体类型 var fonts = [ "SimSun", "SimHei", "Microsoft-YaHei", "KaiTi", "FangSong", "Arial", "Times-New-Roman", "sans-serif", "宋体", "黑体", ]; var Font = Quill.import("formats/font"); Font.whitelist = fonts; Quill.register(Font, true); import { ArticleTypeDataItems } from "@/lib/constData"; import { Component, Vue, Inject, Prop, Watch } from "vue-property-decorator"; import Util from "../../../lib/util"; import AbpBase from "../../../lib/abpbase"; import Article from "@/store/entities/article"; @Component({ components: { quillEditor }, }) export default class EditArticle extends AbpBase { @Prop({ type: Boolean, default: false }) value: boolean; activeName = "first"; rowGutter = 30; formModel: Article = new Article(); articleTypeDataItems = ArticleTypeDataItems; uploadFormat = [".jpg", ".png", ".jpeg"]; editorOption = { placeholder: "请输入", theme: "snow", // or 'bubble' modules: { toolbar: { container: "#toolbar", }, }, }; uploadFiles(e) { let file = e.target.files[0]; /* eslint-disable no-undef */ let param = new FormData(); // 创建form对象 param.append("file", file); // 通过append向form对象添加数据 // param.append("zoneId", zid); // console.log(param.get("file")); // FormData私有类对象,访问不到,可以通过get判断值是否传进去 let config = { headers: { "Content-Type": "multipart/form-data" }, }; // 添加请求头 Axios.post( appconst.remoteServiceBaseUrl + "/api/FileCommon/UploadFile", param, config ).then((res) => { this.formModel.coverImg = res.data.result.fileUrl; }); } handleSuccess(res, file) { console.log(res.data); file.url = res.data.fileUrl; file.name = res.data.fileUrl; this.formModel.coverImg = res.data.fileUrl; } handleFormatError(file) { this.$Notice.warning({ title: "The file format is incorrect", desc: "File format of " + file.name + " is incorrect, please select jpg or png.", }); } handleMaxSize(file) { this.$Notice.warning({ title: "Exceeding file size limit", desc: "File " + file.name + " is too large, no more than 2M.", }); } handleBeforeUpload() { //上传文件之前的逻辑处理,return false 阻止上传 const check = false; if (!check) { this.$Notice.warning({ title: "error,can't upload", }); } return check; } created() { let editModel = null; if (this.$store.state.article.editArticle) { editModel = this.$store.state.article.editArticle; } this.formModel = Util.extend(true, {}, editModel); } // articleTypeChange(val: string) { // this.formModel.articleType = parseInt(val); // } save() { (this.$refs.subForm as any).validate(async (valid: boolean) => { if (valid) { let typeName = "article/create"; if (this && this.formModel && this.formModel.id) { typeName = "article/update"; } await this.$store.dispatch({ type: typeName, data: this.formModel, }); (this.$refs.subForm as any).resetFields(); this.$emit("save-success"); this.$emit("input", false); this.$Notice.success({ title: "tips", desc: "success", }); } }); } cancel() { this.$parent.currentOpt = "list"; this.$parent.currentRouter = "list"; this.$parent.currentId = 0; } rules = { title: [ { required: true, message: this.L("FieldIsRequired", undefined, this.L("ArticleTitle")), trigger: "blur", }, ], author: [ { required: true, message: this.L("FieldIsRequired", undefined, this.L("ArticleAuthor")), trigger: "blur", }, ], contents: [ { required: true, message: this.L( "FieldIsRequired", undefined, this.L("ArticleContents") ), trigger: "blur", }, ], articleType: [ { required: true, message: this.L("FieldIsRequired", undefined, this.L("ArticleType")), trigger: "change", type: "number", }, ], }; } </script>
5、分页组件src》components》Pagination》index.vue:
<template> <div :class="{ hidden: hidden }" class="pagination-container"> <el-pagination :background="background" :current-page.sync="currentPage" :page-size.sync="pageSize" :layout="layout" :page-sizes="pageSizes" :total="total" v-bind="$attrs" @size-change="handleSizeChange" @current-change="handleCurrentChange" /> </div> </template> <script> import { scrollTo } from "@/lib/scroll-to"; export default { name: "Pagination", props: { total: { required: true, type: Number, }, page: { type: Number, default: 1, }, limit: { type: Number, default: 20, }, pageSizes: { type: Array, default() { return [10, 20, 30, 50]; }, }, layout: { type: String, default: "total, sizes, prev, pager, next, jumper", }, background: { type: Boolean, default: true, }, autoScroll: { type: Boolean, default: true, }, hidden: { type: Boolean, default: false, }, }, computed: { currentPage: { get() { return this.page; }, set(val) { this.$emit("update:page", val); }, }, pageSize: { get() { return this.limit; }, set(val) { this.$emit("update:limit", val); }, }, }, methods: { handleSizeChange(val) { this.$emit("pagination", this.currentPage, val); if (this.autoScroll) { scrollTo(0, 800); } }, handleCurrentChange(val) { this.$emit("pagination", val, this.pageSize); if (this.autoScroll) { scrollTo(0, 800); } }, }, }; </script> <style scoped> .pagination-container { background: #fff; padding: 0; } .pagination-container.hidden { display: none; } </style>
6、富文本编辑器我使用的是 npm install vue-quill-editor -save,参考文档:https://www.cnblogs.com/seven077/p/11313137.html
7、使用ViewUI的Upload组件上传文件,这个类写在Framework.Web.Core项目中的Controller文件夹下,这个项目下面可以使用 HttpContext.Request.Form.Files 来获取上传的文件列表,
如果写在Application这个项目里,方法要使用参数IFormFile,这个我没弄成功。
using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Abp.UI; using System.IO; using Framework.Common; using Microsoft.AspNetCore.Http; namespace Framework.Controllers { [Route("api/[controller]/[action]")] public class FileCommonController : FrameworkControllerBase { public FileCommonController() { } /// <summary> /// 上传一个文件,并返回文件上传成功后的信息 /// </summary> /// <param name="file">要上传的文件实体</param> /// <returns>文件上传成功后返回的文件相关信息</returns> [HttpPost] public async Task<FileUploadOutputDto> UploadFile() { try { var file = HttpContext.Request.Form.Files[0]; //文件的原始名称 string fileOriginName = file.FileName; //读取文件保存的根目录 string fileSaveRootDir = Utils.GetAppSetting("App", "FileRootPath"); //读取办公管理文件保存的模块的根目录 //string fileSaveDir = Utils.GetAppSetting("App", "OAFiles"); //文件保存的相对文件夹(保存到wwwroot目录下) string absoluteFileDir = fileSaveRootDir; //文件保存的路径(应用的工作目录+文件夹相对路径); string fileSavePath = Environment.CurrentDirectory + "/wwwroot/" + absoluteFileDir; if (!Directory.Exists(fileSavePath)) { Directory.CreateDirectory(fileSavePath); } //生成文件的名称 string extensionName = Path.GetExtension(fileOriginName);//获取文件的源后缀 if (string.IsNullOrEmpty(extensionName)) { throw new UserFriendlyException("文件上传的原始名称好像不对哦,没有找到文件后缀"); } string fileName = Guid.NewGuid().ToString() + extensionName;//通过uuid和原始后缀生成新的文件名 //最终生成的文件的相对路径(xxx/xxx/xx.xx) string finalyFilePath = fileSavePath + "/" + fileName; FileUploadOutputDto result = new FileUploadOutputDto(); //打开上传文件的输入流 using (Stream stream = file.OpenReadStream()) { //创建输入流的reader //var fileType = stream.GetFileType(); //文件大小 result.FileLength = stream.Length; result.FileName = fileOriginName; result.FileType = extensionName.Substring(1); result.FileUrl = absoluteFileDir + "/" + fileName; //开始保存拷贝文件 using (FileStream targetFileStream = new FileStream(finalyFilePath, FileMode.OpenOrCreate)) { await stream.CopyToAsync(targetFileStream); } } return result; } catch (Exception ex) { throw new UserFriendlyException("文件上传失败,原因" + ex.Message); } } } }
public class FileUploadOutputDto
{
public long FileLength { get; set; }
public string FileName { get; set; }
public string FileType { get; set; }
public string FileUrl { get; set; }
}
Framework.Web.Host》appSettings.json加配置,或者自己随便写也行。
using Abp.Localization; using Framework.Configuration; using Microsoft.Extensions.Configuration; using System; using System.IO; using System.Reflection; namespace Framework.Common { public class Utils { public static ILocalizableString L(string name) { return new LocalizableString(name, FrameworkConsts.LocalizationSourceName); } /// <summary> /// 获取物理路径 /// </summary> /// <param name="seedFile">/floder1/floder2/</param> /// <returns></returns> public static string MapPath(string seedFile) { var absolutePath = new Uri(Assembly.GetExecutingAssembly().Location).AbsolutePath.Replace("/bin/Debug", "").Replace("net5.0", "");//CodeBase var directoryName = Path.GetDirectoryName(absolutePath); var path = directoryName + seedFile.Replace('/', '\\'); return path; } private static IConfigurationRoot _appConfiguration = AppConfigurations.Get(System.Environment.CurrentDirectory); //用法1(有嵌套):GetAppSetting("Authentication", "JwtBearer:SecurityKey") //用法2:GetAppSetting("App", "ServerRootAddress") public static string GetAppSetting(string section, string key) { return _appConfiguration.GetSection(section)[key]; } public static string GetConnectionString(string key) { return _appConfiguration.GetConnectionString(key); } } }
完了之后运行,接口地址就是 /api/FileCommon/UploadFile
abp ViewUI使用Upload组件的代码,一些回调函数我没做,可以自己看着文档写一下。
<Upload name="coverImg" ref="upload" :show-upload-list="false" :format="['jpg', 'jpeg', 'png']" :max-size="2048" multiple type="drag" action="http://localhost:21021/api/FileCommon/UploadFile" > <Button icon="ios-cloud-upload-outline">{{ L("UploadFiles") }}</Button> </Upload>
这样就可以上传了,参考文章:建议都看一看
http://v1.iviewui.com/components/upload
https://www.cnblogs.com/yanan7890/p/12944523.html
https://blog.csdn.net/war3ismylove/article/details/98201270
https://blog.csdn.net/weixin_34114823/article/details/92453634
浙公网安备 33010602011771号