保存进度!

This commit is contained in:
2022-12-15 09:25:51 +08:00
parent 5c72437e2d
commit 5dcff64bf5
130 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
<template>
<div class="home">
<h1>这里是测试文字</h1>
<ul>
<li>无序列表LI</li>
<li>无序列表LI</li>
</ul>
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String,
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss"></style>

View File

@@ -0,0 +1,12 @@
import { h } from 'vue';
const Input = (props, context) => {
return () => {
return (
<div>sdfdsf</div>
)
}
}
Input.props = ['level'];
export default Input;

View File

@@ -0,0 +1,34 @@
export function relationHook(){
/**
* @param { String } value 控制方的选项 value
* @param { Array } data 关联关系的数据配置 relation_hidden
* @param { Object } props 显示/隐藏对象集保
*/
const HiddenItem = (value, data, props) => {
if(data && Array.isArray(data) && data.length > 0) {
data.forEach(item => {
const field = item[0];
const objValue = item[1];
props[field] = objValue[value]
// 选项为 1 时
// props["title"] = objValue["1"] => true
// props["iamge_url"] = objValue["1"] => true
// 选项为 0 时
// props["title"] = objValue["0"] => true
// props["iamge_url"] = objValue["0"] => undefined
})
}
}
const DisabledItem = (value, data, props) => {
if(data && Array.isArray(data) && data.length > 0) {
data.forEach(item => {
const field = item[0];
const objValue = item[1];
props[field] = objValue[value]
})
}
}
return { HiddenItem, DisabledItem };
}

View File

@@ -0,0 +1,75 @@
import { moneyNValid } from "@/utils/amountFormat";
/**
* @returns BasisForm组件创建校验规则
*/
export function rulesHook() {
/**
* @param {*} data 配置额外组件
*/
const InitRules = (data = []) => {
if (data.length === 0) {
return false;
}
// 判断是否有required属性
data.forEach((item) => {
// 初始化规则数组
let rulesArr = [];
if (item.required) {
const json = {
required: true,
message: item.required_msg || messageType(item).msg,
trigger: "blur",
};
rulesArr.push(json);
}
// 生成placeholder
item.placeholder = item.placeholder || messageType(item).placeholder;
// 是否有其他的校验规则
const rule = item.rule;
if (rule && Array.isArray(rule) && rule.length > 0) {
rulesArr = rulesArr.concat(rule);
}
// price
if (item.type === "price" && !item.rule) {
const json = { validator: moneyNValid, trigger: "blur" };
rulesArr.push(json);
}
// 定义rules属性赋值
item.rules = rulesArr;
});
return data;
};
/**
*
* @description 提示文本
*/
const messageType = (data) => {
let msg = "";
switch (data.type) {
case "input":
case "wangeditor":
case "price":
case "range":
case "textarea":
msg = "请输入";
break;
case "upload":
msg = "请上传";
break;
case "radio":
case "checkbox":
case "select":
case "date":
case "cascader":
msg = "请选择";
break;
default:
msg = "未定义";
}
return {
msg: `${msg}${data.label}`,
placeholder: `${msg}${data.label}`,
};
};
return { InitRules };
}

View File

@@ -0,0 +1,180 @@
<template>
<el-form ref="formDom" :model="field" :class="[{'label-gray': labelGray}]" :label-position="labelPosition" element-loading-text="加载中请稍候" :label-width="label_width" >
<el-row :gutter="gutter">
<template v-for="item in form_item" :key="item.prop">
<el-col v-if="!form_hidden[item.prop]" :span="item.col || 24">
<component :is="'htmlComponent'" v-if="item.type === 'html'" :prop="item" />
<el-form-item v-else :label="item.label" :prop="item.prop" :rules="item.rules">
<!--自定义label-->
<template v-if="item.label_html" #label>
<span class="custom-label">
<div>
<span style="position: relative;">
<label class="form-center" v-html="item.label_html.label || ''" />
<em v-if="item.label_html.spot" class="spot"></em>
</span>
</div>
<span :class="item.label_html.suffix_class" @click="item.label_html.suffix_callback && item.label_html.suffix_callback(item)" v-html="item.label_html.suffix_text || ''" />
</span>
</template>
<!--动态组件-->
<div v-show="control" style="width:100%">
<component :is="item.type + 'Component'" v-if="item.type !== 'slot'" v-model:value="field[item.prop]" v-model:second-value="field[item.secondprop]" v-model:select-value="field[item.selProp]" :data="item" :header="item" :disabled="disabled[item.prop]" @callback="componentCallback" />
<!--插槽-->
<template v-else>
<slot :name="item.slot_name" />
<span v-if="item.tip" class="tip">{{ item.tip }}</span>
</template>
</div>
<div v-show="!control">
<slot v-if="item.slot_text" :name="item.slot_text" />
<span v-else>{{ fieldText[item.prop] || '--' }}</span>
</div>
</el-form-item>
</el-col>
</template>
</el-row>
<!-- button -->
<div :class="['form-button', {'dialog-footer': dialog}]">
<template v-if="form_button && form_button.length > 0">
<template v-for="button in form_button" :key="button.key">
<template v-if="!button.hidden">
<CdButton v-if="button.key === 'submit'" :loading="load || loading" margin="0 0 0 16px" :type="button.type" @click="button.callback ? button.callback() : handlerFormValidate()">
{{ button.label }}
</CdButton>
<CdButton v-else-if="button.key === 'cancel'" margin="0 0 0 16px" border @click="button.callback ? button.callback() : handlerFormCancel()">
{{ button.label }}
</CdButton>
<CdButton v-else :type="button.type" margin="0 0 0 16px" @click="button.callback && button.callback()">{{ button.label }}</CdButton>
</template>
</template>
</template>
</div>
</el-form>
</template>
<script>
import { defineAsyncComponent, onBeforeUnmount, reactive, ref, watch } from "vue";
import { checkDataType } from "../common";
// hook
import { rulesHook } from "./hook/rulesHook";
import { relationHook } from "./hook/relationHook";
// props
import { props } from "./props";
// require import components
const files = require.context("@/cd-components/cd-control", true, /\index.vue$/);
const modules = {};
files.keys().forEach((key) => {
const name = key.split("/");
modules[name[1] + "Component"] = files(key).default || files(key);
});
export default {
name: "BasisForm",
components: {
...modules,
CdButton: defineAsyncComponent(() => import("../cd-button")),
},
props,
emits: ["callback", "cancel"],
setup(props, { emit }) {
const { InitRules } = rulesHook();
const { HiddenItem, DisabledItem } = relationHook();
// 校验规则处理
const form_item = reactive(InitRules(props.item));
const label_width = ref(props.labelWidth);
const form_button = reactive(props.button);
const form_disabled = reactive(props.disabled);
const load = ref(false)
let form_hidden = ref({});
watch(() => props.hidden, (value) => {
form_hidden.value = value
})
// 表单提交验证
const formDom = ref(null);
const handlerFormValidate = () => {
formDom.value.validate((valid) => {
if(valid) {
if (checkDataType().isFunction(props.beforeChange)) {
load.value = true
props.beforeChange().then(() => {
load.value = false
}).catch(() => {
load.value = false
})
return false
}
emit("callback")
}
});
};
const handlerFormCancel = () => {
handlerFormReset()
emit("cancel");
};
/**
* 重置表单
*/
const handlerFormReset = () => {
formDom.value.resetFields();
};
// change 事件
const componentCallback = (params) => {};
/**
* 生命周期
*/
onBeforeUnmount(() => {
form_hidden.value = {}
handlerFormReset()
})
return {
form_item,
label_width,
form_button,
form_hidden,
form_disabled,
formDom,
load,
handlerFormValidate,
handlerFormCancel,
handlerFormReset,
componentCallback,
};
},
};
</script>
<style lang="scss" scoped>
.form-button {
width: 100%;
}
.form-left {
text-align: left;
}
.form-center {
text-align: center;
}
.form-right {
text-align: right;
}
.custom-label {
position: relative;
> span {
float: right;
}
}
::v-deep .color-lable-danger {
width: 6px;
height: 6px;
background: #ff6060;
border-radius: 50%;
position: absolute;
right: -7px;
top: 7px;
}
:deep(.el-input__wrapper) {
border-radius: 0;
}
</style>

