From a8c4096aed37a52fe9c098d5f1f7f4a5e6238efe Mon Sep 17 00:00:00 2001 From: yinqi <18774398105@163.com> Date: Thu, 11 Dec 2025 14:18:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=A1=A8=E6=A0=BC=E9=80=9A?= =?UTF-8?q?=E7=94=A8=E7=BB=84=E4=BB=B6=E3=80=81=E6=90=9C=E7=B4=A2=E9=80=9A?= =?UTF-8?q?=E7=94=A8=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 4 +- src/api/demo/demo/index.ts | 19 + src/assets/styles/ruoyi.scss | 1 + src/components/CloseBox/main.vue | 29 ++ src/components/FormTable/exSlot.tsx | 34 ++ src/components/FormTable/index.vue | 507 +++++++++++++++++++++++++ src/components/SearchForm/formElem.vue | 213 +++++++++++ src/components/SearchForm/index.vue | 185 +++++++++ src/components/SearchPanel/index.vue | 95 +++++ src/components/ToolbarButton/index.vue | 90 +++++ src/lang/en_US.ts | 69 ++++ src/lang/zh_CN.ts | 68 ++++ src/layout/components/AppMain.vue | 4 +- src/main.ts | 3 + src/utils/common.ts | 413 ++++++++++++++++++++ src/utils/validate.ts | 23 ++ src/views/demo/tableDemo/index.vue | 256 +++++++++++++ vite/plugins/index.ts | 11 +- 18 files changed, 2020 insertions(+), 4 deletions(-) create mode 100644 src/components/CloseBox/main.vue create mode 100644 src/components/FormTable/exSlot.tsx create mode 100644 src/components/FormTable/index.vue create mode 100644 src/components/SearchForm/formElem.vue create mode 100644 src/components/SearchForm/index.vue create mode 100644 src/components/SearchPanel/index.vue create mode 100644 src/components/ToolbarButton/index.vue create mode 100644 src/utils/common.ts create mode 100644 src/views/demo/tableDemo/index.vue diff --git a/package.json b/package.json index dc3bd55..18ea5b3 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,9 @@ "vue-json-pretty": "2.6.0", "vue-router": "4.6.3", "vue-types": "6.0.0", - "vxe-table": "4.17.7" + "vxe-table": "4.17.7", + "@layui/layui-vue": "^2.12.0", + "@vitejs/plugin-vue-jsx": "^4.1.2" }, "devDependencies": { "@iconify/json": "^2.2.403", diff --git a/src/api/demo/demo/index.ts b/src/api/demo/demo/index.ts index 7441720..d9b495c 100644 --- a/src/api/demo/demo/index.ts +++ b/src/api/demo/demo/index.ts @@ -15,6 +15,25 @@ export const listDemo = (query?: DemoQuery): AxiosPromise => { }); }; +/** + * 表格查询测试单列表 + * @param current 当前页 + * @param size 每页数量 + * @param params 查询参数 + * @returns {*} + */ +export const listTableDemo = (current: number, size: number, params: any): AxiosPromise => { + return request({ + url: '/demo/demo/list', + method: 'get', + params: { + pageNum:current, + pageSize: size, + ...params + } + }); +}; + /** * 查询测试单详细 * @param id diff --git a/src/assets/styles/ruoyi.scss b/src/assets/styles/ruoyi.scss index 9526017..a836e3c 100644 --- a/src/assets/styles/ruoyi.scss +++ b/src/assets/styles/ruoyi.scss @@ -193,6 +193,7 @@ h6 { } .el-card__body { + height: 100%; padding: 15px 20px 20px 20px !important; } diff --git a/src/components/CloseBox/main.vue b/src/components/CloseBox/main.vue new file mode 100644 index 0000000..b411aba --- /dev/null +++ b/src/components/CloseBox/main.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/src/components/FormTable/exSlot.tsx b/src/components/FormTable/exSlot.tsx new file mode 100644 index 0000000..e5467c0 --- /dev/null +++ b/src/components/FormTable/exSlot.tsx @@ -0,0 +1,34 @@ +import { defineComponent } from 'vue'; + +export default defineComponent({ + name: 'ex-slot', + props: { + value: [String, Number], + row: Object, + render: Function, + rowIndex: Number, + column: Object, + columnIndex: Number, + class: [String, Array, Object], + style: [String, Array, Object], + dict: [Array] + }, + render() { + const value = this.render({ + value: this.value, + row: this.row, + column: this.column, + rowIndex: this.rowIndex, + columnIndex: this.columnIndex, + dict: this.dict + }); + if (this.style || this.class) { + return ( +
+ {value} +
+ ); + } + return value; + } +}); diff --git a/src/components/FormTable/index.vue b/src/components/FormTable/index.vue new file mode 100644 index 0000000..1a41718 --- /dev/null +++ b/src/components/FormTable/index.vue @@ -0,0 +1,507 @@ + + + + + diff --git a/src/components/SearchForm/formElem.vue b/src/components/SearchForm/formElem.vue new file mode 100644 index 0000000..6d133df --- /dev/null +++ b/src/components/SearchForm/formElem.vue @@ -0,0 +1,213 @@ + + + + + diff --git a/src/components/SearchForm/index.vue b/src/components/SearchForm/index.vue new file mode 100644 index 0000000..781dd69 --- /dev/null +++ b/src/components/SearchForm/index.vue @@ -0,0 +1,185 @@ + + + + + + diff --git a/src/components/SearchPanel/index.vue b/src/components/SearchPanel/index.vue new file mode 100644 index 0000000..e86e702 --- /dev/null +++ b/src/components/SearchPanel/index.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/src/components/ToolbarButton/index.vue b/src/components/ToolbarButton/index.vue new file mode 100644 index 0000000..97f49c1 --- /dev/null +++ b/src/components/ToolbarButton/index.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/src/lang/en_US.ts b/src/lang/en_US.ts index b090d3e..426a75a 100644 --- a/src/lang/en_US.ts +++ b/src/lang/en_US.ts @@ -81,5 +81,74 @@ export default { layoutSetting: 'Layout Setting', personalCenter: 'Personal Center', logout: 'Logout' + }, + // layui-vue国际化 + input: { + placeholder: 'please input' + }, + page: { + previous: 'previous', + next: 'next', + goTo: 'Go to', + confirm: 'confirm', + page: 'page', + item: 'item', + total: 'total' + }, + table: { + filter: 'filter', + export: 'export', + print: 'print' + }, + datePicker: { + year: '', + month: 'month', + sunday: 'SU', + monday: 'MO', + tuesday: 'TU', + wednesday: 'WE', + thursday: 'TH', + friday: 'FR', + saturday: 'SA', + january: 'January', + february: 'February', + march: 'March', + april: 'April', + may: 'May', + june: 'June', + july: 'July', + august: 'August', + september: 'September', + october: 'October', + november: 'November', + december: 'December', + selectDate: 'select date', + selectTime: 'select time', + selectYear: 'select year', + selectMonth: 'select month', + confirm: 'confirm', + cancel: 'cancel', + now: 'now', + startTime: 'start time', + endTime: 'end time' + }, + empty: { + description: 'No data' + }, + upload: { + text: 'Upload files', + dragText: 'Click Upload or drag the file here', + defaultErrorMsg: 'Upload failed', + urlErrorMsg: 'The upload address format is illegal', + numberErrorMsg: 'The number of files uploaded exceeds the specified number', + cutInitErrorMsg: 'Clipping plug-in initialization failed', + uploadSuccess: 'Upload succeeded', + cannotSupportCutMsg: + 'The current version does not support single multiple file clipping. Try to set multiple to false, and get the returned file object through @ done', + occurFileSizeErrorMsg: 'File size warning,The maximum file size cannot exceed target KB', + startUploadMsg: 'Upload Start', + confirmBtn: 'confirm', + cancelBtn: 'cancel', + title: 'title' } }; diff --git a/src/lang/zh_CN.ts b/src/lang/zh_CN.ts index 3cc9872..3a91743 100644 --- a/src/lang/zh_CN.ts +++ b/src/lang/zh_CN.ts @@ -81,5 +81,73 @@ export default { layoutSetting: '布局设置', personalCenter: '个人中心', logout: '退出登录' + }, + // layui-vue国际化 + input: { + placeholder: '请输入' + }, + page: { + previous: '上一页', + next: '下一页', + goTo: '到第', + confirm: '确认', + page: '页', + item: '条', + total: '共' + }, + table: { + filter: '筛选', + export: '导出', + print: '打印' + }, + datePicker: { + year: '年', + month: '月', + sunday: '日', + monday: '一', + tuesday: '二', + wednesday: '三', + thursday: '四', + friday: '五', + saturday: '六', + january: '1月', + february: '2月', + march: '3月', + april: '4月', + may: '5月', + june: '6月', + july: '7月', + august: '8月', + september: '9月', + october: '10月', + november: '11月', + december: '12月', + selectDate: '选择日期', + selectTime: '选择时间', + selectYear: '选择年份', + selectMonth: '选择月份', + confirm: '确认', + cancel: '取消', + now: '现在', + startTime: '开始时间', + endTime: '结束时间' + }, + empty: { + description: '无数据' + }, + upload: { + text: '上传文件', + dragText: '点击上传,或将文件拖拽到此处', + defaultErrorMsg: '上传失败', + urlErrorMsg: '上传地址格式不合法', + numberErrorMsg: '文件上传超过规定的个数', + cutInitErrorMsg: '剪裁插件初始化失败', + uploadSuccess: '上传成功', + cannotSupportCutMsg: '当前版本暂不支持单次多文件剪裁,尝试设置 multiple 为 false, 通过 @done 获取返回文件对象', + occurFileSizeErrorMsg: '文件大小超过限制,文件最大不可超过传入的指定size属性的KB数', + startUploadMsg: '开始上传', + confirmBtn: '确认', + cancelBtn: '取消', + title: '标题' } }; diff --git a/src/layout/components/AppMain.vue b/src/layout/components/AppMain.vue index b6c7b61..6fe4ff4 100644 --- a/src/layout/components/AppMain.vue +++ b/src/layout/components/AppMain.vue @@ -55,9 +55,10 @@ function addIframe() { .app-main { /* 50= navbar 50 */ min-height: calc(100vh - 50px); + height: calc(100vh - 50px); width: 100%; position: relative; - overflow: hidden; + overflow: auto; } .fixed-header + .app-main { @@ -68,6 +69,7 @@ function addIframe() { .app-main { /* 84 = navbar + tags-view = 50 + 34 */ min-height: calc(100vh - 84px); + height: calc(100vh - 84px); } .fixed-header + .app-main { diff --git a/src/main.ts b/src/main.ts index 7a6e4d7..d7a37d0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -8,6 +8,8 @@ import '@/assets/styles/index.scss'; import App from './App.vue'; import store from './store'; import router from './router'; +import Layui from '@layui/layui-vue'; +import '@layui/layui-vue/lib/index.css'; // 自定义指令 import directive from './directive'; @@ -51,6 +53,7 @@ app.use(HighLight); app.use(ElementIcons); app.use(router); app.use(store); +app.use(Layui); app.use(i18n); app.use(VXETable); app.use(plugins); diff --git a/src/utils/common.ts b/src/utils/common.ts new file mode 100644 index 0000000..2d44a81 --- /dev/null +++ b/src/utils/common.ts @@ -0,0 +1,413 @@ +import { validatenull } from './validate'; + +/**树形数组根据key值查找对应data + * Arr 数组 + * value 结果 + * key value对应的key(可为数组) + * condition 多条件判断类型(或:||,且:&&) + **/ +export function getDataFromTreeByKey(Arr, value, key = 'href', condition = '||') { + let Deep, T, F; + if (Arr.length > 0) { + for (F = Arr.length; F; ) { + T = Arr[--F]; + if (typeof key == 'string' && T[key] == value) return T; + if (Array.isArray(key) && key.length > 0) { + const cond = key.map((k) => `T['${k}']==value`); + if (eval(cond.join(condition))) return T; + } + if (T.hasChildren || T.children) { + Deep = getDataFromTreeByKey(T.children, value, key); + if (Deep) return Deep; + } + } + } +} + +//数组转树型数据 +export function toTree(data, parentKey = 'parentId') { + const result = []; + if (!Array.isArray(data)) { + return result; + } + const map = {}; + data.forEach((item) => { + delete item.children; + delete item.hasChildren; + map[item.id] = item; + }); + data.forEach((item) => { + const parent = map[item[parentKey]]; + if (parent && parent.id != '#') { + (parent.children || (parent.children = [])).push(item); + } else { + result.push(item); + } + }); + return result; +} +//对象数组去重 +/* + arr: 数组数据 + uniId: 对象内key +*/ +export function uniqueFunc(arr, uniId) { + const res = new Map(); + return arr.filter((item) => !res.has(getProperty(item, uniId)) && res.set(getProperty(item, uniId), 1)); +} +//去除空的children +export function hasChildren(tree) { + return tree.map((item) => { + if (!item.hasChildren) { + delete item.children; + return item; + } + hasChildren(item.children); + return item; + }); +} + +/* + 补零函数 + e: 需补零数字 + t: 数字固定长度(默认长度2) +*/ +export function digit(e, t = 2) { + let i = ''; + e = String(e); + for (let a = e.length; a < t; a++) i += '0'; + return e < Math.pow(10, t) ? i + (0 | e) : e; +} + +/* + 判断两个对象是否相等(内部key一样,key值一样) + a:对象1 + b:对象2 +*/ +export function isObjValEqual(a, b) { + //取对象a和b的属性名 + const aProps = Object.getOwnPropertyNames(a); + const bProps = Object.getOwnPropertyNames(b); + //判断属性名的length是否一致 + if (aProps.length != bProps.length) { + return false; + } + //循环取出属性名,再判断属性值是否一致 + for (let i = 0; i < aProps.length; i++) { + const propName = aProps[i]; + if (a[propName] !== b[propName]) { + return false; + } + } + return true; +} +/** + * 文件下载 + * file: 文件对象(blob) + * filename: 后台传入文件名 + */ +export function downloadFile({ file, filename }) { + const blob = new Blob([file], { + type: 'application/vnd.ms-excel' + }); + if (window.navigator.msSaveOrOpenBlob) { + navigator.msSaveBlob(blob, filename); + } else { + const link = document.createElement('a'); + link.href = window.URL.createObjectURL(blob); + link.download = filename; + link.click(); + window.URL.revokeObjectURL(link.href); + } +} + +export function formVal(form, data = {}) { + Object.keys(form).forEach((key) => { + if (data[key] !== undefined && data[key] !== null) { + form[key] = data[key]; + } + }); +} + +/** + * @description 对象转url参数 + * @param {object} data,对象 + * @param {Boolean} isPrefix,是否自动加上"?" + * @param {string} arrayFormat 规则 indices|brackets|repeat|comma + */ +export function queryParams(data = {}, isPrefix = true, arrayFormat = 'brackets') { + const prefix = isPrefix ? '?' : ''; + const _result = []; + if (['indices', 'brackets', 'repeat', 'comma'].indexOf(arrayFormat) == -1) arrayFormat = 'brackets'; + for (const key in data) { + const value = data[key]; + // 去掉为空的参数 + if (['', undefined, null].indexOf(value) >= 0) { + continue; + } + // 如果值为数组,另行处理 + if (value.constructor === Array) { + // e.g. {ids: [1, 2, 3]} + switch (arrayFormat) { + case 'indices': + // 结果: ids[0]=1&ids[1]=2&ids[2]=3 + for (let i = 0; i < value.length; i++) { + _result.push(`${key}[${i}]=${value[i]}`); + } + break; + case 'brackets': + // 结果: ids[]=1&ids[]=2&ids[]=3 + value.forEach((_value) => { + _result.push(`${key}[]=${_value}`); + }); + break; + case 'repeat': + // 结果: ids=1&ids=2&ids=3 + value.forEach((_value) => { + _result.push(`${key}=${_value}`); + }); + break; + case 'comma': + // 结果: ids=1,2,3 + let commaStr = ''; + value.forEach((_value) => { + commaStr += (commaStr ? ',' : '') + _value; + }); + _result.push(`${key}=${commaStr}`); + break; + default: + value.forEach((_value) => { + _result.push(`${key}[]=${_value}`); + }); + } + } else { + _result.push(`${key}=${value}`); + } + } + return _result.length ? prefix + _result.join('&') : ''; +} + +export function deepClone(obj) { + const newObj = Array.isArray(obj) ? [] : {}; + if (obj && typeof obj === 'object') { + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + newObj[key] = obj && typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key]; + } + } + } + return newObj; +} + +/** + * @description 获取某个对象下的属性,用于通过类似'a.b.c'的形式去获取一个对象的的属性的形式 + * @param {object} obj 对象 + * @param {string} key 需要获取的属性字段 + * @returns {*} + */ +export function getProperty(obj, key) { + if (!obj) { + return; + } + if (typeof key !== 'string' || key === '') { + return ''; + } + if (key.indexOf('.') !== -1) { + const keys = key.split('.'); + let firstObj = obj[keys[0]] || {}; + + for (let i = 1; i < keys.length; i++) { + if (firstObj) { + firstObj = firstObj[keys[i]]; + } + } + return firstObj; + } + return obj[key]; +} + +export function findLastIndex(arr, callback, thisArg) { + for (let index = arr.length - 1; index >= 0; index--) { + const value = arr[index]; + if (callback.call(thisArg, value, index, arr)) { + return index; + } + } + return -1; +} + +export function startToEnd(startTime, endTime = new Date(), equation = true) { + if (equation) { + return Number(new Date(startTime)) <= Number(new Date(endTime)); + } + return Number(new Date(startTime)) < Number(new Date(endTime)); +} + +/** + * 四舍五入,保留小数 + * @param {需要保留小数的值} val + * @param {保留几位小数} decimal + * @returns + */ +export function toDecimal(val, decimal = 2) { + const power = Math.pow(10, decimal); + let f = parseFloat(val); + if (isNaN(f)) { + return ''; + } + f = Math.round(val * power) / power; + return f; +} + +/** + * 根据年月获取月份天数 + * @param {*} month + * @param {*} year + * @returns + */ + +export function getEndDate(month = new Date().getMonth() + 1, year = new Date().getFullYear()) { + const a = new Date(); + return a.setFullYear(year, month, 1), new Date(a.getTime() - 864e5).getDate(); +} + +/** + * 数字转中文 + * @param {数字} value + * @returns + */ +export function numberToChanie(value) { + const chnNumChar = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']; + return chnNumChar[value * 1]; +} + +/** + * 获取视频时长 + * @param {视频文件对象} file + * @returns + */ + +export function getDuration(file) { + const fileName = file.name || ''; + const ext = fileName.split('.')[fileName.split('.').length - 1]; + return new Promise((resolve, reject) => { + if (ext != 'mp4') { + reject('请上传MP4文件'); + return; + } + const url = URL.createObjectURL(file); + const audioElement = new Audio(url); + audioElement.addEventListener('loadedmetadata', (_event) => { + resolve(audioElement.duration); + }); + }); +} + +/** + * 秒钟数转小时,分钟,秒数 + * @param {秒钟数} seconds + * @returns + */ +export function getTimes(seconds) { + const h = parseInt((seconds / 60 / 60) % 24); + const m = parseInt((seconds / 60) % 60); + const s = parseInt(seconds % 60); + let val = ''; + if (h > 0) { + val += `${h}小时`; + } + if (m > 0) { + val += `${m}分钟`; + } + if (!isNaN(s)) { + val += `${s}秒`; + } + return val; +} + +/** + * File文件对象转Bolb对象 + * @param {文件对象} file + */ +export function FileToBolb(file) { + return new Promise((resolve) => { + const reader = new FileReader(); + reader.onload = function (e) { + const arr = e.target.result.split(','); + const data = window.atob(arr[1]); + const mime = arr[0].match(/:(.*?);/)[1]; + const ia = new Uint8Array(data.length); + for (let i = 0; i < data.length; i++) { + ia[i] = data.charCodeAt(i); + } + resolve(new Blob([ia], { type: mime })); + }; + reader.readAsDataURL(file); + }); +} +/** + * blob文件对象转File文件对象 + * @param {文件blob对象} blob + * @param {文件名} fileName + * @param {文件类型} fileType + * @returns + */ +export function BlobToFile(blob, fileName, fileType) { + return new window.File([blob], fileName, { type: fileType }); +} + +/** + * 数据对象转FormData + * @param {数据对象} obj + * @returns + */ +export function ObjToForm(obj) { + const formData = new FormData(); + if (obj && Object.keys(obj).length > 0) { + Object.keys(obj).forEach((key) => { + const val = obj[key]; + formData.append(key, val); + }); + } + return formData; +} + +/** + * 密码强度校验 + * @param {密码} pwd + * @returns + */ +export function checkPwd(pwd) { + let level = 0; + if (pwd.length < 8) { + level = 1; + } else { + level = 0; + if (pwd.match(/\d/g)) { + level++; + } + if (pwd.match(/[a-z]/gi)) { + level++; + } + if (pwd.match(/[A-Z]/gi)) { + level++; + } + if (pwd.match("[`~!@#$^&*()=|{}':;',\\[\\].<>《》./~!@#¥……&*()——|{}【】‘;:”“'。,、? ]")) { + level++; + } + } + return level; +} + +/** + * 金额格式化 + * @param {金额} value + * @returns + */ +export function toLocaleString(value, min = 2, max = 7) { + const imumFractionDigits = { minimumFractionDigits: min, maximumFractionDigits: max }; + if (validatenull(value) || isNaN(value)) { + return ''; + } + return value.toLocaleString('en-US', imumFractionDigits); +} diff --git a/src/utils/validate.ts b/src/utils/validate.ts index e2886e4..8f9bdb9 100644 --- a/src/utils/validate.ts +++ b/src/utils/validate.ts @@ -106,3 +106,26 @@ export const isArray = (arg: string | string[]) => { } return Array.isArray(arg); }; + +/** + * 判断是否为空 + */ +export function validatenull(val) { + if (typeof val == 'boolean') { + return false; + } + if (typeof val == 'number') { + return false; + } + if (val instanceof Array) { + if (val.length == 0) return true; + } else if (val instanceof Object) { + if (JSON.stringify(val) === '{}') return true; + } else { + if (val == 'null' || val == null || val == 'undefined' || val == undefined || val == ''){ + return true; + } + return false; + } + return false; +} diff --git a/src/views/demo/tableDemo/index.vue b/src/views/demo/tableDemo/index.vue new file mode 100644 index 0000000..dca9437 --- /dev/null +++ b/src/views/demo/tableDemo/index.vue @@ -0,0 +1,256 @@ + + + + + diff --git a/vite/plugins/index.ts b/vite/plugins/index.ts index 9ef8faf..ffc93dd 100644 --- a/vite/plugins/index.ts +++ b/vite/plugins/index.ts @@ -1,4 +1,5 @@ import vue from '@vitejs/plugin-vue'; +import vueJsx from '@vitejs/plugin-vue-jsx'; import vueDevTools from 'vite-plugin-vue-devtools'; import createUnoCss from './unocss'; @@ -11,8 +12,14 @@ import createSetupExtend from './setup-extend'; import path from 'path'; export default (viteEnv: any, isBuild = false): [] => { - const vitePlugins: any = []; - vitePlugins.push(vue()); + const vitePlugins: any = [ + vue({ + script: { + defineModel: true + } + }), + vueJsx() + ]; vitePlugins.push(vueDevTools()); vitePlugins.push(createUnoCss()); vitePlugins.push(createAutoImport(path)); -- Gitee