Brian 3 months ago
parent 45e9b03e2d
commit bf5bf7287f

@ -0,0 +1,20 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "Launch Java Program",
"request": "launch",
"mainClass": ""
},
{
"type": "java",
"name": "Current File",
"request": "launch",
"mainClass": "${file}"
}
]
}

@ -0,0 +1 @@
Subproject commit 2023c204150af2d4a5f41485e12868d123ad8e08

@ -18,34 +18,34 @@ services:
--max_allowed_packet=128M
--default-authentication-plugin=caching_sha2_password
ports:
- 3306:3306
- 3308:3306
networks:
- jeecg-boot
jeecg-boot-redis:
image: registry.cn-hangzhou.aliyuncs.com/jeecgdocker/redis:5.0
ports:
- 6379:6379
- 6378:6379
restart: always
hostname: jeecg-boot-redis
container_name: jeecg-boot-redis
networks:
- jeecg-boot
jeecg-boot-system:
build:
context: ./jeecg-module-system/jeecg-system-start
restart: on-failure
depends_on:
- jeecg-boot-mysql
- jeecg-boot-redis
container_name: jeecg-boot-system
image: jeecg-boot-system
hostname: jeecg-boot-system
ports:
- 8080:8080
networks:
- jeecg-boot
jeecg-boot-system:
build:
context: ./jeecg-module-system/jeecg-system-start
restart: on-failure
depends_on:
- jeecg-boot-mysql
- jeecg-boot-redis
container_name: jeecg-boot-system
image: jeecg-boot-system
hostname: jeecg-boot-system
ports:
- 8123:8123
networks:
- jeecg-boot
networks:
jeecg-boot:

@ -38,7 +38,8 @@ public class Swagger3Config implements WebMvcConfigurer {
"/sys/cas/client/validateLogin",
"/test/jeecgDemo/demo3",
"/sys/thirdLogin/**",
"/sys/user/register"
"/sys/user/register",
"/weixn/weixinUser/getWxUserInfo"
));
/**

@ -102,6 +102,7 @@ public class ShiroConfig {
filterChainDefinitionMap.put("/sys/common/static/**", "anon");//图片预览 &下载文件不限制token
filterChainDefinitionMap.put("/sys/common/pdf/**", "anon");//pdf预览
//filterChainDefinitionMap.put("/sys/common/view/**", "anon");//图片预览不限制token
//filterChainDefinitionMap.put("/sys/common/download/**", "anon");//文件下载不限制token
filterChainDefinitionMap.put("/generic/**", "anon");//pdf预览需要文件

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>jeecg-boot-parent</artifactId>
<groupId>org.jeecgframework.boot</groupId>
<version>3.7.4</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jeecg-module-computed</artifactId>
<dependencies>
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-boot-base-core</artifactId>
</dependency>
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-system-biz</artifactId>
</dependency>
</dependencies>
</project>

@ -0,0 +1,180 @@
package org.jeecg.modules.calc.controller;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.query.QueryRuleEnum;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.calc.entity.Calc;
import org.jeecg.modules.calc.service.ICalcService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.jeecgframework.poi.excel.ExcelImportUtil;
import org.jeecgframework.poi.excel.def.NormalExcelConstants;
import org.jeecgframework.poi.excel.entity.ExportParams;
import org.jeecgframework.poi.excel.entity.ImportParams;
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
import org.jeecg.common.system.base.controller.JeecgController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;
import com.alibaba.fastjson.JSON;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.apache.shiro.authz.annotation.RequiresPermissions;
/**
* @Description:
* @Author: jeecg-boot
* @Date: 2025-04-05
* @Version: V1.0
*/
@Tag(name="算法计算器表")
@RestController
@RequestMapping("/calc/calc")
@Slf4j
public class CalcController extends JeecgController<Calc, ICalcService> {
@Autowired
private ICalcService calcService;
/**
*
*
* @param calc
* @param pageNo
* @param pageSize
* @param req
* @return
*/
//@AutoLog(value = "算法计算器表-分页列表查询")
@Operation(summary = "算法计算器表-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<Calc>> queryPageList(Calc calc,
@RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
@RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
HttpServletRequest req) {
QueryWrapper<Calc> queryWrapper = QueryGenerator.initQueryWrapper(calc, req.getParameterMap());
Page<Calc> page = new Page<Calc>(pageNo, pageSize);
IPage<Calc> pageList = calcService.page(page, queryWrapper);
return Result.OK(pageList);
}
/**
*
*
* @param calc
* @return
*/
@AutoLog(value = "算法计算器表-添加")
@Operation(summary = "算法计算器表-添加")
@RequiresPermissions("calc:computed:add")
@PostMapping(value = "/add")
public Result<String> add(@RequestBody Calc calc) {
calcService.save(calc);
return Result.OK("添加成功!");
}
/**
*
*
* @param calc
* @return
*/
@AutoLog(value = "算法计算器表-编辑")
@Operation(summary = "算法计算器表-编辑")
@RequiresPermissions("calc:computed:edit")
@RequestMapping(value = "/edit", method = {RequestMethod.PUT,RequestMethod.POST})
public Result<String> edit(@RequestBody Calc calc) {
calcService.updateById(calc);
return Result.OK("编辑成功!");
}
/**
* id
*
* @param id
* @return
*/
@AutoLog(value = "算法计算器表-通过id删除")
@Operation(summary = "算法计算器表-通过id删除")
@RequiresPermissions("calc:computed:delete")
@DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name="id",required=true) String id) {
calcService.removeById(id);
return Result.OK("删除成功!");
}
/**
*
*
* @param ids
* @return
*/
@AutoLog(value = "算法计算器表-批量删除")
@Operation(summary = "算法计算器表-批量删除")
@RequiresPermissions("calc:computed:deleteBatch")
@DeleteMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestParam(name="ids",required=true) String ids) {
this.calcService.removeByIds(Arrays.asList(ids.split(",")));
return Result.OK("批量删除成功!");
}
/**
* id
*
* @param id
* @return
*/
//@AutoLog(value = "算法计算器表-通过id查询")
@Operation(summary = "算法计算器表-通过id查询")
@GetMapping(value = "/queryById")
public Result<Calc> queryById(@RequestParam(name="id",required=true) String id) {
Calc calc = calcService.getById(id);
if(calc==null) {
return Result.error("未找到对应数据");
}
return Result.OK(calc);
}
/**
* excel
*
* @param request
* @param calc
*/
@RequiresPermissions("calc:computed:exportXls")
@RequestMapping(value = "/exportXls")
public ModelAndView exportXls(HttpServletRequest request, Calc calc) {
return super.exportXls(request, calc, Calc.class, "算法计算器表");
}
/**
* excel
*
* @param request
* @param response
* @return
*/
@RequiresPermissions("calc:computed:importExcel")
@RequestMapping(value = "/importExcel", method = RequestMethod.POST)
public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
return super.importExcel(request, response, Calc.class);
}
}

@ -0,0 +1,80 @@
package org.jeecg.modules.calc.entity;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableLogic;
import org.jeecg.common.constant.ProvinceCityArea;
import org.jeecg.common.util.SpringContextUtils;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
import org.jeecgframework.poi.excel.annotation.Excel;
import org.jeecg.common.aspect.annotation.Dict;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* @Description:
* @Author: jeecg-boot
* @Date: 2025-04-05
* @Version: V1.0
*/
@Data
@TableName("computed")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@Schema(description="算法计算器表")
public class Calc implements Serializable {
private static final long serialVersionUID = 1L;
/**主键*/
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "主键")
private String id;
/**创建人*/
@Schema(description = "创建人")
private String createBy;
/**创建日期*/
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
@Schema(description = "创建日期")
private Date createTime;
/**更新人*/
@Schema(description = "更新人")
private String updateBy;
/**更新日期*/
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
@Schema(description = "更新日期")
private Date updateTime;
/**所属部门*/
@Schema(description = "所属部门")
private String sysOrgCode;
/**算法名称*/
@Excel(name = "算法名称", width = 15)
@Schema(description = "算法名称")
private String name;
/**算法测试地址*/
@Excel(name = "算法测试地址", width = 15)
@Schema(description = "算法测试地址")
private String testlink;
/**算法编辑地址*/
@Excel(name = "算法编辑地址", width = 15)
@Schema(description = "算法编辑地址")
private String editlink;
/**算法id*/
@Excel(name = "算法id", width = 15)
@Schema(description = "算法id")
private String difyid;
/**算法编码*/
@Excel(name = "算法编码", width = 15)
@Schema(description = "算法编码")
private String computedCode;
}

@ -0,0 +1,17 @@
package org.jeecg.modules.calc.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import org.jeecg.modules.calc.entity.Calc;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @Description:
* @Author: jeecg-boot
* @Date: 2025-04-05
* @Version: V1.0
*/
public interface CalcMapper extends BaseMapper<Calc> {
}

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.jeecg.modules.demo.calc.mapper.CalcMapper">
</mapper>

@ -0,0 +1,14 @@
package org.jeecg.modules.calc.service;
import org.jeecg.modules.calc.entity.Calc;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @Description:
* @Author: jeecg-boot
* @Date: 2025-04-05
* @Version: V1.0
*/
public interface ICalcService extends IService<Calc> {
}

@ -0,0 +1,19 @@
package org.jeecg.modules.calc.service.impl;
import org.jeecg.modules.calc.entity.Calc;
import org.jeecg.modules.calc.mapper.CalcMapper;
import org.jeecg.modules.calc.service.ICalcService;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
/**
* @Description:
* @Author: jeecg-boot
* @Date: 2025-04-05
* @Version: V1.0
*/
@Service
public class CalcServiceImpl extends ServiceImpl<CalcMapper, Calc> implements ICalcService {
}

@ -0,0 +1,26 @@
package org.jeecg.modules.weixn.config; // 放在配置包下
import lombok.Data;
import org.jeecg.modules.weixn.entity.IComputedTypes;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.annotation.Validated; // 可选,用于校验
import javax.validation.constraints.NotEmpty; // 可选,用于校验
import java.util.Map;
@Configuration
@ConfigurationProperties(prefix = "spring.calculation.keys") // 对应配置文件的前缀
@Data // 使用 Lombok
@Validated // 可选:启用 JSR 303 校验
public class ExternalApiKeysConfig {
// 使用 Map 来存储不同类型的 API Key
// key: 标识符 (如 "tanyuan", "chanzhalv")
// value: 对应的 API Key
@NotEmpty // 可选:确保配置不为空 Map
private Map<String, String> calculation;
// 你也可以为特定的 key 添加单独的字段,如果它们很常用或需要特殊处理
// private String defaultKey;
}

@ -0,0 +1,268 @@
package org.jeecg.modules.weixn.controller;
import java.util.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.alibaba.fastjson.JSONObject;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.query.QueryRuleEnum;
import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.util.PasswordUtil;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.config.shiro.IgnoreAuth;
import org.jeecg.modules.calc.entity.Calc;
import org.jeecg.modules.weixn.entity.CalcData;
import org.jeecg.modules.weixn.entity.CalcDataBaseDto;
import org.jeecg.modules.weixn.entity.Dto.*;
import org.jeecg.modules.weixn.service.ICalcService;
import org.jeecg.modules.system.entity.SysUser;
import org.jeecg.modules.system.service.ISysUserService;
import org.jeecg.modules.weixn.entity.IComputedTypes;
import org.jeecg.modules.weixn.entity.WeixinUser;
import org.jeecg.modules.weixn.service.IWeixinUserService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.modules.weixn.vo.WeChatUserData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.ModelAndView;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.apache.shiro.authz.annotation.RequiresPermissions;
/**
* @Description:
* @Author: jeecg-boot
* @Date: 2025-04-04
* @Version: V1.0
*/
@Tag(name="小程序用户表")
@RestController
@RequestMapping("/weixn/weixinUser")
@Slf4j
public class WeixinUserController extends JeecgController<WeixinUser, IWeixinUserService> {
@Autowired
private IWeixinUserService weixinUserService;
@Autowired
private ICalcService calcService ;
/**
*
*
* @param weixinUser
* @param pageNo
* @param pageSize
* @param req
* @return
*/
//@AutoLog(value = "小程序用户表-分页列表查询")
@Operation(summary = "小程序用户表-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<WeixinUser>> queryPageList(WeixinUser weixinUser,
@RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
@RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
HttpServletRequest req) {
// 自定义查询规则
Map<String, QueryRuleEnum> customeRuleMap = new HashMap<>();
// 自定义多选的查询规则为LIKE_WITH_OR
customeRuleMap.put("computedids", QueryRuleEnum.LIKE_WITH_OR);
QueryWrapper<WeixinUser> queryWrapper = QueryGenerator.initQueryWrapper(weixinUser, req.getParameterMap(),customeRuleMap);
Page<WeixinUser> page = new Page<WeixinUser>(pageNo, pageSize);
IPage<WeixinUser> pageList = weixinUserService.page(page, queryWrapper);
return Result.OK(pageList);
}
/**
*
*
* @param weixinUser
* @return
*/
@AutoLog(value = "小程序用户表-添加")
@Operation(summary = "小程序用户表-添加")
@RequiresPermissions("weixn:weixin_user:add")
@PostMapping(value = "/add")
public Result<String> add(@RequestBody WeixinUser weixinUser) {
weixinUserService.save(weixinUser);
return Result.OK("添加成功!");
}
/**
*
*
* @param weixinUser
* @return
*/
@AutoLog(value = "小程序用户表-编辑")
@Operation(summary = "小程序用户表-编辑")
@RequiresPermissions("weixn:weixin_user:edit")
@RequestMapping(value = "/edit", method = {RequestMethod.PUT,RequestMethod.POST})
public Result<String> edit(@RequestBody WeixinUser weixinUser) {
weixinUserService.updateById(weixinUser);
return Result.OK("编辑成功!");
}
/**
* id
*
* @param id
* @return
*/
@AutoLog(value = "小程序用户表-通过id删除")
@Operation(summary = "小程序用户表-通过id删除")
@RequiresPermissions("weixn:weixin_user:delete")
@DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name="id",required=true) String id) {
weixinUserService.removeById(id);
return Result.OK("删除成功!");
}
/**
*
*
* @param ids
* @return
*/
@AutoLog(value = "小程序用户表-批量删除")
@Operation(summary = "小程序用户表-批量删除")
@RequiresPermissions("weixn:weixin_user:deleteBatch")
@DeleteMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestParam(name="ids",required=true) String ids) {
this.weixinUserService.removeByIds(Arrays.asList(ids.split(",")));
return Result.OK("批量删除成功!");
}
/**
* id
*
* @param id
* @return
*/
//@AutoLog(value = "小程序用户表-通过id查询")
@Operation(summary = "小程序用户表-通过id查询")
@GetMapping(value = "/queryById")
public Result<WeixinUser> queryById(@RequestParam(name="id",required=true) String id) {
WeixinUser weixinUser = weixinUserService.getById(id);
if(weixinUser==null) {
return Result.error("未找到对应数据");
}
return Result.OK(weixinUser);
}
/**
* excel
*
* @param request
* @param weixinUser
*/
@RequiresPermissions("weixn:weixin_user:exportXls")
@RequestMapping(value = "/exportXls")
public ModelAndView exportXls(HttpServletRequest request, WeixinUser weixinUser) {
return super.exportXls(request, weixinUser, WeixinUser.class, "小程序用户表");
}
/**
* excel
*
* @param request
* @param response
* @return
*/
@RequiresPermissions("weixn:weixin_user:importExcel")
@RequestMapping(value = "/importExcel", method = RequestMethod.POST)
public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
return super.importExcel(request, response, WeixinUser.class);
}
private static final String appid = "wx06cb24e3533246ad";
private static final String secret = "8f61f11453c467b12dd7bfd29910d281";
private final RestTemplate restTemplate = new RestTemplate();
/**
*
* @description
* 1.
* 2.
* 3.
* 4.
* 5.
*
* @param code
* @return Result
*/
@Operation(summary = "获取微信小程序手机号")
@IgnoreAuth
@PostMapping("/getPhoneNumber")
@ResponseBody
public Result<?> getPhoneNumber(@RequestParam String code) {
try {
// 1. 首先获取access_token
String tokenUrl = String.format(
"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s",
appid, secret
);
JSONObject tokenResponse = restTemplate.getForObject(tokenUrl, JSONObject.class);
if (tokenResponse == null || tokenResponse.getString("access_token") == null) {
return Result.error("获取access_token失败");
}
String accessToken = tokenResponse.getString("access_token");
// 2. 使用access_token和code获取手机号
String phoneUrl = "https://api.weixin.qq.com/wxa/business/getuserphonenumber";
String finalUrl = phoneUrl + "?access_token=" + accessToken;
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
JSONObject requestBody = new JSONObject();
requestBody.put("code", code);
HttpEntity<String> request = new HttpEntity<>(requestBody.toString(), headers);
JSONObject response = restTemplate.postForObject(finalUrl, request, JSONObject.class);
if (response != null && response.getInteger("errcode") == 0) {
JSONObject phoneInfo = response.getJSONObject("phone_info");
String phoneNumber = phoneInfo.getString("phoneNumber");
return Result.OK(weixinUserService.wxLogin(phoneNumber));
} else {
return Result.error("获取手机号失败:" + (response != null ? response.getString("errmsg") : "未知错误"));
}
} catch (Exception e) {
return Result.error("获取手机号异常:" + e.getMessage());
}
}
@Operation(summary = "计算")
@PostMapping("/calculate")
@ResponseBody
public Result<?> calculate(@RequestBody CalcData entity) {
String phone = entity.getPhone();
CalcDataBaseDto data = entity.getData();
String computedCode = data.getCalcModelName();
try {
String res = calcService.calc(data, phone, computedCode);
return Result.OK(res);
} catch (IllegalArgumentException e) {
return Result.error("计算异常:" + e.getMessage());
}
}
}

@ -0,0 +1,17 @@
package org.jeecg.modules.weixn.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
@Data
@Schema(description="计算器传参对象")
public class CalcData implements Serializable {
@Schema(description = "用户手机号")
private String phone;
@Schema(description = "具体的计算参数,类型根据业务决定")
private CalcDataBaseDto data;
}

@ -0,0 +1,49 @@
package org.jeecg.modules.weixn.entity; // 或者你专门放 DTO 的包
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.swagger.v3.oas.annotations.media.DiscriminatorMapping;
import io.swagger.v3.oas.annotations.media.Schema;
import org.jeecg.modules.weixn.entity.Dto.*;
import java.io.Serializable;
// --- 关键:在基础类型上添加 Swagger 注解 ---
@Schema(
description = "基础计算数据模型,具体类型由 'computedType' 属性决定",
// 告诉 Swagger 使用哪个属性作为判别器 (必须和你 @JsonTypeInfo 中的 property 一致)
discriminatorProperty = "computedType",
// 可选但推荐:显式列出可能的子类型及其在判别器中的值
// (注意:这里的 value 必须和你 @JsonSubTypes 中定义的 name 完全一致)
discriminatorMapping = {
@DiscriminatorMapping(value = IComputedTypes.CHAN_ZHA_LV_NAME, schema = ChanZhaLv.class),
@DiscriminatorMapping(value = IComputedTypes.TAN_YUAN_NAME, schema = TanYuan.class),
@DiscriminatorMapping(value = IComputedTypes.WU_SHUI_TANYUAN_NAME, schema = WuShuiTanYuan.class),
@DiscriminatorMapping(value = IComputedTypes.YAN_YANG_GUAN_QI_LV_NAME, schema = YanYangGuanQiLv.class),
@DiscriminatorMapping(value = IComputedTypes.YAN_YANG_JI_FU_HE_NAME, schema = YanYangYouJiFuHe.class),
@DiscriminatorMapping(value = IComputedTypes.ZHAO_QI_NAME, schema = ZhaoQi.class)
}
// 如果不写 discriminatorMappingspringdoc 有时也能根据 @JsonSubTypes 推断,
// 但显式写出来更可靠,并且允许你为文档中的每个子类型指定 schema 引用。
)
// --- 保留 Jackson 的注解,它们在运行时序列化/反序列化时仍然需要 ---
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "computedType" // 与 discriminatorProperty 保持一致
)
@JsonSubTypes({
@JsonSubTypes.Type(value = ChanZhaLv.class, name = IComputedTypes.CHAN_ZHA_LV_NAME),
@JsonSubTypes.Type(value = TanYuan.class, name = IComputedTypes.TAN_YUAN_NAME),
@JsonSubTypes.Type(value = WuShuiTanYuan.class, name = IComputedTypes.WU_SHUI_TANYUAN_NAME),
@JsonSubTypes.Type(value = YanYangGuanQiLv.class, name = IComputedTypes.YAN_YANG_GUAN_QI_LV_NAME),
@JsonSubTypes.Type(value = YanYangYouJiFuHe.class, name = IComputedTypes.YAN_YANG_JI_FU_HE_NAME),
@JsonSubTypes.Type(value = ZhaoQi.class, name = IComputedTypes.ZHAO_QI_NAME)
})
public interface CalcDataBaseDto extends Serializable {
// 目前保持为空,仅作类型标记
@JsonProperty("@computedType")
String getCalcModelName();
}

@ -0,0 +1,32 @@
package org.jeecg.modules.weixn.entity.Dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.jeecg.modules.weixn.entity.CalcDataBaseDto;
import org.jeecg.modules.weixn.entity.IComputedTypes;
@Data
@Getter
@Setter
@Schema(description="产渣率计算参数 DTO") // 给个描述
public class ChanZhaLv implements CalcDataBaseDto { // <--- 实现接口
private static final long serialVersionUID = 1L; // 最好给实现 Serializable 的类加上这个
@Schema(description = "垃圾收运量")
private String ljsyl; // 垃圾收运量
@Schema(description = "大渣产渣量")
private String dzczl; // 大渣产渣量
@Schema(description = "沼渣产渣量")
private String zzczl; // 沼渣产渣量
@Override
public String getCalcModelName() {
return IComputedTypes.CHAN_ZHA_LV_NAME;
}
}

