feat: 优化

This commit is contained in:
2026-02-23 00:09:36 +08:00
parent 547953cd00
commit 1e2e726744
6 changed files with 198 additions and 28 deletions

View File

@@ -0,0 +1,42 @@
---
name: frontend-design
description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.
license: Complete terms in LICENSE.txt
---
This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.
The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.
## Design Thinking
Before coding, understand the context and commit to a BOLD aesthetic direction:
- **Purpose**: What problem does this interface solve? Who uses it?
- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction.
- **Constraints**: Technical requirements (framework, performance, accessibility).
- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember?
**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.
Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is:
- Production-grade and functional
- Visually striking and memorable
- Cohesive with a clear aesthetic point-of-view
- Meticulously refined in every detail
## Frontend Aesthetics Guidelines
Focus on:
- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font.
- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.
- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.
- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.
- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays.
NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character.
Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations.
**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.
Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.

View File

@@ -24,18 +24,32 @@
<!-- 分类筛选 --> <!-- 分类筛选 -->
<div class="category-section"> <div class="category-section">
<nav class="category-nav"> <div class="category-wrapper" :class="{ 'is-expanded': isExpanded }">
<button <nav class="category-nav">
v-for="category in categories" <button
:key="category.id" v-for="category in categories"
class="category-btn" :key="category.id"
:class="{ 'category-btn--active': activeCategory === category.id }" class="category-btn"
@click="handleCategoryChange(category.id)" :class="{ 'category-btn--active': activeCategory === category.id }"
> @click="handleCategoryChange(category.id)"
<span>{{ category.name }}</span> >
<span class="category-count" v-if="category.count > 0">{{ category.count }}</span> <span>{{ category.name }}</span>
</button> <span class="category-count" v-if="category.count > 0">{{ category.count }}</span>
</nav> </button>
</nav>
<!-- 底部遮罩仅在折叠状态显示 -->
<div class="category-mask" v-if="!isExpanded && categories.length > 10"></div>
</div>
<!-- 展开/收起按钮 -->
<button
v-if="categories.length > 10"
class="expand-btn"
@click="isExpanded = !isExpanded"
>
<span>{{ isExpanded ? '收起更多' : '显示更多' }}</span>
<component :is="isExpanded ? UpOutlined : DownOutlined" />
</button>
</div> </div>
</div> </div>
</div> </div>
@@ -100,7 +114,9 @@ import { ref, computed, onMounted } from 'vue'
import { import {
SearchOutlined, SearchOutlined,
RobotOutlined, RobotOutlined,
CloseOutlined CloseOutlined,
DownOutlined,
UpOutlined
} from '@ant-design/icons-vue' } from '@ant-design/icons-vue'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import FullWidthLayout from '@/layouts/components/FullWidthLayout.vue' import FullWidthLayout from '@/layouts/components/FullWidthLayout.vue'
@@ -113,6 +129,7 @@ const activeCategory = ref('all')
const searchKeyword = ref('') const searchKeyword = ref('')
const chatDrawerVisible = ref(false) const chatDrawerVisible = ref(false)
const currentAgent = ref(null) const currentAgent = ref(null)
const isExpanded = ref(false)
// 智能体列表数据(从 API 获取) // 智能体列表数据(从 API 获取)
const agentList = ref([]) const agentList = ref([])
@@ -136,6 +153,20 @@ const categories = computed(() => {
cats.push({ id: name, name, count }) cats.push({ id: name, name, count })
}) })
// Mock data to demonstrate "lots of content"
// Only add if we have few real categories, to test the UI
if (cats.length < 10) {
const mockCategories = [
'市场营销', '产品设计', '人力资源', '财务会计', '法律咨询',
'客户服务', '项目管理', '数据科学', '网络安全', '云计算',
'人工智能', '区块链', '物联网', '虚拟现实', '增强现实',
'游戏开发', '移动应用', '前端开发', '后端开发', '全栈开发'
]
mockCategories.forEach((name, index) => {
cats.push({ id: `mock-${index}`, name, count: Math.floor(Math.random() * 50) + 1 })
})
}
return cats return cats
}) })
@@ -346,15 +377,47 @@ onMounted(() => {
width: 100%; width: 100%;
padding: 0 20px; padding: 0 20px;
display: flex; display: flex;
justify-content: center; flex-direction: column;
align-items: center;
}
.category-wrapper {
position: relative;
width: 100%;
max-width: 1000px;
// Default state: collapsed (show 1 row approx)
max-height: 48px;
overflow: hidden;
transition: max-height 0.3s ease-in-out;
/* Mobile optimization: Keep horizontal scroll on small screens if desired, &.is-expanded {
but user asked for PC fix. Let's make it responsive. */ max-height: 240px; // Limit expanded height
overflow-y: auto; // Allow scrolling when content exceeds max-height
}
// Scrollbar styling for desktop
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: rgba(148, 163, 184, 0.4);
border-radius: 3px;
&:hover {
background-color: rgba(148, 163, 184, 0.6);
}
}
/* Mobile optimization: Keep horizontal scroll on small screens */
@media (max-width: 640px) { @media (max-width: 640px) {
display: block; max-height: none;
overflow-x: auto; overflow-x: auto;
white-space: nowrap; white-space: nowrap;
justify-content: flex-start;
mask-image: linear-gradient(to right, transparent, black 20px, black 90%, transparent); mask-image: linear-gradient(to right, transparent, black 20px, black 90%, transparent);
-webkit-mask-image: linear-gradient(to right, transparent, black 20px, black 90%, transparent); -webkit-mask-image: linear-gradient(to right, transparent, black 20px, black 90%, transparent);
@@ -372,16 +435,51 @@ onMounted(() => {
justify-content: center; justify-content: center;
flex-wrap: wrap; flex-wrap: wrap;
gap: 12px; gap: 12px;
max-width: 1000px; padding: 4px 0; // Removed horizontal padding to fit wrapper
margin: 0 auto; width: 100%;
padding: 4px 20px;
@media (max-width: 640px) { @media (max-width: 640px) {
display: inline-flex; display: inline-flex;
flex-wrap: nowrap; flex-wrap: nowrap;
width: max-content; width: max-content;
justify-content: flex-start; justify-content: flex-start;
margin: 0; padding: 4px 20px; // Add padding back for mobile scroll
}
}
.category-mask {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 24px;
background: linear-gradient(to bottom, transparent, #F8FAFC);
pointer-events: none;
@media (max-width: 640px) {
display: none;
}
}
.expand-btn {
display: flex;
align-items: center;
gap: 6px;
margin-top: 12px;
background: transparent;
border: none;
font-size: 13px;
color: #64748B;
cursor: pointer;
transition: color 0.2s;
padding: 4px 12px;
&:hover {
color: #334155;
}
@media (max-width: 640px) {
display: none;
} }
} }

View File

@@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.tik.muye.aiagent.dal.AiAgentDO; import cn.iocoder.yudao.module.tik.muye.aiagent.dal.AiAgentDO;
import cn.iocoder.yudao.module.tik.muye.aiagent.service.AiAgentService; import cn.iocoder.yudao.module.tik.muye.aiagent.service.AiAgentService;
import cn.iocoder.yudao.module.tik.muye.aiagent.vo.AiAgentRespVO; import cn.iocoder.yudao.module.tik.muye.aiagent.vo.AppAiAgentRespVO;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
@@ -33,9 +33,9 @@ public class AppAiAgentController {
@GetMapping("/list") @GetMapping("/list")
@Operation(summary = "获取启用的智能体列表") @Operation(summary = "获取启用的智能体列表")
public CommonResult<List<AiAgentRespVO>> getEnabledAgentList() { public CommonResult<List<AppAiAgentRespVO>> getEnabledAgentList() {
List<AiAgentDO> list = aiAgentService.getEnabledAgentList(); List<AiAgentDO> list = aiAgentService.getEnabledAgentList();
return success(BeanUtils.toBean(list, AiAgentRespVO.class)); return success(BeanUtils.toBean(list, AppAiAgentRespVO.class));
} }
} }

View File

@@ -115,9 +115,7 @@ public class AiAgentServiceImpl implements AiAgentService {
if (importAgent.getCategoryName() == null || importAgent.getCategoryName().isEmpty()) { if (importAgent.getCategoryName() == null || importAgent.getCategoryName().isEmpty()) {
throw new RuntimeException("分类不能为空"); throw new RuntimeException("分类不能为空");
} }
if (importAgent.getIcon() == null || importAgent.getIcon().isEmpty()) { // 图标URL可以为空
throw new RuntimeException("图标URL不能为空");
}
if (importAgent.getStatus() == null) { if (importAgent.getStatus() == null) {
throw new RuntimeException("状态不能为空"); throw new RuntimeException("状态不能为空");
} }

View File

@@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.tik.muye.aiagent.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 用户 App - AI智能体 Response VO
* 精简版,不包含敏感字段(如 systemPrompt
*/
@Schema(description = "用户 App - AI智能体 Response VO")
@Data
public class AppAiAgentRespVO {
@Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "5900")
private Long id;
@Schema(description = "智能体ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "24745")
private String agentId;
@Schema(description = "智能体名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "文案助手")
private String agentName;
@Schema(description = "图标URL")
private String icon;
@Schema(description = "设定描述", example = "专业的文案创作助手")
private String description;
@Schema(description = "分类名称(中文)", example = "文案创作")
private String categoryName;
}