init commit & 混料程序模块

This commit is contained in:
lb
2023-01-16 11:08:54 +08:00
commit 55b1918419
242 changed files with 12171 additions and 0 deletions

0
src/components/.gitkeep Normal file
View File

301
src/components/AttrForm.vue Normal file
View File

@@ -0,0 +1,301 @@
<template>
<div class="attr-form">
<h3>
{{ title }} <el-button style="margin-left: 8px;" type="text" v-if="!isDetail && !showAddAttr" @click="showAddAttr = true">{{ $t('add') }}</el-button>
</h3>
<div v-if="!showAddAttr">
<component
key="sub-table"
:is="require('./BaseTable.vue').default"
:table-head-configs="filterTableConfigs()"
:data="dataList"
:page="pageIndex"
:size="pageSize"
:max-height="calcMaxHeight(8)"
@operate-event="handleOperations"
/>
<el-pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
:current-page="pageIndex"
:page-sizes="[5, 10, 20, 50]"
:page-size="pageSize"
:total="totalPage"
layout="total, sizes, prev, pager, next, jumper"
>
</el-pagination>
</div>
<div v-else style="background: #eee; border-radius: 8px; padding: 12px;">
<el-row>
<el-col>
<el-form ref="AttrForm" :model="AttrForm" :rules="AttrFormRules" :inline="true" label-position="top">
<el-row :gutter="20" style="padding: 0 24px;">
<el-col :span="attrFormFields.length > 6 ? 6 : 12" v-for="field in attrFormFields" :key="field.prop + 'col'">
<el-form-item :key="field.prop" :prop="field.prop" :label="field.name" style="width: 100%">
<el-input v-if="field.formType === 'input' || !field.formType" v-model="AttrForm[field.prop]" :placeholder="$t('hints.input')" clearable />
<el-select v-if="field.formType === 'select'" v-model="AttrForm[field.prop]" clearable>
<el-option v-for="opt in field.formOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
</el-select>
<!-- add more... -->
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-col>
</el-row>
<el-row style="text-align: right;">
<el-button size="small" @click="handleCloseAttrForm">{{ $t('cancel') }}</el-button>
<el-button type="success" size="small" @click="handleSaveAttrForm">{{ $t('save') }}</el-button>
</el-row>
</div>
</div>
</template>
<script>
import i18n from '@/i18n'
import BaseTable from '@/components/base-table'
import { pick } from 'lodash/object'
/** 计算表格的最大高 */
function calcMaxHeight(num) {
const FIXED_HEIGHT = 220
let clientHeight = 0
const bodyHeight = document.body.clientHeight || null
const documentHeight = document.documentElement.clientHeight || null
if (bodyHeight && documentHeight) {
clientHeight = Math.max(bodyHeight, documentHeight)
} else {
clientHeight = documentHeight ? documentHeight : bodyHeight
}
const finalHeight = clientHeight - num - FIXED_HEIGHT
return finalHeight > 0 ? finalHeight : -finalHeight
}
export default {
name: 'AttrForm',
components: { BaseTable },
props: {
isDetail: {
type: Boolean,
default: false
},
visible: {
type: Boolean,
default: false
},
/** subtable 需要设置的属性 */
title: {
type: String,
default: ''
},
url: {
type: String,
default: ''
},
tableConfigs: {
type: Array,
default: () => []
},
/** 表单提交需要的属性 */
relatedId: {
type: String,
required: true,
default: null
},
relatedField: {
type: String,
required: true,
default: null
}
},
data() {
return {
calcMaxHeight,
showAddAttr: false,
dataList: [],
pageIndex: 1,
pageSize: 10,
totalPage: 0,
AttrForm: {}, // 需动态设置
AttrFormRules: {} // 需动态设置
}
},
computed: {
attrFormFields() {
const _ = this.tableConfigs.filter(item => item.formField)
/** 顺带配置 AttrForm */
_.forEach(item => {
this.$set(this.AttrForm, [item.prop], '')
})
this.$set(this.AttrForm, 'id', null)
return _
}
},
mounted() {
this.getDataList()
/** 设置 AttrForm 的 rules */
for (const config of this.tableConfigs) {
if (config.rules) {
this.$set(this.AttrFormRules, [config.prop], config.rules)
}
}
},
methods: {
filterTableConfigs() {
if (this.isDetail) {
/** 如果是查看详情,就屏蔽操作列 */
return this.tableConfigs.filter(opt => opt.prop !== 'operations')
}
return this.tableConfigs
},
/** init dataform */
initAttrForm() {
Object.entries(this.AttrForm).forEach(([key, value]) => {
if (typeof value === 'object' || typeof value === 'number') {
this.AttrForm[key] = null
} else if (Array.isArray(value)) {
this.AttrForm[key] = []
} else {
this.AttrForm[key] = ''
}
})
},
/** requests */
getDataList() {
this.dataListLoading = true
// 获取动态属性列表
this.$http({
url: this.$http.adornUrl(`${this.url}/page`),
method: 'get',
params: this.$http.adornParams({
page: this.pageIndex,
limit: this.pageSize,
[this.relatedField]: this.relatedId
// order: 'asc/desc',
// orderField: 'name'
})
}).then(({ data: res }) => {
if (res && res.code === 0) {
this.dataList = res.data.list
this.totalPage = res.data.total
} else {
this.dataList = []
this.totalPage = 0
}
this.dataListLoading = false
})
},
/** handlers */
handleOperations({ type, data: id }) {
switch (type) {
case 'edit':
{
this.showAddAttr = true
this.$nextTick(() => {
this.$http.get(this.$http.adornUrl(`${this.url}/${id}`)).then(({ data: res }) => {
if (res && res.code === 0 && res.data) {
const neededFields = [...this.attrFormFields.map(item => item.prop), 'id']
const filtered = pick(res.data, neededFields)
for (let field of neededFields) {
this.AttrForm[field] = filtered[field]
}
}
})
})
}
break
case 'delete':
return this.deleteHandle(id)
}
},
deleteHandle(id) {
var ids = id ? [id] : []
this.$confirm(`${i18n.t('prompt.info', { handle: id ? i18n.t('delete').toLowerCase() : i18n.t('deleteBatch').toLowerCase() })}`, i18n.t('prompt.title'), {
confirmButtonText: i18n.t('confirm'),
cancelButtonText: i18n.t('cancel'),
type: 'warning'
}).then(() => {
this.$http({
url: this.$http.adornUrl(this.url),
method: 'delete',
data: this.$http.adornData(ids, false, 'raw')
}).then(({ data }) => {
if (data && data.code === 0) {
this.$message({
message: i18n.t('prompt.success'),
type: 'success',
duration: 1500,
onClose: () => {
this.getDataList()
}
})
} else {
this.$message.error(data.msg)
}
})
})
},
handleCloseAttrForm() {
this.showAddAttr = false
this.initAttrForm()
},
handleSaveAttrForm() {
this.$refs['AttrForm'].validate(valid => {
if (valid) {
this.$http({
// url: this.$http.adornUrl(`${this.url}/${!this.AttrForm.id ? '' : this.AttrForm.id}`),
url: this.$http.adornUrl(this.url),
method: this.AttrForm.id ? 'put' : 'post',
headers: {
'Content-Type': 'application/json'
},
data: JSON.stringify({ ...this.AttrForm, [this.relatedField]: this.relatedId })
}).then(({ data }) => {
if (data && data.code === 0) {
this.$message({
message: i18n.t('prompt.success'),
type: 'success',
duration: 1500,
onClose: () => {
this.showAddAttr = false
this.getDataList()
this.initAttrForm()
}
})
} else {
this.$message.error(data.msg)
}
})
}
})
},
// 每页数
sizeChangeHandle(val) {
this.pageSize = val
this.pageIndex = 1
this.getDataList()
},
// 当前页
currentChangeHandle(val) {
this.pageIndex = val
this.getDataList()
}
}
}
</script>
<style scoped>
.attr-form >>> .el-form .el-form-item__label {
padding: 0;
}
</style>

View File

@@ -0,0 +1,30 @@
<template>
<!-- 同意或不同意组件点击不同意的时候出现额外的输入框说明原因 -->
<el-row>
<el-button type="primary" @click="doAgree">同意</el-button>
<el-button @click="dontAgree">不同意</el-button>
</el-row>
</template>
<script>
export default {
name: '',
props: {},
emits: ['not-agree', 'agree'],
data() {
return {
reason: 'dont agree reason',
};
},
methods: {
doAgree() {
this.$emit('agree');
},
dontAgree() {
this.$emit('not-agree', this.reason);
},
},
};
</script>
<style scoped></style>

View File