@ -0,0 +1,44 @@
package org.jeecg.modules.weixn.entity.Dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.jeecg.modules.weixn.entity.CalcDataBaseDto;
import org.jeecg.modules.weixn.entity.IComputedTypes;
@Getter
@Setter
@Data
@Schema(description="碳源计算参数 DTO") // 给个描述
public class TanYuan implements CalcDataBaseDto {
@Schema(description = "提油后废水产量")
private String fscl; // 提油后废水产量
@Schema(description = "1方沼气提纯天然气量/1方沼气发电量kwh")
private String mfcl; // 1方沼气提纯天然气量/1方沼气发电量kwh
@Schema(description = "1方天然气售价元/m³/1kwh发电售价元/kwh")
private String mfsj; // 1方天然气售价元/m³/1kwh发电售价元/kwh
@Schema(description = "提纯1方天然气的成本元/m³/1kwh发电成本元/kwh")
private String mfcb; // 提纯1方天然气的成本元/m³/1kwh发电成本元/kwh
@Schema(description = "目前吨污水处置成本(元/t")
private String czcb; // 目前吨污水处置成本(元/t
@Schema(description = "目前使用碳源种类")
private String tyzl; // 目前使用碳源种类
@Schema(description = "目前使用碳源单价(元/t")
private String tydj; // 目前使用碳源单价(元/t
@Schema(description = "目前每日使用碳源量t")
private String mrtyl; // 目前每日使用碳源量t
@Override
public String getCalcModelName() {
return IComputedTypes.TAN_YUAN_NAME; // 返回具体的计算模型名称
}
}

@ -0,0 +1,43 @@
package org.jeecg.modules.weixn.entity.Dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.jeecg.modules.weixn.entity.CalcDataBaseDto;
import org.jeecg.modules.weixn.entity.IComputedTypes;
@Getter
@Setter
@Data
@Schema(description="污水碳源计算参数 DTO") // 给个描述
public class WuShuiTanYuan implements CalcDataBaseDto {
private static final long serialVersionUID = 1L; // 最好给实现 Serializable 的类加上这个
@Schema(description = "污水-处理量")
private String cll; // 污水-处理量
@Schema(description = "污水-COD")
private String cod; // 污水-COD
@Schema(description = "污水-总氮")
private String zd; // 污水-总氮
@Schema(description = "污水-出水总氮")
private String cszd; // 污水-出水总氮
@Schema(description = "待拖氮量")
private String dtdl; // 待拖氮量
@Schema(description = "碳源种类")
private String tyzl; // 碳源种类
@Schema(description = "碳源种有效SCOD")
private String scod; // 碳源种有效SCOD
@Override
public String getCalcModelName() {
return IComputedTypes.WU_SHUI_TANYUAN_NAME;
}
}

@ -0,0 +1,37 @@
package org.jeecg.modules.weixn.entity.Dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.jeecg.modules.weixn.entity.CalcDataBaseDto;
import org.jeecg.modules.weixn.entity.IComputedTypes;
@Getter
@Setter
@Data
@Schema(description="厌氧罐气率计算参数 DTO") // 给个描述
public class YanYangGuanQiLv implements CalcDataBaseDto {
private static final long serialVersionUID = 1L; // 最好给实现 Serializable 的类加上这个
@Schema(description = "总产气")
private String zcq;
@Schema(description = "进料TS")
private String jlts;
@Schema(description = "进料VS")
private String jlvs;
@Schema(description = "进罐浆液量")
private String jgjyl;
@Schema(description = "原生垃圾量")
private String ysljl;
@Override
public String getCalcModelName() {
return IComputedTypes.YAN_YANG_GUAN_QI_LV_NAME;
}
}

@ -0,0 +1,36 @@
package org.jeecg.modules.weixn.entity.Dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.jeecg.modules.weixn.entity.CalcDataBaseDto;
import org.jeecg.modules.weixn.entity.IComputedTypes;
import java.io.Serial;
@Getter
@Setter
@Data
@Schema(description="厌氧有机负荷计算参数 DTO") // 给个描述
public class YanYangYouJiFuHe implements CalcDataBaseDto {
@Serial
private static final long serialVersionUID = 1L; // 最好给实现 Serializable 的类加上这个
@Schema(description = "罐容")
private String gr; // 罐容
@Schema(description = "进料TS")
private String jlts; // 进料TS
@Schema(description = "进料VS")
private String jlvs; // 进料VS
@Schema(description = "进罐浆液量")
private String jgjyl; // 进罐浆液量
@Override
public String getCalcModelName() {
return IComputedTypes.YAN_YANG_JI_FU_HE_NAME;
}
}

@ -0,0 +1,49 @@
package org.jeecg.modules.weixn.entity.Dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.jeecg.modules.weixn.entity.CalcDataBaseDto;
import org.jeecg.modules.weixn.entity.IComputedTypes;
@Getter
@Setter
@Data
@Schema(description="沼气增产剂计算参数 DTO") // 给个描述
public class ZhaoQi implements CalcDataBaseDto {
private static final long serialVersionUID = 1L; // 最好给实现 Serializable 的类加上这个
@Schema(description = "罐容")
private String gr; // 罐容
@Schema(description = "物科原料")
private String wkyl; // 物科原料
@Schema(description = "进料TS")
private String jlts; // 进料TS%
@Schema(description = "进料VS")
private String jlvs; // 进料VS%
@Schema(description = "30日日均进罐浆液量")
private String rjyl; // 30日日均进罐浆液量t/d
@Schema(description = "30日沼气日均产量")
private String rjcl; // 30日沼气日均产量m³/d
@Schema(description = "30日沼气日均甲烷浓度")
private String rjnd; // 30日沼气日均甲烷浓度%
@Schema(description = "单位沼气纯收益元")
private String dwsy; // 单位沼气纯收益元(元/m³
@Schema(description = "预计使用增产剂天数")
private String ts; // 预计使用增产剂天数(天)
@Override
public String getCalcModelName() {
return IComputedTypes.ZHAO_QI_NAME;
}
}

@ -0,0 +1,31 @@
package org.jeecg.modules.weixn.entity; // 或者你选择的其他包名
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor; // 如果需要无参构造函数
import java.io.Serial;
import java.io.Serializable;
/**
* API (http://aiprice.orgcoder.com/v1/workflow/run) 返回的 JSON 结构
*/
@Data // Lombok: 自动生成 getter, setter, toString, equals, hashCode
@NoArgsConstructor // Lombok: 自动生成无参构造函数 (Jackson 反序列化需要)
@JsonIgnoreProperties(ignoreUnknown = true) // Jackson: 忽略 JSON 中有但本类中没有定义的字段,增加健壮性
public class ExternalApiResponse implements Serializable {
@Serial
private static final long serialVersionUID = 1L; // 保持 Serializable 规范
@JsonProperty("workflow_run_id") // Jackson: 将 JSON 中的 "workflow_run_id" 映射到这个字段
private String workflowRunId;
@JsonProperty("task_id")
private String taskId;
@JsonProperty("data") // 对应 JSON 中的 "data" 对象
private ResponseData data; // 嵌套类,见下方定义
}

@ -0,0 +1,46 @@
package org.jeecg.modules.weixn.entity;
import lombok.Getter;
@Getter
public enum IComputedTypes {
// 1. 定义枚举常量,并在括号中调用构造函数传入关联的字符串值
TAN_YUAN("TAN_YUAN"),
ZHAO_QI("ZHAO_QI"),
CHAN_ZHA_LV("CHAN_ZHA_LV"),
YAN_YANG_JI_FU_HE("YAN_YANG_JI_FU_HE"),
WU_SHUI_TANYUAN("WU_SHUI_TANYUAN"),
YAN_YANG_GUAN_QI_LV("YAN_YANG_GUAN_QI_LV"); // 最后一个常量后用分号结束常量列表
// --- 对应的 public static final String 常量 ---
// 这些常量的值必须和构造函数传入的值(也就是你希望在 JSON 中使用的 name一致
public static final String TAN_YUAN_NAME = "TAN_YUAN";
public static final String ZHAO_QI_NAME = "ZHAO_QI";
public static final String CHAN_ZHA_LV_NAME = "CHAN_ZHA_LV"; // 这个字符串是编译时常量
public static final String YAN_YANG_JI_FU_HE_NAME = "YAN_YANG_JI_FU_HE";
public static final String WU_SHUI_TANYUAN_NAME = "WU_SHUI_TANYUAN";
public static final String YAN_YANG_GUAN_QI_LV_NAME = "YAN_YANG_GUAN_QI_LV";
// 4. (可选) 提供一个公共 getter 方法来访问这个值
// 2. 定义一个私有 final 字段来存储关联的值
private final String value;
// 3. 定义构造函数 (枚举的构造函数默认且必须是 private 或 package-private)
IComputedTypes(String value) {
this.value = value;
}
// 你还可以添加其他方法,比如根据字符串值查找枚举常量
public static IComputedTypes fromValue(String value) {
for (IComputedTypes type : IComputedTypes.values()) {
// 这里比较关联的 value 字段,而不是枚举常量名
if (type.value.equalsIgnoreCase(value)) { // 忽略大小写比较通常更健壮
return type;
}
}
// 如果找不到,可以返回 null 或抛出异常
throw new IllegalArgumentException("No enum constant with value " + value);
}
}

@ -0,0 +1,48 @@
package org.jeecg.modules.weixn.entity;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable; /**
* JSON "data"
*/
@Data
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class ResponseData implements Serializable { // 这个类可以是 ExternalApiResponse 的内部类,或者单独的文件
@Serial
private static final long serialVersionUID = 1L;
@JsonProperty("id")
private String id;
@JsonProperty("workflow_id")
private String workflowId;
@JsonProperty("status") // "succeeded" 或其他
private String status;
@JsonProperty("outputs") // 对应 JSON 中的 "outputs" 对象
private ResponseOutputs outputs; // 嵌套类,见下方定义
@JsonProperty("error")
private String error; // 假设错误信息是字符串,如果结构复杂需要再定义类
@JsonProperty("elapsed_time")
private Double elapsedTime;
@JsonProperty("total_tokens")
private Integer totalTokens;
@JsonProperty("total_steps")
private Integer totalSteps;
@JsonProperty("created_at")
private Long createdAt; // 时间戳通常用 Long
@JsonProperty("finished_at")
private Long finishedAt;
}

@ -0,0 +1,23 @@
package org.jeecg.modules.weixn.entity;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable; /**
* JSON "outputs"
*/
@Data
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
@Getter
public class ResponseOutputs implements Serializable { // 这个类也可以是内部类或单独文件
@Serial
private static final long serialVersionUID = 1L;
@JsonProperty("text") // 我们最关心的 "text" 字段
public String text;
}

@ -0,0 +1,94 @@
package org.jeecg.modules.weixn.entity;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableLogic;
import org.jeecg.common.constant.ProvinceCityArea;
import org.jeecg.common.util.SpringContextUtils;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
import org.jeecgframework.poi.excel.annotation.Excel;
import org.jeecg.common.aspect.annotation.Dict;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* @Description:
* @Author: jeecg-boot
* @Date: 2025-04-04
* @Version: V1.0
*/
@Data
@TableName("weixin_user")
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@Schema(description="小程序用户表")
public class WeixinUser implements Serializable {
private static final long serialVersionUID = 1L;
/**主键*/
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "主键")
private String id;
/**创建人*/
@Schema(description = "创建人")
private String createBy;
/**创建日期*/
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
@Schema(description = "创建日期")
private Date createTime;
/**更新人*/
@Schema(description = "更新人")
private String updateBy;
/**更新日期*/
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
@Schema(description = "更新日期")
private Date updateTime;
/**手机号*/
@Excel(name = "手机号", width = 15)
@Schema(description = "手机号")
private String phone;
/**用户昵称*/
@Excel(name = "用户昵称", width = 15)
@Schema(description = "用户昵称")
private String nickname;
/**性别*/
@Excel(name = "性别", width = 15)
@Schema(description = "性别")
private Integer sex;
/**微信id*/
@Excel(name = "微信id", width = 15)
@Schema(description = "微信id")
private String openid;
/**城市*/
@Excel(name = "城市", width = 15)
@Schema(description = "城市")
private String city;
/**经度*/
@Excel(name = "经度", width = 15)
@Schema(description = "经度")
private String log;
/**纬度*/
@Excel(name = "纬度", width = 15)
@Schema(description = "纬度")
private String lat;
/**算法配置*/
@Excel(name = "算法配置", width = 15, dictTable = "computed", dicText = "name", dicCode = "id")
@Dict(dictTable = "computed", dicText = "name", dicCode = "id")
@Schema(description = "算法配置")
private String computedids;
@Excel(name = "系统用户Id", width = 15)
@Schema(description = "系统用户Id")
private String sysUserId;
}

@ -0,0 +1,17 @@
package org.jeecg.modules.weixn.mapper;
import org.jeecg.modules.weixn.entity.WeixinUser;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @Description:
* @Author: jeecg-boot
* @Date: 2025-04-04
* @Version: V1.0
*/
public interface WeixinUserMapper extends BaseMapper<WeixinUser> {
WeixinUser getByPhone(String phoneNumber);
int updateSysUserIdByPhone(String phone);
}

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.jeecg.modules.weixn.mapper.WeixinUserMapper">
<select id="getByPhone" resultType="org.jeecg.modules.weixn.entity.WeixinUser">
SELECT *
FROM weixin_user
WHERE phone = #{phone}
</select>
<!-- 插入微信用户 -->
<insert id="insert" parameterType="org.jeecg.modules.weixn.entity.WeixinUser">
INSERT INTO weixin_user (id, phone, nickname, openid, sex, computedids, sys_user_id)
VALUES (
#{id},
#{phone},
#{nickname},
#{openid},
#{sex},
#{computedids},
<!-- 动态插入 sys_user_id根据手机号查询 sys_user 表 -->
(SELECT id FROM sys_user WHERE phone = #{phone} LIMIT 1)
)
</insert>
<!-- 根据手机号更新 sys_user_id -->
<update id="updateSysUserIdByPhone" >
UPDATE weixin_user
SET sys_user_id = (
SELECT id
FROM sys_user
WHERE phone = #{phone}
LIMIT 1 -- 确保唯一性
)
WHERE phone = #{phone}
</update>
</mapper>

@ -0,0 +1,9 @@
package org.jeecg.modules.weixn.service;
import org.jeecg.modules.weixn.entity.CalcDataBaseDto;
import org.jeecg.modules.weixn.entity.IComputedTypes;
public interface ICalcService {
String calc(CalcDataBaseDto data, String phone, String computedTypes);
}

@ -0,0 +1,19 @@
package org.jeecg.modules.weixn.service;
import com.alibaba.fastjson.JSONObject;
import org.jeecg.modules.weixn.entity.WeixinUser;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @Description:
* @Author: jeecg-boot
* @Date: 2025-04-04
* @Version: V1.0
*/
public interface IWeixinUserService extends IService<WeixinUser> {
WeixinUser getByPhone(String phoneNumber);
JSONObject wxLogin(String phone);
}

@ -0,0 +1,148 @@
package org.jeecg.modules.weixn.service.impl;
import org.jeecg.modules.weixn.service.ICalcService;
import org.jeecg.modules.weixn.config.ExternalApiKeysConfig;
import org.jeecg.modules.weixn.entity.*;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired; // 用于注入
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert; // 用于断言检查
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import com.alibaba.fastjson.JSONObject;
import java.time.Duration;
@Service
public class CalcService implements ICalcService {
@Value("${spring.calculation.api.requestUrl:http://aiprice.orgcoder.com/v1/workflow/run}")
private String requestUrl;
// --- 注入 API Key 配置 ---
private final ExternalApiKeysConfig apiKeysConfig;
private final RestTemplate restTemplate = new RestTemplateBuilder()
.setConnectTimeout(Duration.ofSeconds(10)) // Keep connect timeout reasonable
.setReadTimeout(Duration.ofSeconds(60))// !! Set long read timeout here (e.g., 60 seconds) !!;
.build();
// 使用构造函数注入配置类
@Autowired
private CalcService(ExternalApiKeysConfig apiKeysConfig) {
this.apiKeysConfig = apiKeysConfig;
}
// ---
/**
* ( apiKey )
*/
private ExternalApiResponse executeAndMapRequest(CalcDataBaseDto dto, String phone, String apiKey) {
Assert.hasText(apiKey, "API Key 不能为空"); // 增加校验
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(apiKey); // <--- 使用传入的 apiKey
// ... (构建请求体逻辑不变)
JSONObject requestBodyJson = new JSONObject();
requestBodyJson.put("inputs", dto);
requestBodyJson.put("response_mode", "blocking");
requestBodyJson.put("user", phone);
HttpEntity<String> requestEntity = new HttpEntity<>(requestBodyJson.toJSONString(), headers);
try {
ResponseEntity<ExternalApiResponse> responseEntity = restTemplate.postForEntity(
requestUrl,
requestEntity,
ExternalApiResponse.class
);
// ... (处理响应逻辑不变)
if (responseEntity.getStatusCode().is2xxSuccessful() && responseEntity.getBody() != null) {
return responseEntity.getBody();
} else {
throw new RuntimeException("外部 API 请求失败或响应为空HTTP 状态码: " + responseEntity.getStatusCode());
}
} catch (HttpClientErrorException e) {
throw new RuntimeException("外部 API 请求客户端错误: " + e.getStatusCode() + ", " + e.getResponseBodyAsString());
} catch (RestClientException e) {
throw new RuntimeException("调用外部 API 时发生错误: " + e.getMessage());
}
}
/**
* text ()
*/
@NotNull
@Contract("null -> fail")
private String extractTextFromApiResponse(ExternalApiResponse response) {
ResponseOutputs outputs = getResponseOutputs(response);
if (outputs == null) { // 2. 检查 outputs 对象是否为 null
throw new RuntimeException("响应格式错误:缺少 outputs 字段");
}
String text = outputs.getText(); // 3. 如果 outputs 不是 null再获取 text
if (text == null) { // 4. 检查 text 是否为 null
// 根据你的业务需求,如果 text 可以为空字符串,这里可能不需要抛异常
// 如果 text 必须有值,则抛异常
throw new RuntimeException("响应格式错误:缺少 text 字段");
}
// --- 检查结束 ---
return text; // 如果代码能执行到这里,说明 text 不是 null
}
private static ResponseOutputs getResponseOutputs(ExternalApiResponse response) {
if (response == null) {
throw new RuntimeException("响应格式错误:响应体为空");
}
if (response.getData() == null) {
throw new RuntimeException("响应格式错误:缺少 data 部分");
}
ResponseData data = response.getData();
if (!"succeeded".equalsIgnoreCase(data.getStatus())) {
throw new RuntimeException("外部 API 任务执行失败: " + (data.getError() != null ? data.getError() : "状态为 " + data.getStatus()));
}
// --- 正确的检查逻辑 ---
return data.getOutputs();
}
@Override
public String calc(CalcDataBaseDto calcData, String phone, String computedTypes) {
String apiKey = getApiKeyForType(String.valueOf(computedTypes)); // 使用辅助方法获取 Key
try {
ExternalApiResponse response = executeAndMapRequest(calcData, phone, apiKey); // 传递 apiKey
return extractTextFromApiResponse(response);
} catch (Exception e) {
throw new RuntimeException("碳源计算请求处理失败: " + e.getMessage(), e);
}
}
// --- 添加一个辅助方法来获取 API Key并处理 Key 不存在的情况 ---
@NotNull
private String getApiKeyForType(String typeIdentifier) {
String apiKey = apiKeysConfig.getCalculation().get(typeIdentifier);
if (apiKey == null || apiKey.trim().isEmpty()) {
throw new IllegalArgumentException("缺少类型 '" + typeIdentifier + "' 的 API Key 配置");
}
return apiKey;
}
}

@ -0,0 +1,148 @@
package org.jeecg.modules.weixn.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.util.PasswordUtil;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.calc.entity.Calc;
import org.jeecg.modules.calc.service.ICalcService;
import org.jeecg.modules.system.entity.SysUser;
import org.jeecg.modules.system.service.ISysUserService;
import org.jeecg.modules.weixn.entity.WeixinUser;
import org.jeecg.modules.weixn.mapper.WeixinUserMapper;
import org.jeecg.modules.weixn.service.IWeixinUserService;
import org.jeecg.modules.weixn.vo.WeChatUserData;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.util.ArrayList;
import java.util.List;
/**
* @Description:
* @Author: jeecg-boot
* @Date: 2025-04-04
* @Version: V1.0
*/
@Service
public class WeixinUserServiceImpl extends ServiceImpl<WeixinUserMapper, WeixinUser> implements IWeixinUserService {
@Autowired
private ICalcService calcService;
@Autowired
private ISysUserService sysUserService;
@Autowired
private RedisUtil redisUtil;
@Override
public WeixinUser getByPhone(String phoneNumber) {
return baseMapper.getByPhone(phoneNumber);
}
@NotNull
private WeixinUser create(String phoneNumber) {
WeixinUser weixinUser = new WeixinUser();
weixinUser.setPhone(phoneNumber);
weixinUser.setNickname(null);
weixinUser.setOpenid(null);
weixinUser.setSex(null);
List<Calc> calcList = calcService.list();
// 计算器列表
StringBuilder computedIds = new StringBuilder();
for (Calc calc : calcList) {
computedIds.append(calc.getId()).append(",");
}
if (!computedIds.isEmpty()) {
computedIds.deleteCharAt(computedIds.length() - 1); // 删除最后一个逗号
}
weixinUser.setComputedids(computedIds.toString());
return weixinUser;
}
@NotNull
private SysUser createSysUser(String phoneNumber) {
SysUser sysUser = new SysUser();
sysUser.setUsername(phoneNumber);
sysUser.setPhone(phoneNumber);
String salt = oConvertUtils.randomGen(8);
sysUser.setSalt(salt);
String defaultPassword = "123456QWR!@#LZAQWEasdf";
String passwordEncode = PasswordUtil.encrypt(sysUser.getUsername(), defaultPassword, salt);
sysUser.setPassword(passwordEncode);
sysUser.setRealname("小程序用户");
sysUser.setStatus(1);
sysUser.setOrgCode(null);
sysUser.setDelFlag(0);
sysUser.setActivitiSync(1);
sysUser.setUserIdentity(1);
return sysUser;
}
@Override
public JSONObject wxLogin(String phone) {
JSONObject jsonObject = new JSONObject();
WeixinUser weixinUser = this.getByPhone(phone);
if (weixinUser == null) {
// 新建用户
SysUser newSysUser = this.createSysUser(phone);
// 设置角色和部门
sysUserService.saveUser(newSysUser, "ee8626f80f7c2619917b6236f3a7f02b", "", "");
WeixinUser newUser = this.create(phone);
this.save(newUser);
weixinUser = this.getByPhone(phone);
}
ArrayList<Calc> userByCalc = new ArrayList<>();
for (String computedId : weixinUser.getComputedids().split(",")) {
Calc calc = calcService.getById(computedId);
userByCalc.add(calc);
}
ArrayList<String> computedCodes = new ArrayList<>();
for (Calc calc : userByCalc) {
computedCodes.add(calc.getComputedCode());
}
WeChatUserData weChatUserData = new WeChatUserData();
weChatUserData.setPhoneNumber(phone);
weChatUserData.setComputedCodes(computedCodes);
SysUser sysUser = sysUserService.getById(weixinUser.getSysUserId());
String username = sysUser.getUsername();
String syspassword = sysUser.getPassword();
//1.生成token
String token = JwtUtil.sign(username, syspassword);
// 设置token缓存有效时间
redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token);
redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME * 2 / 1000);
jsonObject.put("weixinUserData", weChatUserData);
jsonObject.put("token", token);
return jsonObject;
}
// 插入用户后自动关联 sys_user_id
@Override
public boolean save(WeixinUser entity) {
// 1. 插入微信用户insert语句已包含sys_user_id的查询
int insertResult = baseMapper.insert(entity);
// 2. 保险操作:再次更新 sys_user_id防止插入时未查到
if (insertResult > 0) {
baseMapper.updateSysUserIdByPhone(entity.getPhone());
}
return insertResult > 0;
}
}

