提交 efc0de2a 作者: 方治民

Merge branch '3.x' of https://gitlab.yiring.com/basic/basic-vue-admin into dev

...@@ -15,7 +15,12 @@ module.exports = { ...@@ -15,7 +15,12 @@ module.exports = {
jsx: true, jsx: true,
}, },
}, },
extends: ['plugin:vue/vue3-recommended', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'], extends: [
'@antfu',
'plugin:vue/vue3-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
rules: { rules: {
'vue/script-setup-uses-vars': 'error', 'vue/script-setup-uses-vars': 'error',
'@typescript-eslint/ban-ts-ignore': 'off', '@typescript-eslint/ban-ts-ignore': 'off',
...@@ -69,5 +74,9 @@ module.exports = { ...@@ -69,5 +74,9 @@ module.exports = {
], ],
'vue/multi-word-component-names': 'off', 'vue/multi-word-component-names': 'off',
'vue/no-reserved-component-names': 'off', 'vue/no-reserved-component-names': 'off',
'vue/no-constant-condition': 'off',
'no-console': 'off',
'antfu/if-newline': 'off',
'symbol-description': 'off',
}, },
} }
{ {
"recommendations": [ "recommendations": [
"vue.volar", "vue.volar",
"dbaeumer.vscode-eslint", "dbaeumer.vscode-eslint",
"stylelint.vscode-stylelint", "stylelint.vscode-stylelint",
"DavidAnson.vscode-markdownlint",
"esbenp.prettier-vscode", "esbenp.prettier-vscode",
"mrmlnc.vscode-less", "EditorConfig.EditorConfig",
"lokalise.i18n-ally",
"antfu.unocss",
"antfu.iconify", "antfu.iconify",
"lokalise.i18n-ally",
"mrmlnc.vscode-less",
"heybourn.headwind",
"aaron-bond.better-comments",
"shardulm94.trailing-spaces",
"formulahendry.auto-close-tag",
"formulahendry.auto-rename-tag",
"vincaslt.highlight-matching-tag",
"ChakrounAnas.turbo-console-log",
"mikestead.dotenv", "mikestead.dotenv",
"heybourn.headwind" "redhat.vscode-yaml",
"eamodio.gitlens",
"christian-kohler.path-intellisense",
"IBM.output-colorizer",
"PKief.material-icon-theme"
] ]
} }
{ {
"typescript.tsdk": "./node_modules/typescript/lib", "typescript.tsdk": "./node_modules/typescript/lib",
"volar.tsPlugin": true,
"volar.tsPluginStatus": false,
"npm.packageManager": "pnpm", "npm.packageManager": "pnpm",
"editor.tabSize": 2, "editor.tabSize": 4,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"files.eol": "\n", "files.eol": "\n",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"i18n-ally.localesPaths": ["src/locales/lang"],
"i18n-ally.keystyle": "nested",
"i18n-ally.readonly": true,
"i18n-ally.sortKeys": true,
"i18n-ally.namespace": true,
"i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}",
"i18n-ally.enabledParsers": ["ts", "json"],
"i18n-ally.sourceLanguage": "en",
"i18n-ally.displayLanguage": "zh-Hans",
"i18n-ally.enabledFrameworks": ["vue", "react"],
"stylelint.enable": true,
"stylelint.validate": ["css", "less", "postcss", "scss", "vue", "sass"],
"path-intellisense.mappings": {
"/@/": "${workspaceRoot}/src"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[less]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"[vue]": {
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.fixAll.stylelint": true
}
},
"search.exclude": { "search.exclude": {
"**/node_modules": true, "**/node_modules": true,
"**/*.log": true, "**/*.log": true,
...@@ -29,7 +81,8 @@ ...@@ -29,7 +81,8 @@
"res": true, "res": true,
"screenshots": true, "screenshots": true,
"yarn-error.log": true, "yarn-error.log": true,
"**/.yarn": true "**/.yarn": true,
"**/.pnpm": true
}, },
"files.exclude": { "files.exclude": {
"**/.cache": true, "**/.cache": true,
...@@ -52,91 +105,7 @@ ...@@ -52,91 +105,7 @@
"**/tmp/**": true, "**/tmp/**": true,
"**/bower_components/**": true, "**/bower_components/**": true,
"**/dist/**": true, "**/dist/**": true,
"**/yarn.lock": true "**/yarn.lock": true,
}, "**/pnpm-lock.yaml": true
"stylelint.enable": true, }
"stylelint.validate": ["css", "less", "postcss", "scss", "vue", "sass"],
"path-intellisense.mappings": {
"/@/": "${workspaceRoot}/src"
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[less]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"[vue]": {
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.fixAll.stylelint": true
}
},
"i18n-ally.localesPaths": ["src/locales/lang"],
"i18n-ally.keystyle": "nested",
"i18n-ally.sortKeys": true,
"i18n-ally.namespace": true,
"i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}",
"i18n-ally.enabledParsers": ["ts"],
"i18n-ally.sourceLanguage": "en",
"i18n-ally.displayLanguage": "zh-CN",
"i18n-ally.enabledFrameworks": ["vue", "react"],
"cSpell.words": [
"vben",
"windi",
"browserslist",
"tailwindcss",
"esnext",
"antv",
"tinymce",
"qrcode",
"sider",
"pinia",
"sider",
"nprogress",
"INTLIFY",
"stylelint",
"esno",
"vitejs",
"sortablejs",
"mockjs",
"codemirror",
"iconify",
"commitlint",
"vditor",
"echarts",
"cropperjs",
"logicflow",
"vueuse",
"zxcvbn",
"lintstagedrc",
"brotli",
"tailwindcss",
"sider",
"pnpm",
"antd"
],
"vetur.format.scriptInitialIndent": true,
"vetur.format.styleInitialIndent": true,
"vetur.validation.script": false
} }
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { MockMethod } from 'vite-plugin-mock' import type { MockMethod } from 'vite-plugin-mock'
/** /**
* 构建 URL 请求地址 * 构建 URL 请求地址
......
import { MockMethod } from 'vite-plugin-mock' import type { MockMethod } from 'vite-plugin-mock'
import { Response } from '../_util' import { Response } from '../_util'
import * as HTTP from '../../src/api/types' import * as HTTP from '../../src/api/types'
......
import { MockMethod } from 'vite-plugin-mock' import type { MockMethod } from 'vite-plugin-mock'
import { Response } from '../_util' import { Response } from '../_util'
const areaList: any[] = [ const areaList: any[] = [
......
import { MockMethod } from 'vite-plugin-mock' import type { MockMethod } from 'vite-plugin-mock'
import { Response } from '../_util' import { Response } from '../_util'
const demoList = (keyword, count = 20) => { const demoList = (keyword, count = 20) => {
......
import { MockMethod } from 'vite-plugin-mock' import type { MockMethod } from 'vite-plugin-mock'
import { Response } from '../_util' import { Response } from '../_util'
const accountList = (() => { const accountList = (() => {
...@@ -101,12 +101,11 @@ const menuList = (() => { ...@@ -101,12 +101,11 @@ const menuList = (() => {
children.push({ children.push({
id: `${index}-${j}-${k}`, id: `${index}-${j}-${k}`,
type: '2', type: '2',
menuName: '按钮' + (j + 1) + '-' + (k + 1), menuName: `按钮${j + 1}-${k + 1}`,
icon: '', icon: '',
permission: permission: `${
['menu1:view', 'menu2:add', 'menu3:update', 'menu4:del'][index] + ['menu1:view', 'menu2:add', 'menu3:update', 'menu4:del'][index]
':btn' + }:btn${k + 1}`,
(k + 1),
component: [ component: [
'/dashboard/welcome/index', '/dashboard/welcome/index',
'/dashboard/workbench/index', '/dashboard/workbench/index',
...@@ -192,7 +191,7 @@ export default [ ...@@ -192,7 +191,7 @@ export default [
method: 'post', method: 'post',
response: ({ body }) => { response: ({ body }) => {
const { account } = body || {} const { account } = body || {}
if (account && account.indexOf('admin') !== -1) { if (account && account.includes('admin')) {
return Response.no('该字段不能包含admin') return Response.no('该字段不能包含admin')
} else { } else {
return Response.ok(`${account} can use`) return Response.ok(`${account} can use`)
......
import { MockMethod } from 'vite-plugin-mock' import type { MockMethod } from 'vite-plugin-mock'
import { Random } from 'mockjs' import { Random } from 'mockjs'
import { Response } from '../_util' import { Response } from '../_util'
......
import { MockMethod } from 'vite-plugin-mock' import type { MockMethod } from 'vite-plugin-mock'
import { Response } from '../_util' import { Response } from '../_util'
const demoTreeList = (keyword) => { const demoTreeList = (keyword) => {
......
import { MockMethod } from 'vite-plugin-mock' import type { MockMethod } from 'vite-plugin-mock'
import { Request, Response } from '../_util'
import { createFakeUserList } from './user' import { createFakeUserList } from './user'
import { Response, Request } from '../_util'
// single // single
const dashboardRoute = { const dashboardRoute = {
...@@ -241,11 +241,11 @@ export default [ ...@@ -241,11 +241,11 @@ export default [
let menu: Object[] let menu: Object[]
switch (id) { switch (id) {
case '1': case '1':
dashboardRoute.redirect = dashboardRoute.path + '/' + dashboardRoute.children[0].path dashboardRoute.redirect = `${dashboardRoute.path}/${dashboardRoute.children[0].path}`
menu = [dashboardRoute, authRoute, levelRoute, sysRoute, linkRoute] menu = [dashboardRoute, authRoute, levelRoute, sysRoute, linkRoute]
break break
case '2': case '2':
dashboardRoute.redirect = dashboardRoute.path + '/' + dashboardRoute.children[1].path dashboardRoute.redirect = `${dashboardRoute.path}/${dashboardRoute.children[1].path}`
menu = [dashboardRoute, authRoute, levelRoute, linkRoute] menu = [dashboardRoute, authRoute, levelRoute, linkRoute]
break break
default: default:
......
import { IncomingMessage } from 'http' import type { IncomingMessage } from 'node:http'
import Mock from 'mockjs' import Mock from 'mockjs'
import qs from 'qs' import qs from 'qs'
import { MockMethod } from 'vite-plugin-mock' import type { MockMethod } from 'vite-plugin-mock'
import { Response, Request } from '../_util' import { Request, Response } from '../_util'
const parseFormParams = (req: IncomingMessage): Promise<Recordable> => { const parseFormParams = (req: IncomingMessage): Promise<Recordable> => {
return new Promise((resolve) => { return new Promise((resolve) => {
...@@ -12,7 +12,6 @@ const parseFormParams = (req: IncomingMessage): Promise<Recordable> => { ...@@ -12,7 +12,6 @@ const parseFormParams = (req: IncomingMessage): Promise<Recordable> => {
}) })
req.on('end', function () { req.on('end', function () {
resolve(qs.parse(body) as any) resolve(qs.parse(body) as any)
return
}) })
}) })
} }
......
...@@ -102,6 +102,7 @@ ...@@ -102,6 +102,7 @@
"xlsx": "^0.18.5" "xlsx": "^0.18.5"
}, },
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "^0.37.0",
"@commitlint/cli": "^17.5.0", "@commitlint/cli": "^17.5.0",
"@commitlint/config-conventional": "^17.4.4", "@commitlint/config-conventional": "^17.4.4",
"@iconify/json": "^2.2.40", "@iconify/json": "^2.2.40",
......
<template>
<ConfigProvider :locale="getAntdLocale">
<AppProvider>
<RouterView />
</AppProvider>
</ConfigProvider>
</template>
<script lang="ts" setup> <script lang="ts" setup>
import { ConfigProvider } from 'ant-design-vue' import { ConfigProvider } from 'ant-design-vue'
import { AppProvider } from '/@/components/Application' import { AppProvider } from '/@/components/Application'
...@@ -19,3 +11,11 @@ ...@@ -19,3 +11,11 @@
// Listening to page changes and dynamically changing site titles // Listening to page changes and dynamically changing site titles
useTitle() useTitle()
</script> </script>
<template>
<ConfigProvider :locale="getAntdLocale">
<AppProvider>
<RouterView />
</AppProvider>
</ConfigProvider>
</template>
import { defHttp } from '/@/utils/http/axios' import { defHttp } from '/@/utils/http/axios'
import { getMenuListResultModel } from './model/menuModel' import type { getMenuListResultModel } from './model/menuModel'
enum Api { enum Api {
GetMenuList = '/user/getMenuList', GetMenuList = '/user/getMenuList',
......
import { UploadApiResult } from './model/uploadModel' import type { UploadApiResult } from './model/uploadModel'
import { defHttp } from '/@/utils/http/axios' import { defHttp } from '/@/utils/http/axios'
import { UploadFileParams } from '/#/axios' import type { UploadFileParams } from '/#/axios'
import { useGlobSetting } from '/@/hooks/setting' import { useGlobSetting } from '/@/hooks/setting'
const { uploadUrl = '' } = useGlobSetting() const { uploadUrl = '' } = useGlobSetting()
......
import { defHttp } from '/@/utils/http/axios' import { defHttp } from '/@/utils/http/axios'
import { LoginParams, LoginResultModel, GetUserInfoModel } from './model/userModel' import type { GetUserInfoModel, LoginParams, LoginResultModel } from './model/userModel'
import { ErrorMessageMode } from '/#/axios' import type { ErrorMessageMode } from '/#/axios'
enum Api { enum Api {
Login = '/auth/login', Login = '/auth/login',
......
<template>
<div v-if="getShowDarkModeToggle" :class="getClass" @click="toggleDarkMode">
<div :class="`${prefixCls}-inner`"></div>
<SvgIcon size="14" name="sun" />
<SvgIcon size="14" name="moon" />
</div>
</template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, unref } from 'vue' import { computed, unref } from 'vue'
import { SvgIcon } from '/@/components/Icon' import { SvgIcon } from '/@/components/Icon'
...@@ -34,6 +27,15 @@ ...@@ -34,6 +27,15 @@
updateSidebarBgColor() updateSidebarBgColor()
} }
</script> </script>
<template>
<div v-if="getShowDarkModeToggle" :class="getClass" @click="toggleDarkMode">
<div :class="`${prefixCls}-inner`"></div>
<SvgIcon size="14" name="sun" />
<SvgIcon size="14" name="moon" />
</div>
</template>
<style lang="less" scoped> <style lang="less" scoped>
@prefix-cls: ~'@{namespace}-dark-switch'; @prefix-cls: ~'@{namespace}-dark-switch';
......
...@@ -2,25 +2,10 @@ ...@@ -2,25 +2,10 @@
* @Author: Vben * @Author: Vben
* @Description: Multi-language switching component * @Description: Multi-language switching component
--> -->
<template>
<Dropdown
placement="bottom"
:trigger="['click']"
:dropMenuList="localeList"
:selectedKeys="selectedKeys"
@menu-event="handleMenuEvent"
overlayClassName="app-locale-picker-overlay"
>
<span class="cursor-pointer flex items-center">
<Icon icon="ion:language" />
<span v-if="showText" class="ml-1">{{ getLocaleText }}</span>
</span>
</Dropdown>
</template>
<script lang="ts" setup> <script lang="ts" setup>
import type { LocaleType } from '/#/config' import type { LocaleType } from '/#/config'
import type { DropMenu } from '/@/components/Dropdown' import type { DropMenu } from '/@/components/Dropdown'
import { ref, watchEffect, unref, computed } from 'vue' import { computed, ref, unref, watchEffect } from 'vue'
import { Dropdown } from '/@/components/Dropdown' import { Dropdown } from '/@/components/Dropdown'
import { Icon } from '/@/components/Icon' import { Icon } from '/@/components/Icon'
import { useLocale } from '/@/locales/useLocale' import { useLocale } from '/@/locales/useLocale'
...@@ -67,6 +52,22 @@ ...@@ -67,6 +52,22 @@
} }
</script> </script>
<template>
<Dropdown
placement="bottom"
:trigger="['click']"
:dropMenuList="localeList"
:selectedKeys="selectedKeys"
@menu-event="handleMenuEvent"
overlayClassName="app-locale-picker-overlay"
>
<span class="cursor-pointer flex items-center">
<Icon icon="ion:language" />
<span v-if="showText" class="ml-1">{{ getLocaleText }}</span>
</span>
</Dropdown>
</template>
<style lang="less"> <style lang="less">
.app-locale-picker-overlay { .app-locale-picker-overlay {
.ant-dropdown-menu-item { .ant-dropdown-menu-item {
......
...@@ -2,14 +2,6 @@ ...@@ -2,14 +2,6 @@
* @Author: Vben * @Author: Vben
* @Description: logo component * @Description: logo component
--> -->
<template>
<div class="anticon" :class="getAppLogoClass" @click="goHome" :title="version">
<img src="../../../assets/images/logo.png" />
<div class="ml-2 truncate md:opacity-100" :class="getTitleClass" v-show="showTitle">
{{ title }}
</div>
</div>
</template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, unref } from 'vue' import { computed, unref } from 'vue'
import { useGlobSetting } from '/@/hooks/setting' import { useGlobSetting } from '/@/hooks/setting'
...@@ -60,6 +52,16 @@ ...@@ -60,6 +52,16 @@
go(userStore.getUserInfo.homePath || PageEnum.BASE_HOME) go(userStore.getUserInfo.homePath || PageEnum.BASE_HOME)
} }
</script> </script>
<template>
<div class="anticon" :class="getAppLogoClass" @click="goHome" :title="version">
<img src="../../../assets/images/logo.png" />
<div class="ml-2 truncate md:opacity-100" :class="getTitleClass" v-show="showTitle">
{{ title }}
</div>
</div>
</template>
<style lang="less" scoped> <style lang="less" scoped>
@prefix-cls: ~'@{namespace}-app-logo'; @prefix-cls: ~'@{namespace}-app-logo';
......
<script lang="ts"> <script lang="ts">
import { defineComponent, toRefs, ref, unref } from 'vue' import { defineComponent, ref, toRefs, unref } from 'vue'
import { createAppProviderContext } from './useAppContext' import { createAppProviderContext } from './useAppContext'
import { createBreakpointListen } from '/@/hooks/event/useBreakpoint' import { createBreakpointListen } from '/@/hooks/event/useBreakpoint'
import { prefixCls } from '/@/settings/designSetting' import { prefixCls } from '/@/settings/designSetting'
......
<script lang="ts" setup>
import AppSearchKeyItem from './AppSearchKeyItem.vue'
import { useDesign } from '/@/hooks/web/useDesign'
import { useI18n } from '/@/hooks/web/useI18n'
const { prefixCls } = useDesign('app-search-footer')
const { t } = useI18n()
</script>
<template> <template>
<div :class="`${prefixCls}`"> <div :class="`${prefixCls}`">
<AppSearchKeyItem :class="`${prefixCls}-item`" icon="ant-design:enter-outlined" /> <AppSearchKeyItem :class="`${prefixCls}-item`" icon="ant-design:enter-outlined" />
...@@ -10,13 +18,6 @@ ...@@ -10,13 +18,6 @@
</div> </div>
</template> </template>
<script lang="ts" setup>
import AppSearchKeyItem from './AppSearchKeyItem.vue'
import { useDesign } from '/@/hooks/web/useDesign'
import { useI18n } from '/@/hooks/web/useI18n'
const { prefixCls } = useDesign('app-search-footer')
const { t } = useI18n()
</script>
<style lang="less" scoped> <style lang="less" scoped>
@prefix-cls: ~'@{namespace}-app-search-footer'; @prefix-cls: ~'@{namespace}-app-search-footer';
......
<template>
<span :class="$attrs.class">
<Icon :icon="icon" />
</span>
</template>
<script lang="ts" setup> <script lang="ts" setup>
import { Icon } from '/@/components/Icon' import { Icon } from '/@/components/Icon'
defineProps({ defineProps({
icon: String, icon: String,
}) })
</script> </script>
<template>
<span :class="$attrs.class">
<Icon :icon="icon" />
</span>
</template>
<script lang="ts" setup>
import { computed, nextTick, ref, unref, watch } from 'vue'
import { SearchOutlined } from '@ant-design/icons-vue'
import AppSearchFooter from './AppSearchFooter.vue'
import Icon from '/@/components/Icon'
// @ts-expect-error
import vClickOutside from '/@/directives/clickOutside'
import { useDesign } from '/@/hooks/web/useDesign'
import { useRefs } from '/@/hooks/core/useRefs'
import { useMenuSearch } from './useMenuSearch'
import { useI18n } from '/@/hooks/web/useI18n'
import { useAppInject } from '/@/hooks/web/useAppInject'
const props = defineProps({
visible: { type: Boolean },
})
const emit = defineEmits(['close'])
const scrollWrap = ref(null)
const inputRef = ref<Nullable<HTMLElement>>(null)
const { t } = useI18n()
const { prefixCls } = useDesign('app-search-modal')
const [refs, setRefs] = useRefs()
const { getIsMobile } = useAppInject()
const { handleSearch, searchResult, keyword, activeIndex, handleEnter, handleMouseenter } = useMenuSearch(
refs,
scrollWrap,
emit,
)
const getIsNotData = computed(() => !keyword || unref(searchResult).length === 0)
const getClass = computed(() => {
return [
prefixCls,
{
[`${prefixCls}--mobile`]: unref(getIsMobile),
},
]
})
watch(
() => props.visible,
(visible: boolean) => {
visible &&
nextTick(() => {
unref(inputRef)?.focus()
})
},
)
function handleClose() {
searchResult.value = []
emit('close')
}
</script>
<template> <template>
<Teleport to="body"> <Teleport to="body">
<transition name="zoom-fade" mode="out-in"> <transition name="zoom-fade" mode="out-in">
...@@ -57,65 +117,6 @@ ...@@ -57,65 +117,6 @@
</Teleport> </Teleport>
</template> </template>
<script lang="ts" setup>
import { computed, unref, ref, watch, nextTick } from 'vue'
import { SearchOutlined } from '@ant-design/icons-vue'
import AppSearchFooter from './AppSearchFooter.vue'
import Icon from '/@/components/Icon'
// @ts-ignore
import vClickOutside from '/@/directives/clickOutside'
import { useDesign } from '/@/hooks/web/useDesign'
import { useRefs } from '/@/hooks/core/useRefs'
import { useMenuSearch } from './useMenuSearch'
import { useI18n } from '/@/hooks/web/useI18n'
import { useAppInject } from '/@/hooks/web/useAppInject'
const props = defineProps({
visible: { type: Boolean },
})
const emit = defineEmits(['close'])
const scrollWrap = ref(null)
const inputRef = ref<Nullable<HTMLElement>>(null)
const { t } = useI18n()
const { prefixCls } = useDesign('app-search-modal')
const [refs, setRefs] = useRefs()
const { getIsMobile } = useAppInject()
const { handleSearch, searchResult, keyword, activeIndex, handleEnter, handleMouseenter } = useMenuSearch(
refs,
scrollWrap,
emit,
)
const getIsNotData = computed(() => !keyword || unref(searchResult).length === 0)
const getClass = computed(() => {
return [
prefixCls,
{
[`${prefixCls}--mobile`]: unref(getIsMobile),
},
]
})
watch(
() => props.visible,
(visible: boolean) => {
visible &&
nextTick(() => {
unref(inputRef)?.focus()
})
},
)
function handleClose() {
searchResult.value = []
emit('close')
}
</script>
<style lang="less" scoped> <style lang="less" scoped>
@prefix-cls: ~'@{namespace}-app-search-modal'; @prefix-cls: ~'@{namespace}-app-search-modal';
@footer-prefix-cls: ~'@{namespace}-app-search-footer'; @footer-prefix-cls: ~'@{namespace}-app-search-footer';
......
import type { Menu } from '/@/router/types' import type { Menu } from '/@/router/types'
import { ref, onBeforeMount, unref, Ref, nextTick } from 'vue' import type { Ref } from 'vue'
import { nextTick, onBeforeMount, ref, unref } from 'vue'
import { getMenus } from '/@/router/menus' import { getMenus } from '/@/router/menus'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import { filter, forEach } from '/@/utils/helper/treeHelper' import { filter, forEach } from '/@/utils/helper/treeHelper'
......
import { InjectionKey, Ref } from 'vue' import type { InjectionKey, Ref } from 'vue'
import { createContext, useContext } from '/@/hooks/core/useContext' import { createContext, useContext } from '/@/hooks/core/useContext'
export interface AppProviderContextProps { export interface AppProviderContextProps {
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<script lang="ts"> <script lang="ts">
import type { PropType } from 'vue' import type { PropType } from 'vue'
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
import { RoleEnum } from '/@/enums/roleEnum' import type { RoleEnum } from '/@/enums/roleEnum'
import { usePermission } from '/@/hooks/web/usePermission' import { usePermission } from '/@/hooks/web/usePermission'
import { getSlot } from '/@/utils/helper/tsxHelper' import { getSlot } from '/@/utils/helper/tsxHelper'
......
...@@ -2,11 +2,6 @@ ...@@ -2,11 +2,6 @@
* @Author: Vben * @Author: Vben
* @Description: Arrow component with animation * @Description: Arrow component with animation
--> -->
<template>
<span :class="getClass">
<Icon icon="ion:chevron-forward" :style="$attrs.iconStyle" />
</span>
</template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue' import { computed } from 'vue'
import { Icon } from '/@/components/Icon' import { Icon } from '/@/components/Icon'
...@@ -47,6 +42,13 @@ ...@@ -47,6 +42,13 @@
] ]
}) })
</script> </script>
<template>
<span :class="getClass">
<Icon icon="ion:chevron-forward" :style="$attrs.iconStyle" />
</span>
</template>
<style lang="less" scoped> <style lang="less" scoped>
@prefix-cls: ~'@{namespace}-basic-arrow'; @prefix-cls: ~'@{namespace}-basic-arrow';
......
<script lang="tsx"> <script lang="tsx">
import type { CSSProperties, PropType } from 'vue' import type { CSSProperties, PropType } from 'vue'
import { defineComponent, computed, unref } from 'vue' import { computed, defineComponent, unref } from 'vue'
import { Tooltip } from 'ant-design-vue' import { Tooltip } from 'ant-design-vue'
import { InfoCircleOutlined } from '@ant-design/icons-vue' import { InfoCircleOutlined } from '@ant-design/icons-vue'
import { getPopupContainer } from '/@/utils' import { getPopupContainer } from '/@/utils'
import { isString, isArray } from '/@/utils/is' import { isArray, isString } from '/@/utils/is'
import { getSlot } from '/@/utils/helper/tsxHelper' import { getSlot } from '/@/utils/helper/tsxHelper'
import { useDesign } from '/@/hooks/web/useDesign' import { useDesign } from '/@/hooks/web/useDesign'
...@@ -89,6 +89,7 @@ ...@@ -89,6 +89,7 @@
}, },
}) })
</script> </script>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-basic-help'; @prefix-cls: ~'@{namespace}-basic-help';
......
<template>
<span :class="getClass">
<slot></slot>
<BasicHelp :class="`${prefixCls}-help`" v-if="helpMessage" :text="helpMessage" />
</span>
</template>
<script lang="ts" setup> <script lang="ts" setup>
import type { PropType } from 'vue' import type { PropType } from 'vue'
import { useSlots, computed } from 'vue' import { computed, useSlots } from 'vue'
import BasicHelp from './BasicHelp.vue' import BasicHelp from './BasicHelp.vue'
import { useDesign } from '/@/hooks/web/useDesign' import { useDesign } from '/@/hooks/web/useDesign'
...@@ -39,6 +33,14 @@ ...@@ -39,6 +33,14 @@
{ [`${prefixCls}-normal`]: props.normal }, { [`${prefixCls}-normal`]: props.normal },
]) ])
</script> </script>
<template>
<span :class="getClass">
<slot></slot>
<BasicHelp :class="`${prefixCls}-help`" v-if="helpMessage" :text="helpMessage" />
</span>
</template>
<style lang="less" scoped> <style lang="less" scoped>
@prefix-cls: ~'@{namespace}-basic-title'; @prefix-cls: ~'@{namespace}-basic-title';
......
...@@ -2,7 +2,7 @@ import { withInstall } from '/@/utils' ...@@ -2,7 +2,7 @@ import { withInstall } from '/@/utils'
import type { ExtractPropTypes } from 'vue' import type { ExtractPropTypes } from 'vue'
import button from './src/BasicButton.vue' import button from './src/BasicButton.vue'
import popConfirmButton from './src/PopConfirmButton.vue' import popConfirmButton from './src/PopConfirmButton.vue'
import { buttonProps } from './src/props' import type { buttonProps } from './src/props'
export const Button = withInstall(button) export const Button = withInstall(button)
export const PopConfirmButton = withInstall(popConfirmButton) export const PopConfirmButton = withInstall(popConfirmButton)
......
<script lang="ts">
import { Button } from 'ant-design-vue'
import { Icon } from '/@/components/Icon'
import { buttonProps } from './props'
import { useAttrs } from '/@/hooks/core/useAttrs'
export default defineComponent({
name: 'AButton',
components: { Button, Icon },
extends: Button,
inheritAttrs: false,
props: buttonProps,
setup(props) {
// get component class
const attrs = useAttrs({ excludeDefaultKeys: false })
const getButtonClass = computed(() => {
const { color, disabled } = props
return [
{
[`ant-btn-${color}`]: !!color,
[`is-disabled`]: disabled,
},
]
})
// get inherit binding value
const getBindValue = computed(() => ({ ...unref(attrs), ...props }))
return {
getBindValue,
getButtonClass,
}
},
})
</script>
<template> <template>
<Button v-bind="getBindValue" :class="getButtonClass" @click="onClick"> <Button v-bind="getBindValue" :class="getButtonClass" @click="onClick">
<template #default="data"> <template #default="data">
...@@ -7,33 +44,3 @@ ...@@ -7,33 +44,3 @@
</template> </template>
</Button> </Button>
</template> </template>
<script lang="ts">
import { defineComponent } from 'vue'
import { Button } from 'ant-design-vue'
export default defineComponent({
name: 'AButton',
extends: Button,
inheritAttrs: false,
})
</script>
<script lang="ts" setup>
import { computed, unref } from 'vue'
import Icon from '/@/components/Icon/src/Icon.vue'
import { buttonProps } from './props'
import { useAttrs } from '/@/hooks/core/useAttrs'
const props = defineProps(buttonProps)
// get component class
const attrs = useAttrs({ excludeDefaultKeys: false })
const getButtonClass = computed(() => {
const { color, disabled } = props
return [
{
[`ant-btn-${color}`]: !!color,
[`is-disabled`]: disabled,
},
]
})
// get inherit binding value
const getBindValue = computed(() => ({ ...unref(attrs), ...props }))
</script>
<template>
<div class="p-2">
<div class="p-4 mb-2 bg-white">
<BasicForm @register="registerForm" />
</div>
<div class="p-2 bg-white">
<List
:grid="{ gutter: 5, xs: 1, sm: 2, md: 4, lg: 4, xl: 6, xxl: grid }"
:data-source="data"
:pagination="paginationProp"
>
<template #header>
<div class="flex justify-end space-x-2"
><slot name="header"></slot>
<Tooltip>
<template #title>
<div class="w-50">每行显示数量</div
><Slider id="slider" v-bind="sliderProp" v-model:value="grid" @change="sliderChange"
/></template>
<Button><TableOutlined /></Button>
</Tooltip>
<Tooltip @click="fetch">
<template #title>刷新</template>
<Button><RedoOutlined /></Button>
</Tooltip>
</div>
</template>
<template #renderItem="{ item }">
<ListItem>
<Card>
<template #title></template>
<template #cover>
<div :class="height">
<Image :src="item.imgs[0]" />
</div>
</template>
<template #actions>
<!-- <SettingOutlined key="setting" />-->
<EditOutlined key="edit" />
<Dropdown
:trigger="['hover']"
:dropMenuList="[
{
text: '删除',
event: '1',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, item.id),
},
},
]"
popconfirm
>
<EllipsisOutlined key="ellipsis" />
</Dropdown>
</template>
<CardMeta>
<template #title>
<TypographyText :content="item.name" :ellipsis="{ tooltip: item.address }" />
</template>
<template #avatar>
<Avatar :src="item.avatar" />
</template>
<template #description>{{ item.time }}</template>
</CardMeta>
</Card>
</ListItem>
</template>
</List>
</div>
</div>
</template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, onMounted, ref } from 'vue' import { computed, onMounted, ref } from 'vue'
import { EditOutlined, EllipsisOutlined, RedoOutlined, TableOutlined } from '@ant-design/icons-vue' import { EditOutlined, EllipsisOutlined, RedoOutlined, TableOutlined } from '@ant-design/icons-vue'
import { List, Card, Image, Typography, Tooltip, Slider, Avatar } from 'ant-design-vue' import { Avatar, Card, Image, List, Slider, Tooltip, Typography } from 'ant-design-vue'
import { Dropdown } from '/@/components/Dropdown' import { Dropdown } from '/@/components/Dropdown'
import { BasicForm, useForm } from '/@/components/Form' import { BasicForm, useForm } from '/@/components/Form'
import { propTypes } from '/@/utils/propTypes' import { propTypes } from '/@/utils/propTypes'
import { Button } from '/@/components/Button' import { Button } from '/@/components/Button'
import { isFunction } from '/@/utils/is' import { isFunction } from '/@/utils/is'
import { useSlider, grid } from './data' import { grid, useSlider } from './data'
const ListItem = List.Item
const CardMeta = Card.Meta
const TypographyText = Typography.Text
// 获取slider属性
const sliderProp = computed(() => useSlider(4))
// 组件接收参数 // 组件接收参数
const props = defineProps({ const props = defineProps({
// 请求API的参数 // 请求API的参数
params: propTypes.object.def({}), params: propTypes.object.def({}),
//api // api
api: propTypes.func, api: propTypes.func,
}) })
//暴露内部方法 // 暴露内部方法
const emit = defineEmits(['getMethod', 'delete']) const emit = defineEmits(['getMethod', 'delete'])
//数据 const ListItem = List.Item
const CardMeta = Card.Meta
const TypographyText = Typography.Text
// 获取slider属性
const sliderProp = computed(() => useSlider(4))
// 数据
const data = ref([]) const data = ref([])
// 切换每行个数 // 切换每行个数
// cover图片自适应高度 // cover图片自适应高度
//修改pageSize并重新请求数据 // 修改pageSize并重新请求数据
const height = computed(() => { const height = computed(() => {
return `h-${120 - grid.value * 6}` return `h-${120 - grid.value * 6}`
}) })
//表单 // 表单
const [registerForm, { validate }] = useForm({ const [registerForm, { validate }] = useForm({
schemas: [{ field: 'type', component: 'Input', label: '类型' }], schemas: [{ field: 'type', component: 'Input', label: '类型' }],
labelWidth: 80, labelWidth: 80,
...@@ -113,7 +40,7 @@ ...@@ -113,7 +40,7 @@
autoSubmitOnEnter: true, autoSubmitOnEnter: true,
submitFunc: handleSubmit, submitFunc: handleSubmit,
}) })
//表单提交 // 表单提交
async function handleSubmit() { async function handleSubmit() {
const data = await validate() const data = await validate()
await fetch(data) await fetch(data)
...@@ -137,7 +64,7 @@ ...@@ -137,7 +64,7 @@
total.value = res.total total.value = res.total
} }
} }
//分页相关 // 分页相关
const page = ref(1) const page = ref(1)
const pageSize = ref(36) const pageSize = ref(36)
const total = ref(0) const total = ref(0)
...@@ -166,3 +93,77 @@ ...@@ -166,3 +93,77 @@
emit('delete', id) emit('delete', id)
} }
</script> </script>
<template>
<div class="p-2">
<div class="p-4 mb-2 bg-white">
<BasicForm @register="registerForm" />
</div>
<div class="p-2 bg-white">
<List
:grid="{ gutter: 5, xs: 1, sm: 2, md: 4, lg: 4, xl: 6, xxl: grid }"
:data-source="data"
:pagination="paginationProp"
>
<template #header>
<div class="flex justify-end space-x-2"
><slot name="header"></slot>
<Tooltip>
<template #title>
<div class="w-50">每行显示数量</div
><Slider id="slider" v-bind="sliderProp" v-model:value="grid" @change="sliderChange"
/></template>
<Button><TableOutlined /></Button>
</Tooltip>
<Tooltip @click="fetch">
<template #title>刷新</template>
<Button><RedoOutlined /></Button>
</Tooltip>
</div>
</template>
<template #renderItem="{ item }">
<ListItem>
<Card>
<template #title></template>
<template #cover>
<div :class="height">
<Image :src="item.imgs[0]" />
</div>
</template>
<template #actions>
<!-- <SettingOutlined key="setting" /> -->
<EditOutlined key="edit" />
<Dropdown
:trigger="['hover']"
:dropMenuList="[
{
text: '删除',
event: '1',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, item.id),
},
},
]"
popconfirm
>
<EllipsisOutlined key="ellipsis" />
</Dropdown>
</template>
<CardMeta>
<template #title>
<TypographyText :content="item.name" :ellipsis="{ tooltip: item.address }" />
</template>
<template #avatar>
<Avatar :src="item.avatar" />
</template>
<template #description>{{ item.time }}</template>
</CardMeta>
</Card>
</ListItem>
</template>
</List>
</div>
</div>
</template>
<template>
<div ref="wrap">
<slot></slot>
</div>
</template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted } from 'vue' import { onMounted, ref } from 'vue'
import { onClickOutside } from '@vueuse/core' import { onClickOutside } from '@vueuse/core'
const emit = defineEmits(['mounted', 'clickOutside']) const emit = defineEmits(['mounted', 'clickOutside'])
const wrap = ref<ElRef>(null) const wrap = ref<ElRef>(null)
...@@ -17,3 +12,9 @@ ...@@ -17,3 +12,9 @@
emit('mounted') emit('mounted')
}) })
</script> </script>
<template>
<div ref="wrap">
<slot></slot>
</div>
</template>
<template>
<div class="h-full">
<CodeMirrorEditor :value="getValue" @change="handleValueChange" :mode="mode" :readonly="readonly" />
</div>
</template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue' import { computed } from 'vue'
import CodeMirrorEditor from './codemirror/CodeMirror.vue' import CodeMirrorEditor from './codemirror/CodeMirror.vue'
...@@ -47,3 +42,9 @@ ...@@ -47,3 +42,9 @@
emit('change', v) emit('change', v)
} }
</script> </script>
<template>
<div class="h-full">
<CodeMirrorEditor :value="getValue" @change="handleValueChange" :mode="mode" :readonly="readonly" />
</div>
</template>
<template>
<div class="relative !h-full w-full overflow-hidden" ref="el"></div>
</template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted, onUnmounted, watchEffect, watch, unref, nextTick } from 'vue' import { nextTick, onMounted, onUnmounted, ref, unref, watch, watchEffect } from 'vue'
import { useDebounceFn } from '@vueuse/core' import { useDebounceFn } from '@vueuse/core'
import { useAppStore } from '/@/store/modules/app' import { useAppStore } from '/@/store/modules/app'
import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn' import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn'
...@@ -45,7 +41,7 @@ ...@@ -45,7 +41,7 @@
await nextTick() await nextTick()
const oldValue = editor?.getValue() const oldValue = editor?.getValue()
if (value !== oldValue) { if (value !== oldValue) {
editor?.setValue(value ? value : '') editor?.setValue(value || '')
} }
}, },
{ flush: 'post' }, { flush: 'post' },
...@@ -108,3 +104,7 @@ ...@@ -108,3 +104,7 @@
editor = null editor = null
}) })
</script> </script>
<template>
<div class="relative !h-full w-full overflow-hidden" ref="el"></div>
</template>
<template>
<vue-json-pretty :path="'res'" :deep="3" :showLength="true" :data="data" />
</template>
<script lang="ts" setup> <script lang="ts" setup>
import VueJsonPretty from 'vue-json-pretty' import VueJsonPretty from 'vue-json-pretty'
import 'vue-json-pretty/lib/styles.css' import 'vue-json-pretty/lib/styles.css'
...@@ -10,3 +6,7 @@ ...@@ -10,3 +6,7 @@
data: Object, data: Object,
}) })
</script> </script>
<template>
<VueJsonPretty path="res" :deep="3" :showLength="true" :data="data" />
</template>
<template>
<transition-group class="h-full w-full" v-bind="$attrs" ref="elRef" :name="transitionName" :tag="tag" mode="out-in">
<div key="component" v-if="isInit">
<slot :loading="loading"></slot>
</div>
<div key="skeleton" v-else>
<slot name="skeleton" v-if="$slots.skeleton"></slot>
<Skeleton v-else />
</div>
</transition-group>
</template>
<script lang="ts"> <script lang="ts">
import type { PropType } from 'vue' import type { PropType } from 'vue'
import { defineComponent, reactive, onMounted, ref, toRef, toRefs } from 'vue' import { defineComponent, onMounted, reactive, ref, toRef, toRefs } from 'vue'
import { Skeleton } from 'ant-design-vue' import { Skeleton } from 'ant-design-vue'
import { useTimeoutFn } from '/@/hooks/core/useTimeout' import { useTimeoutFn } from '/@/hooks/core/useTimeout'
import { useIntersectionObserver } from '/@/hooks/event/useIntersectionObserver' import { useIntersectionObserver } from '/@/hooks/event/useIntersectionObserver'
...@@ -136,3 +125,15 @@ ...@@ -136,3 +125,15 @@
}, },
}) })
</script> </script>
<template>
<transition-group class="h-full w-full" v-bind="$attrs" ref="elRef" :name="transitionName" :tag="tag" mode="out-in">
<div key="component" v-if="isInit">
<slot :loading="loading"></slot>
</div>
<div key="skeleton" v-else>
<slot name="skeleton" v-if="$slots.skeleton"></slot>
<Skeleton v-else />
</div>
</transition-group>
</template>
<template>
<Scrollbar ref="scrollbarRef" class="scroll-container" v-bind="$attrs">
<slot></slot>
</Scrollbar>
</template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref, unref, nextTick } from 'vue' import { defineComponent, nextTick, ref, unref } from 'vue'
import { Scrollbar, ScrollbarType } from '/@/components/Scrollbar' import type { ScrollbarType } from '/@/components/Scrollbar'
import { Scrollbar } from '/@/components/Scrollbar'
import { useScrollTo } from '/@/hooks/event/useScrollTo' import { useScrollTo } from '/@/hooks/event/useScrollTo'
export default defineComponent({ export default defineComponent({
...@@ -76,6 +71,13 @@ ...@@ -76,6 +71,13 @@
}, },
}) })
</script> </script>
<template>
<Scrollbar ref="scrollbarRef" class="scroll-container" v-bind="$attrs">
<slot></slot>
</Scrollbar>
</template>
<style lang="less"> <style lang="less">
.scroll-container { .scroll-container {
width: 100%; width: 100%;
......
<template>
<div :class="prefixCls">
<CollapseHeader v-bind="props" :prefixCls="prefixCls" :show="show" @expand="handleExpand">
<template #title>
<slot name="title"></slot>
</template>
<template #action>
<slot name="action"></slot>
</template>
</CollapseHeader>
<div class="p-2">
<CollapseTransition :enable="canExpan">
<Skeleton v-if="loading" :active="loading" />
<div :class="`${prefixCls}__body`" v-else v-show="show">
<slot></slot>
</div>
</CollapseTransition>
</div>
<div :class="`${prefixCls}__footer`" v-if="$slots.footer">
<slot name="footer"></slot>
</div>
</div>
</template>
<script lang="ts" setup> <script lang="ts" setup>
import type { PropType } from 'vue' import type { PropType } from 'vue'
import { ref } from 'vue' import { ref } from 'vue'
...@@ -74,6 +50,32 @@ ...@@ -74,6 +50,32 @@
handleExpand, handleExpand,
}) })
</script> </script>
<template>
<div :class="prefixCls">
<CollapseHeader v-bind="props" :prefixCls="prefixCls" :show="show" @expand="handleExpand">
<template #title>
<slot name="title"></slot>
</template>
<template #action>
<slot name="action"></slot>
</template>
</CollapseHeader>
<div class="p-2">
<CollapseTransition :enable="canExpan">
<Skeleton v-if="loading" :active="loading" />
<div :class="`${prefixCls}__body`" v-else v-show="show">
<slot></slot>
</div>
</CollapseTransition>
</div>
<div :class="`${prefixCls}__footer`" v-if="$slots.footer">
<slot name="footer"></slot>
</div>
</div>
</template>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-collapse-container'; @prefix-cls: ~'@{namespace}-collapse-container';
.@{prefix-cls} { .@{prefix-cls} {
......
<template>
<div :class="[`${prefixCls}__header px-2 py-5`, $attrs.class]">
<BasicTitle :helpMessage="helpMessage" normal>
<template v-if="title">
{{ title }}
</template>
<template v-else>
<slot name="title"></slot>
</template>
</BasicTitle>
<div :class="`${prefixCls}__action`">
<slot name="action"></slot>
<BasicArrow v-if="canExpan" up :expand="show" @click="$emit('expand')" />
</div>
</div>
</template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
import { BasicArrow, BasicTitle } from '/@/components/Basic' import { BasicArrow, BasicTitle } from '/@/components/Basic'
...@@ -36,3 +20,20 @@ ...@@ -36,3 +20,20 @@
emits: ['expand'], emits: ['expand'],
}) })
</script> </script>
<template>
<div :class="[`${prefixCls}__header px-2 py-5`, $attrs.class]">
<BasicTitle :helpMessage="helpMessage" normal>
<template v-if="title">
{{ title }}
</template>
<template v-else>
<slot name="title"></slot>
</template>
</BasicTitle>
<div :class="`${prefixCls}__action`">
<slot name="action"></slot>
<BasicArrow v-if="canExpan" up :expand="show" @click="$emit('expand')" />
</div>
</div>
</template>
<script lang="tsx"> <script lang="tsx">
import type { ContextMenuItem, ItemContentProps, Axis } from './typing' import type { CSSProperties, FunctionalComponent, PropType } from 'vue'
import type { FunctionalComponent, CSSProperties, PropType } from 'vue' import { computed, defineComponent, nextTick, onMounted, onUnmounted, ref, unref } from 'vue'
import { defineComponent, nextTick, onMounted, computed, ref, unref, onUnmounted } from 'vue'
import Icon from '/@/components/Icon' import Icon from '/@/components/Icon'
import { Menu, Divider } from 'ant-design-vue' import { Divider, Menu } from 'ant-design-vue'
import type { Axis, ContextMenuItem, ItemContentProps } from './typing'
const prefixCls = 'context-menu' const prefixCls = 'context-menu'
const props = { const props = {
width: { type: Number, default: 156 }, width: { type: Number, default: 156 },
...@@ -120,6 +120,7 @@ ...@@ -120,6 +120,7 @@
}, },
}) })
</script> </script>
<style lang="less"> <style lang="less">
@default-height: 42px !important; @default-height: 42px !important;
@small-height: 36px !important; @small-height: 36px !important;
......
import { createVNode, render } from 'vue'
import contextMenuVue from './ContextMenu.vue' import contextMenuVue from './ContextMenu.vue'
import { isClient } from '/@/utils/is' import { isClient } from '/@/utils/is'
import { CreateContextOptions, ContextMenuProps } from './typing' import type { ContextMenuProps, CreateContextOptions } from './typing'
import { createVNode, render } from 'vue'
const menuManager: { const menuManager: {
domList: Element[] domList: Element[]
......
<template>
<Button v-bind="$attrs" :disabled="isStart" @click="handleStart" :loading="loading">
{{ getButtonText }}
</Button>
</template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref, watchEffect, computed, unref } from 'vue' import { computed, defineComponent, ref, unref, watchEffect } from 'vue'
import { Button } from 'ant-design-vue' import { Button } from 'ant-design-vue'
import { useCountdown } from './useCountdown' import { useCountdown } from './useCountdown'
import { isFunction } from '/@/utils/is' import { isFunction } from '/@/utils/is'
...@@ -60,3 +55,9 @@ ...@@ -60,3 +55,9 @@
}, },
}) })
</script> </script>
<template>
<Button v-bind="$attrs" :disabled="isStart" @click="handleStart" :loading="loading">
{{ getButtonText }}
</Button>
</template>
<template>
<a-input v-bind="$attrs" :class="prefixCls" :size="size" :value="state">
<template #addonAfter>
<CountButton :size="size" :count="count" :value="state" :beforeStartFunc="sendCodeApi" />
</template>
<template #[item]="data" v-for="item in Object.keys($slots).filter((k) => k !== 'addonAfter')">
<slot :name="item" v-bind="data || {}"></slot>
</template>
</a-input>
</template>
<script lang="ts"> <script lang="ts">
import { defineComponent, PropType } from 'vue' import type { PropType } from 'vue'
import { defineComponent } from 'vue'
import CountButton from './CountButton.vue' import CountButton from './CountButton.vue'
import { useDesign } from '/@/hooks/web/useDesign' import { useDesign } from '/@/hooks/web/useDesign'
import { useRuleFormItem } from '/@/hooks/component/useFormItem' import { useRuleFormItem } from '/@/hooks/component/useFormItem'
...@@ -37,6 +28,18 @@ ...@@ -37,6 +28,18 @@
}, },
}) })
</script> </script>
<template>
<a-input v-bind="$attrs" :class="prefixCls" :size="size" :value="state">
<template #addonAfter>
<CountButton :size="size" :count="count" :value="state" :beforeStartFunc="sendCodeApi" />
</template>
<template #[item]="data" v-for="item in Object.keys($slots).filter((k) => k !== 'addonAfter')">
<slot :name="item" v-bind="data || {}"></slot>
</template>
</a-input>
</template>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-countdown-input'; @prefix-cls: ~'@{namespace}-countdown-input';
......
<template>
<span :style="{ color }">
{{ value }}
</span>
</template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref, computed, watchEffect, unref, onMounted, watch } from 'vue' import { computed, defineComponent, onMounted, ref, unref, watch, watchEffect } from 'vue'
import { useTransition, TransitionPresets } from '@vueuse/core' import { TransitionPresets, useTransition } from '@vueuse/core'
import { isNumber } from '/@/utils/is' import { isNumber } from '/@/utils/is'
const props = { const props = {
...@@ -98,7 +93,7 @@ ...@@ -98,7 +93,7 @@
const rgx = /(\d+)(\d{3})/ const rgx = /(\d+)(\d{3})/
if (separator && !isNumber(separator)) { if (separator && !isNumber(separator)) {
while (rgx.test(x1)) { while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + separator + '$2') x1 = x1.replace(rgx, `$1${separator}$2`)
} }
} }
return prefix + x1 + x2 + suffix return prefix + x1 + x2 + suffix
...@@ -108,3 +103,9 @@ ...@@ -108,3 +103,9 @@
}, },
}) })
</script> </script>
<template>
<span :style="{ color }">
{{ value }}
</span>
</template>
<script lang="ts">
import type { CropendResult, Cropper } from './typing'
import { defineComponent, ref } from 'vue'
import CropperImage from './Cropper.vue'
import { Avatar, Space, Tooltip, Upload } from 'ant-design-vue'
import { useDesign } from '/@/hooks/web/useDesign'
import { BasicModal, useModalInner } from '/@/components/Modal'
import { dataURLtoBlob } from '/@/utils/file/base64Conver'
import { isFunction } from '/@/utils/is'
import { useI18n } from '/@/hooks/web/useI18n'
interface apiFunParams {
file: Blob
name: string
filename: string
}
const props = {
circled: { type: Boolean, default: true },
uploadApi: {
type: Function as PropType<(params: apiFunParams) => Promise<any>>,
},
}
export default defineComponent({
name: 'CropperModal',
components: { BasicModal, Space, CropperImage, Upload, Avatar, Tooltip },
props,
emits: ['uploadSuccess', 'register'],
setup(props, { emit }) {
let filename = ''
const src = ref('')
const previewSource = ref('')
const cropper = ref<Cropper>()
let scaleX = 1
let scaleY = 1
const { prefixCls } = useDesign('cropper-am')
const [register, { closeModal, setModalProps }] = useModalInner()
const { t } = useI18n()
// Block upload
function handleBeforeUpload(file: File) {
const reader = new FileReader()
reader.readAsDataURL(file)
src.value = ''
previewSource.value = ''
reader.onload = function (e) {
src.value = (e.target?.result as string) ?? ''
filename = file.name
}
return false
}
function handleCropend({ imgBase64 }: CropendResult) {
previewSource.value = imgBase64
}
function handleReady(cropperInstance: Cropper) {
cropper.value = cropperInstance
}
function handlerToolbar(event: string, arg?: number) {
if (event === 'scaleX') {
scaleX = arg = scaleX === -1 ? 1 : -1
}
if (event === 'scaleY') {
scaleY = arg = scaleY === -1 ? 1 : -1
}
cropper?.value?.[event]?.(arg)
}
async function handleOk() {
const uploadApi = props.uploadApi
if (uploadApi && isFunction(uploadApi)) {
const blob = dataURLtoBlob(previewSource.value)
try {
setModalProps({ confirmLoading: true })
const result = await uploadApi({ name: 'file', file: blob, filename })
emit('uploadSuccess', { source: previewSource.value, data: result.data })
closeModal()
} finally {
setModalProps({ confirmLoading: false })
}
}
}
return {
t,
prefixCls,
src,
register,
previewSource,
handleBeforeUpload,
handleCropend,
handleReady,
handlerToolbar,
handleOk,
}
},
})
</script>
<template> <template>
<BasicModal <BasicModal
v-bind="$attrs" v-bind="$attrs"
...@@ -110,105 +214,6 @@ ...@@ -110,105 +214,6 @@
</div> </div>
</BasicModal> </BasicModal>
</template> </template>
<script lang="ts">
import type { CropendResult, Cropper } from './typing'
import { defineComponent, ref } from 'vue'
import CropperImage from './Cropper.vue'
import { Space, Upload, Avatar, Tooltip } from 'ant-design-vue'
import { useDesign } from '/@/hooks/web/useDesign'
import { BasicModal, useModalInner } from '/@/components/Modal'
import { dataURLtoBlob } from '/@/utils/file/base64Conver'
import { isFunction } from '/@/utils/is'
import { useI18n } from '/@/hooks/web/useI18n'
type apiFunParams = { file: Blob; name: string; filename: string }
const props = {
circled: { type: Boolean, default: true },
uploadApi: {
type: Function as PropType<(params: apiFunParams) => Promise<any>>,
},
}
export default defineComponent({
name: 'CropperModal',
components: { BasicModal, Space, CropperImage, Upload, Avatar, Tooltip },
props,
emits: ['uploadSuccess', 'register'],
setup(props, { emit }) {
let filename = ''
const src = ref('')
const previewSource = ref('')
const cropper = ref<Cropper>()
let scaleX = 1
let scaleY = 1
const { prefixCls } = useDesign('cropper-am')
const [register, { closeModal, setModalProps }] = useModalInner()
const { t } = useI18n()
// Block upload
function handleBeforeUpload(file: File) {
const reader = new FileReader()
reader.readAsDataURL(file)
src.value = ''
previewSource.value = ''
reader.onload = function (e) {
src.value = (e.target?.result as string) ?? ''
filename = file.name
}
return false
}
function handleCropend({ imgBase64 }: CropendResult) {
previewSource.value = imgBase64
}
function handleReady(cropperInstance: Cropper) {
cropper.value = cropperInstance
}
function handlerToolbar(event: string, arg?: number) {
if (event === 'scaleX') {
scaleX = arg = scaleX === -1 ? 1 : -1
}
if (event === 'scaleY') {
scaleY = arg = scaleY === -1 ? 1 : -1
}
cropper?.value?.[event]?.(arg)
}
async function handleOk() {
const uploadApi = props.uploadApi
if (uploadApi && isFunction(uploadApi)) {
const blob = dataURLtoBlob(previewSource.value)
try {
setModalProps({ confirmLoading: true })
const result = await uploadApi({ name: 'file', file: blob, filename })
emit('uploadSuccess', { source: previewSource.value, data: result.data })
closeModal()
} finally {
setModalProps({ confirmLoading: false })
}
}
}
return {
t,
prefixCls,
src,
register,
previewSource,
handleBeforeUpload,
handleCropend,
handleReady,
handlerToolbar,
handleOk,
}
},
})
</script>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-cropper-am'; @prefix-cls: ~'@{namespace}-cropper-am';
......
<template>
<div :class="getClass" :style="getWrapperStyle">
<img v-show="isReady" ref="imgElRef" :src="src" :alt="alt" :crossorigin="crossorigin" :style="getImageStyle" />
</div>
</template>
<script lang="ts"> <script lang="ts">
import type { CSSProperties } from 'vue' import type { CSSProperties } from 'vue'
import { defineComponent, onMounted, ref, unref, computed, onUnmounted } from 'vue' import { computed, defineComponent, onMounted, onUnmounted, ref, unref } from 'vue'
import Cropper from 'cropperjs' import Cropper from 'cropperjs'
import 'cropperjs/dist/cropper.css' import 'cropperjs/dist/cropper.css'
import { useDesign } from '/@/hooks/web/useDesign' import { useDesign } from '/@/hooks/web/useDesign'
...@@ -81,7 +76,7 @@ ...@@ -81,7 +76,7 @@
}) })
const getWrapperStyle = computed((): CSSProperties => { const getWrapperStyle = computed((): CSSProperties => {
return { height: `${props.height}`.replace(/px/, '') + 'px' } return { height: `${`${props.height}`.replace(/px/, '')}px` }
}) })
onMounted(init) onMounted(init)
...@@ -125,13 +120,13 @@ ...@@ -125,13 +120,13 @@
if (!cropper.value) { if (!cropper.value) {
return return
} }
let imgInfo = cropper.value.getData() const imgInfo = cropper.value.getData()
const canvas = props.circled ? getRoundedCanvas() : cropper.value.getCroppedCanvas() const canvas = props.circled ? getRoundedCanvas() : cropper.value.getCroppedCanvas()
canvas.toBlob((blob) => { canvas.toBlob((blob) => {
if (!blob) { if (!blob) {
return return
} }
let fileReader: FileReader = new FileReader() const fileReader: FileReader = new FileReader()
fileReader.readAsDataURL(blob) fileReader.readAsDataURL(blob)
fileReader.onloadend = (e) => { fileReader.onloadend = (e) => {
emit('cropend', { emit('cropend', {
...@@ -167,6 +162,13 @@ ...@@ -167,6 +162,13 @@
}, },
}) })
</script> </script>
<template>
<div :class="getClass" :style="getWrapperStyle">
<img v-show="isReady" ref="imgElRef" :src="src" :alt="alt" :crossorigin="crossorigin" :style="getImageStyle" />
</div>
</template>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-cropper-image'; @prefix-cls: ~'@{namespace}-cropper-image';
......
<template>
<div :class="getClass" :style="getStyle">
<div :class="`${prefixCls}-image-wrapper`" :style="getImageWrapperStyle" @click="openModal">
<div :class="`${prefixCls}-image-mask`" :style="getImageWrapperStyle">
<Icon
icon="ant-design:cloud-upload-outlined"
:size="getIconWidth"
:style="getImageWrapperStyle"
color="#d6d6d6"
/>
</div>
<img :src="sourceValue" v-if="sourceValue" alt="avatar" />
</div>
<a-button :class="`${prefixCls}-upload-btn`" @click="openModal" v-if="showBtn" v-bind="btnProps">
{{ btnText ? btnText : t('component.cropper.selectImage') }}
</a-button>
<CopperModal
@register="register"
@upload-success="handleUploadSuccess"
:uploadApi="uploadApi"
:src="sourceValue"
/>
</div>
</template>
<script lang="ts"> <script lang="ts">
import { defineComponent, computed, CSSProperties, unref, ref, watchEffect, watch, PropType } from 'vue' import type { CSSProperties, PropType } from 'vue'
import { computed, defineComponent, ref, unref, watch, watchEffect } from 'vue'
import CopperModal from './CopperModal.vue' import CopperModal from './CopperModal.vue'
import { useDesign } from '/@/hooks/web/useDesign' import { useDesign } from '/@/hooks/web/useDesign'
import { useModal } from '/@/components/Modal' import { useModal } from '/@/components/Modal'
...@@ -52,8 +28,8 @@ ...@@ -52,8 +28,8 @@
const { createMessage } = useMessage() const { createMessage } = useMessage()
const { t } = useI18n() const { t } = useI18n()
const getClass = computed(() => [prefixCls]) const getClass = computed(() => [prefixCls])
const getWidth = computed(() => `${props.width}`.replace(/px/, '') + 'px') const getWidth = computed(() => `${`${props.width}`.replace(/px/, '')}px`)
const getIconWidth = computed(() => parseInt(`${props.width}`.replace(/px/, '')) / 2 + 'px') const getIconWidth = computed(() => `${parseInt(`${props.width}`.replace(/px/, '')) / 2}px`)
const getStyle = computed((): CSSProperties => ({ width: unref(getWidth) })) const getStyle = computed((): CSSProperties => ({ width: unref(getWidth) }))
const getImageWrapperStyle = computed( const getImageWrapperStyle = computed(
(): CSSProperties => ({ width: unref(getWidth), height: unref(getWidth) }), (): CSSProperties => ({ width: unref(getWidth), height: unref(getWidth) }),
...@@ -89,6 +65,32 @@ ...@@ -89,6 +65,32 @@
}) })
</script> </script>
<template>
<div :class="getClass" :style="getStyle">
<div :class="`${prefixCls}-image-wrapper`" :style="getImageWrapperStyle" @click="openModal">
<div :class="`${prefixCls}-image-mask`" :style="getImageWrapperStyle">
<Icon
icon="ant-design:cloud-upload-outlined"
:size="getIconWidth"
:style="getImageWrapperStyle"
color="#d6d6d6"
/>
</div>
<img :src="sourceValue" v-if="sourceValue" alt="avatar" />
</div>
<a-button :class="`${prefixCls}-upload-btn`" @click="openModal" v-if="showBtn" v-bind="btnProps">
{{ btnText ? btnText : t('component.cropper.selectImage') }}
</a-button>
<CopperModal
@register="register"
@upload-success="handleUploadSuccess"
:uploadApi="uploadApi"
:src="sourceValue"
/>
</div>
</template>
<style lang="less" scoped> <style lang="less" scoped>
@prefix-cls: ~'@{namespace}-cropper-avatar'; @prefix-cls: ~'@{namespace}-cropper-avatar';
.@{prefix-cls} { .@{prefix-cls} {
......
<script lang="tsx"> <script lang="tsx">
import type { DescriptionProps, DescInstance, DescItem } from './typing' import type { DescInstance, DescItem, DescriptionProps } from './typing'
import type { DescriptionsProps } from 'ant-design-vue/es/descriptions/index' import type { DescriptionsProps } from 'ant-design-vue/es/descriptions/index'
import type { CSSProperties } from 'vue' import type { CSSProperties } from 'vue'
import type { CollapseContainerOptions } from '/@/components/Container/index' import type { CollapseContainerOptions } from '/@/components/Container/index'
import { defineComponent, computed, ref, unref, toRefs } from 'vue' import { computed, defineComponent, ref, toRefs, unref } from 'vue'
import { get } from 'lodash-es' import { get } from 'lodash-es'
import { Descriptions } from 'ant-design-vue' import { Descriptions } from 'ant-design-vue'
import { CollapseContainer } from '/@/components/Container/index' import { CollapseContainer } from '/@/components/Container/index'
...@@ -107,7 +107,7 @@ ...@@ -107,7 +107,7 @@
return null return null
} }
const getField = get(_data, field) const getField = get(_data, field)
if (getField && !toRefs(_data).hasOwnProperty(field)) { if (getField && !Object.prototype.hasOwnProperty.call(toRefs(_data), field)) {
return isFunction(render) ? render('', _data) : '' return isFunction(render) ? render('', _data) : ''
} }
return isFunction(render) ? render(getField, _data) : getField ?? '' return isFunction(render) ? render(getField, _data) : getField ?? ''
......
import type { VNode, CSSProperties } from 'vue' import type { CSSProperties, VNode } from 'vue'
import type { CollapseContainerOptions } from '/@/components/Container/index' import type { CollapseContainerOptions } from '/@/components/Container/index'
import type { DescriptionsProps } from 'ant-design-vue/es/descriptions/index' import type { DescriptionsProps } from 'ant-design-vue/es/descriptions/index'
......
import type { DescriptionProps, DescInstance, UseDescReturnType } from './typing' import type { DescInstance, DescriptionProps, UseDescReturnType } from './typing'
import { ref, getCurrentInstance, unref } from 'vue' import { getCurrentInstance, ref, unref } from 'vue'
import { isProdMode } from '/@/utils/env' import { isProdMode } from '/@/utils/env'
export function useDescription(props?: Partial<DescriptionProps>): UseDescReturnType { export function useDescription(props?: Partial<DescriptionProps>): UseDescReturnType {
......
<template>
<Drawer :class="prefixCls" @close="onClose" v-bind="getBindValues">
<template #title v-if="!$slots.title">
<DrawerHeader
:title="getMergeProps.title"
:isDetail="isDetail"
:showDetailBack="showDetailBack"
@close="onClose"
>
<template #titleToolbar>
<slot name="titleToolbar"></slot>
</template>
</DrawerHeader>
</template>
<template v-else #title>
<slot name="title"></slot>
</template>
<ScrollContainer
:style="getScrollContentStyle"
v-loading="getLoading"
:loading-tip="loadingText || t('common.loadingText')"
>
<slot></slot>
</ScrollContainer>
<DrawerFooter v-bind="getProps" @close="onClose" @ok="handleOk" :height="getFooterHeight">
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data || {}"></slot>
</template>
</DrawerFooter>
</Drawer>
</template>
<script lang="ts"> <script lang="ts">
import type { DrawerInstance, DrawerProps } from './typing' import type { DrawerInstance, DrawerProps } from './typing'
import type { CSSProperties } from 'vue' import type { CSSProperties } from 'vue'
import { defineComponent, ref, computed, watch, unref, nextTick, toRaw, getCurrentInstance } from 'vue' import { computed, defineComponent, getCurrentInstance, nextTick, ref, toRaw, unref, watch } from 'vue'
import { Drawer } from 'ant-design-vue' import { Drawer } from 'ant-design-vue'
import { useI18n } from '/@/hooks/web/useI18n' import { useI18n } from '/@/hooks/web/useI18n'
import { isFunction, isNumber } from '/@/utils/is' import { isFunction, isNumber } from '/@/utils/is'
...@@ -59,7 +27,7 @@ ...@@ -59,7 +27,7 @@
const { prefixVar, prefixCls } = useDesign('basic-drawer') const { prefixVar, prefixCls } = useDesign('basic-drawer')
const drawerInstance: DrawerInstance = { const drawerInstance: DrawerInstance = {
setDrawerProps: setDrawerProps, setDrawerProps,
emitVisible: undefined, emitVisible: undefined,
} }
...@@ -181,6 +149,40 @@ ...@@ -181,6 +149,40 @@
}, },
}) })
</script> </script>
<template>
<Drawer :class="prefixCls" @close="onClose" v-bind="getBindValues">
<template #title v-if="!$slots.title">
<DrawerHeader
:title="getMergeProps.title"
:isDetail="isDetail"
:showDetailBack="showDetailBack"
@close="onClose"
>
<template #titleToolbar>
<slot name="titleToolbar"></slot>
</template>
</DrawerHeader>
</template>
<template v-else #title>
<slot name="title"></slot>
</template>
<ScrollContainer
:style="getScrollContentStyle"
v-loading="getLoading"
:loading-tip="loadingText || t('common.loadingText')"
>
<slot></slot>
</ScrollContainer>
<DrawerFooter v-bind="getProps" @close="onClose" @ok="handleOk" :height="getFooterHeight">
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data || {}"></slot>
</template>
</DrawerFooter>
</Drawer>
</template>
<style lang="less"> <style lang="less">
@header-height: 60px; @header-height: 60px;
@detail-header-height: 40px; @detail-header-height: 40px;
......
<template>
<div :class="prefixCls" :style="getStyle" v-if="showFooter || $slots.footer">
<template v-if="!$slots.footer">
<slot name="insertFooter"></slot>
<a-button v-bind="cancelButtonProps" @click="handleClose" class="mr-2" v-if="showCancelBtn">
{{ cancelText }}
</a-button>
<slot name="centerFooter"></slot>
<a-button
:type="okType"
@click="handleOk"
v-bind="okButtonProps"
class="mr-2"
:loading="confirmLoading"
v-if="showOkBtn"
>
{{ okText }}
</a-button>
<slot name="appendFooter"></slot>
</template>
<template v-else>
<slot name="footer"></slot>
</template>
</div>
</template>
<script lang="ts"> <script lang="ts">
import type { CSSProperties } from 'vue' import type { CSSProperties } from 'vue'
import { defineComponent, computed } from 'vue' import { computed, defineComponent } from 'vue'
import { useDesign } from '/@/hooks/web/useDesign' import { useDesign } from '/@/hooks/web/useDesign'
import { footerProps } from '../props' import { footerProps } from '../props'
...@@ -63,6 +37,33 @@ ...@@ -63,6 +37,33 @@
}) })
</script> </script>
<template>
<div :class="prefixCls" :style="getStyle" v-if="showFooter || $slots.footer">
<template v-if="!$slots.footer">
<slot name="insertFooter"></slot>
<a-button v-bind="cancelButtonProps" @click="handleClose" class="mr-2" v-if="showCancelBtn">
{{ cancelText }}
</a-button>
<slot name="centerFooter"></slot>
<a-button
:type="okType"
@click="handleOk"
v-bind="okButtonProps"
class="mr-2"
:loading="confirmLoading"
v-if="showOkBtn"
>
{{ okText }}
</a-button>
<slot name="appendFooter"></slot>
</template>
<template v-else>
<slot name="footer"></slot>
</template>
</div>
</template>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-basic-drawer-footer'; @prefix-cls: ~'@{namespace}-basic-drawer-footer';
@footer-height: 60px; @footer-height: 60px;
......
<template>
<BasicTitle v-if="!isDetail" :class="prefixCls">
<slot name="title"></slot>
{{ !$slots.title ? title : '' }}
</BasicTitle>
<div :class="[prefixCls, `${prefixCls}--detail`]" v-else>
<span :class="`${prefixCls}__twrap`">
<span @click="handleClose" v-if="showDetailBack">
<ArrowLeftOutlined :class="`${prefixCls}__back`" />
</span>
<span v-if="title">{{ title }}</span>
</span>
<span :class="`${prefixCls}__toolbar`">
<slot name="titleToolbar"></slot>
</span>
</div>
</template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
import { BasicTitle } from '/@/components/Basic' import { BasicTitle } from '/@/components/Basic'
...@@ -46,6 +27,26 @@ ...@@ -46,6 +27,26 @@
}) })
</script> </script>
<template>
<BasicTitle v-if="!isDetail" :class="prefixCls">
<slot name="title"></slot>
{{ !$slots.title ? title : '' }}
</BasicTitle>
<div :class="[prefixCls, `${prefixCls}--detail`]" v-else>
<span :class="`${prefixCls}__twrap`">
<span @click="handleClose" v-if="showDetailBack">
<ArrowLeftOutlined :class="`${prefixCls}__back`" />
</span>
<span v-if="title">{{ title }}</span>
</span>
<span :class="`${prefixCls}__toolbar`">
<slot name="titleToolbar"></slot>
</span>
</div>
</template>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-basic-drawer-header'; @prefix-cls: ~'@{namespace}-basic-drawer-header';
@footer-height: 60px; @footer-height: 60px;
......
import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes' import type { ButtonProps } from 'ant-design-vue/lib/button/buttonTypes'
import type { CSSProperties, VNodeChild, ComputedRef } from 'vue' import type { CSSProperties, ComputedRef, VNodeChild } from 'vue'
import type { ScrollContainerOptions } from '/@/components/Container/index' import type { ScrollContainerOptions } from '/@/components/Container/index'
export interface DrawerInstance { export interface DrawerInstance {
......
import type { import type {
UseDrawerReturnType,
DrawerInstance, DrawerInstance,
ReturnMethods,
DrawerProps, DrawerProps,
ReturnMethods,
UseDrawerInnerReturnType, UseDrawerInnerReturnType,
UseDrawerReturnType,
} from './typing' } from './typing'
import { ref, getCurrentInstance, unref, reactive, watchEffect, nextTick, toRaw, computed } from 'vue' import { computed, getCurrentInstance, nextTick, reactive, ref, toRaw, unref, watchEffect } from 'vue'
import { isProdMode } from '/@/utils/env' import { isProdMode } from '/@/utils/env'
import { isFunction } from '/@/utils/is' import { isFunction } from '/@/utils/is'
import { tryOnUnmounted } from '@vueuse/core' import { tryOnUnmounted } from '@vueuse/core'
...@@ -66,7 +66,7 @@ export function useDrawer(): UseDrawerReturnType { ...@@ -66,7 +66,7 @@ export function useDrawer(): UseDrawerReturnType {
openDrawer: <T = any>(visible = true, data?: T, openOnSet = true): void => { openDrawer: <T = any>(visible = true, data?: T, openOnSet = true): void => {
getInstance()?.setDrawerProps({ getInstance()?.setDrawerProps({
visible: visible, visible,
}) })
if (!data) return if (!data) return
......
<template>
<a-dropdown :trigger="trigger" v-bind="$attrs">
<span>
<slot></slot>
</span>
<template #overlay>
<a-menu :selectedKeys="selectedKeys">
<template v-for="item in dropMenuList" :key="`${item.event}`">
<a-menu-item v-bind="getAttr(item.event)" @click="handleClickMenu(item)" :disabled="item.disabled">
<a-popconfirm v-if="popconfirm && item.popConfirm" v-bind="getPopConfirmAttrs(item.popConfirm)">
<template #icon v-if="item.popConfirm.icon">
<Icon :icon="item.popConfirm.icon" />
</template>
<div>
<Icon :icon="item.icon" v-if="item.icon" :color="getColor(item.color)" />
<span class="ml-1" :style="{ color: getColor(item.color) }">{{ item.text }}</span>
</div>
</a-popconfirm>
<template v-else>
<Icon :icon="item.icon" v-if="item.icon" :color="getColor(item.color)" />
<span class="ml-1" :style="{ color: getColor(item.color) }">{{ item.text }}</span>
</template>
</a-menu-item>
<a-menu-divider v-if="item.divider" :key="`d-${item.event}`" />
</template>
</a-menu>
</template>
</a-dropdown>
</template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, PropType } from 'vue' import type { PropType } from 'vue'
import { computed } from 'vue'
import type { DropMenu } from './typing' import type { DropMenu } from './typing'
import { Dropdown, Menu, Popconfirm } from 'ant-design-vue' import { Dropdown, Menu, Popconfirm } from 'ant-design-vue'
import { Icon } from '/@/components/Icon' import { Icon } from '/@/components/Icon'
import { omit } from 'lodash-es' import { omit } from 'lodash-es'
import { isFunction } from '/@/utils/is' import { isFunction } from '/@/utils/is'
const ADropdown = Dropdown
const AMenu = Menu
const AMenuItem = Menu.Item
const AMenuDivider = Menu.Divider
const APopconfirm = Popconfirm
const props = defineProps({ const props = defineProps({
popconfirm: Boolean, popconfirm: Boolean,
/** /**
...@@ -64,8 +29,12 @@ ...@@ -64,8 +29,12 @@
default: () => [], default: () => [],
}, },
}) })
const emit = defineEmits(['menuEvent']) const emit = defineEmits(['menuEvent'])
const ADropdown = Dropdown
const AMenu = Menu
const AMenuItem = Menu.Item
const AMenuDivider = Menu.Divider
const APopconfirm = Popconfirm
function handleClickMenu(item: DropMenu) { function handleClickMenu(item: DropMenu) {
const { event } = item const { event } = item
...@@ -90,11 +59,41 @@ ...@@ -90,11 +59,41 @@
const getPopConfirmAttrs = computed(() => { const getPopConfirmAttrs = computed(() => {
return (attrs) => { return (attrs) => {
const originAttrs = omit(attrs, ['confirm', 'cancel', 'icon']) const originAttrs = omit(attrs, ['confirm', 'cancel', 'icon'])
if (!attrs.onConfirm && attrs.confirm && isFunction(attrs.confirm)) originAttrs['onConfirm'] = attrs.confirm if (!attrs.onConfirm && attrs.confirm && isFunction(attrs.confirm)) originAttrs.onConfirm = attrs.confirm
if (!attrs.onCancel && attrs.cancel && isFunction(attrs.cancel)) originAttrs['onCancel'] = attrs.cancel if (!attrs.onCancel && attrs.cancel && isFunction(attrs.cancel)) originAttrs.onCancel = attrs.cancel
return originAttrs return originAttrs
} }
}) })
const getAttr = (key: string | number) => ({ key }) const getAttr = (key: string | number) => ({ key })
</script> </script>
<template>
<ADropdown :trigger="trigger" v-bind="$attrs">
<span>
<slot></slot>
</span>
<template #overlay>
<AMenu :selectedKeys="selectedKeys">
<template v-for="item in dropMenuList" :key="`${item.event}`">
<AMenuItem v-bind="getAttr(item.event)" @click="handleClickMenu(item)" :disabled="item.disabled">
<APopconfirm v-if="popconfirm && item.popConfirm" v-bind="getPopConfirmAttrs(item.popConfirm)">
<template #icon v-if="item.popConfirm.icon">
<Icon :icon="item.popConfirm.icon" />
</template>
<div>
<Icon :icon="item.icon" v-if="item.icon" :color="getColor(item.color)" />
<span class="ml-1" :style="{ color: getColor(item.color) }">{{ item.text }}</span>
</div>
</APopconfirm>
<template v-else>
<Icon :icon="item.icon" v-if="item.icon" :color="getColor(item.color)" />
<span class="ml-1" :style="{ color: getColor(item.color) }">{{ item.text }}</span>
</template>
</AMenuItem>
<AMenuDivider v-if="item.divider" :key="`d-${item.event}`" />
</template>
</AMenu>
</template>
</ADropdown>
</template>
import * as xlsx from 'xlsx' import * as xlsx from 'xlsx'
import type { WorkBook } from 'xlsx' import type { WorkBook } from 'xlsx'
import type { JsonToSheet, AoAToSheet } from './typing' import type { AoAToSheet, JsonToSheet } from './typing'
const { utils, writeFile } = xlsx const { utils, writeFile } = xlsx
......
<template>
<BasicModal v-bind="$attrs" :title="t('component.excel.exportModalTitle')" @ok="handleOk" @register="registerModal">
<BasicForm :labelWidth="100" :schemas="schemas" :showActionButtonGroup="false" @register="registerForm" />
</BasicModal>
</template>
<script lang="ts"> <script lang="ts">
import type { ExportModalResult } from './typing' import type { ExportModalResult } from './typing'
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
import { BasicModal, useModalInner } from '/@/components/Modal' import { BasicModal, useModalInner } from '/@/components/Modal'
import { BasicForm, FormSchema, useForm } from '/@/components/Form/index' import type { FormSchema } from '/@/components/Form/index'
import { BasicForm, useForm } from '/@/components/Form/index'
import { useI18n } from '/@/hooks/web/useI18n' import { useI18n } from '/@/hooks/web/useI18n'
...@@ -79,3 +75,9 @@ ...@@ -79,3 +75,9 @@
}, },
}) })
</script> </script>
<template>
<BasicModal v-bind="$attrs" :title="t('component.excel.exportModalTitle')" @ok="handleOk" @register="registerModal">
<BasicForm :labelWidth="100" :schemas="schemas" :showActionButtonGroup="false" @register="registerForm" />
</BasicModal>
</template>
<template>
<div>
<input ref="inputRef" type="file" v-show="false" accept=".xlsx, .xls" @change="handleInputClick" />
<div @click="handleUpload">
<slot></slot>
</div>
</div>
</template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref, unref } from 'vue' import { defineComponent, ref, unref } from 'vue'
import * as XLSX from 'xlsx' import * as XLSX from 'xlsx'
...@@ -48,7 +40,7 @@ ...@@ -48,7 +40,7 @@
/* walk every column in the range */ /* walk every column in the range */
const cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })] const cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })]
/* find the cell in the first row */ /* find the cell in the first row */
let hdr = 'UNKNOWN ' + C // <-- replace with your desired default let hdr = `UNKNOWN ${C}` // <-- replace with your desired default
if (cell && cell.t) hdr = XLSX.utils.format_cell(cell) if (cell && cell.t) hdr = XLSX.utils.format_cell(cell)
headers.push(hdr) headers.push(hdr)
} }
...@@ -65,10 +57,10 @@ ...@@ -65,10 +57,10 @@
const header: string[] = getHeaderRow(worksheet) const header: string[] = getHeaderRow(worksheet)
let results = XLSX.utils.sheet_to_json(worksheet, { let results = XLSX.utils.sheet_to_json(worksheet, {
raw: true, raw: true,
dateNF: dateFormat, //Not worked dateNF: dateFormat, // Not worked
}) as object[] }) as object[]
results = results.map((row: object) => { results = results.map((row: object) => {
for (let field in row) { for (const field in row) {
if (row[field] instanceof Date) { if (row[field] instanceof Date) {
if (timeZone === 8) { if (timeZone === 8) {
row[field].setSeconds(row[field].getSeconds() + 43) row[field].setSeconds(row[field].getSeconds() + 43)
...@@ -150,3 +142,12 @@ ...@@ -150,3 +142,12 @@
}, },
}) })
</script> </script>
<template>
<div>
<input ref="inputRef" type="file" v-show="false" accept=".xlsx, .xls" @change="handleInputClick" />
<div @click="handleUpload">
<slot></slot>
</div>
</div>
</template>
import type { JSON2SheetOpts, WritingOptions, BookType } from 'xlsx' import type { BookType, JSON2SheetOpts, WritingOptions } from 'xlsx'
export interface ExcelData<T = any> { export interface ExcelData<T = any> {
header: string[] header: string[]
......
<template>
<div class="h-full" :class="prefixCls">
<FlowChartToolbar :prefixCls="prefixCls" v-if="toolbar" @view-data="handlePreview" />
<div ref="lfElRef" class="h-full"></div>
<BasicModal @register="register" title="流程数据" width="50%">
<JsonPreview :data="graphData" />
</BasicModal>
</div>
</template>
<script lang="ts"> <script lang="ts">
import type { Ref } from 'vue' import type { Ref } from 'vue'
import type { Definition } from '@logicflow/core' import type { Definition } from '@logicflow/core'
import { defineComponent, ref, onMounted, unref, nextTick, computed, watch } from 'vue' import { computed, defineComponent, nextTick, onMounted, ref, unref, watch } from 'vue'
import FlowChartToolbar from './FlowChartToolbar.vue'
import LogicFlow from '@logicflow/core' import LogicFlow from '@logicflow/core'
import { Snapshot, BpmnElement, Menu, DndPanel, SelectionSelect } from '@logicflow/extension' import { BpmnElement, DndPanel, Menu, SelectionSelect, Snapshot } from '@logicflow/extension'
import FlowChartToolbar from './FlowChartToolbar.vue'
import { useDesign } from '/@/hooks/web/useDesign' import { useDesign } from '/@/hooks/web/useDesign'
import { useAppStore } from '/@/store/modules/app' import { useAppStore } from '/@/store/modules/app'
import { createFlowChartContext } from './useFlowContext' import { createFlowChartContext } from './useFlowContext'
import { toLogicFlowData } from './adpterForTurbo' import { toLogicFlowData } from './adpterForTurbo'
import { useModal, BasicModal } from '/@/components/Modal' import { BasicModal, useModal } from '/@/components/Modal'
import { JsonPreview } from '/@/components/CodeEditor' import { JsonPreview } from '/@/components/CodeEditor'
import { configDefaultDndPanel } from './config' import { configDefaultDndPanel } from './config'
import '@logicflow/core/dist/style/index.css' import '@logicflow/core/dist/style/index.css'
...@@ -156,3 +147,13 @@ ...@@ -156,3 +147,13 @@
}, },
}) })
</script> </script>
<template>
<div class="h-full" :class="prefixCls">
<FlowChartToolbar :prefixCls="prefixCls" v-if="toolbar" @view-data="handlePreview" />
<div ref="lfElRef" class="h-full"></div>
<BasicModal @register="register" title="流程数据" width="50%">
<JsonPreview :data="graphData" />
</BasicModal>
</div>
</template>
<template>
<div :class="`${prefixCls}-toolbar`" class="flex items-center px-2 py-1">
<template v-for="item in toolbarItemList" :key="item.type">
<Tooltip placement="bottom" v-bind="item.disabled ? { visible: false } : {}">
<template #title>{{ item.tooltip }}</template>
<span :class="`${prefixCls}-toolbar__icon`" v-if="item.icon" @click="onControl(item)">
<Icon :icon="item.icon" :class="item.disabled ? 'cursor-not-allowed disabeld' : 'cursor-pointer'" />
</span>
</Tooltip>
<Divider v-if="item.separate" type="vertical" />
</template>
</div>
</template>
<script lang="ts"> <script lang="ts">
import type { ToolbarConfig } from './types' import { defineComponent, nextTick, onUnmounted, ref, unref, watchEffect } from 'vue'
import { defineComponent, ref, onUnmounted, unref, nextTick, watchEffect } from 'vue'
import { Divider, Tooltip } from 'ant-design-vue' import { Divider, Tooltip } from 'ant-design-vue'
import type { ToolbarConfig } from './types'
import { Icon } from '/@/components/Icon' import { Icon } from '/@/components/Icon'
import { useFlowChartContext } from './useFlowContext' import { useFlowChartContext } from './useFlowContext'
...@@ -129,6 +115,21 @@ ...@@ -129,6 +115,21 @@
}, },
}) })
</script> </script>
<template>
<div :class="`${prefixCls}-toolbar`" class="flex items-center px-2 py-1">
<template v-for="item in toolbarItemList" :key="item.type">
<Tooltip placement="bottom" v-bind="item.disabled ? { visible: false } : {}">
<template #title>{{ item.tooltip }}</template>
<span :class="`${prefixCls}-toolbar__icon`" v-if="item.icon" @click="onControl(item)">
<Icon :icon="item.icon" :class="item.disabled ? 'cursor-not-allowed disabeld' : 'cursor-pointer'" />
</span>
</Tooltip>
<Divider v-if="item.separate" type="vertical" />
</template>
</div>
</template>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-flow-chart-toolbar'; @prefix-cls: ~'@{namespace}-flow-chart-toolbar';
......
...@@ -23,7 +23,7 @@ function convertFlowElementToEdge(element) { ...@@ -23,7 +23,7 @@ function convertFlowElementToEdge(element) {
} }
const excludeProperties = ['startPoint', 'endPoint', 'pointsList', 'text', 'logicFlowType'] const excludeProperties = ['startPoint', 'endPoint', 'pointsList', 'text', 'logicFlowType']
Object.keys(element.properties).forEach((property) => { Object.keys(element.properties).forEach((property) => {
if (excludeProperties.indexOf(property) === -1) { if (!excludeProperties.includes(property)) {
edge.properties[property] = element.properties[property] edge.properties[property] = element.properties[property]
} }
}) })
...@@ -43,7 +43,7 @@ function convertFlowElementToNode(element) { ...@@ -43,7 +43,7 @@ function convertFlowElementToNode(element) {
} }
const excludeProperties = ['x', 'y', 'text', 'logicFlowType'] const excludeProperties = ['x', 'y', 'text', 'logicFlowType']
Object.keys(element.properties).forEach((property) => { Object.keys(element.properties).forEach((property) => {
if (excludeProperties.indexOf(property) === -1) { if (!excludeProperties.includes(property)) {
node.properties[property] = element.properties[property] node.properties[property] = element.properties[property]
} }
}) })
......
import { NodeConfig } from '@logicflow/core' import type { NodeConfig } from '@logicflow/core'
import { ToolbarTypeEnum } from './enum' import type { ToolbarTypeEnum } from './enum'
export interface NodeItem extends NodeConfig { export interface NodeItem extends NodeConfig {
icon: string icon: string
......
import type LogicFlow from '@logicflow/core' import type LogicFlow from '@logicflow/core'
import { provide, inject } from 'vue' import { inject, provide } from 'vue'
const key = Symbol('flow-chart') const key = Symbol('flow-chart')
type Instance = { interface Instance {
logicFlow: LogicFlow logicFlow: LogicFlow
} }
......
<template>
<Form
v-bind="getBindValue"
:class="getFormClass"
ref="formElRef"
:model="formModel"
@keypress.enter="handleEnterPress"
>
<Row v-bind="getRow">
<slot name="formHeader"></slot>
<template v-for="schema in getSchema" :key="schema.field">
<FormItem
:isAdvanced="fieldsIsAdvancedMap[schema.field]"
:tableAction="tableAction"
:formActionType="formActionType"
:schema="schema"
:formProps="getProps"
:allDefaultValues="defaultValueRef"
:formModel="formModel"
:setFormModel="setFormModel"
>
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data || {}"></slot>
</template>
</FormItem>
</template>
<FormAction v-bind="getFormActionBindProps" @toggle-advanced="handleToggleAdvanced">
<template
#[item]="data"
v-for="item in ['resetBefore', 'submitBefore', 'advanceBefore', 'advanceAfter']"
>
<slot :name="item" v-bind="data || {}"></slot>
</template>
</FormAction>
<slot name="formFooter"></slot>
</Row>
</Form>
</template>
<script lang="ts"> <script lang="ts">
import type { FormActionType, FormProps, FormSchema } from './types/form'
import type { AdvanceState } from './types/hooks'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import { defineComponent, reactive, ref, computed, unref, onMounted, watch, nextTick } from 'vue' import { computed, defineComponent, nextTick, onMounted, reactive, ref, unref, watch } from 'vue'
import { Form, Row } from 'ant-design-vue' import { Form, Row } from 'ant-design-vue'
import { useDebounceFn } from '@vueuse/core'
import { cloneDeep } from 'lodash-es'
import type { FormActionType, FormProps, FormSchema } from './types/form'
import type { AdvanceState } from './types/hooks'
import FormItem from './components/FormItem.vue' import FormItem from './components/FormItem.vue'
import FormAction from './components/FormAction.vue' import FormAction from './components/FormAction.vue'
import { dateItemType } from './helper' import { dateItemType } from './helper'
...@@ -55,10 +18,8 @@ ...@@ -55,10 +18,8 @@
import { createFormContext } from './hooks/useFormContext' import { createFormContext } from './hooks/useFormContext'
import { useAutoFocus } from './hooks/useAutoFocus' import { useAutoFocus } from './hooks/useAutoFocus'
import { useModalContext } from '/@/components/Modal' import { useModalContext } from '/@/components/Modal'
import { useDebounceFn } from '@vueuse/core'
import { basicProps } from './props' import { basicProps } from './props'
import { useDesign } from '/@/hooks/web/useDesign' import { useDesign } from '/@/hooks/web/useDesign'
import { cloneDeep } from 'lodash-es'
export default defineComponent({ export default defineComponent({
name: 'BasicForm', name: 'BasicForm',
components: { FormItem, Form, Row, FormAction }, components: { FormItem, Form, Row, FormAction },
...@@ -226,7 +187,7 @@ ...@@ -226,7 +187,7 @@
if (!autoSubmitOnEnter) return if (!autoSubmitOnEnter) return
if (e.key === 'Enter' && e.target && e.target instanceof HTMLElement) { if (e.key === 'Enter' && e.target && e.target instanceof HTMLElement) {
const target: HTMLElement = e.target as HTMLElement const target: HTMLElement = e.target as HTMLElement
if (target && target.tagName && target.tagName.toUpperCase() == 'INPUT') { if (target && target.tagName && target.tagName.toUpperCase() === 'INPUT') {
handleSubmit() handleSubmit()
} }
} }
...@@ -244,7 +205,7 @@ ...@@ -244,7 +205,7 @@
validateFields, validateFields,
validate, validate,
submit: handleSubmit, submit: handleSubmit,
scrollToField: scrollToField, scrollToField,
} }
onMounted(() => { onMounted(() => {
initDefault() initDefault()
...@@ -271,6 +232,47 @@ ...@@ -271,6 +232,47 @@
}, },
}) })
</script> </script>
<template>
<Form
v-bind="getBindValue"
:class="getFormClass"
ref="formElRef"
:model="formModel"
@keypress.enter="handleEnterPress"
>
<Row v-bind="getRow">
<slot name="formHeader"></slot>
<template v-for="schema in getSchema" :key="schema.field">
<FormItem
:isAdvanced="fieldsIsAdvancedMap[schema.field]"
:tableAction="tableAction"
:formActionType="formActionType"
:schema="schema"
:formProps="getProps"
:allDefaultValues="defaultValueRef"
:formModel="formModel"
:setFormModel="setFormModel"
>
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data || {}"></slot>
</template>
</FormItem>
</template>
<FormAction v-bind="getFormActionBindProps" @toggle-advanced="handleToggleAdvanced">
<template
#[item]="data"
v-for="item in ['resetBefore', 'submitBefore', 'advanceBefore', 'advanceAfter']"
>
<slot :name="item" v-bind="data || {}"></slot>
</template>
</FormAction>
<slot name="formFooter"></slot>
</Row>
</Form>
</template>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-basic-form'; @prefix-cls: ~'@{namespace}-basic-form';
.@{prefix-cls} { .@{prefix-cls} {
......
...@@ -5,20 +5,20 @@ import type { ComponentType } from './types/index' ...@@ -5,20 +5,20 @@ import type { ComponentType } from './types/index'
* Component list, register here to setting it in the form * Component list, register here to setting it in the form
*/ */
import { import {
Input,
Select,
Radio,
Checkbox,
AutoComplete, AutoComplete,
Cascader, Cascader,
Checkbox,
DatePicker, DatePicker,
Divider,
Input,
InputNumber, InputNumber,
Radio,
Rate,
Select,
Slider,
Switch, Switch,
TimePicker, TimePicker,
TreeSelect, TreeSelect,
Slider,
Rate,
Divider,
} from 'ant-design-vue' } from 'ant-design-vue'
import ApiRadioGroup from './components/ApiRadioGroup.vue' import ApiRadioGroup from './components/ApiRadioGroup.vue'
......
<template>
<a-cascader
v-model:value="state"
:options="options"
:load-data="loadData"
change-on-select
@change="handleChange"
:displayRender="handleRenderDisplay"
>
<template #suffixIcon v-if="loading">
<LoadingOutlined spin />
</template>
<template #notFoundContent v-if="loading">
<span>
<LoadingOutlined spin class="mr-1" />
{{ t('component.form.apiSelectNotFound') }}
</span>
</template>
</a-cascader>
</template>
<script lang="ts"> <script lang="ts">
import { defineComponent, PropType, ref, unref, watch, watchEffect } from 'vue' import type { PropType } from 'vue'
import { defineComponent, ref, unref, watch, watchEffect } from 'vue'
import { Cascader } from 'ant-design-vue' import { Cascader } from 'ant-design-vue'
import { propTypes } from '/@/utils/propTypes' import { propTypes } from '/@/utils/propTypes'
import { isFunction } from '/@/utils/is' import { isFunction } from '/@/utils/is'
...@@ -196,3 +177,24 @@ ...@@ -196,3 +177,24 @@
}, },
}) })
</script> </script>
<template>
<a-cascader
v-model:value="state"
:options="options"
:load-data="loadData"
change-on-select
@change="handleChange"
:displayRender="handleRenderDisplay"
>
<template #suffixIcon v-if="loading">
<LoadingOutlined spin />
</template>
<template #notFoundContent v-if="loading">
<span>
<LoadingOutlined spin class="mr-1" />
{{ t('component.form.apiSelectNotFound') }}
</span>
</template>
</a-cascader>
</template>
<!-- <!--
* @Description:It is troublesome to implement radio button group in the form. So it is extracted independently as a separate component * @Description:It is troublesome to implement radio button group in the form. So it is extracted independently as a separate component
--> -->
<template>
<RadioGroup v-bind="attrs" v-model:value="state" button-style="solid" @change="handleChange">
<template v-for="item in getOptions" :key="`${item.value}`">
<RadioButton v-if="props.isBtn" :value="item.value" :disabled="item.disabled">
{{ item.label }}
</RadioButton>
<Radio v-else :value="item.value" :disabled="item.disabled">
{{ item.label }}
</Radio>
</template>
</RadioGroup>
</template>
<script lang="ts"> <script lang="ts">
import { defineComponent, PropType, ref, watchEffect, computed, unref, watch } from 'vue' import type { PropType } from 'vue'
import { computed, defineComponent, ref, unref, watch, watchEffect } from 'vue'
import { Radio } from 'ant-design-vue' import { Radio } from 'ant-design-vue'
import { isFunction } from '/@/utils/is' import { isFunction } from '/@/utils/is'
import { useRuleFormItem } from '/@/hooks/component/useFormItem' import { useRuleFormItem } from '/@/hooks/component/useFormItem'
...@@ -22,7 +11,11 @@ ...@@ -22,7 +11,11 @@
import { propTypes } from '/@/utils/propTypes' import { propTypes } from '/@/utils/propTypes'
import { get, omit } from 'lodash-es' import { get, omit } from 'lodash-es'
import { useI18n } from '/@/hooks/web/useI18n' import { useI18n } from '/@/hooks/web/useI18n'
type OptionsItem = { label: string; value: string | number | boolean; disabled?: boolean } interface OptionsItem {
label: string
value: string | number | boolean
disabled?: boolean
}
export default defineComponent({ export default defineComponent({
name: 'ApiRadioGroup', name: 'ApiRadioGroup',
...@@ -128,3 +121,16 @@ ...@@ -128,3 +121,16 @@
}, },
}) })
</script> </script>
<template>
<RadioGroup v-bind="attrs" v-model:value="state" button-style="solid" @change="handleChange">
<template v-for="item in getOptions" :key="`${item.value}`">
<RadioButton v-if="props.isBtn" :value="item.value" :disabled="item.disabled">
{{ item.label }}
</RadioButton>
<Radio v-else :value="item.value" :disabled="item.disabled">
{{ item.label }}
</Radio>
</template>
</RadioGroup>
</template>
<template>
<Select
@dropdown-visible-change="handleFetch"
v-bind="$attrs"
@change="handleChange"
:options="getOptions"
v-model:value="state"
>
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data || {}"></slot>
</template>
<template #suffixIcon v-if="loading">
<LoadingOutlined spin />
</template>
<template #notFoundContent v-if="loading">
<span>
<LoadingOutlined spin class="mr-1" />
{{ t('component.form.apiSelectNotFound') }}
</span>
</template>
</Select>
</template>
<script lang="ts"> <script lang="ts">
import { defineComponent, PropType, ref, watchEffect, computed, unref, watch } from 'vue' import type { PropType } from 'vue'
import { computed, defineComponent, ref, unref, watch, watchEffect } from 'vue'
import { Select } from 'ant-design-vue' import { Select } from 'ant-design-vue'
import { isFunction } from '/@/utils/is' import { isFunction } from '/@/utils/is'
import { useRuleFormItem } from '/@/hooks/component/useFormItem' import { useRuleFormItem } from '/@/hooks/component/useFormItem'
...@@ -30,9 +9,11 @@ ...@@ -30,9 +9,11 @@
import { LoadingOutlined } from '@ant-design/icons-vue' import { LoadingOutlined } from '@ant-design/icons-vue'
import { useI18n } from '/@/hooks/web/useI18n' import { useI18n } from '/@/hooks/web/useI18n'
import { propTypes } from '/@/utils/propTypes' import { propTypes } from '/@/utils/propTypes'
interface OptionsItem {
type OptionsItem = { label: string; value: string; disabled?: boolean } label: string
value: string
disabled?: boolean
}
export default defineComponent({ export default defineComponent({
name: 'ApiSelect', name: 'ApiSelect',
components: { components: {
...@@ -149,3 +130,26 @@ ...@@ -149,3 +130,26 @@
}, },
}) })
</script> </script>
<template>
<Select
@dropdown-visible-change="handleFetch"
v-bind="$attrs"
@change="handleChange"
:options="getOptions"
v-model:value="state"
>
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data || {}"></slot>
</template>
<template #suffixIcon v-if="loading">
<LoadingOutlined spin />
</template>
<template #notFoundContent v-if="loading">
<span>
<LoadingOutlined spin class="mr-1" />
{{ t('component.form.apiSelectNotFound') }}
</span>
</template>
</Select>
</template>
<template>
<Transfer
:data-source="getdataSource"
:filter-option="filterOption"
:render="(item) => item.title"
:showSelectAll="showSelectAll"
:selectedKeys="selectedKeys"
:targetKeys="getTargetKeys"
:showSearch="showSearch"
@change="handleChange"
/>
</template>
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, watch, ref, unref, watchEffect } from 'vue' import { computed, defineComponent, ref, unref, watch, watchEffect } from 'vue'
import { Transfer } from 'ant-design-vue' import { Transfer } from 'ant-design-vue'
import { isFunction } from '/@/utils/is' import { isFunction } from '/@/utils/is'
import { get, omit } from 'lodash-es' import { get, omit } from 'lodash-es'
import { propTypes } from '/@/utils/propTypes' import { propTypes } from '/@/utils/propTypes'
import { useI18n } from '/@/hooks/web/useI18n' import { useI18n } from '/@/hooks/web/useI18n'
import { TransferDirection, TransferItem } from 'ant-design-vue/lib/transfer' import type { TransferDirection, TransferItem } from 'ant-design-vue/lib/transfer'
export default defineComponent({ export default defineComponent({
name: 'ApiTransfer', name: 'ApiTransfer',
components: { Transfer }, components: { Transfer },
...@@ -122,7 +109,6 @@ ...@@ -122,7 +109,6 @@
emitChange() emitChange()
} catch (error) { } catch (error) {
console.warn(error) console.warn(error)
} finally {
} }
} }
function emitChange() { function emitChange() {
...@@ -132,3 +118,16 @@ ...@@ -132,3 +118,16 @@
}, },
}) })
</script> </script>
<template>
<Transfer
:data-source="getdataSource"
:filter-option="filterOption"
:render="(item) => item.title"
:showSelectAll="showSelectAll"
:selectedKeys="selectedKeys"
:targetKeys="getTargetKeys"
:showSearch="showSearch"
@change="handleChange"
/>
</template>
<template>
<a-tree v-bind="getAttrs" @change="handleChange">
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data || {}"></slot>
</template>
<template #suffixIcon v-if="loading">
<LoadingOutlined spin />
</template>
</a-tree>
</template>
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, watch, ref, onMounted, unref } from 'vue' import { computed, defineComponent, onMounted, ref, unref, watch } from 'vue'
import { Tree } from 'ant-design-vue' import { Tree } from 'ant-design-vue'
import { isArray, isFunction } from '/@/utils/is' import { isArray, isFunction } from '/@/utils/is'
import { get } from 'lodash-es' import { get } from 'lodash-es'
...@@ -83,3 +72,14 @@ ...@@ -83,3 +72,14 @@
}, },
}) })
</script> </script>
<template>
<ATree v-bind="getAttrs" @change="handleChange">
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data || {}"></slot>
</template>
<template #suffixIcon v-if="loading">
<LoadingOutlined spin />
</template>
</ATree>
</template>
<template>
<a-tree-select v-bind="getAttrs" @change="handleChange">
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data || {}"></slot>
</template>
<template #suffixIcon v-if="loading">
<LoadingOutlined spin />
</template>
</a-tree-select>
</template>
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, watch, ref, onMounted, unref } from 'vue' import { computed, defineComponent, onMounted, ref, unref, watch } from 'vue'
import { TreeSelect } from 'ant-design-vue' import { TreeSelect } from 'ant-design-vue'
import { isArray, isFunction } from '/@/utils/is' import { isArray, isFunction } from '/@/utils/is'
import { get } from 'lodash-es' import { get } from 'lodash-es'
...@@ -84,3 +73,14 @@ ...@@ -84,3 +73,14 @@
}, },
}) })
</script> </script>
<template>
<ATreeSelect v-bind="getAttrs" @change="handleChange">
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data || {}"></slot>
</template>
<template #suffixIcon v-if="loading">
<LoadingOutlined spin />
</template>
</ATreeSelect>
</template>
<template>
<a-col v-bind="actionColOpt" v-if="showActionButtonGroup">
<div style="width: 100%" :style="{ textAlign: actionColOpt.style.textAlign }">
<FormItem>
<slot name="resetBefore"></slot>
<Button
type="default"
class="mr-2"
v-bind="getResetBtnOptions"
@click="resetAction"
v-if="showResetButton"
>
{{ getResetBtnOptions.text }}
</Button>
<slot name="submitBefore"></slot>
<Button
type="primary"
class="mr-2"
v-bind="getSubmitBtnOptions"
@click="submitAction"
v-if="showSubmitButton"
>
{{ getSubmitBtnOptions.text }}
</Button>
<slot name="advanceBefore"></slot>
<Button type="link" size="small" @click="toggleAdvanced" v-if="showAdvancedButton && !hideAdvanceBtn">
{{ isAdvanced ? t('component.form.putAway') : t('component.form.unfold') }}
<BasicArrow class="ml-1" :expand="!isAdvanced" up />
</Button>
<slot name="advanceAfter"></slot>
</FormItem>
</div>
</a-col>
</template>
<script lang="ts"> <script lang="ts">
import type { ColEx } from '../types/index' import type { ColEx } from '../types/index'
//import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes'; // import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
import { defineComponent, computed, PropType } from 'vue' import type { PropType } from 'vue'
import { Form, Col } from 'ant-design-vue' import { computed, defineComponent } from 'vue'
import { Button, ButtonProps } from '/@/components/Button' import { Col, Form } from 'ant-design-vue'
import type { ButtonProps } from '/@/components/Button'
import { Button } from '/@/components/Button'
import { BasicArrow } from '/@/components/Basic' import { BasicArrow } from '/@/components/Basic'
import { useFormContext } from '../hooks/useFormContext' import { useFormContext } from '../hooks/useFormContext'
import { useI18n } from '/@/hooks/web/useI18n' import { useI18n } from '/@/hooks/web/useI18n'
...@@ -126,3 +92,40 @@ ...@@ -126,3 +92,40 @@
}, },
}) })
</script> </script>
<template>
<a-col v-bind="actionColOpt" v-if="showActionButtonGroup">
<div style="width: 100%" :style="{ textAlign: actionColOpt.style.textAlign }">
<FormItem>
<slot name="resetBefore"></slot>
<Button
type="default"
class="mr-2"
v-bind="getResetBtnOptions"
@click="resetAction"
v-if="showResetButton"
>
{{ getResetBtnOptions.text }}
</Button>
<slot name="submitBefore"></slot>
<Button
type="primary"
class="mr-2"
v-bind="getSubmitBtnOptions"
@click="submitAction"
v-if="showSubmitButton"
>
{{ getSubmitBtnOptions.text }}
</Button>
<slot name="advanceBefore"></slot>
<Button type="link" size="small" @click="toggleAdvanced" v-if="showAdvancedButton && !hideAdvanceBtn">
{{ isAdvanced ? t('component.form.putAway') : t('component.form.unfold') }}
<BasicArrow class="ml-1" :expand="!isAdvanced" up />
</Button>
<slot name="advanceAfter"></slot>
</FormItem>
</div>
</a-col>
</template>
...@@ -56,7 +56,7 @@ ...@@ -56,7 +56,7 @@
const itemLabelWidthProp = useItemLabelWidth(schema, formProps) const itemLabelWidthProp = useItemLabelWidth(schema, formProps)
const getValues = computed(() => { const getValues = computed(() => {
const { allDefaultValues, formModel, schema } = props const { allDefaultValues, formModel, schema } = props
// @ts-ignore // @ts-expect-error
const { mergeDynamicData } = props.formProps const { mergeDynamicData } = props.formProps
return { return {
field: schema.field, field: schema.field,
...@@ -66,7 +66,7 @@ ...@@ -66,7 +66,7 @@
...allDefaultValues, ...allDefaultValues,
...formModel, ...formModel,
} as Recordable, } as Recordable,
schema: schema, schema,
} }
}) })
const getComponentsProps = computed(() => { const getComponentsProps = computed(() => {
...@@ -84,7 +84,7 @@ ...@@ -84,7 +84,7 @@
return componentProps as Recordable return componentProps as Recordable
}) })
const getDisable = computed(() => { const getDisable = computed(() => {
// @ts-ignore // @ts-expect-error
const { disabled: globDisabled } = props.formProps const { disabled: globDisabled } = props.formProps
const { dynamicDisabled } = props.schema const { dynamicDisabled } = props.schema
const { disabled: itemDisabled = false } = unref(getComponentsProps) const { disabled: itemDisabled = false } = unref(getComponentsProps)
...@@ -99,7 +99,7 @@ ...@@ -99,7 +99,7 @@
}) })
function getShow(): { isShow: boolean; isIfShow: boolean } { function getShow(): { isShow: boolean; isIfShow: boolean } {
const { show, ifShow } = props.schema const { show, ifShow } = props.schema
// @ts-ignore // @ts-expect-error
const { showAdvancedButton } = props.formProps const { showAdvancedButton } = props.formProps
const itemIsAdvanced = showAdvancedButton const itemIsAdvanced = showAdvancedButton
? isBoolean(props.isAdvanced) ? isBoolean(props.isAdvanced)
...@@ -136,12 +136,12 @@ ...@@ -136,12 +136,12 @@
return dynamicRules(unref(getValues)) as ValidationRule[] return dynamicRules(unref(getValues)) as ValidationRule[]
} }
let rules: ValidationRule[] = cloneDeep(defRules) as ValidationRule[] let rules: ValidationRule[] = cloneDeep(defRules) as ValidationRule[]
// @ts-ignore // @ts-expect-error
const { rulesMessageJoinLabel: globalRulesMessageJoinLabel } = props.formProps const { rulesMessageJoinLabel: globalRulesMessageJoinLabel } = props.formProps
const joinLabel = Reflect.has(props.schema, 'rulesMessageJoinLabel') const joinLabel = Reflect.has(props.schema, 'rulesMessageJoinLabel')
? rulesMessageJoinLabel ? rulesMessageJoinLabel
: globalRulesMessageJoinLabel : globalRulesMessageJoinLabel
const defaultMsg = createPlaceholderMessage(component) + `${joinLabel ? label : ''}` const defaultMsg = `${createPlaceholderMessage(component)}${joinLabel ? label : ''}`
function validator(rule: any, value: any) { function validator(rule: any, value: any) {
const msg = rule.message || defaultMsg const msg = rule.message || defaultMsg
if (value === undefined || isNull(value)) { if (value === undefined || isNull(value)) {
...@@ -236,7 +236,7 @@ ...@@ -236,7 +236,7 @@
}, },
} }
const Comp = componentMap.get(component) as ReturnType<typeof defineComponent> const Comp = componentMap.get(component) as ReturnType<typeof defineComponent>
// @ts-ignore // @ts-expect-error
const { autoSetPlaceHolder, size } = props.formProps const { autoSetPlaceHolder, size } = props.formProps
const propsData: Recordable = { const propsData: Recordable = {
allowClear: true, allowClear: true,
...@@ -294,7 +294,7 @@ ...@@ -294,7 +294,7 @@
function renderItem() { function renderItem() {
const { itemProps, slot, render, field, suffix, component } = props.schema const { itemProps, slot, render, field, suffix, component } = props.schema
const { labelCol, wrapperCol } = unref(itemLabelWidthProp) const { labelCol, wrapperCol } = unref(itemLabelWidthProp)
// @ts-ignore // @ts-expect-error
const { colon } = props.formProps const { colon } = props.formProps
if (component === 'Divider') { if (component === 'Divider') {
return ( return (
...@@ -336,7 +336,7 @@ ...@@ -336,7 +336,7 @@
if (!componentMap.has(component)) { if (!componentMap.has(component)) {
return null return null
} }
// @ts-ignore // @ts-expect-error
const { baseColProps = {} } = props.formProps const { baseColProps = {} } = props.formProps
const realColProps = { ...baseColProps, ...colProps } const realColProps = { ...baseColProps, ...colProps }
const { isIfShow, isShow } = getShow() const { isIfShow, isShow } = getShow()
......
<!-- <!--
* @Description:It is troublesome to implement radio button group in the form. So it is extracted independently as a separate component * @Description:It is troublesome to implement radio button group in the form. So it is extracted independently as a separate component
--> -->
<template>
<RadioGroup v-bind="attrs" v-model:value="state" button-style="solid">
<template v-for="item in getOptions" :key="`${item.value}`">
<RadioButton :value="item.value" :disabled="item.disabled">
{{ item.label }}
</RadioButton>
</template>
</RadioGroup>
</template>
<script lang="ts"> <script lang="ts">
import { defineComponent, PropType, computed } from 'vue' import type { PropType } from 'vue'
import { computed, defineComponent } from 'vue'
import { Radio } from 'ant-design-vue' import { Radio } from 'ant-design-vue'
import { isString } from '/@/utils/is' import { isString } from '/@/utils/is'
import { useRuleFormItem } from '/@/hooks/component/useFormItem' import { useRuleFormItem } from '/@/hooks/component/useFormItem'
import { useAttrs } from '/@/hooks/core/useAttrs' import { useAttrs } from '/@/hooks/core/useAttrs'
type OptionsItem = { label: string; value: string | number | boolean; disabled?: boolean } interface OptionsItem {
label: string
value: string | number | boolean
disabled?: boolean
}
type RadioItem = string | OptionsItem type RadioItem = string | OptionsItem
export default defineComponent({ export default defineComponent({
...@@ -55,3 +51,13 @@ ...@@ -55,3 +51,13 @@
}, },
}) })
</script> </script>
<template>
<RadioGroup v-bind="attrs" v-model:value="state" button-style="solid">
<template v-for="item in getOptions" :key="`${item.value}`">
<RadioButton :value="item.value" :disabled="item.disabled">
{{ item.label }}
</RadioButton>
</template>
</RadioGroup>
</template>
import type { ComputedRef, Ref } from 'vue'
import { computed, getCurrentInstance, shallowReactive, unref, watch } from 'vue'
import { useDebounceFn } from '@vueuse/core'
import type { ColEx } from '../types' import type { ColEx } from '../types'
import type { AdvanceState } from '../types/hooks' import type { AdvanceState } from '../types/hooks'
import { ComputedRef, getCurrentInstance, Ref, shallowReactive } from 'vue'
import type { FormProps, FormSchema } from '../types/form' import type { FormProps, FormSchema } from '../types/form'
import { computed, unref, watch } from 'vue'
import { isBoolean, isFunction, isNumber, isObject } from '/@/utils/is' import { isBoolean, isFunction, isNumber, isObject } from '/@/utils/is'
import { useBreakpoint } from '/@/hooks/event/useBreakpoint' import { useBreakpoint } from '/@/hooks/event/useBreakpoint'
import { useDebounceFn } from '@vueuse/core'
const BASIC_COL_LEN = 24 const BASIC_COL_LEN = 24
...@@ -123,7 +123,7 @@ export default function ({ advanceState, emit, getProps, getSchema, formModel, d ...@@ -123,7 +123,7 @@ export default function ({ advanceState, emit, getProps, getSchema, formModel, d
if (isFunction(show)) { if (isFunction(show)) {
isShow = show({ isShow = show({
schema: schema, schema,
model: formModel, model: formModel,
field: schema.field, field: schema.field,
values: { values: {
......
import type { ComputedRef, Ref } from 'vue' import type { ComputedRef, Ref } from 'vue'
import type { FormSchema, FormActionType, FormProps } from '../types/form' import { nextTick, unref, watchEffect } from 'vue'
import type { FormActionType, FormProps, FormSchema } from '../types/form'
import { unref, nextTick, watchEffect } from 'vue'
interface UseAutoFocusContext { interface UseAutoFocusContext {
getSchema: ComputedRef<FormSchema[]> getSchema: ComputedRef<FormSchema[]>
......
import type { ComponentType } from '../types/index'
import { tryOnUnmounted } from '@vueuse/core' import { tryOnUnmounted } from '@vueuse/core'
import { add, del } from '../componentMap'
import type { Component } from 'vue' import type { Component } from 'vue'
import type { ComponentType } from '../types/index'
import { add, del } from '../componentMap'
export function useComponentRegister(compName: ComponentType, comp: Component) { export function useComponentRegister(compName: ComponentType, comp: Component) {
add(compName, comp) add(compName, comp)
......
import type { FormProps, FormActionType, UseFormReturnType, FormSchema } from '../types/form' import type { FormActionType, FormProps, FormSchema, UseFormReturnType } from '../types/form'
import type { NamePath } from 'ant-design-vue/lib/form/interface' import type { NamePath } from 'ant-design-vue/lib/form/interface'
import type { DynamicProps } from '/#/utils' import type { DynamicProps } from '/#/utils'
import { ref, onUnmounted, unref, nextTick, watch } from 'vue' import { nextTick, onUnmounted, ref, unref, watch } from 'vue'
import { isProdMode } from '/@/utils/env' import { isProdMode } from '/@/utils/env'
import { error } from '/@/utils/log' import { error } from '/@/utils/log'
import { getDynamicProps } from '/@/utils' import { getDynamicProps } from '/@/utils'
......
import type { ComputedRef, Ref } from 'vue' import type { ComputedRef, Ref } from 'vue'
import type { FormProps, FormSchema, FormActionType } from '../types/form' import type { FormActionType, FormProps, FormSchema } from '../types/form'
import type { NamePath } from 'ant-design-vue/lib/form/interface' import type { NamePath } from 'ant-design-vue/lib/form/interface'
import { unref, toRaw, nextTick } from 'vue' import { nextTick, toRaw, unref } from 'vue'
import { isArray, isFunction, isObject, isString, isDef, isNullOrUnDef } from '/@/utils/is' import { isArray, isDef, isFunction, isNullOrUnDef, isObject, isString } from '/@/utils/is'
import { deepMerge } from '/@/utils' import { deepMerge } from '/@/utils'
import { dateItemType, handleInputNumberValue, defaultValueComponents } from '../helper' import { dateItemType, defaultValueComponents, handleInputNumberValue } from '../helper'
import { dateUtil } from '/@/utils/dateUtil' import { dateUtil } from '/@/utils/dateUtil'
import { cloneDeep, uniqBy } from 'lodash-es' import { cloneDeep, uniqBy } from 'lodash-es'
import { error } from '/@/utils/log' import { error } from '/@/utils/log'
...@@ -58,7 +58,7 @@ export function useFormEvents({ ...@@ -58,7 +58,7 @@ export function useFormEvents({
// key 支持 a.b.c 的嵌套写法 // key 支持 a.b.c 的嵌套写法
const delimiter = '.' const delimiter = '.'
const nestKeyArray = fields.filter((item) => item.indexOf(delimiter) >= 0) const nestKeyArray = fields.filter((item) => item.includes(delimiter))
const validKeys: string[] = [] const validKeys: string[] = []
Object.keys(values).forEach((key) => { Object.keys(values).forEach((key) => {
...@@ -93,7 +93,8 @@ export function useFormEvents({ ...@@ -93,7 +93,8 @@ export function useFormEvents({
} else { } else {
nestKeyArray.forEach((nestKey: string) => { nestKeyArray.forEach((nestKey: string) => {
try { try {
const value = eval('values' + delimiter + nestKey) // eslint-disable-next-line no-eval
const value = eval(`values${delimiter}${nestKey}`)
if (isDef(value)) { if (isDef(value)) {
formModel[nestKey] = value formModel[nestKey] = value
validKeys.push(nestKey) validKeys.push(nestKey)
...@@ -229,7 +230,7 @@ export function useFormEvents({ ...@@ -229,7 +230,7 @@ export function useFormEvents({
const currentFieldsValue = getFieldsValue() const currentFieldsValue = getFieldsValue()
schemas.forEach((item) => { schemas.forEach((item) => {
if ( if (
item.component != 'Divider' && item.component !== 'Divider' &&
Reflect.has(item, 'field') && Reflect.has(item, 'field') &&
item.field && item.field &&
!isNullOrUnDef(item.defaultValue) && !isNullOrUnDef(item.defaultValue) &&
......
import { isArray, isFunction, isObject, isString, isNullOrUnDef } from '/@/utils/is' import { isArray, isFunction, isNullOrUnDef, isObject, isString } from '/@/utils/is'
import { dateUtil } from '/@/utils/dateUtil' import { dateUtil } from '/@/utils/dateUtil'
import { unref } from 'vue' import { unref } from 'vue'
import type { Ref, ComputedRef } from 'vue' import type { ComputedRef, Ref } from 'vue'
import type { FormProps, FormSchema } from '../types/form' import type { FormProps, FormSchema } from '../types/form'
import { cloneDeep, set } from 'lodash-es' import { cloneDeep, set } from 'lodash-es'
......
import type { NamePath, RuleObject } from 'ant-design-vue/lib/form/interface' import type { NamePath, RuleObject } from 'ant-design-vue/lib/form/interface'
import type { VNode } from 'vue' import type { CSSProperties, VNode } from 'vue'
import type { ButtonProps as AntdButtonProps } from '/@/components/Button' import type { ButtonProps as AntdButtonProps } from '/@/components/Button'
import type { RowProps } from 'ant-design-vue/lib/grid/Row'
import type { FormItem } from './formItem' import type { FormItem } from './formItem'
import type { ColEx, ComponentType } from './index' import type { ColEx, ComponentType } from './index'
import type { TableActionType } from '/@/components/Table/src/types/table' import type { TableActionType } from '/@/components/Table/src/types/table'
import type { CSSProperties } from 'vue'
import type { RowProps } from 'ant-design-vue/lib/grid/Row'
export type FieldMapToTime = [string, [string, string], (string | [string, string])?][] export type FieldMapToTime = [string, [string, string], (string | [string, string])?][]
......
<template>
<SvgIcon :size="size" :name="getSvgIcon" v-if="isSvgIcon" :class="[$attrs.class, 'anticon']" :spin="spin" />
<span
v-else
ref="elRef"
:class="[$attrs.class, 'app-iconify anticon', spin && 'app-iconify-spin']"
:style="getWrapStyle"
></span>
</template>
<script lang="ts"> <script lang="ts">
import type { PropType } from 'vue' import type { CSSProperties, PropType } from 'vue'
import { defineComponent, ref, watch, onMounted, nextTick, unref, computed, CSSProperties } from 'vue' import { computed, defineComponent, nextTick, onMounted, ref, unref, watch } from 'vue'
import SvgIcon from './SvgIcon.vue' import SvgIcon from './SvgIcon.vue'
import Iconify from '@purge-icons/generated' import Iconify from '@purge-icons/generated'
import { isString } from '/@/utils/is' import { isString } from '/@/utils/is'
...@@ -37,7 +28,7 @@ ...@@ -37,7 +28,7 @@
const isSvgIcon = computed(() => props.icon?.endsWith(SVG_END_WITH_FLAG)) const isSvgIcon = computed(() => props.icon?.endsWith(SVG_END_WITH_FLAG))
const getSvgIcon = computed(() => props.icon.replace(SVG_END_WITH_FLAG, '')) const getSvgIcon = computed(() => props.icon.replace(SVG_END_WITH_FLAG, ''))
const getIconRef = computed(() => `${props.prefix ? props.prefix + ':' : ''}${props.icon}`) const getIconRef = computed(() => `${props.prefix ? `${props.prefix}:` : ''}${props.icon}`)
const update = async () => { const update = async () => {
if (unref(isSvgIcon)) return if (unref(isSvgIcon)) return
...@@ -71,7 +62,7 @@ ...@@ -71,7 +62,7 @@
return { return {
fontSize: `${fs}px`, fontSize: `${fs}px`,
color: color, color,
display: 'inline-flex', display: 'inline-flex',
} }
}) })
...@@ -84,6 +75,18 @@ ...@@ -84,6 +75,18 @@
}, },
}) })
</script> </script>
<template>
<SvgIcon :size="size" :name="getSvgIcon" v-if="isSvgIcon" class="anticon" :class="[$attrs.class]" :spin="spin" />
<span
v-else
ref="elRef"
class="app-iconify anticon"
:class="[$attrs.class, spin && 'app-iconify-spin']"
:style="getWrapStyle"
></span>
</template>
<style lang="less"> <style lang="less">
.app-iconify { .app-iconify {
display: inline-block; display: inline-block;
......
<template>
<a-input
disabled
:style="{ width }"
:placeholder="t('component.icon.placeholder')"
:class="prefixCls"
v-model:value="currentSelect"
>
<template #addonAfter>
<a-popover
placement="bottomLeft"
trigger="click"
v-model="visible"
:overlayClassName="`${prefixCls}-popover`"
>
<template #title>
<div class="flex justify-between">
<a-input
:placeholder="t('component.icon.search')"
@change="debounceHandleSearchChange"
allowClear
/>
</div>
</template>
<template #content>
<div v-if="getPaginationList.length">
<ScrollContainer class="border border-solid border-t-0">
<ul class="flex flex-wrap px-2">
<li
v-for="icon in getPaginationList"
:key="icon"
:class="currentSelect === icon ? 'border border-primary' : ''"
class="p-2 w-1/8 cursor-pointer mr-1 mt-1 flex justify-center items-center border border-solid hover:border-primary"
@click="handleClick(icon)"
:title="icon"
>
<!-- <Icon :icon="icon" :prefix="prefix" /> -->
<SvgIcon v-if="isSvgMode" :name="icon" />
<Icon :icon="icon" v-else />
</li>
</ul>
</ScrollContainer>
<div class="flex py-2 items-center justify-center" v-if="getTotal >= pageSize">
<a-pagination
showLessItems
size="small"
:pageSize="pageSize"
:total="getTotal"
@change="handlePageChange"
/>
</div>
</div>
<template v-else
><div class="p-5"><a-empty /></div>
</template>
</template>
<span class="cursor-pointer px-2 py-1 flex items-center" v-if="isSvgMode && currentSelect">
<SvgIcon :name="currentSelect" />
</span>
<Icon :icon="currentSelect || 'ion:apps-outline'" class="cursor-pointer px-2 py-1" v-else />
</a-popover>
</template>
</a-input>
</template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, watchEffect, watch, unref } from 'vue' import { ref, unref, watch, watchEffect } from 'vue'
import { useDesign } from '/@/hooks/web/useDesign' import { useDesign } from '/@/hooks/web/useDesign'
import { ScrollContainer } from '/@/components/Container' import { ScrollContainer } from '/@/components/Container'
import { Input, Popover, Pagination, Empty } from 'ant-design-vue' import { Empty, Input, Pagination, Popover } from 'ant-design-vue'
import { useDebounceFn } from '@vueuse/core'
import svgIcons from 'virtual:svg-icons-names'
import iconsData from '../data/icons.data'
import Icon from './Icon.vue' import Icon from './Icon.vue'
import SvgIcon from './SvgIcon.vue' import SvgIcon from './SvgIcon.vue'
import iconsData from '../data/icons.data'
import { propTypes } from '/@/utils/propTypes' import { propTypes } from '/@/utils/propTypes'
import { usePagination } from '/@/hooks/web/usePagination' import { usePagination } from '/@/hooks/web/usePagination'
import { useDebounceFn } from '@vueuse/core'
import { useI18n } from '/@/hooks/web/useI18n' import { useI18n } from '/@/hooks/web/useI18n'
import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard' import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard'
import { useMessage } from '/@/hooks/web/useMessage' import { useMessage } from '/@/hooks/web/useMessage'
import svgIcons from 'virtual:svg-icons-names'
const props = defineProps({
value: propTypes.string,
width: propTypes.string.def('100%'),
pageSize: propTypes.number.def(140),
copy: propTypes.bool.def(false),
mode: propTypes.oneOf<('svg' | 'iconify')[]>(['svg', 'iconify']).def('iconify'),
})
const emit = defineEmits(['change', 'update:value'])
// 没有使用别名引入,是因为WebStorm当前版本还不能正确识别,会报unused警告 // 没有使用别名引入,是因为WebStorm当前版本还不能正确识别,会报unused警告
const AInput = Input const AInput = Input
const APopover = Popover const APopover = Popover
...@@ -103,16 +45,6 @@ ...@@ -103,16 +45,6 @@
return svgIcons.map((icon) => icon.replace('icon-', '')) return svgIcons.map((icon) => icon.replace('icon-', ''))
} }
const props = defineProps({
value: propTypes.string,
width: propTypes.string.def('100%'),
pageSize: propTypes.number.def(140),
copy: propTypes.bool.def(false),
mode: propTypes.oneOf<('svg' | 'iconify')[]>(['svg', 'iconify']).def('iconify'),
})
const emit = defineEmits(['change', 'update:value'])
const isSvgMode = props.mode === 'svg' const isSvgMode = props.mode === 'svg'
const icons = isSvgMode ? getSvgIcons() : getIcons() const icons = isSvgMode ? getSvgIcons() : getIcons()
...@@ -165,6 +97,74 @@ ...@@ -165,6 +97,74 @@
currentList.value = icons.filter((item) => item.includes(value)) currentList.value = icons.filter((item) => item.includes(value))
} }
</script> </script>
<template>
<AInput
disabled
:style="{ width }"
:placeholder="t('component.icon.placeholder')"
:class="prefixCls"
v-model:value="currentSelect"
>
<template #addonAfter>
<APopover
placement="bottomLeft"
trigger="click"
v-model="visible"
:overlayClassName="`${prefixCls}-popover`"
>
<template #title>
<div class="flex justify-between">
<AInput
:placeholder="t('component.icon.search')"
@change="debounceHandleSearchChange"
allowClear
/>
</div>
</template>
<template #content>
<div v-if="getPaginationList.length">
<ScrollContainer class="border border-solid border-t-0">
<ul class="flex flex-wrap px-2">
<li
v-for="icon in getPaginationList"
:key="icon"
:class="currentSelect === icon ? 'border border-primary' : ''"
class="p-2 w-1/8 cursor-pointer mr-1 mt-1 flex justify-center items-center border border-solid hover:border-primary"
@click="handleClick(icon)"
:title="icon"
>
<!-- <Icon :icon="icon" :prefix="prefix" /> -->
<SvgIcon v-if="isSvgMode" :name="icon" />
<Icon :icon="icon" v-else />
</li>
</ul>
</ScrollContainer>
<div class="flex py-2 items-center justify-center" v-if="getTotal >= pageSize">
<APagination
showLessItems
size="small"
:pageSize="pageSize"
:total="getTotal"
@change="handlePageChange"
/>
</div>
</div>
<template v-else
><div class="p-5"><AEmpty /></div>
</template>
</template>
<span class="cursor-pointer px-2 py-1 flex items-center" v-if="isSvgMode && currentSelect">
<SvgIcon :name="currentSelect" />
</span>
<Icon :icon="currentSelect || 'ion:apps-outline'" class="cursor-pointer px-2 py-1" v-else />
</APopover>
</template>
</AInput>
</template>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-icon-picker'; @prefix-cls: ~'@{namespace}-icon-picker';
......
<template>
<svg :class="[prefixCls, $attrs.class, spin && 'svg-icon-spin']" :style="getStyle" aria-hidden="true">
<use :xlink:href="symbolId" />
</svg>
</template>
<script lang="ts"> <script lang="ts">
import type { CSSProperties } from 'vue' import type { CSSProperties } from 'vue'
import { defineComponent, computed } from 'vue' import { computed, defineComponent } from 'vue'
import { useDesign } from '/@/hooks/web/useDesign' import { useDesign } from '/@/hooks/web/useDesign'
export default defineComponent({ export default defineComponent({
...@@ -45,6 +40,13 @@ ...@@ -45,6 +40,13 @@
}, },
}) })
</script> </script>
<template>
<svg :class="[prefixCls, $attrs.class, spin && 'svg-icon-spin']" :style="getStyle" aria-hidden="true">
<use :xlink:href="symbolId" />
</svg>
</template>
<style lang="less" scoped> <style lang="less" scoped>
@prefix-cls: ~'@{namespace}-svg-icon'; @prefix-cls: ~'@{namespace}-svg-icon';
......
<template>
<section
class="full-loading"
:class="{ absolute, [theme]: !!theme }"
:style="[background ? `background-color: ${background}` : '']"
v-show="loading"
>
<Spin v-bind="$attrs" :tip="tip" :size="size" :spinning="loading" />
</section>
</template>
<script lang="ts"> <script lang="ts">
import { PropType } from 'vue'
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
import type { PropType } from 'vue'
import { Spin } from 'ant-design-vue' import { Spin } from 'ant-design-vue'
import { SizeEnum } from '/@/enums/sizeEnum' import { SizeEnum } from '/@/enums/sizeEnum'
...@@ -46,6 +36,18 @@ ...@@ -46,6 +36,18 @@
}, },
}) })
</script> </script>
<template>
<section
class="full-loading"
:class="{ absolute, [theme]: !!theme }"
:style="[background ? `background-color: ${background}` : '']"
v-show="loading"
>
<Spin v-bind="$attrs" :tip="tip" :size="size" :spinning="loading" />
</section>
</template>
<style lang="less" scoped> <style lang="less" scoped>
.full-loading { .full-loading {
position: fixed; position: fixed;
......
import { VNode, defineComponent } from 'vue' import type { VNode } from 'vue'
import { createVNode, defineComponent, h, reactive, render } from 'vue'
import type { LoadingProps } from './typing' import type { LoadingProps } from './typing'
import { createVNode, render, reactive, h } from 'vue'
import Loading from './Loading.vue' import Loading from './Loading.vue'
export function createLoading(props?: Partial<LoadingProps>, target?: HTMLElement, wait = false) { export function createLoading(props?: Partial<LoadingProps>, target?: HTMLElement, wait = false) {
......
import { SizeEnum } from '/@/enums/sizeEnum' import type { SizeEnum } from '/@/enums/sizeEnum'
export interface LoadingProps { export interface LoadingProps {
tip: string tip: string
......
import { unref } from 'vue' import { unref } from 'vue'
import type { Ref } from 'vue'
import { createLoading } from './createLoading' import { createLoading } from './createLoading'
import type { LoadingProps } from './typing' import type { LoadingProps } from './typing'
import type { Ref } from 'vue'
export interface UseLoadingOptions { export interface UseLoadingOptions {
target?: any target?: any
......
import * as cesium from 'cesium'
import type { Cesium } from './src/tools'
// 重新导出一个自定义类型的 Cesium 变量,用来做类型同步
export default cesium as Cesium
import { withInstall } from '/@/utils'
import marsMap from './src/MarsMap.vue'
export const MarsMap = withInstall(marsMap)
export * from './src/tools'
<script lang="ts" setup>
// 引入cesium基础库
import 'mars3d-cesium/Build/Cesium/Widgets/widgets.css'
// 导入mars3d主库
import 'mars3d/dist/mars3d.css'
import * as mars3d from 'mars3d'
import './index.css'
import { config } from './tools'
import { nanoid } from 'nanoid'
import { propTypes } from '/@/utils/propTypes'
const props = defineProps({
id: propTypes.string,
width: propTypes.string.def('100%'),
height: propTypes.string.def('100%'),
})
const id = props.id ?? nanoid()
const mapRef = ref<mars3d.Map>()
onMounted(() => {
// 示例
// https://sandcastle.cesium.com/index.html
// 关闭一些基础配置
// http://events.jianshu.io/p/14b47da20909
const map = new mars3d.Map(id, config)
mapRef.value = map
})
onUnmounted(() => {
mapRef.value?.destroy()
})
defineExpose({
map: markRaw(mapRef),
})
</script>
<template>
<div :id="id" class="map"></div>
</template>
<style lang="less" scoped>
.map {
width: v-bind(width);
height: v-bind(height);
}
</style>
/**cesium 工具按钮栏*/
.cesium-viewer-toolbar {
top: auto;
bottom: 35px;
left: 12px;
right: auto;
}
.cesium-toolbar-button img {
height: 100%;
vertical-align: middle;
}
.cesium-viewer-toolbar > .cesium-toolbar-button,
.cesium-navigationHelpButton-wrapper,
.cesium-viewer-geocoderContainer {
margin-bottom: 5px;
float: left;
clear: both;
text-align: center;
}
.mar3d-toolButton img,
.mar3d-toolButton svg,
.mar3d-toolButton div {
width: 100%;
}
.cesium-button {
background-color: rgba(23, 49, 71, 0.7);
border: none;
color: #ffffff;
fill: #e6e6e6;
line-height: 32px;
}
.cesium-button:hover {
background-color: rgba(0, 138, 255, 0.7);
box-shadow: none;
border: none;
}
/**cesium 底图切换面板*/
.cesium-baseLayerPicker-dropDown {
bottom: 0;
left: 40px;
max-height: 700px;
margin-bottom: 5px;
background-color: rgba(23, 49, 71, 0.7);
}
/**cesium 帮助面板*/
.cesium-navigation-help {
top: auto;
bottom: 0;
left: 40px;
transform-origin: left bottom;
}
.cesium-navigation-help-instructions,
.cesium-navigation-button {
background-color: rgba(23, 49, 71, 0.8);
}
.cesium-navigation-button-selected,
.cesium-navigation-button-unselected:hover {
background-color: rgba(23, 49, 71, 1);
}
/**cesium 二维三维切换*/
.cesium-sceneModePicker-wrapper {
width: auto;
}
.cesium-sceneModePicker-wrapper .cesium-sceneModePicker-dropDown-icon {
float: right;
margin: 0 3px;
}
/**cesium POI查询输入框*/
.cesium-viewer-geocoderContainer .search-results {
left: 0;
right: 40px;
width: auto;
z-index: 9999;
}
.cesium-geocoder-searchButton {
background-color: rgba(23, 49, 71, 0.8);
}
.cesium-viewer-geocoderContainer .cesium-geocoder-input {
background-color: rgba(63, 72, 84, 0.7);
}
.cesium-viewer-geocoderContainer .cesium-geocoder-input:focus {
background-color: rgba(63, 72, 84, 0.9);
}
.cesium-viewer-geocoderContainer .search-results {
background-color: rgba(23, 49, 71, 0.8);
}
/**cesium info信息框*/
.cesium-infoBox {
top: 50px;
background: rgba(63, 72, 84, 0.9);
}
.cesium-infoBox-title {
background-color: rgba(23, 49, 71, 0.8);
}
/**cesium 任务栏的FPS信息*/
.cesium-performanceDisplay-defaultContainer {
top: auto;
bottom: 35px;
right: 50px;
}
.cesium-performanceDisplay-ms,
.cesium-performanceDisplay-fps {
color: #fff;
}
/**cesium tileset调试信息面板*/
.cesium-viewer-cesiumInspectorContainer {
top: 10px;
left: 10px;
right: auto;
}
.cesium-cesiumInspector {
background-color: rgba(23, 49, 71, 0.8);
}
/**覆盖mars3d内部控件的颜色等样式*/
.mars3d-compass .mars3d-compass-outer {
fill: rgba(23, 49, 71, 0.8);
}
.mars3d-contextmenu-ul,
.mars3d-sub-menu {
background-color: rgba(23, 49, 71, 0.8);
}
.mars3d-contextmenu-ul > li > a:hover,
.mars3d-sub-menu > li > a:hover,
.mars3d-contextmenu-ul > li > a:focus,
.mars3d-sub-menu > li > a:focus,
.mars3d-contextmenu-ul > li > .active,
.mars3d-sub-menu > li > .active {
background-color: #3ea6ff;
}
.mars3d-contextmenu-ul > .active > a,
.mars3d-sub-menu > .active > a,
.mars3d-contextmenu-ul > .active > a:hover,
.mars3d-sub-menu > .active > a:hover,
.mars3d-contextmenu-ul > .active > a:focus,
.mars3d-sub-menu > .active > a:focus {
background-color: #3ea6ff;
}
/* Popup样式*/
.mars3d-popup-color {
color: #ffffff;
}
.mars3d-popup-background {
background: rgba(23, 49, 71, 0.8);
}
.mars3d-template-content label {
padding-right: 6px;
}
.mars3d-template-titile {
border-bottom: 1px solid #3ea6ff;
}
.mars3d-template-titile a {
font-size: 16px;
}
.mars3d-popup-btn-custom {
padding: 3px 10px;
border: 1px solid #209ffd;
background: #209ffd1c;
}
.mars3d-popup-content {
margin: 15px;
}
.mars3d-divGraphic:hover {
z-index: 999 !important;
}
import { Ref } from 'vue'
import * as Cesium from 'mars3d-cesium'
import * as mars3d from 'mars3d'
// 定义类型
export type Cesium = typeof Cesium
// 服务域名
const tdtUrl = 'https://t{s}.tianditu.gov.cn/'
// 服务负载子域
const subdomains = ['0', '1', '2', '3', '4', '5', '6', '7']
/**
* 资源服务器地址
*/
export const host = 'http://192.168.0.156'
// 天地图 token
export const token = 'aa0ccd36f2dbb86dbb16cbf63f0034a6'
interface MarsMapConfig {
scene?: mars3d.Map.sceneOptions
control?: mars3d.Map.controlOptions
mouse?: mars3d.Map.mouseOptions
effect?: mars3d.Map.effectOptions
terrain?: mars3d.Map.terrainOptions
basemaps?: mars3d.Map.basemapOptions[]
layers?: mars3d.Map.layerOptions[]
chinaCRS?: mars3d.ChinaCRS
lang?: mars3d.LangType
templateValues?: any
token?: mars3d.Map.tokenOptions
}
export const config: MarsMapConfig = {
basemaps: [{ name: '天地图', type: 'tdt', layer: 'img_d', show: true }],
scene: {
center: { lat: 30.054604, lng: 108.885436, alt: 22000000, heading: 0, pitch: -90 },
// center: {
// lat: 30.48564,
// lng: 112.86168,
// alt: 350,
// heading: 180,
// pitch: -25,
// roll: 0,
// },
scene3DOnly: false,
shadows: false,
removeDblClick: true,
sceneMode: 3,
showSun: true,
showMoon: true,
showSkyBox: true,
showSkyAtmosphere: true,
fog: true,
fxaa: true,
requestRenderMode: true,
globe: {
depthTestAgainstTerrain: false,
baseColor: '#546a53',
showGroundAtmosphere: true,
enableLighting: false,
},
cameraController: {
zoomFactor: 3.0,
minimumZoomDistance: 1,
maximumZoomDistance: 50000000,
enableRotate: true,
enableTranslate: true,
enableTilt: true,
enableZoom: true,
enableCollisionDetection: true,
minimumCollisionTerrainHeight: 15000,
},
},
control: {
homeButton: true,
baseLayerPicker: true,
sceneModePicker: true,
vrButton: false,
fullscreenButton: true,
navigationHelpButton: true,
animation: false,
timeline: false,
infoBox: false,
geocoder: false,
selectionIndicator: false,
contextmenu: { hasDefault: true },
mouseDownView: true,
zoom: { insertIndex: 1 },
compass: { bottom: 'toolbar', left: '5px' },
distanceLegend: { left: '0px', bottom: '2px' },
locationBar: {
fps: true,
format: "<div>经度:{lng}</div> <div>纬度:{lat}</div> <div>海拔:{alt}米</div> <div class='hide700'>层级:{level}</div><div>方向:{heading}°</div> <div>俯仰角:{pitch}°</div><div class='hide700'>视高:{cameraHeight}米</div>",
},
},
}
/**
* 默认的 Cesium3DTileset 配置项
*/
export const defaultTilesetConfig = {
show: true,
backFaceCulling: true,
maximumScreenSpaceError: 16,
maximumMemoryUsage: 256,
cullWithChildrenBounds: true,
cullRequestsWhileMovingMultiplier: 10,
dynamicScreenSpaceError: true,
dynamicScreenSpaceErrorDensity: 0.00278,
dynamicScreenSpaceErrorFactor: 4,
dynamicScreenSpaceErrorHeightFalloff: 0.25,
skipLevelOfDetail: true,
baseScreenSpaceError: 1024,
skipScreenSpaceErrorFactor: 16,
skipLevels: 1,
immediatelyLoadDesiredLevelOfDetail: false,
loadSiblings: true,
luminanceAtZenith: 0.2,
preferLeaves: true,
progressiveResolutionHeightFraction: 0.5,
debugFreezeFrame: false,
debugColorizeTiles: false,
debugWireframe: false,
debugShowBoundingVolume: false,
debugShowContentBoundingVolume: false,
debugShowViewerRequestVolume: false,
debugShowGeometricError: false,
debugShowRenderingStatistics: false,
debugShowMemoryUsage: false,
debugShowUrl: false,
}
/**
* 默认显示区域
*/
export const getDefaultRectangle = (Cesium: Cesium) =>
Cesium.Rectangle.fromDegrees(112.858933, 30.473384, 112.866068, 30.481188)
/**
* 天地图影像底图
*/
export const buildBaseImageryProvider = (Cesium: Cesium) => {
return new Cesium.UrlTemplateImageryProvider({
url: tdtUrl + 'DataServer?T=img_w&x={x}&y={y}&l={z}&tk=' + token,
subdomains: subdomains,
tilingScheme: new Cesium.WebMercatorTilingScheme(),
maximumLevel: 18,
})
}
/**
* 2D 建模图层
*/
export const buildTiffImageryProvider = (Cesium: Cesium) => {
return new Cesium.UrlTemplateImageryProvider({
url: `${host}:1234/api/tilesets/kshg-2d-image/{z}/{x}/{y}.jpg`,
tilingScheme: new Cesium.WebMercatorTilingScheme(),
minimumLevel: 10,
maximumLevel: 24,
tileWidth: 256,
tileHeight: 256,
rectangle: getDefaultRectangle(Cesium),
})
}
/**
* 添加 2D 建模图层
* @param viewer Cesium.Viewer
*/
export const add2DImageLayer = (Cesium: Cesium, viewer: Cesium.Viewer) => {
// 叠加 2D 影像底图
viewer.imageryLayers.addImageryProvider(buildTiffImageryProvider(Cesium))
// 切换到影像可视区域
viewer.scene.camera.setView({ destination: getDefaultRectangle(Cesium) })
}
/**
* 添加 3D 倾斜摄影模型
* @param viewer Cesium.Viewer
*/
export const add3DTileset = (Cesium: Cesium, viewer: Cesium.Viewer) => {
const tileset = new Cesium.Cesium3DTileset({
url: `${host}:9003/model/ehXZf942/tileset.json`,
...defaultTilesetConfig,
shadows: Cesium.ShadowMode.DISABLED,
})
tileset.readyPromise.then((tileset) => {
// 模型与地面的高度差,单位:米
// 20m 是通过手动调试出来的结果
const height = -20
const cartographic = Cesium.Cartographic.fromCartesian(tileset.boundingSphere.center)
const surface = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, 0.0)
const offset = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, height)
const translation = Cesium.Cartesian3.subtract(offset, surface, new Cesium.Cartesian3())
tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation)
// 叠加 3D 模型
viewer.scene.primitives.add(tileset)
// 切换到模型可视区域
// viewer.zoomTo(tileset, new Cesium.HeadingPitchRange(0.5, -0.5, tileset.boundingSphere.radius * 1.0))
// 切换到模型最佳可视区域
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(112.86168, 30.48564, 350),
orientation: {
heading: Cesium.Math.toRadians(180),
pitch: Cesium.Math.toRadians(-25),
roll: 0,
},
})
})
}
/**
* 获取 Cesium Viewer 实例
* @param ref 地图组件 ref
* @returns Cesium.Viewer
*/
export const getMapViewerInstance = (ref: Ref<any>): Cesium.Viewer => toRaw(ref.value?.viewer) as Cesium.Viewer
<template>
<div ref="wrapRef"></div>
</template>
<script lang="ts"> <script lang="ts">
import type { Ref } from 'vue' import type { Ref } from 'vue'
import { defineComponent, ref, unref, nextTick, computed, watch, onBeforeUnmount, onDeactivated } from 'vue' import { computed, defineComponent, nextTick, onBeforeUnmount, onDeactivated, ref, unref, watch } from 'vue'
import Vditor from 'vditor' import Vditor from 'vditor'
import 'vditor/dist/index.css' import 'vditor/dist/index.css'
import { useLocale } from '/@/locales/useLocale' import { useLocale } from '/@/locales/useLocale'
...@@ -106,7 +103,7 @@ ...@@ -106,7 +103,7 @@
}) })
}, },
blur: () => { blur: () => {
//unref(vditorRef)?.setValue(props.value); // unref(vditorRef)?.setValue(props.value);
}, },
...bindValue, ...bindValue,
cache: { cache: {
...@@ -136,3 +133,7 @@ ...@@ -136,3 +133,7 @@
}, },
}) })
</script> </script>
<template>
<div ref="wrapRef"></div>
</template>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论