View File

@@ -0,0 +1,57 @@
export const props = {
item: {
type: Array,
default: () => [],
},
button: {
type: Array,
default: () => [],
},
labelWidth: {
type: [String, Number],
default: "100px",
},
field: {
type: Object,
default: () => ({}),
},
fieldText: {
type: Object,
default: () => ({}),
},
loading: {
type: Boolean,
default: false,
},
hidden: {
type: Object,
default: () => ({}),
},
disabled: {
type: Object,
default: () => ({}),
},
position: {
type: String,
default: "left",
},
labelPosition: {
type: String,
default: "right",
},
gutter: {
type: Number,
default: 0,
},
beforeChange: Function,
dialog: Boolean,
labelGray: Boolean,
control: {
type: Boolean,
default: true,
},
fieldData: {
type: Object,
default: () => ({}),
},
};

View File

@@ -0,0 +1,86 @@
<template>
<el-cascader :style="{width}" :disabled="disabled" v-model="data.value" :options="data.options" :props="data.props" @change="handlerChange"></el-cascader>
</template>
<script>
import { reactive, onBeforeMount, watch } from 'vue';
// API
import { CommonApi } from "@/api/common";
// requestUrl
import ApiUrl from "@/api/requestUrl";
export default {
name: 'BasisCascader',
components: {},
props: {
cascaderProps: {
type: Object,
default: () => ({})
},
url: {
type: String,
default: ""
},
method: {
type: String,
default: "post"
},
data: {
type: Object,
default: () => ({})
},
value: {
type: [String, Number],
default: 0
},
width: {
type: String,
default: "100%"
},
disabled: {
type: Boolean,
default: false
}
},
emits: ["update:value", 'callback'],
setup(props, { emit }){
const data = reactive({
props: props.data.props,
options: [],
value: 0
})
const handlerChange = (value) => {
// 获取最后一项
const val = value[value.length - 1];
// 返回
emit("update:value", val);
}
/** 获取列表 */
const getData = () => {
const url = ApiUrl?.cascader?.[props.url || props?.data?.url]?.url;
const method = ApiUrl?.cascader?.[props.url]?.method || "post";
if(!url) {
throw new Error("url地址不存在");
}
// 参数
const request_data = {
url,
method,
data: props.data,
}
CommonApi(request_data).then(response => {
data.options = response.data;
})
}
/** 生命周期挂载之前 */
onBeforeMount(() => {
getData();
})
/** 监听 */
watch(() => props.value, (newValue) => {
data.value = newValue
} )
return { data, handlerChange }
}
}
</script>

View File

@@ -0,0 +1,43 @@
<template>
<el-checkbox-group v-model="value" :disabled="form_disabled[data.prop]" @change="handlerChange">
<el-checkbox v-for="checkbox in data.options" :label="checkbox.value" :key="checkbox.value">{{ checkbox.label }}</el-checkbox>
</el-checkbox-group>
</template>
<script>
import { reactive, toRefs } from 'vue';
export default {
name: "CheckboxComponent",
props: {
data: {
type: Object,
default: () => ({})
},
value: {
type: Array,
default: () => ([])
}
},
emits: ['update:value', 'callback'],
setup(props, { emit }){
const data = reactive({
value: '',
data: props.data
})
const handlerChange = (val) => {
emit("update:value", val)
emit("callback", {
type: 'checkbox',
value: val,
data: props.data
})
}
return {
...toRefs(data),
handlerChange
}
}
}
</script>

View File

@@ -0,0 +1,52 @@
<template>
<el-date-picker :style="`width: ${data.width}`"
v-model="value"
:disabled="disabled"
:type="data.date_type || 'datetime'"
:format="data.date_format || 'YYYY-MM-DD HH:mm:ss'"
:placeholder="data.placeholder || '选择日期时间'"
:start-placeholder="data.start_placeholder || '请选择开始日期'"
:end-placeholder="data.end_placeholder || '请选择结束日期'"
:range-separator="data.range_placeholder || '-'"
@change="handlerChange"
>
</el-date-picker>
</template>
<script>
import { reactive, toRefs } from 'vue';
export default {
name: "DateComponent",
props: {
data: {
type: Object,
default: () => ({})
},
value: {
type: [Array, String],
default: ''
},
disabled: Boolean
},
emits: ['update:value', 'callback'],
setup(props, { emit }){
const data = reactive({
value: '',
data: props.data
})
const handlerChange = (val) => {
emit("update:value", val)
emit("callback", {
type: 'date',
value: val,
data: props.data
})
}
return {
...toRefs(data),
handlerChange
}
}
}
</script>

View File

@@ -0,0 +1,49 @@
<template>
<el-input
v-model="value"
:type="data.value_type || 'text'"
v-on:input="handlerEnter"
debounce
:placeholder="placeholder"
:maxlength="prop.maxlength"
:minlength="prop.minlength || 0"
/>
</template>
<script>
import { reactive, toRefs, watchEffect } from 'vue';
export default {
name: "InputComponent",
props: {
data: {
type: Object,
default: () => ({})
},
value: {
type: [Number, String],
default: ''
}
},
emits: ['update:value', 'callback'],
setup(props, { emit }){
const data = reactive({
value: '',
prop: props.data,
placeholder: props.data.placeholder
})
const handlerEnter = (val) => {
emit("update:value", val)
}
return {
...toRefs(data),
handlerEnter
}
}
}
</script>
<style>
</style>

View File

@@ -0,0 +1,41 @@
<template>
<el-input-number v-model="value" :min="data.min || 0" :max="data.max || 99999999" @change="handleChange" />
</template>
<script>
import { reactive, toRefs, watchEffect } from 'vue';
export default {
name: "InputComponent",
props: {
data: {
type: Object,
default: () => ({})
},
value: {
type: [Number, String],
default: ''
}
},
emits: ['update:value', 'callback'],
setup(props, { emit }){
const data = reactive({
value: '',
data: props.data
})
const handleChange = (val) => {
console.log(val)
emit("update:value", val)
}
return {
...toRefs(data),
handleChange
}
}
}
</script>
<style>
</style>

View File

