枚举值实现下拉和tag展示的组件封装(vue2+js+elementUI)
枚举值组件EnumBase
(实现展示数据的时候提供tag样式,也可以变成下拉选择)
-
AllowSelect
判断是要下拉选择还是展示tag标签 (所有prop要使用的时候尽量通过computed进行,避免直接修改prop值)<div :class="[AllowSelect?'auto-width':'']"> <tr-select v-if="AllowSelect" ... /> <template v-else> ... </template> </div>
-
传入的枚举数据需要处理。使用传入过滤方法或者默认方法进行过滤,将源数据转换成数组形式,然后使用数组的
filter
方法过滤,需确保返回结果是数组。Options: { get() { const defaultFilter = (value) => value const filterMethod = this.optionFilter ?? defaultFilter const sourceOptions = this.EnumData?.toArray() || [] const filterResult = sourceOptions.filter(filterMethod) const filterResultIsArray = Array.isArray(filterResult) return filterResultIsArray ? filterResult : [] } }
-
对于不允许选择,存在直接展示和tag展示。tag的样式从prop传入数组中获取。 这时候需要注意
label
的长度来控制展示效果(getStringLength方法
)TagStyle: { get() { let widestLabelLength = 0 this.Options.forEach((option) => { const labelWidth = this.getStringLength(option.label) widestLabelLength = widestLabelLength < labelWidth ? labelWidth : widestLabelLength }) const defaultTagWidth = `${widestLabelLength * 5 + 10}px` return `width:${this.tagWidth ?? defaultTagWidth};` } },
EnumBase
代码:
<template>
<div :class="[AllowSelect?'auto-width':'']">
<tr-select
v-if="AllowSelect"
:data.sync="Data"
:options="Options"
v-bind="$attrs"
width="100%"
v-on="$listeners"
/>
<template v-else>
<el-tag
v-if="UseTag && LabelVisible"
:type="Type"
:disable-transitions="true"
v-bind="$attrs"
v-on="$listeners"
>
<div class="tag-content" :style="TagStyle">
<span>{{ Label }}</span>
</div>
</el-tag>
<span v-else>{{ Label }}</span>
</template>
</div>
</template>
<script>
import TrSelect from '@/components/Select/Select.vue'
// #region 值转换函数
// #endregion
export default {
components: {
TrSelect
},
model: {
prop: 'data',
event: 'update:data'
},
props: {
/** 绑定值 */
data: {
type: [String, Number, Array],
default: undefined
},
/** 是否允许选择 */
allowSelect: {
type: Boolean,
default: true
},
/** 是否使用tag装饰,仅在allowSelect为false时有效 */
useTag: {
type: Boolean,
default: true
},
/** Tag的宽度 */
tagWidth: {
type: String,
default: undefined
},
/** options的过滤函数 */
optionFilter: {
type: Function,
default: undefined
}
},
data() {
return {
myData: undefined,
enumData: undefined,
EnumData: undefined
}
},
computed: {
Data: {
get() {
return this.data ?? this.myData
},
set(val) {
this.myData = val
this.$emit('update:data', val)
}
},
AllowSelect: {
get() {
return this.allowSelect
}
},
UseTag: {
get() {
return !this.allowSelect && this.useTag
}
},
TagStyle: {
get() {
let widestLabelLength = 0
this.Options.forEach((option) => {
const labelWidth = this.getStringLength(option.label)
widestLabelLength = widestLabelLength < labelWidth ? labelWidth : widestLabelLength
})
// console.info(widestLabelLength)
const defaultTagWidth = `${widestLabelLength * 5 + 10}px`
return `width:${this.tagWidth ?? defaultTagWidth};`
}
},
Label: {
get() {
// console.info(this.Data)
// console.info(this.valueToLabel(this.Data))
return this.valueToLabel(this.Data)
}
},
LabelVisible: {
get() {
return this.Label !== ''
}
},
Type: {
get() {
return this.valueToType(this.Data)
}
},
Options: {
get() {
const defaultFilter = (value) => value
const filterMethod = this.optionFilter ?? defaultFilter
const sourceOptions = this.EnumData?.toArray() || []
const filterResult = sourceOptions.filter(filterMethod)
const filterResultIsArray = Array.isArray(filterResult)
return filterResultIsArray ? filterResult : []
}
}
},
watch: {
enumData: {
deep: true,
handler(newValue) {
this.EnumData = newValue
}
},
data: {
handler(value) {
this.myData = value
}
}
},
mounted() {
},
methods: {
valueToLabel(value) {
// console.info(this.EnumData)
const label = this.EnumData?.getItemByValue(value)?.label || ''
return label
},
valueToType(value) {
const type = this.EnumData?.getItemByValue(value)?.type || ''
return type
},
getStringLength(str) {
if (str === undefined) { return 0 }
const chineseCharacterLength = 2
const englishCharacterLength = 1
const chinese = str.match(/[\u4e00-\u9fa5]/g)
const chineseCount = chinese ? chinese.length : 0
const englishCount = str.length - chineseCount
return chineseCount * chineseCharacterLength + englishCount * englishCharacterLength
}
}
}
</script>
<style lang="scss" scoped>
.tag-content {
display: flex;
justify-content: center;
}
</style>
TrSelect
封装
TrSelect
代码
<template>
<div class="tr-select" :style="WidthStyle">
<el-select
ref="selectRef"
v-model="Data"
v-bind="$attrs"
:filterable="Filterable"
:remote="remote"
:placeholder="placeholder || $t('qing-xuan-ze')"
:remote-method="remoteSearch"
:loading="loading"
:clearable="clearable"
:multiple="multiple"
:collapse-tags="true"
:disabled="disabled"
:default-first-option="true"
style="width: 100%"
@focus="handleFocus"
@change="handleChange"
@remove-tag="handleRemoveTag"
@clear="handleClear"
@blur="handleBlur"
>
<el-option
v-for="(opt, index) in Options"
:key="index"
:label="opt.label"
:value="opt.value"
:disabled="opt.disabled || false"
/>
</el-select>
</div>
</template>
<script>
export default {
name: 'TrSelect',
components: {},
model: {
prop: 'data',
event: 'update:data'
},
props: {
data: {
type: [String, Number, Array],
default: ''
},
/**
* getDataFunction(done,data,searchWord)
* 当调用done(newOptions)时,会把newOptions内的选项传递给组件
* 当设置remote为True时,用户输入的内容会随searchWord传递
* 为了能够解决远程搜索的反显问题,当remote为true时,会把当前组件绑定的data也传递进去
* 这样可以通过该参数搜索后台,并进行反显
* */
getDataFunction: {
type: Function,
default: undefined
},
/** 设置默认的选项,该选项不会被getDataFunction传递的选项覆盖 */
options: {
type: Array,
default: () => [
// {
// label: '选项1',
// value: 1
// },
// {
// label: '选项2',
// value: 2
// },
// {
// label: '选项3',
// value: 3
// }
]
},
/**
* 刷新选项的方式: 1:created时刷新(默认值) 2.mounted时刷新 4.focus时刷新 8.refreshFlag更改时触发
* 可以任意组合上述四项,例如传递3,代表了created和mounted时会刷新,传递5,代表了created和focus时会刷新
*/
refreshMode: {
type: Number,
default: 1
},
/** 使用deepWatch监听 */
refreshFlag: {
type: [Boolean, String, Object, Array, Number],
default: undefined
},
refreshDeepWatch: {
type: Boolean,
default: false
},
/** 是否自动选中选项中的第一项,优先级低于其他选中配置 */
autoSelect: {
type: Boolean,
default: false
},
autoSelectFirstOne: {
type: Boolean,
default: false
},
/** 是否根据label值默认选中选项 */
defaultSelectByLabel: {
type: [String, Number, Array],
default: undefined
},
/** 是否根据value值默认选中选项 */
defaultSelectByValue: {
type: [String, Number, Array],
default: undefined
},
/** 可以通过'100px'设定固定宽度,也可以通过'100%'设置百分比宽度,默认值为240px,与el-select一致*/
width: {
type: String,
default: undefined
},
placeholder: {
type: String,
default: undefined
},
/** 需要过滤时设为true */
filterable: {
type: Boolean,
default: false
},
/** 需要启用远程功能时,设为true */
remote: {
type: Boolean,
default: false
},
/** 当用户输入完内容后,间隔多久后开始调用getDataFunction,单位毫秒 */
remoteDelay: {
type: Number,
default: 300
},
/** 请勿在这里传递,该属性仅仅起到阻断作用 */
remoteMethod: {
type: Function,
default: undefined
},
/** 远程搜索是否允许空的字符串 */
remoteSearhAllowEmptyString: {
type: Boolean,
default: false
},
/** 是否可清除 */
clearable: {
type: Boolean,
default: true
},
/** 是否多选 */
multiple: {
type: Boolean,
default: false
},
/** 是否不可更改 */
disabled: {
type: Boolean,
default: false
},
/** 当前选择的内容的全部数据 */
currentSelect: {
type: [Array, String, Object, Number, Boolean],
default: undefined
}
},
data() {
return {
loading: false,
myOptions: [],
remoteSearchKeyword: undefined,
myRemoteTask: null,
myOptionsDictionary: {},
disabledWatchEmit: false
}
},
computed: {
Data: {
get() {
return this.data
},
set(val) {
// console.info('update:data', val)
this.$emit('update:currentSelect', this.myOptionsDictionary[val])
this.$emit('update:data', val)
this.$emit('change', val)
this.disabledWatchEmit = true
setTimeout(() => {
this.disabledWatchEmit = false
}, 200)
// console.info(this.myOptionsDictionary)
// console.info('设置了', val)
}
},
Options: {
get() {
var temp = []
// 合并默认选项
if (typeof this.options === 'object') {
Object.assign(temp, this.options)
}
// 合并远程选项
Object.assign(temp, this.myOptions)
// 建立value和项对应的字典
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
this.myOptionsDictionary = {}
Object.keys(temp).forEach((key) => {
this.myOptionsDictionary[temp[key].value] = temp[key]
})
return temp
},
set(val) {
if (typeof val === 'object') {
this.myOptions = val
// 通过访get方法,建立字典,下一行不可删除
var test = this.Options
}
}
},
RefreshWhenCreated: {
get() {
return (this.refreshMode & 1) === 1
}
},
RefreshWhenMounted: {
get() {
return (this.refreshMode & 2) === 2
}
},
RefreshWhenFocus: {
get() {
// console.info('计算')
// console.info(this.refreshMode)
// console.info(this.refreshMode & 4)
return (this.refreshMode & 4) === 4
}
},
RefreshWhenRefreshFlagChange: {
get() {
return (this.refreshMode & 8) === 8
}
},
Filterable: {
get() {
// 当启用了远程搜索时,直接返回true,否则返回filterable
if (this.remote === true) {
return true
} else {
return this.filterable
}
}
},
WidthStyle: {
get() {
if (this.width === undefined) {
return 'display:inline-block;position:relative;'
} else {
return `width:${this.width};`
}
}
}
},
watch: {
refreshFlag: {
deep: true,
handler(newValue, oldValue) {
if (newValue === undefined) {
return
}
// 在调用之前,需要将loading重置,否则不会重新刷新方法
this.loading = false
this.refreshOptions()
},
immediate: false
},
Options: {
deep: true,
handler(newValue, oldValue) {
// 当选项更改时,要考虑绑定值是否与选项相契合,如果绑定值为空,则要考虑自动选中
if ([undefined, null, ''].includes(this.Data)) {
setTimeout(() => {
this.doAutoSelect(newValue)
}, 10)
} else {
this.fitCurrentValue(newValue)
}
}
},
Data: {
handler(value) {
if (this.disabledWatchEmit) return
this.$emit('change', value)
}
}
},
created() {
if (this.RefreshWhenCreated) {
this.refreshOptions()
}
},
mounted() {
if (this.RefreshWhenMounted) {
this.refreshOptions()
}
// console.info(this.$listeners)
},
methods: {
/** 远程搜索,防抖 */
remoteSearch(keyword) {
// console.info(keyword)
if ([undefined, null].includes(keyword)) {
return
}
if (typeof keyword !== 'string') {
return
}
const word = keyword.trim()
if (word === '' && !this.remoteSearhAllowEmptyString) {
return
}
// 取消掉之前的任务
if (this.myRemoteTask !== null) {
clearTimeout(this.myRemoteTask)
}
// 清空绑定值
// 重新建立新任务
this.myRemoteTask = setTimeout(() => {
this.refreshOptions(keyword)
this.myRemoteTask = null
}, this.remoteDelay)
},
/** 刷新选项 */
refreshOptions(keyword) {
if (!this.loading) {
// debugger
this.loading = true
var value = null
if (this.remote) {
value = JSON.parse(JSON.stringify(this.Data))
}
// //console.info(typeof (this.getDataFunction))
// //console.info(12)
if (
this.getDataFunction !== undefined &&
typeof this.getDataFunction === 'function'
) {
this.getDataFunction(this.appendOptions, value, keyword)
} else {
this.loading = false
}
}
},
/** 添加远程搜索的选项 */
appendOptions(newOptions) {
if (this.loading) {
// debugger
this.loading = false
this.Options = newOptions
}
},
/** 绑定值更改 */
handleChange(data) {
// console.info(this.myOptionsDictionary)
// console.info(data)
},
/** 被聚焦 */
handleFocus() {
if (this.RefreshWhenFocus) {
// console.info(11)
this.refreshOptions()
}
this.$emit('focus')
},
/** 多选项被移除 */
handleRemoveTag(value) {
this.$emit('removeTag', value)
},
/** 绑定值被清除 */
handleClear() {
// this.$emit('update:currentSelect', undefined)
this.$emit('clear')
},
/** 组件失去焦点 */
handleBlur(event) {
// console.info('失去焦点')
// console.info(event)
this.$emit('blur', event)
},
/** */
fitCurrentValue(newOptions) {
newOptions.forEach((opt) => {
if (opt.value === this.Data) {
this.Data = opt.value
}
})
},
/** 执行自动选择 */
doAutoSelect(newOptions) {
// 只有一个选项时,默认选中,和有多个选项时,默认选中第一个是相同的
// 如果指定了选择的标准(按照value值或者按照label值,优先级value大于label,),则按照标准进行选择
// 选择了后要触发值更改事件
// console.info('执行自动选择')
// const newOptions = this.Options
const valueList = []
const labelList = []
if (this.autoSelectFirstOne && newOptions.length >= 1) {
valueList.push(newOptions[0].value)
}
try {
// 考虑value选中(单选时优先级高于label)
// 考虑label选中
// 考虑第一项自动选中,优先级最低
// 遍历选项,根据条件找到所有的满足条件的项
newOptions.forEach((opt) => {
if (this.defaultSelectByValue !== undefined) {
if (
['string', 'number'].includes(typeof this.defaultSelectByValue)
) {
// 简单类型
if (this.defaultSelectByValue === opt.value) {
valueList.push(opt.value)
}
} else if (this.defaultSelectByValue instanceof Array) {
// 数组类型
if (this.defaultSelectByValue.includes(opt.value)) {
valueList.push(opt.value)
}
}
}
if (this.defaultSelectByLabel !== undefined) {
if (['string', 'number'].includes(typeof (this.defaultSelectByLabel))) {
// 简单类型
if (this.defaultSelectByLabel === opt.label) {
labelList.push(opt.value)
}
} else if (this.defaultSelectByLabel instanceof Array) {
// 数组类型
if (this.defaultSelectByLabel.includes(opt.label)) {
labelList.push(opt.value)
}
}
}
})
const a = new Set(labelList)
const b = new Set(valueList)
const multiSelect = Array.from(new Set([...a, ...b]))
const multiData = multiSelect.length >= 1 ? multiSelect : ''
const firstValue = newOptions.length === 1 ? [newOptions[0].value] : []
const singleSelect = [].concat(firstValue, labelList, valueList)
const singleData =
singleSelect.length >= 1 ? singleSelect[singleSelect.length - 1] : ''
const newData = this.multiple === true ? multiData : singleData
// 设置值
if (newData !== '') {
this.Data = newData
this.$refs.selectRef.$emit('change', newData)
this.$refs.selectRef.blur()
}
} catch (err) {
console.error(err)
}
},
clearOptions() {
this.Options = []
}
}
}
</script>
<style lang="scss" scoped>
</style>
使用方法:
定义js文件
import { EsEnum } from '@/components/utils'
export const LogsType = new EsEnum([
{
code: 'ESign',
value: 1,
label: '电子签名',
type: 'success'
},
{
code: 'User',
label: ‘用户操作,
type: 'primary'
}
])
定义vue文件
<script>
import EnumBase from '@/components/Template/EnumBase'
import { LogsType } from './LogsType.js'
export default {
extends: EnumBase,
created() {
this.enumData = LogsType
}
}
</script>
EsEnum方法的封装
// #region 名称转换
/**
* 函数将name转换为 camelCase, CamelCase, kebab-case 的形式
* @param name: 待格式化的名称
* @param pattern: 模式,若传入number类型,则camelCase为1,其余按顺序递增
*/
export function NameFormat(name, pattern) {
const patternRange = ['camelCase', 'CamelCase', 'kebab-case']
let selectedPattern = null
if (typeof pattern === 'number') {
let index = pattern < 1 ? 1 : pattern
index = index > patternRange.length ? patternRange.length : index
selectedPattern = patternRange[index - 1]
} else {
if (patternRange.indexOf(pattern) === -1) {
selectedPattern = patternRange[0]
} else {
selectedPattern = pattern
}
}
switch (selectedPattern) {
case 'camelCase':
return camelCase(name)
case 'CamelCase':
return CamelCase(name)
case 'kebab-case':
return kebabCase(name)
}
}
/**
* 将输入的字符串转换为kebab-case
* @param str 输入的字符串
* @returns 转换后的结果
*/
function kebabCase(str) {
return str
.replace(/([^A-Za-z0-9]+)/g, '-') // 任意非数字字母的连续字符 => -
.replace(/^([0-9-]+)([a-zA-Z])/g, '$2') // 去掉单词前的所有非字母字符
.replace(/([a-zA-z0-9])(-?)$/g, '$1') // 去掉单词后的所有非字母、非数字字符
.replace(/([a-z])([A-Z])/g, '$1-$2')// aA=>a-A
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')// AAAa=>AA-Aa
.replace(/([0-9])([a-zA-Z])/g, '$1-$2') // V9a=>V9-a
.toLowerCase()
}
/**
* 将输入的字符串转为CamelCase
* @param str 输入的字符串
* @returns 转换后的结果
*/
function CamelCase(str) {
return kebabCase(str)
.replace(/(.)/, '-$1')
.replace(/(-[a-z])/g, (str) => str[1].toUpperCase())
}
/**
* 将输入的字符串转为camelCase
* @param str 输入的字符串
* @returns 转换后的结果
*/
function camelCase(str) {
return CamelCase(str)
.replace(/(.)/, (str) => str.toLowerCase())
}
// const testData = ['goodMorning', 'GoodMorning', '-458---goodMorning....', 'good--Morning', 'goodMORNINGEveryday']
// for (const str of testData) {
// console.log(str)
// console.log(kebabCase(str))
// console.log(CamelCase(str))
// console.log(camelCase(str))
// console.log('')
// }
// #endregion
/**
* 该函数考虑expression和method(params)
* @param {*} expression 返回布尔值的表达式
* @param {*} valueDefault 默认值
* @param {Function} method 返回布尔值的函数
* @param {Object|Array} params 函数的入参
* @returns
*/
export function BoolValueHandle(expression, valueDefault, method, params) {
if (method === undefined) {
if (expression === undefined) {
return valueDefault
} else {
return expression
}
} else {
var temp = method(params)
if (temp !== undefined && typeof (temp) === 'boolean') {
return temp
} else {
console.warn('方法应该返回boolean类型的值')
if (expression === undefined) {
return valueDefault
} else {
return expression
}
}
}
}
/**
* 该函数将考虑valueOrFunction的类型是function还是其他。
* 为undefined,返回 valueDefault;
* 为function时,返回 function(params) ?? valueDefault
* 其他情况,返回 value
* @param {*} valueOrFunction 值或者函数
* @param {*} valueDefault 值或函数为undefined时的默认值
* @param {*} params 传递给函数的参数
* @returns
*/
export function ValueHandle(valueOrFunction, valueDefault, params) {
// console.info(valueOrFunction)
if (valueOrFunction === undefined) {
return valueDefault
}
if (typeof valueOrFunction !== 'function') {
const value = valueOrFunction
return value
}
// console.info(valueOrFunction)
const method = valueOrFunction
const methodReturns = method(params)
// console.info(methodReturns)
return methodReturns ?? valueDefault
}
/**
* 生成枚举型的Object
* @param {*} arr 枚举的设定值。例如[['red',0],['green',1]],最终可以通过.red .green获得对应值0 和 1
* @returns 枚举型
*/
export function MakeEnum(arr) {
const obj = {}
if (!Array.isArray(arr)) {
throw new Error('arr 不是 Array')
}
arr.forEach((element, index) => {
if (typeof element !== 'object' && typeof element !== 'string' && !Array.isArray(element)) {
throw new Error('arr的元素只允许是object array string 类型的')
}
let tempObject = {}
if (Array.isArray(element)) {
let [code, value] = element
if (code === undefined) {
throw new Error('arr的元素若为Array类型,至少需要有一个元素')
}
value = value ?? index
const temp = { code, value }
tempObject = Object.assign({}, temp)
} else if (typeof element === 'string') {
const code = element
const value = index
const temp = { code, value }
tempObject = Object.assign({}, temp)
} else {
let { code, value } = element
if (code === undefined) {
throw new Error('arr的元素若为Object类型,至少需要有code属性')
}
value = value ?? index
const temp = { code, value }
tempObject = Object.assign({}, temp)
}
// 编码若出现重复,后者覆盖前者
obj[tempObject.code] = tempObject.value
})
return Object.freeze(obj)
}
/**
* 传入[item0,item1,item2,item3,...],其中item至少有code和value两个属性
* 返回一个枚举类,提供code到value,value到item的映射,并可以通过迭代器获得
* [item0,item1,item2,item3,...]
*/
export class EsEnum {
constructor(arr, startValue = 1) {
if (!Array.isArray(arr)) {
throw new Error('arr 不是 Array')
}
let nextValue = startValue
// 建立value与index的映射关系
this.indexObject = {}
this.codeObject = {}
// 建立code到value的映射关系的同时,生成共迭代器访问的Array
this.data = arr.map((item, index) => {
const { code, value } = item
// code 不允许为undefined
if ([undefined, null, ''].includes(code)) {
throw new Error('arr的元素的code 不允许为 空或null或undefined')
}
const useValue = value ?? nextValue
nextValue = (value ?? nextValue) + 1
this[code] = useValue
this.indexObject[useValue] = index
this.codeObject[item.code] = index
// 使用浅拷贝
return Object.assign({}, item, { value: useValue })
})
}
/**
* 默认迭代器
*/
* [Symbol.iterator]() {
for (const item of this.data) {
yield item
}
}
/**
* 返回array
*/
toArray() {
return [...this.data]
}
/**
* 使用value查找与value匹配的枚举项配的结果
* @param {*} value 待匹配的value值
* @returns 返回item或者undefined
*/
getItemByValue(value) {
const index = this.indexObject[value]
return this.data[index]
}
/**
* 使用code查找与code匹配的枚举项配的结果
* @param {*} code 待匹配的code值
* @returns 返回item或者undefined
*/
getItemByCode(code) {
const index = this.codeObject[code]
return this.data[index]
}
}
/**
* 该函数处理window.addEventListener事件传递的event,从event中提取按键的字符串(key)和ASC2码(keyCode)
* @param {*} event addEventListener
* @returns {key ,keyCode}
*/
export function KeyboardListener(event) {
const e = event || window.event || arguments.callee.caller.arguments[0]
if (!e) return
const { key, keyCode } = e
// console.log(key, keyCode)
return { key, keyCode }
}
/**
* 获取当前系统颜色
* @param {String} type 类型
* @returns 颜色代码
*/
export function GetColorByType(type) {
switch (type) {
case '':
case 'primary':
return '#409EFF'
case 'success':
return '#67C23A'
case 'danger':
return '#F56C6C'
case 'warning':
return '#E6A23C'
case 'info':
default:
return '#909399'
}
}
下拉框使用
<LogsType v-model="xxx"/> //引用.vue文件。此时会提供一个下拉框(数据展示的是label值),选中数据后xxx获取的是value值,也可以直接用js通过xxx=LogsType.[code]来赋值
tag使用
<LogsType v-model="xxx" :allow-select="false" />
直接使用
<LogsType v-model="xxx" :allow-select="false" :use-tag="false" /> //增加use-tag属性
$t和i18n是多语言处理,可以看前面的博客
原文地址:https://blog.csdn.net/qq_51384850/article/details/142659495
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!