@@ -0,0 +1,649 @@
<template>
<el-dialog
class="super-flexible-dialog"
:title="isDetail ? title.detail : !dataForm.id ? title.add : title.edit"
:visible.sync="visible"
@close="handleClose"
:distory-on-close="true"
:close-on-click-modal="false">
<div style="max-height: 60vh; overflow-y: scroll; overflow-x: hidden">
<el-form ref="dataForm" :model="dataForm" :rules="dataFormRules">
<!-- 如果需要更精细一点的布局可以根据配置项实现地再复杂一点但此处暂时全部采用一行两列布局 -->
<el-row v-for="n in rows" :key="n" :gutter="20">
<el-col v-for="c in COLUMN_PER_ROW" :key="`${n}+'col'+${c}`" :span="getSpan(n, c)">
<!-- <el-col v-for="c in COLUMN_PER_ROW" :key="`${n}+'col'+${c}`" :span="24 / COLUMN_PER_ROW"> -->
<!-- :class="{ 'hidden-input': configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].hidden }" -->
<el-form-item
v-if="configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)]"
:prop="configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].name"
:key="`${n}-col-${c}-item`"
:label="getLabel(n, c)">
<!-- 暂时先不实现部分输入方式 -->
<el-input
v-if="getType(n, c) === 'input'"
:placeholder="getPlaceholder(n, c)"
v-model="dataForm[configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].name]"
clearable
:disabled="isDetail" />
<el-radio
v-if="getType(n, c) === 'radio'"
v-model="dataForm[configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].name]"
:disabled="isDetail" />
<el-checkbox
v-if="getType(n, c) === 'check'"
v-model="dataForm[configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].name]"
:disabled="isDetail" />
<el-select
v-if="getType(n, c) === 'select'"
:placeholder="getPlaceholder(n, c)"
v-model="dataForm[configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].name]"
clearable
:disabled="isDetail"
@change="emitSelectChange(configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].name, $event)">
<el-option
v-for="opt in configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].options"
:key="opt.label + Math.random()"
:label="opt.label"
:value="opt.value" />
</el-select>
<el-switch
v-if="getType(n, c) === 'switch'"
v-model="dataForm[configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].name]"
:disabled="isDetail" />
<el-cascader
v-if="getType(n, c) === 'cascader'"
v-model="dataForm[configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].name]"
:options="configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].options"
:props="configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].props"
:disabled="isDetail"
clearable />
<el-time-select
v-if="getType(n, c) === 'time'"
v-model="dataForm[configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].name]"
:disabled="isDetail" />
<el-date-picker
v-if="getType(n, c) === 'date'"
v-bind="configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].props"
:placeholder="getPlaceholder(n, c)"
v-model="dataForm[configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)].name]"
:disabled="isDetail" />
</el-form-item>
</el-col>
</el-row>
<!-- extra components , like Markdown or RichEdit -->
<template v-if="configs.extraComponents && configs.extraComponents.length > 0">
<el-form-item
v-for="(ec, index) in configs.extraComponents"
:key="ec.name + index"
:label="ec.label"
class="extra-components">
<component
style="margin-top: 40px"
v-if="ec.hasModel"
:is="ec.component"
v-bind="ec.props"
v-model="dataForm[ec.name]"
@ready="handleEditorReady"
:read-only="isDetail" />
<!-- <component v-if="ec.hasModel" :is="ec.component" v-bind="ec.props" v-model="dataForm[ec.name]" /> -->
<component
v-else
:is="ec.component"
v-bind="ec.props"
@uploader-update-filelist="handleUploadListUpdate($event, ec.props.extraParams.typeCode)"
:uploader-inject-file-list="/*用于设备分流的*/ fileList[ec.props.extraParams.typeCode]"
:read-only="isDetail" />
</el-form-item>
</template>
</el-form>
<!-- <template v-if="dataForm.id && configs.subtable">
<attr-form :related-id="dataForm.id" v-bind="configs.subtable" :is-detail="isDetail" />
</template> -->
</div>
<span slot="footer" class="dialog-footer">
<template v-for="(operate, index) in configs.operations">
<!-- {{ operate.name | btnNameFilter }} -->
<el-button
v-if="
!isDetail &&
(operate.showAlways ||
(((dataForm.id && operate.showOnEdit) || (!dataForm.id && !operate.showOnEdit)) &&
(operate.permission ? $hasPermission(operate.permission) : true)))
"
:key="`operate-${index}`"
:type="btnType[operate.name]"
@click="handleClick(operate)"
>{{ btnName[operate.name] }}</el-button
>
</template>
<el-button v-if="isDetail" @click="handleClick({ name: 'cancel' })">{{ $t('cancel') }}</el-button>
</span>
</el-dialog>
</template>
<script>
// import CKEditor from 'ckeditor4-vue'
// import AttrForm from './AttrForm'
// import { pick } from 'lodash/object'
import { pick as __pick } from '@/utils/filters';
// import i18n from '@/i18n'
import $http from '@/utils/request';
const i18n = {
t: (a) => a,
};
// 标题 for i18n
const title = {
// detail: i18n.t('detail'),
// add: i18n.t('add'),
// edit: i18n.t('edit'),
detail: '详情',
add: '添加',
edit: '编辑',
};
// 或者也可以改造成自定义颜色:
const btnType = {
save: 'success',
update: 'primary',
reset: 'text',
// cancel: 'text'
// add more...
};
const btnName = {
save: '保存',
update: '更新',
reset: '重置',
cancel: '取消',
// for i18n
// save: i18n.t('save'),
// update: i18n.t('update'),
// reset: i18n.t('reset'),
// cancel: i18n.t('cancel'),
// add more...
};
// 每行的列数
const COLUMN_PER_ROW = 2;
export default {
name: 'AddOrUpdateDialog',
// components: { AttrForm },
props: {
configs: {
type: Object,
default: () => ({}), // 此处省去类型检查,使用者自行注意就好
},
url: {
type: Object,
default: () => ({}), // 此处省去类型检查,使用者自行注意就好
},
},
filters: {
nameFilter: function (name) {
if (!name) return null;
// for i18n
const defaultNames = {
name: '名称',
code: '编码',
remark: '备注',
description: '描述',
specifications: '规格',
// name: i18n.t('name'),
// code: i18n.t('code'),
// remark: i18n.t('remark'),
// description: i18n.t('desc'),
// specifications: i18n.t('prod.spec'),
// add more...
};
return defaultNames[name];
},
},
// provide() {
// return {
// _df: this.dataForm
// }
// },
data() {
return {
COLUMN_PER_ROW,
title,
/** 按钮相关属性 */
btnName,
btnType,
defaultNames: {
name: '名称',
code: '编码',
remark: '备注',
description: '描述',
specifications: '规格',
// add more...
// name: i18n.t('name'),
// code: i18n.t('code'),
// remark: i18n.t('remark'),
// description: i18n.t('desc'),
// specifications: i18n.t('prod.spec'),
// add more...
},
defaultPlaceholders: {}, // 自动根据 defaultNames 计算得来
/** 表单相关属性 */
visible: false,
isEdit: false,
isDetail: false,
dataForm: {},
dataFormRules: {},
tempForm: [], // 临时保存自动生成的code或其他数据
shouldWait: null,
fileForm: {}, // 文件上传分流用、合并用的表单,根据 typeCode 进行分流,在请求时合并
fileList: {}, // 文件加载时分流,依据 typeCode
};
},
computed: {
rows() {
// 本组件只实现了'一行两列'的表单布局
return Math.ceil(this.configs.fields.length / COLUMN_PER_ROW);
},
},
created() {
/** load lang */
// CKEditor.load()
// console.log('lang', CKEditor.component.props.config.defaultLanguage = 'en' )
},
mounted() {
/** 计算 defaultPlaceholders */
// const prefix = i18n.t('hints.input');
const prefix = '请输入';
Object.entries(this.defaultNames).map(([key, value]) => {
this.defaultPlaceholders[key] = prefix + value;
});
/** 转换 configs.fields 的结构,把纯字符串转为对象 */
this.$nextTick(() => {
this.configs.fields = this.configs.fields.map((item) => {
if (typeof item === 'string') {
return { name: item };
}
return item;
});
/** 动态设置dataForm字段 */
this.configs.fields.forEach((item) => {
this.$set(this.dataForm, [item.name], '');
/** select 的默认值设置 */
if (item.type === 'select') {
const opts = item.options || [];
const dft = opts.find((item) => item.default || false);
if (dft) {
this.$set(this.dataForm, [item.name], dft.value);
}
}
if (item.api) {
/** 自动请求并填充 */
// or this.shouldWaitPool = []
this.shouldWait =
// this.$http({
// url: this.$http.adornUrl(item.api),
// method: 'POST', // 也可以改成动态决定
// })
$http
.get(item.api)
// .then(({ data: res }) => {
.then((res) => {
if (res && res.code === 0) {
// this.dataForm[item.name] = res.data // <=== 此处需要对接口
this.tempForm.push({ name: item.name, data: res.data });
}
});
} // end if (item.api)
// 如果有 relatedField就需要在当前item的数据加载后刷新 relatedField 的列表
if (item.relatedField) {
this.$watch(
function () {
return this.dataForm[item.name];
},
function (val, old) {
if (val && val !== old) {
this.$emit('select-change', { name: item.name, id: val });
}
},
{ deep: true, immediate: true }
);
}
if (item.required) {
const requiredRule = {
required: true,
message: '必填项',
// message: i18n.t('validate.required'),
// trigger: 'change'
trigger: 'blur',
};
/** 检查是否已经存在该字段的规则 */
const exists = this.dataFormRules[item.name] || null;
/** 设置验证规则 */
if (exists) {
const unset = true;
for (const rule of exists) {
if (rule.required) unset = false;
}
if (unset) {
exists.push(requiredRule);
}
} else {
/** 不存在已有规则 */
this.$set(this.dataFormRules, [item.name], [requiredRule]);
}
} // end if (item.required)
if (item.rules) {
const exists = this.dataFormRules[item.name] || null;
if (exists) {
// 浅拷贝过去
exists.push(...item.rules);
} else {
this.$set(this.dataFormRules, [item.name], [...item.rules]);
}
} // end if (item.rules)
});
/** 计算默认值 */
function calDefault(type) {
switch (type) {
case 'array':
return [];
// more case...
default:
return '';
}
}
/** 检查是否需要额外的组件 */
this.configs.extraComponents &&
this.configs.extraComponents.forEach((item) => {
// if (Object.hasOwn(this.dataForm, [item.name])) {
if (this.dataForm.hasOwnProperty(item.name)) {
return;
} else {
this.$set(this.dataForm, [item.name], calDefault(item.fieldType));
}
});
/** 单独设置 id */
this.$set(this.dataForm, 'id', null);
});
},
methods: {
getSpan(n, c) {
const opt = this.configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)];
return opt && opt.span ? opt.span : 24 / COLUMN_PER_ROW;
},
getLabel(n, c) {
const opt = this.configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)];
if (opt) {
// if opt is valid
return opt.label ? opt.label : this.defaultNames[opt.name];
}
},
getPlaceholder(n, c) {
if (this.isDetail) {
/** 如果是详情,就不展示 提示文本 */
return '';
}
const opt = this.configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)];
if (opt) {
// if opt is valid
return opt.placeholder
? opt.placeholder
: this.defaultPlaceholders[opt.name]
? this.defaultPlaceholders[opt.name]
: opt.label
? (opt.type === 'select' ? '请选择' : '请输入') + opt.label
: // ? (opt.type === 'select' ? i18n.t('choose') : i18n.t('hints.input')) + opt.label
null;
// : opt.type === 'select'
// ? i18n.t('choose')
// : '请输入'
}
},
getType(n, c) {
const opt = this.configs.fields[(n - 1) * COLUMN_PER_ROW + (c - 1)];
if (opt) {
if (!opt.type || ['input', 'number' /** add more.. */].includes(opt.type)) {
return 'input';
} else if (['select' /** add more.. */].includes(opt.type)) {
return 'select';
} else if (['cascader'].includes(opt.type)) {
return 'cascader';
} else if (['date'].includes(opt.type)) {
return 'date';
}
// add more...
} else {
return 'input';
}
},
init(id, isdetail = false) {
this.isDetail = isdetail;
this.visible = true;
this.$nextTick(() => {
this.$refs['dataForm'].resetFields();
this.dataForm.id = id || null;
if (this.dataForm.id) {
// this.$http({
// url: this.$http.adornUrl(`${this.configs.infoUrl}/${this.dataForm.id}`),
// method: 'get'
// })
$http.get(this.url.base + `/${this.dataForm.id}`).then((res) => {
console.log('[base dialog init] ', res);
if (res && res.code === 0) {
const dataFormKeys = Object.keys(this.dataForm);
console.log('keys ===> ', dataFormKeys);
// console.log('data form keys: ', dataFormKeys, pick(res.data, dataFormKeys))
this.dataForm = __pick(res.data, dataFormKeys);
console.log('pick(res.data, dataFormKeys) ===> ', __pick(res.data, dataFormKeys));
// LABEL: FILE_RELATED
/** 对文件下载进行分流 */
this.fileList = {};
if (this.dataForm.files) {
// console.log('files: ', this.dataForm.files)
this.dataForm.files.forEach((file) => {
// const fileName = file.fileUrl.split('/').pop()
/** [1] 处理 fileList */
// if (Object.hasOwn(this.fileList, file.typeCode)) {
if (this.fileList.hasOwnProperty(file.typeCode)) {
/** 已存在 */
// this.fileList[file.typeCode].push({ id: file.id, name: fileName, typeCode: file.typeCode })
this.fileList[file.typeCode].push(file);
} else {
// this.fileList[file.typeCode] = [{ id: file.id, name: fileName, typeCode: file.typeCode }]
this.fileList[file.typeCode] = [file];
}
/** [2] 处理 fileForm */
// if (Object.hasOwn(this.fileForm, file.typeCode)) {
if (this.fileForm.hasOwnProperty(file.typeCode)) {
this.fileForm[file.typeCode].push(file.id);
} else {
this.fileForm[file.typeCode] = [file.id];
}
});
}
}
});
} else {
/** 如果不是编辑,就填充自动生成的数据 */
if (this.shouldWait)
this.shouldWait.then(() => {
if (this.tempForm.length) {
// console.log('create new, tempform', JSON.stringify(this.tempForm.length))
this.tempForm.forEach((item) => {
// console.log('item data', item.data)
this.dataForm[item.name] = item.data;
});
// console.log('create new, dataform', JSON.stringify(this.dataForm))
}
this.shouldWait = null;
});
}
});
},
emitSelectChange(name, id) {
const currentField = this.configs.fields.find((item) => item.name === name);
if (currentField.relatedField) {
this.dataForm[currentField.relatedField] = null;
}
this.$emit('select-change', { name, id });
},
handleEditorReady(val) {},
handleClick(btn) {
/** 提取url */
// const urls = {};
// this.configs.operations.map((item) => {
// urls[item.name] = {};
// urls[item.name].url = item.url;
// urls[item.name].extraFields = item.extraFields || {};
// });
/** 操作 */
switch (btn.name) {
case 'save':
case 'update':
/** 需要验证表单的操作 */
this.$refs['dataForm'].validate((valid) => {
if (valid) {
/** 对于文件上传的单独处理(合并处理) */
if (Object.keys(this.fileForm).length) {
// LABEL: FILE_RELATED
let fileIds = [];
for (const [key, item] of Object.entries(this.fileForm)) {
if (Array.isArray(item)) {
fileIds = fileIds.concat(item);
} else {
console.error('handleClick(): 上传文件数组类型不正确');
}
}
this.$set(this.dataForm, 'fileIds', fileIds);
}
// console.log('before send: ', this.dataForm)
// this.$http({
// url: this.$http.adornUrl(urls[btn.name].url),
// method: btn.name === 'save' ? 'POST' : 'PUT',
// data: { ...this.dataForm, ...urls[btn.name].extraFields },
// })
$http[btn.name === 'save' ? 'post' : 'put'](btn.url, { ...this.dataForm, ...btn.extraFields })
.then((res) => {
// .then(({ data: res }) => {
console.log('save res: ', res, btn.name, this.dataForm);
if (res && res.code === 0) {
this.$message({
message: '操作成功!',
// message: i18n.t('prompt.success'),
// message: btn.name === 'save' ? i18n.t('prompt.success') : '更新成功!',
type: 'success',
duration: 1500,
onClose: () => {
this.$emit('refreshDataList');
this.visible = false;
},
});
} else {
this.$message.error(res.msg);
}
})
.catch((err) => {
this.$message({
message: err,
type: 'error',
duration: 2000,
});
});
}
});
return;
case 'reset':
for (const key of Object.keys(this.dataForm)) {
if (typeof this.dataForm[key] === 'string') {
this.dataForm[key] = '';
} else if (this.dataForm[key] instanceof Array) {
this.dataForm[key].splice(0);
} else {
this.dataForm[key] = null;
}
}
break;
case 'cancel':
this.handleClose();
// add more..
}
},
// LABEL: FILE_RELATED
handleUploadListUpdate(filelist, typeCode = 'DefaultTypeCode') {
// console.log('before handleUploadListUpdate(): ', JSON.parse(JSON.stringify(this.fileForm)))
// 设备类型 typeCode: EquipmentTypeFile
// 设备信息 typeCode: EquipmentInfoFile | EquipmentInfoImage
// 原先写法:直接更新 dataForm 对象,不适用于有多个上传组件的需求
// this.$set(
// this.dataForm,
// 'fileIds',
// filelist.map(item => item.id)
// )
// console.log('handleUploadListUpdate(): ', this.dataForm)
// 现更改为分流写法
this.$set(
this.fileForm,
typeCode,
filelist.map((item) => item.id)
);
// console.log('after handleUploadListUpdate(): ', this.fileForm)
},
handleClose() {
this.$emit('destory-dialog');
this.visible = false;
},
},
};
</script>
<style scoped>
.super-flexible-dialog >>> .el-select,
.super-flexible-dialog >>> .el-cascader {
width: 100%;
}
.super-flexible-dialog >>> ::-webkit-scrollbar {
width: 4px;
border-radius: 4px;
background: #fff;
}
.super-flexible-dialog >>> ::-webkit-scrollbar-thumb {
width: 4px;
border-radius: 4px;
background: #ccc;
}
.super-flexible-dialog >>> .hidden-input {
display: none;
}
</style>