@ -0,0 +1,14 @@
package org.jeecg.modules.weixn.vo;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.ArrayList;
@Getter
@Setter
public class WeChatUserData implements Serializable {
private String phoneNumber;
private ArrayList<String> computedCodes;
}

@ -45,7 +45,7 @@ import java.util.List;
* @Version:V2.0
*/
@Slf4j
@Tag(name = "单表DEMO")
@Tag(name = "单表DEMO2")
@RestController
@RequestMapping("/test/jeecgDemo")
public class JeecgDemoController extends JeecgController<JeecgDemo, IJeecgDemoService> {

@ -0,0 +1,54 @@
server {
listen 443 ssl;
server_name factory.orgcoder.com;
# 设置最大上传大小为10MB
client_max_body_size 10M;
# SSL 证书路径
ssl_certificate /server/factory/factory.orgcoder.com.pem;
ssl_certificate_key /server/factory/factory.orgcoder.com.key;
location / {
root /server/factory/dist; # 或者您的默认根目录
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
#前端
location /api/ {
proxy_pass http://localhost:8123/jeecg-boot/; # 转发到后端服务
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 连接后端服务器的超时时间(默认 60秒
proxy_connect_timeout 300;
# 向后端发送请求的超时时间(默认 60秒
proxy_send_timeout 300;
# 从后端读取响应的超时时间(默认 60秒这是关键参数
proxy_read_timeout 300;
}
#后端
location /jeecg-boot/ {
proxy_pass http://localhost:8123/jeecg-boot/; # 转发到后端服务
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 连接后端服务器的超时时间(默认 60秒
proxy_connect_timeout 300;
# 向后端发送请求的超时时间(默认 60秒
proxy_send_timeout 300;
# 从后端读取响应的超时时间(默认 60秒这是关键参数
proxy_read_timeout 300;
}
}

@ -24,7 +24,13 @@
<artifactId>jeecg-module-demo</artifactId>
<version>${jeecgboot.version}</version>
</dependency>
<!-- 计算器项目模块 -->
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-module-computed</artifactId>
<version>${jeecgboot.version}</version>
</dependency>
<!-- flyway 数据库自动升级 -->
<dependency>
<groupId>org.flywaydb</groupId>

@ -7,6 +7,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.env.Environment;
import java.net.InetAddress;

@ -1,5 +1,5 @@
server:
port: 8080
port: 8123
undertow:
# max-http-post-size: 10MB # 平替 tomcat server.tomcat.max-swallow-siz undertow该值默认为-1
worker-threads: 16 # 4核CPU标准配置
@ -149,7 +149,7 @@ spring:
slow-sql-millis: 5000
datasource:
master:
url: jdbc:mysql://127.0.0.1:3306/jeecg-boot?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
url: jdbc:mysql://127.0.0.1:3308/jeecg-boot?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
@ -163,7 +163,7 @@ spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
port: 6378
password:
#mybatis plus 设置
mybatis-plus:
@ -189,7 +189,7 @@ jeecg:
ai-chat:
enabled: true
model: deepseek-chat
apiKey: ??
apiKey: sk-aff64e37bcc7412ba648a093c8c321cb
apiHost: https://api.deepseek.com
timeout: 60
# 平台上线安全配置

@ -2,4 +2,22 @@ spring:
application:
name: jeecg-system
profiles:
active: '@profile.name@'
active: '@profile.name@'
calculation:
api:
requestUrl: http://aiprice.orgcoder.com/v1/workflows/run
keys:
calculation:
# 碳源
TAN_YUAN: app-FMYftrMR2YpkHvszHjeN4byA
# 沼气
ZHAO_QI: app-1g43UYH246H2iiOWLhGt36Qo
# 产渣率
CHAN_ZHA_LV: app-3e5IegYWfIIHYIGcRjNc2MmT
# 厌氧有机负荷
YAN_YANG_JI_FU_HE: app-quUgZTamWh2DDQomcTyZWek4
# 污水碳源
WU_SHUI_TANYUAN: app-ZM1L5tm0LnSaHJ9tRF2JXJGe
# 厌氧罐气率
YAN_YANG_GUAN_QI_LV: app-aLIGiBuUgj7yxUIlGUDgfn7v

@ -27,3 +27,5 @@ page_search_filed_num=1
#page_filter_fields
page_filter_fields=create_time,create_by,update_time,update_by
exclude_table=act_,ext_act_,design_,onl_,sys_,qrtz_

@ -17,5 +17,4 @@
<module>jeecg-system-biz</module>
<module>jeecg-system-start</module>
</modules>
</project>

@ -86,7 +86,8 @@
<module>jeecg-boot-base-core</module>
<module>jeecg-module-demo</module>
<module>jeecg-module-system</module>
</modules>
<module>jeecg-module-computed</module>
</modules>
<repositories>
<repository>

@ -0,0 +1,14 @@
# 这是一个Python示例,实际使用时根据你的后端语言进行调整
import requests
def get_phone_number(code):
url = 'https://api.weixin.qq.com/wxa/business/getuserphonenumber'
# 通过code换取手机号
data = {
'code': code,
'access_token': '你的access_token'
}
response = requests.post(url, json=data)
return response.json()

@ -1,19 +0,0 @@
root = true
[*]
charset=utf-8
end_of_line=lf
insert_final_newline=true
indent_style=space
indent_size=2
max_line_length = 100
[*.{yml,yaml,json}]
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab

@ -1,22 +0,0 @@
# port
VITE_PORT = 3100
# 网站标题
VITE_GLOB_APP_TITLE = JeecgBoot 企业级低代码平台
# 简称,此变量只能是字符/下划线
VITE_GLOB_APP_SHORT_NAME = JeecgBoot_Pro
# 单点登录服务端地址
VITE_GLOB_APP_CAS_BASE_URL=http://cas.test.com:8443/cas
# 是否开启单点登录
VITE_GLOB_APP_OPEN_SSO = false
# 开启微前端模式
VITE_GLOB_APP_OPEN_QIANKUN=true
# 文件预览地址
VITE_GLOB_ONLINE_VIEW_URL=http://fileview.jeecg.com/onlinePreview

@ -1,30 +0,0 @@
# 是否打开mock
VITE_USE_MOCK = true
# 发布路径
VITE_PUBLIC_PATH = /
# 跨域代理,您可以配置多个 ,请注意,没有换行符
VITE_PROXY = [["/jeecgboot","http://localhost:8080/jeecg-boot"],["/upload","http://localhost:3300/upload"]]
#后台接口全路径地址(必填)
VITE_GLOB_DOMAIN_URL=http://localhost:8080/jeecg-boot
#后台接口父地址(必填)
VITE_GLOB_API_URL=/jeecgboot
# 接口前缀
VITE_GLOB_API_URL_PREFIX=
#微前端qiankun应用,命名必须以VITE_APP_SUB_开头,jeecg-app-1为子应用的项目名称,也是子应用的路由父路径
VITE_APP_SUB_jeecg-app-1 = '//localhost:8092'
# 填写后将作为乾坤子应用启动主应用注册时AppName需保持一致放开 VITE_GLOB_QIANKUN_MICRO_APP_NAME 参数表示jeecg-vue3将以乾坤子应用模式启动
#VITE_GLOB_QIANKUN_MICRO_APP_NAME=jeecg-vue3
# 作为乾坤子应用启动时必填需与qiankun主应用注册子应用时填写的 entry 保持一致
#VITE_GLOB_QIANKUN_MICRO_APP_ENTRY=//localhost:3001/jeecg-vue3
# 全局隐藏哪些布局。可选属性sider,header,multi-tabs多个用逗号隔开
#VITE_GLOB_HIDE_LAYOUT_TYPES=sider,header,multi-tabs

@ -1,31 +0,0 @@
# 是否启用mock
VITE_USE_MOCK = false
# 发布路径
VITE_PUBLIC_PATH = /
# 是否启用gzip或brotli压缩
# 选项值: gzip | brotli | none
# 如果需要多个可以使用“,”分隔
VITE_BUILD_COMPRESS = 'gzip'
# 使用压缩时是否删除原始文件默认为false
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
#后台接口父地址(必填)
VITE_GLOB_API_URL=/jeecgboot
#后台接口全路径地址(必填)
VITE_GLOB_DOMAIN_URL=http://jeecg-boot-system:8080/jeecg-boot
# 接口父路径前缀
VITE_GLOB_API_URL_PREFIX=
# 填写后将作为乾坤子应用启动主应用注册时AppName需保持一致放开 VITE_GLOB_QIANKUN_MICRO_APP_NAME 参数表示jeecg-vue3将以乾坤子应用模式启动
#VITE_GLOB_QIANKUN_MICRO_APP_NAME=jeecg-vue3
# 作为乾坤子应用启动时必填需与qiankun主应用注册子应用时填写的 entry 保持一致
#VITE_GLOB_QIANKUN_MICRO_APP_ENTRY=//qiankun.boot3.jeecg.com/jeecg-vue3
# 全局隐藏哪些布局。可选属性sider,header,multi-tabs多个用逗号隔开
#VITE_GLOB_HIDE_LAYOUT_TYPES=sider,header,multi-tabs

@ -1,15 +0,0 @@
*.sh
node_modules
*.md
*.woff
*.ttf
.vscode
.idea
dist
/public
/docs
.husky
.local
/bin
Dockerfile

@ -1,78 +0,0 @@
// @ts-check
const { defineConfig } = require('eslint-define-config');
module.exports = defineConfig({
root: true,
env: {
browser: true,
node: true,
es6: true,
},
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
ecmaVersion: 2020,
sourceType: 'module',
jsxPragma: 'React',
ecmaFeatures: {
jsx: true,
},
},
extends: [
'plugin:vue/vue3-recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
'plugin:prettier/recommended',
'plugin:jest/recommended',
],
rules: {
'vue/script-setup-uses-vars': 'error',
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-empty-function': 'off',
'vue/custom-event-name-casing': 'off',
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'space-before-function-paren': 'off',
'vue/attributes-order': 'off',
'vue/one-component-per-file': 'off',
'vue/html-closing-bracket-newline': 'off',
'vue/max-attributes-per-line': 'off',
'vue/multiline-html-element-content-newline': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/attribute-hyphenation': 'off',
'vue/require-default-prop': 'off',
'vue/html-self-closing': [
'error',
{
html: {
void: 'always',
normal: 'never',
component: 'always',
},
svg: 'always',
math: 'always',
},
],
},
});

@ -1,36 +0,0 @@
node_modules
.DS_Store
.github
dist
.npmrc
.cache
tests/server/static
tests/server/static/upload
.local
# local env files
.env.local
.env.*.local
.eslintcache
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.svn
# .vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
/os_del.cmd
os_del.cmd
/.vscode/
/.history/
/svn clear.bat

@ -1,6 +0,0 @@
ports:
- port: 3344
onOpen: open-preview
tasks:
- init: yarn
command: yarn dev

@ -1,9 +0,0 @@
/dist/*
.local
.output.js
/node_modules/**
**/*.svg
**/*.sh
/public/*

@ -1,3 +0,0 @@
/dist/*
/public/*
public/*

@ -1,48 +0,0 @@
# test directories
__tests__
test
tests
powered-test
# asset directories
docs
doc
website
images
assets
# examples
example
examples
# code coverage directories
coverage
.nyc_output
# build scripts
Makefile
Gulpfile.js
Gruntfile.js
# configs
appveyor.yml
circle.yml
codeship-services.yml
codeship-steps.yml
wercker.yml
.tern-project
.gitattributes
.editorconfig
.*ignore
.eslintrc
.jshintrc
.flowconfig
.documentup.json
.yarn-metadata.json
.travis.yml
# misc
*.md
!istanbul-reports/lib/html/assets
!istanbul-api/node_modules/istanbul-reports/lib/html/assets

@ -1,30 +0,0 @@
FROM registry.cn-hangzhou.aliyuncs.com/dockerhub_mirror/nginx
MAINTAINER jeecgos@163.com
VOLUME /tmp
ENV LANG en_US.UTF-8
RUN echo "server { \
listen 80; \
location /jeecgboot/ { \
proxy_pass http://jeecg-boot-system:8080/jeecg-boot/; \
proxy_redirect off; \
proxy_set_header Host jeecg-boot-system; \
proxy_set_header X-Real-IP \$remote_addr; \
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; \
} \
#解决Router(mode: 'history')模式下,刷新路由地址不能找到页面的问题 \
location / { \
root /var/www/html/; \
index index.html index.htm; \
if (!-e \$request_filename) { \
rewrite ^(.*)\$ /index.html?s=\$1 last; \
break; \
} \
} \
access_log /var/log/nginx/access.log ; \
} " > /etc/nginx/conf.d/default.conf \
&& mkdir -p /var/www \
&& mkdir -p /var/www/html
ADD dist/ /var/www/html/
EXPOSE 80
EXPOSE 443

@ -1,33 +0,0 @@
MIT License
Copyright (c) 2020-present, Jeecg
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
In any case, you must not make any such use of this software as to develop software which may be considered competitive with this software.
JeecgBoot 是由 北京国炬信息技术有限公司 发行的软件。 总部位于北京地址中国·北京·朝阳区科荟前街1号院奥林佳泰大厦。邮箱jeecgos@163.com
本软件受适用的国家软件著作权法(包括国际条约)和开源协议 双重保护许可。
开源协议中文释意如下:
1.JeecgBoot开源版本无任何限制在遵循本开源协议条款下允许商用使用不会造成侵权行为。
2.允许基于本平台软件开展业务系统开发。
3.在任何情况下,您不得使用本软件开发可能被认为与本软件竞争的软件。
最终解释权归http://www.jeecg.com

@ -1,106 +0,0 @@
JeecgBoot 企业级低代码开发平台
===============
当前最新版本: 3.7.4发布时间2025-04-10
[![AUR](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
[![](https://img.shields.io/badge/Author-北京国炬软件-orange.svg)](http://jeecg.com/aboutusIndex)
[![](https://img.shields.io/badge/version-3.7.4-brightgreen.svg)](https://github.com
/zhangdaiscott/jeecg-boot)
[![GitHub stars](https://img.shields.io/github/stars/zhangdaiscott/jeecg-boot.svg?style=social&label=Stars)](https://github.com/zhangdaiscott/jeecg-boot)
[![GitHub forks](https://img.shields.io/github/forks/zhangdaiscott/jeecg-boot.svg?style=social&label=Fork)](https://github.com/zhangdaiscott/jeecg-boot)
## 简介
JeecgBoot-Vue3采用 Vue3.0、Vite、 Ant-Design-Vue4、TypeScript 等新技术方案包括二次封装组件、utils、hooks、动态菜单、权限校验、按钮级别权限控制等功能。
> 强大的代码生成器让前后端代码一键生成! JeecgBoot引领低代码开发模式(OnlineCoding-> 代码生成-> 手工MERGE) 帮助解决Java项目70%的重复工作,让开发更多关注业务。既能快速提高效率,节省成本,同时又不失灵活性
## 开发环境搭建
- [前端开发环境准备](https://help.jeecg.com/setup/dev)
- [前端项目快速启动](https://help.jeecg.com/setup/startup)
- [通过IDEA启动项目](https://help.jeecg.com/java/setup/idea/startup)
## 技术文档
- 官方文档:[https://help.jeecg.com](https://help.jeecg.com)
- 快速入门:[快速入门](http://jeecg.com/doc/quickstart) | [常见问题](http://help.jeecg.com/qa)
- QQ交流群⑩716488839、⑨808791225、其他满
- 在线演示 [系统演示](http://boot3.jeecg.com) | [APP演示](http://jeecg.com/appIndex)
> 演示系统的登录账号密码,请点击 [获取账号密码](http://jeecg.com/doc/demo) 获取
## 安装与使用
* 本地环境安装 `Node.js 、npm 、pnpm`
* Node.js 版本建议`v20.15.0`,要求`Node 20+` 版本以上
` ( 因为Vite5 不再支持已 EOL 的 Node.js 14 / 16 / 17 / 19现在需要 Node.js 18 / 20+ )`
- Get the project code
```bash
git clone https://github.com/jeecgboot/JeecgBoot.git
```
- Installation dependencies
```bash
cd JeecgBoot/jeecgboot-vue3
pnpm install
```
- 配置接口地址 `.env.development`
```bash
VITE_PROXY = [["/jeecgboot","http://localhost:8080/jeecg-boot"],["/upload","http://localhost:3300/upload"]]
VITE_GLOB_DOMAIN_URL=http://localhost:8080/jeecg-boot
```
> 说明:把`http://localhost:8080/jeecg-boot` 换成自己地址,其他不用改。
- run
```bash
pnpm dev
```
- build
```bash
pnpm build
```
## 入门必备
本项目需要一定前端基础知识,请确保掌握 Vue 的基础知识,以便能处理一些常见的问题。 建议在开发前先学一下以下内容,提前了解和学习这些知识,会对项目理解非常有帮助:
* [JeecgBoot文档](http://help.jeecg.com)
* [Vue3 文档](https://cn.vuejs.org/)
* [Vben文档](https://doc.vvbin.cn)
* [Ant-Design-Vue](https://www.antdv.com/docs/vue/introduce-cn/)
* [TypeScript](https://www.typescriptlang.org/)
* [Vue-router](https://router.vuejs.org/zh)
* [Es6](https://es6.ruanyifeng.com/)
* [Vitejs](https://cn.vitejs.dev/guide/)
* [Pinia(vuex替代方案)](https://pinia.esm.dev/introduction.html)
* [Vue-RFCS](https://github.com/vuejs/rfcs)
* [vxetable文档](https://vxetable.cn)
## 浏览器支持
**本地开发**推荐使用`Chrome 最新版`浏览器,**不支持**`Chrome 90`以下版本。
**生产环境**支持现代浏览器,不支持 IE。
| [![IE](https://raw.githubusercontent.com/alrra/browser-logos/master/src/archive/internet-explorer_9-11/internet-explorer_9-11_48x48.png)](http://godban.github.io/browsers-support-badges/)IE | [![ Edge](https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png)](http://godban.github.io/browsers-support-badges/)Edge | [![Firefox](https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png)](http://godban.github.io/browsers-support-badges/)Firefox | [![Chrome](https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png)](http://godban.github.io/browsers-support-badges/)Chrome | [![Safari](https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png)](http://godban.github.io/browsers-support-badges/)Safari |
| --- | --- | --- | --- | --- |
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |

@ -1,79 +0,0 @@
import { generate } from '@ant-design/colors';
export const primaryColor = '#1890FF';
export const darkMode = 'light';
type Fn = (...arg: any) => any;
type GenerateTheme = 'default' | 'dark';
export interface GenerateColorsParams {
mixLighten: Fn;
mixDarken: Fn;
tinycolor: any;
color?: string;
}
export function generateAntColors(color: string, theme: GenerateTheme = 'default') {
return generate(color, {
theme,
});
}
export function getThemeColors(color?: string) {
const tc = color || primaryColor;
const lightColors = generateAntColors(tc);
const primary = lightColors[5];
const modeColors = generateAntColors(primary, 'dark');
return [...lightColors, ...modeColors];
}
export function generateColors({
color = primaryColor,
mixLighten,
mixDarken,
tinycolor,
}: GenerateColorsParams) {
const arr = new Array(19).fill(0);
const lightens = arr.map((_t, i) => {
return mixLighten(color, i / 5);
});
const darkens = arr.map((_t, i) => {
return mixDarken(color, i / 5);
});
const alphaColors = arr.map((_t, i) => {
return tinycolor(color)
.setAlpha(i / 20)
.toRgbString();
});
const shortAlphaColors = alphaColors.map((item) => item.replace(/\s/g, '').replace(/0\./g, '.'));
const tinycolorLightens = arr
.map((_t, i) => {
return tinycolor(color)
.lighten(i * 5)
.toHexString();
})
.filter((item) => item !== '#ffffff');
const tinycolorDarkens = arr
.map((_t, i) => {
return tinycolor(color)
.darken(i * 5)
.toHexString();
})
.filter((item) => item !== '#000000');
return [
...lightens,
...darkens,
...alphaColors,
...shortAlphaColors,
...tinycolorDarkens,
...tinycolorLightens,
].filter((item) => !item.includes('-'));
}

@ -1,6 +0,0 @@
/**
* The name of the configuration file entered in the production environment
*/
export const GLOB_CONFIG_FILE_NAME = '_app.config.js';
export const OUTPUT_DIR = 'dist';

@ -1,49 +0,0 @@
import { primaryColor } from '../config/themeConfig';
// import { getThemeVariables } from 'ant-design-vue/dist/theme';
import { resolve } from 'path';
import { generate } from '@ant-design/colors';
import { theme } from 'ant-design-vue/lib';
import convertLegacyToken from 'ant-design-vue/lib/theme/convertLegacyToken';
const { defaultAlgorithm, defaultSeed } = theme;
function generateAntColors(color: string, theme: 'default' | 'dark' = 'default') {
return generate(color, {
theme,
});
}
/**
* less global variable
*/
export function generateModifyVars() {
const palettes = generateAntColors(primaryColor);
const primary = palettes[5];
const primaryColorObj: Record<string, string> = {};
for (let index = 0; index < 10; index++) {
primaryColorObj[`primary-${index + 1}`] = palettes[index];
}
const mapToken = defaultAlgorithm(defaultSeed);
const v3Token = convertLegacyToken(mapToken);
return {
...v3Token,
// ...modifyVars,
// Used for global import to avoid the need to import each style file separately
// reference: Avoid repeated references
hack: `true; @import (reference) "${resolve('src/design/config.less')}";`,
'primary-color': primary,
...primaryColorObj,
'info-color': primary,
'processing-color': primary,
'success-color': '#55D187', // Success color
'error-color': '#ED6F6F', // False color
'warning-color': '#EFBD47', // Warning color
//'border-color-base': '#EEEEEE',
'font-size-base': '14px', // Main font size
'border-radius-base': '2px', // Component/float fillet
'link-color': primary, // Link color
'app-content-background': '#fafafa', // Link color
};
}

@ -1,68 +0,0 @@
import path from 'path';
import fs from 'fs-extra';
import inquirer from 'inquirer';
import colors from 'picocolors';
import pkg from '../../../package.json';
async function generateIcon() {
const dir = path.resolve(process.cwd(), 'node_modules/@iconify/json');
const raw = await fs.readJSON(path.join(dir, 'collections.json'));
const collections = Object.entries(raw).map(([id, v]) => ({
...(v as any),
id,
}));
const choices = collections.map((item) => ({ key: item.id, value: item.id, name: item.name }));
inquirer
.prompt([
{
type: 'list',
name: 'useType',
choices: [
{ key: 'local', value: 'local', name: 'Local' },
{ key: 'onLine', value: 'onLine', name: 'OnLine' },
],
message: 'How to use icons?',
},
{
type: 'list',
name: 'iconSet',
choices: choices,
message: 'Select the icon set that needs to be generated?',
},
{
type: 'input',
name: 'output',
message: 'Select the icon set that needs to be generated?',
default: 'src/components/Icon/data',
},
])
.then(async (answers) => {
const { iconSet, output, useType } = answers;
const outputDir = path.resolve(process.cwd(), output);
fs.ensureDir(outputDir);
const genCollections = collections.filter((item) => [iconSet].includes(item.id));
const prefixSet: string[] = [];
for (const info of genCollections) {
const data = await fs.readJSON(path.join(dir, 'json', `${info.id}.json`));
if (data) {
const { prefix } = data;
const isLocal = useType === 'local';
const icons = Object.keys(data.icons).map((item) => `${isLocal ? prefix + ':' : ''}${item}`);
await fs.writeFileSync(
path.join(output, `icons.data.ts`),
`export default ${isLocal ? JSON.stringify(icons) : JSON.stringify({ prefix, icons })}`
);
prefixSet.push(prefix);
}
}
fs.emptyDir(path.join(process.cwd(), 'node_modules/.vite'));
console.log(`${colors.cyan(`[${pkg.name}]`)}` + ' - Icon generated successfully:' + `[${prefixSet}]`);
});
}
generateIcon();

@ -1,7 +0,0 @@
/**
* Get the configuration file variable name
* @param env
*/
export const getConfigFileName = (env: Record<string, any>) => {
return `__PRODUCTION__${env.VITE_GLOB_APP_SHORT_NAME || '__APP'}__CONF__`.toUpperCase().replace(/\s/g, '');
};

@ -1,47 +0,0 @@
/**
* 生成外部配置文件用于生产发布后配置无需重新打包
*/
import { GLOB_CONFIG_FILE_NAME, OUTPUT_DIR } from '../constant';
import fs, { writeFileSync } from 'fs-extra';
import colors from 'picocolors';
import { getEnvConfig, getRootPath } from '../utils';
import { getConfigFileName } from '../getConfigFileName';
import pkg from '../../package.json';
interface CreateConfigParams {
configName: string;
config: any;
configFileName?: string;
}
function createConfig(params: CreateConfigParams) {
const { configName, config, configFileName } = params;
try {
const windowConf = `window.${configName}`;
// Ensure that the variable will not be modified
let configStr = `${windowConf}=${JSON.stringify(config)};`;
configStr += `
Object.freeze(${windowConf});
Object.defineProperty(window, "${configName}", {
configurable: false,
writable: false,
});
`.replace(/\s/g, '');
fs.mkdirp(getRootPath(OUTPUT_DIR));
writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr);
console.log(colors.cyan(`✨ [${pkg.name}]`) + ` - configuration file is build successfully:`);
console.log(colors.gray(OUTPUT_DIR + '/' + colors.green(configFileName)) + '\n');
} catch (error) {
console.log(colors.red('configuration file configuration file failed to package:\n' + error));
}
}
export function runBuildConfig() {
const config = getEnvConfig();
const configFileName = getConfigFileName(config);
createConfig({ config, configName: configFileName, configFileName: GLOB_CONFIG_FILE_NAME });
}

@ -1,23 +0,0 @@
// #!/usr/bin/env node
import { runBuildConfig } from './buildConf';
import colors from 'picocolors';
import pkg from '../../package.json';
export const runBuild = async () => {
try {
const argvList = process.argv.splice(2);
// Generate configuration file
if (!argvList.includes('disabled-config')) {
runBuildConfig();
}
console.log(`${colors.cyan(`[${pkg.name}]`)}` + ' - build successfully!');
} catch (error) {
console.log(colors.red('vite build error:\n' + error));
process.exit(1);
}
};
runBuild();

@ -1,94 +0,0 @@
import fs from 'fs';
import path from 'path';
import dotenv from 'dotenv';
export function isDevFn(mode: string): boolean {
return mode === 'development';
}
export function isProdFn(mode: string): boolean {
return mode === 'production';
}
/**
* Whether to generate package preview
*/
export function isReportMode(): boolean {
return process.env.REPORT === 'true';
}
// Read all environment variable configuration files to process.env
export function wrapperEnv(envConf: Recordable): ViteEnv {
const ret: any = {};
for (const envName of Object.keys(envConf)) {
let realName = envConf[envName].replace(/\\n/g, '\n');
realName = realName === 'true' ? true : realName === 'false' ? false : realName;
if (envName === 'VITE_PORT') {
realName = Number(realName);
}
if (envName === 'VITE_PROXY' && realName) {
try {
realName = JSON.parse(realName.replace(/'/g, '"'));
} catch (error) {
realName = '';
}
}
ret[envName] = realName;
if (typeof realName === 'string') {
process.env[envName] = realName;
} else if (typeof realName === 'object') {
process.env[envName] = JSON.stringify(realName);
}
}
return ret;
}
/**
* 获取当前环境下生效的配置文件名
*/
function getConfFiles() {
const script = process.env.npm_lifecycle_script;
// update-begin--author:liaozhiyang---date:20240326---forQQYUN-8690
const reg = new RegExp('NODE_ENV=([a-z_\\d]+)');
// update-end--author:liaozhiyang---date:20240326---forQQYUN-8690
const result = reg.exec(script as string) as any;
if (result) {
const mode = result[1] as string;
return ['.env', `.env.${mode}`];
}
return ['.env', '.env.production'];
}
/**
* Get the environment variables starting with the specified prefix
* @param match prefix
* @param confFiles ext
*/
export function getEnvConfig(match = 'VITE_GLOB_', confFiles = getConfFiles()) {
let envConfig = {};
confFiles.forEach((item) => {
try {
const env = dotenv.parse(fs.readFileSync(path.resolve(process.cwd(), item)));
envConfig = { ...envConfig, ...env };
} catch (e) {
console.error(`Error in parsing ${item}`, e);
}
});
const reg = new RegExp(`^(${match})`);
Object.keys(envConfig).forEach((key) => {
if (!reg.test(key)) {
Reflect.deleteProperty(envConfig, key);
}
});
return envConfig;
}
/**
* Get user root directory
* @param dir file path
*/
export function getRootPath(...dir: string[]) {
return path.resolve(process.cwd(), ...dir);
}

@ -1,36 +0,0 @@
/**
* Used to package and output gzip. Note that this does not work properly in Vite, the specific reason is still being investigated
* https://github.com/anncwb/vite-plugin-compression
*/
import type { PluginOption } from 'vite';
import compressPlugin from 'vite-plugin-compression';
export function configCompressPlugin(compress: 'gzip' | 'brotli' | 'none', deleteOriginFile = false): PluginOption | PluginOption[] {
const compressList = compress.split(',');
const plugins: PluginOption[] = [];
if (compressList.includes('gzip')) {
plugins.push(
compressPlugin({
verbose: true,
disable: false,
threshold: 10240,
algorithm: 'gzip',
ext: '.gz',
deleteOriginFile,
})
);
}
if (compressList.includes('brotli')) {
plugins.push(
compressPlugin({
ext: '.br',
algorithm: 'brotliCompress',
deleteOriginFile,
})
);
}
return plugins;
}

@ -1,45 +0,0 @@
/**
* Plugin to minimize and use ejs template syntax in index.html.
* https://github.com/anncwb/vite-plugin-html
*/
import type { PluginOption } from 'vite';
import { createHtmlPlugin } from 'vite-plugin-html';
import pkg from '../../../package.json';
import { GLOB_CONFIG_FILE_NAME } from '../../constant';
export function configHtmlPlugin(env: ViteEnv, isBuild: boolean, isQiankunMicro: boolean) {
const { VITE_GLOB_APP_TITLE, VITE_PUBLIC_PATH } = env;
const path = VITE_PUBLIC_PATH.endsWith('/') ? VITE_PUBLIC_PATH : `${VITE_PUBLIC_PATH}/`;
const getAppConfigSrc = () => {
return `${path || '/'}${GLOB_CONFIG_FILE_NAME}?v=${pkg.version}-${new Date().getTime()}`;
};
// JEECG
const {VITE_GLOB_QIANKUN_MICRO_APP_ENTRY} = env;
const basePublicPath = isQiankunMicro ? VITE_GLOB_QIANKUN_MICRO_APP_ENTRY : '';
const htmlPlugin: PluginOption[] = createHtmlPlugin({
minify: isBuild,
inject: {
// html
data: {
title: VITE_GLOB_APP_TITLE,
basePublicPath: basePublicPath,
},
// app.config.jshtml
tags: isBuild
? [
{
tag: 'script',
attrs: {
src: getAppConfigSrc(),
},
},
]
: [],
},
});
return htmlPlugin;
}

@ -1,35 +0,0 @@
//
// Image resource files used to compress the output of the production environment
// https://github.com/anncwb/vite-plugin-imagemin
import viteImagemin from 'vite-plugin-imagemin';
export function configImageminPlugin() {
const plugin = viteImagemin({
gifsicle: {
optimizationLevel: 7,
interlaced: false,
},
optipng: {
optimizationLevel: 7,
},
mozjpeg: {
quality: 20,
},
pngquant: {
quality: [0.8, 0.9],
speed: 4,
},
svgo: {
plugins: [
{
name: 'removeViewBox',
},
{
name: 'removeEmptyAttrs',
active: false,
},
],
},
});
return plugin;
}

@ -1,83 +0,0 @@
import { PluginOption } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import purgeIcons from 'vite-plugin-purge-icons';
import UnoCSS from 'unocss/vite';
import { presetTypography, presetUno } from 'unocss';
// https
import VitePluginCertificate from 'vite-plugin-mkcert';
//[issues/555]vscode
import vueSetupExtend from 'vite-plugin-vue-setup-extend-plus';
import { configHtmlPlugin } from './html';
import { configMockPlugin } from './mock';
import { configCompressPlugin } from './compress';
import { configVisualizerConfig } from './visualizer';
import { configThemePlugin } from './theme';
import { configSvgIconsPlugin } from './svgSprite';
import { configQiankunMicroPlugin } from './qiankunMicro';
// //(vite3)
// import OptimizationPersist from 'vite-plugin-optimize-persist';
// import PkgConfig from 'vite-plugin-package-config';
/**
*
* @param viteEnv
* @param isBuild
* @param isQiankunMicro 是否JEECG作为乾坤子应用
*/
export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, isQiankunMicro: boolean) {
const { VITE_USE_MOCK, VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE } = viteEnv;
const vitePlugins: (PluginOption | PluginOption[])[] = [
// have to
vue(),
// have to
vueJsx(),
// support name
vueSetupExtend(),
// @ts-ignore
VitePluginCertificate({
source: 'coding',
}),
];
vitePlugins.push(UnoCSS({ presets: [presetUno(), presetTypography()] }));
// vite-plugin-html
vitePlugins.push(configHtmlPlugin(viteEnv, isBuild, isQiankunMicro));
// vite-plugin-svg-icons
vitePlugins.push(configSvgIconsPlugin(isBuild));
// vite-plugin-mock
VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild));
// vite-plugin-purge-icons
vitePlugins.push(purgeIcons());
// rollup-plugin-visualizer
vitePlugins.push(configVisualizerConfig());
// vite-plugin-theme
vitePlugins.push(configThemePlugin(isBuild));
// JEECG
if (isQiankunMicro) {
// vite-plugin-qiankun
vitePlugins.push(...configQiankunMicroPlugin(viteEnv))
}
// The following plugins only work in the production environment
if (isBuild) {
// rollup-plugin-gzip
vitePlugins.push(configCompressPlugin(VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE));
}
// //vite-plugin-themevite
// vitePlugins.push(PkgConfig());
// vitePlugins.push(OptimizationPersist());
return vitePlugins;
}

@ -1,19 +0,0 @@
/**
* Mock plugin for development and production.
* https://github.com/anncwb/vite-plugin-mock
*/
import { viteMockServe } from 'vite-plugin-mock';
export function configMockPlugin(isBuild: boolean) {
return viteMockServe({
ignore: /^\_/,
mockPath: 'mock',
localEnabled: !isBuild,
prodEnabled: isBuild,
injectCode: `
import { setupProdMockServer } from '../mock/_createProductionServer';
setupProdMockServer();
`,
});
}

@ -1,16 +0,0 @@
import qiankun from 'vite-plugin-qiankun';
/**
* JEECG作为乾坤子应用Vite适配乾坤以子应用模式运行
* @param env
*/
export function configQiankunMicroPlugin(env: ViteEnv) {
const {VITE_GLOB_QIANKUN_MICRO_APP_NAME} = env
return [
qiankun(VITE_GLOB_QIANKUN_MICRO_APP_NAME!, {
useDevMode: true,
})
]
}

@ -1,82 +0,0 @@
/**
* 样式按需加载插件 主要处理antd的样式
* Introduces component library styles on demand.
* https://github.com/anncwb/vite-plugin-style-import
*/
import { createStyleImportPlugin } from 'vite-plugin-style-import';
export function configStyleImportPlugin(_isBuild: boolean) {
if (!_isBuild) {
return [];
}
const styleImportPlugin = createStyleImportPlugin({
libs: [
{
libraryName: 'ant-design-vue',
esModule: true,
resolveStyle: (name) => {
//
const ignoreList = [
'anchor-link',
'sub-menu',
'menu-item',
'menu-divider',
'menu-item-group',
'breadcrumb-item',
'breadcrumb-separator',
'form-item',
'step',
'select-option',
'select-opt-group',
'card-grid',
'card-meta',
'collapse-panel',
'descriptions-item',
'list-item',
'list-item-meta',
'table-column',
'table-column-group',
'tab-pane',
'tab-content',
'timeline-item',
'tree-node',
'skeleton-input',
'skeleton-avatar',
'skeleton-title',
'skeleton-paragraph',
'skeleton-image',
'skeleton-button',
];
//
//
const replaceList = {
'typography-text': 'typography',
'typography-title': 'typography',
'typography-paragraph': 'typography',
'typography-link': 'typography',
'dropdown-button': 'dropdown',
'input-password': 'input',
'input-search': 'input',
'input-group': 'input',
'radio-group': 'radio',
'checkbox-group': 'checkbox',
'layout-sider': 'layout',
'layout-content': 'layout',
'layout-footer': 'layout',
'layout-header': 'layout',
'month-picker': 'date-picker',
'range-picker': 'date-picker',
'image-preview-group': 'image',
};
return ignoreList.includes(name)
? ''
: replaceList.hasOwnProperty(name)
? `ant-design-vue/es/${replaceList[name]}/style/index`
: `ant-design-vue/es/${name}/style/index`;
},
},
],
});
return styleImportPlugin;
}

@ -1,17 +0,0 @@
/**
* Vite Plugin for fast creating SVG sprites.
* https://github.com/anncwb/vite-plugin-svg-icons
*/
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
import path from 'path';
export function configSvgIconsPlugin(isBuild: boolean) {
const svgIconsPlugin = createSvgIconsPlugin({
iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
svgoOptions: isBuild,
// default
symbolId: 'icon-[dir]-[name]',
});
return svgIconsPlugin;
}

@ -1,100 +0,0 @@
/**
* Vite plugin for website theme color switching
* https://github.com/anncwb/vite-plugin-theme
*/
import type { PluginOption } from 'vite';
import path from 'path';
import { viteThemePlugin, antdDarkThemePlugin, mixLighten, mixDarken, tinycolor } from '@rys-fe/vite-plugin-theme';
import { getThemeColors, generateColors } from '../../config/themeConfig';
import { generateModifyVars } from '../../generate/generateModifyVars';
export function configThemePlugin(isBuild: boolean): PluginOption[] {
const colors = generateColors({
mixDarken,
mixLighten,
tinycolor,
});
// update-begin------------------------
// https://github.com/vbenjs/vue-vben-admin/issues/1445
// viteThemePluginenforce
const vite_theme_plugin = viteThemePlugin({
resolveSelector: (s) => {
s = s.trim();
switch (s) {
case '.ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon':
return '.ant-steps-item-icon > .ant-steps-icon';
case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)':
case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover':
case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active':
return s;
case '.ant-steps-item-icon > .ant-steps-icon':
return s;
case '.ant-select-item-option-selected:not(.ant-select-item-option-disabled)':
return s;
default:
if (s.indexOf('.ant-btn') >= -1) {
// class
return s;
}
}
return s.startsWith('[data-theme') ? s : `[data-theme] ${s}`;
},
colorVariables: [...getThemeColors(), ...colors],
});
vite_theme_plugin.forEach(function (item) {
//vite:theme
if ('vite:theme' === item.name) {
// enforce: "post"vite 2.6.xapp-theme-styleasync transform(code, id) {code
if (isBuild) {
delete item.enforce;
}
}
});
// update-end------------------------
const plugin = [
vite_theme_plugin,
antdDarkThemePlugin({
preloadFiles: [
// path.resolve(process.cwd(), 'node_modules/ant-design-vue/dist/reset.css'),
//path.resolve(process.cwd(), 'node_modules/ant-design-vue/dist/antd.dark.less'),
path.resolve(process.cwd(), 'src/design/index.less'),
],
filter: (id) => (isBuild ? !id.endsWith('antd.less') : true),
// extractCss: false,
darkModifyVars: {
...generateModifyVars(true),
'text-color': '#c9d1d9',
'primary-1': 'rgb(255 255 255 / 8%)',
'text-color-base': '#c9d1d9',
'component-background': '#151515',
'heading-color': 'rgb(255 255 255 / 65%)',
// black: '#0e1117',
// #8b949e
'text-color-secondary': '#8b949e',
'border-color-base': '#303030',
'header-light-bottom-border-color': '#303030',
// 'border-color-split': '#30363d',
'item-active-bg': '#111b26',
'app-content-background': '#1e1e1e',
'tree-node-selected-bg': '#11263c',
'alert-success-border-color': '#274916',
'alert-success-bg-color': '#162312',
'alert-success-icon-color': '#49aa19',
'alert-info-border-color': '#153450',
'alert-info-bg-color': '#111b26',
'alert-info-icon-color': '#177ddc',
'alert-warning-border-color': '#594214',
'alert-warning-bg-color': '#2b2111',
'alert-warning-icon-color': '#d89614',
'alert-error-border-color': '#58181c',
'alert-error-bg-color': '#2a1215',
'alert-error-icon-color': '#a61d24',
},
}),
];
return plugin as unknown as PluginOption[];
}

