From 2adf6d26fa6b82c1843a6dcba047446c61ff967f Mon Sep 17 00:00:00 2001 From: Linzp Date: Wed, 15 Apr 2026 17:51:05 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/components/GroupSelect/GroupFolder.js | 87 ++++++ src/components/GroupSelect/index.js | 307 ++++++++++--------- src/components/GroupSelect/locale/en-US.js | 22 ++ src/components/GroupSelect/locale/zh-CN.js | 22 ++ src/components/GroupSelect/style.module.scss | 62 +++- src/components/GroupSelect/withLocale.js | 14 + 7 files changed, 366 insertions(+), 150 deletions(-) create mode 100644 src/components/GroupSelect/GroupFolder.js create mode 100644 src/components/GroupSelect/locale/en-US.js create mode 100644 src/components/GroupSelect/locale/zh-CN.js create mode 100644 src/components/GroupSelect/withLocale.js diff --git a/package.json b/package.json index 7f24cac..7cfe1a2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kne-components/components-admin", - "version": "1.1.29", + "version": "1.1.30", "description": "用于实现一个后台管理系统的必要组件", "scripts": { "init": "husky", diff --git a/src/components/GroupSelect/GroupFolder.js b/src/components/GroupSelect/GroupFolder.js new file mode 100644 index 0000000..465dcb7 --- /dev/null +++ b/src/components/GroupSelect/GroupFolder.js @@ -0,0 +1,87 @@ +import { createWithRemoteLoader } from '@kne/remote-loader'; +import Fetch from '@kne/react-fetch'; +import { Tree, Flex } from 'antd'; +import useControlValue from '@kne/use-control-value'; +import { FolderOutlined, FolderOpenOutlined } from '@ant-design/icons'; +import { useMemo } from 'react'; +import { useIntl } from '@kne/react-intl'; +import withLocale from './withLocale'; +import styles from './style.module.scss'; + +const GroupFolder = createWithRemoteLoader({ + modules: ['components-core:Global@usePreset', 'components-core:Global@useGlobalValue'] +})( + withLocale(({ remoteModules, type, language: propsLanguage, showRoot = true, rootTitle, children, ...props }) => { + const [value, onChange] = useControlValue(props); + const { formatMessage } = useIntl(); + const [usePreset, useGlobalValue] = remoteModules; + const { apis } = usePreset(); + const locale = useGlobalValue('locale'); + + const language = propsLanguage || locale || 'zh-CN'; + const displayRootTitle = rootTitle || formatMessage({ id: 'GroupSelectAll' }); + + const selectedKeys = useMemo(() => { + if (!value) return ['root']; + return [value]; + }, [value]); + + return ( + { + if (loading) { + return
{formatMessage({ id: 'GroupSelectLoading' })}
; + } + + const treeData = showRoot + ? [ + { + code: 'root', + name: displayRootTitle, + children: data || [] + } + ] + : data || []; + const tree = ( + item.name} + fieldNames={{ title: 'name', key: 'code', children: 'children' }} + onSelect={(keys, item) => { + const selectedKey = keys[0]; + if (onChange) { + onChange(selectedKey === 'root' ? null : selectedKey, selectedKey === 'root' ? null : item.selectedNodes[0]); + } + }} + icon={props => { + if (props.key === 'root') return ; + return props.expanded ? : ; + }} + {...props} + /> + ); + if (typeof children === 'function') { + return children({ treeData, selectedKeys, onChange, tree }); + } + return ( + + + {tree} + + + {children} + + + ); + }} + /> + ); + }) +); + +export default GroupFolder; diff --git a/src/components/GroupSelect/index.js b/src/components/GroupSelect/index.js index a40ee29..3a7d1d9 100644 --- a/src/components/GroupSelect/index.js +++ b/src/components/GroupSelect/index.js @@ -1,167 +1,182 @@ import { createWithRemoteLoader } from '@kne/remote-loader'; import { App, Button } from 'antd'; import merge from 'lodash/merge'; +import { useIntl } from '@kne/react-intl'; +import withLocale from './withLocale'; import style from './style.module.scss'; -const GroupSelect = createWithRemoteLoader({ - modules: [ - 'components-core:FormInfo', - 'components-core:FormInfo@useFormModal', - 'components-core:Global@usePreset', - 'components-core:Global@useGlobalValue' - ] -})(({ - remoteModules, - name, - label, - type, - language: propsLanguage, - rule, - apis, - valueKey = 'code', - labelKey = 'name', - single, - placeholder, - disabled, - groupName = '标签', - ...props -}) => { - const [FormInfo, useFormModal, usePreset, useGlobalValue] = remoteModules; - const { fields } = FormInfo; - const { SuperSelectTableList, SuperSelectTree } = fields; - const { ajax, apis: presetApis } = usePreset(); - const { message, modal } = App.useApp(); - const formModal = useFormModal(); - const locale = useGlobalValue('locale'); +const createComponent = (callback = item => item) => { + return createWithRemoteLoader({ + modules: [ + 'components-core:FormInfo', + 'components-core:FormInfo@useFormModal', + 'components-core:Global@usePreset', + 'components-core:Global@useGlobalValue' + ] + })( + withLocale(({ + remoteModules, + name, + label, + type, + language: propsLanguage, + rule, + apis, + valueKey = 'code', + labelKey = 'name', + single, + placeholder, + disabled, + groupName: propsGroupName, + ...props + }) => { + const { formatMessage } = useIntl(); + const groupName = propsGroupName || formatMessage({ id: 'GroupSelectDefaultName' }); + const [FormInfo, useFormModal, usePreset, useGlobalValue] = remoteModules; + const { fields } = FormInfo; + const { SuperSelectTableList, SuperSelectTree } = fields; + const { ajax, apis: presetApis } = usePreset(); + const { message, modal } = App.useApp(); + const formModal = useFormModal(); + const locale = useGlobalValue('locale'); - const language = propsLanguage || locale || 'zh-CN'; + const language = propsLanguage || locale || 'zh-CN'; - const handleDelete = async (item, { fetchApi, value, setValue }) => { - try { - const { data: resData } = await ajax( - Object.assign({}, apis?.remove || presetApis?.group?.remove, { - data: { id: item.id, code: item.code, type } - }) - ); - - if (resData.code !== 0) { - return; - } - - message.success(`删除${groupName}成功`); - fetchApi.reload(); - - // 如果删除的项目在已选值中,从已选值中移除 - if (value && value.length > 0 && value.find(target => item[valueKey] === target[valueKey])) { - const index = value.findIndex(target => item[valueKey] === target[valueKey]); - const newValue = value.slice(0); - newValue.splice(index, 1); - setValue(newValue); - } - } catch (error) { - message.error(`删除${groupName}失败`); - } - }; - - const handleAdd = ({ reload }) => { - formModal({ - title: `添加${groupName}`, - size: 'small', - formProps: { - onSubmit: async formData => { + const handleDelete = async (item, { fetchApi, value, setValue }) => { + try { const { data: resData } = await ajax( - Object.assign({}, apis?.create || presetApis?.group?.create, { - data: Object.assign({}, formData, { type, language }) + Object.assign({}, apis?.remove || presetApis?.group?.remove, { + data: { id: item.id, code: item.code, type } }) ); + if (resData.code !== 0) { - return false; + return; } - message.success(`添加${groupName}成功`); - reload(); + + message.success(formatMessage({ id: 'GroupSelectDeleteSuccess' })); + fetchApi.reload(); + + // 如果删除的项目在已选值中,从已选值中移除 + if (value && value.length > 0 && value.find(target => item[valueKey] === target[valueKey])) { + const index = value.findIndex(target => item[valueKey] === target[valueKey]); + const newValue = value.slice(0); + newValue.splice(index, 1); + setValue(newValue); + } + } catch (error) { + message.error(formatMessage({ id: 'GroupSelectDeleteFailed' })); } - }, - children: ( - , - , - , - - ]} - /> - ) - }); - }; + }; + + const handleAdd = ({ reload }) => { + formModal({ + title: formatMessage({ id: 'GroupSelectAdd' }, { name: groupName }), + size: 'small', + formProps: { + onSubmit: async formData => { + const { data: resData } = await ajax( + Object.assign({}, apis?.create || presetApis?.group?.create, { + data: Object.assign({}, formData, { type, language }) + }) + ); + if (resData.code !== 0) { + return false; + } + message.success(formatMessage({ id: 'GroupSelectAddSuccess' })); + reload(); + } + }, + children: ( + , + , + , + + ]} + /> + ) + }); + }; - const hasApis = apis !== undefined; - const showAdd = hasApis ? !!apis?.create : !!presetApis?.group?.create; - const showDelete = hasApis ? !!apis?.remove : !!presetApis?.group?.remove; + const hasApis = apis !== undefined; + const showAdd = hasApis ? !!apis?.create : !!presetApis?.group?.create; + const showDelete = hasApis ? !!apis?.remove : !!presetApis?.group?.remove; - const columns = [ - { title: '编码', name: 'code', span: 4 }, - { title: '名称', name: 'name', span: 6 }, - { title: '描述', name: 'description', span: showDelete ? 10 : 14 } - ]; + const columns = [ + { title: formatMessage({ id: 'GroupSelectCode' }), name: 'code', span: 4 }, + { title: formatMessage({ id: 'GroupSelectName' }), name: 'name', span: 6 }, + { title: formatMessage({ id: 'GroupSelectDescription' }), name: 'description', span: showDelete ? 10 : 14 } + ]; - if (showDelete) { - columns.push({ - title: '操作', - name: 'options', - span: 4, - getValueOf: (item, { context }) => { - const { fetchApi, value, setValue } = context; - return ( - e.stopPropagation()}> - { - modal.confirm({ - title: '确认删除', - content: `确定要删除${groupName}"${item.name}"吗?`, - okText: '确定', - cancelText: '取消', - onOk: () => handleDelete(item, { fetchApi, value, setValue }) - }); - }}> - 删除 - - - ); + if (showDelete) { + columns.push({ + title: formatMessage({ id: 'GroupSelectOperation' }), + name: 'options', + span: 4, + getValueOf: (item, { context }) => { + const { fetchApi, value, setValue } = context; + return ( + e.stopPropagation()}> + { + modal.confirm({ + title: formatMessage({ id: 'GroupSelectConfirmDelete' }), + content: formatMessage({ id: 'GroupSelectDeleteConfirm' }, { name: groupName, title: item.name }), + okText: formatMessage({ id: 'GroupSelectConfirm' }), + cancelText: formatMessage({ id: 'GroupSelectCancel' }), + onOk: () => handleDelete(item, { fetchApi, value, setValue }) + }); + }}> + {formatMessage({ id: 'GroupSelectDelete' })} + + + ); + } + }); } - }); - } - return ( - ({ - filter: { keyword: searchText } - })} - pagination={{ paramsType: 'params' }} - columns={columns} - isPopup={false} - footer={showAdd ? ({ reload }) => : null} - {...props} - /> + const Component = callback(SuperSelectTableList); + return ( + ({ + filter: { keyword: searchText } + })} + pagination={{ paramsType: 'params' }} + columns={columns} + isPopup={false} + footer={showAdd ? ({ reload }) => : null} + {...props} + /> + ); + }) ); -}); +}; + +const GroupSelect = createComponent(); + +GroupSelect.Field = createComponent(item => item.Field); + +export { default as GroupFolder } from './GroupFolder'; export default GroupSelect; diff --git a/src/components/GroupSelect/locale/en-US.js b/src/components/GroupSelect/locale/en-US.js new file mode 100644 index 0000000..23a5e8a --- /dev/null +++ b/src/components/GroupSelect/locale/en-US.js @@ -0,0 +1,22 @@ +export default { + GroupSelectDefaultName: 'Tag', + GroupSelectCode: 'Code', + GroupSelectName: 'Name', + GroupSelectParent: 'Parent', + GroupSelectDescription: 'Description', + GroupSelectOperation: 'Operation', + GroupSelectDelete: 'Delete', + GroupSelectConfirm: 'Confirm', + GroupSelectCancel: 'Cancel', + GroupSelectConfirmDelete: 'Confirm Delete', + GroupSelectDeleteSuccess: 'Deleted successfully', + GroupSelectDeleteFailed: 'Delete failed', + GroupSelectAddSuccess: 'Added successfully', + GroupSelectAdd: 'Add {name}', + GroupSelectDeleteConfirm: 'Are you sure you want to delete {name} "{title}"?', + GroupSelectCodePlaceholder: 'Please enter a unique code', + GroupSelectNamePlaceholder: 'Please enter {name} name', + GroupSelectDescPlaceholder: 'Please enter {name} description', + GroupSelectLoading: 'Loading...', + GroupSelectAll: 'All' +}; diff --git a/src/components/GroupSelect/locale/zh-CN.js b/src/components/GroupSelect/locale/zh-CN.js new file mode 100644 index 0000000..2b85d10 --- /dev/null +++ b/src/components/GroupSelect/locale/zh-CN.js @@ -0,0 +1,22 @@ +export default { + GroupSelectDefaultName: '标签', + GroupSelectCode: '编码', + GroupSelectName: '名称', + GroupSelectParent: '父级', + GroupSelectDescription: '描述', + GroupSelectOperation: '操作', + GroupSelectDelete: '删除', + GroupSelectConfirm: '确定', + GroupSelectCancel: '取消', + GroupSelectConfirmDelete: '确认删除', + GroupSelectDeleteSuccess: '删除成功', + GroupSelectDeleteFailed: '删除失败', + GroupSelectAddSuccess: '添加成功', + GroupSelectAdd: '添加{name}', + GroupSelectDeleteConfirm: '确定要删除{name}"{title}"吗?', + GroupSelectCodePlaceholder: '请输入唯一编码', + GroupSelectNamePlaceholder: '请输入{name}名称', + GroupSelectDescPlaceholder: '请输入{name}描述', + GroupSelectLoading: '加载中...', + GroupSelectAll: '全部' +}; diff --git a/src/components/GroupSelect/style.module.scss b/src/components/GroupSelect/style.module.scss index a03722f..dd5b86b 100644 --- a/src/components/GroupSelect/style.module.scss +++ b/src/components/GroupSelect/style.module.scss @@ -1,5 +1,61 @@ -:global { - .select-table-search { - padding: 0; +.group-folder { + background-color: #f5f5f5; + border-radius: 8px; + padding: 12px; + height: 100%; + overflow: auto; + + :global { + .ant-tree { + background: transparent; + min-width: max-content; + + .ant-tree-list-holder { + overflow-x: auto; + } + + .ant-tree-treenode { + padding: 4px 8px; + border-radius: 6px; + transition: all 0.2s; + width: 100%; + } + + .ant-tree-node-selected { + background-color: var(--primary-color-1) !important; + + .ant-tree-title { + color: var(--primary-color); + font-weight: 500; + } + + .ant-tree-iconEle { + color: var(--primary-color); + } + } + + .ant-tree-switcher { + line-height: 32px; + } + + .ant-tree-iconEle { + line-height: 32px; + margin-right: 4px; + } + + .ant-tree-title { + font-size: 14px; + line-height: 32px; + min-width: 100px; + display: inline-block; + color: #595959; + } + } } +} + +.loading-wrapper { + padding: 24px 12px; + color: #999; + text-align: center; } \ No newline at end of file diff --git a/src/components/GroupSelect/withLocale.js b/src/components/GroupSelect/withLocale.js new file mode 100644 index 0000000..a7a53a7 --- /dev/null +++ b/src/components/GroupSelect/withLocale.js @@ -0,0 +1,14 @@ +import { createWithIntlProvider } from '@kne/react-intl'; +import zhCN from './locale/zh-CN'; +import enUS from './locale/en-US'; + +const withLocale = createWithIntlProvider({ + defaultLocale: 'zh-CN', + messages: { + 'zh-CN': zhCN, + 'en-US': enUS + }, + namespace: 'components-admin:GroupSelect' +}); + +export default withLocale;