IE11也不再支持document.createElement("<input type='file' name='upload'/>");

以前IE浏览器会支持这样的js代码


document.createElement("<input type='file'  name='upload'/>");,但现在我们必须这样写:

var uploadHTML = document.createElement("input");
         uploadHTML.setAttribute("type", "file");
         uploadHTML.setAttribute("name", "upload");

又如,换行这样写: uploadHTML = document.createElement("br");
        document.getElementById("files").appendChild(uploadHTML);

这是一个基于HTML、CSS和JavaScript的简单文件上传和展示页面的示例代码: HTML: 复制 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>File Upload and Display Example</title> </head> <body> <h1>File Upload and Display Example</h1> <form id="uploadForm" action="" method="POST" enctype="multipart/form-data"> <input type="file" name="fileToUpload" id="fileToUpload" /><br /> <input type="submit" value="Upload File" name="submit" /> </form> <div id="fileDisplayArea"></div> </body> </html> CSS: 复制 h1 { color: #333; font-size: 24px; font-weight: bold; margin-bottom: 20px; } #fileDisplayArea { border: 1px solid #ddd; padding: 20px; margin-top: 20px; overflow: auto; } JavaScript: 复制 window.onload = function() { document.getElementById('uploadForm').addEventListener('submit', uploadFile); } function uploadFile(event) { event.preventDefault(); var file = document.getElementById('fileToUpload').files[0]; var formData = new FormData(); formData.append('fileToUpload', file); var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { displayFile(xhr.responseText); } } xhr.open('POST', 'upload.php', true); xhr.send(formData); } function displayFile(fileText) { var fileDisplayArea = document.getElementById('fileDisplayArea'); fileDisplayArea.innerHTML = fileText; } 在这个示例中,我们使用了一个简单的HTML表单来上传文件,使用JavaScript编写了一个事件监听器函数来处理文件上传请求,并使用XMLHttpRequest对象将文件发送到服务器。一旦文件上传成功,我们使用JavaScript编写的另一个函数来将文件内容显示在页面上。此代码还可以扩展,以便同时上传和显示图片文件及其缩略图。为什么无法实现预览和下载
05-24
<!-- 员工新增/Add --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <link rel="stylesheet" href="../../layui/css/layui.css"> <link rel="stylesheet" href="../../css/diy.css"> <script src="../../js/axios.min.js"></script> <style> img { width: 200px; } .layui-upload-list{ overflow: hidden; } .layui-upload-list .multiple_block .upload_img_multiple{ height: auto; width: 100px; } .multiple_block{ position: relative; float: left; width: 100px; margin: 0 10px 10px 0; } .multiple_block .upload-img-del{ position: absolute; top: 5px; right: 5px; color: #fff; border-radius: 100%; background: #0000009c; width: 20px; height: 20px; text-align: center; line-height: 20px; cursor: pointer; } </style> </head> <body> <article class="sign_in"> <div class="warp tpl"> <div class="layui-container"> <div class="layui-row"> <form class="layui-form" action=""> <div class="form-input-box from-input-box-i"> <div class="layui-form-item"> <div class="layui-upload"> <button type="button" class="layui-btn" id="employee_users">上传头像</button> <div class="layui-upload-list"> <img class="layui-upload-img" id="employee_users_img"> <p id="demoText"></p> </div> <div style="width: 95px;"> <div class="layui-progress layui-progress-big" lay-showpercent="yes" lay-filter="employee_users"> <div class="layui-progress-bar" lay-percent=""></div> </div> </div> </div> </div> <div class="layui-form-item"> <label class="layui-form-label">账号</label> <div class="layui-input-block input-i block"> <input type="text" name="title" lay-verify="title" autocomplete="off" placeholder="请输入账号" class="layui-input" id="username"> </div> </div> <div id="password_box" class="layui-form-item"> <label class="layui-form-label">密码</label> <div class="layui-input-block input-i block"> <input type="password" name="password" placeholder="请输入密码" autocomplete="off" class="layui-input" id="password"> </div> </div> <div class="layui-form-item"> <label class="layui-form-label">昵称</label> <div class="layui-input-block input-i block"> <input type="text" name="title" lay-verify="title" autocomplete="off" placeholder="请输入昵称" class="layui-input" id="nickname"> </div> </div> <div class="layui-form-item"> <label class="layui-form-label">邮箱</label> <div class="layui-input-block input-i block"> <input type="text" name="title" lay-verify="title" autocomplete="off" placeholder="请输入邮箱" class="layui-input" id="email"> </div> </div> <div class="layui-form-item"> <label class="layui-form-label">状态</label> <div class="layui-input-block select block"> <select name="interest" lay-filter="state" id="state"> <option value=""></option> <option value="1">可用</option> <option value="2">异常</option> <option value="3">已冻结</option> <option value="4">已注销</option> </select> </div> </div> <div class="layui-form-item unique" id="employee_name_box"> <label class="layui-form-label">员工姓名</label> <div class="layui-input-block input-i block"> <input type="text" name="title" lay-verify="title" autocomplete="off" placeholder="请输入员工姓名" class="layui-input" id="employee_name"> </div> </div> <div class="layui-form-item select-box" id="employee_gender_box"> <label class="layui-form-label">员工性别</label> <div class="layui-input-block select block"> <select name="interest" lay-filter="employee_gender" id="employee_gender"> <option value=""></option> </select> </div> </div> <div class="layui-form-item phone" id="employee_phone_number_box"> <label class="layui-form-label">员工电话</label> <div class="layui-input-block input-i block"> <input type="text" name="title" lay-verify="title" autocomplete="off" placeholder="请输入员工电话" class="layui-input" id="employee_phone_number"> </div> </div> <div class="layui-form-item unique" id="employee_id_box"> <label class="layui-form-label">员工工号</label> <div class="layui-input-block input-i block"> <input type="text" name="title" lay-verify="title" autocomplete="off" placeholder="请输入员工工号" class="layui-input" id="employee_id"> </div> </div> </div> </form> <div class="layui-btn-container"> <button type="button" class="layui-btn layui-btn-normal login" id="submit">确认</button> <button type="button" class="layui-btn layui-btn-normal login" id="cancel">取消</button> </div> </div> </div> </div> </article> </body> <script src="../../layui/layui.js"></script> <script src="../../js/base.js"></script> <script src="../../js/index.js"></script> <script> var BaseUrl = baseUrl() let cancel = document.querySelector("#cancel") cancel.addEventListener("click",()=>{ colseLayer() }) let employee_users_id = location.search.substring(1) layui.use(['upload', 'element', 'layer', 'laydate', 'layedit'], function () { var $ = layui.jquery , upload = layui.upload , element = layui.element , layer = layui.layer , laydate = layui.laydate , layedit = layui.layedit , form = layui.form; let url let token = sessionStorage.token || null let personInfo = JSON.parse(sessionStorage.personInfo) let user_group = personInfo.user_group let use_id = personInfo.user_id function $get_stamp() { return new Date().getTime(); } function $get_rand(len) { var rand = Math.random(); return Math.ceil(rand * 10 ** len); } // 权限判断 /** * 获取路径对应操作权限 鉴权 * @param {String} action 操作名 */ function $check_action(path1, action = "get") { var o = $get_power(path1); if (o && o[action] != 0 && o[action] != false) { return true; } return false; } /** * 是否有显示或操作字段的权限 * @param {String} action 操作名 * @param {String} field 查询的字段 */ function $check_field(action, field, path1) { var o = $get_power(path1); var auth; if (o && o[action] != 0 && o[action] != false) { auth = o["field_" + action]; } if (auth) { return auth.indexOf(field) !== -1; } return false; } /** * 获取权限 * @param {String} path 路由路径 */ function $get_power(path) { var list_data = JSON.parse(sessionStorage.list_data) var list = list_data; var obj; for (var i = 0; i < list.length; i++) { var o = list[i]; if (o.path === path) { obj = o; break; } } return obj; } let submit = document.querySelector('#submit') // 提交按钮校验权限 if ($check_action('/employee_users/view', 'add') || $check_action('/employee_users/view', 'set') || $check_option('/employee_users/table', 'examine')) { }else { $("#submit").hide() } // style="display: none" //常规使用 - 普通图片上传 var uploadInst = upload.render({ elem: '#employee_users' , url: BaseUrl + '/api/employee_users/upload?' //此处用的是第三方的 http 请求演示,实际使用时改成您自己的上传接口即可。 , headers: { 'x-auth-token': token }, before: function (obj) { //预读本地文件示例,不支持ie8 obj.preview(function (index, file, result) { $('#employee_users_img').attr('src', fullUrl(BaseUrl,result)); //图片链接(base64) }); element.progress('employee_users', '0%'); //进度条复位 layer.msg('上传中', {icon: 16, time: 0}); } , done: function (res) { //如果上传失败 if (res.code > 0) { return layer.msg('上传失败'); } //上传成功的一些操作 //…… form_data.avatar = res.result.url $('#demoText').html(''); //置空上传失败的状态 } , error: function () { //演示失败状态,并实现重传 var demoText = $('#demoText'); demoText.html('<span style="color: #FF5722;">上传失败</span> <a class="layui-btn layui-btn-xs demo-reload">重试</a>'); demoText.find('.demo-reload').on('click', function () { uploadInst.upload(); }); } //进度条 , progress: function (n, elem, e) { element.progress('employee_users', n + '%'); //可配合 layui 进度条元素使用 if (n == 100) { layer.msg('上传完毕', {icon: 1}); } } }); let username = document.querySelector('#username') let password = document.querySelector('#password') let nickname = document.querySelector('#nickname') let email = document.querySelector('#email') // let phone = document.querySelector('#phone') // let email_state = document.querySelector('#email_state') let form_data = { user_id: 0, username: '', nickname: '', password: '', avatar: '', // phone: '', email: '', user_group: "员工", // phone_state: 0, // email_state: 0, state: 1, } let form_sub = { "employee_name": '', // 员工姓名 "employee_gender": '', // 员工性别 "employee_phone_number": '', // 员工电话 "employee_id": '', // 员工工号 "user_id": 0, "employee_users_id": 0 // ID } // 员工性别选项列表 let employee_gender_data = ['男','女'] async function list_employee_gender() { var employee_gender = document.querySelector("#employee_gender") var op1 = document.createElement("option"); op1.value = '0' employee_gender.appendChild(op1) // 收集数据 长度 var count // 收集数据 数组 var arr = [] count = employee_gender_data.length arr = employee_gender_data for (var i = 0; i < arr.length; i++) { var op = document.createElement("option"); // 给节点赋值 op.innerHTML = arr[i] op.value = arr[i] // 新增/Add节点 employee_gender.appendChild(op) if (form_sub.employee_gender==arr[i].employee_gender){ op.selected = true } layui.form.render("select"); } } layui.form.on('select(employee_gender)', function (data) { form_sub.employee_gender = data.elem[data.elem.selectedIndex].text; }) list_employee_gender() // 单选框点击事件 // layui.form.on('select(email_state)', function (data) { // form_data.email_state = Number(data.elem[data.elem.selectedIndex].value); // }) layui.form.on('select(state)', function (data) { form_data.state = Number(data.elem[data.elem.selectedIndex].value); }) // layui.form.on('select(phone_state)', function (data) { // form_data.phone_state = Number(data.elem[data.elem.selectedIndex].value); // console.log() // }) let employee_name = document.querySelector("#employee_name") let employee_gender = document.querySelector("#employee_gender") let employee_phone_number = document.querySelector("#employee_phone_number") let employee_id = document.querySelector("#employee_id") if (employee_users_id !== '') { username.disabled = "disabled" password.disabled = "disabled" username.style.border = "none" password.style.border = "none" $('#print').show(); if (user_group!=='管理员'){ let state = document.querySelector("#state") state.disabled = "disabled" state.style.border = "none" } async function axios_get_4() { const {data: rese} = await axios.get(BaseUrl + '/api/user/get_obj', { params: { user_id: employee_users_id } }) let data_2 = rese.result.obj Object.keys(form_data).forEach((key) => { form_data[key] = data_2[key]; }); console.log(form_data) for (let key in form_data) { if (key == 'avatar') { employee_users_img.src = fullUrl(BaseUrl,form_data.avatar) } } // for (let key in form_data) { // if (key == 'email_state') { // let alls = document.querySelector('#email_state').querySelectorAll('option') // let test = form_data[key] // for (let i = 0; i < alls.length; i++) { // layui.form.render("select"); // if (alls[i].value == test) { // alls[i].selected = true // layui.form.render("select"); // } // } // } // } // for (let key in form_data) { // if (key == 'phone_state') { // let alls = document.querySelector('#phone_state').querySelectorAll('option') // let test = form_data[key] // for (let i = 0; i < alls.length; i++) { // layui.form.render("select"); // if (alls[i].value == test) { // alls[i].selected = true // layui.form.render("select"); // } // } // } // } for (let key in form_data) { if (key == 'state') { let alls = document.querySelector('#state').querySelectorAll('option') let test = form_data[key] for (let i = 0; i < alls.length; i++) { layui.form.render("select"); if (alls[i].value == test) { alls[i].selected = true layui.form.render("select"); } } } } username.value = form_data.username password.value = form_data.password nickname.value = form_data.nickname email.value = form_data.email // phone.value = form_data.phone document.querySelector('#password_box').style.display = "none"; } axios_get_4() async function axios_get_5() { const {data: rese} = await axios.get(BaseUrl + '/api/employee_users/get_obj', { params: { user_id: employee_users_id } }) let data_2 = rese.result.obj Object.keys(form_sub).forEach((key) => { form_sub[key] = data_2[key]; }); for (let key in form_sub) { } employee_name.value = form_sub.employee_name employee_phone_number.value = form_sub.employee_phone_number employee_id.value = form_sub.employee_id for (let key in data_2) { if (key == 'employee_gender') { let alls = document.querySelector('#employee_gender').querySelectorAll('option') let test = data_2[key] for (let i = 0; i < alls.length; i++) { if (alls[i].innerHTML == test) { alls[i].selected = true layui.form.render("select"); } } } } } axios_get_5() } submit.onclick = async function () { form_data.username = username.value form_data.password = password.value form_data.nickname = nickname.value form_data.email = email.value form_data.password = password.value // form_data.phone = phone.value form_data.avatar = !form_data.avatar ? "/api/upload/default_avatar.jpg" : form_data.avatar form_sub.employee_name = employee_name.value form_sub.employee_phone_number = employee_phone_number.value form_sub.employee_id = employee_id.value var email_regular = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; // var phone_regular = /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/; if (!username.value) { layer.msg('账号不能为空'); } else if (username.value.length > 16 || username.value.length < 5) { layer.msg('账号长度应为5到16个字符之间'); } else if (!password.value) { layer.msg('密码不能为空'); } else if (!employee_users_id && (password.value.length > 16 || password.value.length < 5)) { layer.msg('密码长度应为5到16个字符之间'); } else if (nickname.value.length > 12 || nickname.value.length < 2) { layer.msg('昵称长度应为2到12个字符之间'); } else if (email.value && !email_regular.test(email.value)) { layer.msg('请输入正确的邮箱地址 例:test@test.com!'); } // else if (phone.value && !phone_regular.test(phone.value)) { // layer.msg('请输入正确的手机号码 例:18955552312!'); // } else if (form_data) { let employee_phone_number_phone_regular = /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/; if(form_sub.employee_phone_number && !employee_phone_number_phone_regular.test(form_sub.employee_phone_number)){ layer.msg('手机号格式错误'); return; } if (!form_sub.employee_id){ layer.msg('员工工号不能为空'); return; } else if (!form_sub.employee_users_id) { const {data: res} = await axios.get(BaseUrl + '/api/employee_users/count', { params: { "employee_id": form_sub.employee_id } }) if (res.result == 1) { layer.msg("员工工号已存在!"); return; } } if (employee_users_id == '') { const {data: res} = await axios.post(BaseUrl + '/api/user/register?', form_data, { headers: { 'x-auth-token': token, 'Content-Type': 'application/json' } }) if (res.error){ layer.msg(res.error.message); return; } } Object.keys(form_data).forEach(item => { if (form_data[item] === '') delete form_data[item] if (form_data[item] === null) delete form_data[item] }) let data_p = { username: form_data.username, nickname: form_data.nickname, avatar: form_data.avatar, // phone: form_data.phone, email: form_data.email, user_group: form_data.user_group, // phone_state: form_data.phone_state, // email_state: form_data.email_state, state: form_data.state, } if (employee_users_id !== '') { data_p['user_id'] = employee_users_id } let data_p_param = { username:data_p.username, nickname:data_p.nickname } const {data: rese} = await axios.get(BaseUrl + '/api/user/get_obj', { params: data_p_param }) let idd if (employee_users_id == '') { idd = rese.result.obj.user_id }else { idd = employee_users_id } form_sub.user_id = idd //文本 form_sub.employee_name = employee_name.value //文本 if ((form_sub['employee_users_id'] && $check_field('set', 'employee_name')) || (!form_sub['employee_users_id'] && $check_field('add', 'employee_name'))) { }else { $("#employee_name").attr("disabled", true); $("#employee_name > input[name='file']").attr('disabled', true); } if ((form_sub['employee_users_id'] && $check_field('set', 'employee_gender')) || (!form_sub['employee_users_id'] && $check_field('add', 'employee_gender'))) { }else { $("#employee_gender").attr("disabled", true); $("#employee_gender > input[name='file']").attr('disabled', true); } if ((form_sub['employee_users_id'] && $check_field('set', 'employee_phone_number')) || (!form_sub['employee_users_id'] && $check_field('add', 'employee_phone_number'))) { }else { $("#employee_phone_number").attr("disabled", true); $("#employee_phone_number > input[name='file']").attr('disabled', true); } //文本 form_sub.employee_id = employee_id.value //文本 //提交修改请求 if ((form_sub['employee_users_id'] && $check_field('set', 'employee_id')) || (!form_sub['employee_users_id'] && $check_field('add', 'employee_id'))) { }else { $("#employee_id").attr("disabled", true); $("#employee_id > input[name='file']").attr('disabled', true); } if (employee_users_id == '') { console.log("新增/Add") const {data: ress} = await axios.post(BaseUrl + '/api/employee_users/add?', form_sub, { headers: { 'x-auth-token': token, 'Content-Type': 'application/json' } }) if (ress.result == 1) { layer.msg('确认完毕'); setTimeout(function () { colseLayer() }, 1000) }else if(ress.error){ let user_id = form_sub.user_id; axios.get(BaseUrl + '/api/user/del', {user_id}) layer.msg(ress.error.message); } } else { console.log("详情/Details") const {data: res_data} = await axios.post(BaseUrl + '/api/user/set?user_id=' + form_data.user_id, form_data, { headers: { 'x-auth-token': token, 'Content-Type': 'application/json' } }) console.log(res_data) const {data: res_data2} = await axios.post(BaseUrl + '/api/employee_users/set?employee_users_id=' + form_sub.employee_users_id, form_sub, { headers: { 'x-auth-token': token, 'Content-Type': 'application/json' } }) console.log(res_data2) if (res_data2.result == 1) { layer.msg('确认完毕'); setTimeout(function () { colseLayer() }, 1000) } } } } }) ; </script> </html> 逐个分析我给的代码,各代码对应的功能,详细一点,不要自己写
05-30
<template> <el-main> <!-- 查询条件 --> <el-form :model="searchModel" ref="searchForm" label-width="80px" :inline="true" size="small" > <el-form-item> <el-input v-model="searchModel.name" placeholder="请输入姓名" style="width: 220px" ></el-input> </el-form-item> <el-form-item> <el-input v-model="searchModel.category" placeholder="请输入事项类别" style="width: 220px" ></el-input> </el-form-item> <!-- 审核状态下拉框查询条件 --> <el-form-item label="审核状态"> <el-select v-model="searchModel.auditStatus" placeholder="全部" style="width: 180px" @change="search()" > <el-option label="全部" value="" /> <el-option label="待审核" value="0" /> <el-option label="已初审" value="1" /> <el-option label="初审驳回" value="2" /> <el-option label="已终审" value="3" /> <el-option label="终审驳回" value="4" /> </el-select> </el-form-item> <el-form-item> <div style="display: flex; align-items: center; gap: 10px"> <!-- 查询/重置/新增按钮组 --> <div> <el-button type="primary" icon="el-icon-search" @click="search()" >查询</el-button > <el-button icon="el-icon-refresh-right" @click="resetValue()" >重置</el-button > <el-button type="success" icon="el-icon-plus" @click="openAddWindow()" >积分申请</el-button > </div> <!-- 导出/导入按钮组 --> <div style="display: flex; gap: 10px; margin-left: 10px"> <el-button type="primary" icon="el-icon-download" @click="exportExcel()" >导出Excel</el-button > <el-upload :action="importExcelUrl" :headers="uploadHeaders" :show-file-list="false" :on-success="importExcel" :before-upload="beforeUpload" > <el-button type="success" icon="el-icon-upload2" >导入Excel</el-button > </el-upload> </div> </div> </el-form-item> </el-form> <!-- 数据表格 --> <el-table :data="scoreDetailList" :height="tableHeight" border stripe class="el-table-ellipsis" style="width: 100%; margin-bottom: 10px" show-summary :summary-method="getSummaries" > <!-- 原有列保持不变 --> <el-table-column prop="id" label="序号" width="80" align="center" fixed="left" ></el-table-column> <el-table-column prop="recordDate" label="日期" width="120" header-align="center" align="center" fixed="left" ></el-table-column> <el-table-column prop="name" label="姓名" width="100" header-align="center" align="center" fixed="left" ></el-table-column> <!-- 新增图像列 --> <el-table-column prop="avatar" label="图像" width="120" align="center" header-align="center" > <template slot-scope="scope"> <el-image v-if="scope.row.avatar" style="width: 50px; height: 50px; border-radius: 10%" :src="getAvatarUrl(scope.row.avatar)" :preview-src-list="[getAvatarUrl(scope.row.avatar)]" @error="() => handleImageError(scope.row)" > <div slot="error" class="image-error" /> </el-image> </template> </el-table-column> <!-- 剩余列保持不变 --> <el-table-column prop="category" label="事项类别" width="180" header-align="center" ></el-table-column> <el-table-column prop="score" label="奖惩积分" width="120" header-align="center" align="center" ></el-table-column> <el-table-column prop="originalScore" label="原始分值" width="120" header-align="center" align="center" ></el-table-column> <el-table-column prop="executor" label="执行扣分人" width="100" header-align="center" align="center" ></el-table-column> <el-table-column prop="errorType" label="错误类型" width="180" header-align="center" ></el-table-column> <el-table-column prop="mistakeCount" label="犯错次数" width="80" header-align="center" align="center" ></el-table-column> <el-table-column prop="redEnvelope" label="红包" width="80" header-align="center" align="center" ></el-table-column> <el-table-column prop="details" label="具体事情" width="250" header-align="center" ></el-table-column> <el-table-column prop="creator" label="制单人" width="120" header-align="center" ></el-table-column> <el-table-column prop="createDate" label="制单日期" width="120" header-align="center" align="center" ></el-table-column> <el-table-column prop="remarks" label="备注" width="200" header-align="center" ></el-table-column> <el-table-column prop="auditStatus" label="审核状态" width="100" align="center" header-align="center" > <template slot-scope="scope"> {{ formatAuditStatus(scope.row.auditStatus) }} </template> </el-table-column> <el-table-column prop="firstAuditOpinion" label="初审意见" width="200" header-align="center" ></el-table-column> <el-table-column prop="finalAuditOpinion" label="终审意见" width="200" header-align="center" ></el-table-column> <!-- 操作列 --> <el-table-column label="操作" align="center" width="400" fixed="right"> <template slot-scope="scope"> <!-- 编辑按钮:仅待审核、初审驳回、终审驳回时可见 --> <el-button icon="el-icon-edit" type="primary" size="small" @click="handleEdit(scope.row)" :disabled="scope.row.auditStatus === 3" v-if="[0, 2, 4].includes(scope.row.auditStatus)" >编辑</el-button > <!-- 删除按钮:仅待审核、终审驳回时可见 --> <el-button icon="el-icon-delete" type="danger" size="small" @click="handleDelete(scope.row)" :disabled=" scope.row.auditStatus === 1 || scope.row.auditStatus === 3 " v-if="[0, 4].includes(scope.row.auditStatus)" >删除</el-button > <!-- 初审按钮:仅待审核、初审驳回时可见,已初审状态显示为浅蓝色 --> <el-button icon="el-icon-edit" :type="scope.row.auditStatus === 1 ? 'info' : 'primary'" size="small" @click="firstAudit(scope.row)" v-if=" hasPermission('api:scoreDetail:audit:save') && [0, 1, 2].includes(scope.row.auditStatus) " >初审</el-button > <!-- 终审按钮:仅初审通过、终审驳回时可见,已终审状态显示为浅蓝色 --> <el-button icon="el-icon-edit" :type="scope.row.auditStatus === 3 ? 'info' : 'success'" size="small" @click="finalAudit(scope.row)" v-if=" hasPermission('api:scoreDetail:audit:save') && [1, 3, 4].includes(scope.row.auditStatus) " >终审</el-button > <!-- 行内上传图像按钮 --> <el-upload :action="uploadFileUrl" :show-file-list="false" :headers="uploadHeaders" :before-upload="(file) => beforeAvatarUpload(file, scope.row)" :on-success=" (response, file, fileList) => handleRowAvatarUpload(scope.row, response) " :on-error=" (error, file, fileList) => handleAvatarUploadError(scope.row, error) " style="display: inline-flex; align-items: center" > <el-button type="primary" size="small" icon="el-icon-upload" >上传图像</el-button > </el-upload> </template> </el-table-column> </el-table> <!-- 分页工具栏 --> <el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="pageNum" :page-sizes="[10, 20, 30, 40, 50]" :page-size="10" layout="total, sizes, prev, pager, next, jumper" :total="total" ></el-pagination> <!-- 添加和修改分数详情窗口 --> <el-dialog :title="scoreDetailDialog.title" :visible.sync="scoreDetailDialog.visible" width="80%" > <el-form :model="scoreDetail" ref="scoreDetailForm" :rules="rules" label-width="80px" :inline="false" size="small" > <!-- 第1行:增加奖惩类型单选按钮 --> <el-row> <el-col :span="24"> <el-form-item label="奖惩类型"> <el-radio-group v-model="scoreType" @change="handleScoreTypeChange" > <el-radio label="reward">奖分</el-radio> <el-radio label="punish">扣分</el-radio> </el-radio-group> </el-form-item> </el-col> </el-row> <!-- 第2行 --> <el-row> <el-col :span="6"> <el-form-item label="日期" prop="recordDate"> <el-date-picker v-model="scoreDetail.recordDate" type="date" placeholder="选择日期" style="width: 180px" ></el-date-picker> </el-form-item> </el-col> <el-col :span="4"> <el-form-item label="姓名" prop="name"> <el-input v-model="scoreDetail.name" style="width: 150px" ></el-input> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="事项类别" prop="category"> <el-input v-model="scoreDetail.category" style="width: 180px" ></el-input> <el-button type="success" size="small" @click="openStandardDialog()" style="margin-left: 5px" > 积分标准 </el-button> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="奖惩积分" prop="score"> <el-input v-model.number="scoreDetail.score" style="width: 180px" ></el-input> </el-form-item> </el-col> </el-row> <!-- 第3行 --> <el-row> <!-- 左侧12列区域 - 跨两行显示积分标准 --> <el-col :span="12"> <el-form-item label="积分标准" prop="standard"> <!-- 使用文本域并设置合适的高度,使其视觉上占据两行空间 --> <el-input v-model="scoreDetail.standard" type="textarea" :rows="5" style="width: 100%;" placeholder="请输入积分标准" ></el-input > </el-form-item> </el-col> <!-- 仅扣分时显示 --> <el-col :span="4" v-if="scoreType === 'punish'"> <el-form-item label="原始分值" prop="originalScore"> <el-input v-model.number="scoreDetail.originalScore" style="width: 180px" ></el-input> </el-form-item> </el-col> <el-col :span="4" v-if="scoreType === 'punish'"> <el-form-item label="犯错次数" prop="mistakeCount"> <el-input v-model.number="scoreDetail.mistakeCount" style="width: 100%" ></el-input> </el-form-item> </el-col> <el-col :span="4" v-if="scoreType === 'punish'"> <el-form-item label="红包" prop="redEnvelope"> <el-input v-model.number="scoreDetail.redEnvelope" style="width: 100%" ></el-input> </el-form-item> </el-col> </el-row> <!-- 第4行 --> <el-row> <el-col :span="12"> </el-col> <!-- 仅扣分时显示 --> <el-col :span="8" v-if="scoreType === 'punish'"> <el-form-item label="执行扣分人" prop="executor"> <el-input v-model="scoreDetail.executor" style="width: 200px" @click.native="openPersonSelectDialog" :readonly="true" ></el-input> </el-form-item> </el-col> <el-col :span="4" v-if="scoreType === 'punish'"> <el-form-item label="错误类型" prop="errorType"> <el-input v-model="scoreDetail.errorType" style="width: 180px" @click.native="openErrorTypeDialog" :readonly="true" ></el-input> </el-form-item> </el-col> </el-row> <!-- 第5行 --> <el-row> <el-col :span="12"> <el-form-item label="具体事情" prop="details"> <el-input v-model="scoreDetail.details" type="textarea" :rows="4" style="width: 100%" ></el-input> </el-form-item> </el-col> <el-col :span="12"> <!-- 图像容器:整体居中 --> <div style=" display: flex; flex-direction: column; align-items: center; height: 100%; " > <!-- 上传按钮:在图像上方居中 --> <el-form-item label="图像" prop="avatar" style="width: 100%; text-align: center; margin-bottom: 15px" > <el-upload :action="uploadFileUrl" :show-file-list="false" :headers="uploadHeaders" :before-upload="beforeAvatarUpload" :on-success="handleFormAvatarUpload" :on-error="handleAvatarUploadError" style="display: inline-block" > <el-button type="primary" size="small" icon="el-icon-upload" >上传图像</el-button > </el-upload> </el-form-item> <!-- 图像预览区:居中显示 --> <div style=" flex: 1; display: flex; align-items: center; justify-content: center; width: 100%; " > <el-image v-if="scoreDetail.avatar" style="width: 120px; height: 120px; border-radius: 10%" :src="getAvatarUrl(scoreDetail.avatar)" :preview-src-list="[getAvatarUrl(scoreDetail.avatar)]" @error="handleFormImageError" > <div slot="error" class="image-error" style=" width: 120px; height: 120px; display: flex; align-items: center; justify-content: center; " /> </el-image> <!-- 无图像时的占位符:居中显示 --> <div v-else style=" width: 120px; height: 120px; border-radius: 10%; background-color: #f5f5f5; display: flex; align-items: center; justify-content: center; color: #ccc; font-size: 14px; " > 暂无图像 </div> </div> </div> </el-col> </el-row> <!-- 第6行 --> <el-row> <el-col :span="6"> <el-form-item label="制单人" prop="creator"> <el-input v-model="scoreDetail.creator" style="width: 180px" ></el-input> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="制单日期" prop="createDate"> <el-date-picker v-model="scoreDetail.createDate" type="date" placeholder="选择日期" style="width: 180px" ></el-date-picker> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="备注" prop="remarks"> <el-input v-model="scoreDetail.remarks" style="width: 100%" ></el-input> </el-form-item> </el-col> <el-col :span="6"> <el-form-item label="审核状态" prop="auditStatus"> <!-- 显示汉字状态而非数字 --> <el-input v-model="currentAuditStatusText" style="width: 100px" disabled ></el-input> </el-form-item> </el-col> </el-row> <!-- 审核区域 --> <div v-if="['初审', '终审'].includes(scoreDetailDialog.title)"> <el-row> <el-col :span="8"> <el-form-item label="审核意见" prop="auditOpinion" :rules="getAuditOpinionRules()" > <el-input v-model="scoreDetail.auditOpinion" type="textarea" :rows="2" style="width: 100%" ></el-input> </el-form-item> </el-col> <el-col :span="4"> <el-form-item label="审核人员"> <el-input v-model="currentAuditUser" disabled style="width: 100%" ></el-input> </el-form-item> </el-col> <el-col :span="4"> <el-form-item label="审核时间"> <el-input v-model="currentAuditTime" disabled style="width: 100%" ></el-input> </el-form-item> </el-col> <el-col :span="8"> <el-form-item> <el-button type="success" @click="handleAuditApprove" >审核通过</el-button > <el-button @click="handleAuditCancel">取消审核</el-button> <el-button type="danger" @click="handleAuditReject" >审核驳回</el-button > </el-form-item> </el-col> </el-row> </div> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="scoreDetailDialog.visible = false">取消</el-button> <el-button type="primary" @click="onConfirm" v-if=" ['新增分数详情', '编辑分数详情'].includes(scoreDetailDialog.title) " >确定</el-button > </div> </el-dialog> <!-- 积分标准选择窗口 --> <el-dialog :title="standardSelectDialog.title" :visible.sync="standardSelectDialog.visible" width="60%" > <!-- 保持不变 --> <el-form :model="standardSearchModel" ref="standardSearchForm" label-width="80px" :inline="true" size="small" style="margin-bottom: 10px" > <el-form-item label="事项类别"> <el-input v-model="standardSearchModel.category" placeholder="请输入事项类别" style="width: 220px" ></el-input> </el-form-item> <el-form-item label="积分标准"> <el-input v-model="standardSearchModel.standard" placeholder="请输入积分标准" style="width: 220px" ></el-input> </el-form-item> <el-form-item> <el-button type="primary" icon="el-icon-search" @click="searchStandards()" >查询</el-button > </el-form-item> </el-form> <el-table :data="standardList" :height="standardTableHeight" border stripe class="el-table-ellipsis" style="width: 100%; margin-bottom: 10px" > <el-table-column prop="id" label="序号" width="80" align="center" fixed="left" ></el-table-column> <el-table-column prop="category" label="事项类别" width="200" header-align="center" ></el-table-column> <el-table-column prop="standard" label="事项标准" header-align="center" ></el-table-column> <el-table-column prop="score" label="分值" width="120" header-align="center" ></el-table-column> <el-table-column prop="remarks" label="备注" header-align="center" ></el-table-column> <!-- 操作列 --> <el-table-column label="操作" align="center" width="120" fixed="right"> <template slot-scope="scope"> <el-button icon="el-icon-check" type="success" size="small" @click="selectStandard(scope.row)" >选择</el-button > </template> </el-table-column> </el-table> <!-- 积分标准选择窗口分页工具栏 --> <el-pagination v-if="standardTotal > 0" background @size-change="handleStandardSizeChange" @current-change="handleStandardCurrentChange" :current-page="standardPageNum" :page-sizes="[10, 20, 30, 40, 50]" :page-size="standardPageSize" layout="total, sizes, prev, pager, next, jumper" :total="standardTotal" ></el-pagination> <div slot="footer" class="dialog-footer"> <el-button @click="standardSelectDialog.visible = false" >取消</el-button > </div> </el-dialog> <!-- 新增:选择执行扣分人对话框 --> <el-dialog :title="personSelectDialog.title" :visible.sync="personSelectDialog.visible" width="20%" > <el-form :model="personSearchModel" ref="personSearchForm" label-width="80px" :inline="true" size="small" style="margin-bottom: 10px" > <el-form-item label="人员姓名"> <el-input v-model="personSearchModel.name" placeholder="请输入人员姓名" style="width: 220px" ></el-input> </el-form-item> <el-form-item> <el-button type="primary" icon="el-icon-search" @click="searchPersons()" >查询</el-button > </el-form-item> </el-form> <el-table :data="personList" height="400" border stripe class="el-table-ellipsis" style="width: 100%; margin-bottom: 10px" > <el-table-column prop="id" label="序号" width="80" align="center" ></el-table-column> <el-table-column prop="name" label="姓名" width="120" header-align="center" > <template slot-scope="scope"> <el-button type="text" @click="selectPerson(scope.row)"> {{ scope.row.name }} </el-button> </template> </el-table-column> <!-- 操作列 --> </el-table> <div slot="footer" class="dialog-footer"> <el-button @click="personSelectDialog.visible = false">取消</el-button> </div> </el-dialog> <!-- 新增:错误类型选择对话框 --> <el-dialog :title="errorTypeDialog.title" :visible.sync="errorTypeDialog.visible" width="30%" > <el-form :model="errorTypeSearchModel" ref="errorTypeSearchForm" label-width="80px" :inline="true" size="small" style="margin-bottom: 10px" > <el-form-item label="错误类型"> <el-input v-model="errorTypeSearchModel.name" placeholder="请输入错误类型" style="width: 220px" ></el-input> </el-form-item> <el-form-item> <el-button type="primary" icon="el-icon-search" @click="searchErrorTypes()" >查询</el-button > </el-form-item> </el-form> <el-table :data="errorTypeList" height="400" border stripe style="width: 100%; margin-bottom: 10px" > <el-table-column prop="id" label="序号" width="80" align="center" ></el-table-column> <el-table-column prop="name" label="错误类型" header-align="center"> <!-- 修改:点击文本即可选择 --> <template slot-scope="scope"> <el-button type="text" @click="selectErrorType(scope.row)"> {{ scope.row.name }} </el-button> </template> </el-table-column> </el-table> <div slot="footer" class="dialog-footer"> <el-button @click="errorTypeDialog.visible = false">取消</el-button> </div> </el-dialog> </el-main> </template> <script> import { getScoreDetails, addScoreDetail, updateScoreDetail, deleteScoreDetail, getErrorCount, getScoreErrorTypes, } from "@/api/company/hr/scoreDetail"; import { getScoreStandards } from "@/api/company/hr/scoreStandard"; import { getScorePersons } from "@/api/company/hr/scorePerson"; import { getToken, getUsername } from "@/utils/auth"; import axios from "axios"; export default { name: "scoreDetailList", data() { return { // 新增:奖惩类型,默认奖分 scoreType: "reward", // 错误类型对话框相关数据 errorTypeDialog: { title: "选择错误类型", visible: false, }, errorTypeSearchModel: { errorType: "", pageNum: 1, pageSize: 10, }, errorTypeList: [], errorTypePageNum: 1, errorTypePageSize: 10, errorTypeTotal: 0, // 当前审核记录在列表中的索引(用于自动跳转) currentAuditIndex: -1, // 审核状态映射(数字转汉字) auditStatusMap: { 0: "待审核", 1: "已初审", 2: "初审驳回", 3: "已终审", 4: "终审驳回", }, // 查询条件 searchModel: { name: "", category: "", auditStatus: "", // 审核状态:空-全部,0-待审核,1-已初审,2-初审驳回,3-已终审,4-终审驳回 pageNum: 1, pageSize: 10, }, // 表格数据 scoreDetailList: [], tableHeight: 0, pageNum: 1, pageSize: 10, total: 0, // 分数详情表单 scoreDetail: { id: "", recordDate: new Date(), name: this.$store.getters.name, // 当前登录用户 category: "", score: "", originalScore: "", executor: "", errorType: "", mistakeCount: "", redEnvelope: "", details: "", creator: "", createDate: new Date(), remarks: "", auditStatus: 0, auditOpinion: "", firstAuditUser: "", firstAuditTime: "", finalAuditUser: "", finalAuditTime: "", avatar: "", // 图像字段 }, // 积分标准选择对话框数据 standardSelectDialog: { title: "选择积分标准", visible: false, }, standardSearchModel: { category: "", standard: "", pageNum: 1, pageSize: 10, }, standardList: [], standardTableHeight: 0, standardPageNum: 1, standardPageSize: 10, standardTotal: 0, // 表单验证规则 rules: { name: [{ required: true, trigger: "blur", message: "请输入姓名" }], recordDate: [ { required: true, trigger: "blur", message: "请选择日期" }, ], category: [ { required: true, trigger: "blur", message: "请输入事项类别" }, ], score: [{ required: true, trigger: "blur", message: "请输入奖惩积分" }], }, // 对话框 scoreDetailDialog: { title: "", visible: false, }, // 导入导出相关 uploadHeaders: { token: "" }, importExcelUrl: process.env.VUE_APP_BASE_API + "api/scoreDetail/import/easyExcel", // 图像上传相关配置 uploadFileUrl: process.env.VUE_APP_BASE_API + "files/upload", // 图像上传接口 uploadAvatarFileUrl: process.env.VUE_APP_BASE_API + "files/", // 图像显示基础路径 // 审核相关数据 currentAuditType: "", // 'first' 或 'final' currentAuditUser: "", currentAuditTime: "", currentAuditAction: "", // 'approve' 或 'reject' // 审核状态控制 auditStatusControl: { firstAudit: { enabled: true, // 始终启用初审 status: 0, // 0:待审核, 1:已初审, 2:初审驳回 }, finalAudit: { enabled: true, // 始终启用终审 status: 0, // 0:待终审, 1:已终审, 2:终审驳回 }, }, // 新增:选择执行扣分人对话框数据 personSelectDialog: { title: "选择执行扣分人", visible: false, }, personSearchModel: { name: "", pageNum: 1, pageSize: 10, }, personList: [], personTotal: 0, personPageNum: 1, personPageSize: 10, }; }, computed: { currentToken() { return getToken(); }, currentUsername() { return this.$store.getters.name; }, // 计算属性:当前审核状态文本 currentAuditStatusText() { const statusMap = { 0: "待审核", 1: "已初审", 2: "初审驳回", 3: "已终审", 4: "终审驳回", }; // 处理新增时的默认状态 return this.scoreDetail.id ? statusMap[this.scoreDetail.auditStatus] || "未知状态" : "待审核"; }, }, watch: { currentToken(newVal) { this.uploadHeaders.token = newVal; }, // 监听对话框标题变化,更新审核相关数据 "scoreDetailDialog.title"(newVal) { if (newVal === "初审") { this.currentAuditType = "first"; this.currentAuditUser = this.scoreDetail.firstAuditUser || this.currentUsername; this.currentAuditTime = this.scoreDetail.firstAuditTime; this.scoreDetail.auditOpinion = this.scoreDetail.firstAuditOpinion || ""; // 更新审核状态控制 this.auditStatusControl.firstAudit.status = this.scoreDetail.auditStatus; } else if (newVal === "终审") { this.currentAuditType = "final"; this.currentAuditUser = this.scoreDetail.finalAuditUser || this.currentUsername; this.currentAuditTime = this.scoreDetail.finalAuditTime; this.scoreDetail.auditOpinion = this.scoreDetail.finalAuditOpinion || ""; // 更新审核状态控制 this.auditStatusControl.finalAudit.status = this.scoreDetail.auditStatus; } }, }, methods: { // 新增:表格合计方法 getSummaries(param) { const { columns, data } = param; const sums = []; columns.forEach((column, index) => { if (index === 0) { // 第一列显示"合计" sums[index] = '合计'; return; } // 只对奖惩积分列进行合计 if (column.property === 'score') { const values = data.map(item => Number(item.score)); if (!values.every(value => isNaN(value))) { const sum = values.reduce((prev, curr) => { const value = Number(curr); return isNaN(value) ? prev : prev + value; }, 0); // 格式化合计值(保留两位小数,千分位分隔) sums[index] = sum.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } else { sums[index] = ''; } } else { // 其他列不显示合计 sums[index] = ''; } }); return sums; }, // 新增:处理奖惩类型变更 handleScoreTypeChange(val) { // 当切换为奖分时清空扣分相关字段 if (val === "reward") { this.scoreDetail.originalScore = ""; this.scoreDetail.mistakeCount = ""; this.scoreDetail.redEnvelope = ""; this.scoreDetail.executor = ""; this.scoreDetail.errorType = ""; } }, // 打开错误类型对话框 openErrorTypeDialog() { this.errorTypeSearchModel = { errorType: "", pageNum: 1, pageSize: 10, }; this.searchErrorTypes(); this.errorTypeDialog.visible = true; }, // 查询错误类型列表 async searchErrorTypes() { try { const res = await getScoreErrorTypes({ ...this.errorTypeSearchModel, pageNum: this.errorTypePageNum, pageSize: this.errorTypePageSize, }); if (res.code === 200) { this.errorTypeList = res.data.rows || []; this.errorTypeTotal = res.data.total || 0; } else { this.$message.error(res.message || "查询错误类型失败"); } } catch (error) { this.$message.error("查询错误类型请求异常"); } }, // 选择错误类型 - 简化逻辑 async selectErrorType(row) { // 直接使用row.name作为错误类型值 this.scoreDetail.errorType = row.name; this.errorTypeDialog.visible = false; // 强制触发视图更新 this.$forceUpdate(); // 自动更新犯错次数 await this.updateMistakeCount(); }, // 更新犯错次数 async updateMistakeCount() { if ( this.scoreDetail.recordDate && this.scoreDetail.name && this.scoreDetail.errorType ) { try { // 从日期中提取年份 const year = new Date(this.scoreDetail.recordDate).getFullYear(); // 调用API获取错误次数 const res = await getErrorCount({ name: this.scoreDetail.name, year: year, errorType: this.scoreDetail.errorType, }); if (res.code === 200) { // 使用$set确保响应式更新 const newCount = (res.data || 0) + 1; this.$set(this.scoreDetail, "mistakeCount", newCount); // 再次强制更新,确保视图同步 this.$nextTick(() => { this.$forceUpdate(); }); } else { this.$message.error(res.message || "获取错误次数失败"); } } catch (error) { console.error("获取错误次数请求异常:", error); this.$message.error( `获取错误次数失败: ${error.message || "网络错误"}` ); } } else { this.$message.warning("请先填写日期、姓名和错误类型"); } }, // 日期格式化辅助函数 formatDate(date) { const d = new Date(date); let month = "" + (d.getMonth() + 1); let day = "" + d.getDate(); const year = d.getFullYear(); if (month.length < 2) month = "0" + month; if (day.length < 2) day = "0" + day; return [year, month, day].join("-"); }, // 新增:打开选择执行扣分人对话框 openPersonSelectDialog() { this.personSearchModel = { name: "", pageNum: 1, pageSize: 10, }; this.searchPersons(); this.personSelectDialog.visible = true; }, // 新增:查询执行扣分人列表 async searchPersons() { try { const res = await getScorePersons({ ...this.personSearchModel, pageNum: this.personPageNum, pageSize: this.personPageSize, }); if (res.code === 200) { this.personList = res.data.rows || []; this.personTotal = res.data.total || 0; } else { this.$message.error(res.message || "查询人员列表失败"); } } catch (error) { this.$message.error("查询人员列表请求异常"); } }, // 新增:选择执行扣分人 selectPerson(row) { this.scoreDetail.executor = row.name; this.personSelectDialog.visible = false; }, // 获取审核意见的验证规则 getAuditOpinionRules() { return this.currentAuditAction === "reject" ? { required: true, message: "请输入驳回意见", trigger: "blur" } : []; }, // 打开初审窗口 firstAudit(row) { this.scoreDetailDialog.title = "初审"; this.scoreDetailDialog.visible = true; this.scoreDetail = { ...row }; // 记录当前索引 this.currentAuditIndex = this.scoreDetailList.findIndex( (item) => item.id === row.id ); // 初始化当前审核人 this.currentAuditUser = this.currentUsername; this.currentAuditType = "first"; // 更新审核状态控制 this.auditStatusControl.firstAudit.status = this.scoreDetail.auditStatus; }, // 打开终审窗口 finalAudit(row) { // 允许对任何状态记录进行终审操作 this.scoreDetailDialog.title = "终审"; this.scoreDetailDialog.visible = true; this.scoreDetail = { ...row }; this.currentAuditIndex = this.scoreDetailList.findIndex( (item) => item.id === row.id ); this.currentAuditUser = this.currentUsername; this.currentAuditType = "final"; // 更新审核状态控制 this.auditStatusControl.finalAudit.status = this.scoreDetail.auditStatus; }, // 审核通过 async handleAuditApprove() { this.currentAuditAction = "approve"; // 设置当前操作为通过 this.$refs.scoreDetailForm.validate(async (valid) => { if (!valid) return; const now = new Date(); const auditTime = now.toISOString().slice(0, 19).replace("T", " "); if (this.currentAuditType === "first") { this.scoreDetail.auditStatus = 1; this.scoreDetail.firstAuditUser = this.currentUsername; this.scoreDetail.firstAuditTime = auditTime; this.scoreDetail.firstAuditOpinion = this.scoreDetail.auditOpinion; this.auditStatusControl.firstAudit.status = 1; // 更新状态为已初审 } else { this.scoreDetail.auditStatus = 3; this.scoreDetail.finalAuditUser = this.currentUsername; this.scoreDetail.finalAuditTime = auditTime; this.scoreDetail.finalAuditOpinion = this.scoreDetail.auditOpinion; this.auditStatusControl.finalAudit.status = 1; // 更新状态为已终审 } await this.saveAuditInfo(); this.$message.success(`${this.scoreDetailDialog.title}通过`); this.jumpToNextAuditRecord(this.currentAuditType === "first" ? 0 : 1); }); }, // 取消审核 async handleAuditCancel() { if (this.currentAuditType === "first") { this.scoreDetail.auditStatus = 0; this.scoreDetail.firstAuditOpinion = ""; this.scoreDetail.firstAuditTime = null; this.auditStatusControl.firstAudit.status = 0; // 更新状态为待审核 } else { // 终审取消,回到初审通过状态 this.scoreDetail.auditStatus = 1; this.scoreDetail.finalAuditOpinion = ""; this.scoreDetail.finalAuditTime = null; this.auditStatusControl.finalAudit.status = 0; // 更新状态为待终审 } await this.saveAuditInfo(); this.$message.success(`已取消${this.scoreDetailDialog.title}`); this.scoreDetailDialog.visible = false; }, // 审核驳回 async handleAuditReject() { this.currentAuditAction = "reject"; // 设置当前操作为驳回 this.$refs.scoreDetailForm.validate(async (valid) => { if (!valid) return; const now = new Date(); const auditTime = now.toISOString().slice(0, 19).replace("T", " "); if (this.currentAuditType === "first") { this.scoreDetail.auditStatus = 2; this.scoreDetail.firstAuditUser = this.currentUsername; this.scoreDetail.firstAuditTime = auditTime; this.scoreDetail.firstAuditOpinion = this.scoreDetail.auditOpinion; this.auditStatusControl.firstAudit.status = 2; // 更新状态为初审驳回 } else { this.scoreDetail.auditStatus = 4; this.scoreDetail.finalAuditUser = this.currentUsername; this.scoreDetail.finalAuditTime = auditTime; this.scoreDetail.finalAuditOpinion = this.scoreDetail.auditOpinion; this.auditStatusControl.finalAudit.status = 2; // 更新状态为终审驳回 } await this.saveAuditInfo(); this.$message.success(`${this.scoreDetailDialog.title}驳回`); this.jumpToNextAuditRecord(this.currentAuditType === "first" ? 0 : 1); }); }, // 保存审核信息到后端 async saveAuditInfo() { try { const res = await updateScoreDetail(this.scoreDetail); if (res.code === 200) { this.search(); // 刷新列表 } else { this.$message.error(res.message); } } catch (error) { this.$message.error("审核操作失败"); } }, // 自动跳转到下一个符合条件的记录 jumpToNextAuditRecord(targetStatus) { const nextIndex = this.scoreDetailList.findIndex( (item, index) => index > this.currentAuditIndex && item.auditStatus === targetStatus ); if (nextIndex > -1) { const nextRecord = this.scoreDetailList[nextIndex]; this.scoreDetailDialog.visible = false; // 根据目标状态决定打开初审还是终审 targetStatus === 0 ? this.firstAudit(nextRecord) : this.finalAudit(nextRecord); } else { this.scoreDetailDialog.visible = false; this.$message.info("已无符合条件的下一条记录"); } }, // 表格列格式化审核状态 formatAuditStatus(status) { return this.auditStatusMap[status] || "未知状态"; }, // 初始化组件时获取用户名 initUserInfo() { this.scoreDetail.name = this.currentUsername; this.scoreDetail.creator = this.currentUsername; }, // 分数详情列表查询 async search() { try { const res = await getScoreDetails(this.searchModel); if (res.code === 200) { this.scoreDetailList = res.data.rows || []; this.total = res.data.total || 0; } else { this.$message.error(res.message || "查询失败"); } } catch (error) { this.$message.error("请求异常"); } }, // 重置查询条件 resetValue() { this.searchModel = { name: "", category: "", auditStatus: "", // 重置为全部 pageNum: 1, pageSize: 10, }; this.search(); }, // 分页事件处理 handleSizeChange(size) { this.searchModel.pageSize = size; this.search(); }, handleCurrentChange(page) { this.searchModel.pageNum = page; this.search(); }, // 打开新增窗口(优化:确保表单完全重置) openAddWindow() { // 先销毁表单验证状态,再重置字段 if (this.$refs.scoreDetailForm) { this.$refs.scoreDetailForm.clearValidate(); // 清除所有验证状态 this.$refs.scoreDetailForm.resetFields(); // 重置字段值 } // 初始化新增表单数据 this.scoreType = "reward"; // 默认奖分 this.scoreDetail = { recordDate: new Date(), createDate: new Date(), name: this.currentUsername, creator: this.currentUsername, auditStatus: 0, category: "", score: "", avatar: "", }; this.scoreDetailDialog.title = "新增分数详情"; this.scoreDetailDialog.visible = true; }, // 编辑分数详情 handleEdit(row) { this.scoreDetail = { ...row }; // 设置奖惩类型 this.scoreType = row.originalScore || row.mistakeCount || row.redEnvelope ? "punish" : "reward"; // 日期格式转换 if (this.scoreDetail.recordDate) { this.scoreDetail.recordDate = new Date(this.scoreDetail.recordDate); } if (this.scoreDetail.createDate) { this.scoreDetail.createDate = new Date(this.scoreDetail.createDate); } this.scoreDetailDialog.title = "编辑分数详情"; this.scoreDetailDialog.visible = true; }, // 打开积分标准选择对话框 openStandardDialog() { this.standardSearchModel = { category: "", standard: "", pageNum: 1, pageSize: 10, }; this.searchStandards(); this.standardSelectDialog.visible = true; }, // 查询积分标准 async searchStandards() { try { const res = await getScoreStandards({ ...this.standardSearchModel, pageNum: this.standardPageNum, pageSize: this.standardPageSize, }); if (res.code === 200) { this.standardList = res.data.rows || []; this.standardTotal = res.data.total || 0; } else { this.$message.error(res.message || "查询积分标准失败"); } } catch (error) { this.$message.error("查询积分标准请求异常"); } }, // 积分标准分页大小变更 handleStandardSizeChange(size) { this.standardPageSize = size; this.standardSearchModel.pageSize = size; this.searchStandards(); }, // 积分标准分页页码变更 handleStandardCurrentChange(page) { this.standardPageNum = page; this.standardSearchModel.pageNum = page; this.searchStandards(); }, // 选择积分标准 selectStandard(row) { // 给事项类别、积分标准、奖惩积分赋值 this.scoreDetail.category = row.category; this.scoreDetail.standard = row.standard; this.scoreDetail.score = row.score; // 手动触发表单验证 if (this.$refs.scoreDetailForm) { this.$refs.scoreDetailForm.validateField(["category", "score"]); } // 关闭选择窗口 this.standardSelectDialog.visible = false; }, // 保存分数详情 async onConfirm() { this.$refs.scoreDetailForm.validate(async (valid) => { if (!valid) return; try { const res = this.scoreDetail.id ? await updateScoreDetail(this.scoreDetail) : await addScoreDetail(this.scoreDetail); if (res.code === 200) { this.$message.success(res.message); this.scoreDetailDialog.visible = false; this.search(); } else { this.$message.error(res.message); } } catch (error) { this.$message.error("操作失败"); } }); }, // 删除分数详情 async handleDelete(row) { try { await this.$confirm("确定删除该分数详情?", "提示", { type: "warning", }); const res = await deleteScoreDetail(row.id); if (res.code === 200) { this.$message.success(res.message); this.search(); } else { this.$message.error(res.message); } } catch (error) { // 取消删除 } }, // 导出Excel async exportExcel() { try { const instance = axios.create({ baseURL: process.env.VUE_APP_BASE_API, headers: { token: getToken() }, responseType: "blob", }); const response = await instance.get( "/api/scoreDetail/export/easyExcel", { params: { name: this.searchModel.name, }, } ); const fileName = response.headers["content-disposition"] ? decodeURIComponent( response.headers["content-disposition"].split("filename=")[1] ).replace(/"/g, "") : "scoreDetails_export.xls"; const link = document.createElement("a"); link.href = URL.createObjectURL(new Blob([response.data])); link.download = fileName; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(link.href); } catch (error) { this.handleExportError(error); } }, // 导入Excel importExcel(response) { if (response.code === 200) { this.search(); this.$message.success("导入成功"); } else { this.$message.error(response.message || "导入失败"); } }, // 导入文件校验 beforeUpload(file) { const allowedTypes = [ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.ms-excel", ]; if (!allowedTypes.includes(file.type)) { this.$message.error("仅支持 .xls 或 .xlsx 格式文件"); return false; } return true; }, // 导出错误处理 handleExportError(error) { if (error.response?.status === 401) { this.$message.error("登录已过期,请重新登录"); this.$store.dispatch("user/logout"); this.$router.push("/login"); } else if (error.response?.data instanceof Blob) { const reader = new FileReader(); reader.onload = () => { try { const errorData = JSON.parse(reader.result); this.$message.error(errorData.message || "导出失败"); } catch { this.$message.error("导出文件解析失败"); } }; reader.readAsText(error.response.data); } else { this.$message.error(error.message || "导出失败"); } }, // 图像上传相关方法 // 获取图像完整URL getAvatarUrl(avatar) { if (!avatar) return ""; const timestamp = Date.now(); return `${this.uploadAvatarFileUrl}${encodeURIComponent(avatar)}?token=${ this.currentToken }&t=${timestamp}`; }, // 处理图片加载错误 handleImageError(row) { console.error("图片加载失败:", row.avatar); // 简单的错误处理 this.$set(row, "avatar", ""); }, // 表单图片加载错误处理 handleFormImageError() { console.error("表单图片加载失败"); this.scoreDetail.avatar = ""; }, // 图像上传前验证 beforeAvatarUpload(file, row) { const isImage = file.type.startsWith("image/"); const isLt5M = file.size / 1024 / 1024 < 5; if (!isImage) { this.$message.error("只能上传图片文件!"); } if (!isLt5M) { this.$message.error("图像图片大小不能超过5MB!"); } return isImage && isLt5M; }, // 处理表单中的图像上传成功 handleFormAvatarUpload(response) { if (response.code === 200) { this.scoreDetail.avatar = response.data; this.$message.success("图像上传成功"); } else { this.$message.error(response.message || "图像上传失败"); } }, // 处理行内图像上传成功 handleRowAvatarUpload(row, response) { if (response.code === 200) { // 更新当前行的图像信息 this.$set(row, "avatar", response.data); this.$message.success("图像上传成功"); // 同步更新到后端 const updateData = { id: row.id, avatar: response.data, }; updateScoreDetail(updateData) .then((res) => { if (res.code !== 200) { this.$message.warning("图像信息同步失败"); } }) .catch(() => { this.$message.warning("图像信息同步失败"); }); } else { this.$message.error(response.message || "图像上传失败"); } }, // 处理图像上传失败 handleAvatarUploadError(row, error) { console.error("图像上传失败:", error); this.$message.error("图像上传失败,请重试"); }, }, created() { this.search(); }, mounted() { this.uploadHeaders.token = getToken(); this.initUserInfo(); this.$nextTick(() => { this.tableHeight = window.innerHeight - 220; this.standardTableHeight = window.innerHeight - 300; }); }, }; </script> <style> /* 全局单元格样式 */ .el-table-ellipsis .cell { white-space: nowrap !important; /* 强制不换行 */ overflow: hidden !important; text-overflow: ellipsis !important; } /* 禁用操作列的省略号效果 */ .el-table-ellipsis .no-ellipsis .cell { white-space: normal !important; /* 恢复默认换行 */ overflow: visible !important; text-overflow: clip !important; } /* 可选:表头对齐方式 */ .el-table-ellipsis th > .cell { display: block; /* 解决表头与内容列宽度轻微错位问题 */ } .dialog-footer { display: flex; justify-content: flex-end; gap: 10px; } /* 图片错误占位样式 */ .image-error { width: 100%; height: 100%; background-color: #f5f5f5; display: flex; align-items: center; justify-content: center; color: #ccc; } .image-error::after { content: "图片错误"; font-size: 12px; } /* 新增:合计行样式 */ .el-table .el-table__footer .cell { font-weight: bold; color: #409EFF; } </style> 报错: vue.runtime.esm.js:620 [Vue warn]: Property or method "showSettings1" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties. found in ---> <Layout> at src/layout/index.vue <App> at src/App.vue <Root> 6 vue.runtime.esm.js:620 [Vue warn]: Property or method "formatNumber" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties. found in ---> <ScoreTotal> at src/views/company/hr/scoreTotal.vue <AppMain> at src/layout/components/AppMain.vue <Layout> at src/layout/index.vue <App> at src/App.vue <Root> 合计数没有展示出来,优化代码
最新发布
08-06
<template> <div class="news-images"> <div v-for="(img, idx) in images" :key="idx" class="news-img-wrap"> <!-- 上传进度显示 --> <div v-if="img.status === 'uploading'" class="progress-overlay"> <van-circle v-model:current-rate="img.progress" :rate="100" :speed="10" size="120rem" :stroke-width="20" stroke-linecap="round" class="progress" > <span class="progress-text">{{ img.progress }}%</span> </van-circle> </div> <!-- 错误状态显示 --> <div v-if="img.status === 'error'" class="error-overlay"> <van-icon name="warning" size="32rem" color="#ff4444" /> <span>上传失败</span> <van-button type="primary" size="small" @click="reUpload(idx)" style="margin-top: 16rem">重新上传</van-button> </div> <img :src="img.url" class="news-img" /> <div class="img-close" @click="removeImg(idx)"> <van-icon name="cross" size="32rem" color="#fff" /> </div> </div> <div v-if="images.length < maxCount" class="news-img-add" @click="onAddImg"> <van-icon name="plus" size="64rem" color="#bbb" /> </div> <!-- 隐藏的文件选择器 --> <input ref="fileInput" type="file" multiple accept="image/*" style="display: none" @change="handleFileChange" /> </div> </template> <script setup lang="ts"> import { apis } from '@/api' import System from '@/utils/System' import { showToast } from 'vant' import { ref, onUnmounted } from 'vue' const emit = defineEmits(['update:modelValue']) const props = defineProps({ autoUpload: { type: Boolean, default: true }, maxCount: { type: Number, default: 9 }, modelValue: { type: Array as () => imgageItem[], default: () => [] } }) interface imgageItem { url: string progress: number status: 'pending' | 'uploading' | 'success' | 'error' file?: File } const fileInput = ref<HTMLInputElement | null>(null) const images = ref<imgageItem[]>(props.modelValue) const onAddImg = () => { fileInput.value?.click() } const removeImg = async (idx: number) => { images.value.splice(idx, 1) emit('update:modelValue', [...images.value]) } // const handleFileChange = async (e: Event) => { // const target = e.target as HTMLInputElement // const files = target.files // if (!files || files.length === 0) return // for (const file of Array.from(files)) { // if (images.value.length >= props.maxCount) break // try { // const reader = new FileReader() // console.log("🚀 ~ handleFileChange ~ reader:", reader) // reader.onload = (ev) => { // if (typeof ev.target?.result === 'string') { // const index = images.value.length // images.value.push({ // url: ev.target.result, // 临时预览URL // progress: 0, // status: 'pending', // file: file // }) // console.log("🚀 ~ handleFileChange ~ images.value:", images.value) // if (props.autoUpload) { // uploadFile(file, index) // } // reader.readAsDataURL(file) // } // } // // 清空 input // ;(e.target as HTMLInputElement).value = '' // // 更新父组件 // emit('update:modelValue', [...images.value]) // } catch (error) { // System.toast('图片处理失败') // console.error('文件处理错误:', error) // } // } // // 清空文件选择器 // ;(e.target as HTMLInputElement).value = '' // } const handleFileChange = (e: Event) => { const files = (e.target as HTMLInputElement).files if (files && files.length > 0) { Array.from(files).forEach((file) => { if (images.value.length >= 6) return const reader = new FileReader() reader.onload = (ev) => { if (typeof ev.target?.result === 'string') { const index = images.value.length images.value.push({ url: ev.target.result, // 临时预览URL progress: 0, status: 'pending', file: file }) // 开始上传 if (props.autoUpload) { uploadFile(file, index) } } } reader.readAsDataURL(file) }) emit('update:modelValue', [...images.value]) // 清空 input ;(e.target as HTMLInputElement).value = '' } } const uploadFile = async (file: File, index: number) => { // try { images.value[index].status = 'uploading' // 调用API上传 const formData = new FormData() formData.append("file",file) const res: any = await apis.uploadFile({ file: formData.get('file') }, (progressEvent: { loaded: number; total: number }) => { if (progressEvent.total > 0) { images.value[index].progress = Math.round((progressEvent.loaded / progressEvent.total) * 100) } }) if (res.code === 200) { images.value[index].status = 'success' images.value[index].url = res.data } else { images.value[index].status = 'error' System.toast('图片上传失败') } emit('update:modelValue', [...images.value]) // } catch (error) { // images.value[index].status = 'error' // System.toast('图片上传失败') // console.error('上传错误:', error) // emit('update:modelValue', [...images.value]) // } } // 自动上传全部待上传图片 const autoUploadAll = () => { images.value.forEach((img, index) => { if (img.status === 'pending' && img.file) { uploadFile(img.file, index) } }) } // 重新上传指定失败图片 const reUpload = (index: number) => { if (images.value[index].status !== 'error' || !images.value[index].file) return images.value[index].status = 'pending' uploadFile(images.value[index].file!, index) } // 组件卸载时清理临时文件 onUnmounted(async () => {}) defineExpose({ autoUploadAll, reUpload }) </script> <style lang="less" scoped> .news-images { display: flex; flex-wrap: wrap; gap: 18.5rem; margin-bottom: 48rem; .news-img-wrap { position: relative; width: 216rem; height: 216rem; border-radius: 23rem; overflow: hidden; margin-bottom: 32rem; // 新增,保证多行间距 .progress-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 2; .progress { display: flex; align-items: center; justify-content: center; } .progress-text { color: #fff; font-size: 24rem; } } .error-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); display: flex; flex-direction: column; justify-content: center; align-items: center; z-index: 2; span { color: #fff; font-size: 24rem; margin-top: 8rem; } } .news-img { width: 100%; height: 100%; object-fit: cover; border-radius: 24rem; } .img-close { position: absolute; top: 8rem; right: 8rem; background: rgba(0, 0, 0, 0.4); border-radius: 50%; width: 40rem; height: 40rem; display: flex; align-items: center; justify-content: center; cursor: pointer; z-index: 3; } } .news-img-add { width: 216rem; height: 216rem; background: #f6f6f6; border-radius: 23rem; display: flex; align-items: center; justify-content: center; cursor: pointer; margin-bottom: 32rem; // 保持添加按钮与图片对齐 .van-icon { font-weight: bold; } } } </style> 这个代码帮我解决
07-05
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值