@@ -0,0 +1,74 @@
<template>
<el-select v-model="key" placeholder="请选择" class="key-word-select" @change="handlerChange">
<el-option v-for="item in options" :key="item.value" :value="item.value" :label="item.label"></el-option>
</el-select>
<el-input v-model="value" class="width-200" :placeholder="placeholder" v-on:input="callback"></el-input>
</template>
<script>
import { reactive, toRefs, watch } from "vue";
export default {
name: "KeyWord",
props: {
data: {
type: Object,
default: () => ({})
},
},
emits: ["callback"],
setup(props, { emit }){
const data = reactive({
key: "",
value: "",
placeholder: "请输入搜索的关键字",
options: props.data.options
})
/** 下拉事件 */
const handlerChange = (value) => {
// 清除input输入框文本
data.value = "";
// 更新input输入框占位文本
updatePlaceholder();
// 回调
callback()
}
/** 事件回调 */
const callback = () => {
emit("callback", {
type: 'keyword',
value: { key: data.key, value: data.value },
formItem: props.data
})
}
/** update Placeholder */
const updatePlaceholder =() => {
const item = data.options.filter(item => item.value === data.key)[0];
data.placeholder = `请输入${item.label}`;
}
/** 清除 */
const handlerClear = () => {
data.key = "";
data.value = "";
}
return {
...toRefs(data),
handlerChange,
handlerClear,
callback
}
}
}
</script>
<style lang="scss" scoped>
.key-word-select {
width: 100px;
margin-right: 10px;
}
.width-200 { width: 200px; }
</style>

View File

@@ -0,0 +1,57 @@
<template>
<template v-if="!data.options && data.callback && data.callback(data)"></template>
<el-radio-group v-else v-model="value" :disabled="disabled" @change="handlerChange">
<el-radio
v-for="radio in data.options"
:key="radio[data.key_value] || radio.value"
:label="radio[data.key_value] || radio.value">
{{ radio[data.key_label] || radio.label }}
</el-radio>
</el-radio-group>
</template>
<script>
import { reactive, toRefs, watch } from 'vue';
// API
import { RoleListAll } from "@/api/role";
export default {
name: "DateComponent",
props: {
data: {
type: Object,
default: () => ({})
},
value: {
type: [Number, String],
default: ''
},
disabled: Boolean
},
emits: ['update:value', 'callback'],
setup(props, { emit }){
const dataa = reactive({
value: '',
data: props.data
})
watch(() => props.data, () => {
RoleListAll().then(response => {
console.log(111)
}, {
deep: true,
immediate: true
})
})
const handlerChange = (val) => {
emit("update:value", val)
emit("callback", {
type: 'radio',
value: val,
formItem: props.data
})
}
return {
handlerChange
}
}
}
</script>

View File

@@ -0,0 +1,41 @@
<template>
<el-select v-model="value" :disabled="disabled" :style="`width: ${data.width || '100%'}`" @change="handlerChange">
<el-option v-for="select in data.options" :key="select.value" :label="select.label" :value="select.value"></el-option>
</el-select>
</template>
<script>
import { reactive, toRefs } from 'vue';
export default {
name: "SelectComponent",
props: {
data: {
type: Object,
default: () => ({})
},
disabled: {
type: Boolean,
default: false
},
value: {
type: [Number, String],
default: ''
}
},
emits: ['update:value', 'callback'],
setup(props, { emit }){
const data = reactive({
value: '',
data: props.data
})
const handlerChange = (val) => {
emit("update:value", val)
}
return {
...toRefs(data),
handlerChange
}
}
}
</script>

View File

@@ -0,0 +1,80 @@
<template>
<el-switch
v-model="init_data.value"
:loading="init_data.loading"
:before-change="handlerSwitch"
>
</el-switch>
</template>
<script>
import { reactive, getCurrentInstance, watch } from 'vue';
// requestUrl
import ApiUrl from "@/api/requestUrl";
//API
import { SwitchStatus } from "@/api/common";
export default {
name: 'SwitchComponent',
props: {
data: {
type: Object,
default: () => ({})
},
config: {
type: Object,
default: () => ({})
}
},
emits: ['update:value', 'callback'],
setup(props){
// 配置信息
const config = reactive(props.config);
// 数据
const data = reactive(props.data);
// 初始值
const init_data = reactive({
value: data[config.prop],
loading: false
});
// 监听数据
watch(() => props.data, (newValue, oldValue) => {
init_data.value = newValue[config.prop]
})
// 获取实例上下文
const { proxy } = getCurrentInstance();
// 事件
const handlerSwitch = (value) => {
init_data.loading = true;
// 请求参数
const url = config.api_url || ApiUrl?.[config.api_module]?.[config.api_key]?.url;
const method = config.method || ApiUrl?.[config.api_module]?.[config.api_key]?.method || "post";
if(!url) {
console.log("请求接口不存在");
return false
}
// 请求参数
const key_id = config.key_id || 'id'
const request_data = {
url,
method,
data: {
[key_id]: data[key_id], // 等价于["id"]: data["id"]
[config.prop]: !init_data.value // 等价于["status"]: value
}
}
return new Promise((resolve, reject) => {
SwitchStatus(request_data).then(response => {
proxy.$message.success(response.message);
data.status = value;
init_data.loading = false;
resolve(true)
}).catch(error => {
init_data.loading = false;
reject(false)
})
})
}
return { config, init_data, handlerSwitch }
}
}
</script>

View File

@@ -0,0 +1,44 @@
<template>
<el-input
v-model="value"
type="textarea"
debounce
:rows="data.rows || 5"
:disabled="form_disabled[data.prop]"
:style="`width: ${data.width || '100%'}`"
:placeholder="data.placeholder"
@change="handlerChange"
/>
</template>
<script>
import { reactive, toRefs } from 'vue';
export default {
name: "TextareaComponent",
props: {
data: {
type: Object,
default: () => ({})
},
value: {
type: String,
default: ''
}
},
emits: ['update:value', 'callback'],
setup(props, { emit }){
const data = reactive({
value: '',
data: props.data
})
const handlerChange = (val) => {
emit("update:value", val)
}
return {
...toRefs(data),
handlerChange
}
}
}
</script>

View File

@@ -0,0 +1,86 @@
<template>
<el-upload
class="avatar-uploader"
action="#"
:http-request="handlerUpload"
:before-upload="handlerBeforeOnUpload"
:on-error="handlerOnError"
:show-file-list="false"
:disabled="disabled"
>
<img v-if="data.image_url" :src="data.image_url" class="avatar" />
<span v-else>+</span>
</el-upload>
</template>
<script>
import { reactive, getCurrentInstance, watch } from 'vue';
import { UploadFile } from "@/api/common";
export default {
name: "BasisUpload",
components: {},
props: {
value: {
type: String,
default: ""
},
disabled: {
type: Boolean,
default: false
}
},
emits: ["update:value", 'callback'],
setup(props, { emit }){
// 获取实例上下文
const { proxy } = getCurrentInstance();
const data = reactive({
image_url: ""
})
const handlerOnError = (res, file) => {}
const handlerBeforeOnUpload = async (file) => {
const isLt2M = file.size / 1024 / 1024 < 2; // 限制文件大小不能大于 2M
if (!isLt2M) {
proxy.$message.error("上传图片大小不能超过 2MB!");
return false;
}
return isJPG && isLt2M;
}
const handlerUpload = async (params) => {
const file = params.file;
// 实例化表单对象
const form = new FormData();
// 表单添加 files 字段
form.append("files", file);
// 上传接口
try{
const url = await startUpload(form)
data.image_url = url;
emit("update:imageUrl", url)
}catch{
console.log("上传失败")
}
}
/**
* 开始文件上传
*/
const startUpload = (form) => {
return new Promise((resolve, reject) => {
UploadFile(form).then(response => {
resolve(response.data.files_path);
})
})
}
watch(() => props.value, (newValue, oldValue) => {
data.image_url = newValue
})
return {
data,
handlerUpload,
handlerOnError,
handlerBeforeOnUpload
}
}
}
</script>
<style lang='scss' scoped>
</style>

View File