View File

@@ -0,0 +1,19 @@
<!-- 基本抽屉 -->
<template>
<div></div>
</template>
<script>
export default {
name: '',
props: {},
data() {
return {};
},
created() {},
mounted() {},
methods: {},
};
</script>
<style scoped></style>

View File

@@ -0,0 +1,120 @@
<!-- 这里单纯的配置表格就好了-->
<template>
<div class="base-list-table w-full">
<el-table :data="tableData" v-bind="tableConfig" ref="base-list-table">
<!-- @cell-mouse-enter="(row, col, cell, event) => $emit('cell-mouse-enter', row, col, cell, event)"> -->
<!-- @cell-mouse-leave="(row, col, cell, event) => $emit('cell-mouse-leave', row, col, cell, event)"> -->
<!-- 表格头定义 -->
<template v-for="(head, idx) in columnConfig">
<!-- 索引列 -->
<el-table-column
:key="idx"
v-if="head.type"
:type="head.type"
:label="head.label || head.name || ''"
:header-align="head.align || 'center'"
:align="head.align || 'center'"
:width="head.width || 50"
:index="
head.type === 'index'
? (val) => {
return val + 1 + (page - 1) * size;
}
: null
"
v-bind="head.more"></el-table-column>
<!-- 普通的表头 -->
<el-table-column
v-else
:key="idx + 'else'"
:label="head.label ? head.label : head.name"
:prop="head.prop || null"
:width="head.width || null"
:min-width="head.minWidth || null"
:fixed="head.fixed || null"
:show-overflow-tooltip="head.showOverflowTooltip || true"
:tooltip-effect="head.tooltipEffect || 'light'"
filter-placement="top"
:align="head.align || null"
v-bind="head.more">
<!-- 子组件 -->
<template v-if="head.prop" slot-scope="scope">
<component
v-if="head.subcomponent"
:is="head.subcomponent"
:key="idx + 'sub'"
:inject-data="{ ...scope.row, head }"
@emit-data="handleSubEmitData" />
<!-- 直接展示数据或应用过滤器 -->
<span v-else>{{ scope.row[head.prop] | commonFilter(head.filter) }}</span>
</template>
<!-- 多级表头 -->
<template v-if="!head.prop && head.children">
<TableHead
v-for="(subhead, subindex) in head.children"
:key="'subhead-' + idx + '-subindex-' + subindex"
:opt="subhead" />
</template>
</el-table-column>
</template>
</el-table>
</div>
</template>
<script>
import TableHead from './TableHead.vue';
import $http from '@/utils/request'
// TODO:
// 1. 表格拖拽开启/关闭
// 2. 表格的样式'
// 3. more...
export default {
name: 'BaseListTable',
components: { TableHead },
filters: {
commonFilter: (source, filterType = (a) => a) => {
return filterType(source);
},
},
props: {
tableConfig: {
type: Object,
default: () => ({}),
},
columnConfig: {
type: Array,
default: () => [],
},
tableData: {
type: Array,
default: () => [],
}
},
inject: ['urls'],
data() {
return {
page: 1,
size: 20, // 默认20
dataList: []
};
},
watch: {
// 'props.tableData': {
// handler: () => {
// this.$refs['base-list-table'].doLayout();
// },
// immediate: true,
// },
},
methods: {
handleSubEmitData(payload) {
console.log('[component] BaseListTable handleSubEmitData(): ', payload);
this.$emit('operate-event', payload);
},
},
};
</script>
<style scoped></style>

