|
@@ -0,0 +1,333 @@
|
|
|
|
|
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
|
|
|
|
|
+import { z } from '@hono/zod-openapi';
|
|
|
|
|
+
|
|
|
|
|
+// 省份查询参数Schema
|
|
|
|
|
+const getProvincesSchema = z.object({
|
|
|
|
|
+ page: z.coerce.number().int().min(1).default(1).openapi({
|
|
|
|
|
+ example: 1,
|
|
|
|
|
+ description: '页码'
|
|
|
|
|
+ }),
|
|
|
|
|
+ pageSize: z.coerce.number().int().min(1).max(100).default(50).openapi({
|
|
|
|
|
+ example: 50,
|
|
|
|
|
+ description: '每页数量'
|
|
|
|
|
+ })
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// 城市查询参数Schema
|
|
|
|
|
+const getCitiesSchema = z.object({
|
|
|
|
|
+ provinceId: z.coerce.number().int().positive('省份ID必须为正整数').openapi({
|
|
|
|
|
+ example: 1,
|
|
|
|
|
+ description: '省份ID'
|
|
|
|
|
+ }),
|
|
|
|
|
+ page: z.coerce.number().int().min(1).default(1).openapi({
|
|
|
|
|
+ example: 1,
|
|
|
|
|
+ description: '页码'
|
|
|
|
|
+ }),
|
|
|
|
|
+ pageSize: z.coerce.number().int().min(1).max(100).default(50).openapi({
|
|
|
|
|
+ example: 50,
|
|
|
|
|
+ description: '每页数量'
|
|
|
|
|
+ })
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// 区县查询参数Schema
|
|
|
|
|
+const getDistrictsSchema = z.object({
|
|
|
|
|
+ cityId: z.coerce.number().int().positive('城市ID必须为正整数').openapi({
|
|
|
|
|
+ example: 34,
|
|
|
|
|
+ description: '城市ID'
|
|
|
|
|
+ }),
|
|
|
|
|
+ page: z.coerce.number().int().min(1).default(1).openapi({
|
|
|
|
|
+ example: 1,
|
|
|
|
|
+ description: '页码'
|
|
|
|
|
+ }),
|
|
|
|
|
+ pageSize: z.coerce.number().int().min(1).max(100).default(50).openapi({
|
|
|
|
|
+ example: 50,
|
|
|
|
|
+ description: '每页数量'
|
|
|
|
|
+ })
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// 省市区响应Schema
|
|
|
|
|
+const areaResponseSchema = z.object({
|
|
|
|
|
+ id: z.number(),
|
|
|
|
|
+ name: z.string(),
|
|
|
|
|
+ code: z.string(),
|
|
|
|
|
+ level: z.number(),
|
|
|
|
|
+ parentId: z.number().nullable()
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// 省份列表响应Schema
|
|
|
|
|
+const provincesResponseSchema = z.object({
|
|
|
|
|
+ success: z.boolean(),
|
|
|
|
|
+ data: z.object({
|
|
|
|
|
+ provinces: z.array(areaResponseSchema),
|
|
|
|
|
+ pagination: z.object({
|
|
|
|
|
+ page: z.number(),
|
|
|
|
|
+ pageSize: z.number(),
|
|
|
|
|
+ total: z.number(),
|
|
|
|
|
+ totalPages: z.number()
|
|
|
|
|
+ })
|
|
|
|
|
+ }),
|
|
|
|
|
+ message: z.string()
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// 城市列表响应Schema
|
|
|
|
|
+const citiesResponseSchema = z.object({
|
|
|
|
|
+ success: z.boolean(),
|
|
|
|
|
+ data: z.object({
|
|
|
|
|
+ cities: z.array(areaResponseSchema),
|
|
|
|
|
+ pagination: z.object({
|
|
|
|
|
+ page: z.number(),
|
|
|
|
|
+ pageSize: z.number(),
|
|
|
|
|
+ total: z.number(),
|
|
|
|
|
+ totalPages: z.number()
|
|
|
|
|
+ })
|
|
|
|
|
+ }),
|
|
|
|
|
+ message: z.string()
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// 区县列表响应Schema
|
|
|
|
|
+const districtsResponseSchema = z.object({
|
|
|
|
|
+ success: z.boolean(),
|
|
|
|
|
+ data: z.object({
|
|
|
|
|
+ districts: z.array(areaResponseSchema),
|
|
|
|
|
+ pagination: z.object({
|
|
|
|
|
+ page: z.number(),
|
|
|
|
|
+ pageSize: z.number(),
|
|
|
|
|
+ total: z.number(),
|
|
|
|
|
+ totalPages: z.number()
|
|
|
|
|
+ })
|
|
|
|
|
+ }),
|
|
|
|
|
+ message: z.string()
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// 错误响应Schema
|
|
|
|
|
+const errorSchema = z.object({
|
|
|
|
|
+ code: z.number(),
|
|
|
|
|
+ message: z.string(),
|
|
|
|
|
+ errors: z.array(z.object({
|
|
|
|
|
+ path: z.array(z.string()),
|
|
|
|
|
+ message: z.string()
|
|
|
|
|
+ })).optional()
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// 创建省份查询路由
|
|
|
|
|
+const getProvincesRoute = createRoute({
|
|
|
|
|
+ method: 'get',
|
|
|
|
|
+ path: '/provinces',
|
|
|
|
|
+ request: {
|
|
|
|
|
+ query: getProvincesSchema
|
|
|
|
|
+ },
|
|
|
|
|
+ responses: {
|
|
|
|
|
+ 200: {
|
|
|
|
|
+ description: '获取省份列表成功',
|
|
|
|
|
+ content: {
|
|
|
|
|
+ 'application/json': { schema: provincesResponseSchema }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ 500: {
|
|
|
|
|
+ description: '获取省份列表失败',
|
|
|
|
|
+ content: { 'application/json': { schema: errorSchema } }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// 创建城市查询路由
|
|
|
|
|
+const getCitiesRoute = createRoute({
|
|
|
|
|
+ method: 'get',
|
|
|
|
|
+ path: '/cities',
|
|
|
|
|
+ request: {
|
|
|
|
|
+ query: getCitiesSchema
|
|
|
|
|
+ },
|
|
|
|
|
+ responses: {
|
|
|
|
|
+ 200: {
|
|
|
|
|
+ description: '获取城市列表成功',
|
|
|
|
|
+ content: {
|
|
|
|
|
+ 'application/json': { schema: citiesResponseSchema }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ 400: {
|
|
|
|
|
+ description: '参数错误',
|
|
|
|
|
+ content: { 'application/json': { schema: errorSchema } }
|
|
|
|
|
+ },
|
|
|
|
|
+ 500: {
|
|
|
|
|
+ description: '获取城市列表失败',
|
|
|
|
|
+ content: { 'application/json': { schema: errorSchema } }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// 创建区县查询路由
|
|
|
|
|
+const getDistrictsRoute = createRoute({
|
|
|
|
|
+ method: 'get',
|
|
|
|
|
+ path: '/districts',
|
|
|
|
|
+ request: {
|
|
|
|
|
+ query: getDistrictsSchema
|
|
|
|
|
+ },
|
|
|
|
|
+ responses: {
|
|
|
|
|
+ 200: {
|
|
|
|
|
+ description: '获取区县列表成功',
|
|
|
|
|
+ content: {
|
|
|
|
|
+ 'application/json': { schema: districtsResponseSchema }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ 400: {
|
|
|
|
|
+ description: '参数错误',
|
|
|
|
|
+ content: { 'application/json': { schema: errorSchema } }
|
|
|
|
|
+ },
|
|
|
|
|
+ 500: {
|
|
|
|
|
+ description: '获取区县列表失败',
|
|
|
|
|
+ content: { 'application/json': { schema: errorSchema } }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const app = new OpenAPIHono()
|
|
|
|
|
+ .openapi(getProvincesRoute, async (c) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const { page, pageSize } = c.req.valid('query');
|
|
|
|
|
+
|
|
|
|
|
+ // 模拟省份数据
|
|
|
|
|
+ const mockProvinces = [
|
|
|
|
|
+ { id: 1, name: '北京市', code: '110000', level: 1, parentId: null },
|
|
|
|
|
+ { id: 2, name: '天津市', code: '120000', level: 1, parentId: null },
|
|
|
|
|
+ { id: 3, name: '河北省', code: '130000', level: 1, parentId: null },
|
|
|
|
|
+ { id: 4, name: '山西省', code: '140000', level: 1, parentId: null },
|
|
|
|
|
+ { id: 5, name: '内蒙古自治区', code: '150000', level: 1, parentId: null },
|
|
|
|
|
+ { id: 6, name: '辽宁省', code: '210000', level: 1, parentId: null },
|
|
|
|
|
+ { id: 7, name: '吉林省', code: '220000', level: 1, parentId: null },
|
|
|
|
|
+ { id: 8, name: '黑龙江省', code: '230000', level: 1, parentId: null },
|
|
|
|
|
+ { id: 9, name: '上海市', code: '310000', level: 1, parentId: null },
|
|
|
|
|
+ { id: 10, name: '江苏省', code: '320000', level: 1, parentId: null }
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ // 分页
|
|
|
|
|
+ const startIndex = (page - 1) * pageSize;
|
|
|
|
|
+ const endIndex = startIndex + pageSize;
|
|
|
|
|
+ const paginatedProvinces = mockProvinces.slice(startIndex, endIndex);
|
|
|
|
|
+
|
|
|
|
|
+ return c.json({
|
|
|
|
|
+ success: true,
|
|
|
|
|
+ data: {
|
|
|
|
|
+ provinces: paginatedProvinces,
|
|
|
|
|
+ pagination: {
|
|
|
|
|
+ page,
|
|
|
|
|
+ pageSize,
|
|
|
|
|
+ total: mockProvinces.length,
|
|
|
|
|
+ totalPages: Math.ceil(mockProvinces.length / pageSize)
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ message: '获取省份列表成功'
|
|
|
|
|
+ }, 200);
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('获取省份列表失败:', error);
|
|
|
|
|
+ return c.json({
|
|
|
|
|
+ code: 500,
|
|
|
|
|
+ message: error instanceof Error ? error.message : '获取省份列表失败'
|
|
|
|
|
+ }, 500);
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ .openapi(getCitiesRoute, async (c) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const { provinceId, page, pageSize } = c.req.valid('query');
|
|
|
|
|
+
|
|
|
|
|
+ // 模拟城市数据(基于省份ID)
|
|
|
|
|
+ const mockCities = {
|
|
|
|
|
+ 1: [ // 北京市
|
|
|
|
|
+ { id: 11, name: '北京市', code: '110100', level: 2, parentId: 1 }
|
|
|
|
|
+ ],
|
|
|
|
|
+ 9: [ // 上海市
|
|
|
|
|
+ { id: 31, name: '上海市', code: '310100', level: 2, parentId: 9 }
|
|
|
|
|
+ ],
|
|
|
|
|
+ 10: [ // 江苏省
|
|
|
|
|
+ { id: 32, name: '南京市', code: '320100', level: 2, parentId: 10 },
|
|
|
|
|
+ { id: 33, name: '苏州市', code: '320500', level: 2, parentId: 10 },
|
|
|
|
|
+ { id: 34, name: '无锡市', code: '320200', level: 2, parentId: 10 }
|
|
|
|
|
+ ]
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const cities = mockCities[provinceId] || [];
|
|
|
|
|
+
|
|
|
|
|
+ // 分页
|
|
|
|
|
+ const startIndex = (page - 1) * pageSize;
|
|
|
|
|
+ const endIndex = startIndex + pageSize;
|
|
|
|
|
+ const paginatedCities = cities.slice(startIndex, endIndex);
|
|
|
|
|
+
|
|
|
|
|
+ return c.json({
|
|
|
|
|
+ success: true,
|
|
|
|
|
+ data: {
|
|
|
|
|
+ cities: paginatedCities,
|
|
|
|
|
+ pagination: {
|
|
|
|
|
+ page,
|
|
|
|
|
+ pageSize,
|
|
|
|
|
+ total: cities.length,
|
|
|
|
|
+ totalPages: Math.ceil(cities.length / pageSize)
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ message: '获取城市列表成功'
|
|
|
|
|
+ }, 200);
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('获取城市列表失败:', error);
|
|
|
|
|
+ return c.json({
|
|
|
|
|
+ code: 500,
|
|
|
|
|
+ message: error instanceof Error ? error.message : '获取城市列表失败'
|
|
|
|
|
+ }, 500);
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ .openapi(getDistrictsRoute, async (c) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const { cityId, page, pageSize } = c.req.valid('query');
|
|
|
|
|
+
|
|
|
|
|
+ // 模拟区县数据(基于城市ID)
|
|
|
|
|
+ const mockDistricts = {
|
|
|
|
|
+ 11: [ // 北京市
|
|
|
|
|
+ { id: 110101, name: '东城区', code: '110101', level: 3, parentId: 11 },
|
|
|
|
|
+ { id: 110102, name: '西城区', code: '110102', level: 3, parentId: 11 },
|
|
|
|
|
+ { id: 110105, name: '朝阳区', code: '110105', level: 3, parentId: 11 },
|
|
|
|
|
+ { id: 110106, name: '丰台区', code: '110106', level: 3, parentId: 11 },
|
|
|
|
|
+ { id: 110107, name: '石景山区', code: '110107', level: 3, parentId: 11 }
|
|
|
|
|
+ ],
|
|
|
|
|
+ 31: [ // 上海市
|
|
|
|
|
+ { id: 310101, name: '黄浦区', code: '310101', level: 3, parentId: 31 },
|
|
|
|
|
+ { id: 310104, name: '徐汇区', code: '310104', level: 3, parentId: 31 },
|
|
|
|
|
+ { id: 310105, name: '长宁区', code: '310105', level: 3, parentId: 31 },
|
|
|
|
|
+ { id: 310106, name: '静安区', code: '310106', level: 3, parentId: 31 },
|
|
|
|
|
+ { id: 310107, name: '普陀区', code: '310107', level: 3, parentId: 31 }
|
|
|
|
|
+ ],
|
|
|
|
|
+ 34: [ // 无锡市
|
|
|
|
|
+ { id: 320202, name: '梁溪区', code: '320202', level: 3, parentId: 34 },
|
|
|
|
|
+ { id: 320205, name: '锡山区', code: '320205', level: 3, parentId: 34 },
|
|
|
|
|
+ { id: 320206, name: '惠山区', code: '320206', level: 3, parentId: 34 },
|
|
|
|
|
+ { id: 320211, name: '滨湖区', code: '320211', level: 3, parentId: 34 },
|
|
|
|
|
+ { id: 320214, name: '新吴区', code: '320214', level: 3, parentId: 34 }
|
|
|
|
|
+ ]
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const districts = mockDistricts[cityId] || [];
|
|
|
|
|
+
|
|
|
|
|
+ // 分页
|
|
|
|
|
+ const startIndex = (page - 1) * pageSize;
|
|
|
|
|
+ const endIndex = startIndex + pageSize;
|
|
|
|
|
+ const paginatedDistricts = districts.slice(startIndex, endIndex);
|
|
|
|
|
+
|
|
|
|
|
+ return c.json({
|
|
|
|
|
+ success: true,
|
|
|
|
|
+ data: {
|
|
|
|
|
+ districts: paginatedDistricts,
|
|
|
|
|
+ pagination: {
|
|
|
|
|
+ page,
|
|
|
|
|
+ pageSize,
|
|
|
|
|
+ total: districts.length,
|
|
|
|
|
+ totalPages: Math.ceil(districts.length / pageSize)
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ message: '获取区县列表成功'
|
|
|
|
|
+ }, 200);
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('获取区县列表失败:', error);
|
|
|
|
|
+ return c.json({
|
|
|
|
|
+ code: 500,
|
|
|
|
|
+ message: error instanceof Error ? error.message : '获取区县列表失败'
|
|
|
|
|
+ }, 500);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+export default app;
|