@ -1,17 +0,0 @@
/**
* Package file volume analysis
*/
import visualizer from 'rollup-plugin-visualizer';
import { isReportMode } from '../../utils';
export function configVisualizerConfig() {
if (isReportMode()) {
return visualizer({
filename: './node_modules/.cache/visualizer/stats.html',
open: true,
gzipSize: true,
brotliSize: true,
}) as Plugin;
}
return [];
}

@ -1,34 +0,0 @@
/**
* Used to parse the .env.development proxy configuration
*/
import type { ProxyOptions } from 'vite';
type ProxyItem = [string, string];
type ProxyList = ProxyItem[];
type ProxyTargetList = Record<string, ProxyOptions>;
const httpsRE = /^https:\/\//;
/**
* Generate proxy
* @param list
*/
export function createProxy(list: ProxyList = []) {
const ret: ProxyTargetList = {};
for (const [prefix, target] of list) {
const isHttps = httpsRE.test(target);
// https://github.com/http-party/node-http-proxy#options
ret[prefix] = {
target: target,
changeOrigin: true,
ws: true,
rewrite: (path) => path.replace(new RegExp(`^${prefix}`), ''),
// https is require secure=false
...(isHttps ? { secure: false } : {}),
};
}
return ret;
}

@ -1,32 +0,0 @@
module.exports = {
ignores: [(commit) => commit.includes('init')],
extends: ['@commitlint/config-conventional'],
rules: {
'body-leading-blank': [2, 'always'],
'footer-leading-blank': [1, 'always'],
'header-max-length': [2, 'always', 108],
'subject-empty': [2, 'never'],
'type-empty': [2, 'never'],
'type-enum': [
2,
'always',
[
'feat',
'fix',
'perf',
'style',
'docs',
'test',
'refactor',
'build',
'ci',
'chore',
'revert',
'wip',
'workflow',
'types',
'release',
],
],
},
};