View File

@@ -0,0 +1,167 @@
<template>
<el-row class="base-search-form rounded">
<el-form :inline="true" :model="searchForm" :rules="headConfig.rules">
<!-- <el-col :span="opt.span ? +opt.span : 4" v-for="opt in options" :key="opt.id"> -->
<el-form-item
v-for="(opt, index) in headConfig.fields"
:key="opt.prop"
:label="opt.label ? opt.label : null"
:prop="'' + index"
:rules="opt.elconfig?.rules ? opt.elconfig.rules : undefined"
>
<el-input
v-if="opt.input"
v-model="searchForm[index]"
v-bind="opt.elconfig"
/>
<el-select
v-if="opt.select"
v-model="searchForm[index]"
v-bind="opt.elconfig"
>
<el-option
v-for="item in opt.select"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-date-picker
v-if="opt.timerange"
v-model="searchForm[index]"
v-bind="opt.elconfig"
/>
<el-upload
v-if="opt.upload"
:key="'upload_' + Math.random().toString()"
class="inline-block pl-3"
action="https://jsonplaceholder.typicode.com/posts/"
>
<el-button type="primary">上传文件</el-button>
</el-upload>
<el-button
v-if="opt.button && (!opt.button.permission || $hasPermission(opt.button.permission))"
:key="'button' + Math.random().toString()"
:type="opt.button.type"
@click="handleBtnClick(opt.button.name)"
>{{ opt.button.name }}</el-button
>
</el-form-item>
</el-form>
</el-row>
</template>
<script>
export default {
name: "",
props: {
headConfig: {
type: Object,
default: () => ({}),
},
},
data() {
return {
searchForm: {
// 最大参数个数10个不够则在这里加
0: null, // 参数一
1: null,
2: null,
3: null,
4: null,
5: null,
6: null,
7: null,
8: null,
9: null,
},
};
},
watch: {
searchForm: {
handler: (val) => {
console.log("new val", val);
},
deep: true,
},
},
created() {},
mounted() {
console.log(
"head form config",
JSON.parse(JSON.stringify(this.headConfig))
);
this.headConfig.fields.forEach((field, index) => {
if (field.default) {
// default 必须有个 value 属性!哪怕是 input 输入框
this.searchForm[index] = field.default.value;
}
// 更新选项列表
if (!field.watch && field.fn && typeof field.fn === "function") {
// 设置自身的选项列表
field.fn().then((res) => {
field.select = res.map((_) => {
// 找到默认项
if (_.default) {
this.searchForm[index] = _.value;
}
return _;
});
console.log("[update] 更新选项列表", res, field.select);
});
}
// 模拟请求:
const axios = function(url, data) {
return new Promise((resolve) => {
resolve("success");
});
};
// 如果需要监听关联字段
if (field.watch) {
const { index: innerIdx, condition } = field.watch;
console.log(
"=====field.watch=====",
innerIdx,
this.searchForm[innerIdx],
this.headConfig.fields[innerIdx].default
);
// 设置监听器
this.$watch(
() => this.searchForm[innerIdx],
(val) => {
const queryParams = { [condition]: val };
// field.watch.condition.forEach(c => {
// queryParams
// })
axios(field.url, queryParams).then((res) => {
console.log("[==>] 更新有前置条件的字段!!!", queryParams, res);
// 此处是外部的 index
this.searchForm[index] = Math.floor(Math.random() * 10);
});
}
);
console.log("xxxxxxxxxx", this.searchForm);
// 如果此时已经有默认值了,就立马根据这个默认值来发起请求
if (this.searchForm[innerIdx]) {
// TODO: 这个判断好像不太需要...
console.log("TODO: 这个判断好像不太需要...");
} else {
console.log("TODO: 监听的字段还没来得及设置值呢...");
}
}
});
},
methods: {
handleBtnClick(name) {
this.$emit("btn-click", { btnName: name, payload: null });
},
},
};
</script>
<style scoped></style>

View File

