配置路由: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