项目前端使用的是 vue + element 框架为基础, 来缩短开发的流程和时间, 谁用谁知道~!
因为前端整体界面风格参考的是ruoyi的界面, 不过有vue的基础, 代码读起来也基本不会太吃力的
以下是项目开发中遇到的问题解决, 以及代码中值得学习的片段
同时也慢慢加入了自己的一点总结反思, 只为变的更强嘻嘻
1. 富文本
rich 编辑使用的是轻量级富文本编辑器 Vue-Quill-Editor
1.1 安装
npm install vue-quill-editor --save
1.2 在 main.js 中将组件引入到全局
// 引入富文本编辑器
import VueQuillEditor from 'vue-quill-editor'
// require styles 富文本编辑器对应的样式
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
// // 注册富文本编辑器组件为全局组件
Vue.use(VueQuillEditor)
1.3 在 vue 中使用
该例子已经将富文本中图片上传优化为上传到图片服务器上, 原本为 base64 格式存储在字段中
熟悉之后, 如果各个模块使用较多, 可以将富文本封装为一个新的组件 components, 便于复用
代码:
html片段
<el-form-item label="内容" prop="content">
<div class="edit_container">
<quill-editor v-model="form.content"
class="quill-editor ql-editor"
ref="myQuillEditor"
:options="editorOption"
@blur="onEditorBlur($event)"
@focus="onEditorFocus($event)"
@change="onEditorChange($event)" />
</div>
<!-- 图片上传组件辅助-->
<el-upload
class="avatar-uploader"
:action="uploadUrl"
name="file"
:show-file-list="false"
list-type="picture"
:multiple="false"
:on-success="quillUploadSuccess"
:on-error="quillUploadError"
:before-upload="quillBeforeUpload" />
</el-form-item>
JavaScript片段
data() {
return {
uploadUrl: process.env.VUE_APP_BASE_API + "/api/file/upload", //上传路径
//富文本编辑
editorOption: {
placeholder: '请在此输入内容...',
modules: {
imageDrop: true, //开启拖拽,
toolbar: {
container: [
['bold', 'italic', 'underline', 'strike'], //加粗,斜体,下划线,删除线
['blockquote', 'code-block'], //引用,代码块
[{ 'header': 1 }, { 'header': 2 }], // 标题,键值对的形式;1、2表示字体大小
[{ 'list': 'ordered'}, { 'list': 'bullet' }], //列表
[{ 'script': 'sub'}, { 'script': 'super' }], // 上下标
[{ 'indent': '-1'}, { 'indent': '+1' }], // 缩进
[{ 'direction': 'rtl' }], // 文本方向
[{ 'size': ['small', false, 'large', 'huge'] }], // 字体大小
[{ 'header': [1, 2, 3, 4, 5, 6, false] }], //几级标题
[{ 'color': [] }, { 'background': [] }], // 字体颜色,字体背景颜色
[{ 'font': [] }], //字体
[{ 'align': [] }], //对齐方式
['clean'], //清除字体样式
['image'] //上传图片、上传视频
],
// container: "#toolbar",
handlers: {
image: function(value) {
if (value) {
// 触发input框选择图片文件
document.querySelector(".avatar-uploader input").click();
} else {
this.quill.format("image", false);
}
},
}
}
},
theme:'snow',
},
}
}
methods: {
//富文本编辑器---------------
onEditorReady(editor) {}, // 准备编辑器
onEditorBlur() {}, // 失去焦点事件
onEditorFocus(val, editor) {
// console.log(val); // 富文本获得焦点时的内容
// editor.enable(false); // 在获取焦点的时候禁用
}, // 获得焦点事件
onEditorChange() {}, // 内容改变事件
saveQuillEditorValue: function(event) {
alert(this.quillEidtorValue);
}, //-----------------------
// 富文本图片上传前 ============================================富文本
quillBeforeUpload() {
// 显示loading动画
this.quillUpdateImg = true;
},
quillUploadSuccess(res, file) {
// res为图片服务器返回的数据
// 获取富文本组件实例
let quill = this.$refs.myQuillEditor.quill;
// 如果上传成功
if (res.code == 200) {
// 获取光标所在位置
let length = quill.getSelection().index;
// 插入图片 res.url为服务器返回的图片地址
quill.insertEmbed(length, "image", res.data);
// 调整光标到最后
quill.setSelection(length + 1);
} else {
this.$message.error("图片插入失败");
}
// loading动画消失
this.quillUpdateImg = false;
},
// 富文本图片上传失败
quillUploadError() {
// loading动画消失
this.quillUpdateImg = false;
this.$message.error("图片插入失败");
}, // ======================================================
}
css片段
p {
margin: 10px;
}
.ql-container{
height: 300px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
content: "14px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before {
content: "10px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before {
content: "18px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before {
content: "32px";
}
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
content: "文本";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
content: "标题1";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
content: "标题2";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
content: "标题3";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
content: "标题4";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
content: "标题5";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
content: "标题6";
}
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
content: "标准字体";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before {
content: "衬线字体";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before {
content: "等宽字体";
}
2. 数据校验
大多数时刻前端都要对用户输入的内容进行判断, 输入是否是合法的, 即数据校验
例如不能输入为空, 手机号必须11位等
复杂的内容校验推荐使用正则表达式判断
<!-- 为表单添加校验规则 -->
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
js 片段
// 表单校验
rules: {
type: [
{ required: true, message: "类型不能为空", trigger: "blur" }
],
unitPrice: [
{required: true, message: "价格不能为空", trigger: "blur"},
{ pattern: /^[0-9]+([.]{1}[0-9]+){0,1}$/, message: '价格只能为数字或小数',
trigger: 'blur' }
],
taobaoUrl: [
{ pattern: /^https{0,1}:\/\/[^\n\r\s]{3,}$/, message: '链接格式错误',
trigger: 'blur' }
],
// ...
}
3. 图片上传
使用的基础为 element-ui 的上传组件来上传图片
代码:
<el-upload
:action="uploadUrl"
list-type="picture-card"
:class="{hide: hideImageUploadEdit}"
:on-change="handleEditChange"
:on-preview="handlePictureCardPreview"
:before-upload="beforeAvatarUpload"
:on-success="handleSuccess"
:on-remove="handleRemove"
:limit="limit"
:file-list="picList">
<i class="el-icon-plus"></i>
</el-upload>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="dialogImageUrl" alt="">
</el-dialog>
JavaScript片段
// 图片对象数组
picList: [],
limit: 1,
hideImageUploadEdit: false, // 上传限制 隐藏上传按钮
dialogImageUrl: '',
dialogVisible: false,
uploadUrl: process.env.VUE_APP_BASE_API + "/api/file/upload",
// 加文件、上传成功和上传失败时都会被调用 --------------------------- 图片上传
handleEditChange(file, fileList) {
this.hideImageUploadEdit = fileList.length >= this.limit;
// console.log("this.fileList:", this.fileList);
// console.log("this.hideUploadEdit:", this.hideImageUploadEdit);
},
// 图片上传成功
handleSuccess(response, file, fileList){
file.url= response.data;
this.picList = fileList;
},
// 图片移除事件
handleRemove(file, fileList) {
//console.log(file, fileList);
this.picList = fileList;
this.hideImageUploadEdit = fileList.length >= this.limit;
},
// 点击图片事件
handlePictureCardPreview(file) {
this.dialogImageUrl = file.url;
this.dialogVisible = true;
},
//上传前校验图片
beforeAvatarUpload(file) {
const isJPG = file.type === "image/jpeg" || file.type === "image/png";
// const isLt10M = file.size / 1024 / 1024 < 10;
if (!isJPG) {
this.$message.error('上传图片只能是 JPG 或 PNG 格式!');
}
/*if (!isLt10M) {
this.$message.error('上传图片大小不能超过 10MB!');
}*/
return isJPG ;
}, // ------------------------------------------------------------------
css片段
这里是为了添加图片的上传限制, 超过指定数量隐藏 +
号
.hide .el-upload--picture-card {
display: none;
}
.el-upload-list__item {
transition: none !important;
}
4. select 选择器
<el-select v-model="form.type" placeholder="请选择类型">
<el-option label="上方金刚区" value="1" />
<el-option label="下方广告区" value="2" />
</el-select>
<!-- 可搜索 可清空 -->
<el-select v-model="form.productId" placeholder="请选择关联商品" filterable clearable>
<el-option
v-for="item in mallProductList"
:key="item.id"
:label="item.name"
:value="item.id" />
</el-select>
字典数据获取
ruoyi 框架提供灵活的字典功能, 例如保存性别键值
男 1; 女 0
sortOptions: [], // 指标类别
// 获取Options
getSort(){
this.getDicts("t_review_item_type").then(response => {
this.sortOptions = response.data;
console.log(this.sortOptions)
});
},
5. api 调用
request 方法
前端index调用封装的 js 方法 — request 方法统一放在 src/api/(大模块名)/xxx.js
下
优点: 可以传若干个自定义的参数
import request from '@/utils/request'
// 后端采用方法体接收 `@RequestBody`
// 资讯状态修改
// 根据id修改数据的状态(state)
export function changeNewStatus(id, state) {
const data = {
id,
state
};
return request({
url: '/module/waterBeautyEconomy/changeStatus',
method: 'put',
data: data
})
}
switch 启用
touch 开关, 弹出确认框(包含确认和取消)
点击确认, 调用接口成功后, 弹出消息 “启用成功”
点击取消, 则复原开关状态
代码:
html 片段
<el-switch
v-model="scope.row.status"
:active-value="1"
:inactive-value="0"
@change="handleStatusChange(scope.row)" />
switch 开关使用的是 element 组件
js 片段
// 状态修改
handleStatusChange(row) {
let text = row.status == 1 ? "启用" : "停用";
this.$confirm(
'确认要"' + text + '" "' + row.questionStem + '" 试题吗?',
"警告",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
)
.then(function () {
return changeNewStatus(row.id, row.status);
})
.then(() => {
this.msgSuccess(text + "成功");
})
.catch(function () {
row.status = row.status == 0 ? 1 : 0;
});
},
list 获取
listWaterBeautyEconomy 这里是调用了/api 下的 js 方法
/** 查询水美经济列表 */
getList() {
this.loading = true;
listWaterBeautyEconomy(this.queryParams).then(response => {
this.waterBeautyEconomyList = response.rows;
this.total = response.total;
this.loading = false;
});
},
弹出确认框
先为按钮添加特定事件 ->
点击特定按钮弹出 confirm 框, 点击确定则调用接口
js 片段
/** 退款申请 */
handlePut(row) {
const id = row.id;
// console.log(id);
this.$confirm('是否确认退款申请, 编号:' + row.number , "退款申请", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "info"
}).then(function() {
return refund(id);
}).then(() => {
this.getList();
this.msgSuccess("退款申请成功");
});
},
add update 方法
reqeust 片段
// 新增试题
export function addQuestion(data) {
return request({
url: '/driver/question',
method: 'post',
data: data
})
}
// 修改试题
export function updateQuestion(data) {
return request({
url: '/driver/question',
method: 'put',
data: data
})
}
js 片段
/** 提交按钮 */
submitForm: function() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.id != undefined) {
updateQuestion(this.form).then(response => {
if (response.code === 200) {
this.msgSuccess("修改成功");
this.open = false;
this.getList();
}
});
} else {
addQuestion(this.form).then(response => {
if (response.code === 200) {
this.msgSuccess("新增成功");
this.open = false;
this.getList();
}
});
}
} // if(v..)
});
},
delete 方法
request 片段
// Rest风格
// 删除考评任务
export function delQuarterlyReviewGroup(id) {
return request({
url: '/module/quarterlyReviewGroup/' + id,
method: 'delete'
})
}
js 片段
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids;
this.$confirm('是否确认删除试题编号为"' + ids + '"的数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function() {
return delQuestion(ids);
}).then(() => {
this.getList();
this.msgSuccess("删除成功");
}).catch(function() {});
},
js 片段
updateQuarterlyReviewGroup(this.form).then(response => {
if (response.code === 200) {
this.msgSuccess("修改成功");
this.open = false;
this.getList();
}
});
6. 查询参数
list 页面可以提供查询筛选的功能
参数:
// 查询参数
queryParams: {
orderByColumn: "create_time",
isAsc: "desc",
pageNum: 1,
pageSize: 10,
title: undefined,
},
7. Table 表格
二级 table
例如点击考评员, 进入新的 table 页面, 选择是哪个考评员
代码:
html 片段
<!-- 选择考评人对话框 -->
<el-dialog title="选择考评员" :visible.sync="examinerOpen" width="900px" append-to-body>
<el-form :model="examinerQueryParams" ref="examinerQueryForm"
:inline="true" label-width="68px">
<el-form-item label="状态" prop="state">
<el-select v-model="examinerQueryParams.state" placeholder="请选择状态">
<el-option value="leisure" label="空闲"/>
<el-option value="appoint" label="已指定"/>
<el-option value="busy" label="考评中"/>
</el-select>
<!--<el-input
v-model="queryParams.number"
placeholder="请输入编号"
clearable
size="small"
@keyup.enter.native="handleQuery"
/>-->
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini"
@click="examinerHandleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini"
@click="examinerResetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-table v-loading="examinerLoading" :data="examinerList"
@selection-change="examinerHandleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<!--<el-table-column label="id" align="center" prop="id" />-->
<el-table-column label="编号" align="center" prop="number" />
<el-table-column label="派往村" align="center" prop="ruralId">
<template slot-scope="scope">
<span v-if="scope.row.ruralId==null || scope.row.ruralId===''">-</span>
<span v-for="rural in ruralList">
<span v-if="rural.id ===scope.row.ruralId">{{rural.villageName}}</span>
</span>
</template>
</el-table-column>
<el-table-column label="姓名" align="center" prop="name" />
<el-table-column label="所在县市" align="center" prop="addressId" >
<template slot-scope="scope">
<span v-if="scope.row.addressId==null || scope.row.addressId===''">-</span>
<span v-for="rural in ruralList">
<span v-if="rural.id ===scope.row.addressId">
{{rural.cityName}}{{rural.countyName}}</span>
</span>
</template>
</el-table-column>
<el-table-column label="联系方式" align="center" prop="phone" >
<template slot-scope="scope">
<span v-if="scope.row.phone==null || scope.row.phone===''">-</span>
<span v-else>{{scope.row.phone}}</span>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="state" >
<template slot-scope="scope">
<span style="color:green" v-if="scope.row.state==='leisure'">空闲</span>
<span style="color:red" v-else-if="scope.row.state==='appoint'">
已指定</span>
<span style="color:red" v-else-if="scope.row.state==='busy'">考评中</span>
<span v-else>-</span>
</template>
</el-table-column>
</el-table>
<pagination
v-show="examinerTotal>0"
:total="examinerTotal"
:page.sync="examinerQueryParams.pageNum"
:limit.sync="examinerQueryParams.pageSize"
@pagination="getExaminerList"/>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="examinerSubmitForm">确 定</el-button>
<el-button @click="examinerCancel">取 消</el-button>
</div>
</el-dialog>
data 数据
export default {
data() {
// 弹出考评人选择
examinerOpen: false,
examinerIds: [],
// 总条数
examinerTotal: 0,
examinerLoading: true,
// 查询参数
examinerQueryParams: {
orderByColumn: "create_time",
isAsc: "desc",
pageNum: 1,
pageSize: 10,
state: undefined,
},
}
}
js 片段
// 选择考评人按钮
examinerHandle(){
this.examinerOpen = true;
//this.resetForm("queryForm");
},
// 多选框选中 触发事件
examinerHandleSelectionChange(selection) {
if (selection.length > 1) this.msgError("只能指派一个考评人");
this.examinerIds = selection.map(item => item.id);
},
// 确认按钮操作
examinerSubmitForm(){
if (this.examinerIds.length > 1) {
this.msgError("只能指派一个考评人");
return;
}
this.form.examinerId = this.examinerIds[0];
this.examinerOpen = false;
this.examinerResetQuery();
console.log(this.form);
},
// 取消按钮操作
examinerCancel(){
this.examinerOpen = false;
// this.reset(); // 重置form this.resetForm("form");
},
/** 搜索按钮操作 */
examinerHandleQuery() {
this.examinerQueryParams.pageNum = 1;
this.getExaminerList();
},
/** 重置按钮操作 */
examinerResetQuery() {
this.resetForm("examinerQueryForm"); // 重置查询表单
this.examinerHandleQuery(); // 搜索一次
},
序号显示
在数据前的第一列添加一个序号显示 1 2 3 …
<el-table-column type="index" label="序号" align="center" :index="indexMethod"/>
例如第一页序号为 1-10, 第二页序号为 11-20
/** 连续序号 */
indexMethod(index) {
return (index+1) + (this.queryParams.pageNum-1)*this.queryParams.pageSize;
},