@@ -0,0 +1,206 @@
<!--
* @Date: 2020-12-14 09:07:03
* @LastEditors: gtz
* @LastEditTime: 2022-06-13 08:59:21
* @FilePath: \mt-bus-fe\src\components\BaseTable\index.vue
* @Description:
-->
<template>
<div class="">
<el-table
:header-cell-style="{
background: '#FAFAFA',
color: '#606266',
height: '40px',
}"
:data="renderData"
:show-header="showHeader"
:border="border"
fit
highlight-current-rows
style="width: 100%"
@row-click="handleRowClick"
@selection-change="handleSelectionChange"
>
<!-- :max-height="height ? height : tableHeight(325)" -->
<el-table-column v-if="sbox" type="selection" width="55" fixed />
<el-table-column
v-if="page && limit && !toggleCustomIndex"
prop="_pageIndex"
:label="'tableHeader.index' | i18nFilter"
width="70"
align="center"
fixed
/>
<el-table-column
v-for="item in renderTableHeadList"
:key="item.prop"
v-bind="item"
:align="item.align ? item.align : 'left'"
:min-width="item.minWidth ? item.minWidth : 150"
:fixed="item.isFixed ? true : false"
:show-overflow-tooltip="true"
>
<template slot-scope="scope">
<component
:is="item.subcomponent"
v-if="item.subcomponent"
:key="scope.row.id"
:inject-data="{ ...scope.row, ...item }"
@emitData="emitData"
/>
<span v-else>{{
scope.row[item.prop] | commonFilter(item.filter)
}}</span>
</template>
</el-table-column>
<slot name="content" />
<slot name="handleBtn" />
</el-table>
</div>
</template>
<script>
export default {
name: "BaseTable",
filters: {
commonFilter: (source, filterType = (a) => a) => {
return filterType(source);
},
},
props: {
toggleCustomIndex: {
type: Boolean,
default: false,
},
topBtnConfig: {
type: Array,
default: () => {
return [];
},
},
showHeader: {
type: Boolean,
default: true,
},
border: {
type: Boolean,
default: false,
},
height: {
type: Number,
required: false,
default: 0,
},
tableData: {
type: Array,
required: true,
// validator: (val) => val.filter((item) => !isObject(item)).length === 0,
},
tableConfig: {
type: Array,
required: true,
// validator: (val) =>
// val.filter((item) => !isString(item.prop) || !isString(item.label))
// .length === 0,
},
isLoading: {
type: Boolean,
required: false,
},
page: {
type: Number,
required: false,
default: 0,
},
limit: {
type: Number,
required: false,
default: 0,
},
sbox: {
type: Boolean,
required: false,
},
highIndex: {
// 首行是否默认高亮
type: Boolean,
required: false,
},
},
data() {
return {
tableConfigBak: [],
selectedBox: new Array(100).fill(true),
// tableHeight,
tableRowIndex: null,
};
},
computed: {
renderData() {
return this.tableData.map((item, index) => {
return {
...item,
_pageIndex: (this.page - 1) * this.limit + index + 1,
};
});
},
renderTableHeadList() {
return this.tableConfig.filter((item, index) => {
return this.selectedBox[index];
});
},
},
watch: {
tableData: function (val) {
if (this.highIndex) {
// 默认高亮行的,重置默认高亮行为第一行
this.tableRowIndex = 0;
}
},
},
beforeMount() {
this.selectedBox = new Array(100).fill(true);
if (this.highIndex) {
this.tableRowIndex = 0;
}
},
// mounted() {
// this.tableConfigBak = cloneDeep(this.tableConfig).map(item => {
// return {
// ...item,
// selected: true
// }
// })
// },
methods: {
emitData(val) {
this.$emit("emitFun", val);
},
clickTopButton(val) {
this.$emit("clickTopBtn", val);
},
handleSelectionChange(val) {
this.$emit("selectChangeFun", val);
},
handleRowClick(row) {
this.tableRowIndex = this.getArrayIndex(this.tableData, row); // 获取当前点击行下标
this.$emit("selectRow", row);
},
tableRowClassName({ row, rowIndex }) {
if (rowIndex === this.tableRowIndex) {
return "success-row";
}
return "";
},
getArrayIndex(arr, obj) {
var i = arr.length;
while (i--) {
if (arr[i].id === obj.id) {
return i;
}
}
return -1;
},
},
};
</script>

View File

@@ -0,0 +1,19 @@
<!-- 基本上传 -->
<template>
<div></div>
</template>
<script>
export default {
name: '',
props: {},
data() {
return {};
},
created() {},
mounted() {},
methods: {},
};
</script>
<style scoped></style>

View File

