!36 use unocss

* chore: update deps
* fix: dark
* fix: use table
* feat: 解决Form组件slot必须传递component字段才显示的问题
* style(TableColumn): fix Column settings container is too wide
* fix: eslint
* chore: update deps
* refactor: use setup
* docs: doc
* refactor: use unocss
pull/38/head
xingyu 2023-09-24 12:34:42 +00:00
parent 6b8325c4af
commit 2334fc31ab
58 changed files with 1536 additions and 2355 deletions

View File

@ -6,6 +6,7 @@
"stylelint.vscode-stylelint",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"usernamehw.errorlens",
"mrmlnc.vscode-less",
"lokalise.i18n-ally",
"redhat.vscode-yaml",

12
.vscode/settings.json vendored
View File

@ -66,6 +66,12 @@
"source.fixAll.eslint": true,
"source.organizeImports": false
},
"eslint.rules.customizations": [
{
"rule": "@stylistic/*",
"severity": "off"
}
],
"eslint.validate": [
"javascript",
"javascriptreact",
@ -82,7 +88,7 @@
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
@ -120,6 +126,7 @@
"i18n-ally.enabledFrameworks": ["vue", "react"],
"cSpell.words": [
"antd",
"antdv",
"antfu",
"antv",
"brotli",
@ -166,6 +173,9 @@
"*.env": "$(capture).env.*",
"package.json": ".hintrc,pnpm-lock.yaml,yarn.lock,LICENSE,README*,CHANGELOG*,CNAME,.gitattributes,.gitignore,prettier.config.js,stylelint.config.js,commitlint.config.js,.stylelintignore,.prettierignore,.gitpod.yml,.eslintrc.js,.eslintignore"
},
"eslint.codeAction.showDocumentation": {
"enable": true
},
"terminal.integrated.scrollback": 10000,
"nuxt.isNuxtApp": false
}

View File