@@ -0,0 +1,69 @@
<template>
<div ref="editor"></div>
</template>
<script>
import { ref, onMounted, watch, nextTick } from 'vue';
import WangEditor from 'wangeditor';
// cookies
import { getToken } from "@/utils/cookies"; // 这是封装好的方法
export default {
name: 'Wangeditor',
components: {},
props: {
value: {
type: String,
default: ""
}
},
emits: ["update:value", 'callback'],
setup(props, { emit }){
let html = ref(null)
// 内容
let content = ref('');
// 富文本DOM元素
const editor = ref(null);
// 富文本对象
let editor_instance = null;
// 生命周期
onMounted(() => {
nextTick(() => {
// 创建富文本对象
editor_instance = new WangEditor(editor.value);
// 图片上传配置
editor_instance.config.uploadImgServer = process.env.VUE_APP_DEV_TARGET + '/upload' // 上传图片的接口地址
editor_instance.config.uploadFileName = 'files' // formdata中的name属性
editor_instance.config.uploadImgHeaders = {
Token: getToken() // 设置请求头
}
editor_instance.config.uploadImgHooks = {
// 图片上传并返回结果,但图片插入错误时触发
fail: function (xhr, editor, result) {
console.log(result)
},
// 例如服务器端返回的不是 { errno: 0, data: [...] } 这种格式,可使用 customInsert
customInsert: function(insertImgFn, result) {
// insertImgFn 可把图片插入到编辑器,传入图片 src ,执行函数即可
insertImgFn(result.data.files_path)
}
}
// 配置change事件
Object.assign(editor_instance.config, {
onchange(value) {
content.value = value;
emit("update:value", value);
},
});
// 创建
editor_instance.create();
})
})
watch(() => props.value, (newContent, oldContent) => {
editor_instance.txt.html(newContent);
})
return { editor }
}
}
</script>
<style lang='scss' scoped>
</style>

View File

@@ -0,0 +1,34 @@
export function relationHook(){
/**
* @param { String } value 控制方的选项 value
* @param { Array } data 关联关系的数据配置 relation_hidden
* @param { Object } props 显示/隐藏对象集保
*/
const HiddenItem = (value, data, props) => {
if(data && Array.isArray(data) && data.length > 0) {
data.forEach(item => {
const field = item[0];
const objValue = item[1];
props[field] = objValue[value]
// 选项为 1 时
// props["title"] = objValue["1"] => true
// props["iamge_url"] = objValue["1"] => true
// 选项为 0 时
// props["title"] = objValue["0"] => true
// props["iamge_url"] = objValue["0"] => undefined
})
}
}
const DisabledItem = (value, data, props) => {
if(data && Array.isArray(data) && data.length > 0) {
data.forEach(item => {
const field = item[0];
const objValue = item[1];
props[field] = objValue[value]
})
}
}
return { HiddenItem, DisabledItem };
}

View File

@@ -0,0 +1,88 @@
import { reactive } from "vue";
// 验证
import { validate_email, validate_password } from "@/utils/validate";
/**
* @returns BasisForm组件创建校验规则
*/
export function rulesHook(){
/**
* @param {*} data 配置额外组件
*/
const InitRules = (data = []) => {
if(data.length === 0) { return false; }
// 判断是否有required属性
data.forEach(item => {
//初始化规则数组
let rulesArr = [];
if(item.required) {
let json = { required: true, message: item.message || messageType(item), trigger: 'change' }
rulesArr.push(json)
}
// 是否有其他的校验规则
const rule = item.rule;
if(rule && Array.isArray(rule) && rule.length > 0) {
rulesArr = rulesArr.concat(rule);
}
// 用户名
if(item.value_type === 'username') {
const rule = {
validator(rule, value, callback, source, options) {
if(!value || value === ""){
callback(new Error("请输入用户名"));
}else if(!validate_email(value)) {
callback(new Error("邮箱格式不正确"));
} else {
callback();
}
}
}
rulesArr = rulesArr.concat(rule);
}
// 密码
if(item.value_type === 'password') {
const rule = {
validator(rule, value, callback, source, options) {
if(!value || value === ""){
callback(new Error("请输入用密码"));
}else if(!validate_password(value)) {
callback(new Error("请输入>=6并且<=20位的密码包含数字、字母"));
} else {
callback();
}
}
}
rulesArr = rulesArr.concat(rule);
}
// 定义rules属性赋值
item.rules = rulesArr;
});
return data;
}
/**
*
* @description 提示文本
*/
const messageType = (data) => {
let msg = "";
switch(data.type){
case "input":
case "wangeditor":
msg = "请输入";
break;
case "upload":
msg = "请上传";
break;
case "radio":
case "checkbox":
case "select":
case "date":
case "cascader":
msg = "请选择";
break;
default:
msg = "未定义";
}
return `${msg}${data.label}`;
}
return { InitRules };
}

View File