@@ -0,0 +1,433 @@
<template>
<el-dialog
class="dialog-with-menu"
:visible="selfVisible"
@close="handleClose"
:distory-on-close="true"
>
<!-- title -->
<div slot="title" class="">
<h1 class="">编辑</h1>
</div>
<!-- menu -->
<el-tabs v-model="activeMenu" type="card" @tab-click="handleTabClick">
<!-- <el-tab-pane v-for="(tab, index) in configs.menu" :key="index" :label="tab.name" :name="tab.name"> -->
<el-tab-pane
v-for="(tab, index) in actualMenus"
:key="index"
:label="tab.name"
:name="tab.name"
>
<div v-if="index === 0">
<!-- form -->
<el-form ref="dataForm" :model="dataForm">
<el-row
v-for="(row, rowIndex) in configs.form.rows"
:key="'row_' + rowIndex"
:gutter="20"
>
<el-col
v-for="(col, colIndex) in row"
:key="colIndex"
:span="24 / row.length"
>
<el-form-item
:label="col.label"
:prop="col.prop"
:rules="col.rules || null"
>
<el-input
v-if="col.input"
v-model="dataForm[col.prop]"
clearable
:disabled="detailMode"
v-bind="col.elparams"
/>
<el-select
v-if="col.select"
v-model="dataForm[col.prop]"
clearable
:disabled="detailMode"
v-bind="col.elparams"
@change="handleSelectChange(col, $event)"
>
<el-option
v-for="(opt, optIdx) in col.options"
:key="'option_' + optIdx"
:label="opt.label"
:value="opt.value"
/>
</el-select>
<el-switch
v-if="col.switch"
v-model="dataForm[col.prop]"
:active-value="1"
:inactive-value="0"
@change="handleSwitchChange"
:disabled="detailMode"
/>
<el-input
v-if="col.textarea"
type="textarea"
v-model="dataForm[col.prop]"
:disabled="detailMode"
v-bind="col.elparams"
/>
<!-- add more... -->
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<div v-if="dataForm.id && index === 1">
<el-button
v-if="!detailMode"
type="primary"
style="margin-bottom: 16px"
@click="handleAddParam()"
>添加</el-button
>
<BaseListTable
:table-config="null"
:column-config="filteredTableProps"
:table-data="subList"
@operate-event="handleTableRowOperate"
/>
<!-- paginator -->
</div>
</el-tab-pane>
</el-tabs>
<!-- sub dialog -->
<small-dialog
:append-to-body="true"
v-if="showSubDialog"
ref="subDialog"
:url="urls.subase"
:configs="configs.subDialog"
:related-id="dataForm.id"
@refreshDataList="getSubList"
></small-dialog>
<!-- footer -->
<div slot="footer">
<template v-for="(operate, index) in configs.form.operations">
<el-button
v-if="showButton(operate)"
:key="'operation_' + index"
:type="operate.type"
@click="handleBtnClick(operate)"
>{{ operate.label }}</el-button
>
</template>
<el-button @click="handleBtnClick({ name: 'cancel' })">取消</el-button>
</div>
</el-dialog>
</template>
<script>
import { pick as __pick } from "@/utils/filters";
import SmallDialog from "@/components/SmallDialog.vue";
import BaseListTable from "@/components/BaseListTable.vue";
export default {
name: "DialogWithMenu",
components: { SmallDialog, BaseListTable },
props: {
configs: {
type: Object,
default: () => ({}),
},
},
inject: ["urls"],
data() {
const dataForm = {};
this.configs.form.rows.forEach((row) => {
row.forEach((col) => {
dataForm[col.prop] = col.default ?? "";
console.log("==========>", col.prop, dataForm[col.prop]);
});
});
return {
// configs,
activeMenu: this.configs.menu[0].name,
dataForm,
detailMode: false,
selfVisible: false,
showBaseDialog: false,
baseDialogConfig: null,
subList: [],
showSubDialog: false,
};
},
computed: {
actualMenus() {
return this.configs.menu.filter((m) => {
if (m.onlyEditMode && !this.dataForm.id) {
return false;
}
return true;
});
},
filteredTableProps() {
return this.detailMode
? this.configs.table.props.filter((v) => v.prop !== "operations")
: this.configs.table.props;
},
},
methods: {
/** utitilities */
showButton(operate) {
const notDetailMode = !this.detailMode;
const showAlways = operate.showAlways ?? false;
const editMode = operate.showOnEdit && this.dataForm.id;
const addMode = !operate.showOnEdit && !this.dataForm.id;
const permission = operate.permission
? this.$hasPermission(operate.permission)
: true;
return (
notDetailMode && (showAlways || ((editMode || addMode) && permission))
);
},
resetForm(excludeId = false) {
setTimeout(() => {
Object.keys(this.dataForm).forEach((key) => {
if (excludeId && key === "id") return;
this.dataForm[key] = null;
});
this.activeMenu = this.configs.menu[0].name;
}, 500);
},
/** init **/
init(id, detailMode) {
console.log("[dialog] DialogWithHead init():", id, detailMode);
this.detailMode = detailMode ?? false;
this.$nextTick(() => {
// this.$refs['dataForm'].resetFields();
this.dataForm.id = id || null;
if (this.dataForm.id) {
// 如果是编辑
this.$http
.get(this.urls.base + `/${this.dataForm.id}`)
.then(({ data: res }) => {
// dev env:
// if (LOCAL) res.data.id = res.data._id;
// end dev env
console.log("[base dialog init] ", res);
if (res && res.code === 0) {
const dataFormKeys = Object.keys(this.dataForm);
console.log("keys ===> ", dataFormKeys);
// console.log('data form keys: ', dataFormKeys, pick(res.data, dataFormKeys))
this.dataForm = __pick(res.data, dataFormKeys);
console.log(
"pick(res.data, dataFormKeys) ===> ",
__pick(res.data, dataFormKeys)
);
// LABEL: FILE_RELATED
/** 对文件下载进行分流 */
// this.fileList = {};
// if (this.dataForm.files) {
// // console.log('files: ', this.dataForm.files)
// this.dataForm.files.forEach((file) => {
// // const fileName = file.fileurls.split('/').pop()
// /** [1] 处理 fileList */
// // if (Object.hasOwn(this.fileList, file.typeCode)) {
// if (this.fileList.hasOwnProperty(file.typeCode)) {
// /** 已存在 */
// // this.fileList[file.typeCode].push({ id: file.id, name: fileName, typeCode: file.typeCode })
// this.fileList[file.typeCode].push(file);
// } else {
// // this.fileList[file.typeCode] = [{ id: file.id, name: fileName, typeCode: file.typeCode }]
// this.fileList[file.typeCode] = [file];
// }
// /** [2] 处理 fileForm */
// // if (Object.hasOwn(this.fileForm, file.typeCode)) {
// if (this.fileForm.hasOwnProperty(file.typeCode)) {
// this.fileForm[file.typeCode].push(file.id);
// } else {
// this.fileForm[file.typeCode] = [file.id];
// }
// });
// }
}
});
} else {
// 如果不是编辑
}
});
this.selfVisible = true;
},
/** handlers */
handleSelectChange(col, eventValue) {
console.log("[dialog] select change: ", col, eventValue);
},
handleSwitchChange(val) {
console.log("[dialog] switch change: ", val, this.dataForm);
},
handleBtnClick(payload) {
console.log("btn click payload: ", payload);
if ("name" in payload) {
switch (payload.name) {
case "cancel":
this.handleClose();
break;
case "reset":
this.resetForm(true); // true means exclude id
break;
case "add":
case "update": {
const method = payload.name === "add" ? "POST" : "PUT";
this.$http({
url: this.urls.base,
method,
data: this.dataForm,
}).then(({ data: res }) => {
console.log("[add&update] res is: ", res);
this.$message.success(
payload.name === "add" ? "添加成功" : "更新成功"
);
this.$emit("refreshDataList");
this.handleClose();
});
}
}
} else {
console.log("[x] 不是这么用的! 缺少name属性");
}
},
handleTabClick(payload) {
console.log("tab click payload: ", this.activeMenu);
if (this.activeMenu === this.configs.menu[1].name) {
// 获取数据
this.getSubList();
}
},
getSubList(page = 1, size = 20) {
const params = {};
params.page = page;
params.limit = size;
const requiredParams = this.configs.table.extraParams;
if (requiredParams) {
if (Array.isArray(requiredParams)) {
requiredParams.forEach((str) => {
if (/id/i.test(str)) {
params[str] = this.dataForm.id;
} else {
params[str] = "";
}
});
} else if (typeof requiredParams === "string") {
// 如果需要额外参数,一般肯定需要
params[this.configs.table.extraParams] = this.dataForm.id;
// 此时 dataForm.id 一定是存在的
}
}
this.$http.get(this.urls.subpage, { params }).then(({ data: res }) => {
console.log("[subList] getSubList:", res);
if (res.code === 0 && res.data?.list) {
// 有数据
this.subList = res.data.list;
} else {
this.subList.splice(0);
}
});
},
handleAddParam(id) {
this.showSubDialog = true;
this.$nextTick(() => {
this.$refs.subDialog.init(id ?? null);
});
},
handleClose() {
this.resetForm();
this.selfVisible = false;
},
/** 列表handlers */
handleAddItem() {
// console.log('[dialog] handleAddItem ot list...');
this.showBaseDialog = true;
this.$nextTick(() => {
console.log("[sub-dialog] ", this.$refs["sub-dialog"].init());
});
},
handleTableRowOperate({ type, data }) {
console.log("handleTableRowOperate", type, data);
switch (type) {
case "delete": {
// 确认是否删除
return this.$confirm(`是否删除条目: ${data}`, "提示", {
confirmButtonText: "确认",
cancelButtonText: "我再想想",
type: "warning",
})
.then(() => {
// this.$http.delete(this.urls.base + `/${data}`).then((res) => {
this.$http({
url: this.urls.subase,
method: "DELETE",
data: [`${data}`],
}).then(({ data: res }) => {
if (res.code === 0) {
this.$message.success({
message: "删除成功!",
duration: 1000,
onClose: () => {
this.getSubList(1, 20);
},
});
}
});
})
.catch((err) => {});
}
case "edit": {
this.handleAddParam(data); /** data is ==> id */
break;
}
// case 'view-detail-action':
// this.openDialog(data, true);
// break;
}
},
},
};
</script>
<style scoped>
.el-menu {
margin: 16px 0 !important;
}
.el-menu.el-menu--horizontal {
border: none !important;
/* background: #0f02 !important; */
}
/* .el-menu--horizontal > .el-menu-item.is-active { */
/* border-bottom-color: #0b58ff; */
/* } */
.dialog-with-menu >>> .el-dialog__body {
/* padding-top: 16px !important;
padding-bottom: 16px !important; */
padding-top: 0 !important;
padding-bottom: 0 !important;
}
.el-select {
width: 100% !important;
}
</style>

View File

@@ -0,0 +1,2 @@
配置文件选项总结

View File

