feat(web): app page ui upgrade

This commit is contained in:
zhaoying
2026-03-07 13:46:08 +08:00
parent e2b6c713e7
commit 06fe3f2f01
29 changed files with 938 additions and 961 deletions

View File

@@ -2,10 +2,10 @@
* @Author: ZhaoYing
* @Date: 2026-02-05 10:45:08
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-10 17:59:37
* @Last Modified time: 2026-03-04 10:41:35
*/
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import { Space, List, Flex, Tooltip } from 'antd';
import { List, Flex, Tooltip, Form } from 'antd';
import { useTranslation } from 'react-i18next';
import clsx from 'clsx'
@@ -31,7 +31,7 @@ interface SkillModalProps {
*
* A modal dialog for selecting skills from a searchable list.
* Features:
* - Search functionality to filter skills by search
* - Search functionality to filter skills by keywords
* - Grid layout displaying skill cards with icons and descriptions
* - Multi-select capability with visual feedback
* - Excludes already selected skills from the list
@@ -49,17 +49,19 @@ const SkillListModal = forwardRef<SkillModalRef, SkillModalProps>(({
const [visible, setVisible] = useState(false);
const [list, setList] = useState<Skill[]>([])
const [filterList, setFilterList] = useState<Skill[]>([])
const [query, setQuery] = useState<{search?: string}>({})
const [selectedIds, setSelectedIds] = useState<string[]>([])
const [selectedRows, setSelectedRows] = useState<Skill[]>([])
const [form] = Form.useForm()
const query = Form.useWatch([], form)
/**
* Closes the modal and resets all state
* Clears search query, selected IDs, and selected rows
*/
const handleClose = () => {
setVisible(false);
setQuery({})
form.resetFields()
setSelectedIds([])
setSelectedRows([])
};
@@ -70,7 +72,7 @@ const SkillListModal = forwardRef<SkillModalRef, SkillModalProps>(({
*/
const handleOpen = () => {
setVisible(true);
setQuery({})
form.resetFields()
setSelectedIds([])
setSelectedRows([])
};
@@ -82,7 +84,7 @@ const SkillListModal = forwardRef<SkillModalRef, SkillModalProps>(({
if (visible) {
getList()
}
}, [query.search, visible])
}, [query?.search, visible])
/**
* Fetches the skill list from API with current search parameters
@@ -96,6 +98,8 @@ const SkillListModal = forwardRef<SkillModalRef, SkillModalProps>(({
.then(res => {
const response = res as { items: Skill[] }
setList(response.items || [])
setSelectedIds([])
setSelectedRows([])
})
}
@@ -117,17 +121,6 @@ const SkillListModal = forwardRef<SkillModalRef, SkillModalProps>(({
handleClose
}));
/**
* Handles search input changes and resets selection
* Clears current selections when search query changes
* @param value - Search keyword
*/
const handleSearch = (value?: string) => {
setQuery({search: value})
setSelectedIds([])
setSelectedRows([])
}
/**
* Toggles skill selection state
* Adds skill to selection if not selected, removes if already selected
@@ -154,7 +147,7 @@ const SkillListModal = forwardRef<SkillModalRef, SkillModalProps>(({
if (list.length && selectedList.length) {
const unSelectedList = list.filter(item => selectedList.findIndex(vo => vo.id === item.id) < 0)
setFilterList([...unSelectedList])
} else if (list.length) {
} else {
setFilterList([...list])
}
}, [list, selectedList])
@@ -169,13 +162,16 @@ const SkillListModal = forwardRef<SkillModalRef, SkillModalProps>(({
onOk={handleSave}
width={1000}
>
<Space size={24} direction="vertical" className="rb:w-full">
<Flex gap={24} vertical>
{/* Search input for filtering skills */}
<SearchInput
placeholder={t('skills.searchPlaceholder')}
onSearch={handleSearch}
style={{ width: '100%' }}
/>
<Form form={form}>
<Form.Item name="search" noStyle>
<SearchInput
placeholder={t('skills.searchPlaceholder')}
className="rb:w-full!"
/>
</Form.Item>
</Form>
{/* Display empty state or skill grid */}
{filterList.length === 0
? <Empty />
@@ -191,9 +187,9 @@ const SkillListModal = forwardRef<SkillModalRef, SkillModalProps>(({
})} onClick={() => handleSelect(item)}>
<Flex>
{/* Skill avatar showing first letter of name */}
<div className="rb:w-12 rb:h-12 rb:rounded-lg rb:mr-3.25 rb:bg-[#155eef] rb:flex rb:items-center rb:justify-center rb:text-[28px] rb:text-[#ffffff]">
<Flex align="center" justify="center" className="rb:size-12 rb:rounded-lg rb:mr-3.25! rb:bg-[#155eef] rb:text-[28px] rb:text-white">
{item.name[0]}
</div>
</Flex>
{/* Skill name and description */}
<div className="rb:flex-1 rb:max-w-[calc(100%-60px)]">
<div className="rb:font-medium rb:wrap-break-word rb:line-clamp-1">{item.name}</div>
@@ -207,7 +203,7 @@ const SkillListModal = forwardRef<SkillModalRef, SkillModalProps>(({
)}
/>
}
</Space>
</Flex>
</RbModal>
</>
);

View File

@@ -2,12 +2,12 @@
* @Author: ZhaoYing
* @Date: 2026-02-05 10:43:03
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-05 11:10:01
* @Last Modified time: 2026-02-25 15:36:14
*/
import { type FC, useEffect, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { Space, Button, Form, Flex, Tooltip, Checkbox } from 'antd'
import { CloseOutlined, CheckCircleFilled } from '@ant-design/icons'
import { CheckCircleFilled } from '@ant-design/icons'
import type {
SkillConfigForm,
@@ -15,7 +15,6 @@ import type {
} from './types'
import Empty from '@/components/Empty'
import SkillListModal from './SkillListModal'
import Card from '../Card'
import RbAlert from '@/components/RbAlert'
import type { Skill } from '@/views/Skills/types'
@@ -86,20 +85,19 @@ const SkillsItem: FC<SkillsItemProps> = ({
}, [allSkills])
return (
<Card
title={title}
extra={
<Space>
<div>
<Flex align="center" justify="space-between" className="rb:mb-2!">
<div className="rb:text-[#212332] rb:font-medium rb:leading-4.5 rb:px-1">{title}</div>
<Space size={16}>
{/* "Allow all skills" checkbox - only shown if supportAll is true */}
{supportAll && <Form.Item name={[...parentName, 'all_skills']} valuePropName="checked" noStyle>
<Checkbox>{t('application.allSkill')}</Checkbox>
<Checkbox className="rb:text-[12px]!">{t('application.allSkill')}</Checkbox>
</Form.Item>}
{/* Add skill button - disabled when all skills are enabled */}
<Button disabled={allSkills} style={{ padding: '0 8px', height: '24px' }} onClick={handleAddSkill}>+ {t('application.addSkill')}</Button>
<Button disabled={allSkills} type="link" className="rb:h-4! rb:p-0! rb:font-medium! rb:text-[12px]! rb:text-[#212332]" onClick={handleAddSkill}>+ {t('application.addSkill')}</Button>
</Space>
}
variant="borderL"
>
</Flex>
{/* Show alert when all skills enabled, otherwise show skill list */}
{allSkills
? <RbAlert color="green" icon={<CheckCircleFilled />}>{t('application.allSkillIntro')}</RbAlert>
@@ -111,39 +109,29 @@ const SkillsItem: FC<SkillsItemProps> = ({
<Empty size={88} subTitle={emptyTitle} />
) : (
/* Render list of configured skills */
<Space direction="vertical" size={12} className="rb:w-full">
<Flex vertical gap={12}>
{fields.map((field) => {
const skill = form.getFieldValue([...parentName, 'skill_ids', field.name])
return (
/* Individual skill card */
<div key={field.key} className="rb:flex rb:items-center rb:justify-between rb:p-[12px_16px] rb:bg-[#FBFDFF] rb:border rb:border-[#DFE4ED] rb:rounded-lg">
<Flex key={field.key} align="center" justify="space-between" className="rb:p-3! rb:bg-[#FFFFFF] rb-border rb:rounded-lg">
<Flex className="rb:flex-1 rb:max-w-[calc(100%-186px)]!">
{/* Skill icon or fallback initial */}
{skill.icon
? <img src={skill.icon} className="rb:mr-3.25 rb:size-12 rb:rounded-lg" />
: <div className="rb:w-12 rb:h-12 rb:rounded-lg rb:mr-3.25 rb:bg-[#155eef] rb:flex rb:items-center rb:justify-center rb:text-[28px] rb:text-[#ffffff]">
{skill.name?.[0]}
</div>
}
{/* Skill name and description */}
<div className="rb:flex-1 rb:max-w-[calc(100%-60px)]">
<div className="rb:font-medium rb:wrap-break-word rb:line-clamp-1">{skill.name}</div>
<div className="rb:font-medium rb:text-[#212332] rb:leading-5 rb:wrap-break-word rb:line-clamp-1">{skill.name}</div>
<Tooltip title={skill.description}>
<div className="rb:text-[#5B6167] rb:text-[12px] rb:leading-4.25 rb:font-regular rb:-mt-1 rb:wrap-break-word rb:line-clamp-1">{skill.description}</div>
<div className="rb:text-[#5B6167] rb:text-[12px] rb:leading-4.25 rb:font-regular rb:mt-1 rb:wrap-break-word rb:line-clamp-1">{skill.description}</div>
</Tooltip>
</div>
</Flex>
<Space size={16} align="center">
{/* Remove skill button */}
<CloseOutlined
className="rb:cursor-pointer rb:text-[#5B6167] hover:rb:text-[#155EEF]"
onClick={() => remove(field.name)}
/>
</Space>
</div>
<div
className="rb:size-6 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/deleteBorder.svg')] rb:hover:bg-[url('@/assets/images/deleteBg.svg')]"
onClick={() => remove(field.name)}
></div>
</Flex>
)
})}
</Space>
</Flex>
)
)}
</Form.List>
@@ -155,7 +143,7 @@ const SkillsItem: FC<SkillsItemProps> = ({
selectedList={form.getFieldValue([...parentName, 'skill_ids']) || []}
refresh={refresh}
/>
</Card>
</div>
)
}
export default SkillsItem

View File

@@ -1,17 +1,18 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-05 10:42:56
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-05 10:42:56
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-26 10:18:56
*/
import { useEffect, type FC } from 'react'
import { useTranslation } from 'react-i18next'
import { Space, Button, Switch, Form, Flex } from 'antd'
import { Space, Switch, Form, Flex } from 'antd'
import clsx from 'clsx'
import type {
SkillConfigForm,
} from './types'
import Card from '../Card'
import RbCard from '@/components/RbCard/Card'
import SkillsItem from './SkillsItem'
import { getSkillList } from '@/api/skill'
import type { Skill } from '@/views/Skills/types'
@@ -94,15 +95,15 @@ const SkillList: FC<{value?: SkillConfigForm; onChange?: (config: SkillConfigFor
return (
<Card
<RbCard
title={<>
{t('application.skill')}
<span className="rb:font-regular rb:text-[12px] rb:text-[#5B6167]"> ({t('application.skillTitle')})</span>
<div className="rb:font-[MiSans-Bold] rb:font-bold">{t('application.skill')}</div>
<div className="rb:font-regular! rb:text-[12px] rb:text-[#5B6167]"> {t('application.skillTitle')}</div>
</>}
extra={
<Space>
{/* Help button for skill configuration guidance */}
<Button style={{ padding: '0 8px', height: '24px' }}>{t('application.skillHelp')}</Button>
{/* <Button style={{ padding: '0 8px', height: '24px' }}>{t('application.skillHelp')}</Button> */}
{/* Toggle switch to enable/disable skill functionality */}
<Form.Item
valuePropName="checked"
@@ -113,9 +114,28 @@ const SkillList: FC<{value?: SkillConfigForm; onChange?: (config: SkillConfigFor
</Form.Item>
</Space>
}
headerType="borderless"
headerClassName={clsx("rb:py-[16px]! rb:leading-[22px]! rb:font-regular", {
'rb:h-[76px]! rb:py-[16px]!': !skillConfig?.enabled,
'rb:h-[68px]! rb:pb-2!': skillConfig?.enabled,
})}
>
{/* Render skill configuration UI only when enabled */}
{skillConfig?.enabled && <Flex vertical gap={12}>
{skillConfig?.enabled && <Flex vertical gap={8} className="rb:bg-[#FAFAFA] rb:rounded-xl rb:pt-2.5! rb:pb-3! rb:px-3!">
<div className="rb:text-[#212332] rb:font-medium rb:leading-4.5 rb:px-1">{t('application.executeProcessPreview')}</div>
<Flex align="center" justify="space-between" gap={14} className="rb:text-[12px] rb:bg-[#FFFFFF]! rb:rounded-lg rb-border rb:py-2.5! rb:pl-4! rb:pr-3.25! rb:mb-2!">
{/* Render each step in the process flow with numbered badges */}
{processObj.map((key, index) => (<>
<Flex align="center" gap={8}>
{/* Step number badge */}
<Flex align="center" justify="center" className="rb:size-4 rb:rounded-full rb:bg-[#171719] rb:text-white rb:font-medium">{index + 1}</Flex>
{/* Step label */}
<span className="rb:inline-block rb:max-w-16">{t(`application.${key}`)}</span>
</Flex>
{/* Arrow separator between steps (except after last step) */}
{index !== processObj.length - 1 && <div className="rb:w-10 rb:h-4.5 rb:bg-cover rb:bg-[url('@/assets/images/application/arrow_right.svg')]"></div>}
</>))}
</Flex>
{/* Dynamic skill binding configuration section */}
<Form.Item noStyle>
<SkillsItem
@@ -125,27 +145,8 @@ const SkillList: FC<{value?: SkillConfigForm; onChange?: (config: SkillConfigFor
emptyTitle={t('application.dynamicBindingSkill_empty')}
/>
</Form.Item>
{/* Execution process preview card showing workflow steps */}
<Card
title={t('application.executeProcessPreview')}
variant="borderL"
>
<Flex align="center" gap={8} className="rb:text-[12px]">
{/* Render each step in the process flow with numbered badges */}
{processObj.map((key, index) => (<>
<Flex align="center" gap={8} className="rb:border rb:border-[#DFE4ED] rb:rounded-lg rb:bg-white rb:p-2!">
{/* Step number badge */}
<div className="rb:size-4 rb:rounded-full rb:bg-[#155EEF] rb:text-white rb:flex rb:items-center rb:justify-center rb:font-medium">{index + 1}</div>
{/* Step label */}
<span>{t(`application.${key}`)}</span>
</Flex>
{/* Arrow separator between steps (except after last step) */}
{index !== processObj.length - 1 && <div className="rb:text-[#8C9196]"></div>}
</>))}
</Flex>
</Card>
</Flex>}
</Card>
</RbCard>
)
}
export default SkillList