@@ -0,0 +1,128 @@
<template>
<el-form ref="formDom" v-loading="loading" element-loading-text="加载中,请稍候" :label-width="label_width" :model="field">
<el-row>
<template v-for="item in form_item" :key="item.prop">
<el-col v-if="!form_hidden[item.prop]" :span="item.col || 24">
<el-form-item v-if="item.type !== 'slot'" :label="item.label" :prop="item.prop" :rules="item.rules">
<component
v-model:value="field[item.prop]"
:is="item.type + 'Component'"
:data="item"
:disabled="disabled[item.prop]"
@callback="componentCallback"
/>
</el-form-item>
<el-form-item v-else :label="item.label" :prop="item.prop" :rules="item.rules">
<slot :name="item.slot_name"></slot>
</el-form-item>
</el-col>
</template>
</el-row>
<!-- button -->
<el-form-item v-if="form_button && form_button.length > 0">
<el-button
v-for="item in form_button"
:key="item.key"
:type="item.type"
@click="item.callback ? item.callback() : handlerFormActive(item)"
>
{{ item.label }}
</el-button>
</el-form-item>
</el-form>
</template>
<script>
import { reactive, ref } from 'vue';
// hook
import { rulesHook } from "./hook/rulesHook";
import { relationHook } from "./hook/relationHook";
// require import components
const files = require.context('@c/control', true, /\index.vue$/);
const modules = {}
files.keys().forEach(key => {
const name = key.split("/")
modules[name[1]+'Component'] = files(key).default || files(key)
})
export default {
name: 'BasisForm',
components: modules,
props: {
item: {
type: Array,
default: () => ([])
},
button: {
type: Array,
default: () => ([])
},
labelWidth: {
type: [String, Number],
default: "100px"
},
field: {
type: Object,
default: () => ({})
},
hidden: {
type: Object,
default: () => ({})
},
disabled: {
type: Object,
default: () => ({})
},
loading: {
type: Boolean,
default: false
},
hidden: {
type: Object,
default: () => ({})
},
disabled: {
type: Object,
default: () => ({})
}
},
emits: ["callback"],
setup(props, { emit }){
const { InitRules } = rulesHook();
const { HiddenItem, DisabledItem } = relationHook();
// 校验规则处理
const form_item = reactive(InitRules(props.item));
const label_width = ref(props.labelWidth);
const form_button = reactive(props.button);
const form_hidden = reactive(props.hidden);
const form_disabled = reactive(props.disabled);
// 表单提交验证
const formDom = ref(null);
// form表单动作
const handlerFormActive = (data) => {
// 提交表单事件
if(data.key === 'submit') {
formDom.value.validate((valid) => valid && emit("callback"));
}
// 重置表单事件
if(data.key === 'reset') { handlerFormReset(); }
}
/**
* 重置表单
*/
const handlerFormReset = () => {
formDom.value.resetFields();
}
// change 事件
const componentCallback = (params) => {
if(params.formItem.relation_hidden) {
HiddenItem(params.value, params.formItem.relation_hidden, form_hidden)
}
if(params.formItem.relation_disabled) {
DisabledItem(params.value, params.formItem.relation_hidden, form_disabled)
}
}
return { formDom, form_item, label_width, form_button, form_hidden, form_disabled, formDom, handlerFormActive, componentCallback, handlerFormReset }
}
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,69 @@
<template>
<el-pagination
class="pull-right"
sizs="small"
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
v-model:current-page="current_page"
:page-size="10"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</template>
<script>
import { ref, watchEffect, watch } from 'vue';
export default {
name: 'Pagination',
props: {
total: {
type: [Number, String],
default: 0
},
current: {
type: [Number, String],
default: 0
},
flag: {
type: Boolean,
default: false
},
},
emits: ["sizeChange", "currentChange", "update:flag"],
setup(props, { emit }){
// 当前页码
const current_page = ref(1);
// 总条数统计
const total = ref(props.total);
const page_status = ref(false);
// 监听props.total发生变化时更新total
watchEffect(() => {
total.value = props.total;
current_page.value = props.current;
})
watch(() => props.flag, (newValue) => {
page_status.value = newValue
})
// 页码事件
const handleSizeChange = (val) => {
page_status.value = true
emit("update:flag", true)
const params = {
pageNumber: 1,
pageSize: val
}
emit("sizeChange", params, 'page')
}
const handleCurrentChange = (val) => {
if(page_status.value) { return false }
const params = {
pageNumber: val
}
emit("currentChange", params, 'page')
}
return { total, current_page, handleSizeChange, handleCurrentChange }
}
}
</script>

View File

@@ -0,0 +1,130 @@
<template>
<div v-if="button_group.length > 0" class="form-search-button-group">
<el-button v-for="item in button_group" :key="item.id" :type="item.type" @click="item.callback && item.callback()">{{ item.label }}</el-button>
</div>
<el-form ref="searchForm" inline :label-width="label_width" :model="field">
<template v-if="form_item && form_item.length > 0">
<el-form-item v-for="item in form_item" :key="item.prop" :label="item.label" :prop="item.prop" :label-width="item.label_width || label_width">
<component :ref="componentDom" :key="item.type" v-model:value="field[item.prop]" :is="item.type + 'Component'" :data="item" @callback="componentCallback" />
</el-form-item>
<!-- button -->
<el-form-item>
<div class="pull-right">
<el-button type="danger" @click="handlerSearch">搜索</el-button>
<el-button v-if="button.reset_button" @click="handlerReset">重置</el-button>
</div>
</el-form-item>
</template>
</el-form>
</template>
<script>
import { reactive, ref, inject, toRefs, nextTick } from 'vue';
// require import components
const files = require.context('@c/control', true, /\index.vue$/);
const modules = {}
files.keys().forEach(key => {
const name = key.split("/")
modules[name[1]+'Component'] = files(key).default || files(key)
})
export default {
name: 'BasisForm',
components: modules,
props: {
item: {
type: Array,
default: () => ([])
},
labelWidth: {
type: [String, Number],
default: "100px"
},
field: {
type: Object,
default: () => ({})
},
button: {
type: Object,
default: () => ({})
},
button_group: {
type: Array,
default: () => ([])
},
button_col: {
type: Number,
default: 24
}
},
emits: ["callbackSearch"],
setup(props, { emit }){
const data = reactive({ key: "", value: "" });
// 获取父组件provide数据
const search_config = inject("search_config");
// label占位宽度
const label_width = ref(search_config?.label_width || props.labelWidth);
// 组件元素配置
const form_item = reactive(search_config?.form_item || props.item);
// 字段配置
const field = reactive(search_config?.form_data || props.field);
// 按钮配置
const button = reactive(search_config?.form_button || props.button);
// 按钮组配置
const button_group = reactive(search_config?.form_button_group || props.button_group);
// 按钮占位 col
const button_col = reactive(search_config?.button_col || props.button_col);
/**
* 获取动态组件的dom
*/
const keyword = ref(null)
const componentDom = el => {
console.log(el)
if(el && el.data.type === 'keyword') {
keyword.value = el
}
}
// 搜索
const handlerSearch = () => {
const data = formatRequest();
// 回调父组件方法
emit("callbackSearch", data, "search");
}
// 重置
const searchForm = ref(null);
const handlerReset = () => {
searchForm.value.resetFields();
// 调用关键字组件的clear方法
keyword.value && keyword.value.handlerClear()
emit("callbackSearch", {}, "search");
}
/** 参数格式化 */
const formatRequest = () => {
// 声明空的JSON对象临时存储数据
const request_data = {};
// 过滤在值存在的数据
for(let key in field) {
if(field[key] !== "") { request_data[key] = field[key]; }
}
// 判断关键字下拉选择是否有值存在
if(data.key && data.value) { request_data[data.key] = data.value; }
// 返回参数
return request_data;
}
// 回调
const componentCallback = (params) => {
if(params.type === 'keyword') {
data.key = params.value.key
data.value = params.value.value
}
}
return {
...toRefs(data),
form_item, field, label_width, button, componentCallback, handlerSearch, handlerReset, componentDom, searchForm, button_group, button_col
}
}
}
</script>
<style lang="scss" scoped>
.form-search-button-group { margin-bottom: 20px; }
</style>

View File

@@ -0,0 +1,38 @@
<template>
<svg class="svg-class" :class="svgClassName">
<use :href="svgIcon"></use>
</svg>
</template>
<script>
import { ref, computed } from "vue";
export default {
name: "SvgIcon",
props: {
iconName: {
type: String,
default: ""
},
className: {
type: String,
default: ""
}
},
setup(props){
// className
const svgClassName = ref(props.className);
// svg 图标
const svgIcon = computed(() => `#icon-${props.iconName}`);
return {
svgClassName,
svgIcon
}
}
}
</script>
<style lang="scss">
.svg-class {
width: 1em;
height: 1em;
fill: currentColor;
}
</style>

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1608734152787" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1160" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M959.390493 527.761985c0 23.127743-18.419512 41.87369-41.138956 41.87369-12.335956 0-23.276123-5.640464-30.817889-14.393825L541.089525 202.729238c-16.065907-16.353456-42.113143-16.353456-58.179051 0L237.802034 452.204126c2.877537 5.636371 4.653997 11.926634 4.653997 18.690688l0 165.475775c0 0.328481-0.089028 0.635473-0.097214 0.961908l0.297782 0 0 163.003468c0 23.125697 18.506493 41.871643 41.337477 41.871643l82.568531 0L366.562607 677.738765c0-22.850427 18.520819-41.3692 41.3692-41.3692L614.775758 636.369565c22.848381 0 41.3692 18.518772 41.3692 41.3692 0 22.846334-18.520819 41.3692-41.3692 41.3692L449.299983 719.107965l0 165.473729c0 22.427802-17.875113 40.583301-40.142256 41.246403l0 0.121773L263.148305 925.94987c-57.115835 0-103.418394-46.864354-103.418394-104.678084L159.729911 637.331473l0.084934 0c-0.00614-0.326435-0.097214-0.633427-0.097214-0.961908L159.717631 531.680225l-23.162536 23.575952c-7.541767 8.745175-18.475793 14.379499-30.807656 14.379499-22.719444 0-41.138956-18.745946-41.138956-41.87369 0-11.862165 4.895497-22.520923 12.687974-30.141484l-0.237407-0.2415 362.215602-368.668571c40.164768-40.881083 105.285928-40.881083 145.450696 0l361.569896 368.010585-0.299829 0.305969C954.175725 504.680291 959.390493 515.563152 959.390493 527.761985zM614.733802 842.206584c0.443092 0 0.854461 0.119727 1.294482 0.132006l0-0.132006 122.683157 0c22.828938 0 41.337477-18.745946 41.337477-41.871643L780.048919 637.331473l0.534166 0c-0.016373-0.496304-0.145309-0.961908-0.145309-1.464351 0-23.125697 18.417465-41.87369 41.138956-41.87369 22.719444 0 41.136909 18.747993 41.136909 41.87369 0 0.502443-0.125867 0.968048-0.145309 1.464351l0.405229 0 0 183.940312c0 57.812707-46.299489 104.678084-103.418394 104.678084l-143.528927 0 0-0.132006c-0.441045 0.014326-0.852414 0.132006-1.294482 0.132006-22.719444 0-41.138956-18.7439-41.138956-41.869596C573.594847 860.9556 592.014358 842.206584 614.733802 842.206584z" p-id="1161"></path></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1608736466130" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="791" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M782.27531852 936.77037037H241.72468148c-60.19602963 0-109.22666667-49.03063703-109.22666666-109.22666667V196.4562963c0-60.19602963 49.03063703-109.22666667 109.22666666-109.22666667h540.55063704c60.19602963 0 109.22666667 49.03063703 109.22666666 109.22666667v631.0874074c0 60.19602963-49.03063703 109.22666667-109.22666666 109.22666667zM241.72468148 160.04740741c-20.02488889 0-36.40888889 16.384-36.40888889 36.40888889v631.0874074c0 20.02488889 16.384 36.40888889 36.40888889 36.40888889h540.55063704c20.02488889 0 36.40888889-16.384 36.40888889-36.40888889V196.4562963c0-20.02488889-16.384-36.40888889-36.40888889-36.40888889H241.72468148z" p-id="792"></path><path d="M747.44414815 343.30548148H271.70133333c-20.14625185 0-36.40888889-16.26263703-36.40888888-36.40888889s16.26263703-36.40888889 36.40888888-36.40888889h475.74281482c20.14625185 0 36.40888889 16.26263703 36.40888888 36.40888889s-16.26263703 36.40888889-36.40888888 36.40888889zM747.44414815 497.43644445H271.70133333c-20.14625185 0-36.40888889-16.26263703-36.40888888-36.4088889s16.26263703-36.40888889 36.40888888-36.40888888h475.74281482c20.14625185 0 36.40888889 16.26263703 36.40888888 36.40888888s-16.26263703 36.40888889-36.40888888 36.4088889zM554.47703703 653.99466667H271.70133333c-20.14625185 0-36.40888889-16.26263703-36.40888888-36.40888889s16.26263703-36.40888889 36.40888888-36.40888889h282.7757037c20.14625185 0 36.40888889 16.26263703 36.40888889 36.40888889s-16.26263703 36.40888889-36.40888889 36.40888889z" p-id="793"></path></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1621589152962" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2857" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M706.432 195.136c-14.656-9.536-34.56-5.44-44.224 9.408-9.536 14.848-5.376 34.688 9.472 44.288C772.032 314.048 832 424.448 832 544.192c0 193.92-157.504 351.68-351.04 351.68s-351.04-157.76-351.04-351.68c0-119.68 60.032-230.144 160.448-295.296 14.848-9.6 19.072-29.504 9.472-44.352C290.112 189.632 270.336 185.408 255.552 195.072 136.768 272.192 65.856 402.688 65.856 544.192 65.856 773.504 252.032 960 480.96 960S896 773.504 896 544.192C896 402.624 825.152 272.128 706.432 195.136zM480.96 572.16c17.664 0 32-14.656 32-32.832l0-380.8c0-18.112-14.336-32.768-32-32.768s-32 14.656-32 32.768l0 380.8C448.96 557.504 463.232 572.16 480.96 572.16z" p-id="2858"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1621586114074" class="icon" viewBox="0 0 1025 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2473" xmlns:xlink="http://www.w3.org/1999/xlink" width="200.1953125" height="200"><defs><style type="text/css"></style></defs><path d="M980.8 468.8H43.2C17.6 468.8 0 486.4 0 512s17.6 43.2 43.2 43.2h939.2c25.6 0 43.2-17.6 43.2-43.2-1.6-25.6-19.2-43.2-44.8-43.2zM980.8 768H43.2C17.6 768 0 785.6 0 811.2s17.6 43.2 43.2 43.2h939.2c25.6 0 43.2-17.6 43.2-43.2-1.6-25.6-19.2-43.2-44.8-43.2zM43.2 256h939.2c25.6 0 43.2-17.6 43.2-43.2s-17.6-43.2-43.2-43.2H43.2c-25.6 0-43.2 17.6-43.2 43.2S17.6 256 43.2 256z" p-id="2474"></path></svg>

After

Width:  |  Height:  |  Size: 775 B

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1627108347577" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4935" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.eot?#iefix") format("embedded-opentype"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.woff2") format("woff2"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.woff") format("woff"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.ttf") format("truetype"), url("//at.alicdn.com/t/font_1031158_1uhr8ri0pk5.svg#iconfont") format("svg"); }
</style></defs><path d="M660.750044 997.266537c-14.811783 0-29.804198-5.418945-40.461457-15.173046-15.714941-14.45052-69.181866-55.995766-107.114482-55.995767-38.293879 0-91.580173 41.545246-107.114482 55.995767-16.256835 15.173046-42.809667 19.688834-63.221027 10.657259l-3.070736-1.445052-153.898042-89.231964c-17.701887-13.1861-27.094726-38.47451-22.217675-59.969659 4.696419-20.772623 13.908626-87.786911-5.057682-120.661845-18.966308-32.874934-81.64544-58.343976-102.0568-64.846711-20.41136-6.322103-37.932616-26.191568-41.545246-47.325454-0.361263-2.167578-9.212207-54.370083-9.212207-92.663962 0-38.293879 8.850944-90.315752 9.212207-92.663962 3.61263-21.133886 21.133886-41.003352 41.545246-47.325454s83.090492-31.791145 102.0568-64.84671c19.146939-33.055565 9.934733-99.889222 5.057682-120.661845-4.877051-21.675781 4.335156-46.78356 22.217675-59.96966l2.890104-1.986946 153.898042-88.8707c20.41136-9.031575 46.964191-4.515788 63.221027 10.657258 15.714941 14.45052 69.181866 55.995766 107.114482 55.995767 38.293879 0 91.580173-41.545246 107.114483-55.995767 16.256835-15.173046 42.809667-19.688834 63.221026-10.657258l3.070736 1.445052 153.898042 89.231963c17.701887 13.1861 27.094726 38.47451 22.217675 59.969659-4.696419 20.772623-13.908626 87.786911 5.057682 120.661846 18.966308 32.874934 81.64544 58.343976 102.0568 64.84671 20.41136 6.322103 37.932616 26.191568 41.545246 47.325454 0.361263 2.167578 9.212207 54.370083 9.212207 92.663962 0 38.293879-8.850944 90.315752-9.212207 92.663962-3.61263 21.133886-21.133886 41.003352-41.545246 47.325454s-83.090492 31.791145-102.0568 64.84671c-19.146939 33.055565-9.934733 99.889222-5.057682 120.661845 4.877051 21.495149-4.335156 46.78356-22.217675 59.96966l-2.890104 1.986946-153.898042 88.870701c-7.22526 2.890104-14.992415 4.515788-22.75957 4.515787zM225.247486 852.580702l140.531311 81.284177c15.353678-13.727994 82.548598-70.626918 147.214676-70.626918s131.860998 56.718292 147.214677 70.626918l140.53131-81.284177c-4.154525-19.869466-19.869466-106.572588 12.644206-162.748986 33.416828-57.982713 120.661845-87.786911 136.376786-92.844593 1.445052-8.489681 8.128418-50.938084 8.128417-80.561651 0-29.623567-6.683366-72.07197-8.128417-80.561651-15.714941-5.057682-102.959958-34.86188-136.376786-92.844594-32.333039-56.176398-16.79873-142.87952-12.644206-162.748985l-140.53131-81.284177c-15.353678 13.727994-82.548598 70.626918-147.214677 70.626918-64.84671 0-131.860998-56.718292-147.214676-70.626918l-140.531311 81.284177c4.154525 19.869466 19.869466 106.572588-12.644205 162.748985-33.416828 57.982713-120.661845 87.786911-136.376786 92.844594-1.445052 8.489681-8.128418 50.938084-8.128418 80.561651 0 29.623567 6.683366 72.07197 8.128418 80.561651 15.714941 5.057682 102.959958 34.86188 136.376786 92.844593 32.333039 56.176398 16.79873 142.87952 12.644205 162.748986z" p-id="4936"></path><path d="M512.993473 729.751279c-117.59111 0-213.145176-95.734697-213.145175-213.325807s95.734697-213.325807 213.325807-213.325807 213.145176 95.734697 213.145175 213.325807S630.584583 729.751279 512.993473 729.751279z m0-363.972482c-83.090492 0-150.646675 67.556183-150.646675 150.646675s67.556183 150.646675 150.646675 150.646675c83.090492 0 150.646675-67.556183 150.646675-150.646675S596.083965 365.778797 512.993473 365.778797z" p-id="4937"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1608814595533" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="809" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M512 962C263.4875 962 62 760.5125 62 512S263.4875 62 512 62s450 201.4875 450 450-201.4875 450-450 450z m0-825.00000029C304.8875 136.99999971 136.99999971 304.8875 136.99999971 512s167.88750029 375.00000029 375.00000029 375.00000029 375.00000029-167.88750029 375.00000029-375.00000029S719.1125 136.99999971 512 136.99999971z m0 675a299.40000029 299.40000029 0 0 1-234-112.5A299.40000029 299.40000029 0 0 1 512 586.99999971a299.25 299.25 0 0 1 233.96249971 112.5A299.25 299.25 0 0 1 512 811.99999971z m0-262.49999942a150.00000029 150.00000029 0 1 1 0-300.00000058 150.00000029 150.00000029 0 0 1 0 300.00000058z" p-id="810"></path></svg>

After

Width:  |  Height:  |  Size: 1011 B

View File

@@ -0,0 +1,7 @@
// 获取当前svg目录所有为.svg结尾的文件
const context = require.context('./icon', false, /\.svg$/);
// 解析获取的.svg文件的文件名称并返回
const requireAll = (requireContext) => {
return requireContext.keys().map(requireContext);  
};
requireAll(context);

View File

@@ -0,0 +1,67 @@
<template>
<el-switch
v-model="init_data.value"
:loading="init_data.loading"
@change="handlerSwitch($event)"
>
</el-switch>
</template>
<script>
import { reactive, getCurrentInstance } from 'vue';
// requestUrl
import ApiUrl from "@/api/requestUrl";
//API
import { SwitchStatus } from "@/api/common";
export default {
name: 'SwitchComponent',
props: {
data: {
type: Object,
default: () => ({})
},
config: {
type: Object,
default: () => ({})
}
},
setup(props){
// 配置信息
const config = reactive(props.config);
// 数据
const data = reactive(props.data);
// 初始值
const init_data = reactive({
value: data[config.prop],
loading: false
});
// 获取实例上下文
const { proxy } = getCurrentInstance();
// 事件
const handlerSwitch = (value) => {
// 请求参数
const key_id = config.key_id;
if(!key_id) { return false; }
init_data.loading = true;
// 参数
const request_data = {
url: config.api_url || ApiUrl[config.api_module][config.api_module_key],
method: config.method || "post",
data: {
[key_id]: data[key_id], // 等价于["id"]: data["id"]
[config.prop]: value // 等价于["status"]: value
}
}
// 接口请求
SwitchStatus(request_data).then(response => {
proxy.$message.success(response.message);
init_data.value = value;
init_data.loading = false;
}).catch(error => {
init_data.loading = false;
})
}
return { config, init_data, handlerSwitch }
}
}
</script>

View File

@@ -0,0 +1,39 @@
import { reactive } from "vue";
/**
* BasisTable组件默认配置
* @param { Boolean } selection 多选项配置
* @param { Boolean } batch_delete 指量删除按钮
* @param { Boolean } pagination 分页
*/
export function configHook(){
const config = reactive({
selection: true,
batch_delete: true,
pagination: true,
request: true,
search: true,
action_request: false,
row_key: "id",
expand_all: true,
has_button_delete: "",
has_button_batch_delete: ""
})
/**
*
* @param {*} data 配置额外组件true显示false不显示
*/
const configInit = (data = {}) => {
// 无数据时不执行
if(JSON.stringify(data) === "{}") { return false; }
// 获取默认配置项的所有key
const keys = Object.keys(config);
// 循环传入的配置项
for(let key in data) {
// 过滤原型链的对象
if (!data.hasOwnProperty(key)) { continue; }
// ES6语法判断传入的key和默认配置的key相同时则直接更新默认配置项的值
if(keys.includes(key)) { config[key] = data[key]; }
}
}
return { config, configInit };
}

View File

@@ -0,0 +1,121 @@
<template>
<SearchForm v-if="config.search" @callbackSearch="getList" />
<el-table :row-key="config.row_key" :default-expand-all="config.expand_all" :data="table_data.data" v-loading="table_data.loading" element-loading-text="加载中,请稍候" border style="width: 100%" @selection-change="handleSelectionChange">
<el-table-column v-if="config.selection" type="selection" width="40"></el-table-column>
<template v-for="header in data.render_header" :key="header.prop">
<!-- switch -->
<el-table-column v-if="header.type === 'switch'" :label="header.label" :width="header.width">
<template #default="scope">
<Switch :data="scope.row" :config="header" />
</template>
</el-table-column>
<!-- function -->
<el-table-column v-else-if="header.type === 'function'" :label="header.label" :width="header.width">
<template #default="scope">
<div v-html="header.callback && header.callback(scope.row)"></div>
</template>
</el-table-column>
<!-- slot -->
<el-table-column v-else-if="header.type === 'slot'" :label="header.label" :width="header.width">
<template #default="scope">
<slot :name="header.slot_name" :data="scope.row"></slot>
<el-button v-if="header.delete_elem" @click="handlerDelete('delete', scope.row)" v-has-button="config.has_button_delete">删除</el-button>
</template>
</el-table-column>
<!-- text -->
<el-table-column v-else :prop="header.prop" :label="header.label" :width="header.width"></el-table-column>
</template>
</el-table>
<el-row class="margin-top-20">
<el-col :span="6">
<el-button v-if="config.batch_delete" :disabled="!table_data.data_id" @click="handlerDelete('batch')" v-has-button="config.has_button_batch_delete">批量删除</el-button>
</el-col>
<el-col :span="18">
<Pagination
v-if="config.pagination"
@sizeChange="getList"
@currentChange="getList"
:total="table_data.total"
:current="table_data.current_page" />
</el-col>
</el-row>
</template>
<script>
import { reactive, getCurrentInstance, onBeforeMount, watch } from 'vue';
// store
import { useStore } from "vuex";
// components
import SearchForm from "@c/search";
import Pagination from "@c/pagination";
import Switch from "@c/control/switch";
// hook
import { configHook } from "./configHook";
import { requestHook } from "./requestHook";
export default {
name: 'TableComponents',
components: { Pagination, Switch, SearchForm },
props: {
columns: {
type: Array,
default: () => ([])
},
config: {
type: Object,
default: () => ({})
},
request: {
type: Object,
default: () => ({})
},
search: {
type: Boolean,
default: true
}
},
emits: ["onload"],
setup(props, { emit }){
// store
const store = useStore();
// 获取实例上下文
const { proxy } = getCurrentInstance();
const { config, configInit } = configHook();
const { requestData, table_data, handlerDeleteComfirm, saveDataId } = requestHook();
const data = reactive({
table_data: [],
render_header: props.columns,
row_data_id: [],
currentPage: 0,
total: 0
})
onBeforeMount(() => {
// 默认配置项处理
configInit(props.config);
// 请求数据
getList(props.request)
})
const getList = (params, type) => {
requestData(params, type).then(response => {
emit("onload", table_data);
});
}
/** 删除动作 */
const handlerDelete = (type, val) => {
if(type === "delete") { saveDataId(val); }
handlerDeleteComfirm();
}
/** 复选框事件 */
const handleSelectionChange = (val) => {
saveDataId(val);
}
watch(() => store.state.app.table_action_request, () => {
config.action_request && getList();
})
return { data, config, table_data, requestData, getList, handlerDelete, handleSelectionChange }
}
}
</script>
<style lang='scss' scoped>
.margin-top-20 { margin-top: 20px; }
</style>

View File

@@ -0,0 +1,154 @@
import { reactive, getCurrentInstance } from "vue";
// requestUrl
import ApiUrl from "@/api/requestUrl";
// API
import { TableData, CommonApi } from "@/api/common";
import { configHook } from "./configHook";
import { ElMessage } from "element-plus"
export function requestHook(){
const { config, configInit } = configHook();
// 获取实例上下文
const { proxy } = getCurrentInstance();
let request_config = {
has: true, // 是否请求
url: "", // 请求模块
method: "post", // 请求类型
data: {}, // 请求参数
delete_key: '', // 删除接口的KEY
search_params: {}
}
const table_data = reactive({
data: [],
data_id: "",
current_page: 0,
total: 0,
loading: false
})
/**
* @returns 列表数据请求
*/
const loadData = () => {
// 判断条件是否请求接口
if(!request_config.has) { return false; }
if(!request_config.url) { return false; }
// 参数
const url = ApiUrl[request_config.url]?.list?.url;
const method = ApiUrl[request_config.url]?.list?.method || 'post';
const data = { ...request_config.data, ...request_config.search_params };
if(!url) {
console.log("请求地址不存在");
return Promise.reject();
}
const request_data = {
url,
method,
data
}
// 加载状态
table_data.loading = true;
return new Promise((resolve, reject) => {
TableData(request_data).then(response => {
let response_data = response.data.data;
// 是否格式化数据
if(request_config.format_data && Object.prototype.toString.call(request_config.format_data) === "[object Function]") {
response_data = request_config.format_data(response.data.data);
}
console.log(response_data)
table_data.data = response_data;
table_data.total = response.data.total;
table_data.current_page = response.data.current_page;
// 取消加载状态
table_data.loading = false;
resolve(response.data);
}).catch(error => {
reject()
// 取消加载状态
table_data.loading = false;
})
})
}
const requestData = (data = {}, type = 'init') => {
// 初始化逻辑
if(type === "init") {
request_config = {...request_config, ...data};
}
// 分页逻辑
if(type === 'page') {
request_config.data = {...request_config.data, ...data};
}
// 搜索
if(type === "search"){
request_config.data.pageNumber = 1;
request_config.data.pageSize = 10;
request_config.search_params = data;
}
// 请求数据
return loadData();
}
/**
*
* @param {Object} data
* @description 手动请求
*/
const handlerRequestData = (data = {}) => {
table_data.params_data = data;
loadData();
}
/**
*
* @param { String、Array } value
* @description 存储数据ID作用于删除接口使用
* 删除接口
*/
const saveDataId = (value) => {
const isArray = Array.isArray(value);
if(!isArray) {
table_data.data_id = value[request_config.delete_key];
}else{
table_data.data_id = value.length > 0 ? value.map(item => item[request_config.delete_key]).join() : "";
}
console.log(table_data.data_id)
}
/**
*
* @description 删除动作的确认弹窗
*/
const handlerDeleteComfirm = () => {
proxy.deleteConfirm({
title: "删除",
message: "确认删除当前数据吗?",
thenFun: () => { handlerDelete(); }
});
}
const handlerDelete = () => {
const url = ApiUrl?.[request_config.url]?.delete?.url
const method = ApiUrl?.[request_config.url]?.delete?.method || "post"
if(!url) {
console.log("未配置删除接口参数");
return false;
}
// 参数
const request_data = {
url,
method: method,
data: { [request_config.delete_key]: table_data.data_id },
}
CommonApi(request_data).then(response => {
// 成功提示
proxy.$message({
message: response.message,
type: "success"
})
loadData();
table_data.data_id = "";
proxy.deleteConfirmClose();
}).catch(error => {
proxy.confirmButtonLoading(false);
})
}
return { table_data, requestData, handlerDelete, saveDataId, handlerDeleteComfirm };
}

View File

@@ -0,0 +1,67 @@
<template>
<div ref="editor"></div>
</template>
<script>
import { ref, onMounted, watch } from 'vue';
import WangEditor from 'wangeditor';
// cookies
import { getToken } from "@/utils/cookies"; // 这是封装好的方法
export default {
name: 'Wangeditor',
components: {},
props: {
content: {
type: String,
default: ""
}
},
emits: ["update:content"],
setup(props, { emit }){
let html = ref(null)
// 内容
let content = ref();
// 富文本DOM元素
const editor = ref();
// 富文本对象
let editor_instance = null;
// 生命周期
onMounted(() => {
// 创建富文本对象
editor_instance = new WangEditor(editor.value);
// 图片上传配置
editor_instance.config.uploadImgServer = process.env.VUE_APP_DEV_TARGET + '/upload' // 上传图片的接口地址
editor_instance.config.uploadFileName = 'files' // formdata中的name属性
editor_instance.config.uploadImgHeaders = {
Token: getToken() // 设置请求头
}
editor_instance.config.uploadImgHooks = {
// 图片上传并返回结果,但图片插入错误时触发
fail: function (xhr, editor, result) {
console.log(result)
},
// 例如服务器端返回的不是 { errno: 0, data: [...] } 这种格式,可使用 customInsert
customInsert: function(insertImgFn, result) {
// insertImgFn 可把图片插入到编辑器,传入图片 src ,执行函数即可
insertImgFn(result.data.files_path)
}
}
// 配置change事件
Object.assign(editor_instance.config, {
onchange(value) {
content.value = value;
emit("update:content", value);
},
});
// 创建
editor_instance.create();
})
watch(() => props.content, (newContent, oldContent) => {
editor_instance.txt.html(newContent);
})
return { editor }
}
}
</script>
<style lang='scss' scoped>
</style>