@@ -0,0 +1,220 @@
<template>
<!-- :title="isDetail ? title.detail : !dataForm.id ? title.add : title.edit" -->
<el-dialog
class="a-small-dialog"
:visible.sync="visible"
@close="handleClose"
:distory-on-close="true"
:close-on-click-modal="false"
v-bind="$attrs"
>
<!-- :append-to-body="appendToBody"> -->
<div>
<el-form ref="dataForm" :model="dataForm">
<el-row
v-for="(row, rowIndex) in configs.rows"
:key="'row_' + rowIndex"
:gutter="20"
>
<el-col
v-for="(col, colIndex) in row"
:key="colIndex"
:span="col.span ?? 24 / row.length"
>
<el-form-item
:prop="col.prop"
:rules="col.rules || null"
:label="col.label"
>
<el-input
v-if="col.input"
v-model="dataForm[col.prop]"
clearable
:disabled="detailMode"
v-bind="col.elparams"
/>
<el-select
v-if="col.select"
v-model="dataForm[col.prop]"
clearable
:disabled="detailMode"
v-bind="col.elparams"
@change="handleSelectChange(col, $event)"
>
<el-option
v-for="(opt, optIdx) in col.options"
:key="'option_' + optIdx"
:label="opt.label"
:value="opt.value"
/>
</el-select>
<el-switch
v-if="col.switch"
v-model="dataForm[col.prop]"
:active-value="1"
:inactive-value="0"
@change="handleSwitchChange"
:disabled="detailMode"
/>
<el-input
v-if="col.textarea"
type="textarea"
v-model="dataForm[col.prop]"
:disabled="detailMode"
v-bind="col.elparams"
/>
<!-- add more... -->
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<!-- footer -->
<div slot="footer">
<template v-for="(operate, index) in configs.operations">
<el-button
v-if="showButton(operate)"
:key="'operation_' + index"
:type="operate.type"
@click="handleBtnClick(operate)"
>{{ operate.label }}</el-button
>
</template>
<el-button @click="handleBtnClick({ name: 'cancel' })">取消</el-button>
</div>
</el-dialog>
</template>
<script>
// import CKEditor from 'ckeditor4-vue'
// import AttrForm from './AttrForm'
// import { pick } from 'lodash/object'
import { pick as __pick } from "@/utils/filters";
// import i18n from '@/i18n'
export default {
name: "SmallDialog",
props: {
configs: {
type: Object,
default: () => ({}),
},
relatedId: {
type: String,
default: "",
},
},
inject: ["urls"],
data() {
const dataForm = {};
this.configs.rows.forEach((row) => {
row.forEach((col) => {
dataForm[col.prop] = col.default ?? "";
console.log("[small dialog]==========>", col.prop, dataForm[col.prop]);
});
});
return {
visible: false,
detailMode: false,
dataForm,
dataFormRules: {},
tempForm: [], // 临时保存自动生成的code或其他数据
};
},
methods: {
/** utitilities */
showButton(operate) {
const notDetailMode = !this.detailMode;
const showAlways = operate.showAlways ?? false;
const editMode = operate.showOnEdit && this.dataForm.id;
const addMode = !operate.showOnEdit && !this.dataForm.id;
const permission = operate.permission
? this.$hasPermission(operate.permission)
: true;
return (
notDetailMode && (showAlways || ((editMode || addMode) && permission))
);
},
resetForm(excludeId = false) {
setTimeout(() => {
Object.keys(this.dataForm).forEach((key) => {
console.log(">>> clearing key: ", key);
if (excludeId && key === "id") return;
this.dataForm[key] = null;
});
this.detailMode = false;
}, 500);
},
init(id, isdetail = false) {
this.detailMode = isdetail;
console.log("[small dialog] init", id, isdetail);
this.$nextTick(() => {
// this.$refs['dataForm'].resetFields();
this.dataForm.id = id || null;
if (this.dataForm.id) {
// 如果是编辑
$http.get(this.urls.subase + `/${this.dataForm.id}`).then((res) => {
// dev env:
if (LOCAL) res.data.id = res.data._id;
// end dev env
if (res && res.code === 0) {
const dataFormKeys = Object.keys(this.dataForm);
this.dataForm = __pick(res.data, dataFormKeys);
}
});
} else {
// 如果不是编辑
}
});
this.visible = true;
},
handleSelectChange(col, event) {},
handleSwitchChange() {},
handleBtnClick(payload) {
console.log("btn click payload: ", payload);
console.log("configs", this.configs);
if ("name" in payload) {
switch (payload.name) {
case "cancel":
this.handleClose();
break;
case "add":
case "update": {
const method = payload.name === "add" ? "POST" : "PUT";
$http({
url: this.urls.subase,
method,
data: {
...this.dataForm,
[this.configs.extraParams]: this.relatedId,
},
}).then((res) => {
console.log("[add&update] res is: ", res);
this.$message.success(
payload.name === "add" ? "添加成功" : "更新成功"
);
this.$emit("refreshDataList");
this.handleClose();
});
}
}
} else {
console.log("[x] 不是这么用的! 缺少name属性");
}
},
handleClose() {
this.resetForm();
this.visible = false;
},
},
};
</script>

View File

@@ -0,0 +1,40 @@
<template>
<el-table-column
:label="opt.label ? opt.label : opt.name"
:prop="opt.prop || null"
:width="opt.width || null"
:min-width="opt.minWidth || null"
:fixed="opt.fixed || null"
:show-overflow-tooltip="opt.showOverflowTooltip || false"
filter-placement="top"
:align="opt.align || null"
v-bind="opt.more"
>
<template v-if="opt.prop" slot-scope="scope">
<component v-if="opt.subcomponent" :is="opt.subcomponent" :key="idx + 'sub'" :inject-data="{ ...scope.row, head: opt }" @emit-data="handleSubEmitData" />
<!-- 直接展示数据或应用过滤器 -->
<span v-else>{{ scope.row[opt.prop] | commonFilter(opt.filter) }}</span>
</template>
<!-- 递归 -->
<template v-if="!opt.prop && opt.children">
<TableHead v-for="(subhead, index) in opt.children" :key="'subhead' + index" :opt="subhead" />
</template>
</el-table-column>
</template>
<script>
export default {
name: 'TableHead',
filters: {
commonFilter: (source, filterType = a => a) => {
return filterType(source)
}
},
props: {
opt: {
type: Object,
default: () => ({})
}
}
}
</script>

View File

@@ -0,0 +1 @@
用于存放没有模板的组件,一般用于表格中或弹窗里的组件插入

View File

@@ -0,0 +1,31 @@
// import i18n from '@/i18n'
export default {
name: 'TableTextComponent',
props: {
injectData: {
type: Object,
default: () => ({})
}
},
data() {
return {
// for i18n inject:
// defaultText: i18n.t('viewdetail')
defaultText: '查看详情'
}
},
methods: {
emitClick() {
// console.log('inject data:' ,this.injectData)
this.$emit('emit-data', {
type: this.injectData.head?.actionName || 'view-detail-action',
data: this.injectData.head?.emitFullData ? this.injectData : this.injectData.id
})
}
},
render: function (h) {
// console.log('button content:', this.injectData)
return h('span', null, [h('el-button', { props: { type: 'text' }, style: { paddingLeft: 0 }, on: { click: this.emitClick } }, this.injectData.head?.buttonContent || this.defaultText)])
}
}

View File

@@ -0,0 +1,98 @@
// import i18n from '@/i18n'
export default {
name: 'TableOperations',
props: {
injectData: {
type: Object,
default: () => ({})
}
},
data() {
return {
btnTypes: {
add: 'primary',
delete: 'danger',
detail: 'info'
// add more...
},
colors: {
delete: '#FF5454',
preview: '#f09843',
design: '#99089f',
// 'view-trend': 'red'
// add more...
},
text: {
// TODO: i18n
// edit: i18n.t('edit'),
// detail: i18n.t('detail'),
// delete: i18n.t('delete'),
// viewAttr: i18n.t('viewattr'),
// preview: i18n.t('preview'),
// design: i18n.t('design'),
edit: '编辑',
detail: '详情',
delete: '删除',
viewAttr: '查看属性',
preview: '预览',
design: '设计',
'view-trend': '查看趋势'
// add more...
}
}
},
methods: {
// 发射事件
emit(opt) {
let emitFull = false
let eventType = 'default'
let customField
if (typeof opt === 'object') {
eventType = opt.name
customField = opt.emitField || 'id'
emitFull = opt.emitFull || false
} else {
eventType = opt
}
this.$emit('emit-data', { type: eventType, data: emitFull ? this.injectData : customField ? this.injectData[customField] : this.injectData.id })
}
},
render: function (h) {
let btns = []
for (const opt of this.injectData.head?.options) {
const optIsObj = typeof opt === 'object'
if (optIsObj) {
// 可能需要验证权限,如 opt.permission 选项
// 注意为空字符串或null/undefined都会不验证权限
if (!opt.permission || (opt.permission && this.$hasPermission(opt.permission))) {
btns.push(
h('el-button',
{
props: { type: 'text' },
style: { color: this.colors[optionStr.name] || '#409EFF' },
on: { click: this.emit.bind(null, opt) }
},
this.text[opt.name]
)
)
}
} else {
// 此时 opt 是一个 string且默认有操作权限
btns.push(
h('el-button',
{
props: { type: 'text' },
style: { color: this.colors[opt] || '#409EFF' },
on: { click: this.emit.bind(null, opt) }
},
this.text[opt]
)
)
}
}
return h('span', null, btns)
}
}

View File

@@ -0,0 +1,2 @@
// 富文本组件
export default {}

View File

@@ -0,0 +1,47 @@
// import i18n from '@/i18n'
export default {
name: 'StatusComponent',
props: {
injectData: {
type: Object,
default: () => ({})
}
},
data() {
return {
statusText: [
'正常',
'异常',
'损坏',
// more...
],
statusType: [
'success',
'warning',
'danger',
// more...
]
}
},
computed: {
isEnabled() {
return this.injectData && (this.injectData.enabled === 1 || this.injectData.enabled.toString() === '1')
}
},
mounted() {
console.log('[component] StatusComponent: ', this.injectData)
},
methods: {
// 发射事件
emit(opt) { }
},
render: function (h) {
return h('el-tag',
{
props:
{ type: this.isEnabled ? this.statusType[0] : this.statusType[1] }
},
this.isEnabled ? this.statusText[0] : this.statusText[1])
}
}

View File

@@ -0,0 +1,2 @@
// 表格中的开关
export default {}

View File

