图标选择控件

This commit is contained in:
gary.fu
2024-01-02 14:07:17 +08:00
parent 6cd17a6702
commit 4a68e5fbc5
19 changed files with 343 additions and 77 deletions

View File

@@ -57,6 +57,21 @@ html, body, #app, .index-container {
.text-right {
text-align: right;
}
.icon-dialog .el-dialog__body {
padding: 10px;
}
.icon-list::-webkit-scrollbar {
z-index: 10;
width: 6px;
}
.icon-list::-webkit-scrollbar-thumb {
border-radius:5px;
width:6px;
background:var(--el-text-color-disabled)
}
/**
* slide-fade动画
*/

View File

@@ -16,6 +16,7 @@ import ControlChild from '@/components/common-form-control/control-child.vue'
* @property {boolean} required 是否必填,后面解析成为rules的一部分
* @property {string|RegExp} pattern 正则表达式验证解析成为rules的一部分
* @property {string} patternMsg 正则表达式验证消息
* @property {boolean} common 自定义组件
* @property {boolean} disabled 禁用
* @property {boolean} readonly 只读
* @property {string} placeholder 占位提示符
@@ -41,6 +42,9 @@ const props = defineProps({
})
const inputType = computed(() => {
if (props.option.common) {
return `common-${props.option.type}`
}
return `el-${props.option.type || 'input'}`
})

View File

@@ -0,0 +1,167 @@
<script setup>
import { computed, ref } from 'vue'
import { filterIconsByKeywords } from '@/services/icon/IconService'
import { useVModel } from '@vueuse/core'
const props = defineProps({
modelValue: {
type: String,
default: ''
},
dialogAttrs: {
type: Object,
default () {
return {}
}
},
colSize: {
type: Number,
default: 6
},
dialogHeight: {
type: String,
default: '400px'
},
dialogWidth: {
type: String,
default: '600px'
},
disabled: {
type: Boolean,
default: false
},
readonly: {
type: Boolean,
default: false
},
placeholder: {
type: String,
default: ''
}
})
const iconSelectVisible = ref(false)
const keyWords = ref('')
const filterIcons = computed(() => {
if (iconSelectVisible.value) {
return filterIconsByKeywords(keyWords.value, props.colSize)
}
return []
})
const emit = defineEmits(['update:modelValue'])
const vModel = useVModel(props, 'modelValue', emit)
const selectIcon = icon => {
iconSelectVisible.value = false
vModel.value = icon
}
</script>
<template>
<label class="el-radio">
<common-icon
v-if="modelValue"
:icon="modelValue"
class="el-radio__input"
/>
<span
v-if="modelValue"
class="el-radio__label"
>{{ modelValue }}</span>
<el-button
class="icon-select-button"
type="primary"
:disabled="disabled||readonly"
size="small"
@click="iconSelectVisible = true"
>
{{ $t('common.label.select') }}
</el-button>
</label>
<el-button
v-if="vModel"
type="danger"
:disabled="disabled||readonly"
size="small"
@click="vModel = ''"
>
{{ $t('common.label.clear') }}
</el-button>
<el-dialog
v-model="iconSelectVisible"
:width="dialogWidth"
v-bind="dialogAttrs"
draggable
class="icon-dialog"
:title="$t('common.msg.pleaseSelectIcon')"
>
<el-container
style="overflow: auto;"
:style="{ height: dialogHeight }"
class="icon-container"
>
<el-header height="40px">
<el-form label-width="120px">
<el-form-item :label="$t('common.label.keywords')">
<el-input
v-model="keyWords"
:placeholder="$t('common.msg.inputKeywords')"
/>
</el-form-item>
</el-form>
</el-header>
<el-main>
<RecycleScroller
v-slot="{ item }"
class="scroller icon-list"
:items="filterIcons"
:item-size="80"
key-field="id"
>
<el-row>
<el-col
v-for="icon in item.icons"
:key="icon"
:span="24/colSize"
class="text-center"
>
<a
class="el-button el-button--large is-text"
style="height:80px;"
@click="selectIcon(icon)"
>
<div>
<common-icon
size="20"
:icon="icon"
/>
</div>
</a>
</el-col>
</el-row>
</RecycleScroller>
</el-main>
</el-container>
</el-dialog>
</template>
<style scoped>
.scroller {
height: 100%;
}
.icon-container {
overflow: auto;
}
.icon-container .el-input {
width: 80%;
}
.icon-select-button {
margin-left: 10px;
}
.el-radio {
margin-right: 10px;
}
</style>

View File