@ -1,181 +0,0 @@
<!DOCTYPE html>
<html lang="zh_CN" id="htmlRoot">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="renderer" content="webkit" />
<meta
name="viewport"
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
/>
<title><%= title %></title>
<link rel="icon" href="<%= basePublicPath %>/logo.png" />
<!-- 全局配置 -->
<script>
window._CONFIG = {};
</script>
</head>
<body>
<script>
(() => {
var htmlRoot = document.getElementById('htmlRoot');
var theme = window.localStorage.getItem('__APP__DARK__MODE__');
if (htmlRoot && theme) {
htmlRoot.setAttribute('data-theme', theme);
theme = htmlRoot = null;
}
})();
</script>
<div id="app">
<style>
html[data-theme='dark'] .app-loading {
background-color: #2c344a;
}
html[data-theme='dark'] .app-loading .app-loading-title {
color: rgba(255, 255, 255, 0.85);
}
.app-loading {
display: flex;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
flex-direction: column;
background-color: #f4f7f9;
}
.app-loading .app-loading-wrap {
position: absolute;
top: 50%;
left: 50%;
display: flex;
-webkit-transform: translate3d(-50%, -50%, 0);
transform: translate3d(-50%, -50%, 0);
justify-content: center;
align-items: center;
flex-direction: column;
}
.app-loading .dots {
display: flex;
padding: 98px;
justify-content: center;
align-items: center;
}
.app-loading .app-loading-title {
display: flex;
margin-top: 30px;
font-size: 30px;
color: rgba(0, 0, 0, 0.85);
justify-content: center;
align-items: center;
}
.app-loading .app-loading-logo {
display: block;
width: 90px;
margin: 0 auto;
margin-bottom: 20px;
}
.dot {
position: relative;
display: inline-block;
width: 48px;
height: 48px;
margin-top: 30px;
font-size: 32px;
transform: rotate(45deg);
box-sizing: border-box;
animation: antRotate 1.2s infinite linear;
}
.dot i {
position: absolute;
display: block;
width: 20px;
height: 20px;
background-color: #0065cc;
border-radius: 100%;
opacity: 0.3;
transform: scale(0.75);
animation: antSpinMove 1s infinite linear alternate;
transform-origin: 50% 50%;
}
.dot i:nth-child(1) {
top: 0;
left: 0;
}
.dot i:nth-child(2) {
top: 0;
right: 0;
-webkit-animation-delay: 0.4s;
animation-delay: 0.4s;
}
.dot i:nth-child(3) {
right: 0;
bottom: 0;
-webkit-animation-delay: 0.8s;
animation-delay: 0.8s;
}
.dot i:nth-child(4) {
bottom: 0;
left: 0;
-webkit-animation-delay: 1.2s;
animation-delay: 1.2s;
}
@keyframes antRotate {
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
}
@-webkit-keyframes antRotate {
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
}
@keyframes antSpinMove {
to {
opacity: 1;
}
}
@-webkit-keyframes antSpinMove {
to {
opacity: 1;
}
}
</style>
<div class="app-loading">
<div class="app-loading-wrap">
<img src="<%= basePublicPath %>/resource/img/logo.png" class="app-loading-logo" alt="Logo" />
<div class="app-loading-dots">
<span class="dot dot-spin"><i></i><i></i><i></i><i></i></span>
</div>
<div class="app-loading-title"><%= title %></div>
</div>
</div>
</div>
<script type="module" src="/src/main.ts"></script>
<!-- 百度统计 -->
<script>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?0febd9e3cacb3f627ddac64d52caac39";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
</body>
</html>

@ -1,36 +0,0 @@
export default {
preset: 'ts-jest',
roots: ['<rootDir>/tests/'],
clearMocks: true,
moduleDirectories: ['node_modules', 'src'],
moduleFileExtensions: ['js', 'ts', 'vue', 'tsx', 'jsx', 'json', 'node'],
modulePaths: ['<rootDir>/src', '<rootDir>/node_modules'],
testMatch: [
'**/tests/**/*.[jt]s?(x)',
'**/?(*.)+(spec|test).[tj]s?(x)',
'(/__tests__/.*|(\\.|/)(test|spec))\\.(js|ts)$',
],
testPathIgnorePatterns: [
'<rootDir>/tests/server/',
'<rootDir>/tests/__mocks__/',
'/node_modules/',
],
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
transformIgnorePatterns: ['<rootDir>/tests/__mocks__/', '/node_modules/'],
// A map from regular expressions to module names that allow to stub out resources with a single module
moduleNameMapper: {
'\\.(vs|fs|vert|frag|glsl|jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'<rootDir>/tests/__mocks__/fileMock.ts',
'\\.(sass|s?css|less)$': '<rootDir>/tests/__mocks__/styleMock.ts',
'\\?worker$': '<rootDir>/tests/__mocks__/workerMock.ts',
'^/@/(.*)$': '<rootDir>/src/$1',
},
testEnvironment: 'jsdom',
verbose: true,
collectCoverage: false,
coverageDirectory: 'coverage',
collectCoverageFrom: ['src/**/*.{js,ts,vue}'],
coveragePathIgnorePatterns: ['^.+\\.d\\.ts$'],
};

@ -1,18 +0,0 @@
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';
const modules = import.meta.glob('./**/*.ts', { eager: true });
const mockModules: any[] = [];
Object.keys(modules).forEach((key) => {
if (key.includes('/_')) {
return;
}
mockModules.push(...(modules as Recordable)[key].default);
});
/**
* Used in a production environment. Need to manually import all modules
*/
export function setupProdMockServer() {
createProdMockServer(mockModules);
}

@ -1,63 +0,0 @@
// Interface data format used to return a unified format
export function resultSuccess<T = Recordable>(result: T, { message = 'ok' } = {}) {
return {
code: 0,
result,
message,
type: 'success',
};
}
export function resultPageSuccess<T = any>(
pageNo: number,
pageSize: number,
list: T[],
{ message = 'ok' } = {}
) {
const pageData = pagination(pageNo, pageSize, list);
return {
...resultSuccess({
records: pageData,
total: list.length,
}),
message,
};
}
export function resultError(message = 'Request failed', { code = -1, result = null } = {}) {
return {
code,
result,
message,
type: 'error',
};
}
export function pagination<T = any>(pageNo: number, pageSize: number, array: T[]): T[] {
const offset = (pageNo - 1) * Number(pageSize);
const ret =
offset + Number(pageSize) >= array.length
? array.slice(offset, array.length)
: array.slice(offset, offset + Number(pageSize));
return ret;
}
export interface requestParams {
method: string;
body: any;
headers?: { authorization?: string };
query: any;
}
/**
* @description 本函数用于从request数据中获取token请根据项目的实际情况修改
*
*/
export function getRequestToken({ headers }: requestParams): string | undefined {
return headers?.authorization;
}
//TODO
export const baseUrl = '/jeecgboot/mock';

@ -1,70 +0,0 @@
import { MockMethod } from 'vite-plugin-mock';
import { resultSuccess, resultError, baseUrl } from '../_util';
import { ResultEnum } from '../../src/enums/httpEnum';
const userInfo = {
name: 'Jeecg',
userid: '00000001',
email: 'test@gmail.com',
signature: '海纳百川,有容乃大',
introduction: '微笑着,努力着,欣赏着',
title: '交互专家',
group: '某某某事业群某某平台部某某技术部UED',
tags: [
{
key: '0',
label: '很有想法的',
},
{
key: '1',
label: '专注设计',
},
{
key: '2',
label: '辣~',
},
{
key: '3',
label: '大长腿',
},
{
key: '4',
label: '川妹子',
},
{
key: '5',
label: '海纳百川',
},
],
notifyCount: 12,
unreadCount: 11,
country: 'China',
address: 'Xiamen City 77',
phone: '0592-268888888',
};
export default [
{
url: `${baseUrl}/account/getAccountInfo`,
timeout: 1000,
method: 'get',
response: () => {
return resultSuccess(userInfo);
},
},
{
url: `${baseUrl}/user/sessionTimeout`,
method: 'post',
statusCode: 401,
response: () => {
return resultError();
},
},
{
url: '/basic-api/user/tokenExpired',
method: 'post',
statusCode: 200,
response: () => {
return resultError('Token Expired!', { code: ResultEnum.TIMEOUT as number });
},
},
] as MockMethod[];

@ -1,37 +0,0 @@
import { MockMethod } from 'vite-plugin-mock';
import { resultSuccess, baseUrl } from '../_util';
const demoList = (keyword, count = 20) => {
const result = {
list: [] as any[],
};
for (let index = 0; index < count; index++) {
//
let name = `选项${index}`;
if(keyword && name.indexOf(keyword)!=-1){
result.list.push({
name: `选项${index}`,
id: `${index}`,
});
}else if(!keyword){
result.list.push({
name: `选项${index}`,
id: `${index}`,
});
}
}
return result;
};
export default [
{
url: `${baseUrl}/select/getDemoOptions`,
timeout: 1000,
method: 'get',
response: ({ query }) => {
const { keyword,count} = query;
console.log("查询条件:", keyword);
return resultSuccess(demoList(keyword,count));
},
},
] as MockMethod[];

@ -1,298 +0,0 @@
import { MockMethod } from 'vite-plugin-mock';
import { resultError, resultPageSuccess, resultSuccess, baseUrl } from '../_util';
const accountList = (() => {
const result: any[] = [];
for (let index = 0; index < 20; index++) {
result.push({
id: `${index}`,
account: '@first',
email: '@email',
nickname: '@cname()',
role: '@first',
createTime: '@datetime',
remark: '@cword(10,20)',
'status|1': ['0', '1'],
});
}
return result;
})();
const userList = (() => {
const result: any[] = [];
for (let index = 0; index < 20; index++) {
result.push({
id: `${index}`,
username: '@first',
email: '@email',
realname: '@cname()',
createTime: '@datetime',
remark: '@cword(10,20)',
avatar: 'https://q1.qlogo.cn/g?b=qq&nk=190848757&s=640'
});
}
return result;
})();
const roleList = (() => {
const result: any[] = [];
for (let index = 0; index < 4; index++) {
result.push({
id: index + 1,
orderNo: `${index + 1}`,
roleName: ['超级管理员', '管理员', '文章管理员', '普通用户'][index],
roleValue: '@first',
createTime: '@datetime',
remark: '@cword(10,20)',
menu: [['0', '1', '2'], ['0', '1'], ['0', '2'], ['2']][index],
'status|1': ['0', '1'],
});
}
return result;
})();
const newRoleList = (() => {
const result: any[] = [];
for (let index = 0; index < 4; index++) {
result.push({
id: index + 1,
orderNo: `${index + 1}`,
roleName: ['超级管理员', '管理员', '文章管理员', '普通用户'][index],
roleCode: '@first',
createTime: '@datetime',
remark: '@cword(10,20)'
});
}
return result;
})();
const testList = (() => {
const result: any[] = [];
for (let index = 0; index < 4; index++) {
result.push({
id: index + 1,
orderNo: `${index + 1}`,
testName: ['数据1', '数据2', '数据3', '数据4'][index],
testValue: '@first',
createTime: '@datetime'
});
}
return result;
})();
const tableDemoList = (() => {
const result: any[] = [];
for (let index = 0; index < 4; index++) {
result.push({
id: index + 1,
orderCode: '2008200' + `${index + 1}`,
orderMoney: '@natural(1000,3000)',
ctype: '@natural(1,2)',
content: '@cword(10,20)',
orderDate: '@datetime'
});
}
return result;
})();
const deptList = (() => {
const result: any[] = [];
for (let index = 0; index < 3; index++) {
result.push({
id: `${index}`,
deptName: ['华东分部', '华南分部', '西北分部'][index],
orderNo: index + 1,
createTime: '@datetime',
remark: '@cword(10,20)',
'status|1': ['0', '0', '1'],
children: (() => {
const children: any[] = [];
for (let j = 0; j < 4; j++) {
children.push({
id: `${index}-${j}`,
deptName: ['研发部', '市场部', '商务部', '财务部'][j],
orderNo: j + 1,
createTime: '@datetime',
remark: '@cword(10,20)',
'status|1': ['0', '1'],
parentDept: `${index}`,
children: undefined,
});
}
return children;
})(),
});
}
return result;
})();
const menuList = (() => {
const result: any[] = [];
for (let index = 0; index < 3; index++) {
result.push({
id: `${index}`,
icon: ['ion:layers-outline', 'ion:git-compare-outline', 'ion:tv-outline'][index],
component: 'LAYOUT',
type: '0',
menuName: ['Dashboard', '权限管理', '功能'][index],
permission: '',
orderNo: index + 1,
createTime: '@datetime',
'status|1': ['0', '0', '1'],
children: (() => {
const children: any[] = [];
for (let j = 0; j < 4; j++) {
children.push({
id: `${index}-${j}`,
type: '1',
menuName: ['菜单1', '菜单2', '菜单3', '菜单4'][j],
icon: 'ion:document',
permission: ['menu1:view', 'menu2:add', 'menu3:update', 'menu4:del'][index],
component: [
'/dashboard/welcome/index',
'/dashboard/Analysis/index',
'/dashboard/workbench/index',
'/dashboard/test/index',
][j],
orderNo: j + 1,
createTime: '@datetime',
'status|1': ['0', '1'],
parentMenu: `${index}`,
children: (() => {
const children: any[] = [];
for (let k = 0; k < 4; k++) {
children.push({
id: `${index}-${j}-${k}`,
type: '2',
menuName: '按钮' + (j + 1) + '-' + (k + 1),
icon: '',
permission:
['menu1:view', 'menu2:add', 'menu3:update', 'menu4:del'][index] +
':btn' +
(k + 1),
component: [
'/dashboard/welcome/index',
'/dashboard/Analysis/index',
'/dashboard/workbench/index',
'/dashboard/test/index',
][j],
orderNo: j + 1,
createTime: '@datetime',
'status|1': ['0', '1'],
parentMenu: `${index}-${j}`,
children: undefined,
});
}
return children;
})(),
});
}
return children;
})(),
});
}
return result;
})();
export default [
{
url: `${baseUrl}/system/getAccountList`,
timeout: 100,
method: 'get',
response: ({ query }) => {
const { page = 1, pageSize = 20 } = query;
return resultPageSuccess(page, pageSize, accountList);
},
},
{
url: `${baseUrl}/sys/user/list`,
timeout: 100,
method: 'get',
response: ({ query }) => {
const { page = 1, pageSize = 20 } = query;
return resultPageSuccess(page, pageSize, userList);
},
},
{
url: `${baseUrl}/system/getRoleListByPage`,
timeout: 100,
method: 'get',
response: ({ query }) => {
const { page = 1, pageSize = 20 } = query;
return resultPageSuccess(page, pageSize, roleList);
},
},
{
url: `${baseUrl}/sys/role/list`,
timeout: 100,
method: 'get',
response: ({ query }) => {
const { page = 1, pageSize = 20 } = query;
return resultPageSuccess(page, pageSize, newRoleList);
},
},
{
url: `${baseUrl}/system/getTestListByPage`,
timeout: 100,
method: 'get',
response: ({ query }) => {
const { page = 1, pageSize = 20 } = query;
return resultPageSuccess(page, pageSize, testList);
},
},
{
url: `${baseUrl}/system/getDemoTableListByPage`,
timeout: 100,
method: 'get',
response: ({ query }) => {
const { page = 1, pageSize = 20 } = query;
return resultPageSuccess(page, pageSize, tableDemoList);
},
},
{
url: `${baseUrl}/system/setRoleStatus`,
timeout: 500,
method: 'post',
response: ({ query }) => {
const { id, status } = query;
return resultSuccess({ id, status });
},
},
{
url: `${baseUrl}/system/getAllRoleList`,
timeout: 100,
method: 'get',
response: () => {
return resultSuccess(roleList);
},
},
{
url: `${baseUrl}/system/getDeptList`,
timeout: 100,
method: 'get',
response: () => {
return resultSuccess(deptList);
},
},
{
url: `${baseUrl}/system/getMenuList`,
timeout: 100,
method: 'get',
response: () => {
return resultSuccess(menuList);
},
},
{
url: `${baseUrl}/system/accountExist`,
timeout: 500,
method: 'post',
response: ({ body }) => {
const { account } = body || {};
if (account && account.indexOf('admin') !== -1) {
return resultError('该字段不能包含admin');
} else {
return resultSuccess(`${account} can use`);
}
},
},
] as MockMethod[];

@ -1,57 +0,0 @@
import { MockMethod } from 'vite-plugin-mock';
import { Random } from 'mockjs';
import { resultPageSuccess, baseUrl } from '../_util';
function getRandomPics(count = 10): string[] {
const arr: string[] = [];
for (let i = 0; i < count; i++) {
arr.push(Random.image('800x600', Random.color(), Random.color(), Random.title()));
}
return arr;
}
const demoList = (() => {
const result: any[] = [];
for (let index = 0; index < 200; index++) {
result.push({
id: `${index}`,
beginTime: '@datetime',
endTime: '@datetime',
address: '@city()',
name: '@cname()',
name1: '@cname()',
name2: '@cname()',
name3: '@cname()',
name4: '@cname()',
name5: '@cname()',
name6: '@cname()',
name7: '@cname()',
name8: '@cname()',
avatar: Random.image('400x400', Random.color(), Random.color(), Random.first()),
imgArr: getRandomPics(Math.ceil(Math.random() * 3) + 1),
imgs: getRandomPics(Math.ceil(Math.random() * 3) + 1),
age: Math.ceil(Math.random() * 30) + 1,
score: Math.ceil(Math.random() * 80) + 1,
date: `@date('yyyy-MM-dd')`,
time: `@time('HH:mm')`,
'no|100000-10000000': 100000,
'status|1': ['normal', 'enable', 'disable'],
});
}
return result;
})();
export default [
{
url: `${baseUrl}/table/getDemoList`,
timeout: 100,
method: 'get',
response: ({ query }) => {
const { page = 1, pageSize = 20 } = query;
// update-begin--author:liaozhiyang---date:20240730---forissues/6943mockid
const pageNo = +(query.pageNo ?? page);
return resultPageSuccess(pageNo, +pageSize, demoList);
// update-end--author:liaozhiyang---date:20240730---forissues/6943mockid
},
},
] as MockMethod[];

@ -1,38 +0,0 @@
import { MockMethod } from 'vite-plugin-mock';
import { resultSuccess, baseUrl } from '../_util';
const demoTreeList = (keyword) => {
const result = {
list: [] as Recordable[],
};
for (let index = 0; index < 5; index++) {
const children: Recordable[] = [];
for (let j = 0; j < 3; j++) {
children.push({
title: `${keyword ?? ''}选项${index}-${j}`,
value: `${index}-${j}`,
key: `${index}-${j}`,
});
}
result.list.push({
title: `${keyword ?? ''}选项${index}`,
value: `${index}`,
key: `${index}`,
children,
});
}
return result;
};
export default [
{
url: `${baseUrl}/tree/getDemoOptions`,
timeout: 1000,
method: 'get',
response: ({ query }) => {
const { keyword } = query;
console.log("查询条件:", keyword);
return resultSuccess(demoTreeList(keyword));
},
},
] as MockMethod[];