@@ -0,0 +1,2 @@
// 表格中的可点击文本
export default {}

View File

@@ -0,0 +1,7 @@
import RenDeptTree from './src/ren-dept-tree'
RenDeptTree.install = function (Vue) {
Vue.component(RenDeptTree.name, RenDeptTree)
}
export default RenDeptTree

View File

@@ -0,0 +1,118 @@
<template>
<div>
<el-input v-model="showDeptName" :placeholder="placeholder" @focus="deptDialog">
<el-button slot="append" icon="el-icon-search" @click="deptDialog"></el-button>
</el-input>
<el-input :value="value" style="display: none"></el-input>
<el-dialog :visible.sync="visibleDept" width="30%" :modal="false" :title="placeholder" :close-on-click-modal="false" :close-on-press-escape="false">
<el-form size="mini" :inline="true">
<el-form-item :label="$t('keyword')">
<el-input v-model="filterText"></el-input>
</el-form-item>
<el-form-item>
<el-button type="default">{{ $t('query') }}</el-button>
</el-form-item>
</el-form>
<el-tree
class="filter-tree"
:data="deptList"
:default-expanded-keys="expandedKeys"
:props="{ label: 'name', children: 'children' }"
:expand-on-click-node="false"
:filter-node-method="filterNode"
:highlight-current="true"
node-key="id"
ref="tree">
</el-tree>
<template slot="footer">
<el-button type="default" @click="cancelHandle()" size="mini">{{ $t('cancel') }}</el-button>
<el-button v-if="query" type="info" @click="clearHandle()" size="mini">{{ $t('clear') }}</el-button>
<el-button type="primary" @click="commitHandle()" size="mini">{{ $t('confirm') }}</el-button>
</template>
</el-dialog>
</div>
</template>
<script>
export default {
name: 'RenDeptTree',
data () {
return {
filterText: '',
visibleDept: false,
deptList: [],
showDeptName: '',
expandedKeys: null,
defaultProps: {
children: 'children',
label: 'label'
}
}
},
props: {
value: [Number, String],
deptName: String,
query: Boolean,
placeholder: String
},
watch: {
filterText (val) {
this.$refs.tree.filter(val)
},
deptName (val) {
this.showDeptName = val
}
},
methods: {
deptDialog () {
this.expandedKeys = null
if (this.$refs.tree) {
this.$refs.tree.setCurrentKey(null)
}
this.visibleDept = true
this.getDeptList(this.value)
},
filterNode (value, data) {
if (!value) return true
return data.name.indexOf(value) !== -1
},
getDeptList (id) {
return this.$http.get('/sys/dept/list').then(({ data: res }) => {
if (res.code !== 0) {
return this.$message.error(res.msg)
}
this.deptList = res.data
this.$nextTick(() => {
this.$refs.tree.setCurrentKey(id)
this.expandedKeys = [id]
})
}).catch(() => {})
},
cancelHandle () {
this.visibleDept = false
this.deptList = []
this.filterText = ''
},
clearHandle () {
this.$emit('input', '')
this.$emit('update:deptName', '')
this.showDeptName = ''
this.visibleDept = false
this.deptList = []
this.filterText = ''
},
commitHandle () {
const node = this.$refs.tree.getCurrentNode()
if (!node) {
this.$message.error(this.$t('dept.chooseerror'))
return
}
this.$emit('input', node.id)
this.$emit('update:deptName', node.name)
this.showDeptName = node.name
this.visibleDept = false
this.deptList = []
this.filterText = ''
}
}
}
</script>

View File

@@ -0,0 +1,7 @@
import RenRadioGroup from './src/ren-radio-group'
RenRadioGroup.install = function (Vue) {
Vue.component(RenRadioGroup.name, RenRadioGroup)
}
export default RenRadioGroup

View File

@@ -0,0 +1,20 @@
<template>
<el-radio-group :value="value+''" @input="$emit('input', $event)">
<el-radio :label="data.dictValue" v-for="data in dataList" :key="data.dictValue">{{data.dictLabel}}</el-radio>
</el-radio-group>
</template>
<script>
import { getDictDataList } from '@/utils'
export default {
name: 'RenRadioGroup',
data () {
return {
dataList: getDictDataList(this.dictType)
}
},
props: {
value: [Number, String],
dictType: String
}
}
</script>

View File

@@ -0,0 +1,7 @@
import RenRegionTree from './src/ren-region-tree'
RenRegionTree.install = function (Vue) {
Vue.component(RenRegionTree.name, RenRegionTree)
}
export default RenRegionTree

View File

@@ -0,0 +1,132 @@
<template>
<div class="ren-region">
<el-input v-model="showName" :placeholder="placeholder" @focus="treeDialog">
<el-button slot="append" icon="el-icon-search" @click="treeDialog"></el-button>
</el-input>
<el-input :value="value" style="display: none"></el-input>
<el-dialog :visible.sync="visibleTree" width="360px" :modal="false" :title="placeholder" :close-on-click-modal="false" :close-on-press-escape="false">
<el-form size="mini" :inline="true">
<el-form-item :label="$t('keyword')">
<el-input v-model="filterText"></el-input>
</el-form-item>
<el-form-item>
<el-button type="default">{{ $t('query') }}</el-button>
</el-form-item>
</el-form>
<el-tree
class="filter-tree"
:data="dataList"
:default-expanded-keys="expandedKeys"
:props="{ label: 'name', children: 'children' }"
:expand-on-click-node="false"
:filter-node-method="filterNode"
:highlight-current="true"
node-key="id"
ref="tree">
</el-tree>
<template slot="footer">
<el-button type="default" @click="cancelHandle()" size="mini">{{ $t('cancel') }}</el-button>
<el-button type="info" @click="clearHandle()" size="mini">{{ $t('clear') }}</el-button>
<el-button type="primary" @click="commitHandle()" size="mini">{{ $t('confirm') }}</el-button>
</template>
</el-dialog>
</div>
</template>
<style lang="scss">
.ren-region {
.filter-tree {
max-height: 230px;
overflow: auto;
}
.el-dialog__body {
padding: 0px 0px 0px 20px;
}
.el-dialog__footer {
padding: 10px 20px 8px 20px;
}
}
</style>
<script>
import { treeDataTranslate } from '@/utils'
export default {
name: 'RenRegionTree',
data () {
return {
filterText: '',
visibleTree: false,
dataList: [],
showName: '',
expandedKeys: null,
defaultProps: {
children: 'children',
label: 'name'
}
}
},
props: {
value: [Number, String],
parentName: String,
placeholder: String
},
watch: {
filterText (val) {
this.$refs.tree.filter(val)
},
parentName (val) {
this.showName = val
}
},
methods: {
treeDialog () {
this.expandedKeys = null
if (this.$refs.tree) {
this.$refs.tree.setCurrentKey(null)
}
this.visibleTree = true
this.getDataList(this.value)
},
filterNode (value, data) {
if (!value) return true
return data.name.indexOf(value) !== -1
},
getDataList (id) {
return this.$http.get('/sys/region/tree').then(({ data: res }) => {
if (res.code !== 0) {
return this.$message.error(res.msg)
}
this.dataList = treeDataTranslate(res.data)
this.$nextTick(() => {
this.$refs.tree.setCurrentKey(id)
this.expandedKeys = [id]
})
}).catch(() => {})
},
cancelHandle () {
this.visibleTree = false
this.dataList = []
this.filterText = ''
},
clearHandle () {
this.$emit('input', '0')
this.$emit('update:parentName', '')
this.showName = ''
this.visibleTree = false
this.dataList = []
this.filterText = ''
},
commitHandle () {
const node = this.$refs.tree.getCurrentNode()
if (!node) {
this.$message.error(this.$t('choose'))
return
}
this.$emit('input', node.id)
this.$emit('update:parentName', node.name)
this.showName = node.name
this.visibleTree = false
this.dataList = []
this.filterText = ''
}
}
}
</script>

View File

@@ -0,0 +1,7 @@
import RenSelect from './src/ren-select'
RenSelect.install = function (Vue) {
Vue.component(RenSelect.name, RenSelect)
}
export default RenSelect

View File

@@ -0,0 +1,21 @@
<template>
<el-select :value="value+''" @input="$emit('input', $event)" :placeholder="placeholder" clearable>
<el-option :label="data.dictLabel" v-for="data in dataList" :key="data.dictValue" :value ="data.dictValue">{{data.dictLabel}}</el-option>
</el-select>
</template>
<script>
import { getDictDataList } from '@/utils'
export default {
name: 'RenSelect',
data () {
return {
dataList: getDictDataList(this.dictType)
}
},
props: {
value: [Number, String],
dictType: String,
placeholder: String
}
}
</script>