| | |
| | | "@unocss/preset-uno": "^0.50.8", |
| | | "@vitejs/plugin-vue": "5.0.4", |
| | | "@vue/compiler-sfc": "3.2.45", |
| | | "@purge-icons/generated": "^0.9.0", |
| | | "autoprefixer": "10.4.14", |
| | | "eslint": "8.36.0", |
| | | "eslint-config-prettier": "8.8.0", |
| New file |
| | |
| | | @import './variables.scss'; |
| | | // 导出变量 |
| | | :export { |
| | | namespace: $namespace; |
| | | elNamespace: $elNamespace; |
| | | } |
| | |
| | | @import './ruoyi.scss'; |
| | | @import 'animate.css'; |
| | | @import 'element-plus/dist/index.css'; |
| | | @import "./variables.scss"; |
| | | |
| | | body { |
| | | height: 100%; |
| New file |
| | |
| | | // 命名空间 |
| | | $namespace: v; |
| | | // el命名空间 |
| | | $elNamespace: el; |
| New file |
| | |
| | | import Dialog from './src/Dialog.vue' |
| | | |
| | | export { Dialog } |
| New file |
| | |
| | | <script lang="ts" setup> |
| | | import { propTypes } from '@/utils/propTypes' |
| | | import { isNumber } from '@/utils/is' |
| | | defineOptions({ name: 'Dialog' }) |
| | | |
| | | const slots = useSlots() |
| | | |
| | | const props = defineProps({ |
| | | modelValue: propTypes.bool.def(false), |
| | | title: propTypes.string.def('Dialog'), |
| | | fullscreen: propTypes.bool.def(true), |
| | | width: propTypes.oneOfType([String, Number]).def('40%'), |
| | | scroll: propTypes.bool.def(false), // 是否开启滚动条。如果是的话,按照 maxHeight 设置最大高度 |
| | | maxHeight: propTypes.oneOfType([String, Number]).def('400px') |
| | | }) |
| | | |
| | | const getBindValue = computed(() => { |
| | | const delArr: string[] = ['fullscreen', 'title', 'maxHeight', 'appendToBody'] |
| | | const attrs = useAttrs() |
| | | const obj = { ...attrs, ...props } |
| | | for (const key in obj) { |
| | | if (delArr.indexOf(key) !== -1) { |
| | | delete obj[key] |
| | | } |
| | | } |
| | | return obj |
| | | }) |
| | | |
| | | const isFullscreen = ref(false) |
| | | |
| | | const toggleFull = () => { |
| | | isFullscreen.value = !unref(isFullscreen) |
| | | } |
| | | |
| | | const dialogHeight = ref(isNumber(props.maxHeight) ? `${props.maxHeight}px` : props.maxHeight) |
| | | |
| | | watch( |
| | | () => isFullscreen.value, |
| | | async (val: boolean) => { |
| | | await nextTick() |
| | | if (val) { |
| | | const windowHeight = document.documentElement.offsetHeight |
| | | dialogHeight.value = `${windowHeight - 55 - 60 - (slots.footer ? 63 : 0)}px` |
| | | } else { |
| | | dialogHeight.value = isNumber(props.maxHeight) ? `${props.maxHeight}px` : props.maxHeight |
| | | } |
| | | }, |
| | | { |
| | | immediate: true |
| | | } |
| | | ) |
| | | |
| | | const dialogStyle = computed(() => { |
| | | return { |
| | | height: unref(dialogHeight) |
| | | } |
| | | }) |
| | | </script> |
| | | |
| | | <template> |
| | | <ElDialog |
| | | v-bind="getBindValue" |
| | | :close-on-click-modal="true" |
| | | :fullscreen="isFullscreen" |
| | | :width="width" |
| | | destroy-on-close |
| | | lock-scroll |
| | | draggable |
| | | class="com-dialog" |
| | | :show-close="false" |
| | | > |
| | | <template #header="{ close }"> |
| | | <div class="relative h-54px flex items-center justify-between pl-15px pr-15px"> |
| | | <slot name="title"> |
| | | {{ title }} |
| | | </slot> |
| | | <div |
| | | class="absolute right-15px top-[50%] h-54px flex translate-y-[-50%] items-center justify-between" |
| | | > |
| | | <Icon |
| | | v-if="fullscreen" |
| | | class="is-hover mr-10px cursor-pointer" |
| | | :icon="isFullscreen ? 'radix-icons:exit-full-screen' : 'radix-icons:enter-full-screen'" |
| | | color="var(--el-color-info)" |
| | | hover-color="var(--el-color-primary)" |
| | | @click="toggleFull" |
| | | /> |
| | | <Icon |
| | | class="is-hover cursor-pointer" |
| | | icon="ep:close" |
| | | hover-color="var(--el-color-primary)" |
| | | color="var(--el-color-info)" |
| | | @click="close" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <ElScrollbar v-if="scroll" :style="dialogStyle"> |
| | | <slot></slot> |
| | | </ElScrollbar> |
| | | <slot v-else></slot> |
| | | <template v-if="slots.footer" #footer> |
| | | <slot name="footer"></slot> |
| | | </template> |
| | | </ElDialog> |
| | | </template> |
| | | |
| | | <style lang="scss"> |
| | | .com-dialog { |
| | | .el-overlay-dialog { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | } |
| | | |
| | | .el-dialog { |
| | | margin: 0 !important; |
| | | |
| | | &__header { |
| | | height: 54px; |
| | | padding: 0; |
| | | margin-right: 0 !important; |
| | | border-bottom: 1px solid var(--el-border-color); |
| | | } |
| | | |
| | | &__body { |
| | | padding: 15px !important; |
| | | } |
| | | |
| | | &__footer { |
| | | border-top: 1px solid var(--el-border-color); |
| | | } |
| | | |
| | | &__headerbtn { |
| | | top: 0; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | import Icon from './src/Icon.vue' |
| | | import IconSelect from './src/IconSelect.vue' |
| | | |
| | | export { Icon, IconSelect } |
| New file |
| | |
| | | <script lang="ts" setup> |
| | | import { propTypes } from '@/utils/propTypes' |
| | | import Iconify from '@purge-icons/generated' |
| | | import { useDesign } from '@/hooks/web/useDesign' |
| | | |
| | | defineOptions({ name: 'Icon' }) |
| | | |
| | | const { getPrefixCls } = useDesign() |
| | | |
| | | const prefixCls = getPrefixCls('icon') |
| | | |
| | | const props = defineProps({ |
| | | // icon name |
| | | icon: propTypes.string, |
| | | // icon color |
| | | color: propTypes.string, |
| | | // icon size |
| | | size: propTypes.number.def(16), |
| | | // icon svg class |
| | | svgClass: propTypes.string.def('') |
| | | }) |
| | | |
| | | const elRef = ref<ElRef>(null) |
| | | |
| | | const isLocal = computed(() => props.icon.startsWith('svg-icon:')) |
| | | |
| | | const symbolId = computed(() => { |
| | | return unref(isLocal) ? `#icon-${props.icon.split('svg-icon:')[1]}` : props.icon |
| | | }) |
| | | |
| | | const getIconifyStyle = computed(() => { |
| | | const { color, size } = props |
| | | return { |
| | | fontSize: `${size}px`, |
| | | height: '1em', |
| | | color |
| | | } |
| | | }) |
| | | |
| | | const getSvgClass = computed(() => { |
| | | const { svgClass } = props |
| | | return `iconify ${svgClass}` |
| | | }) |
| | | |
| | | const updateIcon = async (icon: string) => { |
| | | if (unref(isLocal)) return |
| | | |
| | | const el = unref(elRef) |
| | | if (!el) return |
| | | |
| | | await nextTick() |
| | | |
| | | if (!icon) return |
| | | |
| | | const svg = Iconify.renderSVG(icon, {}) |
| | | if (svg) { |
| | | el.textContent = '' |
| | | el.appendChild(svg) |
| | | } else { |
| | | const span = document.createElement('span') |
| | | span.className = 'iconify' |
| | | span.dataset.icon = icon |
| | | el.textContent = '' |
| | | el.appendChild(span) |
| | | } |
| | | } |
| | | |
| | | watch( |
| | | () => props.icon, |
| | | (icon: string) => { |
| | | updateIcon(icon) |
| | | } |
| | | ) |
| | | </script> |
| | | |
| | | <template> |
| | | <ElIcon :class="prefixCls" :color="color" :size="size"> |
| | | <svg v-if="isLocal" :class="getSvgClass" aria-hidden="true"> |
| | | <use :xlink:href="symbolId" /> |
| | | </svg> |
| | | |
| | | <span v-else ref="elRef" :class="$attrs.class" :style="getIconifyStyle"> |
| | | <span :class="getSvgClass" :data-icon="symbolId"></span> |
| | | </span> |
| | | </ElIcon> |
| | | </template> |
| New file |
| | |
| | | <script lang="ts" setup> |
| | | import { CSSProperties } from 'vue' |
| | | import { cloneDeep } from 'lodash-es' |
| | | import { IconJson } from '@/components/Icon/src/data' |
| | | |
| | | defineOptions({ name: 'IconSelect' }) |
| | | |
| | | type ParameterCSSProperties = (item?: string) => CSSProperties | undefined |
| | | |
| | | const props = defineProps({ |
| | | modelValue: { |
| | | require: false, |
| | | type: String |
| | | } |
| | | }) |
| | | const emit = defineEmits<{ (e: 'update:modelValue', v: string) }>() |
| | | |
| | | const visible = ref(false) |
| | | const inputValue = toRef(props, 'modelValue') |
| | | const iconList = ref(IconJson) |
| | | const icon = ref('add-location') |
| | | const currentActiveType = ref('ep:') |
| | | // 深拷贝图标数据,前端做搜索 |
| | | const copyIconList = cloneDeep(iconList.value) |
| | | |
| | | const pageSize = ref(96) |
| | | const currentPage = ref(1) |
| | | |
| | | // 搜索条件 |
| | | const filterValue = ref('') |
| | | |
| | | const tabsList = [ |
| | | { |
| | | label: 'Element Plus', |
| | | name: 'ep:' |
| | | }, |
| | | { |
| | | label: 'Font Awesome 4', |
| | | name: 'fa:' |
| | | }, |
| | | { |
| | | label: 'Font Awesome 5 Solid', |
| | | name: 'fa-solid:' |
| | | } |
| | | ] |
| | | |
| | | const pageList = computed(() => { |
| | | if (currentPage.value === 1) { |
| | | return copyIconList[currentActiveType.value] |
| | | ?.filter((v) => v.includes(filterValue.value)) |
| | | .slice(currentPage.value - 1, pageSize.value) |
| | | } else { |
| | | return copyIconList[currentActiveType.value] |
| | | ?.filter((v) => v.includes(filterValue.value)) |
| | | .slice( |
| | | pageSize.value * (currentPage.value - 1), |
| | | pageSize.value * (currentPage.value - 1) + pageSize.value |
| | | ) |
| | | } |
| | | }) |
| | | const iconCount = computed(() => { |
| | | return copyIconList[currentActiveType.value] == undefined |
| | | ? 0 |
| | | : copyIconList[currentActiveType.value].length |
| | | }) |
| | | |
| | | const iconItemStyle = computed((): ParameterCSSProperties => { |
| | | return (item) => { |
| | | if (inputValue.value === currentActiveType.value + item) { |
| | | return { |
| | | borderColor: 'var(--el-color-primary)', |
| | | color: 'var(--el-color-primary)' |
| | | } |
| | | } |
| | | } |
| | | }) |
| | | |
| | | function handleClick({ props }) { |
| | | currentPage.value = 1 |
| | | currentActiveType.value = props.name |
| | | emit('update:modelValue', currentActiveType.value + iconList.value[currentActiveType.value][0]) |
| | | icon.value = iconList.value[currentActiveType.value][0] |
| | | } |
| | | |
| | | function onChangeIcon(item) { |
| | | icon.value = item |
| | | emit('update:modelValue', currentActiveType.value + item) |
| | | visible.value = false |
| | | } |
| | | |
| | | function onCurrentChange(page) { |
| | | currentPage.value = page |
| | | } |
| | | |
| | | watch( |
| | | () => { |
| | | return props.modelValue |
| | | }, |
| | | () => { |
| | | if (props.modelValue && props.modelValue.indexOf(':') >= 0) { |
| | | currentActiveType.value = props.modelValue.substring(0, props.modelValue.indexOf(':') + 1) |
| | | icon.value = props.modelValue.substring(props.modelValue.indexOf(':') + 1) |
| | | } |
| | | } |
| | | ) |
| | | watch( |
| | | () => { |
| | | return filterValue.value |
| | | }, |
| | | () => { |
| | | currentPage.value = 1 |
| | | } |
| | | ) |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="selector"> |
| | | <ElInput v-model="inputValue" @click="visible = !visible"> |
| | | <template #append> |
| | | <ElPopover |
| | | :popper-options="{ |
| | | placement: 'auto' |
| | | }" |
| | | :visible="visible" |
| | | :width="350" |
| | | popper-class="pure-popper" |
| | | trigger="click" |
| | | > |
| | | <template #reference> |
| | | <div |
| | | class="h-32px w-40px flex cursor-pointer items-center justify-center" |
| | | @click="visible = !visible" |
| | | > |
| | | <Icon :icon="currentActiveType + icon" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <ElInput v-model="filterValue" class="p-2" clearable placeholder="搜索图标" /> |
| | | <ElDivider border-style="dashed" /> |
| | | |
| | | <ElTabs v-model="currentActiveType" @tab-click="handleClick"> |
| | | <ElTabPane |
| | | v-for="(pane, index) in tabsList" |
| | | :key="index" |
| | | :label="pane.label" |
| | | :name="pane.name" |
| | | > |
| | | <ElDivider border-style="dashed" class="tab-divider" /> |
| | | <ElScrollbar height="220px"> |
| | | <ul class="ml-2 flex flex-wrap px-2"> |
| | | <li |
| | | v-for="(item, key) in pageList" |
| | | :key="key" |
| | | :style="iconItemStyle(item)" |
| | | :title="item" |
| | | class="icon-item mr-2 mt-1 w-1/10 flex cursor-pointer items-center justify-center border border-solid p-2" |
| | | @click="onChangeIcon(item)" |
| | | > |
| | | <Icon :icon="currentActiveType + item" /> |
| | | </li> |
| | | </ul> |
| | | </ElScrollbar> |
| | | </ElTabPane> |
| | | </ElTabs> |
| | | <ElDivider border-style="dashed" /> |
| | | |
| | | <ElPagination |
| | | :current-page="currentPage" |
| | | :page-size="pageSize" |
| | | :total="iconCount" |
| | | background |
| | | class="h-10 flex items-center justify-center" |
| | | layout="prev, pager, next" |
| | | small |
| | | @current-change="onCurrentChange" |
| | | /> |
| | | </ElPopover> |
| | | </template> |
| | | </ElInput> |
| | | </div> |
| | | </template> |
| | | |
| | | <style lang="scss" scoped> |
| | | .el-divider--horizontal { |
| | | margin: 1px auto !important; |
| | | } |
| | | |
| | | .tab-divider.el-divider--horizontal { |
| | | margin: 0 !important; |
| | | } |
| | | |
| | | .icon-item { |
| | | &:hover { |
| | | color: var(--el-color-primary); |
| | | border-color: var(--el-color-primary); |
| | | transform: scaleX(1.05); |
| | | transition: all 0.4s; |
| | | } |
| | | } |
| | | |
| | | :deep(.el-tabs__nav-next) { |
| | | font-size: 15px; |
| | | line-height: 32px; |
| | | box-shadow: -5px 0 5px -6px #ccc; |
| | | } |
| | | |
| | | :deep(.el-tabs__nav-prev) { |
| | | font-size: 15px; |
| | | line-height: 32px; |
| | | box-shadow: 5px 0 5px -6px #ccc; |
| | | } |
| | | |
| | | :deep(.el-input-group__append) { |
| | | padding: 0; |
| | | } |
| | | |
| | | :deep(.el-tabs__item) { |
| | | height: 30px; |
| | | font-size: 12px; |
| | | font-weight: normal; |
| | | line-height: 30px; |
| | | } |
| | | |
| | | :deep(.el-tabs__header), |
| | | :deep(.el-tabs__nav-wrap) { |
| | | position: static; |
| | | margin: 0; |
| | | } |
| | | </style> |
| New file |
| | |
| | | export const IconJson = { |
| | | 'ep:': [ |
| | | 'add-location', |
| | | 'aim', |
| | | 'alarm-clock', |
| | | 'apple', |
| | | 'arrow-down', |
| | | 'arrow-down-bold', |
| | | 'arrow-left', |
| | | 'arrow-left-bold', |
| | | 'arrow-right', |
| | | 'arrow-right-bold', |
| | | 'arrow-up', |
| | | 'arrow-up-bold', |
| | | 'avatar', |
| | | 'back', |
| | | 'baseball', |
| | | 'basketball', |
| | | 'bell', |
| | | 'bell-filled', |
| | | 'bicycle', |
| | | 'bottom', |
| | | 'bottom-left', |
| | | 'bottom-right', |
| | | 'bowl', |
| | | 'box', |
| | | 'briefcase', |
| | | 'brush', |
| | | 'brush-filled', |
| | | 'burger', |
| | | 'calendar', |
| | | 'camera', |
| | | 'camera-filled', |
| | | 'caret-bottom', |
| | | 'caret-left', |
| | | 'caret-right', |
| | | 'caret-top', |
| | | 'cellphone', |
| | | 'chat-dot-round', |
| | | 'chat-dot-square', |
| | | 'chat-line-round', |
| | | 'chat-line-square', |
| | | 'chat-round', |
| | | 'chat-square', |
| | | 'check', |
| | | 'checked', |
| | | 'cherry', |
| | | 'chicken', |
| | | 'circle-check', |
| | | 'circle-check-filled', |
| | | 'circle-close', |
| | | 'circle-close-filled', |
| | | 'circle-plus', |
| | | 'circle-plus-filled', |
| | | 'clock', |
| | | 'close', |
| | | 'close-bold', |
| | | 'cloudy', |
| | | 'coffee', |
| | | 'coffee-cup', |
| | | 'coin', |
| | | 'cold-drink', |
| | | 'collection', |
| | | 'collection-tag', |
| | | 'comment', |
| | | 'compass', |
| | | 'connection', |
| | | 'coordinate', |
| | | 'copy-document', |
| | | 'cpu', |
| | | 'credit-card', |
| | | 'crop', |
| | | 'd-arrow-left', |
| | | 'd-arrow-right', |
| | | 'd-caret', |
| | | 'data-analysis', |
| | | 'data-board', |
| | | 'data-line', |
| | | 'delete', |
| | | 'delete-filled', |
| | | 'delete-location', |
| | | 'dessert', |
| | | 'discount', |
| | | 'dish', |
| | | 'dish-dot', |
| | | 'document', |
| | | 'document-add', |
| | | 'document-checked', |
| | | 'document-copy', |
| | | 'document-delete', |
| | | 'document-remove', |
| | | 'download', |
| | | 'drizzling', |
| | | 'edit', |
| | | 'edit-pen', |
| | | 'eleme', |
| | | 'eleme-filled', |
| | | 'expand', |
| | | 'failed', |
| | | 'female', |
| | | 'files', |
| | | 'film', |
| | | 'filter', |
| | | 'finished', |
| | | 'first-aid-kit', |
| | | 'flag', |
| | | 'fold', |
| | | 'folder', |
| | | 'folder-add', |
| | | 'folder-checked', |
| | | 'folder-delete', |
| | | 'folder-opened', |
| | | 'folder-remove', |
| | | 'food', |
| | | 'football', |
| | | 'fork-spoon', |
| | | 'fries', |
| | | 'full-screen', |
| | | 'goblet', |
| | | 'goblet-full', |
| | | 'goblet-square', |
| | | 'goblet-square-full', |
| | | 'goods', |
| | | 'goods-filled', |
| | | 'grape', |
| | | 'grid', |
| | | 'guide', |
| | | 'headset', |
| | | 'help', |
| | | 'help-filled', |
| | | 'histogram', |
| | | 'home-filled', |
| | | 'hot-water', |
| | | 'house', |
| | | 'ice-cream', |
| | | 'ice-cream-round', |
| | | 'ice-cream-square', |
| | | 'ice-drink', |
| | | 'ice-tea', |
| | | 'info-filled', |
| | | 'iphone', |
| | | 'key', |
| | | 'knife-fork', |
| | | 'lightning', |
| | | 'link', |
| | | 'list', |
| | | 'loading', |
| | | 'location', |
| | | 'location-filled', |
| | | 'location-information', |
| | | 'lock', |
| | | 'lollipop', |
| | | 'magic-stick', |
| | | 'magnet', |
| | | 'male', |
| | | 'management', |
| | | 'map-location', |
| | | 'medal', |
| | | 'menu', |
| | | 'message', |
| | | 'message-box', |
| | | 'mic', |
| | | 'microphone', |
| | | 'milk-tea', |
| | | 'minus', |
| | | 'money', |
| | | 'monitor', |
| | | 'moon', |
| | | 'moon-night', |
| | | 'more', |
| | | 'more-filled', |
| | | 'mostly-cloudy', |
| | | 'mouse', |
| | | 'mug', |
| | | 'mute', |
| | | 'mute-notification', |
| | | 'no-smoking', |
| | | 'notebook', |
| | | 'notification', |
| | | 'odometer', |
| | | 'office-building', |
| | | 'open', |
| | | 'operation', |
| | | 'opportunity', |
| | | 'orange', |
| | | 'paperclip', |
| | | 'partly-cloudy', |
| | | 'pear', |
| | | 'phone', |
| | | 'phone-filled', |
| | | 'picture', |
| | | 'picture-filled', |
| | | 'picture-rounded', |
| | | 'pie-chart', |
| | | 'place', |
| | | 'platform', |
| | | 'plus', |
| | | 'pointer', |
| | | 'position', |
| | | 'postcard', |
| | | 'pouring', |
| | | 'present', |
| | | 'price-tag', |
| | | 'printer', |
| | | 'promotion', |
| | | 'question-filled', |
| | | 'rank', |
| | | 'reading', |
| | | 'reading-lamp', |
| | | 'refresh', |
| | | 'refresh-left', |
| | | 'refresh-right', |
| | | 'refrigerator', |
| | | 'remove', |
| | | 'remove-filled', |
| | | 'right', |
| | | 'scale-to-original', |
| | | 'school', |
| | | 'scissor', |
| | | 'search', |
| | | 'select', |
| | | 'sell', |
| | | 'semi-select', |
| | | 'service', |
| | | 'set-up', |
| | | 'setting', |
| | | 'share', |
| | | 'ship', |
| | | 'shop', |
| | | 'shopping-bag', |
| | | 'shopping-cart', |
| | | 'shopping-cart-full', |
| | | 'smoking', |
| | | 'soccer', |
| | | 'sold-out', |
| | | 'sort', |
| | | 'sort-down', |
| | | 'sort-up', |
| | | 'stamp', |
| | | 'star', |
| | | 'star-filled', |
| | | 'stopwatch', |
| | | 'success-filled', |
| | | 'sugar', |
| | | 'suitcase', |
| | | 'sunny', |
| | | 'sunrise', |
| | | 'sunset', |
| | | 'switch', |
| | | 'switch-button', |
| | | 'takeaway-box', |
| | | 'ticket', |
| | | 'tickets', |
| | | 'timer', |
| | | 'toilet-paper', |
| | | 'tools', |
| | | 'top', |
| | | 'top-left', |
| | | 'top-right', |
| | | 'trend-charts', |
| | | 'trophy', |
| | | 'turn-off', |
| | | 'umbrella', |
| | | 'unlock', |
| | | 'upload', |
| | | 'upload-filled', |
| | | 'user', |
| | | 'user-filled', |
| | | 'van', |
| | | 'video-camera', |
| | | 'video-camera-filled', |
| | | 'video-pause', |
| | | 'video-play', |
| | | 'view', |
| | | 'wallet', |
| | | 'wallet-filled', |
| | | 'warning', |
| | | 'warning-filled', |
| | | 'watch', |
| | | 'watermelon', |
| | | 'wind-power', |
| | | 'zoom-in', |
| | | 'zoom-out' |
| | | ], |
| | | 'fa:': [ |
| | | '500px', |
| | | 'address-book', |
| | | 'address-book-o', |
| | | 'address-card', |
| | | 'address-card-o', |
| | | 'adjust', |
| | | 'adn', |
| | | 'align-center', |
| | | 'align-justify', |
| | | 'align-left', |
| | | 'amazon', |
| | | 'ambulance', |
| | | 'american-sign-language-interpreting', |
| | | 'anchor', |
| | | 'android', |
| | | 'angellist', |
| | | 'angle-double-left', |
| | | 'angle-double-up', |
| | | 'angle-down', |
| | | 'angle-left', |
| | | 'angle-up', |
| | | 'apple', |
| | | 'archive', |
| | | 'area-chart', |
| | | 'arrow-circle-left', |
| | | 'arrow-circle-o-left', |
| | | 'arrow-circle-o-up', |
| | | 'arrow-circle-up', |
| | | 'arrow-left', |
| | | 'arrow-up', |
| | | 'arrows', |
| | | 'arrows-alt', |
| | | 'arrows-h', |
| | | 'arrows-v', |
| | | 'assistive-listening-systems', |
| | | 'asterisk', |
| | | 'at', |
| | | 'audio-description', |
| | | 'automobile', |
| | | 'backward', |
| | | 'balance-scale', |
| | | 'ban', |
| | | 'bandcamp', |
| | | 'bank', |
| | | 'bar-chart', |
| | | 'barcode', |
| | | 'bars', |
| | | 'bath', |
| | | 'battery', |
| | | 'battery-0', |
| | | 'battery-1', |
| | | 'battery-2', |
| | | 'battery-3', |
| | | 'bed', |
| | | 'beer', |
| | | 'behance', |
| | | 'behance-square', |
| | | 'bell', |
| | | 'bell-o', |
| | | 'bell-slash', |
| | | 'bell-slash-o', |
| | | 'bicycle', |
| | | 'binoculars', |
| | | 'birthday-cake', |
| | | 'bitbucket', |
| | | 'bitbucket-square', |
| | | 'bitcoin', |
| | | 'black-tie', |
| | | 'blind', |
| | | 'bluetooth', |
| | | 'bluetooth-b', |
| | | 'bold', |
| | | 'bolt', |
| | | 'bomb', |
| | | 'book', |
| | | 'bookmark', |
| | | 'bookmark-o', |
| | | 'braille', |
| | | 'briefcase', |
| | | 'bug', |
| | | 'building', |
| | | 'building-o', |
| | | 'bullhorn', |
| | | 'bullseye', |
| | | 'bus', |
| | | 'buysellads', |
| | | 'cab', |
| | | 'calculator', |
| | | 'calendar', |
| | | 'calendar-check-o', |
| | | 'calendar-minus-o', |
| | | 'calendar-o', |
| | | 'calendar-plus-o', |
| | | 'calendar-times-o', |
| | | 'camera', |
| | | 'camera-retro', |
| | | 'caret-down', |
| | | 'caret-left', |
| | | 'caret-square-o-left', |
| | | 'caret-square-o-up', |
| | | 'caret-up', |
| | | 'cart-arrow-down', |
| | | 'cart-plus', |
| | | 'cc', |
| | | 'cc-amex', |
| | | 'cc-diners-club', |
| | | 'cc-discover', |
| | | 'cc-jcb', |
| | | 'cc-mastercard', |
| | | 'cc-paypal', |
| | | 'cc-stripe', |
| | | 'cc-visa', |
| | | 'certificate', |
| | | 'chain', |
| | | 'chain-broken', |
| | | 'check', |
| | | 'check-circle', |
| | | 'check-circle-o', |
| | | 'check-square', |
| | | 'check-square-o', |
| | | 'chevron-circle-left', |
| | | 'chevron-circle-up', |
| | | 'chevron-down', |
| | | 'chevron-left', |
| | | 'chevron-up', |
| | | 'child', |
| | | 'chrome', |
| | | 'circle', |
| | | 'circle-o', |
| | | 'circle-o-notch', |
| | | 'circle-thin', |
| | | 'clipboard', |
| | | 'clock-o', |
| | | 'clone', |
| | | 'close', |
| | | 'cloud', |
| | | 'cloud-download', |
| | | 'cloud-upload', |
| | | 'cny', |
| | | 'code', |
| | | 'code-fork', |
| | | 'codepen', |
| | | 'codiepie', |
| | | 'coffee', |
| | | 'cog', |
| | | 'cogs', |
| | | 'columns', |
| | | 'comment', |
| | | 'comment-o', |
| | | 'commenting', |
| | | 'commenting-o', |
| | | 'comments', |
| | | 'comments-o', |
| | | 'compass', |
| | | 'compress', |
| | | 'connectdevelop', |
| | | 'contao', |
| | | 'copy', |
| | | 'copyright', |
| | | 'creative-commons', |
| | | 'credit-card', |
| | | 'credit-card-alt', |
| | | 'crop', |
| | | 'crosshairs', |
| | | 'css3', |
| | | 'cube', |
| | | 'cubes', |
| | | 'cut', |
| | | 'cutlery', |
| | | 'dashboard', |
| | | 'dashcube', |
| | | 'database', |
| | | 'deaf', |
| | | 'dedent', |
| | | 'delicious', |
| | | 'desktop', |
| | | 'deviantart', |
| | | 'diamond', |
| | | 'digg', |
| | | 'dollar', |
| | | 'dot-circle-o', |
| | | 'download', |
| | | 'dribbble', |
| | | 'drivers-license', |
| | | 'drivers-license-o', |
| | | 'dropbox', |
| | | 'drupal', |
| | | 'edge', |
| | | 'edit', |
| | | 'eercast', |
| | | 'eject', |
| | | 'ellipsis-h', |
| | | 'ellipsis-v', |
| | | 'empire', |
| | | 'envelope', |
| | | 'envelope-o', |
| | | 'envelope-open', |
| | | 'envelope-open-o', |
| | | 'envelope-square', |
| | | 'envira', |
| | | 'eraser', |
| | | 'etsy', |
| | | 'eur', |
| | | 'exchange', |
| | | 'exclamation', |
| | | 'exclamation-circle', |
| | | 'exclamation-triangle', |
| | | 'expand', |
| | | 'expeditedssl', |
| | | 'external-link', |
| | | 'external-link-square', |
| | | 'eye', |
| | | 'eye-slash', |
| | | 'eyedropper', |
| | | 'fa', |
| | | 'facebook', |
| | | 'facebook-official', |
| | | 'facebook-square', |
| | | 'fast-backward', |
| | | 'fax', |
| | | 'feed', |
| | | 'female', |
| | | 'fighter-jet', |
| | | 'file', |
| | | 'file-archive-o', |
| | | 'file-audio-o', |
| | | 'file-code-o', |
| | | 'file-excel-o', |
| | | 'file-image-o', |
| | | 'file-movie-o', |
| | | 'file-o', |
| | | 'file-pdf-o', |
| | | 'file-powerpoint-o', |
| | | 'file-text', |
| | | 'file-text-o', |
| | | 'file-word-o', |
| | | 'film', |
| | | 'filter', |
| | | 'fire', |
| | | 'fire-extinguisher', |
| | | 'firefox', |
| | | 'first-order', |
| | | 'flag', |
| | | 'flag-checkered', |
| | | 'flag-o', |
| | | 'flask', |
| | | 'flickr', |
| | | 'floppy-o', |
| | | 'folder', |
| | | 'folder-o', |
| | | 'folder-open', |
| | | 'folder-open-o', |
| | | 'font', |
| | | 'fonticons', |
| | | 'fort-awesome', |
| | | 'forumbee', |
| | | 'foursquare', |
| | | 'free-code-camp', |
| | | 'frown-o', |
| | | 'futbol-o', |
| | | 'gamepad', |
| | | 'gavel', |
| | | 'gbp', |
| | | 'genderless', |
| | | 'get-pocket', |
| | | 'gg', |
| | | 'gg-circle', |
| | | 'gift', |
| | | 'git', |
| | | 'git-square', |
| | | 'github', |
| | | 'github-alt', |
| | | 'github-square', |
| | | 'gitlab', |
| | | 'gittip', |
| | | 'glass', |
| | | 'glide', |
| | | 'glide-g', |
| | | 'globe', |
| | | 'google', |
| | | 'google-plus', |
| | | 'google-plus-circle', |
| | | 'google-plus-square', |
| | | 'google-wallet', |
| | | 'graduation-cap', |
| | | 'grav', |
| | | 'group', |
| | | 'h-square', |
| | | 'hacker-news', |
| | | 'hand-grab-o', |
| | | 'hand-lizard-o', |
| | | 'hand-o-left', |
| | | 'hand-o-up', |
| | | 'hand-paper-o', |
| | | 'hand-peace-o', |
| | | 'hand-pointer-o', |
| | | 'hand-scissors-o', |
| | | 'hand-spock-o', |
| | | 'handshake-o', |
| | | 'hashtag', |
| | | 'hdd-o', |
| | | 'header', |
| | | 'headphones', |
| | | 'heart', |
| | | 'heart-o', |
| | | 'heartbeat', |
| | | 'history', |
| | | 'home', |
| | | 'hospital-o', |
| | | 'hourglass', |
| | | 'hourglass-1', |
| | | 'hourglass-2', |
| | | 'hourglass-3', |
| | | 'hourglass-o', |
| | | 'houzz', |
| | | 'html5', |
| | | 'i-cursor', |
| | | 'id-badge', |
| | | 'ils', |
| | | 'image', |
| | | 'imdb', |
| | | 'inbox', |
| | | 'indent', |
| | | 'industry', |
| | | 'info', |
| | | 'info-circle', |
| | | 'inr', |
| | | 'instagram', |
| | | 'internet-explorer', |
| | | 'intersex', |
| | | 'ioxhost', |
| | | 'italic', |
| | | 'joomla', |
| | | 'jsfiddle', |
| | | 'key', |
| | | 'keyboard-o', |
| | | 'krw', |
| | | 'language', |
| | | 'laptop', |
| | | 'lastfm', |
| | | 'lastfm-square', |
| | | 'leaf', |
| | | 'leanpub', |
| | | 'lemon-o', |
| | | 'level-up', |
| | | 'life-bouy', |
| | | 'lightbulb-o', |
| | | 'line-chart', |
| | | 'linkedin', |
| | | 'linkedin-square', |
| | | 'linode', |
| | | 'linux', |
| | | 'list', |
| | | 'list-alt', |
| | | 'list-ol', |
| | | 'list-ul', |
| | | 'location-arrow', |
| | | 'lock', |
| | | 'long-arrow-left', |
| | | 'long-arrow-up', |
| | | 'low-vision', |
| | | 'magic', |
| | | 'magnet', |
| | | 'mail-forward', |
| | | 'mail-reply', |
| | | 'mail-reply-all', |
| | | 'male', |
| | | 'map', |
| | | 'map-marker', |
| | | 'map-o', |
| | | 'map-pin', |
| | | 'map-signs', |
| | | 'mars', |
| | | 'mars-double', |
| | | 'mars-stroke', |
| | | 'mars-stroke-h', |
| | | 'mars-stroke-v', |
| | | 'maxcdn', |
| | | 'meanpath', |
| | | 'medium', |
| | | 'medkit', |
| | | 'meetup', |
| | | 'meh-o', |
| | | 'mercury', |
| | | 'microchip', |
| | | 'microphone', |
| | | 'microphone-slash', |
| | | 'minus', |
| | | 'minus-circle', |
| | | 'minus-square', |
| | | 'minus-square-o', |
| | | 'mixcloud', |
| | | 'mobile', |
| | | 'modx', |
| | | 'money', |
| | | 'moon-o', |
| | | 'motorcycle', |
| | | 'mouse-pointer', |
| | | 'music', |
| | | 'neuter', |
| | | 'newspaper-o', |
| | | 'object-group', |
| | | 'object-ungroup', |
| | | 'odnoklassniki', |
| | | 'odnoklassniki-square', |
| | | 'opencart', |
| | | 'openid', |
| | | 'opera', |
| | | 'optin-monster', |
| | | 'pagelines', |
| | | 'paint-brush', |
| | | 'paper-plane', |
| | | 'paper-plane-o', |
| | | 'paperclip', |
| | | 'paragraph', |
| | | 'pause', |
| | | 'pause-circle', |
| | | 'pause-circle-o', |
| | | 'paw', |
| | | 'paypal', |
| | | 'pencil', |
| | | 'pencil-square', |
| | | 'percent', |
| | | 'phone', |
| | | 'phone-square', |
| | | 'pie-chart', |
| | | 'pied-piper', |
| | | 'pied-piper-alt', |
| | | 'pied-piper-pp', |
| | | 'pinterest', |
| | | 'pinterest-p', |
| | | 'pinterest-square', |
| | | 'plane', |
| | | 'play', |
| | | 'play-circle', |
| | | 'play-circle-o', |
| | | 'plug', |
| | | 'plus', |
| | | 'plus-circle', |
| | | 'plus-square', |
| | | 'plus-square-o', |
| | | 'podcast', |
| | | 'power-off', |
| | | 'print', |
| | | 'product-hunt', |
| | | 'puzzle-piece', |
| | | 'qq', |
| | | 'qrcode', |
| | | 'question', |
| | | 'question-circle', |
| | | 'question-circle-o', |
| | | 'quora', |
| | | 'quote-left', |
| | | 'quote-right', |
| | | 'ra', |
| | | 'random', |
| | | 'ravelry', |
| | | 'recycle', |
| | | 'reddit', |
| | | 'reddit-alien', |
| | | 'reddit-square', |
| | | 'refresh', |
| | | 'registered', |
| | | 'renren', |
| | | 'repeat', |
| | | 'retweet', |
| | | 'road', |
| | | 'rocket', |
| | | 'rotate-left', |
| | | 'rouble', |
| | | 'rss-square', |
| | | 'safari', |
| | | 'scribd', |
| | | 'search', |
| | | 'search-minus', |
| | | 'search-plus', |
| | | 'sellsy', |
| | | 'server', |
| | | 'share-alt', |
| | | 'share-alt-square', |
| | | 'share-square', |
| | | 'share-square-o', |
| | | 'shield', |
| | | 'ship', |
| | | 'shirtsinbulk', |
| | | 'shopping-bag', |
| | | 'shopping-basket', |
| | | 'shopping-cart', |
| | | 'shower', |
| | | 'sign-in', |
| | | 'sign-language', |
| | | 'sign-out', |
| | | 'signal', |
| | | 'simplybuilt', |
| | | 'sitemap', |
| | | 'skyatlas', |
| | | 'skype', |
| | | 'slack', |
| | | 'sliders', |
| | | 'slideshare', |
| | | 'smile-o', |
| | | 'snapchat', |
| | | 'snapchat-ghost', |
| | | 'snapchat-square', |
| | | 'snowflake-o', |
| | | 'sort', |
| | | 'sort-alpha-asc', |
| | | 'sort-alpha-desc', |
| | | 'sort-amount-asc', |
| | | 'sort-amount-desc', |
| | | 'sort-asc', |
| | | 'sort-numeric-asc', |
| | | 'sort-numeric-desc', |
| | | 'soundcloud', |
| | | 'space-shuttle', |
| | | 'spinner', |
| | | 'spoon', |
| | | 'spotify', |
| | | 'square', |
| | | 'square-o', |
| | | 'stack-exchange', |
| | | 'stack-overflow', |
| | | 'star', |
| | | 'star-half', |
| | | 'star-half-empty', |
| | | 'star-o', |
| | | 'steam', |
| | | 'steam-square', |
| | | 'step-backward', |
| | | 'stethoscope', |
| | | 'sticky-note', |
| | | 'sticky-note-o', |
| | | 'stop', |
| | | 'stop-circle', |
| | | 'stop-circle-o', |
| | | 'street-view', |
| | | 'strikethrough', |
| | | 'stumbleupon', |
| | | 'stumbleupon-circle', |
| | | 'subscript', |
| | | 'subway', |
| | | 'suitcase', |
| | | 'sun-o', |
| | | 'superpowers', |
| | | 'superscript', |
| | | 'table', |
| | | 'tablet', |
| | | 'tag', |
| | | 'tags', |
| | | 'tasks', |
| | | 'telegram', |
| | | 'television', |
| | | 'tencent-weibo', |
| | | 'terminal', |
| | | 'text-height', |
| | | 'text-width', |
| | | 'th', |
| | | 'th-large', |
| | | 'th-list', |
| | | 'themeisle', |
| | | 'thermometer', |
| | | 'thermometer-0', |
| | | 'thermometer-1', |
| | | 'thermometer-2', |
| | | 'thermometer-3', |
| | | 'thumb-tack', |
| | | 'thumbs-down', |
| | | 'thumbs-o-up', |
| | | 'thumbs-up', |
| | | 'ticket', |
| | | 'times-circle', |
| | | 'times-circle-o', |
| | | 'times-rectangle', |
| | | 'times-rectangle-o', |
| | | 'tint', |
| | | 'toggle-off', |
| | | 'toggle-on', |
| | | 'trademark', |
| | | 'train', |
| | | 'transgender-alt', |
| | | 'trash', |
| | | 'trash-o', |
| | | 'tree', |
| | | 'trello', |
| | | 'tripadvisor', |
| | | 'trophy', |
| | | 'truck', |
| | | 'try', |
| | | 'tty', |
| | | 'tumblr', |
| | | 'tumblr-square', |
| | | 'twitch', |
| | | 'twitter', |
| | | 'twitter-square', |
| | | 'umbrella', |
| | | 'underline', |
| | | 'universal-access', |
| | | 'unlock', |
| | | 'unlock-alt', |
| | | 'upload', |
| | | 'usb', |
| | | 'user', |
| | | 'user-circle', |
| | | 'user-circle-o', |
| | | 'user-md', |
| | | 'user-o', |
| | | 'user-plus', |
| | | 'user-secret', |
| | | 'user-times', |
| | | 'venus', |
| | | 'venus-double', |
| | | 'venus-mars', |
| | | 'viacoin', |
| | | 'viadeo', |
| | | 'viadeo-square', |
| | | 'video-camera', |
| | | 'vimeo', |
| | | 'vimeo-square', |
| | | 'vine', |
| | | 'vk', |
| | | 'volume-control-phone', |
| | | 'volume-down', |
| | | 'volume-off', |
| | | 'volume-up', |
| | | 'wechat', |
| | | 'weibo', |
| | | 'whatsapp', |
| | | 'wheelchair', |
| | | 'wheelchair-alt', |
| | | 'wifi', |
| | | 'wikipedia-w', |
| | | 'window-maximize', |
| | | 'window-minimize', |
| | | 'window-restore', |
| | | 'windows', |
| | | 'wordpress', |
| | | 'wpbeginner', |
| | | 'wpexplorer', |
| | | 'wpforms', |
| | | 'wrench', |
| | | 'xing', |
| | | 'xing-square', |
| | | 'y-combinator', |
| | | 'yahoo', |
| | | 'yelp', |
| | | 'yoast', |
| | | 'youtube', |
| | | 'youtube-play', |
| | | 'youtube-square' |
| | | ], |
| | | 'fa-solid:': [ |
| | | 'abacus', |
| | | 'ad', |
| | | 'address-book', |
| | | 'address-card', |
| | | 'adjust', |
| | | 'air-freshener', |
| | | 'align-center', |
| | | 'align-justify', |
| | | 'align-left', |
| | | 'align-right', |
| | | 'allergies', |
| | | 'ambulance', |
| | | 'american-sign-language-interpreting', |
| | | 'anchor', |
| | | 'angle-double-down', |
| | | 'angle-double-left', |
| | | 'angle-double-right', |
| | | 'angle-double-up', |
| | | 'angle-down', |
| | | 'angle-left', |
| | | 'angle-right', |
| | | 'angle-up', |
| | | 'angry', |
| | | 'ankh', |
| | | 'apple-alt', |
| | | 'archive', |
| | | 'archway', |
| | | 'arrow-alt-circle-down', |
| | | 'arrow-alt-circle-left', |
| | | 'arrow-alt-circle-right', |
| | | 'arrow-alt-circle-up', |
| | | 'arrow-circle-down', |
| | | 'arrow-circle-left', |
| | | 'arrow-circle-right', |
| | | 'arrow-circle-up', |
| | | 'arrow-down', |
| | | 'arrow-left', |
| | | 'arrow-right', |
| | | 'arrow-up', |
| | | 'arrows-alt', |
| | | 'arrows-alt-h', |
| | | 'arrows-alt-v', |
| | | 'assistive-listening-systems', |
| | | 'asterisk', |
| | | 'at', |
| | | 'atlas', |
| | | 'atom', |
| | | 'audio-description', |
| | | 'award', |
| | | 'baby', |
| | | 'baby-carriage', |
| | | 'backspace', |
| | | 'backward', |
| | | 'bacon', |
| | | 'bacteria', |
| | | 'bacterium', |
| | | 'bahai', |
| | | 'balance-scale', |
| | | 'balance-scale-left', |
| | | 'balance-scale-right', |
| | | 'ban', |
| | | 'band-aid', |
| | | 'barcode', |
| | | 'bars', |
| | | 'baseball-ball', |
| | | 'basketball-ball', |
| | | 'bath', |
| | | 'battery-empty', |
| | | 'battery-full', |
| | | 'battery-half', |
| | | 'battery-quarter', |
| | | 'battery-three-quarters', |
| | | 'bed', |
| | | 'beer', |
| | | 'bell', |
| | | 'bell-slash', |
| | | 'bezier-curve', |
| | | 'bible', |
| | | 'bicycle', |
| | | 'biking', |
| | | 'binoculars', |
| | | 'biohazard', |
| | | 'birthday-cake', |
| | | 'blender', |
| | | 'blender-phone', |
| | | 'blind', |
| | | 'blog', |
| | | 'bold', |
| | | 'bolt', |
| | | 'bomb', |
| | | 'bone', |
| | | 'bong', |
| | | 'book', |
| | | 'book-dead', |
| | | 'book-medical', |
| | | 'book-open', |
| | | 'book-reader', |
| | | 'bookmark', |
| | | 'border-all', |
| | | 'border-none', |
| | | 'border-style', |
| | | 'bowling-ball', |
| | | 'box', |
| | | 'box-open', |
| | | 'box-tissue', |
| | | 'boxes', |
| | | 'braille', |
| | | 'brain', |
| | | 'bread-slice', |
| | | 'briefcase', |
| | | 'briefcase-medical', |
| | | 'broadcast-tower', |
| | | 'broom', |
| | | 'brush', |
| | | 'bug', |
| | | 'building', |
| | | 'bullhorn', |
| | | 'bullseye', |
| | | 'burn', |
| | | 'bus', |
| | | 'bus-alt', |
| | | 'business-time', |
| | | 'calculator', |
| | | 'calculator-alt', |
| | | 'calendar', |
| | | 'calendar-alt', |
| | | 'calendar-check', |
| | | 'calendar-day', |
| | | 'calendar-minus', |
| | | 'calendar-plus', |
| | | 'calendar-times', |
| | | 'calendar-week', |
| | | 'camera', |
| | | 'camera-retro', |
| | | 'campground', |
| | | 'candy-cane', |
| | | 'cannabis', |
| | | 'capsules', |
| | | 'car', |
| | | 'car-alt', |
| | | 'car-battery', |
| | | 'car-crash', |
| | | 'car-side', |
| | | 'caravan', |
| | | 'caret-down', |
| | | 'caret-left', |
| | | 'caret-right', |
| | | 'caret-square-down', |
| | | 'caret-square-left', |
| | | 'caret-square-right', |
| | | 'caret-square-up', |
| | | 'caret-up', |
| | | 'carrot', |
| | | 'cart-arrow-down', |
| | | 'cart-plus', |
| | | 'cash-register', |
| | | 'cat', |
| | | 'certificate', |
| | | 'chair', |
| | | 'chalkboard', |
| | | 'chalkboard-teacher', |
| | | 'charging-station', |
| | | 'chart-area', |
| | | 'chart-bar', |
| | | 'chart-line', |
| | | 'chart-pie', |
| | | 'check', |
| | | 'check-circle', |
| | | 'check-double', |
| | | 'check-square', |
| | | 'cheese', |
| | | 'chess', |
| | | 'chess-bishop', |
| | | 'chess-board', |
| | | 'chess-king', |
| | | 'chess-knight', |
| | | 'chess-pawn', |
| | | 'chess-queen', |
| | | 'chess-rook', |
| | | 'chevron-circle-down', |
| | | 'chevron-circle-left', |
| | | 'chevron-circle-right', |
| | | 'chevron-circle-up', |
| | | 'chevron-down', |
| | | 'chevron-left', |
| | | 'chevron-right', |
| | | 'chevron-up', |
| | | 'child', |
| | | 'church', |
| | | 'circle', |
| | | 'circle-notch', |
| | | 'city', |
| | | 'clinic-medical', |
| | | 'clipboard', |
| | | 'clipboard-check', |
| | | 'clipboard-list', |
| | | 'clock', |
| | | 'clone', |
| | | 'closed-captioning', |
| | | 'cloud', |
| | | 'cloud-download-alt', |
| | | 'cloud-meatball', |
| | | 'cloud-moon', |
| | | 'cloud-moon-rain', |
| | | 'cloud-rain', |
| | | 'cloud-showers-heavy', |
| | | 'cloud-sun', |
| | | 'cloud-sun-rain', |
| | | 'cloud-upload-alt', |
| | | 'cocktail', |
| | | 'code', |
| | | 'code-branch', |
| | | 'coffee', |
| | | 'cog', |
| | | 'cogs', |
| | | 'coins', |
| | | 'columns', |
| | | 'comment', |
| | | 'comment-alt', |
| | | 'comment-dollar', |
| | | 'comment-dots', |
| | | 'comment-medical', |
| | | 'comment-slash', |
| | | 'comments', |
| | | 'comments-dollar', |
| | | 'compact-disc', |
| | | 'compass', |
| | | 'compress', |
| | | 'compress-alt', |
| | | 'compress-arrows-alt', |
| | | 'concierge-bell', |
| | | 'cookie', |
| | | 'cookie-bite', |
| | | 'copy', |
| | | 'copyright', |
| | | 'couch', |
| | | 'credit-card', |
| | | 'crop', |
| | | 'crop-alt', |
| | | 'cross', |
| | | 'crosshairs', |
| | | 'crow', |
| | | 'crown', |
| | | 'crutch', |
| | | 'cube', |
| | | 'cubes', |
| | | 'cut', |
| | | 'database', |
| | | 'deaf', |
| | | 'democrat', |
| | | 'desktop', |
| | | 'dharmachakra', |
| | | 'diagnoses', |
| | | 'dice', |
| | | 'dice-d20', |
| | | 'dice-d6', |
| | | 'dice-five', |
| | | 'dice-four', |
| | | 'dice-one', |
| | | 'dice-six', |
| | | 'dice-three', |
| | | 'dice-two', |
| | | 'digital-tachograph', |
| | | 'directions', |
| | | 'disease', |
| | | 'divide', |
| | | 'dizzy', |
| | | 'dna', |
| | | 'dog', |
| | | 'dollar-sign', |
| | | 'dolly', |
| | | 'dolly-flatbed', |
| | | 'donate', |
| | | 'door-closed', |
| | | 'door-open', |
| | | 'dot-circle', |
| | | 'dove', |
| | | 'download', |
| | | 'drafting-compass', |
| | | 'dragon', |
| | | 'draw-polygon', |
| | | 'drum', |
| | | 'drum-steelpan', |
| | | 'drumstick-bite', |
| | | 'dumbbell', |
| | | 'dumpster', |
| | | 'dumpster-fire', |
| | | 'dungeon', |
| | | 'edit', |
| | | 'egg', |
| | | 'eject', |
| | | 'ellipsis-h', |
| | | 'ellipsis-v', |
| | | 'empty-set', |
| | | 'envelope', |
| | | 'envelope-open', |
| | | 'envelope-open-text', |
| | | 'envelope-square', |
| | | 'equals', |
| | | 'eraser', |
| | | 'ethernet', |
| | | 'euro-sign', |
| | | 'exchange-alt', |
| | | 'exclamation', |
| | | 'exclamation-circle', |
| | | 'exclamation-triangle', |
| | | 'expand', |
| | | 'expand-alt', |
| | | 'expand-arrows-alt', |
| | | 'external-link-alt', |
| | | 'external-link-square-alt', |
| | | 'eye', |
| | | 'eye-dropper', |
| | | 'eye-slash', |
| | | 'fan', |
| | | 'fast-backward', |
| | | 'fast-forward', |
| | | 'faucet', |
| | | 'fax', |
| | | 'feather', |
| | | 'feather-alt', |
| | | 'female', |
| | | 'fighter-jet', |
| | | 'file', |
| | | 'file-alt', |
| | | 'file-archive', |
| | | 'file-audio', |
| | | 'file-code', |
| | | 'file-contract', |
| | | 'file-csv', |
| | | 'file-download', |
| | | 'file-excel', |
| | | 'file-export', |
| | | 'file-image', |
| | | 'file-import', |
| | | 'file-invoice', |
| | | 'file-invoice-dollar', |
| | | 'file-medical', |
| | | 'file-medical-alt', |
| | | 'file-pdf', |
| | | 'file-powerpoint', |
| | | 'file-prescription', |
| | | 'file-signature', |
| | | 'file-upload', |
| | | 'file-video', |
| | | 'file-word', |
| | | 'fill', |
| | | 'fill-drip', |
| | | 'film', |
| | | 'filter', |
| | | 'fingerprint', |
| | | 'fire', |
| | | 'fire-alt', |
| | | 'fire-extinguisher', |
| | | 'first-aid', |
| | | 'fish', |
| | | 'fist-raised', |
| | | 'flag', |
| | | 'flag-checkered', |
| | | 'flag-usa', |
| | | 'flask', |
| | | 'flushed', |
| | | 'folder', |
| | | 'folder-minus', |
| | | 'folder-open', |
| | | 'folder-plus', |
| | | 'font', |
| | | 'football-ball', |
| | | 'forward', |
| | | 'frog', |
| | | 'frown', |
| | | 'frown-open', |
| | | 'function', |
| | | 'funnel-dollar', |
| | | 'futbol', |
| | | 'gamepad', |
| | | 'gas-pump', |
| | | 'gavel', |
| | | 'gem', |
| | | 'genderless', |
| | | 'ghost', |
| | | 'gift', |
| | | 'gifts', |
| | | 'glass-cheers', |
| | | 'glass-martini', |
| | | 'glass-martini-alt', |
| | | 'glass-whiskey', |
| | | 'glasses', |
| | | 'globe', |
| | | 'globe-africa', |
| | | 'globe-americas', |
| | | 'globe-asia', |
| | | 'globe-europe', |
| | | 'golf-ball', |
| | | 'gopuram', |
| | | 'graduation-cap', |
| | | 'greater-than', |
| | | 'greater-than-equal', |
| | | 'grimace', |
| | | 'grin', |
| | | 'grin-alt', |
| | | 'grin-beam', |
| | | 'grin-beam-sweat', |
| | | 'grin-hearts', |
| | | 'grin-squint', |
| | | 'grin-squint-tears', |
| | | 'grin-stars', |
| | | 'grin-tears', |
| | | 'grin-tongue', |
| | | 'grin-tongue-squint', |
| | | 'grin-tongue-wink', |
| | | 'grin-wink', |
| | | 'grip-horizontal', |
| | | 'grip-lines', |
| | | 'grip-lines-vertical', |
| | | 'grip-vertical', |
| | | 'guitar', |
| | | 'h-square', |
| | | 'hamburger', |
| | | 'hammer', |
| | | 'hamsa', |
| | | 'hand-holding', |
| | | 'hand-holding-heart', |
| | | 'hand-holding-medical', |
| | | 'hand-holding-usd', |
| | | 'hand-holding-water', |
| | | 'hand-lizard', |
| | | 'hand-middle-finger', |
| | | 'hand-paper', |
| | | 'hand-peace', |
| | | 'hand-point-down', |
| | | 'hand-point-left', |
| | | 'hand-point-right', |
| | | 'hand-point-up', |
| | | 'hand-pointer', |
| | | 'hand-rock', |
| | | 'hand-scissors', |
| | | 'hand-sparkles', |
| | | 'hand-spock', |
| | | 'hands', |
| | | 'hands-helping', |
| | | 'hands-wash', |
| | | 'handshake', |
| | | 'handshake-alt-slash', |
| | | 'handshake-slash', |
| | | 'hanukiah', |
| | | 'hard-hat', |
| | | 'hashtag', |
| | | 'hat-cowboy', |
| | | 'hat-cowboy-side', |
| | | 'hat-wizard', |
| | | 'hdd', |
| | | 'head-side-cough', |
| | | 'head-side-cough-slash', |
| | | 'head-side-mask', |
| | | 'head-side-virus', |
| | | 'heading', |
| | | 'headphones', |
| | | 'headphones-alt', |
| | | 'headset', |
| | | 'heart', |
| | | 'heart-broken', |
| | | 'heartbeat', |
| | | 'helicopter', |
| | | 'highlighter', |
| | | 'hiking', |
| | | 'hippo', |
| | | 'history', |
| | | 'hockey-puck', |
| | | 'holly-berry', |
| | | 'home', |
| | | 'horse', |
| | | 'horse-head', |
| | | 'hospital', |
| | | 'hospital-alt', |
| | | 'hospital-symbol', |
| | | 'hospital-user', |
| | | 'hot-tub', |
| | | 'hotdog', |
| | | 'hotel', |
| | | 'hourglass', |
| | | 'hourglass-end', |
| | | 'hourglass-half', |
| | | 'hourglass-start', |
| | | 'house-damage', |
| | | 'house-user', |
| | | 'hryvnia', |
| | | 'i-cursor', |
| | | 'ice-cream', |
| | | 'icicles', |
| | | 'icons', |
| | | 'id-badge', |
| | | 'id-card', |
| | | 'id-card-alt', |
| | | 'igloo', |
| | | 'image', |
| | | 'images', |
| | | 'inbox', |
| | | 'indent', |
| | | 'industry', |
| | | 'infinity', |
| | | 'info', |
| | | 'info-circle', |
| | | 'integral', |
| | | 'intersection', |
| | | 'italic', |
| | | 'jedi', |
| | | 'joint', |
| | | 'journal-whills', |
| | | 'kaaba', |
| | | 'key', |
| | | 'keyboard', |
| | | 'khanda', |
| | | 'kiss', |
| | | 'kiss-beam', |
| | | 'kiss-wink-heart', |
| | | 'kiwi-bird', |
| | | 'lambda', |
| | | 'landmark', |
| | | 'language', |
| | | 'laptop', |
| | | 'laptop-code', |
| | | 'laptop-house', |
| | | 'laptop-medical', |
| | | 'laugh', |
| | | 'laugh-beam', |
| | | 'laugh-squint', |
| | | 'laugh-wink', |
| | | 'layer-group', |
| | | 'leaf', |
| | | 'lemon', |
| | | 'less-than', |
| | | 'less-than-equal', |
| | | 'level-down-alt', |
| | | 'level-up-alt', |
| | | 'life-ring', |
| | | 'lightbulb', |
| | | 'link', |
| | | 'lira-sign', |
| | | 'list', |
| | | 'list-alt', |
| | | 'list-ol', |
| | | 'list-ul', |
| | | 'location-arrow', |
| | | 'lock', |
| | | 'lock-open', |
| | | 'long-arrow-alt-down', |
| | | 'long-arrow-alt-left', |
| | | 'long-arrow-alt-right', |
| | | 'long-arrow-alt-up', |
| | | 'low-vision', |
| | | 'luggage-cart', |
| | | 'lungs', |
| | | 'lungs-virus', |
| | | 'magic', |
| | | 'magnet', |
| | | 'mail-bulk', |
| | | 'male', |
| | | 'map', |
| | | 'map-marked', |
| | | 'map-marked-alt', |
| | | 'map-marker', |
| | | 'map-marker-alt', |
| | | 'map-pin', |
| | | 'map-signs', |
| | | 'marker', |
| | | 'mars', |
| | | 'mars-double', |
| | | 'mars-stroke', |
| | | 'mars-stroke-h', |
| | | 'mars-stroke-v', |
| | | 'mask', |
| | | 'medal', |
| | | 'medkit', |
| | | 'meh', |
| | | 'meh-blank', |
| | | 'meh-rolling-eyes', |
| | | 'memory', |
| | | 'menorah', |
| | | 'mercury', |
| | | 'meteor', |
| | | 'microchip', |
| | | 'microphone', |
| | | 'microphone-alt', |
| | | 'microphone-alt-slash', |
| | | 'microphone-slash', |
| | | 'microscope', |
| | | 'minus', |
| | | 'minus-circle', |
| | | 'minus-square', |
| | | 'mitten', |
| | | 'mobile', |
| | | 'mobile-alt', |
| | | 'money-bill', |
| | | 'money-bill-alt', |
| | | 'money-bill-wave', |
| | | 'money-bill-wave-alt', |
| | | 'money-check', |
| | | 'money-check-alt', |
| | | 'monument', |
| | | 'moon', |
| | | 'mortar-pestle', |
| | | 'mosque', |
| | | 'motorcycle', |
| | | 'mountain', |
| | | 'mouse', |
| | | 'mouse-pointer', |
| | | 'mug-hot', |
| | | 'music', |
| | | 'network-wired', |
| | | 'neuter', |
| | | 'newspaper', |
| | | 'not-equal', |
| | | 'notes-medical', |
| | | 'object-group', |
| | | 'object-ungroup', |
| | | 'oil-can', |
| | | 'om', |
| | | 'omega', |
| | | 'otter', |
| | | 'outdent', |
| | | 'pager', |
| | | 'paint-brush', |
| | | 'paint-roller', |
| | | 'palette', |
| | | 'pallet', |
| | | 'paper-plane', |
| | | 'paperclip', |
| | | 'parachute-box', |
| | | 'paragraph', |
| | | 'parking', |
| | | 'passport', |
| | | 'pastafarianism', |
| | | 'paste', |
| | | 'pause', |
| | | 'pause-circle', |
| | | 'paw', |
| | | 'peace', |
| | | 'pen', |
| | | 'pen-alt', |
| | | 'pen-fancy', |
| | | 'pen-nib', |
| | | 'pen-square', |
| | | 'pencil-alt', |
| | | 'pencil-ruler', |
| | | 'people-arrows', |
| | | 'people-carry', |
| | | 'pepper-hot', |
| | | 'percent', |
| | | 'percentage', |
| | | 'person-booth', |
| | | 'phone', |
| | | 'phone-alt', |
| | | 'phone-slash', |
| | | 'phone-square', |
| | | 'phone-square-alt', |
| | | 'phone-volume', |
| | | 'photo-video', |
| | | 'pi', |
| | | 'piggy-bank', |
| | | 'pills', |
| | | 'pizza-slice', |
| | | 'place-of-worship', |
| | | 'plane', |
| | | 'plane-arrival', |
| | | 'plane-departure', |
| | | 'plane-slash', |
| | | 'play', |
| | | 'play-circle', |
| | | 'plug', |
| | | 'plus', |
| | | 'plus-circle', |
| | | 'plus-square', |
| | | 'podcast', |
| | | 'poll', |
| | | 'poll-h', |
| | | 'poo', |
| | | 'poo-storm', |
| | | 'poop', |
| | | 'portrait', |
| | | 'pound-sign', |
| | | 'power-off', |
| | | 'pray', |
| | | 'praying-hands', |
| | | 'prescription', |
| | | 'prescription-bottle', |
| | | 'prescription-bottle-alt', |
| | | 'print', |
| | | 'procedures', |
| | | 'project-diagram', |
| | | 'pump-medical', |
| | | 'pump-soap', |
| | | 'puzzle-piece', |
| | | 'qrcode', |
| | | 'question', |
| | | 'question-circle', |
| | | 'quidditch', |
| | | 'quote-left', |
| | | 'quote-right', |
| | | 'quran', |
| | | 'radiation', |
| | | 'radiation-alt', |
| | | 'rainbow', |
| | | 'random', |
| | | 'receipt', |
| | | 'record-vinyl', |
| | | 'recycle', |
| | | 'redo', |
| | | 'redo-alt', |
| | | 'registered', |
| | | 'remove-format', |
| | | 'reply', |
| | | 'reply-all', |
| | | 'republican', |
| | | 'restroom', |
| | | 'retweet', |
| | | 'ribbon', |
| | | 'ring', |
| | | 'road', |
| | | 'robot', |
| | | 'rocket', |
| | | 'route', |
| | | 'rss', |
| | | 'rss-square', |
| | | 'ruble-sign', |
| | | 'ruler', |
| | | 'ruler-combined', |
| | | 'ruler-horizontal', |
| | | 'ruler-vertical', |
| | | 'running', |
| | | 'rupee-sign', |
| | | 'sad-cry', |
| | | 'sad-tear', |
| | | 'satellite', |
| | | 'satellite-dish', |
| | | 'save', |
| | | 'school', |
| | | 'screwdriver', |
| | | 'scroll', |
| | | 'sd-card', |
| | | 'search', |
| | | 'search-dollar', |
| | | 'search-location', |
| | | 'search-minus', |
| | | 'search-plus', |
| | | 'seedling', |
| | | 'server', |
| | | 'shapes', |
| | | 'share', |
| | | 'share-alt', |
| | | 'share-alt-square', |
| | | 'share-square', |
| | | 'shekel-sign', |
| | | 'shield-alt', |
| | | 'shield-virus', |
| | | 'ship', |
| | | 'shipping-fast', |
| | | 'shoe-prints', |
| | | 'shopping-bag', |
| | | 'shopping-basket', |
| | | 'shopping-cart', |
| | | 'shower', |
| | | 'shuttle-van', |
| | | 'sigma', |
| | | 'sign', |
| | | 'sign-in-alt', |
| | | 'sign-language', |
| | | 'sign-out-alt', |
| | | 'signal', |
| | | 'signal-alt', |
| | | 'signal-alt-slash', |
| | | 'signal-slash', |
| | | 'signature', |
| | | 'sim-card', |
| | | 'sink', |
| | | 'sitemap', |
| | | 'skating', |
| | | 'skiing', |
| | | 'skiing-nordic', |
| | | 'skull', |
| | | 'skull-crossbones', |
| | | 'slash', |
| | | 'sleigh', |
| | | 'sliders-h', |
| | | 'smile', |
| | | 'smile-beam', |
| | | 'smile-wink', |
| | | 'smog', |
| | | 'smoking', |
| | | 'smoking-ban', |
| | | 'sms', |
| | | 'snowboarding', |
| | | 'snowflake', |
| | | 'snowman', |
| | | 'snowplow', |
| | | 'soap', |
| | | 'socks', |
| | | 'solar-panel', |
| | | 'sort', |
| | | 'sort-alpha-down', |
| | | 'sort-alpha-down-alt', |
| | | 'sort-alpha-up', |
| | | 'sort-alpha-up-alt', |
| | | 'sort-amount-down', |
| | | 'sort-amount-down-alt', |
| | | 'sort-amount-up', |
| | | 'sort-amount-up-alt', |
| | | 'sort-down', |
| | | 'sort-numeric-down', |
| | | 'sort-numeric-down-alt', |
| | | 'sort-numeric-up', |
| | | 'sort-numeric-up-alt', |
| | | 'sort-up', |
| | | 'spa', |
| | | 'space-shuttle', |
| | | 'spell-check', |
| | | 'spider', |
| | | 'spinner', |
| | | 'splotch', |
| | | 'spray-can', |
| | | 'square', |
| | | 'square-full', |
| | | 'square-root', |
| | | 'square-root-alt', |
| | | 'stamp', |
| | | 'star', |
| | | 'star-and-crescent', |
| | | 'star-half', |
| | | 'star-half-alt', |
| | | 'star-of-david', |
| | | 'star-of-life', |
| | | 'step-backward', |
| | | 'step-forward', |
| | | 'stethoscope', |
| | | 'sticky-note', |
| | | 'stop', |
| | | 'stop-circle', |
| | | 'stopwatch', |
| | | 'stopwatch-20', |
| | | 'store', |
| | | 'store-alt', |
| | | 'store-alt-slash', |
| | | 'store-slash', |
| | | 'stream', |
| | | 'street-view', |
| | | 'strikethrough', |
| | | 'stroopwafel', |
| | | 'subscript', |
| | | 'subway', |
| | | 'suitcase', |
| | | 'suitcase-rolling', |
| | | 'sun', |
| | | 'superscript', |
| | | 'surprise', |
| | | 'swatchbook', |
| | | 'swimmer', |
| | | 'swimming-pool', |
| | | 'synagogue', |
| | | 'sync', |
| | | 'sync-alt', |
| | | 'syringe', |
| | | 'table', |
| | | 'table-tennis', |
| | | 'tablet', |
| | | 'tablet-alt', |
| | | 'tablets', |
| | | 'tachometer-alt', |
| | | 'tag', |
| | | 'tags', |
| | | 'tally', |
| | | 'tape', |
| | | 'tasks', |
| | | 'taxi', |
| | | 'teeth', |
| | | 'teeth-open', |
| | | 'temperature-high', |
| | | 'temperature-low', |
| | | 'tenge', |
| | | 'terminal', |
| | | 'text-height', |
| | | 'text-width', |
| | | 'th', |
| | | 'th-large', |
| | | 'th-list', |
| | | 'theater-masks', |
| | | 'thermometer', |
| | | 'thermometer-empty', |
| | | 'thermometer-full', |
| | | 'thermometer-half', |
| | | 'thermometer-quarter', |
| | | 'thermometer-three-quarters', |
| | | 'theta', |
| | | 'thumbs-down', |
| | | 'thumbs-up', |
| | | 'thumbtack', |
| | | 'ticket-alt', |
| | | 'tilde', |
| | | 'times', |
| | | 'times-circle', |
| | | 'tint', |
| | | 'tint-slash', |
| | | 'tired', |
| | | 'toggle-off', |
| | | 'toggle-on', |
| | | 'toilet', |
| | | 'toilet-paper', |
| | | 'toilet-paper-slash', |
| | | 'toolbox', |
| | | 'tools', |
| | | 'tooth', |
| | | 'torah', |
| | | 'torii-gate', |
| | | 'tractor', |
| | | 'trademark', |
| | | 'traffic-light', |
| | | 'trailer', |
| | | 'train', |
| | | 'tram', |
| | | 'transgender', |
| | | 'transgender-alt', |
| | | 'trash', |
| | | 'trash-alt', |
| | | 'trash-restore', |
| | | 'trash-restore-alt', |
| | | 'tree', |
| | | 'trophy', |
| | | 'truck', |
| | | 'truck-loading', |
| | | 'truck-monster', |
| | | 'truck-moving', |
| | | 'truck-pickup', |
| | | 'tshirt', |
| | | 'tty', |
| | | 'tv', |
| | | 'umbrella', |
| | | 'umbrella-beach', |
| | | 'underline', |
| | | 'undo', |
| | | 'undo-alt', |
| | | 'union', |
| | | 'universal-access', |
| | | 'university', |
| | | 'unlink', |
| | | 'unlock', |
| | | 'unlock-alt', |
| | | 'upload', |
| | | 'user', |
| | | 'user-alt', |
| | | 'user-alt-slash', |
| | | 'user-astronaut', |
| | | 'user-check', |
| | | 'user-circle', |
| | | 'user-clock', |
| | | 'user-cog', |
| | | 'user-edit', |
| | | 'user-friends', |
| | | 'user-graduate', |
| | | 'user-injured', |
| | | 'user-lock', |
| | | 'user-md', |
| | | 'user-minus', |
| | | 'user-ninja', |
| | | 'user-nurse', |
| | | 'user-plus', |
| | | 'user-secret', |
| | | 'user-shield', |
| | | 'user-slash', |
| | | 'user-tag', |
| | | 'user-tie', |
| | | 'user-times', |
| | | 'users', |
| | | 'users-cog', |
| | | 'users-slash', |
| | | 'utensil-spoon', |
| | | 'utensils', |
| | | 'value-absolute', |
| | | 'vector-square', |
| | | 'venus', |
| | | 'venus-double', |
| | | 'venus-mars', |
| | | 'vest', |
| | | 'vest-patches', |
| | | 'vial', |
| | | 'vials', |
| | | 'video', |
| | | 'video-slash', |
| | | 'vihara', |
| | | 'virus', |
| | | 'virus-slash', |
| | | 'viruses', |
| | | 'voicemail', |
| | | 'volleyball-ball', |
| | | 'volume', |
| | | 'volume-down', |
| | | 'volume-mute', |
| | | 'volume-off', |
| | | 'volume-slash', |
| | | 'volume-up', |
| | | 'vote-yea', |
| | | 'vr-cardboard', |
| | | 'walking', |
| | | 'wallet', |
| | | 'warehouse', |
| | | 'water', |
| | | 'wave-square', |
| | | 'weight', |
| | | 'weight-hanging', |
| | | 'wheelchair', |
| | | 'wifi', |
| | | 'wifi-slash', |
| | | 'wind', |
| | | 'window-close', |
| | | 'window-maximize', |
| | | 'window-minimize', |
| | | 'window-restore', |
| | | 'wine-bottle', |
| | | 'wine-glass', |
| | | 'wine-glass-alt', |
| | | 'won-sign', |
| | | 'wrench', |
| | | 'x-ray', |
| | | 'yen-sign', |
| | | 'yin-yang' |
| | | ] |
| | | } |
| New file |
| | |
| | | export interface ScrollToParams { |
| | | el: HTMLElement |
| | | to: number |
| | | position: string |
| | | duration?: number |
| | | callback?: () => void |
| | | } |
| | | |
| | | const easeInOutQuad = (t: number, b: number, c: number, d: number) => { |
| | | t /= d / 2 |
| | | if (t < 1) { |
| | | return (c / 2) * t * t + b |
| | | } |
| | | t-- |
| | | return (-c / 2) * (t * (t - 2) - 1) + b |
| | | } |
| | | const move = (el: HTMLElement, position: string, amount: number) => { |
| | | el[position] = amount |
| | | } |
| | | |
| | | export function useScrollTo({ |
| | | el, |
| | | position = 'scrollLeft', |
| | | to, |
| | | duration = 500, |
| | | callback |
| | | }: ScrollToParams) { |
| | | const isActiveRef = ref(false) |
| | | const start = el[position] |
| | | const change = to - start |
| | | const increment = 20 |
| | | let currentTime = 0 |
| | | |
| | | function animateScroll() { |
| | | if (!unref(isActiveRef)) { |
| | | return |
| | | } |
| | | currentTime += increment |
| | | const val = easeInOutQuad(currentTime, start, change, duration) |
| | | move(el, position, val) |
| | | if (currentTime < duration && unref(isActiveRef)) { |
| | | requestAnimationFrame(animateScroll) |
| | | } else { |
| | | if (callback) { |
| | | callback() |
| | | } |
| | | } |
| | | } |
| | | |
| | | function run() { |
| | | isActiveRef.value = true |
| | | animateScroll() |
| | | } |
| | | |
| | | function stop() { |
| | | isActiveRef.value = false |
| | | } |
| | | |
| | | return { start: run, stop } |
| | | } |
| New file |
| | |
| | | /** |
| | | * 配置浏览器本地存储的方式,可直接存储对象数组。 |
| | | */ |
| | | |
| | | import WebStorageCache from 'web-storage-cache' |
| | | |
| | | type CacheType = 'localStorage' | 'sessionStorage' |
| | | |
| | | export const CACHE_KEY = { |
| | | // 用户相关 |
| | | ROLE_ROUTERS: 'roleRouters', |
| | | USER: 'user', |
| | | // 系统设置 |
| | | IS_DARK: 'isDark', |
| | | LANG: 'lang', |
| | | THEME: 'theme', |
| | | LAYOUT: 'layout', |
| | | DICT_CACHE: 'dictCache', |
| | | // 登录表单 |
| | | LoginForm: 'loginForm', |
| | | TenantId: 'tenantId' |
| | | } |
| | | |
| | | export const useCache = (type: CacheType = 'localStorage') => { |
| | | const wsCache: WebStorageCache = new WebStorageCache({ |
| | | storage: type |
| | | }) |
| | | |
| | | return { |
| | | wsCache |
| | | } |
| | | } |
| | | |
| | | export const deleteUserCache = () => { |
| | | const { wsCache } = useCache() |
| | | wsCache.delete(CACHE_KEY.USER) |
| | | wsCache.delete(CACHE_KEY.ROLE_ROUTERS) |
| | | // 注意,不要清理 LoginForm 登录表单 |
| | | } |
| New file |
| | |
| | | import { ConfigGlobalTypes } from '@/types/configGlobal' |
| | | |
| | | export const useConfigGlobal = () => { |
| | | const configGlobal = inject('configGlobal', {}) as ConfigGlobalTypes |
| | | |
| | | return { |
| | | configGlobal |
| | | } |
| | | } |
| New file |
| | |
| | | import { reactive } from 'vue' |
| | | import { AxiosPromise } from 'axios' |
| | | import { findIndex } from '@/utils' |
| | | import { eachTree, filter, treeMap } from '@/utils/tree' |
| | | import { getBoolDictOptions, getDictOptions, getIntDictOptions } from '@/utils/dict' |
| | | |
| | | import { FormSchema } from '@/types/form' |
| | | import { TableColumn } from '@/types/table' |
| | | import { DescriptionsSchema } from '@/types/descriptions' |
| | | import { ComponentOptions, ComponentProps } from '@/types/components' |
| | | import { DictTag } from '@/components/DictTag' |
| | | import { cloneDeep, merge } from 'lodash-es' |
| | | |
| | | export type CrudSchema = Omit<TableColumn, 'children'> & { |
| | | isSearch?: boolean // 是否在查询显示 |
| | | search?: CrudSearchParams // 查询的详细配置 |
| | | isTable?: boolean // 是否在列表显示 |
| | | table?: CrudTableParams // 列表的详细配置 |
| | | isForm?: boolean // 是否在表单显示 |
| | | form?: CrudFormParams // 表单的详细配置 |
| | | isDetail?: boolean // 是否在详情显示 |
| | | detail?: CrudDescriptionsParams // 详情的详细配置 |
| | | children?: CrudSchema[] |
| | | dictType?: string // 字典类型 |
| | | dictClass?: 'string' | 'number' | 'boolean' // 字典数据类型 string | number | boolean |
| | | } |
| | | |
| | | type CrudSearchParams = { |
| | | // 是否显示在查询项 |
| | | show?: boolean |
| | | // 接口 |
| | | api?: () => Promise<any> |
| | | // 搜索字段 |
| | | field?: string |
| | | } & Omit<FormSchema, 'field'> |
| | | |
| | | type CrudTableParams = { |
| | | // 是否显示表头 |
| | | show?: boolean |
| | | // 列宽配置 |
| | | width?: number | string |
| | | // 列是否固定在左侧或者右侧 |
| | | fixed?: 'left' | 'right' |
| | | } & Omit<FormSchema, 'field'> |
| | | type CrudFormParams = { |
| | | // 是否显示表单项 |
| | | show?: boolean |
| | | // 接口 |
| | | api?: () => Promise<any> |
| | | } & Omit<FormSchema, 'field'> |
| | | |
| | | type CrudDescriptionsParams = { |
| | | // 是否显示表单项 |
| | | show?: boolean |
| | | } & Omit<DescriptionsSchema, 'field'> |
| | | |
| | | interface AllSchemas { |
| | | searchSchema: FormSchema[] |
| | | tableColumns: TableColumn[] |
| | | formSchema: FormSchema[] |
| | | detailSchema: DescriptionsSchema[] |
| | | } |
| | | |
| | | const { t } = useI18n() |
| | | |
| | | // 过滤所有结构 |
| | | export const useCrudSchemas = ( |
| | | crudSchema: CrudSchema[] |
| | | ): { |
| | | allSchemas: AllSchemas |
| | | } => { |
| | | // 所有结构数据 |
| | | const allSchemas = reactive<AllSchemas>({ |
| | | searchSchema: [], |
| | | tableColumns: [], |
| | | formSchema: [], |
| | | detailSchema: [] |
| | | }) |
| | | |
| | | const searchSchema = filterSearchSchema(crudSchema, allSchemas) |
| | | allSchemas.searchSchema = searchSchema || [] |
| | | |
| | | const tableColumns = filterTableSchema(crudSchema) |
| | | allSchemas.tableColumns = tableColumns || [] |
| | | |
| | | const formSchema = filterFormSchema(crudSchema, allSchemas) |
| | | allSchemas.formSchema = formSchema |
| | | |
| | | const detailSchema = filterDescriptionsSchema(crudSchema) |
| | | allSchemas.detailSchema = detailSchema |
| | | |
| | | return { |
| | | allSchemas |
| | | } |
| | | } |
| | | |
| | | // 过滤 Search 结构 |
| | | const filterSearchSchema = (crudSchema: CrudSchema[], allSchemas: AllSchemas): FormSchema[] => { |
| | | const searchSchema: FormSchema[] = [] |
| | | |
| | | // 获取字典列表队列 |
| | | const searchRequestTask: Array<() => Promise<void>> = [] |
| | | eachTree(crudSchema, (schemaItem: CrudSchema) => { |
| | | // 判断是否显示 |
| | | if (schemaItem?.isSearch || schemaItem.search?.show) { |
| | | let component = schemaItem?.search?.component || 'Input' |
| | | const options: ComponentOptions[] = [] |
| | | let comonentProps: ComponentProps = {} |
| | | if (schemaItem.dictType) { |
| | | const allOptions: ComponentOptions = { label: '全部', value: '' } |
| | | options.push(allOptions) |
| | | getDictOptions(schemaItem.dictType).forEach((dict) => { |
| | | options.push(dict) |
| | | }) |
| | | comonentProps = { |
| | | options: options |
| | | } |
| | | if (!schemaItem.search?.component) component = 'Select' |
| | | } |
| | | |
| | | // updated by AKing: 解决了当使用默认的dict选项时,form中事件不能触发的问题 |
| | | const searchSchemaItem = merge( |
| | | { |
| | | // 默认为 input |
| | | component, |
| | | ...schemaItem.search, |
| | | field: schemaItem.field, |
| | | label: schemaItem.search?.label || schemaItem.label |
| | | }, |
| | | { componentProps: comonentProps } |
| | | ) |
| | | if (searchSchemaItem.api) { |
| | | searchRequestTask.push(async () => { |
| | | const res = await (searchSchemaItem.api as () => AxiosPromise)() |
| | | if (res) { |
| | | const index = findIndex(allSchemas.searchSchema, (v: FormSchema) => { |
| | | return v.field === searchSchemaItem.field |
| | | }) |
| | | if (index !== -1) { |
| | | allSchemas.searchSchema[index]!.componentProps!.options = filterOptions( |
| | | res, |
| | | searchSchemaItem.componentProps.optionsAlias?.labelField |
| | | ) |
| | | } |
| | | } |
| | | }) |
| | | } |
| | | // 删除不必要的字段 |
| | | delete searchSchemaItem.show |
| | | |
| | | searchSchema.push(searchSchemaItem) |
| | | } |
| | | }) |
| | | for (const task of searchRequestTask) { |
| | | task() |
| | | } |
| | | return searchSchema |
| | | } |
| | | |
| | | // 过滤 table 结构 |
| | | const filterTableSchema = (crudSchema: CrudSchema[]): TableColumn[] => { |
| | | const tableColumns = treeMap<CrudSchema>(crudSchema, { |
| | | conversion: (schema: CrudSchema) => { |
| | | if (schema?.isTable !== false && schema?.table?.show !== false) { |
| | | // add by 芋艿:增加对 dict 字典数据的支持 |
| | | if (!schema.formatter && schema.dictType) { |
| | | schema.formatter = (_: Recordable, __: TableColumn, cellValue: any) => { |
| | | return h(DictTag, { |
| | | type: schema.dictType!, // ! 表示一定不为空 |
| | | value: cellValue |
| | | }) |
| | | } |
| | | } |
| | | return { |
| | | ...schema.table, |
| | | ...schema |
| | | } |
| | | } |
| | | } |
| | | }) |
| | | |
| | | // 第一次过滤会有 undefined 所以需要二次过滤 |
| | | return filter<TableColumn>(tableColumns as TableColumn[], (data) => { |
| | | if (data.children === void 0) { |
| | | delete data.children |
| | | } |
| | | return !!data.field |
| | | }) |
| | | } |
| | | |
| | | // 过滤 form 结构 |
| | | const filterFormSchema = (crudSchema: CrudSchema[], allSchemas: AllSchemas): FormSchema[] => { |
| | | const formSchema: FormSchema[] = [] |
| | | |
| | | // 获取字典列表队列 |
| | | const formRequestTask: Array<() => Promise<void>> = [] |
| | | |
| | | eachTree(crudSchema, (schemaItem: CrudSchema) => { |
| | | // 判断是否显示 |
| | | if (schemaItem?.isForm !== false && schemaItem?.form?.show !== false) { |
| | | let component = schemaItem?.form?.component || 'Input' |
| | | let defaultValue: any = '' |
| | | if (schemaItem.form?.value) { |
| | | defaultValue = schemaItem.form?.value |
| | | } else { |
| | | if (component === 'InputNumber') { |
| | | defaultValue = 0 |
| | | } |
| | | } |
| | | let comonentProps: ComponentProps = {} |
| | | if (schemaItem.dictType) { |
| | | const options: ComponentOptions[] = [] |
| | | if (schemaItem.dictClass && schemaItem.dictClass === 'number') { |
| | | getIntDictOptions(schemaItem.dictType).forEach((dict) => { |
| | | options.push(dict) |
| | | }) |
| | | } else if (schemaItem.dictClass && schemaItem.dictClass === 'boolean') { |
| | | getBoolDictOptions(schemaItem.dictType).forEach((dict) => { |
| | | options.push(dict) |
| | | }) |
| | | } else { |
| | | getDictOptions(schemaItem.dictType).forEach((dict) => { |
| | | options.push(dict) |
| | | }) |
| | | } |
| | | comonentProps = { |
| | | options: options |
| | | } |
| | | if (!(schemaItem.form && schemaItem.form.component)) component = 'Select' |
| | | } |
| | | |
| | | // updated by AKing: 解决了当使用默认的dict选项时,form中事件不能触发的问题 |
| | | const formSchemaItem = merge( |
| | | { |
| | | // 默认为 input |
| | | component, |
| | | value: defaultValue, |
| | | ...schemaItem.form, |
| | | field: schemaItem.field, |
| | | label: schemaItem.form?.label || schemaItem.label |
| | | }, |
| | | { componentProps: comonentProps } |
| | | ) |
| | | |
| | | if (formSchemaItem.api) { |
| | | formRequestTask.push(async () => { |
| | | const res = await (formSchemaItem.api as () => AxiosPromise)() |
| | | if (res) { |
| | | const index = findIndex(allSchemas.formSchema, (v: FormSchema) => { |
| | | return v.field === formSchemaItem.field |
| | | }) |
| | | if (index !== -1) { |
| | | allSchemas.formSchema[index]!.componentProps!.options = filterOptions( |
| | | res, |
| | | formSchemaItem.componentProps.optionsAlias?.labelField |
| | | ) |
| | | } |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // 删除不必要的字段 |
| | | delete formSchemaItem.show |
| | | |
| | | formSchema.push(formSchemaItem) |
| | | } |
| | | }) |
| | | |
| | | for (const task of formRequestTask) { |
| | | task() |
| | | } |
| | | return formSchema |
| | | } |
| | | |
| | | // 过滤 descriptions 结构 |
| | | const filterDescriptionsSchema = (crudSchema: CrudSchema[]): DescriptionsSchema[] => { |
| | | const descriptionsSchema: FormSchema[] = [] |
| | | |
| | | eachTree(crudSchema, (schemaItem: CrudSchema) => { |
| | | // 判断是否显示 |
| | | if (schemaItem?.isDetail !== false && schemaItem.detail?.show !== false) { |
| | | const descriptionsSchemaItem = { |
| | | ...schemaItem.detail, |
| | | field: schemaItem.field, |
| | | label: schemaItem.detail?.label || schemaItem.label |
| | | } |
| | | if (schemaItem.dictType) { |
| | | descriptionsSchemaItem.dictType = schemaItem.dictType |
| | | } |
| | | if (schemaItem.detail?.dateFormat || schemaItem.formatter == 'formatDate') { |
| | | // 优先使用 detail 下的配置,如果没有默认为 YYYY-MM-DD HH:mm:ss |
| | | descriptionsSchemaItem.dateFormat = schemaItem?.detail?.dateFormat |
| | | ? schemaItem?.detail?.dateFormat |
| | | : 'YYYY-MM-DD HH:mm:ss' |
| | | } |
| | | |
| | | // 删除不必要的字段 |
| | | delete descriptionsSchemaItem.show |
| | | |
| | | descriptionsSchema.push(descriptionsSchemaItem) |
| | | } |
| | | }) |
| | | |
| | | return descriptionsSchema |
| | | } |
| | | |
| | | // 给options添加国际化 |
| | | const filterOptions = (options: Recordable, labelField?: string) => { |
| | | return options?.map((v: Recordable) => { |
| | | if (labelField) { |
| | | v['labelField'] = t(v.labelField) |
| | | } else { |
| | | v['label'] = t(v.label) |
| | | } |
| | | return v |
| | | }) |
| | | } |
| | | |
| | | // 将 tableColumns 指定 fields 放到最前面 |
| | | export const sortTableColumns = (tableColumns: TableColumn[], field: string) => { |
| | | const fieldIndex = tableColumns.findIndex((item) => item.field === field) |
| | | const fieldColumn = cloneDeep(tableColumns[fieldIndex]) |
| | | tableColumns.splice(fieldIndex, 1) |
| | | // 添加到开头 |
| | | tableColumns.unshift(fieldColumn) |
| | | } |
| New file |
| | |
| | | import variables from '@/assets/styles/global.module.scss' |
| | | |
| | | export const useDesign = () => { |
| | | const scssVariables = variables |
| | | |
| | | /** |
| | | * @param scope 类名 |
| | | * @returns 返回空间名-类名 |
| | | */ |
| | | const getPrefixCls = (scope: string) => { |
| | | return `${scssVariables.namespace}-${scope}` |
| | | } |
| | | |
| | | return { |
| | | variables: scssVariables, |
| | | getPrefixCls |
| | | } |
| | | } |
| New file |
| | |
| | | import mitt from 'mitt' |
| | | |
| | | interface Option { |
| | | name: string // 事件名称 |
| | | callback: Fn // 回调 |
| | | } |
| | | |
| | | const emitter = mitt() |
| | | |
| | | export const useEmitt = (option?: Option) => { |
| | | if (option) { |
| | | emitter.on(option.name, option.callback) |
| | | |
| | | onBeforeUnmount(() => { |
| | | emitter.off(option.name) |
| | | }) |
| | | } |
| | | |
| | | return { |
| | | emitter |
| | | } |
| | | } |
| New file |
| | |
| | | import type { Form, FormExpose } from '@/components/Form' |
| | | import type { ElForm } from 'element-plus' |
| | | import type { FormProps } from '@/components/Form/src/types' |
| | | import { FormSchema, FormSetPropsType } from '@/types/form' |
| | | |
| | | export const useForm = (props?: FormProps) => { |
| | | // From实例 |
| | | const formRef = ref<typeof Form & FormExpose>() |
| | | |
| | | // ElForm实例 |
| | | const elFormRef = ref<ComponentRef<typeof ElForm>>() |
| | | |
| | | /** |
| | | * @param ref Form实例 |
| | | * @param elRef ElForm实例 |
| | | */ |
| | | const register = (ref: typeof Form & FormExpose, elRef: ComponentRef<typeof ElForm>) => { |
| | | formRef.value = ref |
| | | elFormRef.value = elRef |
| | | } |
| | | |
| | | const getForm = async () => { |
| | | await nextTick() |
| | | const form = unref(formRef) |
| | | if (!form) { |
| | | console.error('The form is not registered. Please use the register method to register') |
| | | } |
| | | return form |
| | | } |
| | | |
| | | // 一些内置的方法 |
| | | const methods: { |
| | | setProps: (props: Recordable) => void |
| | | setValues: (data: Recordable) => void |
| | | getFormData: <T = Recordable | undefined>() => Promise<T> |
| | | setSchema: (schemaProps: FormSetPropsType[]) => void |
| | | addSchema: (formSchema: FormSchema, index?: number) => void |
| | | delSchema: (field: string) => void |
| | | } = { |
| | | setProps: async (props: FormProps = {}) => { |
| | | const form = await getForm() |
| | | form?.setProps(props) |
| | | if (props.model) { |
| | | form?.setValues(props.model) |
| | | } |
| | | }, |
| | | |
| | | setValues: async (data: Recordable) => { |
| | | const form = await getForm() |
| | | form?.setValues(data) |
| | | }, |
| | | |
| | | /** |
| | | * @param schemaProps 需要设置的schemaProps |
| | | */ |
| | | setSchema: async (schemaProps: FormSetPropsType[]) => { |
| | | const form = await getForm() |
| | | form?.setSchema(schemaProps) |
| | | }, |
| | | |
| | | /** |
| | | * @param formSchema 需要新增数据 |
| | | * @param index 在哪里新增 |
| | | */ |
| | | addSchema: async (formSchema: FormSchema, index?: number) => { |
| | | const form = await getForm() |
| | | form?.addSchema(formSchema, index) |
| | | }, |
| | | |
| | | /** |
| | | * @param field 删除哪个数据 |
| | | */ |
| | | delSchema: async (field: string) => { |
| | | const form = await getForm() |
| | | form?.delSchema(field) |
| | | }, |
| | | |
| | | /** |
| | | * @returns form data |
| | | */ |
| | | getFormData: async <T = Recordable>(): Promise<T> => { |
| | | const form = await getForm() |
| | | return form?.formModel as T |
| | | } |
| | | } |
| | | |
| | | props && methods.setProps(props) |
| | | |
| | | return { |
| | | register, |
| | | elFormRef, |
| | | methods |
| | | } |
| | | } |
| New file |
| | |
| | | import { Config, driver } from 'driver.js' |
| | | import 'driver.js/dist/driver.css' |
| | | import { useDesign } from '@/hooks/web/useDesign' |
| | | import { useI18n } from '@/hooks/web/useI18n' |
| | | |
| | | const { t } = useI18n() |
| | | |
| | | const { variables } = useDesign() |
| | | |
| | | export const useGuide = (options?: Config) => { |
| | | const driverObj = driver( |
| | | options || { |
| | | showProgress: true, |
| | | nextBtnText: t('common.nextLabel'), |
| | | prevBtnText: t('common.prevLabel'), |
| | | doneBtnText: t('common.doneLabel'), |
| | | steps: [ |
| | | { |
| | | element: `#${variables.namespace}-menu`, |
| | | popover: { |
| | | title: t('common.menu'), |
| | | description: t('common.menuDes'), |
| | | side: 'right' |
| | | } |
| | | }, |
| | | { |
| | | element: `#${variables.namespace}-tool-header`, |
| | | popover: { |
| | | title: t('common.tool'), |
| | | description: t('common.toolDes'), |
| | | side: 'left' |
| | | } |
| | | }, |
| | | { |
| | | element: `#${variables.namespace}-tags-view`, |
| | | popover: { |
| | | title: t('common.tagsView'), |
| | | description: t('common.tagsViewDes'), |
| | | side: 'bottom' |
| | | } |
| | | } |
| | | ] |
| | | } |
| | | ) |
| | | |
| | | return { |
| | | ...driverObj |
| | | } |
| | | } |
| New file |
| | |
| | | import { i18n } from '@/plugins/vueI18n' |
| | | |
| | | type I18nGlobalTranslation = { |
| | | (key: string): string |
| | | (key: string, locale: string): string |
| | | (key: string, locale: string, list: unknown[]): string |
| | | (key: string, locale: string, named: Record<string, unknown>): string |
| | | (key: string, list: unknown[]): string |
| | | (key: string, named: Record<string, unknown>): string |
| | | } |
| | | |
| | | type I18nTranslationRestParameters = [string, any] |
| | | |
| | | const getKey = (namespace: string | undefined, key: string) => { |
| | | if (!namespace) { |
| | | return key |
| | | } |
| | | if (key.startsWith(namespace)) { |
| | | return key |
| | | } |
| | | return `${namespace}.${key}` |
| | | } |
| | | |
| | | export const useI18n = ( |
| | | namespace?: string |
| | | ): { |
| | | t: I18nGlobalTranslation |
| | | } => { |
| | | const normalFn = { |
| | | t: (key: string) => { |
| | | return getKey(namespace, key) |
| | | } |
| | | } |
| | | |
| | | if (!i18n) { |
| | | return normalFn |
| | | } |
| | | |
| | | const { t, ...methods } = i18n.global |
| | | |
| | | const tFn: I18nGlobalTranslation = (key: string, ...arg: any[]) => { |
| | | if (!key) return '' |
| | | if (!key.includes('.') && !namespace) return key |
| | | //@ts-ignore |
| | | return t(getKey(namespace, key), ...(arg as I18nTranslationRestParameters)) |
| | | } |
| | | return { |
| | | ...methods, |
| | | t: tFn |
| | | } |
| | | } |
| | | |
| | | export const t = (key: string) => key |
| New file |
| | |
| | | import { h } from 'vue' |
| | | import type { VNode } from 'vue' |
| | | import { Icon } from '@/components/Icon' |
| | | import { IconTypes } from '@/types/icon' |
| | | |
| | | export const useIcon = (props: IconTypes): VNode => { |
| | | return h(Icon, props) |
| | | } |
| New file |
| | |
| | | import { i18n } from '@/plugins/vueI18n' |
| | | import { useLocaleStoreWithOut } from '@/store/modules/locale' |
| | | import { setHtmlPageLang } from '@/plugins/vueI18n/helper' |
| | | |
| | | const setI18nLanguage = (locale: LocaleType) => { |
| | | const localeStore = useLocaleStoreWithOut() |
| | | |
| | | if (i18n.mode === 'legacy') { |
| | | i18n.global.locale = locale |
| | | } else { |
| | | ;(i18n.global.locale as any).value = locale |
| | | } |
| | | localeStore.setCurrentLocale({ |
| | | lang: locale |
| | | }) |
| | | setHtmlPageLang(locale) |
| | | } |
| | | |
| | | export const useLocale = () => { |
| | | // Switching the language will change the locale of useI18n |
| | | // And submit to configuration modification |
| | | const changeLocale = async (locale: LocaleType) => { |
| | | const globalI18n = i18n.global |
| | | |
| | | const langModule = await import(`../../locales/${locale}.ts`) |
| | | |
| | | globalI18n.setLocaleMessage(locale, langModule.default) |
| | | |
| | | setI18nLanguage(locale) |
| | | } |
| | | |
| | | return { |
| | | changeLocale |
| | | } |
| | | } |
| New file |
| | |
| | | import { ElMessage, ElMessageBox, ElNotification } from 'element-plus' |
| | | import { useI18n } from './useI18n' |
| | | export const useMessage = () => { |
| | | const { t } = useI18n() |
| | | return { |
| | | // 消息提示 |
| | | info(content: string) { |
| | | ElMessage.info(content) |
| | | }, |
| | | // 错误消息 |
| | | error(content: string) { |
| | | ElMessage.error(content) |
| | | }, |
| | | // 成功消息 |
| | | success(content: string) { |
| | | ElMessage.success(content) |
| | | }, |
| | | // 警告消息 |
| | | warning(content: string) { |
| | | ElMessage.warning(content) |
| | | }, |
| | | // 弹出提示 |
| | | alert(content: string) { |
| | | ElMessageBox.alert(content, t('common.confirmTitle')) |
| | | }, |
| | | // 错误提示 |
| | | alertError(content: string) { |
| | | ElMessageBox.alert(content, t('common.confirmTitle'), { type: 'error' }) |
| | | }, |
| | | // 成功提示 |
| | | alertSuccess(content: string) { |
| | | ElMessageBox.alert(content, t('common.confirmTitle'), { type: 'success' }) |
| | | }, |
| | | // 警告提示 |
| | | alertWarning(content: string) { |
| | | ElMessageBox.alert(content, t('common.confirmTitle'), { type: 'warning' }) |
| | | }, |
| | | // 通知提示 |
| | | notify(content: string) { |
| | | ElNotification.info(content) |
| | | }, |
| | | // 错误通知 |
| | | notifyError(content: string) { |
| | | ElNotification.error(content) |
| | | }, |
| | | // 成功通知 |
| | | notifySuccess(content: string) { |
| | | ElNotification.success(content) |
| | | }, |
| | | // 警告通知 |
| | | notifyWarning(content: string) { |
| | | ElNotification.warning(content) |
| | | }, |
| | | // 确认窗体 |
| | | confirm(content: string, tip?: string) { |
| | | return ElMessageBox.confirm(content, tip ? tip : t('common.confirmTitle'), { |
| | | confirmButtonText: t('common.ok'), |
| | | cancelButtonText: t('common.cancel'), |
| | | type: 'warning' |
| | | }) |
| | | }, |
| | | // 删除窗体 |
| | | delConfirm(content?: string, tip?: string) { |
| | | return ElMessageBox.confirm( |
| | | content ? content : t('common.delMessage'), |
| | | tip ? tip : t('common.confirmTitle'), |
| | | { |
| | | confirmButtonText: t('common.ok'), |
| | | cancelButtonText: t('common.cancel'), |
| | | type: 'warning' |
| | | } |
| | | ) |
| | | }, |
| | | // 导出窗体 |
| | | exportConfirm(content?: string, tip?: string) { |
| | | return ElMessageBox.confirm( |
| | | content ? content : t('common.exportMessage'), |
| | | tip ? tip : t('common.confirmTitle'), |
| | | { |
| | | confirmButtonText: t('common.ok'), |
| | | cancelButtonText: t('common.cancel'), |
| | | type: 'warning' |
| | | } |
| | | ) |
| | | }, |
| | | // 提交内容 |
| | | prompt(content: string, tip: string) { |
| | | return ElMessageBox.prompt(content, tip, { |
| | | confirmButtonText: t('common.ok'), |
| | | cancelButtonText: t('common.cancel'), |
| | | type: 'warning' |
| | | }) |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | import { useCssVar } from '@vueuse/core' |
| | | import type { NProgressOptions } from 'nprogress' |
| | | import NProgress from 'nprogress' |
| | | import 'nprogress/nprogress.css' |
| | | |
| | | const primaryColor = useCssVar('--el-color-primary', document.documentElement) |
| | | |
| | | export const useNProgress = () => { |
| | | NProgress.configure({ showSpinner: false } as NProgressOptions) |
| | | |
| | | const initColor = async () => { |
| | | await nextTick() |
| | | const bar = document.getElementById('nprogress')?.getElementsByClassName('bar')[0] as ElRef |
| | | if (bar) { |
| | | bar.style.background = unref(primaryColor.value) |
| | | } |
| | | } |
| | | |
| | | initColor() |
| | | |
| | | const start = () => { |
| | | NProgress.start() |
| | | } |
| | | |
| | | const done = () => { |
| | | NProgress.done() |
| | | } |
| | | |
| | | return { |
| | | start, |
| | | done |
| | | } |
| | | } |
| New file |
| | |
| | | import { ref, onBeforeUnmount } from 'vue' |
| | | |
| | | const useNetwork = () => { |
| | | const online = ref(true) |
| | | |
| | | const updateNetwork = () => { |
| | | online.value = navigator.onLine |
| | | } |
| | | |
| | | window.addEventListener('online', updateNetwork) |
| | | window.addEventListener('offline', updateNetwork) |
| | | |
| | | onBeforeUnmount(() => { |
| | | window.removeEventListener('online', updateNetwork) |
| | | window.removeEventListener('offline', updateNetwork) |
| | | }) |
| | | |
| | | return { online } |
| | | } |
| | | |
| | | export { useNetwork } |
| New file |
| | |
| | | import { dateUtil } from '@/utils/dateUtil' |
| | | import { reactive, toRefs } from 'vue' |
| | | import { tryOnMounted, tryOnUnmounted } from '@vueuse/core' |
| | | |
| | | export const useNow = (immediate = true) => { |
| | | let timer: IntervalHandle |
| | | |
| | | const state = reactive({ |
| | | year: 0, |
| | | month: 0, |
| | | week: '', |
| | | day: 0, |
| | | hour: '', |
| | | minute: '', |
| | | second: 0, |
| | | meridiem: '' |
| | | }) |
| | | |
| | | const update = () => { |
| | | const now = dateUtil() |
| | | |
| | | const h = now.format('HH') |
| | | const m = now.format('mm') |
| | | const s = now.get('s') |
| | | |
| | | state.year = now.get('y') |
| | | state.month = now.get('M') + 1 |
| | | state.week = '星期' + ['日', '一', '二', '三', '四', '五', '六'][now.day()] |
| | | state.day = now.get('date') |
| | | state.hour = h |
| | | state.minute = m |
| | | state.second = s |
| | | |
| | | state.meridiem = now.format('A') |
| | | } |
| | | |
| | | function start() { |
| | | update() |
| | | clearInterval(timer) |
| | | timer = setInterval(() => update(), 1000) |
| | | } |
| | | |
| | | function stop() { |
| | | clearInterval(timer) |
| | | } |
| | | |
| | | tryOnMounted(() => { |
| | | immediate && start() |
| | | }) |
| | | |
| | | tryOnUnmounted(() => { |
| | | stop() |
| | | }) |
| | | |
| | | return { |
| | | ...toRefs(state), |
| | | start, |
| | | stop |
| | | } |
| | | } |
| New file |
| | |
| | | import { useAppStoreWithOut } from '@/store/modules/app' |
| | | |
| | | const appStore = useAppStoreWithOut() |
| | | |
| | | export const usePageLoading = () => { |
| | | const loadStart = () => { |
| | | appStore.setPageLoading(true) |
| | | } |
| | | |
| | | const loadDone = () => { |
| | | appStore.setPageLoading(false) |
| | | } |
| | | |
| | | return { |
| | | loadStart, |
| | | loadDone |
| | | } |
| | | } |
| New file |
| | |
| | | import download from '@/utils/download' |
| | | import { Table, TableExpose } from '@/components/Table' |
| | | import { ElMessage, ElMessageBox, ElTable } from 'element-plus' |
| | | import { computed, nextTick, reactive, ref, unref, watch } from 'vue' |
| | | import type { TableProps } from '@/components/Table/src/types' |
| | | |
| | | import { TableSetPropsType } from '@/types/table' |
| | | |
| | | const { t } = useI18n() |
| | | interface ResponseType<T = any> { |
| | | list: T[] |
| | | total?: number |
| | | } |
| | | |
| | | interface UseTableConfig<T = any> { |
| | | getListApi: (option: any) => Promise<T> |
| | | delListApi?: (option: any) => Promise<T> |
| | | exportListApi?: (option: any) => Promise<T> |
| | | // 返回数据格式配置 |
| | | response?: ResponseType |
| | | // 默认传递的参数 |
| | | defaultParams?: Recordable |
| | | props?: TableProps |
| | | } |
| | | |
| | | interface TableObject<T = any> { |
| | | pageSize: number |
| | | currentPage: number |
| | | total: number |
| | | tableList: T[] |
| | | params: any |
| | | loading: boolean |
| | | exportLoading: boolean |
| | | currentRow: Nullable<T> |
| | | } |
| | | |
| | | export const useTable = <T = any>(config?: UseTableConfig<T>) => { |
| | | const tableObject = reactive<TableObject<T>>({ |
| | | // 页数 |
| | | pageSize: 10, |
| | | // 当前页 |
| | | currentPage: 1, |
| | | // 总条数 |
| | | total: 10, |
| | | // 表格数据 |
| | | tableList: [], |
| | | // AxiosConfig 配置 |
| | | params: { |
| | | ...(config?.defaultParams || {}) |
| | | }, |
| | | // 加载中 |
| | | loading: true, |
| | | // 导出加载中 |
| | | exportLoading: false, |
| | | // 当前行的数据 |
| | | currentRow: null |
| | | }) |
| | | |
| | | const paramsObj = computed(() => { |
| | | return { |
| | | ...tableObject.params, |
| | | pageSize: tableObject.pageSize, |
| | | pageNo: tableObject.currentPage |
| | | } |
| | | }) |
| | | |
| | | watch( |
| | | () => tableObject.currentPage, |
| | | () => { |
| | | methods.getList() |
| | | } |
| | | ) |
| | | |
| | | watch( |
| | | () => tableObject.pageSize, |
| | | () => { |
| | | // 当前页不为1时,修改页数后会导致多次调用getList方法 |
| | | if (tableObject.currentPage === 1) { |
| | | methods.getList() |
| | | } else { |
| | | tableObject.currentPage = 1 |
| | | methods.getList() |
| | | } |
| | | } |
| | | ) |
| | | |
| | | // Table实例 |
| | | const tableRef = ref<typeof Table & TableExpose>() |
| | | |
| | | // ElTable实例 |
| | | const elTableRef = ref<ComponentRef<typeof ElTable>>() |
| | | |
| | | const register = (ref: typeof Table & TableExpose, elRef: ComponentRef<typeof ElTable>) => { |
| | | tableRef.value = ref |
| | | elTableRef.value = elRef |
| | | } |
| | | |
| | | const getTable = async () => { |
| | | await nextTick() |
| | | const table = unref(tableRef) |
| | | if (!table) { |
| | | console.error('The table is not registered. Please use the register method to register') |
| | | } |
| | | return table |
| | | } |
| | | |
| | | const delData = async (ids: string | number | string[] | number[]) => { |
| | | let idsLength = 1 |
| | | if (ids instanceof Array) { |
| | | idsLength = ids.length |
| | | await Promise.all( |
| | | ids.map(async (id: string | number) => { |
| | | await (config?.delListApi && config?.delListApi(id)) |
| | | }) |
| | | ) |
| | | } else { |
| | | await (config?.delListApi && config?.delListApi(ids)) |
| | | } |
| | | ElMessage.success(t('common.delSuccess')) |
| | | |
| | | // 计算出临界点 |
| | | tableObject.currentPage = |
| | | tableObject.total % tableObject.pageSize === idsLength || tableObject.pageSize === 1 |
| | | ? tableObject.currentPage > 1 |
| | | ? tableObject.currentPage - 1 |
| | | : tableObject.currentPage |
| | | : tableObject.currentPage |
| | | await methods.getList() |
| | | } |
| | | |
| | | const methods = { |
| | | getList: async () => { |
| | | tableObject.loading = true |
| | | const res = await config?.getListApi(unref(paramsObj)).finally(() => { |
| | | tableObject.loading = false |
| | | }) |
| | | if (res) { |
| | | tableObject.tableList = (res as unknown as ResponseType).list |
| | | tableObject.total = (res as unknown as ResponseType).total ?? 0 |
| | | } |
| | | }, |
| | | setProps: async (props: TableProps = {}) => { |
| | | const table = await getTable() |
| | | table?.setProps(props) |
| | | }, |
| | | setColumn: async (columnProps: TableSetPropsType[]) => { |
| | | const table = await getTable() |
| | | table?.setColumn(columnProps) |
| | | }, |
| | | getSelections: async () => { |
| | | const table = await getTable() |
| | | return (table?.selections || []) as T[] |
| | | }, |
| | | // 与Search组件结合 |
| | | setSearchParams: (data: Recordable) => { |
| | | tableObject.params = Object.assign(tableObject.params, { |
| | | pageSize: tableObject.pageSize, |
| | | pageNo: 1, |
| | | ...data |
| | | }) |
| | | // 页码不等于1时更新页码重新获取数据,页码等于1时重新获取数据 |
| | | if (tableObject.currentPage !== 1) { |
| | | tableObject.currentPage = 1 |
| | | } else { |
| | | methods.getList() |
| | | } |
| | | }, |
| | | // 删除数据 |
| | | delList: async ( |
| | | ids: string | number | string[] | number[], |
| | | multiple: boolean, |
| | | message = true |
| | | ) => { |
| | | const tableRef = await getTable() |
| | | if (multiple) { |
| | | if (!tableRef?.selections.length) { |
| | | ElMessage.warning(t('common.delNoData')) |
| | | return |
| | | } |
| | | } |
| | | if (message) { |
| | | ElMessageBox.confirm(t('common.delMessage'), t('common.confirmTitle'), { |
| | | confirmButtonText: t('common.ok'), |
| | | cancelButtonText: t('common.cancel'), |
| | | type: 'warning' |
| | | }).then(async () => { |
| | | await delData(ids) |
| | | }) |
| | | } else { |
| | | await delData(ids) |
| | | } |
| | | }, |
| | | // 导出列表 |
| | | exportList: async (fileName: string) => { |
| | | tableObject.exportLoading = true |
| | | ElMessageBox.confirm(t('common.exportMessage'), t('common.confirmTitle'), { |
| | | confirmButtonText: t('common.ok'), |
| | | cancelButtonText: t('common.cancel'), |
| | | type: 'warning' |
| | | }) |
| | | .then(async () => { |
| | | const res = await config?.exportListApi?.(unref(paramsObj) as unknown as T) |
| | | if (res) { |
| | | download.excel(res as unknown as Blob, fileName) |
| | | } |
| | | }) |
| | | .finally(() => { |
| | | tableObject.exportLoading = false |
| | | }) |
| | | } |
| | | } |
| | | |
| | | config?.props && methods.setProps(config.props) |
| | | |
| | | return { |
| | | register, |
| | | elTableRef, |
| | | tableObject, |
| | | methods, |
| | | // add by 芋艿:返回 tableMethods 属性,和 tableObject 更统一 |
| | | tableMethods: methods |
| | | } |
| | | } |
| New file |
| | |
| | | import { useTagsViewStoreWithOut } from '@/store/modules/tagsView' |
| | | import { RouteLocationNormalizedLoaded, useRouter } from 'vue-router' |
| | | import { computed, nextTick, unref } from 'vue' |
| | | |
| | | export const useTagsView = () => { |
| | | const tagsViewStore = useTagsViewStoreWithOut() |
| | | |
| | | const { replace, currentRoute } = useRouter() |
| | | |
| | | const selectedTag = computed(() => tagsViewStore.getSelectedTag) |
| | | |
| | | const closeAll = (callback?: Fn) => { |
| | | tagsViewStore.delAllViews() |
| | | callback?.() |
| | | } |
| | | |
| | | const closeLeft = (callback?: Fn) => { |
| | | tagsViewStore.delLeftViews(unref(selectedTag) as RouteLocationNormalizedLoaded) |
| | | callback?.() |
| | | } |
| | | |
| | | const closeRight = (callback?: Fn) => { |
| | | tagsViewStore.delRightViews(unref(selectedTag) as RouteLocationNormalizedLoaded) |
| | | callback?.() |
| | | } |
| | | |
| | | const closeOther = (callback?: Fn) => { |
| | | tagsViewStore.delOthersViews(unref(selectedTag) as RouteLocationNormalizedLoaded) |
| | | callback?.() |
| | | } |
| | | |
| | | const closeCurrent = (view?: RouteLocationNormalizedLoaded, callback?: Fn) => { |
| | | if (view?.meta?.affix) return |
| | | tagsViewStore.delView(view || unref(currentRoute)) |
| | | |
| | | callback?.() |
| | | } |
| | | |
| | | const refreshPage = async (view?: RouteLocationNormalizedLoaded, callback?: Fn) => { |
| | | tagsViewStore.delCachedView() |
| | | const { path, query } = view || unref(currentRoute) |
| | | await nextTick() |
| | | replace({ |
| | | path: '/redirect' + path, |
| | | query: query |
| | | }) |
| | | callback?.() |
| | | } |
| | | |
| | | const setTitle = (title: string, path?: string) => { |
| | | tagsViewStore.setTitle(title, path) |
| | | } |
| | | |
| | | return { |
| | | closeAll, |
| | | closeLeft, |
| | | closeRight, |
| | | closeOther, |
| | | closeCurrent, |
| | | refreshPage, |
| | | setTitle |
| | | } |
| | | } |
| New file |
| | |
| | | import { useTimeAgo as useTimeAgoCore, UseTimeAgoMessages } from '@vueuse/core' |
| | | import { useLocaleStoreWithOut } from '@/store/modules/locale' |
| | | |
| | | const TIME_AGO_MESSAGE_MAP: { |
| | | 'zh-CN': UseTimeAgoMessages |
| | | en: UseTimeAgoMessages |
| | | } = { |
| | | // @ts-ignore |
| | | 'zh-CN': { |
| | | justNow: '刚刚', |
| | | past: (n) => (n.match(/\d/) ? `${n}前` : n), |
| | | future: (n) => (n.match(/\d/) ? `${n}后` : n), |
| | | month: (n, past) => (n === 1 ? (past ? '上个月' : '下个月') : `${n} 个月`), |
| | | year: (n, past) => (n === 1 ? (past ? '去年' : '明年') : `${n} 年`), |
| | | day: (n, past) => (n === 1 ? (past ? '昨天' : '明天') : `${n} 天`), |
| | | week: (n, past) => (n === 1 ? (past ? '上周' : '下周') : `${n} 周`), |
| | | hour: (n) => `${n} 小时`, |
| | | minute: (n) => `${n} 分钟`, |
| | | second: (n) => `${n} 秒` |
| | | }, |
| | | // @ts-ignore |
| | | en: { |
| | | justNow: 'just now', |
| | | past: (n) => (n.match(/\d/) ? `${n} ago` : n), |
| | | future: (n) => (n.match(/\d/) ? `in ${n}` : n), |
| | | month: (n, past) => |
| | | n === 1 ? (past ? 'last month' : 'next month') : `${n} month${n > 1 ? 's' : ''}`, |
| | | year: (n, past) => |
| | | n === 1 ? (past ? 'last year' : 'next year') : `${n} year${n > 1 ? 's' : ''}`, |
| | | day: (n, past) => (n === 1 ? (past ? 'yesterday' : 'tomorrow') : `${n} day${n > 1 ? 's' : ''}`), |
| | | week: (n, past) => |
| | | n === 1 ? (past ? 'last week' : 'next week') : `${n} week${n > 1 ? 's' : ''}`, |
| | | hour: (n) => `${n} hour${n > 1 ? 's' : ''}`, |
| | | minute: (n) => `${n} minute${n > 1 ? 's' : ''}`, |
| | | second: (n) => `${n} second${n > 1 ? 's' : ''}` |
| | | } |
| | | } |
| | | |
| | | export const useTimeAgo = (time: Date | number | string) => { |
| | | const localeStore = useLocaleStoreWithOut() |
| | | |
| | | const currentLocale = computed(() => localeStore.getCurrentLocale) |
| | | |
| | | const timeAgo = useTimeAgoCore(time, { |
| | | messages: TIME_AGO_MESSAGE_MAP[unref(currentLocale).lang] |
| | | }) |
| | | |
| | | return timeAgo |
| | | } |
| New file |
| | |
| | | import { watch, ref } from 'vue' |
| | | import { isString } from '@/utils/is' |
| | | import { useAppStoreWithOut } from '@/store/modules/app' |
| | | |
| | | const appStore = useAppStoreWithOut() |
| | | |
| | | export const useTitle = (newTitle?: string) => { |
| | | const { t } = useI18n() |
| | | const title = ref( |
| | | newTitle ? `${appStore.getTitle} - ${t(newTitle as string)}` : appStore.getTitle |
| | | ) |
| | | |
| | | watch( |
| | | title, |
| | | (n, o) => { |
| | | if (isString(n) && n !== o && document) { |
| | | document.title = n |
| | | } |
| | | }, |
| | | { immediate: true } |
| | | ) |
| | | |
| | | return title |
| | | } |
| New file |
| | |
| | | import { useI18n } from '@/hooks/web/useI18n' |
| | | import { FormItemRule } from 'element-plus' |
| | | |
| | | const { t } = useI18n() |
| | | |
| | | interface LengthRange { |
| | | min: number |
| | | max: number |
| | | message?: string |
| | | } |
| | | |
| | | export const useValidator = () => { |
| | | const required = (message?: string): FormItemRule => { |
| | | return { |
| | | required: true, |
| | | message: message || t('common.required') |
| | | } |
| | | } |
| | | |
| | | const lengthRange = (options: LengthRange): FormItemRule => { |
| | | const { min, max, message } = options |
| | | |
| | | return { |
| | | min, |
| | | max, |
| | | message: message || t('common.lengthRange', { min, max }) |
| | | } |
| | | } |
| | | |
| | | const notSpace = (message?: string): FormItemRule => { |
| | | return { |
| | | validator: (_, val, callback) => { |
| | | if (val?.indexOf(' ') !== -1) { |
| | | callback(new Error(message || t('common.notSpace'))) |
| | | } else { |
| | | callback() |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | const notSpecialCharacters = (message?: string): FormItemRule => { |
| | | return { |
| | | validator: (_, val, callback) => { |
| | | if (/[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/gi.test(val)) { |
| | | callback(new Error(message || t('common.notSpecialCharacters'))) |
| | | } else { |
| | | callback() |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | return { |
| | | required, |
| | | lengthRange, |
| | | notSpace, |
| | | notSpecialCharacters |
| | | } |
| | | } |
| New file |
| | |
| | | const domSymbol = Symbol('watermark-dom') |
| | | |
| | | export function useWatermark(appendEl: HTMLElement | null = document.body) { |
| | | let func: Fn = () => {} |
| | | const id = domSymbol.toString() |
| | | const clear = () => { |
| | | const domId = document.getElementById(id) |
| | | if (domId) { |
| | | const el = appendEl |
| | | el && el.removeChild(domId) |
| | | } |
| | | window.removeEventListener('resize', func) |
| | | } |
| | | const createWatermark = (str: string) => { |
| | | clear() |
| | | |
| | | const can = document.createElement('canvas') |
| | | can.width = 300 |
| | | can.height = 240 |
| | | |
| | | const cans = can.getContext('2d') |
| | | if (cans) { |
| | | cans.rotate((-20 * Math.PI) / 120) |
| | | cans.font = '15px Vedana' |
| | | cans.fillStyle = 'rgba(0, 0, 0, 0.15)' |
| | | cans.textAlign = 'left' |
| | | cans.textBaseline = 'middle' |
| | | cans.fillText(str, can.width / 20, can.height) |
| | | } |
| | | |
| | | const div = document.createElement('div') |
| | | div.id = id |
| | | div.style.pointerEvents = 'none' |
| | | div.style.top = '0px' |
| | | div.style.left = '0px' |
| | | div.style.position = 'absolute' |
| | | div.style.zIndex = '100000000' |
| | | div.style.width = document.documentElement.clientWidth + 'px' |
| | | div.style.height = document.documentElement.clientHeight + 'px' |
| | | div.style.background = 'url(' + can.toDataURL('image/png') + ') left top repeat' |
| | | const el = appendEl |
| | | el && el.appendChild(div) |
| | | return id |
| | | } |
| | | |
| | | function setWatermark(str: string) { |
| | | createWatermark(str) |
| | | func = () => { |
| | | createWatermark(str) |
| | | } |
| | | window.addEventListener('resize', func) |
| | | } |
| | | |
| | | return { setWatermark, clear } |
| | | } |
| | |
| | | |
| | | // svg图标 |
| | | import 'virtual:svg-icons-register'; |
| | | import '@purge-icons/generated' |
| | | import ElementIcons from '@/plugins/svgicon'; |
| | | |
| | | // permission control |
| New file |
| | |
| | | // copy to vben-admin |
| | | |
| | | const toString = Object.prototype.toString |
| | | |
| | | export const is = (val: unknown, type: string) => { |
| | | return toString.call(val) === `[object ${type}]` |
| | | } |
| | | |
| | | export const isDef = <T = unknown>(val?: T): val is T => { |
| | | return typeof val !== 'undefined' |
| | | } |
| | | |
| | | export const isUnDef = <T = unknown>(val?: T): val is T => { |
| | | return !isDef(val) |
| | | } |
| | | |
| | | export const isObject = (val: any): val is Record<any, any> => { |
| | | return val !== null && is(val, 'Object') |
| | | } |
| | | |
| | | export const isEmpty = <T = unknown>(val: T): val is T => { |
| | | if (val === null) { |
| | | return true |
| | | } |
| | | if (isArray(val) || isString(val)) { |
| | | return val.length === 0 |
| | | } |
| | | |
| | | if (val instanceof Map || val instanceof Set) { |
| | | return val.size === 0 |
| | | } |
| | | |
| | | if (isObject(val)) { |
| | | return Object.keys(val).length === 0 |
| | | } |
| | | |
| | | return false |
| | | } |
| | | |
| | | export const isDate = (val: unknown): val is Date => { |
| | | return is(val, 'Date') |
| | | } |
| | | |
| | | export const isNull = (val: unknown): val is null => { |
| | | return val === null |
| | | } |
| | | |
| | | export const isNullAndUnDef = (val: unknown): val is null | undefined => { |
| | | return isUnDef(val) && isNull(val) |
| | | } |
| | | |
| | | export const isNullOrUnDef = (val: unknown): val is null | undefined => { |
| | | return isUnDef(val) || isNull(val) |
| | | } |
| | | |
| | | export const isNumber = (val: unknown): val is number => { |
| | | return is(val, 'Number') |
| | | } |
| | | |
| | | export const isPromise = <T = any>(val: unknown): val is Promise<T> => { |
| | | return is(val, 'Promise') && isObject(val) && isFunction(val.then) && isFunction(val.catch) |
| | | } |
| | | |
| | | export const isString = (val: unknown): val is string => { |
| | | return is(val, 'String') |
| | | } |
| | | |
| | | export const isFunction = (val: unknown): val is Function => { |
| | | return typeof val === 'function' |
| | | } |
| | | |
| | | export const isBoolean = (val: unknown): val is boolean => { |
| | | return is(val, 'Boolean') |
| | | } |
| | | |
| | | export const isRegExp = (val: unknown): val is RegExp => { |
| | | return is(val, 'RegExp') |
| | | } |
| | | |
| | | export const isArray = (val: any): val is Array<any> => { |
| | | return val && Array.isArray(val) |
| | | } |
| | | |
| | | export const isWindow = (val: any): val is Window => { |
| | | return typeof window !== 'undefined' && is(val, 'Window') |
| | | } |
| | | |
| | | export const isElement = (val: unknown): val is Element => { |
| | | return isObject(val) && !!val.tagName |
| | | } |
| | | |
| | | export const isMap = (val: unknown): val is Map<any, any> => { |
| | | return is(val, 'Map') |
| | | } |
| | | |
| | | export const isServer = typeof window === 'undefined' |
| | | |
| | | export const isClient = !isServer |
| | | |
| | | export const isUrl = (path: string): boolean => { |
| | | const reg = |
| | | /(((^https?:(?:\/\/)?)(?:[-:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&%@.\w_]*)#?(?:[\w]*))?)$/ |
| | | return reg.test(path) |
| | | } |
| | | |
| | | export const isDark = (): boolean => { |
| | | return window.matchMedia('(prefers-color-scheme: dark)').matches |
| | | } |
| | | |
| | | // 是否是图片链接 |
| | | export const isImgPath = (path: string): boolean => { |
| | | return /(https?:\/\/|data:image\/).*?\.(png|jpg|jpeg|gif|svg|webp|ico)/gi.test(path) |
| | | } |
| | | |
| | | export const isEmptyVal = (val: any): boolean => { |
| | | return val === '' || val === null || val === undefined |
| | | } |
| | |
| | | <template> |
| | | <div class="p-2"> |
| | | <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px"> |
| | | <Dialog :title="dialog.title" v-model="dialog.visible" width="600px" :draggable="draggable"> |
| | | <el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px" v-loading="formLoading"> |
| | | <el-form-item label="参数名称" prop="configName"> |
| | | <el-input v-model="formData.configName" placeholder="请输入参数名称"/> |
| | |
| | | <el-input v-model="formData.remark" type="textarea" placeholder="请输入内容"/> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="dialog-footer"> |
| | | <slot name="footer"> |
| | | <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> |
| | | <template #footer> |
| | | <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> |
| | | <el-button @click="dialog.visible = false">取 消</el-button> |
| | | </slot> |
| | | </div> |
| | | </el-dialog> |
| | | </template> |
| | | </Dialog> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | visible: false, |
| | | title: '' |
| | | }); |
| | | const resizable = ref(true) |
| | | const draggable = ref(true) |
| | | const isFullscreen = ref(false) |
| | | // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 |
| | | const formLoading = ref(false) |
| | | // 表单的类型:create - 新增;update - 修改 |
| | |
| | | <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" /> |
| | | </el-card> |
| | | <!-- 弹出的详细页面,增加和修改--> |
| | | <detail-form ref="detailFormRef" @success="getList"/> |
| | | <detail-form ref="detailFormRef" @success="getList" apped-to-body/> |
| | | </div> |
| | | </template> |
| | | |