@ -1,270 +0,0 @@
import { resultSuccess, resultError, getRequestToken, requestParams,baseUrl} from '../_util';
import { MockMethod } from 'vite-plugin-mock';
import { createFakeUserList } from './user';
// single
const dashboardRoute = {
path: '/dashboard',
name: 'Dashboard',
component: 'LAYOUT',
redirect: '/dashboard/analysis',
meta: {
title: 'routes.dashboard.dashboard',
hideChildrenInMenu: true,
icon: 'bx:bx-home',
},
children: [
{
path: 'analysis',
name: 'Analysis',
component: '/dashboard/Analysis/index',
meta: {
hideMenu: true,
hideBreadcrumb: true,
title: 'routes.dashboard.analysis',
currentActiveMenu: '/dashboard',
icon: 'bx:bx-home',
},
},
{
path: 'workbench',
name: 'Workbench',
component: '/dashboard/workbench/index',
meta: {
hideMenu: true,
hideBreadcrumb: true,
title: 'routes.dashboard.workbench',
currentActiveMenu: '/dashboard',
icon: 'bx:bx-home',
},
},
],
};
const backRoute = {
path: 'back',
name: 'PermissionBackDemo',
meta: {
title: 'routes.demo.permission.back',
},
children: [
{
path: 'page',
name: 'BackAuthPage',
component: '/demo/permission/back/index',
meta: {
title: 'routes.demo.permission.backPage',
},
},
{
path: 'btn',
name: 'BackAuthBtn',
component: '/demo/permission/back/Btn',
meta: {
title: 'routes.demo.permission.backBtn',
},
},
],
};
const authRoute = {
path: '/permission',
name: 'Permission',
component: 'LAYOUT',
redirect: '/permission/front/page',
meta: {
icon: 'carbon:user-role',
title: 'routes.demo.permission.permission',
},
children: [backRoute],
};
const levelRoute = {
path: '/level',
name: 'Level',
component: 'LAYOUT',
redirect: '/level/menu1/menu1-1',
meta: {
icon: 'carbon:user-role',
title: 'routes.demo.level.level',
},
children: [
{
path: 'menu1',
name: 'Menu1Demo',
meta: {
title: 'Menu1',
},
children: [
{
path: 'menu1-1',
name: 'Menu11Demo',
meta: {
title: 'Menu1-1',
},
children: [
{
path: 'menu1-1-1',
name: 'Menu111Demo',
component: '/demo/level/Menu111',
meta: {
title: 'Menu111',
},
},
],
},
{
path: 'menu1-2',
name: 'Menu12Demo',
component: '/demo/level/Menu12',
meta: {
title: 'Menu1-2',
},
},
],
},
{
path: 'menu2',
name: 'Menu2Demo',
component: '/demo/level/Menu2',
meta: {
title: 'Menu2',
},
},
],
};
const sysRoute = {
path: '/system',
name: 'System',
component: 'LAYOUT',
redirect: '/system/account',
meta: {
icon: 'ion:settings-outline',
title: 'routes.demo.system.moduleName',
},
children: [
{
path: 'account',
name: 'AccountManagement',
meta: {
title: 'routes.demo.system.account',
ignoreKeepAlive: true,
},
component: '/demo/system/account/index',
},
{
path: 'account_detail/:id',
name: 'AccountDetail',
meta: {
hideMenu: true,
title: 'routes.demo.system.account_detail',
ignoreKeepAlive: true,
showMenu: false,
currentActiveMenu: '/system/account',
},
component: '/demo/system/account/AccountDetail',
},
{
path: 'role',
name: 'RoleManagement',
meta: {
title: 'routes.demo.system.role',
ignoreKeepAlive: true,
},
component: '/demo/system/role/index',
},
{
path: 'menu',
name: 'MenuManagement',
meta: {
title: 'routes.demo.system.menu',
ignoreKeepAlive: true,
},
component: '/demo/system/menu/index',
},
{
path: 'dept',
name: 'DeptManagement',
meta: {
title: 'routes.demo.system.dept',
ignoreKeepAlive: true,
},
component: '/demo/system/dept/index',
},
{
path: 'changePassword',
name: 'ChangePassword',
meta: {
title: 'routes.demo.system.password',
ignoreKeepAlive: true,
},
component: '/demo/system/password/index',
},
],
};
const linkRoute = {
path: '/link',
name: 'Link',
component: 'LAYOUT',
meta: {
icon: 'ion:tv-outline',
title: 'routes.demo.iframe.frame',
},
children: [
{
path: 'doc',
name: 'Doc',
meta: {
title: 'routes.demo.iframe.doc',
frameSrc: 'https://vvbin.cn/doc-next/',
},
},
{
path: 'https://vvbin.cn/doc-next/',
name: 'DocExternal',
component: 'LAYOUT',
meta: {
title: 'routes.demo.iframe.docExternal',
},
},
],
};
export default [
{
url: `${baseUrl}/sys/permission/getUserPermissionByToken`,
timeout: 1000,
method: 'get',
response: (request: requestParams) => {
const token = getRequestToken(request);
if (!token) {
return resultError('Invalid token!');
}
const checkUser = createFakeUserList().find((item) => item.token === token);
if (!checkUser) {
return resultError('Invalid user token!');
}
const id = checkUser.userId;
let menu: Object[];
switch (id) {
case '1':
dashboardRoute.redirect = dashboardRoute.path + '/' + dashboardRoute.children[0].path;
menu = [dashboardRoute, authRoute, levelRoute, sysRoute, linkRoute];
break;
case '2':
dashboardRoute.redirect = dashboardRoute.path + '/' + dashboardRoute.children[1].path;
menu = [dashboardRoute, authRoute, levelRoute, linkRoute];
break;
default:
menu = [];
}
return resultSuccess(menu);
},
},
] as MockMethod[];

@ -1,124 +0,0 @@
import { MockMethod } from 'vite-plugin-mock';
import { resultError, resultSuccess, getRequestToken, requestParams, baseUrl } from '../_util';
export function createFakeUserList() {
return [
{
userId: '1',
username: 'admin',
realname: '管理员',
avatar: 'https://q1.qlogo.cn/g?b=qq&nk=190848757&s=640',
desc: 'manager',
password: '123456',
token: 'fakeToken1',
homePath: '/dashboard/analysis',
roles: [
{
roleName: 'Super Admin',
value: 'super',
},
],
},
{
userId: '2',
username: 'jeecg',
password: '123456',
realname: '测试用户',
avatar: 'https://q1.qlogo.cn/g?b=qq&nk=339449197&s=640',
desc: 'tester',
token: 'fakeToken2',
homePath: '/dashboard/workbench',
roles: [
{
roleName: 'Tester',
value: 'test',
},
],
},
];
}
const fakeCodeList: any = {
'1': ['1000', '3000', '5000'],
'2': ['2000', '4000', '6000'],
};
export default [
// mock user login
{
url: `${baseUrl}/sys/login`,
timeout: 200,
method: 'post',
response: ({ body }) => {
const { username, password } = body;
const checkUser = createFakeUserList().find(
(item) => item.username === username && password === item.password
);
if (!checkUser) {
return resultError('Incorrect account or password');
}
const { userId, username: _username, token, realname, desc, roles } = checkUser;
return resultSuccess({
roles,
userId,
username: _username,
token,
realname,
desc,
});
},
},
{
url: `${baseUrl}/sys/user/getUserInfo`,
method: 'get',
response: (request: requestParams) => {
const token = getRequestToken(request);
if (!token) return resultError('Invalid token');
const checkUser = createFakeUserList().find((item) => item.token === token);
if (!checkUser) {
return resultError('The corresponding user information was not obtained!');
}
return resultSuccess(checkUser);
},
},
{
url: `${baseUrl}/sys/permission/getPermCode`,
timeout: 200,
method: 'get',
response: (request: requestParams) => {
const token = getRequestToken(request);
if (!token) return resultError('Invalid token');
const checkUser = createFakeUserList().find((item) => item.token === token);
if (!checkUser) {
return resultError('Invalid token!');
}
const codeList = fakeCodeList[checkUser.userId];
return resultSuccess(codeList);
},
},
{
url: `${baseUrl}/sys/logout`,
timeout: 200,
method: 'get',
response: (request: requestParams) => {
const token = getRequestToken(request);
if (!token) return resultError('Invalid token');
const checkUser = createFakeUserList().find((item) => item.token === token);
if (!checkUser) {
return resultError('Invalid token!');
}
return resultSuccess(undefined, { message: 'Token has been destroyed' });
},
},
{
url: `${baseUrl}/sys/randomImage/1629428467008`,
timeout: 200,
method: 'get',
response: (request: requestParams) => {
const result =
'data:image/jpg;base64,/9j/4AAQSkZJRgABAgAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAAjAGkDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD3h/ME5lErCKNSHi8rO44yCp65/P060gmdbbIaKecxl0VDsEmOmMk8cgZ96dcypFGpkEm0uOUzxznJI6DjnPH54ryTWNW1+P436T4ftNev4LTU7Brhw9tbCSHiZxGrNCcKCijkMeuSSOAD1KJ7hrTZNK0d1Iz7egGVPRSV6HGRkE4z1xV5SWQEqVJGSpxke3FeYL4n1PSfi5pXhK7vxrFlf27XVveyQRrcQHa4KFo1CMhMLdFUjfy2F59NjZm3Bl2kMRxnGO3JA7Y/HI7UAVrjfbWt3NJLNKmCwVAoaNcc4PHTk8+n5rKs1tDGtsdw5X94GkOT90ls5xnr14PYDNeUfDzxvq3xCuNZvG8SNp1zBKHtdFgtYpFjhAADuWXfKpJwwRkIwcFd6geg+Dm12Tw9bN4hkX+0Y5bmKcLGVV9s7KjLkA7dijBPLAgnJ5oWgGrb36zBtqyPh9oKwuvG4DncByCeeexPsLHm7jH5amSNxnzFI2gY479/bNVrxYZpRD57Q3JRkikRFLxlgfmUspGQEbrkccg8V5l8PfEOt6h4p8YW+r+JJf7M8OXZhjWWC2jjMQaZSZWWNSMLGDkFQD1BHFAHq7usMcksjnYoLHI+6AOen0pqllZfNkUMWZVUdG5yOvOcD+dcclt4j1/XlvtM8T3dn4cIc7kgt2a4PGwwBoTiLqfMZm34+VQpVz0Ou2t/eWfk2GrXGmTNxHcW9ukxV8EAOrqwKZIJ+6fl+8M0AXjHcb5GWdcN9xWjyF6ehGe/5j05jmdri3cxPJGI3YOAOXABGARnGTjkcjpwenmvwt8Y61czeIdM8b6g0esaW3mTR3SwQJFAB99dijcOpZySoBjI+8TXR+EtK16SNdR1rWL64WUNPaWtxbxQGBXbKCVY0VjKq/f525dgBxuKA6i4ZfsSEyq0R275Wm8v5f7wZe/TjgHNWqiiSWNEV5vNwp3MygFj68cAdeMen4y0wKk6Mtysp3yQsvktEoyPmI+YgnGB9O57dPIfGML3H7SnhaGO+msXfS2C3EIjLqcXPTzFZeenIPXjnFeyiIJIzx4XzH3SZBO75ccc8dF/L3zXHah4D0K58US+ILuLUPtcMeI5l1G5WT5i+VjYSDYCW2hVIHLDaMg0AX9K8FaVZ+IJPEVxPc6rrTAxLf3rqzRJgKURUVUToeQoPzNk8mt0qRJF5uTMVdBNGuAucHoScdByeOPcAxRw3MdzOpuC3nZkQiEBY8EcMf4sggfRT0qYQSJBJGJWlGwKodirZxjlxzzxzjIOfYAA8Tn+HehfErRoPGvhDUP7J1uQCaWK3YmJLpQWZT0aN95X5xxgBgp3ZPb/AAi8U6r4q8BRajriv9qW5khFy6hBcrkEOoCqAAW2cZ5Q85yBdtPAfhSTSv7Og0mWxiWH7PcQ288lq86Y2/vjEy+cCM8sW6t6mukt9PttNsobLTbC0gtY34gjURIgzuJVVGM55xxz3oAnZf8ASI38vcQrLu3fdBwenccfy96+abbwjqHiu8+K1lpt/NFJBqYlW1MwSO5KzzECRmBJwA2ASBuKljxkfSF9ai7jeESzRtJGy5R3UAdCcoVIOGOCCD36qMc9oHgHw/oGrSalpMd1a3DuWnRb6Z1lYr/y2VnYOw3sQT03Z96AMn4W+PP+Et0xrLVZmi8TaerRX9pJH5Rba2BIE9egbGMMSCACtdxOHuYmXy3CCVRjIHmLkbs5HTrx3A9DXJN8LvCraj/adpa3SajtZBqC6rdGaMhdgw3mZOB8uMjgY9q39X0Sx1myuLLUknWwbLSLDdyQrKGUhgxRlOPvZUnac5OT0AseQfEHT7u71CP4keHrbTZJtFSGWQ/Zmk+3gMytMByDGm0BXwrbVdwwVYmPqXhvxDZeKdEj1bRL6AWM42NvT9/DLtVQkmWOXBx16jbjIINGl+GdPs9I/seG6nSAfIY7e/n3qqEBVSQuZI1A25RWABOOhO7P8NeA/DejRveaBpb2IuW2k/bJmDxjIDlJNyk4LYyOjHBGaAOwjeQogYxtIuBNtbhTjPH6cHsalqlbWMFrcSNuVnkkeZAwG5c/eI79wOO2O+SbtABSMiuMMoYZBwRnkHIP50UUALRRRQAUUUUAFFFFACBVUsVUAscsQOpxjn8AKWiigBFVUQIihVUYAAwAKY8EUkqSOgZ0BCk9uQf5qD+FFFAElFFFAH//2Q==';
return resultSuccess(result);
},
},
] as MockMethod[];

@ -1,177 +0,0 @@
{
"name": "jeecgboot-vue3",
"version": "3.7.4",
"author": {
"name": "北京国炬信息技术有限公司",
"email": "jeecgos@163.com",
"url": "https://www.jeecg.com"
},
"scripts": {
"pinstall": "pnpm install",
"clean:cache": "rimraf node_modules/.cache/ && rimraf node_modules/.vite",
"dev": "vite",
"build": "cross-env NODE_ENV=production NODE_OPTIONS=--max-old-space-size=8192 vite build && esno ./build/script/postBuild.ts",
"build:report": "pnpm clean:cache && cross-env REPORT=true npm run build",
"preview": "npm run build && vite preview",
"reinstall": "rimraf pnpm-lock.yaml && rimraf yarn.lock && rimraf package.lock.json && rimraf node_modules && npm run install",
"clean:lib": "rimraf node_modules",
"gen:icon": "esno ./build/generate/icon/index.ts",
"batch:prettier": "prettier --write \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
"upgrade:log": "conventional-changelog -p angular -i CHANGELOG.md -s",
"husky:install": "husky install"
},
"dependencies": {
"@jeecg/online": "3.7.4-beta",
"@iconify/iconify": "^3.1.1",
"@ant-design/colors": "^7.2.0",
"@ant-design/icons-vue": "^7.0.1",
"@vue/shared": "^3.5.13",
"@vueuse/core": "^10.11.1",
"@tinymce/tinymce-vue": "4.0.7",
"@zxcvbn-ts/core": "^3.0.4",
"ant-design-vue": "^4.2.6",
"axios": "^1.7.9",
"china-area-data": "^5.0.1",
"@vant/area-data": "^1.5.2",
"clipboard": "^2.0.11",
"codemirror": "^5.65.18",
"cron-parser": "^4.9.0",
"cropperjs": "^1.6.2",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.13",
"dom-align": "^1.12.4",
"echarts": "^5.6.0",
"emoji-mart-vue-fast": "^15.0.3",
"enquire.js": "^2.1.6",
"intro.js": "^7.2.0",
"lodash-es": "^4.17.21",
"lodash.get": "^4.4.2",
"markdown-it": "^14.1.0",
"markdown-it-link-attributes": "^4.0.1",
"event-source-polyfill": "^1.0.31",
"highlight.js": "^11.11.1",
"@traptitech/markdown-it-katex": "^3.6.0",
"md5": "^2.3.0",
"mockjs": "^1.1.0",
"nprogress": "^0.2.0",
"path-to-regexp": "^6.3.0",
"pinia": "2.1.7",
"print-js": "^1.6.0",
"qs": "^6.13.1",
"qrcode": "^1.5.4",
"resize-observer-polyfill": "^1.5.1",
"showdown": "^2.1.0",
"sortablejs": "^1.15.6",
"tinymce": "6.6.2",
"vditor": "^3.10.8",
"vue": "^3.5.13",
"vue-cropper": "^0.6.5",
"vue-cropperjs": "^5.0.0",
"vue-i18n": "^9.14.2",
"vue-infinite-scroll": "^2.0.2",
"vue-print-nb-jeecg": "^1.0.12",
"vue-router": "^4.5.0",
"vue-types": "^5.1.3",
"vuedraggable": "^4.1.0",
"vxe-table": "4.6.17",
"vxe-table-plugin-antd": "4.0.7",
"xe-utils": "3.5.26",
"xss": "^1.0.15"
},
"devDependencies": {
"@commitlint/cli": "^18.6.1",
"@commitlint/config-conventional": "^18.6.3",
"@iconify/json": "^2.2.292",
"@purge-icons/generated": "^0.10.0",
"@types/codemirror": "^5.60.15",
"@types/crypto-js": "^4.2.2",
"@types/fs-extra": "^11.0.4",
"@types/inquirer": "^9.0.7",
"@types/intro.js": "^5.1.5",
"@types/jest": "^29.5.14",
"@types/lodash-es": "^4.17.12",
"@types/mockjs": "^1.0.10",
"@types/node": "^20.17.12",
"@types/nprogress": "^0.2.3",
"@types/qrcode": "^1.5.5",
"@types/qs": "^6.9.17",
"@types/showdown": "^2.0.6",
"@types/sortablejs": "^1.15.8",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"@vitejs/plugin-vue": "^5.2.1",
"@vitejs/plugin-vue-jsx": "^4.1.1",
"@vue/compiler-sfc": "^3.5.13",
"@vue/test-utils": "^2.4.6",
"autoprefixer": "^10.4.20",
"commitizen": "^4.3.1",
"conventional-changelog-cli": "^4.1.0",
"cross-env": "^7.0.3",
"cz-git": "^1.11.0",
"czg": "^1.11.0",
"dotenv": "^16.4.7",
"eslint": "^8.57.1",
"eslint-config-prettier": "^9.1.0",
"eslint-define-config": "^2.1.0",
"eslint-plugin-jest": "^27.9.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-vue": "^9.32.0",
"esno": "^4.8.0",
"fs-extra": "^11.2.0",
"http-server": "^14.1.1",
"husky": "^8.0.3",
"inquirer": "^9.3.7",
"is-ci": "^3.0.1",
"jest": "^29.7.0",
"less": "^4.2.1",
"lint-staged": "15.2.2",
"npm-run-all": "^4.1.5",
"picocolors": "^1.1.1",
"postcss": "^8.4.49",
"postcss-html": "^1.7.0",
"postcss-less": "^6.0.0",
"prettier": "^3.4.2",
"pretty-quick": "^4.0.0",
"rimraf": "^5.0.10",
"rollup": "^4.30.0",
"rollup-plugin-visualizer": "^5.13.1",
"stylelint": "^16.12.0",
"stylelint-config-prettier": "^9.0.5",
"stylelint-config-recommended": "^14.0.1",
"stylelint-config-recommended-vue": "^1.5.0",
"stylelint-config-standard": "^36.0.1",
"stylelint-order": "^6.0.4",
"ts-jest": "^29.2.5",
"ts-node": "^10.9.2",
"typescript": "^4.9.5",
"vite": "^6.0.7",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-html": "^3.2.2",
"vite-plugin-mkcert": "^1.17.6",
"vite-plugin-mock": "^2.9.8",
"vite-plugin-optimize-persist": "^0.1.2",
"vite-plugin-package-config": "^0.1.1",
"vite-plugin-purge-icons": "^0.10.0",
"vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-qiankun": "^1.0.15",
"@rys-fe/vite-plugin-theme": "^0.8.6",
"vite-plugin-vue-setup-extend-plus": "^0.1.0",
"unocss": "^0.58.9",
"vue-eslint-parser": "^9.4.3",
"vue-tsc": "^1.8.27",
"dingtalk-jsapi": "^3.0.42",
"big.js": "^6.2.2"
},
"repository": {
"type": "git",
"url": "git+https://github.com/jeecgboot/JeecgBoot.git"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/jeecgboot/JeecgBoot/issues"
},
"homepage": "https://www.jeecg.com",
"engines": {
"node": "^18 || >=20"
}
}

File diff suppressed because it is too large Load Diff

@ -1,5 +0,0 @@
module.exports = {
plugins: {
autoprefixer: {},
},
};

