- 官方文档
- 前端部署到tomat服务器,刷新报404
- tomcat配置虚拟路径
- 修改浏览器窗口标题
- element-ui
- 树形结构问题
- 新增菜单图标
- treeselect 禁用分支节点
- 点击空白处禁止关闭弹窗
- show-overflow-tooltip
- el-select 配置 宽度
- Drawer 滑动
- 列显示图片
- This may cause an update error
- 表单增加图片
- 全局配置EL-table属性
- 表格 行合并
- disabled 禁用
- css cursor(鼠标悬浮禁用)
- 单次定时任务推送
- 抽屉式+标签页
- 子组件不能修改父组件的值
- 父组件修改子组件data值
- 实现表单里面的 lable 标签对齐
- watch 用法
- 增加回收站
- 执行重置方法报错
- el-select 远程搜索
- 固定表格最右一栏
- 去掉 url 中的
- 应用部署在子路径
- springboot配置文件上传大小
- el-upload 上传替换上一个文件
- echarts 图表引入
- 实现第三方登录
- 处理多图上传问题,闪屏
- @EqualsAndHashCode()注解详解
- 上传图片报 403 ,拦截权限问题
- 分页注意
- nginx 配置静态资源目录,反向代理服务
- 列表数据图片预览拼接全路径
- 手动关闭当前标签页
- 修改表头背景颜色
- 修改侧边栏主题
- table展开后搜索问题
- 富文本上传图片
- 引入cards,卡片式统计
- 行拖拽
- 图片按顺序显示
- 前端接口请求超时配置
- 设置el-table行高
- 全局配置el-table字体大小
- 动态配置表格
- 处理图片富文本中图片宽度最大100%
官方文档
http://doc.ruoyi.vip/ruoyi-vue/
前端部署到tomat服务器,刷新报404
添加一个 WEB-INF 文件夹,文件夹里面新建文件 web.xml,内容如下
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1" metadata-complete="true">
<display-name>Router for Tomcat</display-name>
<error-page>
<error-code>404</error-code>
<location>/index.html</location>
</error-page>
</web-app>
tomcat配置虚拟路径
修改server.xml
,Host
节点下添加
<Context docBase="" path="/" reloadable="true" source=""/>
修改浏览器窗口标题
修改位置
element-ui
标签 | 描述 | |
---|---|---|
el-form | form 表单 | |
el-form-item | form 表单项 | |
el-table | 表格 | |
el-table-column | 表格列 | |
el-dialog | 对话框 | |
el-button | 按钮 |
树形结构问题
原因:列是 居中的 ,去掉即可
新增菜单图标
如果你是从 iconfont (opens new window)下载的图标,记得使用如 Sketch 等工具规范一下图标的大小问题,不然可能会造成项目中的图标大小尺寸不统一的问题。 本项目中使用的图标都是 128*128 大小规格的。
将下载的图标 移动到 src/assets/icons/svg
这个目录
treeselect 禁用分支节点
只能点击最子节点
点击空白处禁止关闭弹窗
之前的效果:
main.js
在 Vue.use(Element)
上面添加以下内容
Element.Dialog.props.closeOnClickModal.default = false
禁用后的效果:
show-overflow-tooltip
当内容长度超过了指定长度,会隐藏,然后鼠标悬浮在内容上面,会悬浮显示全部内容
el-select 配置 宽度
在 <el-select>
加上 :style="{width: '100%'}"
<el-row>
<el-col :span="24">
<el-form-item label="角色">
<el-select v-model="form.roleIds" multiple :style="{width: '100%'}" placeholder="请选择" >
<el-option
v-for="item in roleOptions"
:key="item.roleId"
:label="item.roleName"
:value="item.roleId"
></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
Drawer 滑动
.el-drawer.rtl {
overflow: scroll
}
列显示图片
<el-form-item label="照片">
<span v-for="fit in previewImgFormat(props.row)" :key="fit">
<el-image
style="width: 100px; height: 100px ;margin: 5px"
:src="fit"
:preview-src-list="previewImgFormat(props.row)"
></el-image>
</span>
</el-form-item>
export default {
methods: {
previewImgFormat(row, column){
var imgUrl = row.imgUrls;
return imgUrl.split(",");
},
}
}
This may cause an update error
是v-for循环里,key值可能重复了,所以会报这个错
表单增加图片
vue代码如下
<el-row>
<el-col :span="12">
<el-form-item label="封面图片 :" label-width="90px" prop="imgUrl" >
<image-upload-local-vue :parentImage="form.imgUrl" @oneImage="oneImageFun">
</image-upload-local-vue>
</el-form-item>
</el-col>
</el-row>
javascript 代码如下
// 引入 imageUploadLocalVue 组件
import imageUploadLocalVue from "@/components/upload/image-upload-local.vue";
export default {
name: "Branch",
components: {
imageUploadLocalVue
},
methods: {
/** 单图上传 */
oneImageFun(val) {
this.form.imgUrl = val;
},
}
}
全局配置EL-table属性
main.js
添加以下内容
// 带有斑马纹
Element.Table.props.stripe = {
default:true,
type:Boolean
}
// 带有边框
Element.Table.props.border = {
default:true,
type:Boolean
}
Vue.use(Element, {
size: Cookies.get('size') || 'medium' // set element-ui default size
})
最后效果:
表格 行合并
<el-table v-loading="loading" :data="quesPartItemList" :span-method="objectSpanMethod" stripe border>
<el-table-column type="selection" width="55" align="center" fixed="left" />
<el-table-column label="检查部位" align="center" prop="partTypeId" :formatter="partTypeFormat" show-overflow-tooltip/>
</el-table>
<script>
export default {
name: "QuesPartItem",
methods: {
/** 查询问卷检查项列表 */
getList() {
this.loading = true;
listQuesPartItem(this.queryParams).then(response => {
this.quesPartItemList = response.rows;
this.total = response.total;
this.loading = false;
this.getListDataForRowAndColumn(this.quesPartItemList);
});
},
/**
* 处理表格数据
*/
getListDataForRowAndColumn(data){
let self = this;
self.rowAndColumn = [];
self.rowRoomColumn = [];
for (var i = 0; i < data.length; i++) {
if (i === 0) {
// 如果是第一条记录(即索引是0的时候),向数组中加入1
self.rowAndColumn.push(1);
self.pos = 0;
self.rowRoomColumn.push(1);
self.posT = 0;
} else {
if (data[i].partTypeId === data[i - 1].partTypeId) {
// 如果storeName相等就累加,并且push 0
self.rowAndColumn[self.pos] += 1
self.rowAndColumn.push(0)
if (data[i].partTypeId === data[i - 1].partTypeId) {
// 如果roomName相等就累加,并且push 0
self.rowRoomColumn[self.posT] += 1
self.rowRoomColumn.push(0)
}else{
self.rowRoomColumn.push(1)
self.posT = i
}
} else {
// 不相等push 1
self.rowAndColumn.push(1)
self.pos = i;
self.rowRoomColumn.push(1)
self.posT = i
}
}
}
console.log(this.rowAndColumn);
console.log(this.rowRoomColumn);
},
objectSpanMethod({ row, column, rowIndex, columnIndex }) {
let self = this;
if (columnIndex === 0) {
if (self.rowAndColumn[rowIndex]) {
let rowNum = self.rowAndColumn[rowIndex];
return {
rowspan: rowNum,
colspan: rowNum > 0 ? 1 : 0
}
}
return {
rowspan: 0,
colspan: 0
}
}
if (columnIndex === 1) {
if (self.rowRoomColumn[rowIndex]) {
let roomNum = self.rowRoomColumn[rowIndex];
return {
rowspan: roomNum,
colspan: roomNum > 0 ? 1 : 0
}
}
return {
rowspan: 0,
colspan: 0
}
}
},
}
}
</script>
disabled 禁用
内容字体不明显
index.scss
.el-input.is-disabled .el-input__inner {
color: #333!important;
cursor:default; // 鼠标悬浮光标样式
}
.el-range-editor.is-disabled input {
color: #333!important;
cursor:default;
}
.el-textarea.is-disabled .el-textarea__inner {
color: #333!important;
cursor:default;
}
css cursor(鼠标悬浮禁用)
url | 需使用的自定义光标的 URL。注释:请在此列表的末端始终定义一种普通的光标,以防没有由 URL 定义的可用光标。 |
---|---|
default | 默认光标(通常是一个箭头) |
auto | 默认。浏览器设置的光标。 |
crosshair | 光标呈现为十字线。 |
pointer | 光标呈现为指示链接的指针(一只手) |
move | 此光标指示某对象可被移动。 |
e-resize | 此光标指示矩形框的边缘可被向右(东)移动。 |
ne-resize | 此光标指示矩形框的边缘可被向上及向右移动(北/东)。 |
nw-resize | 此光标指示矩形框的边缘可被向上及向左移动(北/西)。 |
n-resize | 此光标指示矩形框的边缘可被向上(北)移动。 |
se-resize | 此光标指示矩形框的边缘可被向下及向右移动(南/东)。 |
sw-resize | 此光标指示矩形框的边缘可被向下及向左移动(南/西)。 |
s-resize | 此光标指示矩形框的边缘可被向下移动(南)。 |
w-resize | 此光标指示矩形框的边缘可被向左移动(西)。 |
text | 此光标指示文本。 |
wait | 此光标指示程序正忙(通常是一只表或沙漏)。 |
help | 此光标指示可用的帮助(通常是一个问号或一个气球)。 |
单次定时任务推送
如何使用?
SysJob sysJob = new SysJob();
// 是否并发执行(0允许 1禁止)
sysJob.setConcurrent("1");
Integer userId = taskPushRecord.getUserId();
userId = userId == null ? 0 : userId;
sysJob.setInvokeTarget("dingNoticeTask.sendDingNotice(" + pushRecordId
+ "," + 1 + ")");
sysJob.setStartAt(DateUtil.parseTimeToday(releaseRemindTime));
sysJob.setJobGroup("releasePushRecord");
sysJob.setJobId(Long.valueOf(pushRecordId));
sysJob.setJobName("发布任务提醒");
// 任务状态(0正常 1暂停)
sysJob.setStatus("0");
OnceScheduleUtils.createScheduleJob(scheduler, sysJob);
OnceScheduleUtils.java
代码如下
package com.hn.common.utils.job;
import cn.hutool.json.JSONUtil;
import com.hn.common.constant.ScheduleConstants;
import com.hn.common.exception.job.TaskException;
import com.hn.project.monitor.domain.SysJob;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
/**
* 定时单次任务工具类
*/
@Slf4j
public class OnceScheduleUtils {
/**
* 得到quartz任务类
*
* @param sysJob 执行计划
* @return 具体执行任务类
*/
private static Class<? extends Job> getQuartzJobClass(SysJob sysJob) {
boolean isConcurrent = "0".equals(sysJob.getConcurrent());
return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;
}
/**
* 构建任务触发对象
*/
public static TriggerKey getTriggerKey(Long jobId, String jobGroup) {
return TriggerKey.triggerKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
}
/**
* 构建任务键对象
*/
public static JobKey getJobKey(Long jobId, String jobGroup) {
return JobKey.jobKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
}
/**
* 创建定时任务
*/
public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException {
log.info("############ 创建定时任务:{}", JSONUtil.toJsonStr(job));
Class<? extends Job> jobClass = getQuartzJobClass(job);
// 构建job信息
Long jobId = job.getJobId();
String jobGroup = job.getJobGroup();
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build();
// 只需要执行一次
SimpleScheduleBuilder simpleScheduleBuilder = SimpleScheduleBuilder.simpleSchedule();
// 按新的cronExpression表达式构建一个新的trigger
SimpleTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity(getTriggerKey(jobId, jobGroup))
.startAt(job.getStartAt())
.withSchedule(simpleScheduleBuilder).build();
// 放入参数,运行时的方法可以获取
jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job);
// 判断是否存在
if (scheduler.checkExists(getJobKey(jobId, jobGroup))) {
log.info("############ 移除定时任务:{}", JSONUtil.toJsonStr(job));
// 防止创建时存在数据问题 先移除,然后在执行创建操作
scheduler.deleteJob(getJobKey(jobId, jobGroup));
}
scheduler.scheduleJob(jobDetail, trigger);
// 暂停任务
if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue())) {
scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
}
}
}
生成的任务都在 qrtz_triggers
数据表
注意: 如果时间点在之前,会立刻推送 比如当前时间 16:00 ,你设置的是当天 8:00 推送 ,那会立刻就推送
抽屉式+标签页
<el-drawer :title="detail.title" size="50%" :visible.sync="detail.open" :direction="detail.direction">
<el-tabs tab-position="top" >
<el-tab-pane label="巡检问卷" name="second">巡检问卷</el-tab-pane>
<el-tab-pane label="任务记录" name="third">任务记录</el-tab-pane>
</el-tabs>
</el-drawer>
<style>
.el-tabs__header {
margin-left: auto;
margin-right: auto;
width: 95%;
}
</style>
子组件不能修改父组件的值
子组件代码:
<template>
<div class="top-right-btn">
<el-row>
<el-tooltip class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top">
<el-button size="mini" circle icon="el-icon-search" @click="toggleSearch()"/>
</el-tooltip>
<el-tooltip class="item" effect="dark" v-if="showGarbage" :content="showGarbageIcon? '垃圾箱' : '返回'"
placement="top">
<el-button size="medium" circle :icon="showGarbageIcon? 'el-icon-delete' : 'el-icon-back'" @click="garbage()"/>
</el-tooltip>
</el-row>
</div>
</template>
<script>
export default {
name: "RightToolbar",
data() {
return {};
},
props: {
showSearch: {
type: Boolean,
default: true,
},
showGarbage: {
type: Boolean,
default: true,
},
showGarbageIcon: {
type: Boolean,
default: true,
},
},
methods: {
//搜索
toggleSearch() {
this.$emit("update:showSearch", !this.showSearch);
},
garbage() {
var showGarbageIcon = this.showGarbageIcon;
// this.showGarbageIcon = !showGarbageIcon; //这行去掉 不能直接改变props中参数的值
this.$emit("queryTable"); //通知父组件改变。
},
},
};
</script>
父组件代码:
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
通过props传递给子组件的show,不能在子组件内部修改props中的show值。
错误信息:
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "showGarbageIcon"
正确传值方法:
this.$emit("queryTable");
父组件修改子组件data值
父组件代码如下
<!--详情-->
<details-from ref="orderDetail" :orderId="orderId"/>
// 引入组件
import detailsFrom from './orderDetail'
components: {
detailsFrom
},
data(){
orderStatusOptions:[],
orderPayTypeOptions:[]
}
method(){
updateChildData(){
// ref='orderDetail'
this.$refs.orderDetail.orderStatusOptions = this.orderStatusOptions
this.$refs.orderDetail.orderPayTypeOptions = this.orderPayTypeOptions
}
}
子组件代码如下
data() {
return {
orderStatusOptions:[],
orderPayTypeOptions:[]
}
},
实现表单里面的 lable 标签对齐
<!-- lable-width 值调大一点 -->
<el-form ref="form" :model="form" :rules="rules" label-width="120px">
</el-form>
watch 用法
watch: {
// 监听这个字段变化
showGarbageIcon: function (val) {
console.log(val)
this.queryParams.delFlag = val ? 0 : 2;
this.showDeleteIcon = val ? 'el-icon-delete':'el-icon-circle-check';
this.getList();
}
},
增加回收站
<right-toolbar :showSearch.sync="showSearch" :showGarbage="garbage.show"
:showGarbageIcon="garbage.iconShow"
@queryGarbage="listGarbage"
@queryTable="getList"></right-toolbar>
api.js
// 删除问卷检查项
export function delQuesPartItem(id, delFlag) {
let url = '/rqinspect/quesPartItem/' + id;
if (delFlag != null) {
url = url + "?delFlag=" + delFlag;
}
return request({
url: url,
method: 'delete'
})
}
vue
queryParams: {
pageNum: 1,
pageSize: 10,
delFlag: 0,
}
查询垃圾箱的js
listGarbage(val){
console.log(val)
this.garbage.iconShow = val;
this.garbage.btnIcon = val ? 'el-icon-delete':'el-icon-circle-check';
this.garbage.btnText = val ? '删除':'启用';
this.queryParams.delFlag = val ? 0 : 2;
this.getList();
},
删除js
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids;
var delFlag = this.garbage.iconShow ? 2 : 0;
var text = this.garbage.btnText;
this.$confirm('是否确认'+text+'网点任务编号为"' + ids + '"的数据项?', "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(function () {
return delTask(ids,delFlag);
}).then(() => {
this.getList();
this.msgSuccess(text+"成功");
}).catch(function () {
});
},
Controller.java
@PreAuthorize("@ss.hasPermi('rqinspect:quesPartItem:remove')")
@Log(title = "问卷检查项", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}/{delFlag}")
public AjaxResult remove(@PathVariable Integer[] ids,@PathVariable("delFlag") Integer delFlag){
return toAjax(quesPartItemService.deleteQuesPartItemByIds(ids));
}
mapper.java
public int deleteQuesByIds(@Param("ids") Integer[] ids, @Param("delFlag") Integer delFlag);
mapper.xml
<delete id="deleteQuesByIds" parameterType="String">
update sys_ques set del_flag = #{delFlag} where id in
<foreach item="id" collection="ids" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
执行重置方法报错
报错信息
TypeError: Cannot read property ‘indexOf’ of undefined at VueComponent.resetField (element-ui.common.js?5c96:23528)
正常的
错误的,path 等于 undefined
element-ui.commmon.js
resetField: function resetField() {
var _this2 = this;
this.validateState = '';
this.validateMessage = '';
var model = this.form.model;
var value = this.fieldValue; // 报undefined
var path = this.prop; // 报undefined
if (path.indexOf(':') !== -1) {
path = path.replace(/:/, '.');
}
var prop = Object(util_["getPropByPath"])(model, path, true);
this.validateDisabled = true;
if (Array.isArray(value)) {
prop.o[prop.k] = [].concat(this.initialValue);
} else {
prop.o[prop.k] = this.initialValue;
}
// reset validateDisabled after onFieldChange triggered
this.$nextTick(function () {
_this2.validateDisabled = false;
});
this.broadcast('ElTimeSelect', 'fieldReset', this.initialValue);
},
- 原因如下,在使用
validate
、resetFields
方法的情况下,prop
属性是必填的
- 解决方法如下
<!--加一个 prop='属性名' -->
<el-form-item label="角色" prop="roleIds">
el-select 远程搜索
el-select 一次性加载数据如果很多,就会很慢,我们这边改进一下。
改成远程搜索
<el-form-item label="用户" prop="userIds">
<el-select filterable remote :remote-method="remoteMethod" v-model="form.userIds" multiple :loading="selectloading" :style="{width: '100%'}" placeholder="请选择用户">
<el-option
v-for="item in userOptions"
:key="item.userId"
:label="item.userName"
:value="item.userId"
></el-option>
</el-select>
</el-form-item>
export default {
name: "Group",
data() {
return {
selectloading: true,
userOptions: []
}
},
methods:{
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const id = row.id;
getGroup(id).then(response => {
this.form = response.data;
this.form.userIds = response.userIds;
// 根据用户id 获取获取用户信息
listAllUser({'userIds':response.userIds.join(',')}).then(response=>{
this.userOptions = response.data;
});
this.open = true;
this.title = "修改分组";
});
},
// 远程搜索
remoteMethod(query){
if (query !== '') {
this.selectloading = true;
var _this = this;
setTimeout(() => {
this.selectloading = false;
listAllUser({'userName':query}).then(response=>{
_this.userOptions = response.data;
});
}, 200);
} else {
this.options = [];
}
}
}
}
固定表格最右一栏
参数 | 说明 | 类型 | 可选值 |
---|---|---|---|
fixed | 列是否固定在左侧或者右侧,true 表示固定在左侧 | string, boolean | true, left, right |
在 el-table-column
加上 fixed="right"
<el-table-column
label="操作"
align="center"
width="160"
class-name="small-padding fixed-width"
fixed="right"
>
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['system:user:edit']"
>修改
</el-button>
</template>
</el-table-column>
去掉 url 中的
nginx 配置的时候注意一下
router/index.js
改成
export default new Router({
// mode: 'history', // 去掉url中的#
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
})
hash和history两种模式的区别 https://www.jianshu.com/p/bfffb4b8c9fa
应用部署在子路径
vue.config.js
// 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。
publicPath: process.env.NODE_ENV === "production" ? "/" : "/",
springboot配置文件上传大小
错误信息如下:
Maximum upload size exceeded; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$FileSizeLimitExceededException: The field avatarfile exceeds its maximum permitted size of 10485760 bytes.
配置文件增加以下内容:
# springboot 2.x
spring:
servlet:
multipart:
# 文件最大
max-file-size: 20MB
# 设置总上传数据总大小
max-request-size: 20MB
el-upload 上传替换上一个文件
应用场景: 导入excel文件的时候用到
# 在 el-upload 加上监听文件修改的方法
:on-change="handleFileChange"
# 限制文件上传个数
:limit="2"
监听代码如下
handleFileChange(file, fileList) {
// 当多余一个的时候替换文件
if (fileList.length > 1) {
fileList.splice(0, 1);
}
},
echarts 图表引入
代码如下,更多请参考 index.vue
<el-col :span="8">
<div class="card_centre">
<div class="card_title">
<div class="item1">收入统计</div>
</div>
<!-- <div class="total_data">
<span>{{indexData.user.total}}</span>
</div>-->
<pie-chart :chart-data="pieChartData"/>
</div>
</el-col>
</el-row>
# 引入
import PieChart from './dashboard/PieChart'
实现第三方登录
创建好token之后在Authorization
携带token访问接口即可。不需要用户名和密码。会根据token查询到对应用户的权限
OapiUserGetuserinfoResponse userInfoByCode = DingUserUtil.getUserInfoByCode(code);
String userid = userInfoByCode.getUserid();
AssertUtils.notNull(userid,"登录失败,用户不存在");
SysUser sysUser = sysUserService.selectUserByDingId(userid);
if(sysUser == null){
throw new BaseException(userid,"登录失败,用户不存在");
}
AsyncManager.me().execute(AsyncFactory.recordLogininfor(sysUser.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
LoginUser loginUser = new LoginUser(sysUser,permissionService.getMenuPermission(sysUser));
// 生成token
return tokenService.createToken(loginUser);
处理多图上传问题,闪屏
去掉watch,绑定唯一key值
<el-form-item label="相册" prop="photoAlbum">
<upload-image-list :key="form.userId" v-model="form.photoAlbum"/>
</el-form-item>
UploadImageList
代码如下
<template>
<div class="component-upload-image">
<el-upload
:action="uploadImgUrl"
:before-upload="handleBeforeUpload"
:data="data"
:file-list="fullUrls"
:http-request="handleRequest"
:limit="limit"
:on-error="handleUploadError"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
:on-success="handleUploadSuccess"
class="uploadComponent"
list-type="picture-card"
name="file"
style="display: inline-block; vertical-align: top"
>
<i class="el-icon-plus avatar-uploader-icon"></i>
<el-dialog :visible.sync="dialogVisible" append-to-body width="500px">
<img :src="dialogImageUrl" width="100%">
</el-dialog>
</el-upload>
</div>
</template>
<script>
import { getMinioToken } from '@/api/oss'
import { createUploadPath } from '@/utils/index'
import axios from 'axios'
export default {
components: {},
data() {
return {
dialogImageUrl: '',
dialogVisible: false,
//process.env.VUE_APP_BASE_API + "/common/upload",
uploadImgUrl: '', // 上传的图片服务器地址
downloadImgUrl: 'http://8.136.97.240:9000/gongchanjuan/', // 上传的图片服务器地址
data: {
key: '', //图片名字处理,
},
method: 'put',
fullUrls: []
}
},
props: {
limit: {
type: Number,
default: 3
},
value: {
type: String,
default: ''
}
},
// watch: {
// value: function(val) {
// console.log(val,"val");
// console.log(this.value,"this.value");
// this.split(val)
// }
// },
created() {
this.split(this.value)
},
methods: {
handleRequest(params) {
axios({
url: this.uploadImgUrl,
method: this.method,
headers: {
'Content-Type': params.file.type
},
data: params.file
}).then(() => {
this.handleUploadSuccess()
}).catch(() => {
this.handleUploadError()
})
},
getQiniuToken() {
// getToken(this.form).then(response => {
// if (response.code === 200) {
// this.data.token = response.data;
// } else {
// this.msgError(response.msg);
// }
// });
},
async getMinioToken(fileName) {
await getMinioToken(fileName).then(response => {
if (response.code === 200) {
this.uploadImgUrl = response.data
console.log(this.uploadImgUrl)
} else {
this.msgError(response.msg)
}
})
},
split(str) {
console.log("### split")
this.fullUrls = []
if (str == null) return
var urlArr = str.split(',')
if (this.limit > 0) {
urlArr.slice(0, this.limit)
}
for (let index in urlArr) {
var url = urlArr[index]
if (url != null && url !== '') {
this.fullUrls.push({ url: this.downloadImgUrl + url, fileName: url })
}
}
},
merge(arr) {
if (arr == null && arr.length == 0) return ''
let str = ''
for (let i in arr) {
var fileName = arr[i].fileName
str += fileName + ','
}
if (str.length > 0) {
str = str.substr(0, str.length - 1)
}
return str
},
handleUploadSuccess() {
this.fullUrls.push({ url: this.downloadImgUrl + this.data.key, fileName: this.data.key })
console.log(this.fullUrls)
var str = this.merge(this.fullUrls)
console.log(str)
this.$emit('input', str)
this.loading.close()
},
async handleBeforeUpload(file) {
console.log(file)
var index1 = file.name.lastIndexOf('.')
var index2 = file.name.length
var suffix = file.name.substring(index1 + 1, index2)
this.data.key = `${createUploadPath()}.${suffix}`
console.log(this.data.key)
await this.getMinioToken(this.data.key)
this.loading = this.$loading({
lock: true,
text: '上传中',
background: 'rgba(0, 0, 0, 0.7)'
})
},
handleRemove(val) {
var uid = val.uid
var index = this.fullUrls.findIndex(x => x.uid === uid)
this.fullUrls.splice(index, 1)
var str = this.merge(this.fullUrls)
this.$emit('input', str)
},
handleUploadError() {
this.$message({
type: 'error',
message: '上传失败'
})
this.loading.close()
},
handlePictureCardPreview(file) {
this.dialogImageUrl = file.url
this.dialogVisible = true
}
}
}
</script>
<style lang="scss" scoped>
.avatar {
width: 100%;
height: 100%;
}
//在样式把这个is-ready给隐藏掉就好了,就是这么Easy
/deep/.el-upload-list__item.is-ready {
display: none;
}
</style>
@EqualsAndHashCode()注解详解
原文中提到的大致有以下几点:
- 此注解会生成equals(Object other) 和 hashCode()方法。
- 它默认使用非静态,非瞬态的属性
- 可通过参数exclude排除一些属性
- 可通过参数of指定仅使用哪些属性
- 它默认仅使用该类中定义的属性且不调用父类的方法
- 可通过callSuper=true解决上一点问题。让其生成的方法中调用父类的方法。
另:@Data相当于@Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode这5个注解的合集。
通过官方文档,可以得知,当使用@Data注解时,则有了@EqualsAndHashCode注解,那么就会在此类中存在equals(Object other) 和 hashCode()方法,且不会使用父类的属性,这就导致了可能的问题。
比如,有多个类有相同的部分属性,把它们定义到父类中,恰好id(数据库主键)也在父类中,那么就会存在部分对象在比较时,它们并不相等,却因为lombok自动生成的equals(Object other) 和 hashCode()方法判定为相等,从而导致出错。
@EqualsAndHashCode(callSuper = true),那就是用自己的属性和从父类继承的属性 来生成hashcode,所以结果为false;
修复此问题的方法很简单:
- 使用@Getter @Setter @ToString代替@Data并且自定义equals(Object other) 和 hashCode()方法,比如有些类只需要判断主键id是否相等即足矣。
- 或者使用在使用@Data时同时加上@EqualsAndHashCode(callSuper=true)注解。
来源 https://blog.csdn.net/c851204293/article/details/96989512
上传图片报 403 ,拦截权限问题
拦截权限问题
// 使用 permitAll() 方法所有人都能访问,包括带上 token 访问
.antMatchers("/admins/**").permitAll()
// 使用 anonymous() 所有人都能访问,但是带上 token 访问后会报错
.antMatchers("/admins/**").anonymous()
分页注意
分页必须要在查询列表的上一行代码
startPage();
// 查询网点列表
List<Integer> branchIds = userService.listBranchIds();
// 查询网点下的任务列表
List<Task> tasks = taskService.listTaskIds();
这时候就会出现 userService.listBranchIds()
分页了
真正需要分页的 taskService.listTaskIds()
没有分页
正确的写法如下:
// 查询网点列表
List<Integer> branchIds = userService.listBranchIds();
startPage();
// 查询网点下的任务列表
List<Task> tasks = taskService.listTaskIds();
nginx 配置静态资源目录,反向代理服务
server
{
listen 80;
server_name xxx.com;
index index.php index.html index.htm default.php default.htm default.html;
root /www/wwwroot/xxx.com/;
location /
{
// 如果访问不到静态资源,就访问反向代理服务
if ( !-e $request_filename)
{
proxy_pass http://127.0.0.1:8130;
}
}
}
列表数据图片预览拼接全路径
引用代码如下:
<el-table-column align="center" label="品牌LOGO" prop="logo" width="100">
<template slot-scope="scope">
<image-view imageStyle="width:20px;height:20px" v-model="scope.row.logo" />
</template>
</el-table-column>
imageView 代码如下
<template>
<div class="component-image-view">
<span v-if="fullUrls.length===0" style="color:#c0c4cc;">
暂无
</span>
<el-image v-for="(item,index) in fullUrls"
:style="imageStyle"
:src="item"
:preview-src-list="fullUrls" slot="reference">
</el-image>
</div>
</template>
<script>
export default {
data() {
return {
source:{
//本地
local:{
downloadImgUrl: process.env.VUE_APP_BASE_API + "/upload",
},
//七牛
qiniu:{
downloadImgUrl: "http://qnimage.fangfujie.cn/",
},
//minio
minio:{
downloadImgUrl: "http://8.136.97.240:9000/gongchanjuan/",
},
// 阿里云
ali:{
downloadImgUrl: "http://youlishop.oss-cn-beijing.aliyuncs.com/",
},
},
fullUrls:[],
};
},
props: {
value: {
type: String,
default: "",
},
imageStyle: {
type: String,
default: "width:100px;height:100px;margin-right:4px",
},
limit:{
type:Number,
default:-1,
},
to:{
type: String,
// 默认访问图片根地址
default: "ali",
},
},
watch: {
value:function(val){
this.value = val;
this.init();
},
imageStyle:function (val) {
this.style = val;
this.init();
},
limit:function (val) {
this.limit = val;
this.init();
},
to:function (val) {
this.to = val;
this.init();
}
},
created() {
this.init();
},
methods: {
init(){
this.fullUrls = [];
if (this.value==null)return
var urlArr = this.value.split(",");
if (this.limit>0){
urlArr = urlArr.slice(0,this.limit)
}
for (let index in urlArr){
var url = urlArr[index];
if (url!=null && url!==''){
this.fullUrls.push(this.source[this.to].downloadImgUrl+url);
}
}
}
},
};
</script>
<style scoped lang="scss">
</style>
手动关闭当前标签页
this.$store.dispatch("tagsView/delView", this.$route);
修改表头背景颜色
修改后
.el-table__header-wrapper, .el-table__fixed-header-wrapper {
th {
word-break: break-word;
//background-color: #f8f8f9;
background-color: #ffffff;
color: #333333;
//height: 20px;
line-height: 23px;
font-weight: 500;
font-size: 12px;
}
}
路径地址: manage-ui/src/assets/styles/ruoyi.scss
修改侧边栏主题
manage-ui/src/settings.js
manage-ui/src/layout/components/Sidebar/Logo.vue
修改左上角标题颜色
manage-ui/src/layout/components/Sidebar/Logo.vue
& .sidebar-title {
display: inline-block;
margin: 0;
// color 变成黑色
color: #131313;
font-weight: 600;
line-height: 50px;
font-size: 14px;
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
vertical-align: middle;
}
注释以下代码:
manage-ui/src/layout/components/Navbar.vue
/*box-shadow: 0 1px 4px rgba(0,21,41,.08);*/
manage-ui/src/layout/components/TagsView/index.vue
/*box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);*/
manage-ui/src/assets/styles/sidebar.scss
//-webkit-box-shadow: 2px 0 6px rgba(0,21,41,.35);
//box-shadow: 2px 0 6px rgba(0,21,41,.35);
最後效果
table展开后搜索问题
/** 查询订单列表 */
getList() {
this.loading = true;
listOrder(this.queryParams).then(response => {
if (response) {
response.rows.map(item => {
item.subOrderList = [];
});
this.orderList = response.rows;
this.total = response.total;
this.loading = false;
// 搜索后置位空
this.expands = [];
}
});
},
富文本上传图片
manage-ui/src/components/Editor/index.vue
<!-- 图片上传组件辅助 -->
<el-upload
class="avatar-uploader quill-img"
:action="host"
:data="reqData"
name="file"
:show-file-list="false"
:on-success="quillImgSuccess"
:on-error="uploadError"
:before-upload="quillImgBefore"
accept='.jpg,.jpeg,.png,.gif,.jfif'
></el-upload>
import { getToken } from "@/api/system/upload.js";
data:{
host:'http://youlishop.oss-cn-beijing.aliyuncs.com/',
reqData: {
},
dir:"fuwenben",
}
methods: {
// 获取阿里云数据
async getAliyunToken(query) {
const { data } = await getToken(query)
this.reqData.OSSAccessKeyId = data.OSSAccessKeyId;
this.reqData.policy = data.policy;
this.reqData.signature = data.signature;
this.reqData.success_action_status = 200;
},
// 富文本图片上传前
async quillImgBefore(file) {
let fileType = file.type;
if(fileType === 'image/jpeg' || fileType === 'image/png' || fileType === 'image/jpg'){
// 获取上传token
var index1 = file.name.lastIndexOf(".");
var index2 = file.name.length;
var suffix = file.name.substring(index1 + 1, index2);
var timestamp = Date.parse(new Date());
this.reqData.key = this.dir+'/'+timestamp+"."+suffix;
let query = {dir:this.dir};
await this.getAliyunToken(query)
console.log(this.reqData.key);
return true;
}else {
this.$message.error('请插入图片类型文件(jpg/jpeg/png)');
return false;
}
},
quillImgSuccess(res, file) {
// res为图片服务器返回的数据
// 获取富文本组件实例
let quill = this.$refs.quillEditor.quill;
// 如果上传成功
// 获取光标所在位置
let length = quill.getSelection().index;
// 插入图片
quill.insertEmbed(length, "image", this.host + this.reqData.key);
// 调整光标到最后
quill.setSelection(length + 1);
},
引入cards,卡片式统计
页面引入
<cards-data :cardLists="cardLists"></cards-data>
import cardsData from '@/components/cards/index'
components: {
detailsFrom,
orderSend,
cardsData
},
data(){
cardLists: [
{ name: '订单数量', count: '' },
{ name: '订单金额', count: '' },
{ name: '微信支付金额', count: '' },
{ name: '余额支付金额', count: '' },
],
}
行拖拽
npm install vuedraggable
图片按顺序显示
前端接口请求超时配置
utils->request.js
设置el-table行高
<el-table v-show="total > 0" v-loading="loading" :data="dataList"
:row-style="{height:'50px'}"
style="font-size: 15px" @selection-change="handleSelectionChange">
全局配置el-table字体大小
.el-table{
font-size: 15px
}
动态配置表格
<el-table-column v-for="column in tableColumnList" :label="column.name" align="center"
>
<!-- <el-table-column min-width="45%" label="数量(袋)" align="center" :prop="'wmsNum' + column.id">-->
<!-- </el-table-column>-->
<!-- <el-table-column min-width='55%' label="重量(千克)" align="center" :prop="'wmsWeight' + column.id">-->
<!-- </el-table-column>-->
<template slot-scope="scope">
<div style="text-align: left;">{{ scope.row['wmsNum' + column.id] }}</div>
<div style="text-align: left;">{{ scope.row['wmsWeight' + column.id] }}</div>
</template>
</el-table-column>
处理图片富文本中图片宽度最大100%
<div v-html="formatContent(form.content)"></div>
// 实现rich-text富文本中图片宽度最大100%
formatContent(html){
if(!html){
return;
}
// 去掉img标签里的style、width、height属性
let content_data= html.replace(/<img[^>]*>/gi,function(match,capture){
match = match.replace(/style="[^"]+"/gi, '').replace(/style='[^']+'/gi, '');
match = match.replace(/width="[^"]+"/gi, '').replace(/width='[^']+'/gi, '');
match = match.replace(/height="[^"]+"/gi, '').replace(/height='[^']+'/gi, '');
return match;
}); // 修改所有style里的width属性为max-width:100%
content_data = content_data.replace(/style="[^"]+"/gi,function(match,capture){
match = match.replace(/width:[^;]+;/gi, 'max-width:100%;').replace(/width:[^;]+;/gi, 'max-width:100%;');
return match;
}); // 去掉<br/>标签
content_data = content_data.replace(/<br[^>]*\/>/gi, ''); // img标签添加style属性:max-width:100%;height:auto
content_data = content_data.replace(/\<img/gi, '<img style="max-width:100%;height:auto;display:block;margin:0px auto;"');
return content_data;
}