@ -35,8 +35,8 @@
| [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.1.6 |
| [vueuse](https://vueuse.org/) | 常用工具集 | 10.4.1 |
| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.4.1 |
| [vue-router](https://router.vuejs.org/) | Vue 路由 | 4.2.4 |
| [unocss](https://uno.antfu.me/) | 原子 css | 0.55.7 |
| [vue-router](https://router.vuejs.org/) | Vue 路由 | 4.2.5 |
| [unocss](https://uno.antfu.me/) | 原子 css | 0.56.1 |
| [iconify](https://icon-sets.iconify.design/) | 在线图标库 | 3.1.1 |
- ![alt VbenAdmin](https://anncwb.github.io/anncwb/images/preview1.png)

View File

@ -76,7 +76,7 @@
"vue": "^3.3.4",
"vue-i18n": "^9.4.1",
"vue-json-pretty": "^2.2.4",
"vue-router": "^4.2.4",
"vue-router": "^4.2.5",
"vue-types": "^5.1.1",
"vuedraggable": "^4.1.0",
"xlsx": "^0.18.5"
@ -84,7 +84,7 @@
"devDependencies": {
"@commitlint/cli": "^17.7.1",
"@commitlint/config-conventional": "^17.7.0",
"@iconify/json": "^2.2.117",
"@iconify/json": "^2.2.119",
"@purge-icons/generated": "^0.9.0",
"@types/codemirror": "^5.60.10",
"@types/crypto-js": "^4.1.2",
@ -92,10 +92,10 @@
"@types/inquirer": "^9.0.3",
"@types/lodash-es": "^4.17.9",
"@types/node": "^20.6.0",
"@types/nprogress": "^0.2.0",
"@types/nprogress": "^0.2.1",
"@types/qs": "^6.9.8",
"@types/sortablejs": "^1.15.2",
"@unocss/eslint-config": "^0.56.0",
"@unocss/eslint-config": "^0.56.1",
"@vitejs/plugin-vue": "4.3.4",
"@vitejs/plugin-vue-jsx": "^3.0.2",
"@vue/compiler-sfc": "^3.3.4",
@ -104,7 +104,7 @@
"cz-git": "^1.7.1",
"czg": "^1.7.1",
"dotenv": "^16.3.1",
"eslint": "^8.49.0",
"eslint": "^8.50.0",
"esno": "^0.17.0",
"fs-extra": "^11.1.1",
"husky": "^8.0.3",
@ -117,7 +117,7 @@
"postcss-less": "^6.0.0",
"prettier": "^3.0.3",
"rimraf": "^5.0.1",
"rollup": "^3.29.2",
"rollup": "^3.29.3",
"rollup-plugin-visualizer": "^5.9.2",
"stylelint": "^15.10.3",
"stylelint-config-recess-order": "^4.3.0",
@ -126,9 +126,9 @@
"stylelint-config-standard": "^34.0.0",
"stylelint-order": "^6.0.3",
"stylelint-prettier": "^4.0.2",
"terser": "^5.19.4",
"terser": "^5.20.0",
"typescript": "^5.2.2",
"unocss": "^0.56.0",
"unocss": "^0.56.1",
"vite": "^4.4.9",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-mkcert": "^1.16.0",
@ -138,7 +138,7 @@
"vite-plugin-svg-icons": "^2.0.1",
"vite-vue-plugin-html": "^1.0.2",
"vue-eslint-parser": "^9.3.1",
"vue-tsc": "^1.8.11"
"vue-tsc": "^1.8.13"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@ const { t } = useI18n()
</script>
<template>
<div :class="`${prefixCls}`">
<div class="relative h-10 flex flex-shrink-0 items-center border-t-1 rounded-bl-2xl px-4 py-0 dark:border-light-100">
<AppSearchKeyItem :class="`${prefixCls}-item`" icon="ant-design:enter-outlined" />
<span>{{ t('component.app.toSearch') }}</span>
<AppSearchKeyItem :class="`${prefixCls}-item`" icon="ion:arrow-up-outline" />
@ -23,18 +23,6 @@ const { t } = useI18n()
@prefix-cls: ~'@{namespace}-app-search-footer';
.@{prefix-cls} {
position: relative;
display: flex;
flex-shrink: 0;
align-items: center;
height: 44px;
padding: 0 16px;
font-size: 12px;
// color: var(--text-color);
// background-color: var(--component-background);
border-top: 1px solid var(--border-color);
border-radius: 0 0 16px 16px;
&-item {
display: flex;
align-items: center;

View File

@ -6,7 +6,6 @@ import { InfoCircleOutlined } from '@ant-design/icons-vue'
import { getPopupContainer } from '@/utils'
import { isArray, isString } from '@/utils/is'
import { getSlot } from '@/utils/helper/tsxHelper'
import { useDesign } from '@/hooks/web/useDesign'
const props = {
/**
@ -44,7 +43,6 @@ export default defineComponent({
components: { Tooltip },
props,
setup(props, { slots }) {
const { prefixCls } = useDesign('basic-help')
const getTooltipStyle = computed((): CSSProperties => ({ color: props.color, fontSize: props.fontSize }))
@ -74,14 +72,14 @@ export default defineComponent({
return () => {
return (
<Tooltip
overlayClassName={`${prefixCls}__wrap`}
overlayClassName="overlay-class"
title={<div style={unref(getTooltipStyle)}>{renderTitle()}</div>}
autoAdjustOverflow={true}
overlayStyle={unref(getOverlayStyle)}
placement={props.placement as 'right'}
getPopupContainer={() => getPopupContainer()}
>
<span class={prefixCls}>{getSlot(slots) || <InfoCircleOutlined />}</span>
<span class="ml-1.5 inline-block cursor-pointer text-sm">{getSlot(slots) || <InfoCircleOutlined />}</span>
</Tooltip>
)
}
@ -89,19 +87,10 @@ export default defineComponent({
})
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-help';
.@{prefix-cls} {
display: inline-block;
margin-left: 6px;
font-size: 14px;
cursor: pointer;
&__wrap {
p {
margin-bottom: 0;
}
<style>
.overlay-class {
p {
margin-bottom: 0;
}
}
</style>

View File

@ -31,7 +31,7 @@ const props = {
const ItemContent: FunctionalComponent<ItemContentProps> = (props) => {
const { item } = props
return (
<span style="display: inline-block; width: 100%; " class="px-4" onClick={props.handler.bind(null, item)}>
<span class="inline-block w-full px-4" onClick={props.handler.bind(null, item)}>
{props.showIcon && item.icon && <Icon class="mr-2" icon={item.icon} />}
<span>{item.label}</span>
</span>

View File

@ -3,7 +3,6 @@ import { ref, watch } from 'vue'
import { Input } from 'ant-design-vue'
import CronTabModal from './CronTabModal.vue'
import { cronEmits, cronProps } from './cron.data'
import { useDesign } from '@/hooks/web/useDesign'
import { useModal } from '@/components/Modal'
import { propTypes } from '@/utils/propTypes'
import { Icon } from '@/components/Icon'
@ -14,7 +13,6 @@ const props = defineProps({
exeStartTime: propTypes.oneOfType([propTypes.number, propTypes.string, propTypes.object]).def(0),
})
const emit = defineEmits([...cronEmits])
const { prefixCls } = useDesign('cron-input')
const [registerModal, { openModal }] = useModal()
const editCronValue = ref(props.value)
@ -37,11 +35,11 @@ function showConfigModal() {
</script>
<template>
<div :class="`${prefixCls}`">
<div>
<Input v-model:value="editCronValue" :placeholder="placeholder" :disabled="disabled">
<template #addonAfter>
<a class="open-btn" :disabled="disabled ? 'disabled' : null" @click="showConfigModal">
<Icon icon="ant-design:setting-outlined" />
<a class="cursor-pointer" :disabled="disabled ? 'disabled' : null" @click="showConfigModal">
<Icon class="relative right-0.5 top-0.25" icon="ant-design:setting-outlined" />
<span>选择</span>
</a>
</template>
@ -56,19 +54,3 @@ function showConfigModal() {
/>
</div>
</template>
<style lang="less">
@prefix-cls: ~'@{namespace}-cron-input';
.@{prefix-cls} {
a.open-btn {
cursor: pointer;
.app-iconify {
position: relative;
top: 1px;
right: 2px;
}
}
}
</style>

View File

@ -1,6 +1,7 @@
<script lang="ts" setup>
import type { CSSProperties } from 'vue'
import { computed, ref, unref, watch, watchEffect } from 'vue'
import { Avatar } from 'ant-design-vue'
import CopperModal from './CopperModal.vue'
import { useDesign } from '@/hooks/web/useDesign'
import { useModal } from '@/components/Modal'
@ -65,9 +66,9 @@ defineExpose({ openModal: openModal.bind(null, true), closeModal })
<div :class="`${prefixCls}-image-mask`" :style="getImageWrapperStyle">
<Icon icon="ant-design:cloud-upload-outlined" :size="getIconWidth" :style="getImageWrapperStyle" color="#d6d6d6" />
</div>
<img v-if="sourceValue" :src="sourceValue" alt="avatar">
<Avatar v-if="sourceValue" :size="{ xs: 24, sm: 32, md: 40, lg: 64, xl: 80, xxl: 100 }" :src="sourceValue" alt="avatar" />
</div>
<a-button v-if="showBtn" :class="`${prefixCls}-upload-btn`" v-bind="btnProps" @click="openModal">
<a-button v-if="showBtn" class="mx-auto my-2.5" v-bind="btnProps" @click="openModal">
{{ btnText ? btnText : t('component.cropper.selectImage') }}
</a-button>
@ -113,9 +114,5 @@ defineExpose({ openModal: openModal.bind(null, true), closeModal })
&-image-mask:hover {
opacity: 40;
}
&-upload-btn {
margin: 10px auto;
}
}
</style>

View File

@ -2,7 +2,6 @@
import type { CSSProperties } from 'vue'
import { computed } from 'vue'
import { footerProps } from '../props'
import { useDesign } from '@/hooks/web/useDesign'
defineOptions({ name: 'BasicDrawerFooter' })
@ -15,8 +14,6 @@ const props = defineProps({
})
const emit = defineEmits(['ok', 'close'])
const { prefixCls } = useDesign('basic-drawer-footer')
const getStyle = computed((): CSSProperties => {
const heightStr = `${props.height}`
return {
@ -35,14 +32,21 @@ function handleClose() {
</script>
<template>
<div v-if="showFooter || $slots.footer" :class="prefixCls" :style="getStyle">
<div
v-if="showFooter || $slots.footer"
class="absolute bottom-0 w-full border-t-1 border-light-200 bg-white pl-5 pr-3 dark:bg-dark"
:style="getStyle"
>
<template v-if="!$slots.footer">
<slot name="insertFooter" />
<a-button v-if="showCancelBtn" v-bind="cancelButtonProps" class="mr-2" @click="handleClose">
{{ cancelText }}
</a-button>
<slot name="centerFooter" />
<a-button v-if="showOkBtn" :type="okType" v-bind="okButtonProps" class="mr-2" :loading="confirmLoading" @click="handleOk">
<a-button
v-if="showOkBtn" :type="okType" v-bind="okButtonProps" class="mr-2" :loading="confirmLoading"
@click="handleOk"
>
{{ okText }}
</a-button>
<slot name="appendFooter" />
@ -53,21 +57,3 @@ function handleClose() {
</template>
</div>
</template>
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-drawer-footer';
@footer-height: 60px;
.@{prefix-cls} {
position: absolute;
bottom: 0;
width: 100%;
padding: 0 12px 0 20px;
text-align: right;
background-color: var(--component-background);
border-top: 1px solid var(--border-color);
> * {
margin-right: 8px;
}
}
</style>

View File

@ -1,7 +1,6 @@
<script lang="ts" setup>
import { ArrowLeftOutlined } from '@ant-design/icons-vue'
import { BasicTitle } from '@/components/Basic'
import { useDesign } from '@/hooks/web/useDesign'
import { propTypes } from '@/utils/propTypes'
defineOptions({ name: 'BasicDrawerHeader' })
@ -13,52 +12,27 @@ defineProps({
})
const emit = defineEmits(['close'])
const { prefixCls } = useDesign('basic-drawer-header')
function handleClose() {
emit('close')
}
</script>
<template>
<BasicTitle v-if="!isDetail" :class="prefixCls">
<BasicTitle v-if="!isDetail" class="h-full flex items-center">
<slot name="title" />
{{ !$slots.title ? title : '' }}
</BasicTitle>
<div v-else :class="[prefixCls, `${prefixCls}--detail`]">
<span :class="`${prefixCls}__twrap`">
<div v-else>
<span class="flex-1">
<span v-if="showDetailBack" @click="handleClose">
<ArrowLeftOutlined :class="`${prefixCls}__back`" />
<ArrowLeftOutlined class="cursor-pointer px-3" />
</span>
<span v-if="title">{{ title }}</span>
</span>
<span :class="`${prefixCls}__toolbar`">
<span class="pr-12.5">
<slot name="titleToolbar" />
</span>
</div>
</template>
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-drawer-header';
@footer-height: 60px;
.@{prefix-cls} {
display: flex;
align-items: center;
height: 100%;
&__back {
padding: 0 12px;
cursor: pointer;
}
&__twrap {
flex: 1;
}
&__toolbar {
padding-right: 50px;
}
}
</style>

View File

@ -280,11 +280,11 @@ function getFileName(path) {
<Upload
:headers="headers"
:multiple="multiple"
:action="uploadUrl as any"
:action="uploadUrl"
:file-list="fileList"
:disabled="disabled"
v-bind="bindProps"
@remove="onRemove as any"
@remove="onRemove"
@change="onFileChange"
@preview="onFilePreview"
>
@ -305,7 +305,6 @@ function getFileName(path) {
</template>
<style lang="less">
//noinspection LessUnresolvedVariable
@prefix-cls: ~'@{namespace}-upload';
.@{prefix-cls} {
@ -323,12 +322,9 @@ function getFileName(path) {
}
}
/* update-begin-author:taoyan date:2022-5-24 for:VUEN-1093详情界面 图片下载按钮显示不全 */
.upload-download-handler {
right: 6px !important;
}
/* update-end-author:taoyan date:2022-5-24 for:VUEN-1093详情界面 图片下载按钮显示不全 */
}
.ant-upload-list-item {

View File

@ -140,7 +140,8 @@ export default defineComponent({
const { rulesMessageJoinLabel: globalRulesMessageJoinLabel } = props.formProps
const joinLabel = Reflect.has(props.schema, 'rulesMessageJoinLabel') ? rulesMessageJoinLabel : globalRulesMessageJoinLabel
const defaultMsg = `${createPlaceholderMessage(component)}${joinLabel ? label : ''}`
const assertLabel = joinLabel ? label : ''
const defaultMsg = component ? createPlaceholderMessage(component) + assertLabel : assertLabel
function validator(rule: any, value: any) {
const msg = rule.message || defaultMsg
@ -354,8 +355,8 @@ export default defineComponent({
}
return () => {
const { colProps = {}, colSlot, renderColContent, component } = props.schema
if (!componentMap.has(component))
const { colProps = {}, colSlot, renderColContent, component, slot } = props.schema
if (!componentMap.has(component) && !slot)
return null
const { baseColProps = {} } = props.formProps

View File

@ -119,7 +119,7 @@ export default defineComponent({
<template>
<div class="v-form-container">
<Form ref="eFormModel" class="v-form-model" :model="formModel" v-bind="formModelProps">
<Form ref="eFormModel" class="overflow-hidden" :model="formModel" v-bind="formModelProps">
<Row>
<FormRender
v-for="(schema, index) of noHiddenList"
@ -143,9 +143,3 @@ export default defineComponent({
</Form>
</div>
</template>
<style lang="less" scoped>
.v-form-model {
overflow: hidden;
}
</style>

View File

@ -1,22 +1,9 @@
<!--
* @Description: 组件属性控件
-->
<script lang="ts">
import {
Checkbox,
Col,
Empty,
Form,
FormItem,
Input,
InputNumber,
RadioGroup,
Row,
Select,
Switch,
} from 'ant-design-vue'
import RadioButtonGroup from '/@/components/Form/src/components/RadioButtonGroup.vue'
import { computed, defineComponent, ref, watch } from 'vue'
<script lang="ts" setup>
import { Checkbox, Col, Empty, Form, FormItem, Select } from 'ant-design-vue'
import { computed, ref, watch } from 'vue'
import { useFormDesignState } from '../../../hooks/useFormDesignState'
import {
baseComponentAttrs,
@ -28,145 +15,116 @@ import { formItemsForEach, remove } from '../../../utils'
import type { IBaseFormAttrs } from '../config/formItemPropsConfig'
import FormOptions from './FormOptions.vue'
export default defineComponent({
name: 'ComponentProps',
components: {
FormOptions,
Empty,
Input,
Form,
FormItem,
Switch,
Checkbox,
Select,
InputNumber,
RadioGroup,
RadioButtonGroup,
Col,
Row,
const { formConfig } = useFormDesignState()
// compuated
const allOptions = ref([] as Omit<IBaseFormAttrs, 'tag'>[])
function showControlAttrs(includes: string[] | undefined) {
if (!includes)
return true
return includes.includes(formConfig.value.currentItem!.component)
}
if (formConfig.value.currentItem) {
formConfig.value.currentItem.componentProps
= formConfig.value.currentItem.componentProps || {}
}
watch(
() => formConfig.value.currentItem?.field,
(_newValue, oldValue) => {
formConfig.value.schemas
&& formItemsForEach(formConfig.value.schemas, (item) => {
if (item.link) {
const index = item.link.findIndex(linkItem => linkItem === oldValue)
index !== -1 && remove(item.link, index)
}
})
},
setup() {
// compuated
)
const allOptions = ref([] as Omit<IBaseFormAttrs, 'tag'>[])
const showControlAttrs = (includes: string[] | undefined) => {
if (!includes)
return true
return includes.includes(formConfig.value.currentItem!.component)
}
watch(
() => formConfig.value.currentItem && formConfig.value.currentItem.component,
() => {
allOptions.value = []
baseComponentControlAttrs.forEach((item) => {
item.category = 'control'
if (!item.includes) {
// include
const { formConfig } = useFormDesignState()
allOptions.value.push(item)
}
else if (item.includes.includes(formConfig.value.currentItem!.component)) {
// include
allOptions.value.push(item)
}
})
if (formConfig.value.currentItem) {
formConfig.value.currentItem.componentProps
= formConfig.value.currentItem.componentProps || {}
}
watch(
() => formConfig.value.currentItem?.field,
(_newValue, oldValue) => {
formConfig.value.schemas
&& formItemsForEach(formConfig.value.schemas, (item) => {
if (item.link) {
const index = item.link.findIndex(linkItem => linkItem === oldValue)
index !== -1 && remove(item.link, index)
}
})
},
)
watch(
() => formConfig.value.currentItem && formConfig.value.currentItem.component,
() => {
allOptions.value = []
baseComponentControlAttrs.forEach((item) => {
item.category = 'control'
if (!item.includes) {
// include
baseComponentCommonAttrs.forEach((item) => {
item.category = 'input'
if (item.includes) {
if (item.includes.includes(formConfig.value.currentItem!.component))
allOptions.value.push(item)
}
else if (item.exclude) {
if (!item.exclude.includes(formConfig.value.currentItem!.component))
allOptions.value.push(item)
}
else {
allOptions.value.push(item)
}
})
baseComponentAttrs[formConfig.value.currentItem!.component]
&& baseComponentAttrs[formConfig.value.currentItem!.component].forEach(async (item) => {
if (item.component) {
if (['Switch', 'Checkbox', 'Radio'].includes(item.component)) {
item.category = 'control'
allOptions.value.push(item)
}
else if (item.includes.includes(formConfig.value.currentItem!.component)) {
// include
allOptions.value.push(item)
}
})
baseComponentCommonAttrs.forEach((item) => {
item.category = 'input'
if (item.includes) {
if (item.includes.includes(formConfig.value.currentItem!.component))
allOptions.value.push(item)
}
else if (item.exclude) {
if (!item.exclude.includes(formConfig.value.currentItem!.component))
allOptions.value.push(item)
}
else {
item.category = 'input'
allOptions.value.push(item)
}
})
baseComponentAttrs[formConfig.value.currentItem!.component]
&& baseComponentAttrs[formConfig.value.currentItem!.component].forEach(async (item) => {
if (item.component) {
if (['Switch', 'Checkbox', 'Radio'].includes(item.component)) {
item.category = 'control'
allOptions.value.push(item)
}
else {
item.category = 'input'
allOptions.value.push(item)
}
}
})
},
{
immediate: true,
},
)
//
const controlOptions = computed(() => {
return allOptions.value.filter((item) => {
return item.category === 'control'
}
})
})
//
const inputOptions = computed(() => {
return allOptions.value.filter((item) => {
return item.category === 'input'
})
})
watch(
() => formConfig.value.currentItem!.componentProps,
() => {
const func = componentPropsFuncs[formConfig.value.currentItem!.component]
if (func)
func(formConfig.value.currentItem!.componentProps, allOptions.value)
},
{
immediate: true,
deep: true,
},
)
const linkOptions = computed(() => {
return (
formConfig.value.schemas
&& formConfig.value.schemas
.filter(item => item.key !== formConfig.value.currentItem!.key)
.map(({ label, field }) => ({ label: `${label}/${field}`, value: field }))
)
})
return {
formConfig,
showControlAttrs,
linkOptions,
controlOptions,
inputOptions,
}
},
{
immediate: true,
},
)
//
const controlOptions = computed(() => {
return allOptions.value.filter((item) => {
return item.category === 'control'
})
})
//
const inputOptions = computed(() => {
return allOptions.value.filter((item) => {
return item.category === 'input'
})
})
watch(
() => formConfig.value.currentItem!.componentProps,
() => {
const func = componentPropsFuncs[formConfig.value.currentItem!.component]
if (func)
func(formConfig.value.currentItem!.componentProps, allOptions.value)
},
{
immediate: true,
deep: true,
},
)
const linkOptions = computed(() => {
return (
formConfig.value.schemas
&& formConfig.value.schemas
.filter(item => item.key !== formConfig.value.currentItem!.key)
.map(({ label, field }) => ({ label: `${label}/${field}`, value: field }))
)
})
</script>
@ -184,27 +142,20 @@ export default defineComponent({
<div v-if="item.children">
<component
v-bind="child.componentProps"
:is="child.component"
v-for="(child, index) of item.children"
:key="index"
v-model:value="formConfig.currentItem.componentProps[item.name][index]"
v-bind="child.componentProps" :is="child.component" v-for="(child, index) of item.children"
:key="index" v-model:value="formConfig.currentItem.componentProps[item.name][index]"
/>
</div>
<!-- 如果不是数组则正常处理属性值 -->
<component
v-bind="item.componentProps"
:is="item.component"
v-else
v-model:value="formConfig.currentItem.componentProps[item.name]"
class="component-prop"
v-bind="item.componentProps" :is="item.component" v-else
v-model:value="formConfig.currentItem.componentProps[item.name]" class="component-prop"
/>
</FormItem>
<FormItem label="控制属性">
<Col v-for="item in controlOptions" :key="item.name">
<Checkbox
v-if="showControlAttrs(item.includes)"
v-bind="item.componentProps"
v-if="showControlAttrs(item.includes)" v-bind="item.componentProps"
v-model:checked="formConfig.currentItem.componentProps[item.name]"
>
{{ item.label }}
@ -213,25 +164,19 @@ export default defineComponent({
</FormItem>
</div>
<FormItem label="关联字段">
<Select
v-model:value="formConfig.currentItem.link"
mode="multiple"
:options="linkOptions"
/>
<Select v-model:value="formConfig.currentItem.link" mode="multiple" :options="linkOptions" />
</FormItem>
<FormItem
v-if="
[
'Select',
'CheckboxGroup',
'RadioGroup',
'TreeSelect',
'Cascader',
'AutoComplete',
].includes(formConfig.currentItem.component)
"
label="选项"
v-if="[
'Select',
'CheckboxGroup',
'RadioGroup',
'TreeSelect',
'Cascader',
'AutoComplete',
].includes(formConfig.currentItem.component)
" label="选项"
>
<FormOptions />
</FormItem>

View File

@ -1,45 +1,20 @@
<!--
* @Description: 表单项属性
-->
<script lang="ts">
import { defineComponent } from 'vue'
import { Checkbox, Empty, Form, FormItem, Input, Select, Slider, Switch } from 'ant-design-vue'
<script lang="ts" setup>
import { Empty, Form, FormItem } from 'ant-design-vue'
import { isArray } from 'lodash-es'
import { baseItemColumnProps } from '../config/formItemPropsConfig'
import { useFormDesignState } from '../../../hooks/useFormDesignState'
import RuleProps from './RuleProps.vue'
export default defineComponent({
name: 'FormItemProps',
components: {
RuleProps,
Empty,
Input,
Form,
FormItem,
Switch,
Checkbox,
Select,
Slider,
},
// props: {} as PropsOptions,
const { formConfig } = useFormDesignState()
function showProps(exclude: string[] | undefined) {
if (!exclude)
return true
setup() {
const { formConfig } = useFormDesignState()
const showProps = (exclude: string[] | undefined) => {
if (!exclude)
return true
return isArray(exclude) ? !exclude.includes(formConfig.value.currentItem!.component) : true
}
return {
baseItemColumnProps,
formConfig,
showProps,
}
},
})
return isArray(exclude) ? !exclude.includes(formConfig.value.currentItem!.component) : true
}
</script>
<template>
@ -50,11 +25,8 @@ export default defineComponent({
<div v-for="item of baseItemColumnProps" :key="item.name">
<FormItem v-if="showProps(item.exclude)" :label="item.label">
<component
v-bind="item.componentProps"
:is="item.component"
v-if="formConfig.currentItem.colProps"
v-model:value="formConfig.currentItem.colProps[item.name]"
class="component-props"
v-bind="item.componentProps" :is="item.component" v-if="formConfig.currentItem.colProps"
v-model:value="formConfig.currentItem.colProps[item.name]" class="component-props"
/>
</FormItem>
</div>

View File

@ -1,8 +1,8 @@
<!--
* @Description: 表单项属性控件属性面板
-->
<script lang="ts">
import { computed, defineComponent, watch } from 'vue'
<script lang="ts" setup>
import { computed, watch } from 'vue'
import {
Checkbox,
Col,
@ -10,9 +10,6 @@ import {
Form,
FormItem,
Input,
RadioGroup,
Select,
Slider,
Switch,
} from 'ant-design-vue'
import { isArray } from 'lodash-es'
@ -26,61 +23,32 @@ import {
import { useFormDesignState } from '../../../hooks/useFormDesignState'
import RuleProps from './RuleProps.vue'
export default defineComponent({
name: 'FormItemProps',
components: {
RuleProps,
Empty,
Input,
Form,
FormItem,
Switch,
Checkbox,
Select,
Slider,
Col,
RadioGroup,
},
// props: {} as PropsOptions,
const { formConfig } = useFormDesignState()
setup() {
const { formConfig } = useFormDesignState()
watch(
() => formConfig.value,
() => {
if (formConfig.value.currentItem) {
formConfig.value.currentItem.itemProps = formConfig.value.currentItem.itemProps || {}
formConfig.value.currentItem.itemProps.labelCol
watch(
() => formConfig.value,
() => {
if (formConfig.value.currentItem) {
formConfig.value.currentItem.itemProps = formConfig.value.currentItem.itemProps || {}
formConfig.value.currentItem.itemProps.labelCol
= formConfig.value.currentItem.itemProps.labelCol || {}
formConfig.value.currentItem.itemProps.wrapperCol
formConfig.value.currentItem.itemProps.wrapperCol
= formConfig.value.currentItem.itemProps.wrapperCol || {}
}
},
{ deep: true, immediate: true },
)
const showProps = (exclude: string[] | undefined) => {
if (!exclude)
return true
return isArray(exclude) ? !exclude.includes(formConfig.value.currentItem!.component) : true
}
const controlPropsList = computed(() => {
return baseFormItemControlAttrs.filter((item) => {
return showProps(item.exclude)
})
})
return {
baseFormItemProps,
advanceFormItemProps,
advanceFormItemColProps,
formConfig,
controlPropsList,
showProps,
}
},
{ deep: true, immediate: true },
)
function showProps(exclude: string[] | undefined) {
if (!exclude)
return true
return isArray(exclude) ? !exclude.includes(formConfig.value.currentItem!.component) : true
}
const controlPropsList = computed(() => {
return baseFormItemControlAttrs.filter((item) => {
return showProps(item.exclude)
})
})
</script>

View File

@ -1,42 +1,29 @@
<!--
* @Description: 拖拽节点控件
-->
<script lang="ts">
<script lang="ts" setup>
import type { PropType } from 'vue'
import { defineComponent, reactive, toRefs } from 'vue'
import type { IVFormComponent } from '../../../typings/v-form-component'
import { useFormDesignState } from '../../../hooks/useFormDesignState'
import VFormItem from '../../VFormItem/index.vue'
import FormNodeOperate from './FormNodeOperate.vue'
// import VFormItem from '../../VFormItem/vFormItem.vue';
export default defineComponent({
name: 'FormNode',
components: {
VFormItem,
FormNodeOperate,
},
props: {
const props = defineProps(
{
schema: {
type: Object as PropType<IVFormComponent>,
required: true,
},
},
},
setup(props) {
const { formConfig, formDesignMethods } = useFormDesignState()
const state = reactive({})
// formDesignMethods
const handleSelectItem = () => {
// formDesignMethods
formDesignMethods.handleSetSelectItem(props.schema)
}
return {
...toRefs(state),
handleSelectItem,
formConfig,
}
},
})
)
const { formConfig, formDesignMethods } = useFormDesignState()
// formDesignMethods
function handleSelectItem() {
// formDesignMethods
formDesignMethods.handleSetSelectItem(props.schema)
}
</script>
<template>

View File

@ -52,10 +52,10 @@ function deleteGridOptions(index: number) {
</div>
<div v-else>
<div v-for="(item, index) of formConfig.currentItem!.componentProps![key]" :key="index">
<div class="options-box">
<div class="mb-1.5 flex items-center">
<Input v-model:value="item.label" />
<Input v-model:value="item.value" class="options-value" />
<a class="options-delete" @click="deleteOptions(index)">
<Input v-model:value="item.value" class="mx-2" />
<a class="h-7.5 w-7.5 rounded-full bg-light-50 text-center text-gray-500 hover:bg-red-500" @click="deleteOptions(index)">
<Icon icon="ant-design:delete-outlined" />
</a>
</div>
@ -67,30 +67,3 @@ function deleteGridOptions(index: number) {
</div>
</div>
</template>
<style lang="less" scoped>
.options-box {
display: flex;
align-items: center;
margin-bottom: 5px;
.options-value {
margin: 0 8px;
}
.options-delete {
flex-shrink: 0;
width: 30px;
height: 30px;
line-height: 30px;
color: #666;
text-align: center;
background: #f5f5f5;
border-radius: 50%;
&:hover {
background: #ff4d4f;
}
}
}
</style>

View File

@ -109,7 +109,7 @@ defineExpose({ showModal })
取消
</a-button>
<Upload
class="upload-button"
class="mx-2.5"
:before-upload="beforeUpload"
:show-upload-list="false"
accept="application/json"
@ -124,9 +124,3 @@ defineExpose({ showModal })
</template>
</Modal>
</template>
<style lang="less" scoped>
.upload-button {
margin: 0 10px;
}
</style>

View File

@ -3,9 +3,9 @@
* 千万不要在template下面的第一行加注释因为这里拖动的第一个元素
-->
<script lang="ts">
<script lang="ts" setup>
import type { PropType } from 'vue'
import { computed, defineComponent, reactive, toRefs } from 'vue'
import { computed } from 'vue'
import draggable from 'vuedraggable'
import { Col, Row } from 'ant-design-vue'
import { useFormDesignState } from '../../../hooks/useFormDesignState'
@ -13,16 +13,8 @@ import type { IVFormComponent } from '../../../typings/v-form-component'
import FormNode from './FormNode.vue'
import FormNodeOperate from './FormNodeOperate.vue'
export default defineComponent({
name: 'LayoutItem',
components: {
FormNode,
FormNodeOperate,
Draggable: draggable,
Row,
Col,
},
props: {
const props = defineProps(
{
schema: {
type: Object as PropType<IVFormComponent>,
required: true,
@ -32,33 +24,23 @@ export default defineComponent({
required: true,
},
},
emits: ['dragStart', 'handleColAdd', 'handle-copy', 'handle-delete'],
setup(props) {
const {
formDesignMethods: { handleSetSelectItem },
formConfig,
} = useFormDesignState()
const state = reactive({})
const colPropsComputed = computed(() => {
const { colProps = {} } = props.schema
return colProps
})
)
const list1 = computed(() => props.schema.columns)
const emit = defineEmits(['dragStart', 'handleColAdd', 'handle-copy', 'handle-delete'])
// AColdiv
const layoutTag = computed(() => {
return formConfig.value.layout === 'horizontal' ? 'Col' : 'div'
})
const Draggable = draggable
return {
...toRefs(state),
colPropsComputed,
handleSetSelectItem,
layoutTag,
list1,
}
},
const { formDesignMethods: { handleSetSelectItem }, formConfig } = useFormDesignState()
const colPropsComputed = computed(() => {
const { colProps = {} } = props.schema
return colProps
})
const list1 = computed(() => props.schema.columns)
// AColdiv
const layoutTag = computed(() => {
return formConfig.value.layout === 'horizontal' ? 'Col' : 'div'
})
</script>
@ -87,8 +69,8 @@ export default defineComponent({
class="drag-move"
:schema="element"
:current-item="currentItem"
@handle-copy="$emit('handle-copy')"
@handle-delete="$emit('handle-delete')"
@handle-copy="emit('handle-copy')"
@handle-delete="emit('handle-delete')"
/>
</template>
</Draggable>
@ -102,8 +84,8 @@ export default defineComponent({
:key="schema.key"
:schema="schema"
:current-item="currentItem"
@handle-copy="$emit('handle-copy')"
@handle-delete="$emit('handle-delete')"
@handle-copy="emit('handle-copy')"
@handle-delete="emit('handle-delete')"
/>
</Col>
</template>

View File

@ -1,16 +1,12 @@
<script lang="ts">
import { defineComponent, reactive, ref, toRefs, unref } from 'vue'
<script lang="ts" setup>
import { ref, unref } from 'vue'
import { CodeEditor, MODE } from '@/components/CodeEditor'
import { useCopyToClipboard } from '@/hooks/web/useCopyToClipboard'
import { useMessage } from '@/hooks/web/useMessage'
export default defineComponent({
name: 'PreviewCode',
components: {
CodeEditor,
},
props: {
const props = defineProps(
{
fileFormat: {
type: String,
default: 'json',
@ -20,51 +16,37 @@ export default defineComponent({
default: '',
},
},
setup(props) {
const state = reactive({
open: false,
})
)
const myEditor = ref(null)
const myEditor = ref(null)
const exportData = (data: string, fileName = `file.${props.fileFormat}`) => {
let content = 'data:text/csv;charset=utf-8,'
content += data
const encodedUri = encodeURI(content)
const actions = document.createElement('a')
actions.setAttribute('href', encodedUri)
actions.setAttribute('download', fileName)
actions.click()
}
function exportData(data: string, fileName = `file.${props.fileFormat}`) {
let content = 'data:text/csv;charset=utf-8,'
content += data
const encodedUri = encodeURI(content)
const actions = document.createElement('a')
actions.setAttribute('href', encodedUri)
actions.setAttribute('download', fileName)
actions.click()
}
const handleExportJson = () => {
exportData(props.editorJson)
}
const { clipboardRef, copiedRef } = useCopyToClipboard()
const { createMessage } = useMessage()
function handleExportJson() {
exportData(props.editorJson)
}
const { clipboardRef, copiedRef } = useCopyToClipboard()
const { createMessage } = useMessage()
const handleCopyJson = () => {
//
const value = props.editorJson
if (!value) {
createMessage.warning('代码为空!')
return
}
clipboardRef.value = value
if (unref(copiedRef))
createMessage.warning('复制成功!')
}
return {
...toRefs(state),
myEditor,
exportData,
handleCopyJson,
handleExportJson,
MODE,
}
},
})
function handleCopyJson() {
//
const value = props.editorJson
if (!value) {
createMessage.warning('代码为空!')
return
}
clipboardRef.value = value
if (unref(copiedRef))
createMessage.warning('复制成功!')
}
</script>
<template>
@ -72,10 +54,10 @@ export default defineComponent({
<div class="v-json-box">
<CodeEditor ref="myEditor" :value="editorJson" :mode="MODE.JSON" />
</div>
<div class="copy-btn-box">
<div class="pt-2 text-center">
<a-button
type="primary"
class="copy-btn"
class="mr-2"
data-clipboard-action="copy"
:data-clipboard-text="editorJson"
@click="handleCopyJson"
@ -88,15 +70,3 @@ export default defineComponent({
</div>
</div>
</template>
<style lang="less" scoped>
// modal
.copy-btn-box {
padding-top: 8px;
text-align: center;
.copy-btn {
margin-right: 8px;
}
}
</style>

View File

@ -19,7 +19,6 @@ import PropsPanel from './modules/PropsPanel.vue'
import ImportJsonModal from './components/ImportJsonModal.vue'
import CodeModal from './components/CodeModal.vue'
import { globalConfigState } from './config/formItemPropsConfig'
import { useDesign } from '@/hooks/web/useDesign'
import { CollapseContainer } from '@/components/Container/index'
import 'codemirror/mode/javascript/javascript'
@ -29,7 +28,6 @@ defineProps({
default: 'v-form-antd表单设计器',
},
})
const { prefixCls } = useDesign('form-design')
//
const propsPanel = ref<null | IPropsPanel>(null)
const jsonModal = ref<null | IToolbarMethods>(null)
@ -251,7 +249,7 @@ provide<IFormDesignMethods>('formDesignMethods', {
<template>
<Layout>
<LayoutSider
:class="`left ${prefixCls}-sider`"
class="bg-white dark:bg-black"
collapsible
collapsed-width="0"
width="270"
@ -302,7 +300,7 @@ provide<IFormDesignMethods>('formDesignMethods', {
/>
</LayoutContent>
<LayoutSider
:class="`right ${prefixCls}-sider`"
class="bg-white dark:bg-black"
collapsible
:reverse-arrow="true"
collapsed-width="0"
@ -327,19 +325,3 @@ provide<IFormDesignMethods>('formDesignMethods', {
<VFormPreview ref="eFormPreview" :form-config="formConfig" />
<VFormPreview2 ref="eFormPreview2" :form-config="formConfig" />
</template>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-form-design';
[data-theme='dark'] {
.@{prefix-cls}-sider {
background-color: #1f1f1f;
}
}
[data-theme='light'] {
.@{prefix-cls}-sider {
background-color: #fff;
}
}
</style>

View File

@ -1,16 +1,13 @@
<script lang="ts">
import { defineComponent, reactive } from 'vue'
<script lang="ts" setup>
import draggable from 'vuedraggable'
import type { IVFormComponent } from '../../../typings/v-form-component'
import { Icon } from '@/components/Icon'
import { useDesign } from '@/hooks/web/useDesign'
export default defineComponent({
name: 'CollapseItem',
components: { Draggable: draggable, Icon },
props: {
const props = defineProps(
{
list: {
type: [Array],
type: Array as unknown as any[],
default: () => [],
},
handleListPush: {
@ -18,24 +15,23 @@ export default defineComponent({
default: null,
},
},
setup(props, { emit }) {
const { prefixCls } = useDesign('form-design-collapse-item')
)
const emit = defineEmits(['start', 'add-attrs', 'handle-list-push'])
const Draggable = draggable
const state = reactive({})
const handleStart = (e: any, list1: IVFormComponent[]) => {
emit('start', list1[e.oldIndex].component)
}
const handleAdd = (e: any) => {
console.log(e)
}
// https://github.com/SortableJS/vue.draggable.next
// https://github.com/SortableJS/vue.draggable.next/blob/master/example/components/custom-clone.vue
const cloneItem = (one) => {
return props.handleListPush(one)
}
return { prefixCls, state, handleStart, handleAdd, cloneItem }
},
})
const { prefixCls } = useDesign('form-design-collapse-item')
function handleStart(e: any, list1: IVFormComponent[]) {
emit('start', list1[e.oldIndex].component)
}
function handleAdd(e: any) {
console.log(e)
}
// https://github.com/SortableJS/vue.draggable.next
// https://github.com/SortableJS/vue.draggable.next/blob/master/example/components/custom-clone.vue
function cloneItem(one) {
return props.handleListPush(one)
}
</script>
<template>
@ -57,8 +53,8 @@ export default defineComponent({
<template #item="{ element, index }">
<li
class="bs-box text-ellipsis"
@dragstart="$emit('add-attrs', list, index)"
@click="$emit('handle-list-push', element)"
@dragstart="emit('add-attrs', list, index)"
@click="emit('handle-list-push', element)"
>
<!-- <svg v-if="element.icon.indexOf('icon-') > -1" class="icon" aria-hidden="true">
<use :xlink:href="`#${element.icon}`" />

View File

@ -2,89 +2,61 @@
* @Description: 中间表单布局面板
* https://github.com/SortableJS/vue.draggable.next/issues/138
-->
<script lang="ts">
<script lang="ts" setup>
import draggable from 'vuedraggable'
import { computed, defineComponent } from 'vue'
import { computed } from 'vue'
import { cloneDeep } from 'lodash-es'
import { Empty, Form } from 'ant-design-vue'
import { useFormDesignState } from '../../../hooks/useFormDesignState'
import LayoutItem from '../components/LayoutItem.vue'
export default defineComponent({
name: 'FormComponentPanel',
components: {
LayoutItem,
Draggable: draggable,
Form,
Empty,
},
emits: ['handleSetSelectItem'],
setup(_, { emit }) {
const { formConfig } = useFormDesignState()
const emit = defineEmits(['handleSetSelectItem'])
/**
* 拖拽完成事件
* @param newIndex
*/
const addItem = ({ newIndex }: any) => {
formConfig.value.schemas = formConfig.value.schemas || []
const Draggable = draggable
const schemas = formConfig.value.schemas
schemas[newIndex] = cloneDeep(schemas[newIndex])
emit('handleSetSelectItem', schemas[newIndex])
}
const { formConfig } = useFormDesignState()
/**
* 拖拽开始事件
* @param e {Object} 事件对象
*/
const handleDragStart = (e: any) => {
emit('handleSetSelectItem', formConfig.value.schemas[e.oldIndex])
}
/**
* 拖拽完成事件
* @param newIndex
*/
function addItem({ newIndex }: any) {
formConfig.value.schemas = formConfig.value.schemas || []
// currentItem
const schemas = formConfig.value.schemas
schemas[newIndex] = cloneDeep(schemas[newIndex])
emit('handleSetSelectItem', schemas[newIndex])
}
// AColdiv
const layoutTag = computed(() => {
return formConfig.value.layout === 'horizontal' ? 'Col' : 'div'
})
/**
* 拖拽开始事件
* @param e {Object} 事件对象
*/
function handleDragStart(e: any) {
emit('handleSetSelectItem', formConfig.value.schemas[e.oldIndex])
}
return {
addItem,
handleDragStart,
formConfig,
layoutTag,
}
},
// currentItem
// AColdiv
const layoutTag = computed(() => {
return formConfig.value.layout === 'horizontal' ? 'Col' : 'div'
})
</script>
<template>
<div class="v-form-container form-panel">
<Empty
v-show="formConfig.schemas.length === 0"
class="empty-text"
description="从左侧选择控件添加"
/>
<Empty v-show="formConfig.schemas.length === 0" class="empty-text" description="从左侧选择控件添加" />
<Form v-bind="formConfig">
<div class="draggable-box">
<Draggable
v-model="formConfig.schemas"
class="list-main ant-row"
group="form-draggable"
:component-data="{ name: 'list', tag: 'div', type: 'transition-group' }"
ghost-class="moving"
:animation="180"
handle=".drag-move"
item-key="key"
@add="addItem"
@start="handleDragStart"
v-model="formConfig.schemas" class="list-main ant-row" group="form-draggable"
:component-data="{ name: 'list', tag: 'div', type: 'transition-group' }" ghost-class="moving" :animation="180"
handle=".drag-move" item-key="key" @add="addItem" @start="handleDragStart"
>
<template #item="{ element }">
<LayoutItem
class="drag-move"
:schema="element"
:data="formConfig"
class="drag-move" :schema="element" :data="formConfig"
:current-item="formConfig.currentItem || {}"
/>
</template>
@ -95,72 +67,74 @@ export default defineComponent({
</template>
<style lang="less" scoped>
@import url('../styles/variable.less');
@import url('../styles/drag.less');
@import url('../styles/variable.less');
@import url('../styles/drag.less');
.v-form-container {
//
.ant-form-inline {
.list-main {
display: flex;
flex-wrap: wrap;
align-content: flex-start;
justify-content: flex-start;
.v-form-container {
.layout-width {
width: 100%;
}
//
.ant-form-inline {
.list-main {
display: flex;
flex-wrap: wrap;
align-content: flex-start;
justify-content: flex-start;
.layout-width {
width: 100%;
}
}
.ant-form-item-control-wrapper {
min-width: 175px !important;
}
}
}
.form-panel {
position: relative;
height: 100%;
.empty-text {
position: absolute;
inset: -10% 0 0;
z-index: 100;
height: 150px;
margin: auto;
color: #aaa;
}
.draggable-box {
height: calc(100vh - 200px);
// width: 100%;
overflow: auto;
.drag-move {
min-height: 62px;
cursor: move;
}
.list-main {
//
.list-enter-active {
transition: all 0.5s;
}
.ant-form-item-control-wrapper {
min-width: 175px !important;
}
}
}
.form-panel {
position: relative;
height: 100%;
.empty-text {
position: absolute;
inset: -10% 0 0;
z-index: 100;
height: 150px;
margin: auto;
color: #aaa;
}
.draggable-box {
height: calc(100vh - 200px);
// width: 100%;
overflow: auto;
.drag-move {
min-height: 62px;
cursor: move;
}
.list-main {
//
.list-enter-active {
transition: all 0.5s;
}
.list-leave-active {
transition: all 0.3s;
}
.list-enter,
.list-leave-to {
opacity: 0;
transform: translateX(-100px);
}
.list-enter {
height: 30px;
}
.list-leave-active {
transition: all 0.3s;
}
.list-enter,
.list-leave-to {
opacity: 0;
transform: translateX(-100px);
}
.list-enter {
height: 30px;
}
}
}
}
</style>

View File

@ -1,8 +1,8 @@
<!--
* @Description: 右侧属性配置面板
-->
<script lang="ts">
import { computed, defineComponent } from 'vue'
<script lang="ts" setup>
import { computed } from 'vue'
import { TabPane, Tabs } from 'ant-design-vue'
import FormProps from '../components/FormProps.vue'
import FormItemProps from '../components/FormItemProps.vue'
@ -15,25 +15,11 @@ type ChangeTabKey = 1 | 2
export interface IPropsPanel {
changeTab: (key: ChangeTabKey) => void
}
export default defineComponent({
name: 'PropsPanel',
components: {
FormProps,
FormItemProps,
ComponentProps,
ComponentColumnProps,
Tabs,
TabPane,
},
setup() {
const { formConfig } = useFormDesignState()
const slotProps = computed(() => {
return customComponents.find(
item => item.component === formConfig.value.currentItem?.component,
)
})
return { formConfig, customComponents, slotProps }
},
const { formConfig } = useFormDesignState()
const slotProps = computed(() => {
return customComponents.find(
item => item.component === formConfig.value.currentItem?.component,
)
})
</script>

View File

@ -1,8 +1,8 @@
<!--
* @Description: 工具栏
-->
<script lang="ts">
import { defineComponent, inject, reactive, toRefs } from 'vue'
<script lang="ts" setup>
import { inject, reactive } from 'vue'
import type { UseRefHistoryReturn } from '@vueuse/core'
import { Divider, Tooltip } from 'ant-design-vue'
import type { IFormConfig } from '../../../typings/v-form-component'
@ -15,62 +15,51 @@ interface IToolbarsConfig {
event: string
}
export default defineComponent({
name: 'OperatingArea',
components: {
Tooltip,
Icon,
Divider,
},
setup() {
const state = reactive<{
toolbarsConfigs: IToolbarsConfig[]
}>({
toolbarsConfigs: [
{
title: '预览-支持布局',
type: 'preview',
event: 'handlePreview',
icon: 'ant-design:chrome-filled',
},
{
title: '预览-不支持布局',
type: 'preview',
event: 'handlePreview2',
icon: 'ant-design:chrome-filled',
},
{
title: '导入JSON',
type: 'importJson',
event: 'handleOpenImportJsonModal',
icon: 'ant-design:import-outlined',
},
{
title: '生成JSON',
type: 'exportJson',
event: 'handleOpenJsonModal',
icon: 'ant-design:export-outlined',
},
{
title: '生成代码',
type: 'exportCode',
event: 'handleOpenCodeModal',
icon: 'ant-design:code-filled',
},
{
title: '清空',
type: 'reset',
event: 'handleClearFormItems',
icon: 'ant-design:clear-outlined',
},
],
})
const historyRef = inject('historyReturn') as UseRefHistoryReturn<IFormConfig, IFormConfig>
const { undo, redo, canUndo, canRedo } = historyRef
return { ...toRefs(state), undo, redo, canUndo, canRedo }
},
const state = reactive<{
toolbarsConfigs: IToolbarsConfig[]
}>({
toolbarsConfigs: [
{
title: '预览-支持布局',
type: 'preview',
event: 'handlePreview',
icon: 'ant-design:chrome-filled',
},
{
title: '预览-不支持布局',
type: 'preview',
event: 'handlePreview2',
icon: 'ant-design:chrome-filled',
},
{
title: '导入JSON',
type: 'importJson',
event: 'handleOpenImportJsonModal',
icon: 'ant-design:import-outlined',
},
{
title: '生成JSON',
type: 'exportJson',
event: 'handleOpenJsonModal',
icon: 'ant-design:export-outlined',
},
{
title: '生成代码',
type: 'exportCode',
event: 'handleOpenCodeModal',
icon: 'ant-design:code-filled',
},
{
title: '清空',
type: 'reset',
event: 'handleClearFormItems',
icon: 'ant-design:clear-outlined',
},
],
})
const historyRef = inject('historyReturn') as UseRefHistoryReturn<IFormConfig, IFormConfig>
const { undo, redo, canUndo, canRedo } = historyRef
</script>
<template>
@ -78,7 +67,7 @@ export default defineComponent({
<!-- 头部操作按钮区域 start -->
<!-- 操作左侧区域 start -->
<div class="left-btn-box">
<Tooltip v-for="item in toolbarsConfigs" :key="item.icon" :title="item.title">
<Tooltip v-for="item in state.toolbarsConfigs" :key="item.icon" :title="item.title">
<a class="toolbar-text" @click="$emit(item.event)">
<Icon :icon="item.icon" />
</a>

View File

@ -70,7 +70,7 @@
&-close {
width: auto !important;
font-weight: normal;
background: rgb(255 255 255/ 0%) !important;
background: transparent !important;
outline: none;
}

View File

@ -9,8 +9,8 @@ const { getCalcContentWidth } = useMenuSetting()
</script>
<template>
<div :class="prefixCls" :style="{ width: getCalcContentWidth }">
<div :class="`${prefixCls}__left`">
<div class="fixed bottom-0 right-0 z-99 w-full flex items-center border-t-1 px-6 text-base/44" :style="{ width: getCalcContentWidth }">
<div class="flex-1-1">
<slot name="left" />
</div>
<slot />
@ -24,24 +24,10 @@ const { getCalcContentWidth } = useMenuSetting()
@prefix-cls: ~'@{namespace}-page-footer';
.@{prefix-cls} {
position: fixed;
right: 0;
bottom: 0;
z-index: @page-footer-z-index;
display: flex;
align-items: center;
width: 100%;
padding: 0 24px;
line-height: 44px;
border-top: 1px solid var(--border-color);
box-shadow:
0 -6px 16px -8px rgb(0 0 0 / 8%),
0 -9px 28px 0 rgb(0 0 0 / 5%),
0 -12px 48px 16px rgb(0 0 0 / 3%);
transition: width 0.2s;
&__left {
flex: 1 1;
}
}
</style>

View File

@ -316,7 +316,7 @@ emit('register', tableAction, formActions)
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-table';
[data-theme='dark'] {
html[data-theme='dark'] {
.ant-table-tbody > tr:hover.ant-table-row-selected > td,
.ant-table-tbody > tr.ant-table-row-selected td {
background-color: #262626;

View File

@ -438,6 +438,7 @@ function updateSortOption(column: BasicColumn) {
}
.ant-checkbox-group {
display: inline-block;
width: 100%;
min-width: 260px;
// flex-wrap: wrap;

View File

@ -1,12 +1,13 @@
import type { ComputedRef } from 'vue'
import { unref } from 'vue'
import type { Key } from 'ant-design-vue/lib/table/interface'
import type { BasicTableProps } from '../types/table'
import { ROW_KEY } from '../const'
import { isFunction, isString } from '@/utils/is'
interface Options {
setSelectedRowKeys: (keys: string[]) => void
getSelectRowKeys: () => string[]
setSelectedRowKeys: (keys: Key[]) => void
getSelectRowKeys: () => Key[]
clearSelectedRowKeys: () => void
emit: EmitType
getAutoCreateKey: ComputedRef<boolean | undefined>

View File

@ -163,8 +163,8 @@ export function useTable(tableProps?: Props): [
scrollTo: (pos: string) => {
getTableInstance().scrollTo(pos)
},
setShowForm: async (show: boolean) => {
await getTableInstance().setShowForm(show)
setShowForm: async (flag: boolean) => {
await getTableInstance().setShowForm(flag)
},
getShowForm: () => {
return toRaw(getTableInstance().getShowForm())

View File

@ -86,7 +86,7 @@ export interface TableActionType {
expandRows: (keys: (string | number)[]) => void
collapseAll: () => void
scrollTo: (pos: string) => void // pos: id | "top" | "bottom"
getSelectRowKeys: () => string[]
getSelectRowKeys: () => Key[]
deleteSelectRowByKey: (key: string) => void
setPagination: (info: Partial<PaginationProps>) => void
setTableData: <T extends Ref<Recordable<any>[]>>(values: T[]) => void
@ -108,11 +108,11 @@ export interface TableActionType {
getCacheColumns: () => BasicColumn[]
emit?: EmitType
updateTableData: (index: number, key: string, value: any) => Recordable
setShowPagination: (show: boolean) => Promise<void>
setShowPagination: (show: boolean) => void
getShowPagination: () => boolean
setCacheColumnsByField?: (dataIndex: string | undefined, value: BasicColumn) => void
setCacheColumns?: (columns: BasicColumn[]) => void
setShowForm: (show: boolean) => Promise<void>
setShowForm: (flag: boolean) => void
getShowForm: () => boolean
}
@ -403,6 +403,7 @@ export interface BasicTableProps<T = any> {
export type CellFormat = string | ((text: string, record: Recordable, index: number) => string | number) | Map<string | number, any>
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
export interface BasicColumn extends ColumnProps<Recordable> {
children?: BasicColumn[]

View File

@ -1,7 +1,6 @@
@import "transition/index.less";
@import "var/index.less";
@import "public.less";
// @import "ant/index.less";
@import "./theme.less";
@import "./entry.css";

View File

@ -11,7 +11,6 @@ import { openWindow } from '@/utils'
import { useI18n } from '@/hooks/web/useI18n'
import { useRootSetting } from '@/hooks/setting/useRootSetting'
import { useDesign } from '@/hooks/web/useDesign'
defineOptions({ name: 'LayoutFooter' })
@ -22,7 +21,6 @@ const Footer = Layout.Footer
const { t } = useI18n()
const { getShowFooter } = useRootSetting()
const { currentRoute } = useRouter()
const { prefixCls } = useDesign('layout-footer')
const footerRef = ref<ComponentRef>(null)
const { setFooterHeight } = useLayoutHeight()
@ -40,43 +38,15 @@ const getShowLayoutFooter = computed(() => {
</script>
<template>
<Footer v-if="getShowLayoutFooter" ref="footerRef" :class="prefixCls">
<div :class="`${prefixCls}__links`">
<a @click="openWindow(SITE_URL)"></a>
<Footer v-if="getShowLayoutFooter" ref="footerRef" class="text-center text-[var(--normal-text)]">
<div class="mb-2">
<a class="text-[var(--normal-text)] hover:text-[var(--hover-text)]" @click="openWindow(SITE_URL)"></a>
<GithubFilled :class="`${prefixCls}__github`" @click="openWindow(GITHUB_URL)" />
<GithubFilled class="mx-7.5 hover:text-[var(--hover-text)]" @click="openWindow(GITHUB_URL)" />
<a @click="openWindow(DOC_URL)">{{ t('layout.footer.onlineDocument') }}</a>
<a class="text-[var(--normal-text)] hover:text-[var(--hover-text)]" @click="openWindow(DOC_URL)">{{
t('layout.footer.onlineDocument') }}</a>
</div>
<div>Copyright &copy;2023 {{ SITE_TITLE }}</div>
</Footer>
</template>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-layout-footer';
.@{prefix-cls} {
color: var(--normal-text);
text-align: center;
&__links {
margin-bottom: 8px;
a {
color: var(--normal-text);
&:hover {
color: var(--hover-text);
}
}
}
&__github {
margin: 0 30px;
&:hover {
color: var(--hover-text);
}
}
}
</style>

View File

@ -1,7 +1,6 @@
<script lang="ts" setup>
import { computed } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { useDesign } from '@/hooks/web/useDesign'
import { BasicForm, useForm } from '@/components/Form'
import { BasicModal, useModalInner } from '@/components/Modal'
import { useUserStore } from '@/store/modules/user'
@ -11,7 +10,6 @@ import headerImg from '@/assets/images/header.jpg'
defineOptions({ name: 'LockModal' })
const { t } = useI18n()
const { prefixCls } = useDesign('header-lock-modal')
const userStore = useUserStore()
const lockStore = useLockStore()
@ -52,18 +50,18 @@ const avatar = computed(() => {
</script>
<template>
<BasicModal :footer="null" width="25%" :title="t('layout.header.lockScreen')" v-bind="$attrs" :class="prefixCls" @register="register">
<div :class="`${prefixCls}__entry`">
<div :class="`${prefixCls}__header`">
<img :src="avatar" :class="`${prefixCls}__header-img`">
<p :class="`${prefixCls}__header-name`">
<BasicModal :footer="null" width="25%" :title="t('layout.header.lockScreen')" v-bind="$attrs" @register="register">
<div class="relative rounded-10 px-8 pb-8 pt-30">
<div class="absolute left-[calc(50%-45px)] top-0 w-auto text-center">
<img :src="avatar" class="w-18 rounded-50%">
<p class="mt-2">
{{ getRealName }}
</p>
</div>
<BasicForm @register="registerForm" />
<div :class="`${prefixCls}__footer`">
<div class="mt-4 text-center">
<a-button type="primary" block class="mt-2" @click="handleLock">
{{ t('layout.header.lockScreenBtn') }}
</a-button>
@ -71,38 +69,3 @@ const avatar = computed(() => {
</div>
</BasicModal>
</template>
<style lang="less">
@prefix-cls: ~'@{namespace}-header-lock-modal';
.@{prefix-cls} {
&__entry {
position: relative;
//height: 240px;
padding: 130px 30px 30px;
border-radius: 10px;
}
&__header {
position: absolute;
top: 0;
left: calc(50% - 45px);
width: auto;
text-align: center;
&-img {
width: 70px;
border-radius: 50%;
}
&-name {
margin-top: 5px;
}
}
&__footer {
margin-top: 16px;
text-align: center;
}
}
</style>

View File

@ -2,7 +2,6 @@
import { computed, ref, unref, watch } from 'vue'
import { Avatar, List, Tag, Typography } from 'ant-design-vue'
import type { ListItem } from './data'
import { useDesign } from '@/hooks/web/useDesign'
import { isNumber } from '@/utils/is'
const props = defineProps({
@ -31,7 +30,6 @@ const props = defineProps({
},
})
const emit = defineEmits(['update:currentPage'])
const { prefixCls } = useDesign('header-notify-list')
const current = ref(props.currentPage || 1)
const getData = computed(() => {
const { pageSize, list } = props
@ -72,12 +70,12 @@ function handleTitleClick(item: ListItem) {
</script>
<template>
<List :class="prefixCls" bordered :pagination="getPagination">
<List class="display-none" bordered :pagination="getPagination">
<template v-for="item in getData" :key="item.id">
<List.Item class="list-item">
<List.Item class="cursor-pointer overflow-hidden p-1.5 transition-all delay-300">
<List.Item.Meta>
<template #title>
<div class="title">
<div class="mb-2 font-normal">
<Typography.Paragraph
style="width: 100%; margin-bottom: 0 !important"
:style="{ cursor: isTitleClickable ? 'pointer' : '' }"
@ -86,8 +84,8 @@ function handleTitleClick(item: ListItem) {
:content="item.title"
@click="handleTitleClick(item)"
/>
<div v-if="item.extra" class="extra">
<Tag class="tag" :color="item.color">
<div v-if="item.extra" class="float-right mr-0 font-normal -mt-0.375">
<Tag class="mr-0" :color="item.color">
{{ item.extra }}
</Tag>
</div>
@ -95,20 +93,20 @@ function handleTitleClick(item: ListItem) {
</template>
<template #avatar>
<Avatar v-if="item.avatar" class="avatar" :src="item.avatar" />
<Avatar v-if="item.avatar" class="mt-1" :src="item.avatar" />
<span v-else> {{ item.avatar }}</span>
</template>
<template #description>
<div>
<div v-if="item.description" class="description">
<div v-if="item.description" class="text-xs/18">
<Typography.Paragraph
style="width: 100%; margin-bottom: 0 !important"
:ellipsis="$props.descRows && $props.descRows > 0 ? { rows: $props.descRows, tooltip: !!item.description } : false"
:content="item.description"
/>
</div>
<div class="datetime">
<div class="mt-1 text-xs/18">
{{ item.datetime }}
</div>
</div>
@ -118,55 +116,3 @@ function handleTitleClick(item: ListItem) {
</template>
</List>
</template>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-header-notify-list';
.@{prefix-cls} {
&::-webkit-scrollbar {
display: none;
}
::v-deep(.ant-pagination-disabled) {
display: inline-block !important;
}
&-item {
padding: 6px;
overflow: hidden;
cursor: pointer;
transition: all 0.3s;
.title {
margin-bottom: 8px;
font-weight: normal;
.extra {
float: right;
margin-top: -1.5px;
margin-right: 0;
font-weight: normal;
.tag {
margin-right: 0;
}
}
.avatar {
margin-top: 4px;
}
.description {
font-size: 12px;
line-height: 18px;
}
.datetime {
margin-top: 4px;
font-size: 12px;
line-height: 18px;
}
}
}
}
</style>

View File

@ -33,5 +33,3 @@ onMounted(async () => {
</Tooltip>
</div>
</template>
<style lang="less"></style>

View File

@ -9,7 +9,6 @@ import LayoutSideBar from './sider/index.vue'
import { useHeaderSetting } from '@/hooks/setting/useHeaderSetting'
import { useMenuSetting } from '@/hooks/setting/useMenuSetting'
import { useAppInject } from '@/hooks/web/useAppInject'
import { useDesign } from '@/hooks/web/useDesign'
import { useLockPage } from '@/hooks/web/useLockPage'
import { createAsyncComponent } from '@/utils/factory/createAsyncComponent'
@ -18,7 +17,6 @@ defineOptions({ name: 'DefaultLayout' })
const LayoutFeatures = createAsyncComponent(() => import('@/layouts/default/feature/index.vue'))
const LayoutFooter = createAsyncComponent(() => import('@/layouts/default/footer/index.vue'))
const { prefixCls } = useDesign('default-layout')
const { getIsMobile } = useAppInject()
const { getShowFullHeaderRef } = useHeaderSetting()
const { getShowSidebar, getIsMixSidebar, getShowMenu } = useMenuSetting()
@ -36,12 +34,12 @@ const layoutClass = computed(() => {
</script>
<template>
<Layout :class="prefixCls" v-bind="lockEvents">
<Layout class="min-h-full w-full flex flex-col" v-bind="lockEvents">
<LayoutFeatures />
<LayoutHeader v-if="getShowFullHeaderRef" fixed />
<Layout :class="[layoutClass]">
<LayoutSideBar v-if="getShowSidebar || getIsMobile" />
<Layout :class="`${prefixCls}-main`">
<Layout class="ml-0.25 w-full">
<LayoutMultipleHeader />
<LayoutContent />
<LayoutFooter />
@ -49,19 +47,3 @@ const layoutClass = computed(() => {
</Layout>
</Layout>
</template>
<style lang="less">
@prefix-cls: ~'@{namespace}-default-layout';
.@{prefix-cls} {
display: flex;
flex-direction: column;
width: 100%;
min-height: 100%;
&-main {
width: 100%;
margin-left: 1px;
}
}
</style>

View File

@ -2,7 +2,6 @@
import { InputNumber } from 'ant-design-vue'
import { baseHandler } from '../handler'
import type { HandlerEnum } from '../enum'
import { useDesign } from '@/hooks/web/useDesign'
defineOptions({ name: 'InputNumberItem' })
@ -14,7 +13,6 @@ const props = defineProps({
type: String,
},
})
const { prefixCls } = useDesign('setting-input-number-item')
function handleChange(e) {
props.event && baseHandler(props.event, e)
@ -22,22 +20,8 @@ function handleChange(e) {
</script>
<template>
<div :class="prefixCls">
<div class="my-4 flex justify-between">
<span> {{ title }}</span>
<InputNumber v-bind="$attrs" :class="`${prefixCls}-input-number`" @change="handleChange" />
<InputNumber v-bind="$attrs" class="max-w-32 w-30" @change="handleChange" />
</div>
</template>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-setting-input-number-item';
.@{prefix-cls} {
display: flex;
justify-content: space-between;
margin: 16px 0;
&-input-number {
width: 126px !important;
}
}
</style>

View File

@ -4,7 +4,6 @@ import { computed } from 'vue'
import { Select } from 'ant-design-vue'
import { baseHandler } from '../handler'
import type { HandlerEnum } from '../enum'
import { useDesign } from '@/hooks/web/useDesign'
defineOptions({ name: 'SelectItem' })
@ -29,33 +28,19 @@ const props = defineProps({
default: () => [],
},
})
const { prefixCls } = useDesign('setting-select-item')
const getBindValue = computed(() => {
return props.def ? { value: props.def, defaultValue: props.initValue || props.def } : {}
})
function handleChange(e: ChangeEvent) {
function handleChange(e) {
props.event && baseHandler(props.event, e)
}
</script>
<template>
<div :class="prefixCls">
<div class="my-4 flex justify-between">
<span> {{ title }}</span>
<Select v-bind="getBindValue" :class="`${prefixCls}-select`" :disabled="disabled" :options="options" @change="handleChange as any" />
<Select v-bind="getBindValue" class="max-w-32 w-30" :disabled="disabled" :options="options" @change="handleChange" />
</div>
</template>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-setting-select-item';
.@{prefix-cls} {
display: flex;
justify-content: space-between;
margin: 16px 0;
&-select {
width: 126px;
}
}
</style>

View File

@ -8,7 +8,6 @@ import { usePermissionStore } from '@/store/modules/permission'
import { useMultipleTabStore } from '@/store/modules/multipleTab'
import { useUserStore } from '@/store/modules/user'
import { useDesign } from '@/hooks/web/useDesign'
import { useI18n } from '@/hooks/web/useI18n'
import { useMessage } from '@/hooks/web/useMessage'
import { useCopyToClipboard } from '@/hooks/web/useCopyToClipboard'
@ -22,7 +21,6 @@ import { updateSidebarBgColor } from '@/logics/theme/updateBackground'
defineOptions({ name: 'SettingFooter' })
const permissionStore = usePermissionStore()
const { prefixCls } = useDesign('setting-footer')
const { t } = useI18n()
const { createSuccessModal, createMessage } = useMessage()
const tabStore = useMultipleTabStore()
@ -63,7 +61,7 @@ function handleClearAndRedo() {
</script>
<template>
<div :class="prefixCls">
<div class="flex flex-col items-center">
<a-button type="primary" block @click="handleCopy">
<CopyOutlined class="mr-2" />
{{ t('layout.setting.copyBtn') }}
@ -80,13 +78,3 @@ function handleClearAndRedo() {
</a-button>
</div>
</template>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-setting-footer';
.@{prefix-cls} {
display: flex;
flex-direction: column;
align-items: center;
}
</style>

View File

@ -4,7 +4,6 @@ import { computed } from 'vue'
import { Switch } from 'ant-design-vue'
import { baseHandler } from '../handler'
import type { HandlerEnum } from '../enum'
import { useDesign } from '@/hooks/web/useDesign'
import { useI18n } from '@/hooks/web/useI18n'
defineOptions({ name: 'SwitchItem' })
@ -23,7 +22,6 @@ const props = defineProps({
type: Boolean,
},
})
const { prefixCls } = useDesign('setting-switch-item')
const { t } = useI18n()
const getBindValue = computed(() => {
@ -35,7 +33,7 @@ function handleChange(e) {
</script>
<template>
<div :class="prefixCls">
<div class="my-4 flex justify-between">
<span> {{ title }}</span>
<Switch
v-bind="getBindValue"
@ -46,13 +44,3 @@ function handleChange(e) {
/>
</div>
</template>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-setting-switch-item';
.@{prefix-cls} {
display: flex;
justify-content: space-between;
margin: 16px 0;
}
</style>

View File

@ -1,7 +1,8 @@
<script lang="ts">
<script lang="ts" setup>
import type { CSSProperties } from 'vue'
import { computed, defineComponent, onMounted, ref, unref, watch } from 'vue'
import { computed, onMounted, ref, unref, watch } from 'vue'
import type { RouteLocationNormalized } from 'vue-router'
import { onClickOutside } from '@vueuse/core'
import LayoutTrigger from '../trigger/index.vue'
import { useDragLine } from './useLayoutSider'
import type { Menu } from '@/router/types'
@ -16,257 +17,220 @@ import { useDesign } from '@/hooks/web/useDesign'
import { useI18n } from '@/hooks/web/useI18n'
import { useGo } from '@/hooks/web/usePage'
import { SIDE_BAR_MINI_WIDTH, SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '@/enums/appEnum'
import clickOutside from '@/directives/clickOutside'
import { getChildrenMenus, getCurrentParentPath, getShallowMenus } from '@/router/menus'
import { listenerRouteChange } from '@/logics/mitt/routeChange'
export default defineComponent({
name: 'LayoutMixSider',
components: {
ScrollContainer,
AppLogo,
SimpleMenu,
Icon,
LayoutTrigger,
SimpleMenuTag,
const wrap = ref(null)
const menuModules = ref<Menu[]>([])
const activePath = ref('')
const childrenMenus = ref<Menu[]>([])
const openMenu = ref(false)
const dragBarRef = ref<ElRef>(null)
const sideRef = ref<ElRef>(null)
const currentRoute = ref<Nullable<RouteLocationNormalized>>(null)
const { prefixCls } = useDesign('layout-mix-sider')
const go = useGo()
const { t } = useI18n()
const {
getMenuWidth,
getCanDrag,
getCloseMixSidebarOnChange,
getMenuTheme,
getMixSideTrigger,
getRealWidth,
getMixSideFixed,
mixSideHasChildren,
setMenuSetting,
getIsMixSidebar,
getCollapsed,
} = useMenuSetting()
const { title } = useGlobSetting()
const permissionStore = usePermissionStore()
useDragLine(sideRef, dragBarRef, true)
const getMenuStyle = computed((): CSSProperties => {
return {
width: unref(openMenu) ? `${unref(getMenuWidth)}px` : 0,
// eslint-disable-next-line @typescript-eslint/no-use-before-define
left: `${unref(getMixSideWidth)}px`,
}
})
const getIsFixed = computed(() => {
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
mixSideHasChildren.value = unref(childrenMenus).length > 0
const isFixed = unref(getMixSideFixed) && unref(mixSideHasChildren)
if (isFixed)
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
openMenu.value = true
return isFixed
})
const getMixSideWidth = computed(() => {
return unref(getCollapsed) ? SIDE_BAR_MINI_WIDTH : SIDE_BAR_SHOW_TIT_MINI_WIDTH
})
const getDomStyle = computed((): CSSProperties => {
const fixedWidth = unref(getIsFixed) ? unref(getRealWidth) : 0
const width = `${unref(getMixSideWidth) + fixedWidth}px`
return getWrapCommonStyle(width)
})
const getWrapStyle = computed((): CSSProperties => {
const width = `${unref(getMixSideWidth)}px`
return getWrapCommonStyle(width)
})
const getMenuEvents = computed(() => {
return !unref(getMixSideFixed)
? {
onMouseleave: () => {
setActive(true)
closeMenu()
},
}
: {}
})
const getShowDragBar = computed(() => unref(getCanDrag))
onMounted(async () => {
menuModules.value = await getShallowMenus()
})
// Menu changes
watch(
[() => permissionStore.getLastBuildMenuTime, () => permissionStore.getBackMenuList],
async () => {
menuModules.value = await getShallowMenus()
},
directives: {
clickOutside,
{
immediate: true,
},
setup() {
const menuModules = ref<Menu[]>([])
const activePath = ref('')
const childrenMenus = ref<Menu[]>([])
const openMenu = ref(false)
const dragBarRef = ref<ElRef>(null)
const sideRef = ref<ElRef>(null)
const currentRoute = ref<Nullable<RouteLocationNormalized>>(null)
)
const { prefixCls } = useDesign('layout-mix-sider')
const go = useGo()
const { t } = useI18n()
const {
getMenuWidth,
getCanDrag,
getCloseMixSidebarOnChange,
getMenuTheme,
getMixSideTrigger,
getRealWidth,
getMixSideFixed,
mixSideHasChildren,
setMenuSetting,
getIsMixSidebar,
getCollapsed,
} = useMenuSetting()
listenerRouteChange((route) => {
currentRoute.value = route
setActive(true)
if (unref(getCloseMixSidebarOnChange))
closeMenu()
})
const { title } = useGlobSetting()
const permissionStore = usePermissionStore()
function getWrapCommonStyle(width: string): CSSProperties {
return {
width,
maxWidth: width,
minWidth: width,
flex: `0 0 ${width}`,
}
}
useDragLine(sideRef, dragBarRef, true)
const getMenuStyle = computed((): CSSProperties => {
return {
width: unref(openMenu) ? `${unref(getMenuWidth)}px` : 0,
// eslint-disable-next-line @typescript-eslint/no-use-before-define
left: `${unref(getMixSideWidth)}px`,
}
})
const getIsFixed = computed(() => {
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
mixSideHasChildren.value = unref(childrenMenus).length > 0
const isFixed = unref(getMixSideFixed) && unref(mixSideHasChildren)
if (isFixed)
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
// Process module menu click
async function handleModuleClick(path: string, hover = false) {
const children = await getChildrenMenus(path)
if (unref(activePath) === path) {
if (!hover) {
if (!unref(openMenu))
openMenu.value = true
return isFixed
})
const getMixSideWidth = computed(() => {
return unref(getCollapsed) ? SIDE_BAR_MINI_WIDTH : SIDE_BAR_SHOW_TIT_MINI_WIDTH
})
const getDomStyle = computed((): CSSProperties => {
const fixedWidth = unref(getIsFixed) ? unref(getRealWidth) : 0
const width = `${unref(getMixSideWidth) + fixedWidth}px`
return getWrapCommonStyle(width)
})
const getWrapStyle = computed((): CSSProperties => {
const width = `${unref(getMixSideWidth)}px`
return getWrapCommonStyle(width)
})
const getMenuEvents = computed(() => {
return !unref(getMixSideFixed)
? {
onMouseleave: () => {
setActive(true)
closeMenu()
},
}
: {}
})
const getShowDragBar = computed(() => unref(getCanDrag))
onMounted(async () => {
menuModules.value = await getShallowMenus()
})
// Menu changes
watch(
[() => permissionStore.getLastBuildMenuTime, () => permissionStore.getBackMenuList],
async () => {
menuModules.value = await getShallowMenus()
},
{
immediate: true,
},
)
listenerRouteChange((route) => {
currentRoute.value = route
setActive(true)
if (unref(getCloseMixSidebarOnChange))
else
closeMenu()
})
function getWrapCommonStyle(width: string): CSSProperties {
return {
width,
maxWidth: width,
minWidth: width,
flex: `0 0 ${width}`,
}
}
// Process module menu click
async function handleModuleClick(path: string, hover = false) {
const children = await getChildrenMenus(path)
if (unref(activePath) === path) {
if (!hover) {
if (!unref(openMenu))
openMenu.value = true
else
closeMenu()
}
else {
if (!unref(openMenu))
openMenu.value = true
}
if (!unref(openMenu))
setActive()
}
else {
else {
if (!unref(openMenu))
openMenu.value = true
activePath.value = path
}
if (!children || children.length === 0) {
if (!hover)
go(path)
childrenMenus.value = []
closeMenu()
return
}
childrenMenus.value = children
}
if (!unref(openMenu))
setActive()
}
else {
openMenu.value = true
activePath.value = path
}
// Set the currently active menu and submenu
async function setActive(setChildren = false) {
const path = currentRoute.value?.path
if (!path)
return
activePath.value = await getCurrentParentPath(path)
// hanldeModuleClick(parentPath);
if (unref(getIsMixSidebar)) {
const activeMenu = unref(menuModules).find(item => item.path === unref(activePath))
const p = activeMenu?.path
if (p) {
const children = await getChildrenMenus(p)
if (setChildren) {
childrenMenus.value = children
if (unref(getMixSideFixed))
openMenu.value = children.length > 0
}
if (children.length === 0)
childrenMenus.value = []
}
}
}
function handleMenuClick(path: string) {
if (!children || children.length === 0) {
if (!hover)
go(path)
}
childrenMenus.value = []
closeMenu()
return
}
childrenMenus.value = children
}
function handleClickOutside() {
setActive(true)
closeMenu()
}
// Set the currently active menu and submenu
async function setActive(setChildren = false) {
const path = currentRoute.value?.path
if (!path)
return
activePath.value = await getCurrentParentPath(path)
// hanldeModuleClick(parentPath);
if (unref(getIsMixSidebar)) {
const activeMenu = unref(menuModules).find(item => item.path === unref(activePath))
const p = activeMenu?.path
if (p) {
const children = await getChildrenMenus(p)
if (setChildren) {
childrenMenus.value = children
function getItemEvents(item: Menu) {
if (unref(getMixSideTrigger) === 'hover') {
return {
onMouseenter: () => handleModuleClick(item.path, true),
onClick: async () => {
const children = await getChildrenMenus(item.path)
if (item.path && (!children || children.length === 0))
go(item.path)
},
}
}
return {
onClick: () => handleModuleClick(item.path),
if (unref(getMixSideFixed))
openMenu.value = children.length > 0
}
if (children.length === 0)
childrenMenus.value = []
}
}
}
function handleFixedMenu() {
setMenuSetting({
mixSideFixed: !unref(getIsFixed),
})
}
function handleMenuClick(path: string) {
go(path)
}
// Close menu
function closeMenu() {
if (!unref(getIsFixed))
openMenu.value = false
}
function handleClickOutside() {
setActive(true)
closeMenu()
}
function getItemEvents(item: Menu) {
if (unref(getMixSideTrigger) === 'hover') {
return {
t,
prefixCls,
menuModules,
handleModuleClick,
activePath,
childrenMenus,
getShowDragBar,
handleMenuClick,
getMenuStyle,
handleClickOutside,
sideRef,
dragBarRef,
title,
openMenu,
getMenuTheme,
getItemEvents,
getMenuEvents,
getDomStyle,
handleFixedMenu,
getMixSideFixed,
getWrapStyle,
getCollapsed,
onMouseenter: () => handleModuleClick(item.path, true),
onClick: async () => {
const children = await getChildrenMenus(item.path)
if (item.path && (!children || children.length === 0))
go(item.path)
},
}
},
}
return {
onClick: () => handleModuleClick(item.path),
}
}
function handleFixedMenu() {
setMenuSetting({
mixSideFixed: !unref(getIsFixed),
})
}
// Close menu
function closeMenu() {
if (!unref(getIsFixed))
openMenu.value = false
}
onClickOutside(wrap, () => {
handleClickOutside()
})
</script>
<template>
<div :class="`${prefixCls}-dom`" :style="getDomStyle" />
<div
v-click-outside="handleClickOutside"
ref="wrap"
:style="getWrapStyle"
:class="[
prefixCls,

View File

@ -26,7 +26,7 @@ const title = computed(() => globSetting?.title ?? '')
</script>
<template>
<div :class="prefixCls" class="relative h-full w-full px-4">
<div :class="prefixCls" class="relative h-full min-h-full w-full overflow-hidden px-4">
<div class="absolute right-4 top-4 flex items-center">
<AppDarkModeToggle v-if="!sessionTimeout" class="enter-x mr-2" />
<AppLocalePicker v-if="!sessionTimeout && showLocale" class="enter-x xl:text-gray-600" :show-text="false" />
@ -108,9 +108,6 @@ html[data-theme='dark'] {
}
.@{prefix-cls} {
min-height: 100%;
overflow: hidden;
@media (max-width: @screen-xl) {
background-color: #293146;

View File

@ -1,13 +1,11 @@
<script lang="ts" setup>
import { onBeforeUnmount, onMounted, ref } from 'vue'
import Login from './Login.vue'
import { useDesign } from '@/hooks/web/useDesign'
import { useUserStore } from '@/store/modules/user'
import { usePermissionStore } from '@/store/modules/permission'
import { useAppStore } from '@/store/modules/app'
import { PermissionModeEnum } from '@/enums/appEnum'
const { prefixCls } = useDesign('st-login')
const userStore = useUserStore()
const permissionStore = usePermissionStore()
const appStore = useAppStore()
@ -36,20 +34,8 @@ onBeforeUnmount(() => {
<template>
<transition>
<div :class="prefixCls">
<div class="fixed z-9999999 h-full w-full bg-[var(--component-background)]">
<Login session-timeout />
</div>
</transition>
</template>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-st-login';
.@{prefix-cls} {
position: fixed;
z-index: 9999999;
width: 100%;
height: 100%;
background: var(--component-background);
}
</style>

View File

@ -32,11 +32,11 @@ onMounted(async () => {
<List.Item>
<List.Item.Meta>
<template #avatar>
<Icon v-if="item.avatar" class="avatar" :icon="item.avatar" :color="item.color" />
<Icon v-if="item.avatar" class="text-4xl" :icon="item.avatar" :color="item.color" />
</template>
<template #title>
{{ item.title }}
<a-button v-if="item.extra" type="link" size="small" class="extra">
<a-button v-if="item.extra" type="link" size="small" class="float-right mr-7.5 mt-2.5 cursor-pointer">
{{ item.extra }}
</a-button>
</template>
@ -49,16 +49,3 @@ onMounted(async () => {
</List>
</CollapseContainer>
</template>
<style lang="less" scoped>
.avatar {
font-size: 40px !important;
}
.extra {
float: right;
margin-top: 10px;
margin-right: 30px;
cursor: pointer;
}
</style>

View File

@ -54,7 +54,7 @@ async function handleSubmit() {
<BasicForm @register="register" />
</Col>
<Col :span="10">
<div class="change-avatar">
<div>
<div class="mb-2">
头像
</div>
@ -63,6 +63,7 @@ async function handleSubmit() {
btn-text="更换头像"
:btn-props="{ preIcon: 'ant-design:cloud-upload-outlined' }"
width="150"
class="mb-4 block rounded-full"
@change="updateAvatar"
/>
</div>
@ -73,13 +74,3 @@ async function handleSubmit() {
</Button>
</CollapseContainer>
</template>
<style lang="less" scoped>
.change-avatar {
img {
display: block;
margin-bottom: 15px;
border-radius: 50%;
}
}
</style>

View File

@ -15,7 +15,7 @@ const ListItemMeta = List.Item.Meta
<ListItemMeta>
<template #title>
{{ item.title }}
<Switch class="extra" checked-children="" un-checked-children="" default-checked />
<Switch class="float-right mr-7.5 mt-0" checked-children="" un-checked-children="" default-checked />
</template>
<template #description>
<div>{{ item.description }}</div>
@ -26,11 +26,3 @@ const ListItemMeta = List.Item.Meta
</List>
</CollapseContainer>
</template>
<style lang="less" scoped>
.extra {
float: right;
margin-top: 10px;
margin-right: 30px;
}
</style>

View File

@ -29,7 +29,7 @@ function handleSuccess() {
<ListItemMeta>
<template #title>
{{ item.title }}
<div v-if="item.extra" class="extra">
<div v-if="item.extra" class="float-right mr-7.5 mt-2.5 cursor-pointer font-normal text-blue-500">
<a-button type="link" @click="handleEdit(item.title)">
{{ item.extra }}
</a-button>
@ -45,14 +45,3 @@ function handleSuccess() {
</CollapseContainer>
<PasswordModal @register="registerModal" @success="handleSuccess" />
</template>
<style lang="less" scoped>
.extra {
float: right;
margin-top: 10px;
margin-right: 30px;
font-weight: normal;
color: #1890ff;
cursor: pointer;
}
</style>

View File

@ -15,7 +15,7 @@ const tabBarStyle = { width: '220px' }
<template>
<ScrollContainer>
<div ref="wrapperRef" class="account-setting">
<div ref="wrapperRef" class="m-3 rounded-1.5 bg-[var(--component-background)]">
<Tabs tab-position="left" :tab-bar-style="tabBarStyle">
<template v-for="item in settingList" :key="item.key">
<TabPane :tab="item.name">
@ -29,19 +29,3 @@ const tabBarStyle = { width: '220px' }
</div>
</ScrollContainer>
</template>
<style lang="less">
.account-setting {
margin: 12px;
background-color: var(--component-background);
border-radius: 6px;
.base-title {
padding-left: 0;
}
// .ant-tabs-tab-active {
// background-color: @item-active-bg;
// }
}
</style>

View File

@ -78,7 +78,7 @@ async function submitForm(formData) {
<template>
<PageWrapper>
<div class="step-form-form">
<div class="mx-auto my-0 mt-2.5 w-200">
<Steps :current="current">
<Steps.Step title="选择流程" />
<Steps.Step title="流程提交" />
@ -113,11 +113,3 @@ async function submitForm(formData) {
</div>
</PageWrapper>
</template>
<style lang="less" scoped>
.step-form-form {
width: 750px;
margin: 0 auto;
margin-top: 10px;
}
</style>

View File

@ -73,7 +73,7 @@ onMounted(async () => {
<template>
<PageWrapper>
<div class="step-form-form">
<div class="mx-auto my-0 mt-2.5 w-200">
<Steps :current="current">
<Step title="生成信息" />
<Step title="字段信息" />
@ -94,11 +94,3 @@ onMounted(async () => {
</div>
</PageWrapper>
</template>
<style lang="less" scoped>
.step-form-form {
width: 750px;
margin: 0 auto;
margin-top: 10px;
}
</style>

View File

@ -51,46 +51,20 @@ watch(
</script>
<template>
<div class="step1">
<div class="step1-form">
<div>
<div class="mx-auto my-0 w-80%">
<BasicForm @register="register" />
</div>
<Divider />
<h3>说明</h3>
<h4>基本信息</h4>
<h3 class="mb-3 text-base">
说明
</h3>
<h4 class="mb-1 text-sm">
基本信息
</h4>
<p> 配置生成的基本信息 </p>
<h4>生成信息</h4>
<p> 配置生成生成的详细信息 </p>
</div>
</template>
<style lang="less" scoped>
.step1 {
&-form {
width: 80%;
margin: 0 auto;
}
h3 {
margin: 0 0 12px;
font-size: 16px;
line-height: 32px;
}
h4 {
margin: 0 0 4px;
font-size: 14px;
line-height: 22px;
}
}
.pay-select {
width: 20%;
}
.pay-input {
width: 70%;
}
</style>

View File

@ -39,11 +39,11 @@ function handleEdit(record: EditRecordRow) {
<template>
<div class="step2">
<div class="step2-table">
<div class="mx-auto my-0 w-full">
<BasicTable :data-source="columnsInfo" @register="registerTable" @row-click="handleEdit" />
</div>
<Divider />
<div class="step2-button">
<div class="flex justify-center">
<a-button @click="customResetFunc">
上一步
</a-button>
@ -51,35 +51,12 @@ function handleEdit(record: EditRecordRow) {
提交
</a-button>
</div>
<h3>说明</h3>
<h4>配置字段</h4>
<h3 class="mb-3 text-base">
说明
</h3>
<h4 class="mb-1 text-sm">
配置字段
</h4>
<p> 配置表的字段类型增删改查字典等 </p>
</div>
</template>
<style lang="less" scoped>
.step2 {
&-table {
width: 100%;
margin: 0 auto;
}
&-button {
display: flex;
justify-content: center;
}
h3 {
margin: 0 0 12px;
font-size: 16px;
line-height: 32px;
}
h4 {
margin: 0 0 4px;
font-size: 14px;
line-height: 22px;
}
}
</style>

View File

@ -36,7 +36,7 @@ function handleGoList() {
</script>
<template>
<div class="result-success m-5">
<div class="m-5 bg-white px-8 py-12 dark:bg-dark">
<Result status="success" title="代码生成成功" sub-title="">
<template #extra>
<a-button key="console" type="primary" @click="handleGoList">
@ -53,15 +53,3 @@ function handleGoList() {
<PreviewModal @register="registerPreviewModal" />
</div>
</template>
<style lang="less" scoped>
.result-success {
padding: 48px 32px;
background-color: var(--component-background);
&__content {
padding: 24px 40px;
background-color: @background-color-light;
}
}
</style>