From 92653c3923357cb7cd3d36a0bef8853ef004151d Mon Sep 17 00:00:00 2001 From: wenhaolai <1174891566@qq.com> Date: Sun, 19 Oct 2025 19:20:40 +0800 Subject: [PATCH] feat: add lwh week4-code-files --- doc/week4-code-lwh/README.md | 2 + doc/week4-code-lwh/demo/DemoApplication.java | 15 ++ doc/week4-code-lwh/demo/HelloController.java | 14 ++ .../demo/app/AppController.java | 72 ++++++ .../demo/app/AppRawController.java | 26 ++ .../demo/app/entity/AppEntity.java | 27 +++ .../demo/app/mapper/AppMapper.java | 24 ++ doc/week4-code-lwh/demo/app/vo/AppVO.java | 29 +++ .../demo/common/ApiResponse.java | 28 +++ .../demo/common/BusinessException.java | 7 + .../demo/common/GlobalExceptionHandler.java | 48 ++++ doc/week4-code-lwh/demo/config/WebConfig.java | 17 ++ doc/week4-code-lwh/src/App.vue | 3 + doc/week4-code-lwh/src/components/AppCard.vue | 55 +++++ doc/week4-code-lwh/src/data/getApps.js | 21 ++ doc/week4-code-lwh/src/main.js | 19 ++ doc/week4-code-lwh/src/mock/apps.js | 223 ++++++++++++++++++ doc/week4-code-lwh/src/router/index.js | 11 + doc/week4-code-lwh/src/stores/counter.js | 12 + doc/week4-code-lwh/src/styles/tailwind.css | 1 + doc/week4-code-lwh/src/utils/http.js | 61 +++++ doc/week4-code-lwh/src/views/AppsView.vue | 146 ++++++++++++ doc/week4-code-lwh/src/views/WelcomeView.vue | 71 ++++++ 23 files changed, 932 insertions(+) create mode 100644 doc/week4-code-lwh/README.md create mode 100644 doc/week4-code-lwh/demo/DemoApplication.java create mode 100644 doc/week4-code-lwh/demo/HelloController.java create mode 100644 doc/week4-code-lwh/demo/app/AppController.java create mode 100644 doc/week4-code-lwh/demo/app/AppRawController.java create mode 100644 doc/week4-code-lwh/demo/app/entity/AppEntity.java create mode 100644 doc/week4-code-lwh/demo/app/mapper/AppMapper.java create mode 100644 doc/week4-code-lwh/demo/app/vo/AppVO.java create mode 100644 doc/week4-code-lwh/demo/common/ApiResponse.java create mode 100644 doc/week4-code-lwh/demo/common/BusinessException.java create mode 100644 doc/week4-code-lwh/demo/common/GlobalExceptionHandler.java create mode 100644 doc/week4-code-lwh/demo/config/WebConfig.java create mode 100644 doc/week4-code-lwh/src/App.vue create mode 100644 doc/week4-code-lwh/src/components/AppCard.vue create mode 100644 doc/week4-code-lwh/src/data/getApps.js create mode 100644 doc/week4-code-lwh/src/main.js create mode 100644 doc/week4-code-lwh/src/mock/apps.js create mode 100644 doc/week4-code-lwh/src/router/index.js create mode 100644 doc/week4-code-lwh/src/stores/counter.js create mode 100644 doc/week4-code-lwh/src/styles/tailwind.css create mode 100644 doc/week4-code-lwh/src/utils/http.js create mode 100644 doc/week4-code-lwh/src/views/AppsView.vue create mode 100644 doc/week4-code-lwh/src/views/WelcomeView.vue diff --git a/doc/week4-code-lwh/README.md b/doc/week4-code-lwh/README.md new file mode 100644 index 0000000..b61aa58 --- /dev/null +++ b/doc/week4-code-lwh/README.md @@ -0,0 +1,2 @@ +src:web-vue +demo: api-springboot \ No newline at end of file diff --git a/doc/week4-code-lwh/demo/DemoApplication.java b/doc/week4-code-lwh/demo/DemoApplication.java new file mode 100644 index 0000000..762710e --- /dev/null +++ b/doc/week4-code-lwh/demo/DemoApplication.java @@ -0,0 +1,15 @@ +package com.example.demo; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +@MapperScan("com.example.demo.app.mapper") +public class DemoApplication { + + public static void main(String[] args) { + SpringApplication.run(DemoApplication.class, args); + } + +} diff --git a/doc/week4-code-lwh/demo/HelloController.java b/doc/week4-code-lwh/demo/HelloController.java new file mode 100644 index 0000000..2fee7a2 --- /dev/null +++ b/doc/week4-code-lwh/demo/HelloController.java @@ -0,0 +1,14 @@ +package com.example.demo; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("api") +public class HelloController { + @GetMapping("/ping") + public String ping() { + return "pong"; + } +} diff --git a/doc/week4-code-lwh/demo/app/AppController.java b/doc/week4-code-lwh/demo/app/AppController.java new file mode 100644 index 0000000..7238e3e --- /dev/null +++ b/doc/week4-code-lwh/demo/app/AppController.java @@ -0,0 +1,72 @@ +// src/main/java/com/example/demo/app/AppController.java +package com.example.demo.app; + +import com.example.demo.common.ApiResponse; +import com.example.demo.app.vo.AppVO; +import com.example.demo.app.entity.AppEntity; +import com.example.demo.app.mapper.AppMapper; +import org.springframework.web.bind.annotation.*; +import java.util.*; + +@RestController +@RequestMapping("/api/apps") +public class AppController { + private final AppMapper appMapper; + + public AppController(AppMapper appMapper) { + this.appMapper = appMapper; + } + + @GetMapping + public ApiResponse> list() { + // 使用 MyBatis 查询数据库 + List entities = appMapper.findAllOrderById(); + //List entities = appMapper.findAllOrderByDownloads(); + //List entities = appMapper.findAllOrderByRating(); + // 转换为 VO 对象 + List out = new ArrayList<>(); + for (AppEntity e : entities) { + AppVO vo = AppVO.builder() + .id(e.getId()) + .name(e.getName()) + .description(e.getDescription()) + .fullDescription(e.getFullDescription()) + .avatar(e.getAvatar()) + .category(e.getCategory()) + .price(e.getPrice()) + .rating(e.getRating()) + .downloads(e.getDownloads()) + .reviews(e.getReviews()) + .author(e.getAuthor()) + .publishedAt(e.getPublishedAt()) + .build(); + out.add(vo); + } + return ApiResponse.ok(out); + } + + // 新增:根据分类查询应用 + @GetMapping("/category/{category}") + public ApiResponse> listByCategory(@PathVariable String category) { + List entities = appMapper.findByCategory(category); + List out = new ArrayList<>(); + for (AppEntity e : entities) { + AppVO vo = AppVO.builder() + .id(e.getId()) + .name(e.getName()) + .description(e.getDescription()) + .fullDescription(e.getFullDescription()) + .avatar(e.getAvatar()) + .category(e.getCategory()) + .price(e.getPrice()) + .rating(e.getRating()) + .downloads(e.getDownloads()) + .reviews(e.getReviews()) + .author(e.getAuthor()) + .publishedAt(e.getPublishedAt()) + .build(); + out.add(vo); + } + return ApiResponse.ok(out); + } +} diff --git a/doc/week4-code-lwh/demo/app/AppRawController.java b/doc/week4-code-lwh/demo/app/AppRawController.java new file mode 100644 index 0000000..11bd418 --- /dev/null +++ b/doc/week4-code-lwh/demo/app/AppRawController.java @@ -0,0 +1,26 @@ +package com.example.demo.app; + +import org.springframework.web.bind.annotation.*; +import java.util.*; + +@RestController +@RequestMapping("/api/apps-raw") +public class AppRawController { + + // 成功:直接返回数组(不是统一响应) + @GetMapping + public List> list() { + List> out = new ArrayList<>(); + Map a = new HashMap<>(); + a.put("id", 1); + a.put("name", "智能客服助手"); + out.add(a); + return out; + } + + // 失败:故意抛错(默认异常响应) + @GetMapping("/boom") + public Object boom() { + throw new RuntimeException("演示:服务器炸了"); + } +} diff --git a/doc/week4-code-lwh/demo/app/entity/AppEntity.java b/doc/week4-code-lwh/demo/app/entity/AppEntity.java new file mode 100644 index 0000000..c77fb6f --- /dev/null +++ b/doc/week4-code-lwh/demo/app/entity/AppEntity.java @@ -0,0 +1,27 @@ +// src/main/java/com/example/demo/app/entity/AppEntity.java +package com.example.demo.app.entity; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +import java.math.BigDecimal; +import java.time.LocalDate; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AppEntity { + private Long id; + private String name; + private String description; + private String fullDescription; // 对应数据库字段 full_description + private String avatar; + private String category; + private BigDecimal price; + private Double rating; + private Integer downloads; + private Integer reviews; + private String author; + private LocalDate publishedAt; // 对应数据库字段 published_at +} diff --git a/doc/week4-code-lwh/demo/app/mapper/AppMapper.java b/doc/week4-code-lwh/demo/app/mapper/AppMapper.java new file mode 100644 index 0000000..66f94c5 --- /dev/null +++ b/doc/week4-code-lwh/demo/app/mapper/AppMapper.java @@ -0,0 +1,24 @@ +package com.example.demo.app.mapper; + +import com.example.demo.app.entity.AppEntity; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper // 标识为 MyBatis Mapper 接口 +public interface AppMapper { + // 查询所有应用,按 ID 升序排列 + List findAllOrderById(); + + // 根据 ID 查询单个应用 + AppEntity findById(Long id); + + // 根据分类查询应用 + List findByCategory(String category); + + // 根据评分排序,可以选择是升序排列还是降序排列 + List findAllOrderByRating(); + + // 根据下载量排序,可以选择是升序排列还是降序排列 + List findAllOrderByDownloads(); +} diff --git a/doc/week4-code-lwh/demo/app/vo/AppVO.java b/doc/week4-code-lwh/demo/app/vo/AppVO.java new file mode 100644 index 0000000..061ba39 --- /dev/null +++ b/doc/week4-code-lwh/demo/app/vo/AppVO.java @@ -0,0 +1,29 @@ +package com.example.demo.app.vo; + +import lombok.*; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AppVO { + private Long id; + private String name; + private String description; + private String fullDescription; + private String avatar; + private String category; + private BigDecimal price; + private Double rating; + private Integer downloads; + private Integer reviews; + private String author; + private LocalDate publishedAt; + private List tags; + private List features; + private List scenarios; +} diff --git a/doc/week4-code-lwh/demo/common/ApiResponse.java b/doc/week4-code-lwh/demo/common/ApiResponse.java new file mode 100644 index 0000000..cbc9b5b --- /dev/null +++ b/doc/week4-code-lwh/demo/common/ApiResponse.java @@ -0,0 +1,28 @@ +package com.example.demo.common; + +import java.time.Instant; + +public class ApiResponse { + private int code; + private String message; + private T data; + private long timestamp; + + public ApiResponse() {} + + public ApiResponse(int code, String message, T data) { + this.code = code; + this.message = message; + this.data = data; + this.timestamp = Instant.now().toEpochMilli(); + } + + public static ApiResponse ok(T data) { return new ApiResponse<>(0, "OK", data); } + public static ApiResponse ok() { return new ApiResponse<>(0, "OK", null); } + public static ApiResponse fail(int code, String message) { return new ApiResponse<>(code, message, null); } + + public int getCode() { return code; } public void setCode(int code) { this.code = code; } + public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } + public T getData() { return data; } public void setData(T data) { this.data = data; } + public long getTimestamp() { return timestamp; } public void setTimestamp(long timestamp) { this.timestamp = timestamp; } +} diff --git a/doc/week4-code-lwh/demo/common/BusinessException.java b/doc/week4-code-lwh/demo/common/BusinessException.java new file mode 100644 index 0000000..4d2954c --- /dev/null +++ b/doc/week4-code-lwh/demo/common/BusinessException.java @@ -0,0 +1,7 @@ +package com.example.demo.common; + +public class BusinessException extends RuntimeException{ + private final int code; + public BusinessException(int code, String message) { super(message); this.code = code; } + public int getCode() { return code; } +} diff --git a/doc/week4-code-lwh/demo/common/GlobalExceptionHandler.java b/doc/week4-code-lwh/demo/common/GlobalExceptionHandler.java new file mode 100644 index 0000000..5386847 --- /dev/null +++ b/doc/week4-code-lwh/demo/common/GlobalExceptionHandler.java @@ -0,0 +1,48 @@ +package com.example.demo.common; + +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.servlet.NoHandlerFoundException; +import org.springframework.web.servlet.resource.NoResourceFoundException; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + // 业务异常(主动抛出) + @ExceptionHandler(BusinessException.class) + public ApiResponse handleBiz(BusinessException ex) { + return ApiResponse.fail(ex.getCode(), ex.getMessage()); + } + + // 400:请求错误(参数缺失/类型不匹配/JSON 解析失败等) + @ExceptionHandler({ + MissingServletRequestParameterException.class, + MethodArgumentTypeMismatchException.class, + HttpMessageNotReadableException.class + }) + public ApiResponse handleBadRequest(Exception ex) { + return ApiResponse.fail(40000, ex.getMessage()); + } + + // 404(Spring 6.1+ / Boot 3.2+ 默认抛出) + @ExceptionHandler(NoResourceFoundException.class) + public ApiResponse handleNoResource(NoResourceFoundException ex) { + return ApiResponse.fail(40400, "资源不存在:" + ex.getResourcePath()); + } + + // 404(兼容旧版本,某些场景仍可能出现) + @ExceptionHandler(NoHandlerFoundException.class) + public ApiResponse handleNotFound(NoHandlerFoundException ex) { + return ApiResponse.fail(40400, "资源不存在:" + ex.getRequestURL()); + } + + // 500:兜底(未预料的异常) + @ExceptionHandler(Exception.class) + public ApiResponse handleOthers(Exception ex) { + // 生产环境建议记录日志/告警 + return ApiResponse.fail(50000, "服务器异常,请稍后再试"); + } +} diff --git a/doc/week4-code-lwh/demo/config/WebConfig.java b/doc/week4-code-lwh/demo/config/WebConfig.java new file mode 100644 index 0000000..a50cae3 --- /dev/null +++ b/doc/week4-code-lwh/demo/config/WebConfig.java @@ -0,0 +1,17 @@ +package com.example.demo.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.*; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/api/**") + .allowedOrigins("http://localhost:5173", "http://127.0.0.1:5173") + .allowedMethods("GET","POST","PUT","DELETE","PATCH","OPTIONS") + .allowedHeaders("*") + .allowCredentials(true) + .maxAge(3600); + } +} diff --git a/doc/week4-code-lwh/src/App.vue b/doc/week4-code-lwh/src/App.vue new file mode 100644 index 0000000..87ed26e --- /dev/null +++ b/doc/week4-code-lwh/src/App.vue @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/doc/week4-code-lwh/src/components/AppCard.vue b/doc/week4-code-lwh/src/components/AppCard.vue new file mode 100644 index 0000000..1897cd4 --- /dev/null +++ b/doc/week4-code-lwh/src/components/AppCard.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/doc/week4-code-lwh/src/data/getApps.js b/doc/week4-code-lwh/src/data/getApps.js new file mode 100644 index 0000000..9eee6fc --- /dev/null +++ b/doc/week4-code-lwh/src/data/getApps.js @@ -0,0 +1,21 @@ +import { apps as mock } from '@/mock/apps' +import { http, USE_MOCK } from '@/utils/http' + +function normalize(app) { + return { + ...app, + price: Number(app.price ?? 0), + rating: Number(app.rating ?? 0), + downloads: Number(app.downloads ?? 0), + reviews: Number(app.reviews ?? 0), + // publishedAt 后端为 "YYYY-MM-DD",目前仅展示,保留字符串即可 + } +} + +export async function getApps() { + if (USE_MOCK) return mock.map(normalize) + + // 由于响应拦截器已解包,get('/apps') 直接返回后端的 data(数组) + const list = await http.get('/apps') + return (Array.isArray(list) ? list : []).map(normalize) +} diff --git a/doc/week4-code-lwh/src/main.js b/doc/week4-code-lwh/src/main.js new file mode 100644 index 0000000..6e5c63e --- /dev/null +++ b/doc/week4-code-lwh/src/main.js @@ -0,0 +1,19 @@ +import { createApp } from 'vue' +import App from './App.vue' +import router from './router' + +// 新增:引入 Tailwind 的入口样式文件(让所有 Tailwind 工具类在全局生效) +import './styles/tailwind.css' + +/* 新增:引入 Element Plus 组件库(JS 逻辑) */ +import ElementPlus from 'element-plus' +/* 新增:引入 Element Plus 的基础样式(必须与上面一行配合,否则组件无样式) */ +import 'element-plus/dist/index.css' + +const app = createApp(App) +app.use(router) + +/* 新增:在应用实例上安装 Element Plus,使 组件可用 */ +app.use(ElementPlus) + +app.mount('#app') diff --git a/doc/week4-code-lwh/src/mock/apps.js b/doc/week4-code-lwh/src/mock/apps.js new file mode 100644 index 0000000..06bb11b --- /dev/null +++ b/doc/week4-code-lwh/src/mock/apps.js @@ -0,0 +1,223 @@ +export const apps = [ + { + id: 1, + name: '智能客服助手', + description: '基于大语言模型的智能客服系统,支持多轮对话和知识库检索', + fullDescription: '这是一个功能强大的智能客服系统,基于最新的大语言模型技术,能够理解用户意图并结合企业知识库进行精准应答,适用于客服外包、在线咨询与售后支持等多场景。', + avatar: 'https://via.placeholder.com/80x80?text=A1', + category: 'customer-service', + price: 0, + rating: 4.8, + downloads: 1200, + reviews: 156, + author: 'AI团队', + publishedAt: '2024-01-15', + tags: ['客服', '对话', 'AI'], + features: ['多轮对话支持','知识库集成','情感分析','多语言支持','实时学习'], + scenarios: ['客户服务','在线咨询','技术支持'] + }, + { + id: 2, + name: '内容创作大师', + description: 'AI驱动的内容生成与优化工具,支持多种文体创作', + fullDescription: '结合语义理解与风格迁移,快速生成营销文案、博客、脚本等;内置SEO建议与可读性评估,适合自媒体与市场团队。', + avatar: 'https://via.placeholder.com/80x80?text=A2', + category: 'content-creation', + price: 0, + rating: 4.9, + downloads: 980, + reviews: 123, + author: '创作工坊', + publishedAt: '2024-01-10', + tags: ['内容创作','文案','SEO'], + features: ['多文体支持','SEO优化','内容检测','风格调整','批量生成'], + scenarios: ['内容营销','社交媒体','博客写作'] + }, + { + id: 3, + name: '数据洞察分析', + description: '自动生成可视化报告与关键指标解读', + fullDescription: '对接CSV/Excel等本地数据源,一键生成图表与洞察说明,帮助业务快速定位异常与机会点。', + avatar: 'https://via.placeholder.com/80x80?text=A3', + category: 'analytics', + price: 49, + rating: 4.6, + downloads: 760, + reviews: 88, + author: 'Insight Lab', + publishedAt: '2024-02-05', + tags: ['BI','报表','可视化'], + features: ['自动图表','异常检测','指标拆解','报告导出','模板库'], + scenarios: ['运营复盘','周报月报','经理汇报'] + }, + { + id: 4, + name: '团队任务协作', + description: '轻量级看板与任务分配,适配移动端', + fullDescription: '支持任务分组、优先级、截止日期与提醒;与团队节奏匹配的简洁交互,大幅减少沟通成本。', + avatar: 'https://via.placeholder.com/80x80?text=A4', + category: 'productivity', + price: 0, + rating: 4.5, + downloads: 1540, + reviews: 201, + author: '轻协作工作室', + publishedAt: '2024-03-12', + tags: ['看板','任务','提醒'], + features: ['看板视图','子任务','到期提醒','批量操作','导入导出'], + scenarios: ['项目管理','个人待办','跨部门协作'] + }, + { + id: 5, + name: '社媒排程管家', + description: '统一管理多平台发帖与数据回收', + fullDescription: '支持主流社媒账号绑定,提供最佳发布时间建议与互动数据汇总,降低内容运营的人力成本。', + avatar: 'https://via.placeholder.com/80x80?text=A5', + category: 'marketing', + price: 29, + rating: 4.3, + downloads: 620, + reviews: 67, + author: 'Growth Kit', + publishedAt: '2024-03-25', + tags: ['社媒','排程','增长'], + features: ['多账号管理','定时发布','热门话题建议','效果追踪','团队协作'], + scenarios: ['品牌运营','活动推广','新品发布'] + }, + { + id: 6, + name: '财务票据识别', + description: '发票/收据OCR识别与入账校验', + fullDescription: '高精度OCR结合规则引擎,自动识别金额、税率与开票方,导出标准化记账数据。', + avatar: 'https://via.placeholder.com/80x80?text=A6', + category: 'finance', + price: 39, + rating: 4.7, + downloads: 830, + reviews: 92, + author: '数算云', + publishedAt: '2024-04-08', + tags: ['OCR','财务','对账'], + features: ['批量识别','异常标注','科目匹配','导出Excel','权限管理'], + scenarios: ['报销入账','月结对账','审计抽查'] + }, + { + id: 7, + name: '在线学习助理', + description: '课程摘要、要点测验与复习计划自动生成', + fullDescription: '读取课程大纲与笔记,生成知识图谱与间隔复习计划,提升学习效率与记忆效果。', + avatar: 'https://via.placeholder.com/80x80?text=A7', + category: 'education', + price: 0, + rating: 4.4, + downloads: 1100, + reviews: 134, + author: 'EduAI', + publishedAt: '2024-04-22', + tags: ['学习','复习','测验'], + features: ['自动摘要','要点卡片','测验生成','复习提醒','学习曲线'], + scenarios: ['考试备考','课程复盘','企业培训'] + }, + { + id: 8, + name: '海报设计精灵', + description: '模板化设计与智能排版,一键出图', + fullDescription: '数十款精美模板与智能排版算法,支持图片裁切、字体替换与品牌主色一键适配。', + avatar: 'https://via.placeholder.com/80x80?text=A8', + category: 'design', + price: 19, + rating: 4.2, + downloads: 540, + reviews: 61, + author: 'Pixel Studio', + publishedAt: '2024-05-06', + tags: ['设计','模板','海报'], + features: ['智能排版','模板中心','品牌色适配','批量出图','高清导出'], + scenarios: ['活动KV','社媒海报','门店物料'] + }, + { + id: 9, + name: '电商转化优化', + description: '商品文案A/B与页面要素建议', + fullDescription: '基于行为数据与行业词库,智能生成卖点文案并给出首屏/CTA/信任背书等优化建议。', + avatar: 'https://via.placeholder.com/80x80?text=A9', + category: 'sales', + price: 59, + rating: 4.6, + downloads: 470, + reviews: 55, + author: 'ConvertX', + publishedAt: '2024-05-20', + tags: ['转化率','A/B测试','电商'], + features: ['文案生成','A/B建议','热区分析','对标模板','转化追踪'], + scenarios: ['商品页优化','活动页设计','新品上架'] + }, + { + id: 10, + name: '招聘筛选助手', + description: '简历要点抽取与匹配评分', + fullDescription: '自动提取候选人关键词与项目经验,生成岗位匹配度评分并输出面试问题建议。', + avatar: 'https://via.placeholder.com/80x80?text=A10', + category: 'hr', + price: 0, + rating: 4.5, + downloads: 690, + reviews: 72, + author: 'TalentOps', + publishedAt: '2024-06-03', + tags: ['HR','简历','招聘'], + features: ['要点抽取','匹配评分','面试题建议','批量处理','导出报告'], + scenarios: ['初筛','批量职位','校招'] + }, + { + id: 11, + name: '运营总览看板', + description: '跨渠道指标聚合与健康度预警', + fullDescription: '聚合广告、社媒、商城等渠道关键指标,提供红绿灯健康度与异常波动提醒。', + avatar: 'https://via.placeholder.com/80x80?text=A11', + category: 'operations', + price: 0, + rating: 4.6, + downloads: 880, + reviews: 101, + author: 'OpsBoard', + publishedAt: '2024-06-18', + tags: ['看板','预警','聚合'], + features: ['渠道聚合','阈值预警','对比分析','自定义维度','快照留存'], + scenarios: ['日常巡检','周例会','异常定位'] + }, + { + id: 12, + name: '安全合规检测', + description: '敏感内容审查与风险报告下载', + fullDescription: '对文本与图片进行敏感内容识别,提供审计明细与风险分级,帮助业务满足合规要求。', + avatar: 'https://via.placeholder.com/80x80?text=A12', + category: 'security', + price: 79, + rating: 4.7, + downloads: 430, + reviews: 49, + author: 'ShieldOne', + publishedAt: '2024-07-02', + tags: ['合规','审计','风控'], + features: ['多模态识别','风险分级','审计报告','策略配置','操作日志'], + scenarios: ['内容审核','风控复核','合规自查'] + }, + { + id: 13, + name: 'API 测试机器人', + description: '基于示例自动生成接口用例与断言', + fullDescription: '读取OpenAPI文档与示例请求,自动生成覆盖率更高的接口测试用例,支持本地执行与报告导出。', + avatar: 'https://via.placeholder.com/80x80?text=A13', + category: 'developer-tools', + price: 0, + rating: 4.4, + downloads: 510, + reviews: 63, + author: 'DevFlow', + publishedAt: '2024-07-18', + tags: ['API','测试','自动化'], + features: ['用例生成','环境变量','断言模板','并发执行','报告导出'], + scenarios: ['接口联调','回归测试','CI集成'] + } +]; diff --git a/doc/week4-code-lwh/src/router/index.js b/doc/week4-code-lwh/src/router/index.js new file mode 100644 index 0000000..31ca4d5 --- /dev/null +++ b/doc/week4-code-lwh/src/router/index.js @@ -0,0 +1,11 @@ +import { createRouter, createWebHistory } from 'vue-router' +const Apps = () => import('@/views/AppsView.vue') +const Welcome = () => import('@/views/WelcomeView.vue') + +export default createRouter({ + history: createWebHistory(), + routes: [ + { path: '/', name: 'welcome', component: Welcome }, + { path: '/apps', name: 'apps', component: Apps }, + ], +}) diff --git a/doc/week4-code-lwh/src/stores/counter.js b/doc/week4-code-lwh/src/stores/counter.js new file mode 100644 index 0000000..b6757ba --- /dev/null +++ b/doc/week4-code-lwh/src/stores/counter.js @@ -0,0 +1,12 @@ +import { ref, computed } from 'vue' +import { defineStore } from 'pinia' + +export const useCounterStore = defineStore('counter', () => { + const count = ref(0) + const doubleCount = computed(() => count.value * 2) + function increment() { + count.value++ + } + + return { count, doubleCount, increment } +}) diff --git a/doc/week4-code-lwh/src/styles/tailwind.css b/doc/week4-code-lwh/src/styles/tailwind.css new file mode 100644 index 0000000..a461c50 --- /dev/null +++ b/doc/week4-code-lwh/src/styles/tailwind.css @@ -0,0 +1 @@ +@import "tailwindcss"; \ No newline at end of file diff --git a/doc/week4-code-lwh/src/utils/http.js b/doc/week4-code-lwh/src/utils/http.js new file mode 100644 index 0000000..e9d9c8a --- /dev/null +++ b/doc/week4-code-lwh/src/utils/http.js @@ -0,0 +1,61 @@ +import axios from 'axios' + +const USE_MOCK = (import.meta.env.VITE_USE_MOCK ?? 'true') !== 'false' +const BASE_API = import.meta.env.VITE_BASE_API || '/api' + +const http = axios.create({ + baseURL: BASE_API, + timeout: 15000, + withCredentials: false, + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + } +}) + +// 请求拦截:可在此注入 Token +http.interceptors.request.use((config) => { + // const token = localStorage.getItem('token') + // if (token) config.headers.Authorization = `Bearer ${token}` + return config +}) + +// 响应拦截:统一处理后端的 {code,message,data,timestamp} +http.interceptors.response.use( + (response) => { + // 二进制下载等直接放行 + const rt = response.request?.responseType + const ct = response.headers?.['content-type'] || '' + if (rt === 'blob' || rt === 'arraybuffer' || ct.includes('octet-stream')) { + return response + } + + const payload = response.data + // 按后端统一结构解包 + if (payload && typeof payload === 'object' && 'code' in payload && 'data' in payload) { + if (payload.code === 0) return payload.data + const err = new Error(payload.message || '业务失败') + err.code = payload.code + err.response = response + throw err + } + // 非统一结构(极少数兼容场景) + return payload + }, + (error) => { + if (error.response) { + const p = error.response.data + const msg = p?.message || `HTTP ${error.response.status}` + const err = new Error(msg) + err.code = p?.code ?? error.response.status + err.response = error.response + throw err + } else if (error.request) { + throw new Error('网络不可用或服务器无响应') + } else { + throw new Error(error.message || '请求发生错误') + } + } +) + +export { http, USE_MOCK } diff --git a/doc/week4-code-lwh/src/views/AppsView.vue b/doc/week4-code-lwh/src/views/AppsView.vue new file mode 100644 index 0000000..c29bafa --- /dev/null +++ b/doc/week4-code-lwh/src/views/AppsView.vue @@ -0,0 +1,146 @@ + + + diff --git a/doc/week4-code-lwh/src/views/WelcomeView.vue b/doc/week4-code-lwh/src/views/WelcomeView.vue new file mode 100644 index 0000000..f1c36c0 --- /dev/null +++ b/doc/week4-code-lwh/src/views/WelcomeView.vue @@ -0,0 +1,71 @@ + + \ No newline at end of file -- Gitee