@ -1,20 +0,0 @@
module.exports = {
printWidth: 150,
tabWidth: 2,
useTabs: false,
semi: true, //语句末尾使用分号
vueIndentScriptAndStyle: true,
singleQuote: true, // 使用单引号
quoteProps: 'as-needed',
bracketSpacing: true,
trailingComma: 'es5',
jsxBracketSameLine: false,
jsxSingleQuote: false,
arrowParens: 'always',
insertPragma: false,
requirePragma: false,
proseWrap: 'never',
htmlWhitespaceSensitivity: 'strict',
endOfLine: 'auto',
rangeStart: 0,
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 430 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

File diff suppressed because one or more lines are too long

@ -1,419 +0,0 @@
tinymce.addI18n('es', {
Redo: 'Rehacer',
Undo: 'Deshacer',
Cut: 'Cortar',
Copy: 'Copiar',
Paste: 'Pegar',
'Select all': 'Seleccionar todo',
'New document': 'Nuevo documento',
Ok: 'Ok',
Cancel: 'Cancelar',
'Visual aids': 'Ayudas visuales',
Bold: 'Negrita',
Italic: 'Cursiva',
Underline: 'Subrayado',
Strikethrough: 'Tachado',
Superscript: 'Super\u00edndice',
Subscript: 'Sub\u00edndice',
'Clear formatting': 'Limpiar formato',
'Align left': 'Alinear a la izquierda',
'Align center': 'Alinear al centro',
'Align right': 'Alinear a la derecha',
Justify: 'Justificar',
'Bullet list': 'Lista de vi\u00f1etas',
'Numbered list': 'Lista numerada',
'Decrease indent': 'Disminuir sangr\u00eda',
'Increase indent': 'Incrementar sangr\u00eda',
Close: 'Cerrar',
Formats: 'Formatos',
"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": 'Su navegador no es compatible con el acceso directo al portapapeles. Use las teclas Crtl+X\/C\/V de su teclado.',
Headers: 'Encabezados',
'Header 1': 'Encabezado 1',
'Header 2': 'Encabezado 2',
'Header 3': 'Encabezado 3',
'Header 4': 'Encabezado 4',
'Header 5': 'Encabezado 5',
'Header 6': 'Encabezado 6',
Headings: 'Encabezados',
'Heading 1': 'Encabezado 1',
'Heading 2': 'Encabezado 2',
'Heading 3': 'Encabezado 3',
'Heading 4': 'Encabezado 4',
'Heading 5': 'Encabezado 5',
'Heading 6': 'Encabezado 6',
Preformatted: 'Con formato previo',
Div: 'Div',
Pre: 'Pre',
Code: 'C\u00f3digo',
Paragraph: 'P\u00e1rrafo',
Blockquote: 'Blockquote',
Inline: 'Alineado',
Blocks: 'Bloques',
'Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.': 'Pegar est\u00e1 ahora en modo de texto plano. El contenido se pegar\u00e1 como texto plano hasta que desactive esta opci\u00f3n.',
Fonts: 'Fuentes',
'Font Sizes': 'Tama\u00f1os de fuente',
Class: 'Clase',
'Browse for an image': 'Buscar una imagen',
OR: 'OR',
'Drop an image here': 'Arrastre una imagen aqu\u00ed',
Upload: 'Cargar',
Block: 'Bloque',
Align: 'Alinear',
Default: 'Por defecto',
Circle: 'C\u00edrculo',
Disc: 'Disco',
Square: 'Cuadrado',
'Lower Alpha': 'Inferior Alfa',
'Lower Greek': 'Inferior Griega',
'Lower Roman': 'Inferior Romana',
'Upper Alpha': 'Superior Alfa',
'Upper Roman': 'Superior Romana',
'Anchor...': 'Anclaje...',
Name: 'Nombre',
Id: 'Id',
'Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.': 'Deber\u00eda comenzar por una letra, seguida solo de letras, n\u00fameros, guiones, puntos, dos puntos o guiones bajos.',
'You have unsaved changes are you sure you want to navigate away?': 'Tiene cambios sin guardar. \u00bfEst\u00e1 seguro de que quiere salir?',
'Restore last draft': 'Restaurar el \u00faltimo borrador',
'Special character...': 'Car\u00e1cter especial...',
'Source code': 'C\u00f3digo fuente',
'Insert\/Edit code sample': 'Insertar\/editar c\u00f3digo de prueba',
Language: 'Idioma',
'Code sample...': 'Ejemplo de c\u00f3digo...',
'Color Picker': 'Selector de colores',
R: 'R',
G: 'V',
B: 'A',
'Left to right': 'De izquierda a derecha',
'Right to left': 'De derecha a izquierda',
'Emoticons...': 'Emoticones...',
'Metadata and Document Properties': 'Metadatos y propiedades del documento',
Title: 'T\u00edtulo',
Keywords: 'Palabras clave',
Description: 'Descripci\u00f3n',
Robots: 'Robots',
Author: 'Autor',
Encoding: 'Codificaci\u00f3n',
Fullscreen: 'Pantalla completa',
Action: 'Acci\u00f3n',
Shortcut: 'Atajo',
Help: 'Ayuda',
Address: 'Direcci\u00f3n',
'Focus to menubar': 'Enfocar la barra del men\u00fa',
'Focus to toolbar': 'Enfocar la barra de herramientas',
'Focus to element path': 'Enfocar la ruta del elemento',
'Focus to contextual toolbar': 'Enfocar la barra de herramientas contextual',
'Insert link (if link plugin activated)': 'Insertar enlace (si el complemento de enlace est\u00e1 activado)',
'Save (if save plugin activated)': 'Guardar (si el componente de salvar est\u00e1 activado)',
'Find (if searchreplace plugin activated)': 'Buscar (si el complemento buscar-remplazar est\u00e1 activado)',
'Plugins installed ({0}):': 'Plugins instalados ({0}):',
'Premium plugins:': 'Complementos premium:',
'Learn more...': 'Aprende m\u00e1s...',
'You are using {0}': 'Estas usando {0}',
Plugins: 'Complementos',
'Handy Shortcuts': 'Accesos directos',
'Horizontal line': 'L\u00ednea horizontal',
'Insert\/edit image': 'Insertar\/editar imagen',
'Image description': 'Descripci\u00f3n de la imagen',
Source: 'Enlace',
Dimensions: 'Dimensiones',
'Constrain proportions': 'Restringir proporciones',
General: 'General',
Advanced: 'Avanzado',
Style: 'Estilo',
'Vertical space': 'Espacio vertical',
'Horizontal space': 'Espacio horizontal',
Border: 'Borde',
'Insert image': 'Insertar imagen',
'Image...': 'Imagen...',
'Image list': 'Lista de im\u00e1genes',
'Rotate counterclockwise': 'Girar a la izquierda',
'Rotate clockwise': 'Girar a la derecha',
'Flip vertically': 'Invertir verticalmente',
'Flip horizontally': 'Invertir horizontalmente',
'Edit image': 'Editar imagen',
'Image options': 'Opciones de imagen',
'Zoom in': 'Acercar',
'Zoom out': 'Alejar',
Crop: 'Recortar',
Resize: 'Redimensionar',
Orientation: 'Orientaci\u00f3n',
Brightness: 'Brillo',
Sharpen: 'Forma',
Contrast: 'Contraste',
'Color levels': 'Niveles de color',
Gamma: 'Gamma',
Invert: 'Invertir',
Apply: 'Aplicar',
Back: 'Atr\u00e1s',
'Insert date\/time': 'Insertar fecha\/hora',
'Date\/time': 'Fecha\/hora',
'Insert\/Edit Link': 'Insertar\/editar enlace',
'Insert\/edit link': 'Insertar\/editar enlace',
'Text to display': 'Texto para mostrar',
Url: 'URL',
'Open link in...': 'Abrir enlace en...',
'Current window': 'Ventana actual',
None: 'Ninguno',
'New window': 'Nueva ventana',
'Remove link': 'Quitar enlace',
Anchors: 'Anclas',
'Link...': 'Enlace...',
'Paste or type a link': 'Pega o introduce un enlace',
'The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?': 'El enlace que has introducido no parece ser una direcci\u00f3n de correo electr\u00f3nico. Quieres a\u00f1adir el prefijo necesario mailto: ?',
'The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?': 'El enlace que has introducido no parece ser una enlace externo. Quieres a\u00f1adir el prefijo necesario http:\/\/ ?',
'Link list': 'Lista de enlaces',
'Insert video': 'Insertar video',
'Insert\/edit video': 'Insertar\/editar video',
'Insert\/edit media': 'Insertar\/editar medio',
'Alternative source': 'Enlace alternativo',
'Alternative source URL': 'Origen de URL alternativo',
'Media poster (Image URL)': 'P\u00f3ster de medio (URL de imagen)',
'Paste your embed code below:': 'Pega tu c\u00f3digo embebido debajo',
Embed: 'Incrustado',
'Media...': 'Medios...',
'Nonbreaking space': 'Espacio fijo',
'Page break': 'Salto de p\u00e1gina',
'Paste as text': 'Pegar como texto',
Preview: 'Previsualizar',
'Print...': 'Imprimir...',
Save: 'Guardar',
Find: 'Buscar',
'Replace with': 'Reemplazar con',
Replace: 'Reemplazar',
'Replace all': 'Reemplazar todo',
Previous: 'Anterior',
Next: 'Siguiente',
'Find and replace...': 'Buscar y reemplazar...',
'Could not find the specified string.': 'No se encuentra la cadena de texto especificada',
'Match case': 'Coincidencia exacta',
'Find whole words only': 'Solo palabras completas',
'Spell check': 'Revisar ortograf\u00eda',
Ignore: 'Ignorar',
'Ignore all': 'Ignorar todos',
Finish: 'Finalizar',
'Add to Dictionary': 'A\u00f1adir al Diccionario',
'Insert table': 'Insertar tabla',
'Table properties': 'Propiedades de la tabla',
'Delete table': 'Eliminar tabla',
Cell: 'Celda',
Row: 'Fila',
Column: 'Columna',
'Cell properties': 'Propiedades de la celda',
'Merge cells': 'Combinar celdas',
'Split cell': 'Dividir celdas',
'Insert row before': 'Insertar fila antes',
'Insert row after': 'Insertar fila despu\u00e9s ',
'Delete row': 'Eliminar fila',
'Row properties': 'Propiedades de la fila',
'Cut row': 'Cortar fila',
'Copy row': 'Copiar fila',
'Paste row before': 'Pegar la fila antes',
'Paste row after': 'Pegar la fila despu\u00e9s',
'Insert column before': 'Insertar columna antes',
'Insert column after': 'Insertar columna despu\u00e9s',
'Delete column': 'Eliminar columna',
Cols: 'Columnas',
Rows: 'Filas',
Width: 'Ancho',
Height: 'Alto',
'Cell spacing': 'Espacio entre celdas',
'Cell padding': 'Relleno de celda',
'Show caption': 'Mostrar t\u00edtulo',
Left: 'Izquierda',
Center: 'Centrado',
Right: 'Derecha',
'Cell type': 'Tipo de celda',
Scope: '\u00c1mbito',
Alignment: 'Alineaci\u00f3n',
'H Align': 'Alineamiento Horizontal',
'V Align': 'Alineamiento Vertical',
Top: 'Arriba',
Middle: 'Centro',
Bottom: 'Abajo',
'Header cell': 'Celda de la cebecera',
'Row group': 'Grupo de filas',
'Column group': 'Grupo de columnas',
'Row type': 'Tipo de fila',
Header: 'Cabecera',
Body: 'Cuerpo',
Footer: 'Pie de p\u00e1gina',
'Border color': 'Color del borde',
'Insert template...': 'Insertar plantilla...',
Templates: 'Plantillas',
Template: 'Plantilla',
'Text color': 'Color del texto',
'Background color': 'Color de fondo',
'Custom...': 'Personalizar...',
'Custom color': 'Color personalizado',
'No color': 'Sin color',
'Remove color': 'Quitar color',
'Table of Contents': 'Tabla de contenidos',
'Show blocks': 'Mostrar bloques',
'Show invisible characters': 'Mostrar caracteres invisibles',
'Word count': 'Contar palabras',
Count: 'Recuento',
Document: 'Documento',
Selection: 'Selecci\u00f3n',
Words: 'Palabras',
'Words: {0}': 'Palabras: {0}',
'{0} words': '{0} palabras',
File: 'Archivo',
Edit: 'Editar',
Insert: 'Insertar',
View: 'Ver',
Format: 'Formato',
Table: 'Tabla',
Tools: 'Herramientas',
'Powered by {0}': 'Desarrollado por {0}',
'Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help': '\u00c1rea de texto enriquecido. Pulse ALT-F9 para el menu. Pulse ALT-F10 para la barra de herramientas. Pulse ALT-0 para ayuda',
'Image title': 'Titulo de imagen',
'Border width': 'Ancho de borde',
'Border style': 'Estilo de borde',
Error: 'Error',
Warn: 'Advertencia',
Valid: 'V\u00e1lido',
'To open the popup, press Shift+Enter': 'Para abrir el elemento emergente, pulse May\u00fas+Intro',
'Rich Text Area. Press ALT-0 for help.': '\u00c1rea de texto enriquecido. Pulse ALT-0 para abrir la ayuda.',
'System Font': 'Fuente de sistema',
'Failed to upload image: {0}': 'Fallo al cargar imagen: {0}',
'Failed to load plugin: {0} from url {1}': 'Fallo al cargar complemento: {0} desde URL {1}',
'Failed to load plugin url: {0}': 'Fallo al cargar URL del complemento: {0}',
'Failed to initialize plugin: {0}': 'Fallo al iniciar el complemento: {0}',
example: 'ejemplo',
Search: 'Buscar',
All: 'Todo',
Currency: 'Divisa',
Text: 'Texto',
Quotations: 'Comillas',
Mathematical: 'S\u00edmbolo matem\u00e1tico',
'Extended Latin': 'Latino extendido A',
Symbols: 'S\u00edmbolos',
Arrows: 'Flechas',
'User Defined': 'Definido por el usuario',
'dollar sign': 'signo de d\u00f3lar',
'currency sign': 'signo de divisa',
'euro-currency sign': 'signo de euro',
'colon sign': 'signo de dos puntos',
'cruzeiro sign': 'signo de cruceiro',
'french franc sign': 'signo de franco franc\u00e9s',
'lira sign': 'signo de lira',
'mill sign': 'signo de mill',
'naira sign': 'signo de naira',
'peseta sign': 'signo de peseta',
'rupee sign': 'signo de rupia',
'won sign': 'signo de won',
'new sheqel sign': 'signo de nuevo s\u00e9quel',
'dong sign': 'signo de dong',
'kip sign': 'signo de kip',
'tugrik sign': 'signo de tugrik',
'drachma sign': 'signo de dracma',
'german penny symbol': 'signo de penique alem\u00e1n',
'peso sign': 'signo de peso',
'guarani sign': 'signo de guaran\u00ed',
'austral sign': 'signo de austral',
'hryvnia sign': 'signo de grivna',
'cedi sign': 'signo de cedi',
'livre tournois sign': 'signo de libra tornesa',
'spesmilo sign': 'signo de spesmilo',
'tenge sign': 'signo de tenge',
'indian rupee sign': 'signo de rupia india',
'turkish lira sign': 'signo de lira turca',
'nordic mark sign': 'signo de marco n\u00f3rdico',
'manat sign': 'signo de manat',
'ruble sign': 'signo de rublo',
'yen character': 'car\u00e1cter de yen',
'yuan character': 'car\u00e1cter de yuan',
'yuan character, in hong kong and taiwan': 'car\u00e1cter de yuan en Hong Kong y Taiw\u00e1n',
'yen\/yuan character variant one': 'Variante uno de car\u00e1cter de yen\/yuan',
'Loading emoticons...': 'Cargando emoticonos...',
'Could not load emoticons': 'No se han podido cargar los emoticonos',
People: 'Personas',
'Animals and Nature': 'Animales y naturaleza',
'Food and Drink': 'Comida y bebida',
Activity: 'Actividad',
'Travel and Places': 'Viajes y lugares',
Objects: 'Objetos',
Flags: 'Banderas',
Characters: 'Caracteres',
'Characters (no spaces)': 'Caracteres (sin espacios)',
'{0} characters': '{0} caracteres',
'Error: Form submit field collision.': 'Error: Colisi\u00f3n de campo al enviar formulario.',
'Error: No form element found.': 'Error: No se encuentra ning\u00fan elemento de formulario.',
Update: 'Actualizar',
'Color swatch': 'Muestrario de colores',
Turquoise: 'Turquesa',
Green: 'Verde',
Blue: 'Azul',
Purple: 'P\u00farpura',
'Navy Blue': 'Azul marino',
'Dark Turquoise': 'Turquesa oscuro',
'Dark Green': 'Verde oscuro',
'Medium Blue': 'Azul medio',
'Medium Purple': 'P\u00farpura medio',
'Midnight Blue': 'Azul medio',
Yellow: 'Amarillo',
Orange: 'Naranja',
Red: 'Rojo',
'Light Gray': 'Gris claro',
Gray: 'Gris',
'Dark Yellow': 'Amarillo oscuro',
'Dark Orange': 'Naranja oscuro',
'Dark Red': 'Rojo oscuro',
'Medium Gray': 'Gris medio',
'Dark Gray': 'Gris oscuro',
'Light Green': 'Verde claro',
'Light Yellow': 'Amarillo claro',
'Light Red': 'Rojo claro',
'Light Purple': 'Morado claro',
'Light Blue': 'Azul claro',
'Dark Purple': 'Morado oscuro',
'Dark Blue': 'Azul oscuro',
Black: 'Negro',
White: 'Blanco',
'Switch to or from fullscreen mode': 'Activar o desactivar modo pantalla completa',
'Open help dialog': 'Abrir di\u00e1logo de ayuda',
history: 'historial',
styles: 'estilos',
formatting: 'formato',
alignment: 'alineaci\u00f3n',
indentation: 'sangr\u00eda',
'permanent pen': 'bol\u00edgrafo permanente',
comments: 'comentarios',
'Format Painter': 'Copiar formato',
'Insert\/edit iframe': 'Insertar\/editar iframe',
Capitalization: 'Uso de may\u00fasculas',
lowercase: 'min\u00fasculas',
UPPERCASE: 'MAY\u00daSCULAS',
'Title Case': 'Tipo T\u00edtulo',
'Permanent Pen Properties': 'Propiedades del bol\u00edgrafo permanente',
'Permanent pen properties...': 'Propiedades del bol\u00edgrafo permanente...',
Font: 'Fuente',
Size: 'Tama\u00f1o',
'More...': 'M\u00e1s...',
'Spellcheck Language': 'Corrector',
'Select...': 'Seleccionar...',
Preferences: 'Preferencias',
Yes: 'S\u00ed',
No: 'No',
'Keyboard Navigation': 'Navegaci\u00f3n con el teclado',
Version: 'Versi\u00f3n',
Anchor: 'Ancla',
'Special character': 'Car\u00e1cter especial',
'Code sample': 'Ejemplo de c\u00f3digo',
Color: 'Color',
Emoticons: 'Emoticonos',
'Document properties': 'Propiedades del documento',
Image: 'Imagen',
'Insert link': 'Insertar enlace',
Target: 'Destino',
Link: 'Enlace',
Poster: 'Miniatura',
Media: 'Media',
Print: 'Imprimir',
Prev: 'Anterior',
'Find and replace': 'Buscar y reemplazar',
'Whole words': 'Palabras completas',
Spellcheck: 'Corrector ortogr\u00e1fico',
Caption: 'Subt\u00edtulo',
'Insert template': 'Insertar plantilla'
})

@ -1,389 +0,0 @@
tinymce.addI18n('zh_CN',{
"Redo": "\u91cd\u505a",
"Undo": "\u64a4\u9500",
"Cut": "\u526a\u5207",
"Copy": "\u590d\u5236",
"Paste": "\u7c98\u8d34",
"Select all": "\u5168\u9009",
"New document": "\u65b0\u6587\u4ef6",
"Ok": "\u786e\u5b9a",
"Cancel": "\u53d6\u6d88",
"Visual aids": "\u7f51\u683c\u7ebf",
"Bold": "\u7c97\u4f53",
"Italic": "\u659c\u4f53",
"Underline": "\u4e0b\u5212\u7ebf",
"Strikethrough": "\u5220\u9664\u7ebf",
"Superscript": "\u4e0a\u6807",
"Subscript": "\u4e0b\u6807",
"Clear formatting": "\u6e05\u9664\u683c\u5f0f",
"Align left": "\u5de6\u8fb9\u5bf9\u9f50",
"Align center": "\u4e2d\u95f4\u5bf9\u9f50",
"Align right": "\u53f3\u8fb9\u5bf9\u9f50",
"Justify": "\u4e24\u7aef\u5bf9\u9f50",
"Bullet list": "\u9879\u76ee\u7b26\u53f7",
"Numbered list": "\u7f16\u53f7\u5217\u8868",
"Decrease indent": "\u51cf\u5c11\u7f29\u8fdb",
"Increase indent": "\u589e\u52a0\u7f29\u8fdb",
"Close": "\u5173\u95ed",
"Formats": "\u683c\u5f0f",
"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u4f60\u7684\u6d4f\u89c8\u5668\u4e0d\u652f\u6301\u6253\u5f00\u526a\u8d34\u677f\uff0c\u8bf7\u4f7f\u7528Ctrl+X\/C\/V\u7b49\u5feb\u6377\u952e\u3002",
"Headers": "\u6807\u9898",
"Header 1": "\u6807\u98981",
"Header 2": "\u6807\u98982",
"Header 3": "\u6807\u98983",
"Header 4": "\u6807\u98984",
"Header 5": "\u6807\u98985",
"Header 6": "\u6807\u98986",
"Headings": "\u6807\u9898",
"Heading 1": "\u6807\u98981",
"Heading 2": "\u6807\u98982",
"Heading 3": "\u6807\u98983",
"Heading 4": "\u6807\u98984",
"Heading 5": "\u6807\u98985",
"Heading 6": "\u6807\u98986",
"Preformatted": "\u9884\u5148\u683c\u5f0f\u5316\u7684",
"Div": "Div",
"Pre": "Pre",
"Code": "\u4ee3\u7801",
"Paragraph": "\u6bb5\u843d",
"Blockquote": "\u5f15\u6587\u533a\u5757",
"Inline": "\u6587\u672c",
"Blocks": "\u57fa\u5757",
"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u5f53\u524d\u4e3a\u7eaf\u6587\u672c\u7c98\u8d34\u6a21\u5f0f\uff0c\u518d\u6b21\u70b9\u51fb\u53ef\u4ee5\u56de\u5230\u666e\u901a\u7c98\u8d34\u6a21\u5f0f\u3002",
"Fonts": "\u5b57\u4f53",
"Font Sizes": "\u5b57\u53f7",
"Class": "\u7c7b\u578b",
"Browse for an image": "\u6d4f\u89c8\u56fe\u50cf",
"OR": "\u6216",
"Drop an image here": "\u62d6\u653e\u4e00\u5f20\u56fe\u50cf\u81f3\u6b64",
"Upload": "\u4e0a\u4f20",
"Block": "\u5757",
"Align": "\u5bf9\u9f50",
"Default": "\u9ed8\u8ba4",
"Circle": "\u7a7a\u5fc3\u5706",
"Disc": "\u5b9e\u5fc3\u5706",
"Square": "\u65b9\u5757",
"Lower Alpha": "\u5c0f\u5199\u82f1\u6587\u5b57\u6bcd",
"Lower Greek": "\u5c0f\u5199\u5e0c\u814a\u5b57\u6bcd",
"Lower Roman": "\u5c0f\u5199\u7f57\u9a6c\u5b57\u6bcd",
"Upper Alpha": "\u5927\u5199\u82f1\u6587\u5b57\u6bcd",
"Upper Roman": "\u5927\u5199\u7f57\u9a6c\u5b57\u6bcd",
"Anchor...": "\u951a\u70b9...",
"Name": "\u540d\u79f0",
"Id": "\u6807\u8bc6\u7b26",
"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "\u6807\u8bc6\u7b26\u5e94\u8be5\u4ee5\u5b57\u6bcd\u5f00\u5934\uff0c\u540e\u8ddf\u5b57\u6bcd\u3001\u6570\u5b57\u3001\u7834\u6298\u53f7\u3001\u70b9\u3001\u5192\u53f7\u6216\u4e0b\u5212\u7ebf\u3002",
"You have unsaved changes are you sure you want to navigate away?": "\u4f60\u8fd8\u6709\u6587\u6863\u5c1a\u672a\u4fdd\u5b58\uff0c\u786e\u5b9a\u8981\u79bb\u5f00\uff1f",
"Restore last draft": "\u6062\u590d\u4e0a\u6b21\u7684\u8349\u7a3f",
"Special characters...": "\u7279\u6b8a\u5b57\u7b26...",
"Source code": "\u6e90\u4ee3\u7801",
"Insert\/Edit code sample": "\u63d2\u5165\/\u7f16\u8f91\u4ee3\u7801\u793a\u4f8b",
"Language": "\u8bed\u8a00",
"Code sample...": "\u793a\u4f8b\u4ee3\u7801...",
"Color Picker": "\u9009\u8272\u5668",
"R": "R",
"G": "G",
"B": "B",
"Left to right": "\u4ece\u5de6\u5230\u53f3",
"Right to left": "\u4ece\u53f3\u5230\u5de6",
"Emoticons...": "\u8868\u60c5\u7b26\u53f7...",
"Metadata and Document Properties": "\u5143\u6570\u636e\u548c\u6587\u6863\u5c5e\u6027",
"Title": "\u6807\u9898",
"Keywords": "\u5173\u952e\u8bcd",
"Description": "\u63cf\u8ff0",
"Robots": "\u673a\u5668\u4eba",
"Author": "\u4f5c\u8005",
"Encoding": "\u7f16\u7801",
"Fullscreen": "\u5168\u5c4f",
"Action": "\u64cd\u4f5c",
"Shortcut": "\u5feb\u6377\u952e",
"Help": "\u5e2e\u52a9",
"Address": "\u5730\u5740",
"Focus to menubar": "\u79fb\u52a8\u7126\u70b9\u5230\u83dc\u5355\u680f",
"Focus to toolbar": "\u79fb\u52a8\u7126\u70b9\u5230\u5de5\u5177\u680f",
"Focus to element path": "\u79fb\u52a8\u7126\u70b9\u5230\u5143\u7d20\u8def\u5f84",
"Focus to contextual toolbar": "\u79fb\u52a8\u7126\u70b9\u5230\u4e0a\u4e0b\u6587\u83dc\u5355",
"Insert link (if link plugin activated)": "\u63d2\u5165\u94fe\u63a5 (\u5982\u679c\u94fe\u63a5\u63d2\u4ef6\u5df2\u6fc0\u6d3b)",
"Save (if save plugin activated)": "\u4fdd\u5b58(\u5982\u679c\u4fdd\u5b58\u63d2\u4ef6\u5df2\u6fc0\u6d3b)",
"Find (if searchreplace plugin activated)": "\u67e5\u627e(\u5982\u679c\u67e5\u627e\u66ff\u6362\u63d2\u4ef6\u5df2\u6fc0\u6d3b)",
"Plugins installed ({0}):": "\u5df2\u5b89\u88c5\u63d2\u4ef6 ({0}):",
"Premium plugins:": "\u4f18\u79c0\u63d2\u4ef6\uff1a",
"Learn more...": "\u4e86\u89e3\u66f4\u591a...",
"You are using {0}": "\u4f60\u6b63\u5728\u4f7f\u7528 {0}",
"Plugins": "\u63d2\u4ef6",
"Handy Shortcuts": "\u5feb\u6377\u952e",
"Horizontal line": "\u6c34\u5e73\u5206\u5272\u7ebf",
"Insert\/edit image": "\u63d2\u5165\/\u7f16\u8f91\u56fe\u7247",
"Image description": "\u56fe\u7247\u63cf\u8ff0",
"Source": "\u5730\u5740",
"Dimensions": "\u5927\u5c0f",
"Constrain proportions": "\u4fdd\u6301\u7eb5\u6a2a\u6bd4",
"General": "\u666e\u901a",
"Advanced": "\u9ad8\u7ea7",
"Style": "\u6837\u5f0f",
"Vertical space": "\u5782\u76f4\u8fb9\u8ddd",
"Horizontal space": "\u6c34\u5e73\u8fb9\u8ddd",
"Border": "\u8fb9\u6846",
"Insert image": "\u63d2\u5165\u56fe\u7247",
"Image...": "\u56fe\u7247...",
"Image list": "\u56fe\u7247\u5217\u8868",
"Rotate counterclockwise": "\u9006\u65f6\u9488\u65cb\u8f6c",
"Rotate clockwise": "\u987a\u65f6\u9488\u65cb\u8f6c",
"Flip vertically": "\u5782\u76f4\u7ffb\u8f6c",
"Flip horizontally": "\u6c34\u5e73\u7ffb\u8f6c",
"Edit image": "\u7f16\u8f91\u56fe\u7247",
"Image options": "\u56fe\u7247\u9009\u9879",
"Zoom in": "\u653e\u5927",
"Zoom out": "\u7f29\u5c0f",
"Crop": "\u88c1\u526a",
"Resize": "\u8c03\u6574\u5927\u5c0f",
"Orientation": "\u65b9\u5411",
"Brightness": "\u4eae\u5ea6",
"Sharpen": "\u9510\u5316",
"Contrast": "\u5bf9\u6bd4\u5ea6",
"Color levels": "\u989c\u8272\u5c42\u6b21",
"Gamma": "\u4f3d\u9a6c\u503c",
"Invert": "\u53cd\u8f6c",
"Apply": "\u5e94\u7528",
"Back": "\u540e\u9000",
"Insert date\/time": "\u63d2\u5165\u65e5\u671f\/\u65f6\u95f4",
"Date\/time": "\u65e5\u671f\/\u65f6\u95f4",
"Insert\/Edit Link": "\u63d2\u5165\/\u7f16\u8f91\u94fe\u63a5",
"Insert\/edit link": "\u63d2\u5165\/\u7f16\u8f91\u94fe\u63a5",
"Text to display": "\u663e\u793a\u6587\u5b57",
"Url": "\u5730\u5740",
"Open link in...": "\u94fe\u63a5\u6253\u5f00\u4f4d\u7f6e...",
"Current window": "\u5f53\u524d\u7a97\u53e3",
"None": "\u65e0",
"New window": "\u5728\u65b0\u7a97\u53e3\u6253\u5f00",
"Remove link": "\u5220\u9664\u94fe\u63a5",
"Anchors": "\u951a\u70b9",
"Link...": "\u94fe\u63a5...",
"Paste or type a link": "\u7c98\u8d34\u6216\u8f93\u5165\u94fe\u63a5",
"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u4e3a\u90ae\u4ef6\u5730\u5740\uff0c\u9700\u8981\u52a0\u4e0amailto:\u524d\u7f00\u5417\uff1f",
"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u5c5e\u4e8e\u5916\u90e8\u94fe\u63a5\uff0c\u9700\u8981\u52a0\u4e0ahttp:\/\/:\u524d\u7f00\u5417\uff1f",
"Link list": "\u94fe\u63a5\u5217\u8868",
"Insert video": "\u63d2\u5165\u89c6\u9891",
"Insert\/edit video": "\u63d2\u5165\/\u7f16\u8f91\u89c6\u9891",
"Insert\/edit media": "\u63d2\u5165\/\u7f16\u8f91\u5a92\u4f53",
"Alternative source": "\u955c\u50cf",
"Alternative source URL": "\u66ff\u4ee3\u6765\u6e90\u7f51\u5740",
"Media poster (Image URL)": "\u5c01\u9762(\u56fe\u7247\u5730\u5740)",
"Paste your embed code below:": "\u5c06\u5185\u5d4c\u4ee3\u7801\u7c98\u8d34\u5728\u4e0b\u9762:",
"Embed": "\u5185\u5d4c",
"Media...": "\u591a\u5a92\u4f53...",
"Nonbreaking space": "\u4e0d\u95f4\u65ad\u7a7a\u683c",
"Page break": "\u5206\u9875\u7b26",
"Paste as text": "\u7c98\u8d34\u4e3a\u6587\u672c",
"Preview": "\u9884\u89c8",
"Print...": "\u6253\u5370...",
"Save": "\u4fdd\u5b58",
"Find": "\u67e5\u627e",
"Replace with": "\u66ff\u6362\u4e3a",
"Replace": "\u66ff\u6362",
"Replace all": "\u5168\u90e8\u66ff\u6362",
"Previous": "\u4e0a\u4e00\u4e2a",
"Next": "\u4e0b\u4e00\u4e2a",
"Find and replace...": "\u67e5\u627e\u5e76\u66ff\u6362...",
"Could not find the specified string.": "\u672a\u627e\u5230\u641c\u7d22\u5185\u5bb9.",
"Match case": "\u533a\u5206\u5927\u5c0f\u5199",
"Find whole words only": "\u5168\u5b57\u5339\u914d",
"Spell check": "\u62fc\u5199\u68c0\u67e5",
"Ignore": "\u5ffd\u7565",
"Ignore all": "\u5168\u90e8\u5ffd\u7565",
"Finish": "\u5b8c\u6210",
"Add to Dictionary": "\u6dfb\u52a0\u5230\u5b57\u5178",
"Insert table": "\u63d2\u5165\u8868\u683c",
"Table properties": "\u8868\u683c\u5c5e\u6027",
"Delete table": "\u5220\u9664\u8868\u683c",
"Cell": "\u5355\u5143\u683c",
"Row": "\u884c",
"Column": "\u5217",
"Cell properties": "\u5355\u5143\u683c\u5c5e\u6027",
"Merge cells": "\u5408\u5e76\u5355\u5143\u683c",
"Split cell": "\u62c6\u5206\u5355\u5143\u683c",
"Insert row before": "\u5728\u4e0a\u65b9\u63d2\u5165",
"Insert row after": "\u5728\u4e0b\u65b9\u63d2\u5165",
"Delete row": "\u5220\u9664\u884c",
"Row properties": "\u884c\u5c5e\u6027",
"Cut row": "\u526a\u5207\u884c",
"Copy row": "\u590d\u5236\u884c",
"Paste row before": "\u7c98\u8d34\u5230\u4e0a\u65b9",
"Paste row after": "\u7c98\u8d34\u5230\u4e0b\u65b9",
"Insert column before": "\u5728\u5de6\u4fa7\u63d2\u5165",
"Insert column after": "\u5728\u53f3\u4fa7\u63d2\u5165",
"Delete column": "\u5220\u9664\u5217",
"Cols": "\u5217",
"Rows": "\u884c",
"Width": "\u5bbd",
"Height": "\u9ad8",
"Cell spacing": "\u5355\u5143\u683c\u5916\u95f4\u8ddd",
"Cell padding": "\u5355\u5143\u683c\u5185\u8fb9\u8ddd",
"Show caption": "\u663e\u793a\u6807\u9898",
"Left": "\u5de6\u5bf9\u9f50",
"Center": "\u5c45\u4e2d",
"Right": "\u53f3\u5bf9\u9f50",
"Cell type": "\u5355\u5143\u683c\u7c7b\u578b",
"Scope": "\u8303\u56f4",
"Alignment": "\u5bf9\u9f50\u65b9\u5f0f",
"H Align": "\u6c34\u5e73\u5bf9\u9f50",
"V Align": "\u5782\u76f4\u5bf9\u9f50",
"Top": "\u9876\u90e8\u5bf9\u9f50",
"Middle": "\u5782\u76f4\u5c45\u4e2d",
"Bottom": "\u5e95\u90e8\u5bf9\u9f50",
"Header cell": "\u8868\u5934\u5355\u5143\u683c",
"Row group": "\u884c\u7ec4",
"Column group": "\u5217\u7ec4",
"Row type": "\u884c\u7c7b\u578b",
"Header": "\u8868\u5934",
"Body": "\u8868\u4f53",
"Footer": "\u8868\u5c3e",
"Border color": "\u8fb9\u6846\u989c\u8272",
"Insert template...": "\u63d2\u5165\u6a21\u677f...",
"Templates": "\u6a21\u677f",
"Template": "\u6a21\u677f",
"Text color": "\u6587\u5b57\u989c\u8272",
"Background color": "\u80cc\u666f\u8272",
"Custom...": "\u81ea\u5b9a\u4e49...",
"Custom color": "\u81ea\u5b9a\u4e49\u989c\u8272",
"No color": "\u65e0",
"Remove color": "\u79fb\u9664\u989c\u8272",
"Table of Contents": "\u5185\u5bb9\u5217\u8868",
"Show blocks": "\u663e\u793a\u533a\u5757\u8fb9\u6846",
"Show invisible characters": "\u663e\u793a\u4e0d\u53ef\u89c1\u5b57\u7b26",
"Word count": "\u5b57\u6570",
"Words: {0}": "\u5b57\u6570\uff1a{0}",
"{0} words": "{0} \u5b57",
"File": "\u6587\u4ef6",
"Edit": "\u7f16\u8f91",
"Insert": "\u63d2\u5165",
"View": "\u89c6\u56fe",
"Format": "\u683c\u5f0f",
"Table": "\u8868\u683c",
"Tools": "\u5de5\u5177",
"Powered by {0}": "\u7531{0}\u9a71\u52a8",
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u5728\u7f16\u8f91\u533a\u6309ALT-F9\u6253\u5f00\u83dc\u5355\uff0c\u6309ALT-F10\u6253\u5f00\u5de5\u5177\u680f\uff0c\u6309ALT-0\u67e5\u770b\u5e2e\u52a9",
"Image title": "\u56fe\u7247\u6807\u9898",
"Border width": "\u8fb9\u6846\u5bbd\u5ea6",
"Border style": "\u8fb9\u6846\u6837\u5f0f",
"Error": "\u9519\u8bef",
"Warn": "\u8b66\u544a",
"Valid": "\u6709\u6548",
"To open the popup, press Shift+Enter": "\u6309Shitf+Enter\u952e\u6253\u5f00\u5bf9\u8bdd\u6846",
"Rich Text Area. Press ALT-0 for help.": "\u7f16\u8f91\u533a\u3002\u6309Alt+0\u952e\u6253\u5f00\u5e2e\u52a9\u3002",
"System Font": "\u7cfb\u7edf\u5b57\u4f53",
"Failed to upload image: {0}": "\u56fe\u7247\u4e0a\u4f20\u5931\u8d25: {0}",
"Failed to load plugin: {0} from url {1}": "\u63d2\u4ef6\u52a0\u8f7d\u5931\u8d25: {0} \u6765\u81ea\u94fe\u63a5 {1}",
"Failed to load plugin url: {0}": "\u63d2\u4ef6\u52a0\u8f7d\u5931\u8d25 \u94fe\u63a5: {0}",
"Failed to initialize plugin: {0}": "\u63d2\u4ef6\u521d\u59cb\u5316\u5931\u8d25: {0}",
"example": "\u793a\u4f8b",
"Search": "\u641c\u7d22",
"All": "\u5168\u90e8",
"Currency": "\u8d27\u5e01",
"Text": "\u6587\u5b57",
"Quotations": "\u5f15\u7528",
"Mathematical": "\u6570\u5b66",
"Extended Latin": "\u62c9\u4e01\u8bed\u6269\u5145",
"Symbols": "\u7b26\u53f7",
"Arrows": "\u7bad\u5934",
"User Defined": "\u81ea\u5b9a\u4e49",
"dollar sign": "\u7f8e\u5143\u7b26\u53f7",
"currency sign": "\u8d27\u5e01\u7b26\u53f7",
"euro-currency sign": "\u6b27\u5143\u7b26\u53f7",
"colon sign": "\u5192\u53f7",
"cruzeiro sign": "\u514b\u9c81\u8d5b\u7f57\u5e01\u7b26\u53f7",
"french franc sign": "\u6cd5\u90ce\u7b26\u53f7",
"lira sign": "\u91cc\u62c9\u7b26\u53f7",
"mill sign": "\u5bc6\u5c14\u7b26\u53f7",
"naira sign": "\u5948\u62c9\u7b26\u53f7",
"peseta sign": "\u6bd4\u585e\u5854\u7b26\u53f7",
"rupee sign": "\u5362\u6bd4\u7b26\u53f7",
"won sign": "\u97e9\u5143\u7b26\u53f7",
"new sheqel sign": "\u65b0\u8c22\u514b\u5c14\u7b26\u53f7",
"dong sign": "\u8d8a\u5357\u76fe\u7b26\u53f7",
"kip sign": "\u8001\u631d\u57fa\u666e\u7b26\u53f7",
"tugrik sign": "\u56fe\u683c\u91cc\u514b\u7b26\u53f7",
"drachma sign": "\u5fb7\u62c9\u514b\u9a6c\u7b26\u53f7",
"german penny symbol": "\u5fb7\u56fd\u4fbf\u58eb\u7b26\u53f7",
"peso sign": "\u6bd4\u7d22\u7b26\u53f7",
"guarani sign": "\u74dc\u62c9\u5c3c\u7b26\u53f7",
"austral sign": "\u6fb3\u5143\u7b26\u53f7",
"hryvnia sign": "\u683c\u91cc\u592b\u5c3c\u4e9a\u7b26\u53f7",
"cedi sign": "\u585e\u5730\u7b26\u53f7",
"livre tournois sign": "\u91cc\u5f17\u5f17\u5c14\u7b26\u53f7",
"spesmilo sign": "spesmilo\u7b26\u53f7",
"tenge sign": "\u575a\u6208\u7b26\u53f7",
"indian rupee sign": "\u5370\u5ea6\u5362\u6bd4",
"turkish lira sign": "\u571f\u8033\u5176\u91cc\u62c9",
"nordic mark sign": "\u5317\u6b27\u9a6c\u514b",
"manat sign": "\u9a6c\u7eb3\u7279\u7b26\u53f7",
"ruble sign": "\u5362\u5e03\u7b26\u53f7",
"yen character": "\u65e5\u5143\u5b57\u6837",
"yuan character": "\u4eba\u6c11\u5e01\u5143\u5b57\u6837",
"yuan character, in hong kong and taiwan": "\u5143\u5b57\u6837\uff08\u6e2f\u53f0\u5730\u533a\uff09",
"yen\/yuan character variant one": "\u5143\u5b57\u6837\uff08\u5927\u5199\uff09",
"Loading emoticons...": "\u52a0\u8f7d\u8868\u60c5\u7b26\u53f7...",
"Could not load emoticons": "\u4e0d\u80fd\u52a0\u8f7d\u8868\u60c5\u7b26\u53f7",
"People": "\u4eba\u7c7b",
"Animals and Nature": "\u52a8\u7269\u548c\u81ea\u7136",
"Food and Drink": "\u98df\u7269\u548c\u996e\u54c1",
"Activity": "\u6d3b\u52a8",
"Travel and Places": "\u65c5\u6e38\u548c\u5730\u70b9",
"Objects": "\u7269\u4ef6",
"Flags": "\u65d7\u5e1c",
"Characters": "\u5b57\u7b26",
"Characters (no spaces)": "\u5b57\u7b26(\u65e0\u7a7a\u683c)",
"Error: Form submit field collision.": "\u9519\u8bef: \u8868\u5355\u63d0\u4ea4\u5b57\u6bb5\u51b2\u7a81\u3002",
"Error: No form element found.": "\u9519\u8bef: \u6ca1\u6709\u8868\u5355\u63a7\u4ef6\u3002",
"Update": "\u66f4\u65b0",
"Color swatch": "\u989c\u8272\u6837\u672c",
"Turquoise": "\u9752\u7eff\u8272",
"Green": "\u7eff\u8272",
"Blue": "\u84dd\u8272",
"Purple": "\u7d2b\u8272",
"Navy Blue": "\u6d77\u519b\u84dd",
"Dark Turquoise": "\u6df1\u84dd\u7eff\u8272",
"Dark Green": "\u6df1\u7eff\u8272",
"Medium Blue": "\u4e2d\u84dd\u8272",
"Medium Purple": "\u4e2d\u7d2b\u8272",
"Midnight Blue": "\u6df1\u84dd\u8272",
"Yellow": "\u9ec4\u8272",
"Orange": "\u6a59\u8272",
"Red": "\u7ea2\u8272",
"Light Gray": "\u6d45\u7070\u8272",
"Gray": "\u7070\u8272",
"Dark Yellow": "\u6697\u9ec4\u8272",
"Dark Orange": "\u6df1\u6a59\u8272",
"Dark Red": "\u6df1\u7ea2\u8272",
"Medium Gray": "\u4e2d\u7070\u8272",
"Dark Gray": "\u6df1\u7070\u8272",
"Black": "\u9ed1\u8272",
"White": "\u767d\u8272",
"Switch to or from fullscreen mode": "\u5207\u6362\u5168\u5c4f\u6a21\u5f0f",
"Open help dialog": "\u6253\u5f00\u5e2e\u52a9\u5bf9\u8bdd\u6846",
"history": "\u5386\u53f2",
"styles": "\u6837\u5f0f",
"formatting": "\u683c\u5f0f\u5316",
"alignment": "\u5bf9\u9f50",
"indentation": "\u7f29\u8fdb",
"permanent pen": "\u8bb0\u53f7\u7b14",
"comments": "\u5907\u6ce8",
"Anchor": "\u951a\u70b9",
"Special character": "\u7279\u6b8a\u7b26\u53f7",
"Code sample": "\u4ee3\u7801\u793a\u4f8b",
"Color": "\u989c\u8272",
"Emoticons": "\u8868\u60c5",
"Document properties": "\u6587\u6863\u5c5e\u6027",
"Image": "\u56fe\u7247",
"Insert link": "\u63d2\u5165\u94fe\u63a5",
"Target": "\u6253\u5f00\u65b9\u5f0f",
"Link": "\u94fe\u63a5",
"Poster": "\u5c01\u9762",
"Media": "\u5a92\u4f53",
"Print": "\u6253\u5370",
"Prev": "\u4e0a\u4e00\u4e2a",
"Find and replace": "\u67e5\u627e\u548c\u66ff\u6362",
"Whole words": "\u5168\u5b57\u5339\u914d",
"Spellcheck": "\u62fc\u5199\u68c0\u67e5",
"Caption": "\u6807\u9898",
"Insert template": "\u63d2\u5165\u6a21\u677f"
});

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save