@@ -27,7 +27,7 @@ const props = defineProps({
required: true
},
/**
* @type {'large'|'small'|'default'}
* @type {''|'large'|'small'|'default'}
*/
buttonSize: {
type: String,
@@ -75,8 +75,9 @@ const props = defineProps({
<template
#default="scope"
>
<template v-for="(button, index) in column.buttons">
<el-button
v-for="(button, index) in column.buttons"
v-if="!button.buttonIf||button.buttonIf(scope.row, scope)"
:key="index"
:type="button.type"
:icon="button.icon"
@@ -88,6 +89,7 @@ const props = defineProps({
>
{{ button.label || $t(button.labelKey) }}
</el-button>
</template>
<slot
name="default"
v-bind="scope"

View File

@@ -92,10 +92,7 @@ const onDropdownVisibleChange = (visible, tab) => {
</template>
<style scoped>
.common-tabs > .el-tabs__content {
padding: 32px;
color: #6b778c;
font-size: 32px;
font-weight: 600;
.common-tabs .el-tabs__header {
margin: 0;
}
</style>

View File

@@ -1,4 +1,5 @@
import CommonIcon from '@/components/common-icon/index.vue'
import CommonIconSelect from '@/components/common-icon-select/index.vue'
import CommonForm from '@/components/common-form/index.vue'
import CommonFormControl from '@/components/common-form-control/index.vue'
import CommonMenu from '@/components/common-menu/index.vue'
@@ -15,6 +16,7 @@ export default {
*/
install (Vue) {
Vue.component('CommonIcon', CommonIcon)
Vue.component('CommonIconSelect', CommonIconSelect)
Vue.component('CommonForm', CommonForm)
Vue.component('CommonFormControl', CommonFormControl)
Vue.component('CommonMenu', CommonMenu)

View File

@@ -33,10 +33,15 @@ common.label.delete = '删除'
common.label.search = '搜索'
common.label.find = '查找'
common.label.back = '返回'
common.label.select = '选择'
common.label.clear = '清空'
common.label.tabMode = '多标签模式'
common.label.cachedTabMode = '缓存标签页'
common.label.showTabIcon = '标签图标'
common.label.keywords = '关键字'
//* =======================msg=====================//
common.msg.nonNull = '{0}不能为空'
common.msg.patternInvalid = '{0}格式校验不通过'
common.msg.pleaseSelectIcon = '请选择图标'
common.msg.inputKeywords = '输入关键字搜索'

View File

@@ -33,10 +33,15 @@ common.label.delete = 'Delete'
common.label.search = 'Search'
common.label.find = 'Find'
common.label.back = 'Back'
common.label.select = 'Select'
common.label.clear = 'Clear'
common.label.tabMode = 'Tabs Mode'
common.label.cachedTabMode = 'Cache Tabs'
common.label.showTabIcon = 'Tab Icon'
common.label.keywords = 'Keywords'
//* =======================msg=====================//
common.msg.nonNull = '{0} is required.'
common.msg.patternInvalid = '{0} pattern check failed.'
common.msg.pleaseSelectIcon = 'Please select icon'
common.msg.inputKeywords = 'Input keywords to search'

View File

@@ -1,4 +1,4 @@
import { createI18n, useI18n } from 'vue-i18n'
import { createI18n } from 'vue-i18n'
import { ref } from 'vue'
import messagesCn from './messages_cn'
import messagesEn from './messages_en'

View File

@@ -10,6 +10,7 @@ menu.label.toolsManagement = '工具管理'
menu.label.toolsIcons = '图标工具'
menu.label.toolsForms = '表单工具'
menu.label.toolsTables = '表格工具'
menu.label.toolsTests = '测试页面'
menu.label.errorPage = '错误页面'
menu.label.errorPage404 = '找不到页面'
menu.label.errorPage403 = '没有权限'

View File

@@ -10,6 +10,7 @@ menu.label.toolsManagement = 'Tools'
menu.label.toolsIcons = 'Icons'
menu.label.toolsForms = 'Forms'
menu.label.toolsTables = 'Tables'
menu.label.toolsTests = 'Test Page'
menu.label.errorPage = 'Error Page'
menu.label.errorPage404 = 'Not Found'
menu.label.errorPage403 = 'Access Denied'

View File

@@ -6,6 +6,10 @@ export default [{
path: '/forms',
name: 'forms',
component: () => import('@/views/tools/Forms.vue')
}, {
path: '/tests',
name: 'tests',
component: () => import('@/views/tools/TestPage.vue')
}, {
path: '/tables',
name: 'tables',

View File

@@ -117,6 +117,11 @@ export const useBusinessMenus = () => {
index: '/tables',
icon: 'Grid',
labelKey: 'menu.label.toolsTables'
},
{
index: '/tests',
icon: 'TipsAndUpdatesOutlined',
labelKey: 'menu.label.toolsTests'
}
]
}])

View File

@@ -0,0 +1,20 @@
import { INSTALL_ICONS } from '@/icons'
import chunk from 'lodash/chunk'
/**
* @param keywords {string}
* @param colSize {number}
* @returns {{id: number, icons: *}[]}
*/
export const filterIconsByKeywords = (keywords, colSize) => {
let installIcons = INSTALL_ICONS
if (keywords) {
installIcons = installIcons.filter(icon => icon.toLowerCase().includes(keywords.toLowerCase()))
}
return chunk(installIcons, colSize).map((arr, idx) => {
return {
id: idx,
icons: arr
}
})
}

View File

@@ -26,7 +26,10 @@ const showLeftMenu = computed(() => {
<el-header>
<top-nav />
</el-header>
<el-header v-if="tabsViewStore.isTabMode">
<el-header
v-if="tabsViewStore.isTabMode"
class="tabs-header"
>
<common-tabs-view />
</el-header>
<el-main>
@@ -51,3 +54,9 @@ const showLeftMenu = computed(() => {
</el-container>
</el-container>
</template>
<style scoped>
.tabs-header {
margin-top: 5px;
height: 40px
}
</style>

View File

@@ -95,6 +95,13 @@ const formOptions = [{
value: 'unknown'
}
]
}, {
label: '图标测试',
prop: 'icon',
value: '',
type: 'icon-select',
required: true,
common: true
}, {
label: '地址',
prop: 'address',

View File

@@ -1,23 +1,14 @@
<script setup>
import { INSTALL_ICONS } from '@/icons'
import chunk from 'lodash/chunk'
import { computed, ref } from 'vue'
import { useClipboard } from '@vueuse/core'
import { ElMessage } from 'element-plus'
import { filterIconsByKeywords } from '@/services/icon/IconService'
const colSize = ref(8)
const keyWords = ref('')
const filterIcons = computed(() => {
let installIcons = INSTALL_ICONS
if (keyWords.value) {
installIcons = installIcons.filter(icon => icon.toLowerCase().includes(keyWords.value.toLowerCase()))
}
return chunk(installIcons, colSize.value).map((arr, idx) => {
return {
id: idx,
icons: arr
}
})
return filterIconsByKeywords(keyWords.value, colSize.value)
})
const copyIcon = (icon) => {
@@ -39,23 +30,23 @@ const copyIcon = (icon) => {
</script>
<template>
<div>
<el-row>
<el-col>
<el-container
class="icon-container"
>
<el-header height="40px">
<el-form label-width="120px">
<el-form-item label="图标名字">
<el-form-item :label="$t('common.label.keywords')">
<el-input
v-model="keyWords"
:placeholder="`输入关键字搜索图标`"
:placeholder="$t('common.msg.inputKeywords')"
/>
</el-form-item>
</el-form>
</el-col>
</el-row>
</el-header>
<el-main>
<RecycleScroller
v-slot="{ item }"
page-mode
class="scroller"
class="scroller icon-list"
:items="filterIcons"
:item-size="80"
key-field="id"
@@ -82,11 +73,12 @@ const copyIcon = (icon) => {
</el-col>
</el-row>
</RecycleScroller>
</div>
</el-main>
</el-container>
</template>
<style scoped>
.scroller {
.scroller, .icon-container {
height: 100%;
}
</style>

View File

@@ -3,18 +3,21 @@ import { ref } from 'vue'
const tableData = [
{
id: '1',
birthday: '2016-05-03',
userName: 'Tom',
gender: 'male',
address: 'No. 189, Grove St, Los Angeles'
},
{
id: '2',
birthday: '2016-05-02',
userName: 'Tom',
gender: 'female',
address: 'No. 189, Grove St, Los Angeles'
},
{
id: '3',
birthday: '2016-05-04',
userName: 'Tom',
gender: 'male',
@@ -54,12 +57,18 @@ const buttons = ref([{
type: 'primary',
click: item => {
console.info('编辑=============', item)
},
buttonIf (item) {
return !!item.id
}
}, {
labelKey: 'common.label.delete',
type: 'danger',
click: item => {
console.info('删除=============', item)
},
buttonIf (item) {
return !!item.id
}
}, {
label: '其他操作'

View File

@@ -0,0 +1,21 @@
<script setup>
import { ref } from 'vue'
const modelIcon = ref('Apple')
</script>
<template>
<el-container>
<el-main>
<el-form>
<el-form-item label="图标选择">
<common-icon-select v-model="modelIcon" />
</el-form-item>
</el-form>
</el-main>
</el-container>
</template>
<style scoped>
</style>