Pārlūkot izejas kodu

Merge branch 'master' of http://git.longjoe.com/shuiping150/JLH

chen_yjin 1 mēnesi atpakaļ
vecāks
revīzija
2ee921f4ae
34 mainītis faili ar 1509 papildinājumiem un 186 dzēšanām
  1. 27 0
      JLHHJSvr/BLL/UserHelper.cs
  2. 23 0
      JLHHJSvr/Com/FormulaCheck.cs
  3. 23 0
      JLHHJSvr/Com/GetFormulaVarList.cs
  4. 27 0
      JLHHJSvr/Com/UnLockUser.cs
  5. 10 0
      JLHHJSvr/DBA/DBModle/u_user_jlhprice.cs
  6. 175 0
      JLHHJSvr/DataStore/_Mapper_fixedParamters.xml
  7. 71 0
      JLHHJSvr/Excutor/FormulaCheckExcutor.cs
  8. 94 0
      JLHHJSvr/Excutor/GetFormulaVarListExcutor.cs
  9. 1 1
      JLHHJSvr/Excutor/GetUserListExcutor.cs
  10. 79 29
      JLHHJSvr/Excutor/LoginExcutor.cs
  11. 53 0
      JLHHJSvr/Excutor/UnLockUserExcutor.cs
  12. 3 0
      JLHHJSvr/GlobalVar/GlobalVar.cs
  13. 7 4
      JLHHJSvr/JLHHJSvr.csproj
  14. 7 0
      JLHHJSvr/LJFrameWork/Tools/LJExprParser.cs
  15. 3 1
      JLHWEB/package.json
  16. 3 0
      JLHWEB/src/api/interface/index.ts
  17. 18 0
      JLHWEB/src/api/modules/basicinfo.ts
  18. 10 5
      JLHWEB/src/components/LjDetail/index.vue
  19. 3 1
      JLHWEB/src/languages/modules/zh-cn/sys.json
  20. 153 83
      JLHWEB/src/main.ts
  21. 3 1
      JLHWEB/src/styles/var/vxe-variable.scss
  22. 15 1
      JLHWEB/src/views/baseinfo/bednetvar/index.vue
  23. 11 1
      JLHWEB/src/views/baseinfo/user/detail.vue
  24. 23 2
      JLHWEB/src/views/baseinfo/user/index.vue
  25. 44 30
      JLHWEB/src/views/erpapi/mattressInterface/detail.vue
  26. 10 4
      JLHWEB/src/views/erpapi/mattressInterface/hooks/index.tsx
  27. 2 2
      JLHWEB/src/views/quote/bednetQuote/components/FormulaItem.vue
  28. 2 2
      JLHWEB/src/views/quote/mattressQuote/components/AllFormula.vue
  29. 4 4
      JLHWEB/src/views/quote/mattressQuote/components/Statistic.vue
  30. 11 5
      JLHWEB/src/views/quote/mattressQuote/detail.vue
  31. 8 7
      JLHWEB/src/views/quote/mattressQuote/hooks/cpQuote.ts
  32. 5 3
      JLHWEB/src/views/quote/mattressQuote/hooks/index.tsx
  33. 396 0
      JLHWEB/src/views/system/formula-editor/editor.vue
  34. 185 0
      JLHWEB/src/views/system/formula-editor/index.vue

+ 27 - 0
JLHHJSvr/BLL/UserHelper.cs

@@ -9,6 +9,7 @@ using LJLib.DAL.SQL;
 using LJLib.Tools.DEncrypt;
 using JLHHJSvr.LJException;
 using System.Linq;
+using DirectService.Tools;
 
 namespace JLHHJSvr.BLL
 {
@@ -290,5 +291,31 @@ namespace JLHHJSvr.BLL
 
             return rslt;
         }
+        /// <summary>
+        /// 判断用户是否已锁定
+        /// </summary>
+        /// <param name="user"></param>
+        /// <returns></returns>
+        public static bool IsLocked(u_user_jlhprice user)
+        {
+            // 检查是否在最近一个月内累计5次错误
+            var cutoff = DateTime.UtcNow.AddMonths(-1);
+            return user.empid != 0 && user.access_failed_count >= 5
+                    && user.last_failed_attempt_time >= cutoff;
+        }
+        /// <summary>
+        /// 账号解锁
+        /// </summary>
+        /// <param name="user"></param>
+        /// <returns></returns>
+        public static bool UnLock(SqlCommand cmd, List<int> empids)
+        {
+            if (empids == null && empids.Count <= 0) return false;
+            cmd.CommandText = $@"UPDATE u_user_jlhprice SET access_failed_count = 0,
+                                    last_failed_attempt_time = GETUTCDATE()
+                                WHERE u_user_jlhprice.empid IN {ListEx.getString(empids)}";
+            cmd.Parameters.Clear();
+            return cmd.ExecuteNonQuery() == 1;
+        }
     }
 }

+ 23 - 0
JLHHJSvr/Com/FormulaCheck.cs

@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using JLHHJSvr.Com.Model;
+using LJLib.Net.SPI.Com;
+
+namespace JLHHJSvr.Com
+{
+    public sealed class FormulaCheckRequest : ILJRequest<FormulaCheckResponse>
+    {
+        public override string GetApiName()
+        {
+            return "FormulaCheck";
+        }
+        public string token { get; set; }
+        public string formula { get; set; }
+    }
+
+    public sealed class FormulaCheckResponse : LJResponse
+    {
+    }
+}

+ 23 - 0
JLHHJSvr/Com/GetFormulaVarList.cs

@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using JLHHJSvr.Com.Model;
+using LJLib.Net.SPI.Com;
+
+namespace JLHHJSvr.Com
+{
+    public sealed class GetFormulaVarListRequest : ILJRequest<GetFormulaVarListResponse>
+    {
+        public override string GetApiName()
+        {
+            return "GetFormulaVarList";
+        }
+        public string token { get; set; }
+    }
+
+    public sealed class GetFormulaVarListResponse : LJResponse
+    {
+        public List<Recursion2> recursionList { get; set; }
+    }
+}

+ 27 - 0
JLHHJSvr/Com/UnLockUser.cs

@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using JLHHJSvr.Com.Model;
+using JLHHJSvr.DBA.DBModle;
+using LJLib.Net.SPI.Com;
+
+namespace JLHHJSvr.Com
+{
+    /// <summary>
+    /// 获取用户列表
+    /// </summary>
+    public sealed class UnLockUserRequest : ILJRequest<UnLockUserResponse>
+    {
+        public override string GetApiName()
+        {
+            return "UnLockUser";
+        }
+        public string token { get; set; }
+        public List<int> useridList { get; set; }
+    }
+
+    public sealed class UnLockUserResponse : LJResponse
+    {
+    }
+}

+ 10 - 0
JLHHJSvr/DBA/DBModle/u_user_jlhprice.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
+using JLHHJSvr.BLL;
 using LJLib.DAL.SQL;
 
 namespace JLHHJSvr.DBA.DBModle
@@ -32,5 +33,14 @@ namespace JLHHJSvr.DBA.DBModle
         public string pricelist_seestr { get; set; }
         public string pricelist_editstr { get; set; }
         public int usermode { get; set; }
+        public int access_failed_count { get; set; }
+        public DateTime? last_failed_attempt_time { get; set; }
+        public bool isLocked
+        {
+            get
+            {
+                return UserHelper.IsLocked(this);
+            }
+        }
     }
 }

+ 175 - 0
JLHHJSvr/DataStore/_Mapper_fixedParamters.xml

@@ -0,0 +1,175 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<data>
+  <json>
+    [
+      { label: "床垫宽", value: 1 },
+      { label: "床垫长", value: 1 },
+      { label: "床垫高", value: 1 },
+      { label: "规格宽", value: 1 },
+      { label: "规格长", value: 1 },
+      { label: "规格高", value: 1 },
+      { label: "双簧", value: 1 },
+      { label: "弹叉数量", value: 1 },
+      { label: "包装方式", value: 1 },
+      { label: "包装数量", value: 1 },
+      { label: "运输方式", value: 1 },
+      { label: "包装材料", value: 1 },
+      { label: "床网类别", value: 1 },
+      { label: "工厂利润率", value: 1 },
+      { label: "部门利润率", value: 1 },
+      { label: "佣金点数", value: 1 },
+      { label: "额外点数", value: 1 },
+      { label: "额外费用", value: 1 },
+      { label: "汇率", value: 1 },
+      { label: "税率", value: 1 },
+      { label: "卷包", value: 1 },
+      { label: "大小单系数", value: 1 },
+      { label: "大小单类型", value: 1 },
+      { label: "弹簧排列个数-宽", value: 1 },
+      { label: "弹簧排列个数-长", value: 1 },
+      { label: "弹簧计算个数(宽)", value: 1 },
+      { label: "弹簧计算个数(长)", value: 1 },
+      { label: "边铁条数", value: 1 },
+      { label: "条数", value: 1 },
+      { label: "弹簧重/个", value: 1 },
+      { label: "弹簧重", value: 1 },
+      { label: "弹簧单价", value: 1 },
+      { label: "四周口袋-排数", value: 1 },
+      { label: "四周口袋", value: 1 },
+      { label: "四周加硬-排数", value: 1 },
+      { label: "四周加硬", value: 1 },
+      { label: "弹簧克重", value: 1 },
+      { label: "床网高", value: 1 },
+      { label: "口袋弹簧高度", value: 1 },
+      { label: "口袋弹簧心径", value: 1 },
+      { label: "加硬弹簧单价", value: 1 },
+      { label: "口袋弹簧单价", value: 1 },
+      { label: "中心直径", value: 1 },
+      { label: "线径MM", value: 1 },
+      { label: "高度CM", value: 1 },
+      { label: "口径CM", value: 1 },
+      { label: "圈数", value: 1 },
+      { label: "克重KG", value: 1 },
+      { label: "卷排列宽", value: 1 },
+      { label: "卷排列长", value: 1 },
+      { label: "排列宽", value: 1 },
+      { label: "排列长", value: 1 },
+      { label: "弹簧材料总成本", value: 1 },
+      { label: "蛇线材料成本", value: 1 },
+      { label: "弹簧总人工成本", value: 1 },
+      { label: "四周口袋弹簧成本", value: 1 },
+      { label: "四周口袋无纺布成本", value: 1 },
+      { label: "四周加硬材料成本", value: 1 },
+      { label: "四周加硬人力成本", value: 1 },
+      { label: "入袋无纺布材料成本", value: 1 },
+      { label: "胶水材料成本", value: 1 },
+      { label: "底面无纺布材料成本", value: 1 },
+      { label: "边铁人力成本", value: 1 },
+      { label: "边铁材料成本", value: 1 },
+      { label: "C钉/夹码材料成本", value: 1 },
+      { label: "C钉/夹码人力成本", value: 1 },
+      { label: "海绵包边材料成本", value: 1 },
+      { label: "海绵包边人力成本", value: 1 },
+      { label: "填充海绵成本", value: 1 },
+      { label: "封边材料成本", value: 1 },
+      { label: "封边人力成本", value: 1 },
+      { label: "弹叉材料成本", value: 1 },
+      { label: "弹叉人力成本", value: 1 },
+      { label: "胶条/包角材料成本", value: 1 },
+      { label: "胶条/包角人力成本", value: 1 },
+      { label: "上垫层物料成本", value: 1 },
+      { label: "下垫层物料成本", value: 1 },
+      { label: "上垫层人力成本", value: 1 },
+      { label: "下垫层人力成本", value: 1 },
+      { label: "包装总成本", value: 1 },
+      { label: "包装人工成本", value: 1 },
+      { label: "外销加点", value: 1 },
+      { label: "总材料成本", value: 1 },
+      { label: "总人力费用", value: 1 },
+      { label: "FOB", value: 1 },
+      { label: "车间成本", value: 1 },
+      { label: "不含税出厂价", value: 1 },
+      { label: "部门不含税价", value: 1 },
+      { label: "税金", value: 1 },
+      { label: "部门含税价", value: 1 },
+      { label: "外币价", value: 1 },
+      { label: "钢丝重量", value: 1 },
+      { label: "边铁重量", value: 1 },
+      { label: "计划价", value: 1 },
+      { label: "四周加硬重量", value: 1 },
+      { label: "入袋无纺布重量", value: 1 },
+      { label: "面底无纺布重量", value: 1 },
+      { label: "上/下垫层重量", value: 1 },
+      { label: "C钉/夹码重量", value: 1 },
+      { label: "海绵包边重量", value: 1 },
+      { label: "填充海绵重量", value: 1 },
+      { label: "封边材料重量", value: 1 },
+      { label: "弹叉材料重量", value: 1 },
+      { label: "特殊工艺费用", value: 1 },
+      { label: "压包数量", value: 1 },
+      { label: "卷包直径", value: 1 },
+      { label: "顶布裥棉外布套做法", value: 1 },
+      { label: "面料外布套做法", value: 1 },
+      { label: "内布套上覆", value: 1 },
+      { label: "内布套侧爱", value: 1 },
+      { label: "内布套下覆", value: 1 },
+      { label: "面料上覆", value: 1 },
+      { label: "面料侧爱", value: 1 },
+      { label: "面料下覆", value: 1 },
+      { label: "面拆", value: 1 },
+      { label: "中拆", value: 1 },
+      { label: "底拆", value: 1 },
+      { label: "部门让利点数", value: 1 },
+      { label: "折扣率", value: 1 },
+      { label: "大柜-普通地区-地区FOB费用", value: 1 },
+      { label: "大柜-特定地区-地区FOB费用", value: 1 },
+      { label: "小柜-普通地区-地区FOB费用", value: 1 },
+      { label: "小柜-特定地区-地区FOB费用", value: 1 },
+      { label: "大柜-柜型立方数", value: 1 },
+      { label: "小柜-柜型立方数", value: 1 },
+      { label: "大柜-柜型米数", value: 1 },
+      { label: "小柜-柜型米数", value: 1 },
+      { label: "木托方式", value: 1 },
+      { label: "物料单价", value: 1 },
+      { label: "物料克重", value: 1 },
+      { label: "幅宽", value: 1 },
+      { label: "数量", value: 1 },
+      { label: "物料厚度", value: 1 },
+      { label: "固定厚度", value: 1 },
+      { label: "厚度", value: 1 },
+      { label: "按面积单价", value: 1 },
+      { label: "物料名称", value: 1 },
+      { label: "物料收缩率", value: 1 },
+      { label: "垫层", value: 1 },
+      { label: "布料幅宽", value: 1 },
+      { label: "布套高", value: 1 },
+      { label: "收缩率", value: 1 },
+      { label: "纸箱宽", value: 1 },
+      { label: "垫层数量", value: 1 },
+      { label: "制造费用", value: 1 },
+      { label: "管理费点", value: 1 },
+      { label: "人工费用", value: 1 },
+      { label: "边带条数", value: 1 },
+      { label: "边带费用", value: 1 },
+      { label: "材料成本", value: 1 },
+      { label: "散单金额", value: 1 },
+      { label: "拼侧数量", value: 1 },
+      { label: "大侧数量", value: 1 },
+      { label: "工艺点数", value: 1 },
+      { label: "内布套简单款", value: 1 },
+      { label: "内布套复杂款", value: 1 },
+      { label: "内布套点数", value: 1 },
+      { label: "拆装数量", value: 1 },
+      { label: "拆装点数", value: 1 },
+      { label: "海绵扣点", value: 1 },
+      { label: "大小单", value: 1 },
+      { label: "大小单类型", value: 1 },
+      { label: "大小单系数", value: 1 },
+      { label: "款式系数", value: 1 },
+      { label: "管理费用点", value: 1 },
+      { label: "公司利润点", value: 1 },
+      { label: "底价", value: 1 },
+      { label: "佣金", value: 1 }
+    ]
+  </json>
+</data>

+ 71 - 0
JLHHJSvr/Excutor/FormulaCheckExcutor.cs

@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.Data.SqlClient;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Text.RegularExpressions;
+using DirectService.Tools;
+using JLHHJSvr.BLL;
+using JLHHJSvr.Com;
+using JLHHJSvr.Com.Model;
+using JLHHJSvr.LJFramework.Tools;
+using LJLib.DAL.SQL;
+using LJLib.Net.SPI.Server;
+using Newtonsoft.Json.Linq;
+
+namespace JLHHJSvr.Excutor
+{
+    internal sealed class FormulaCheckExcutor : ExcutorBase<FormulaCheckRequest, FormulaCheckResponse>
+    {
+        protected override void ExcuteInternal(FormulaCheckRequest request, object state, FormulaCheckResponse rslt)
+        {
+            var tokendata = BllHelper.GetToken(request.token);
+            if (tokendata == null)
+            {
+                rslt.ErrMsg = "会话已经中断,请重新登录";
+                return;
+            }
+
+            if(string.IsNullOrEmpty(request.formula))
+            {
+                rslt.ErrMsg = "公式为空,请检查!";
+                return;
+            }
+            
+            var replacements = new Dictionary<string, decimal>();
+
+            var formula = request.formula.Replace("(", "(")
+                 .Replace(")", ")")
+                 .Replace(",", ",")
+                 .Replace("。", ".")
+                 .Replace(":", ":")
+                 .Replace(";", ";")
+                 .Replace("“", "\"")
+                 .Replace("”", "\"")
+                 .Replace("‘", "'")
+                 .Replace("’", "'");
+            // 定义正则表达式模式,匹配包含【】的内容
+            string pattern = @"【(.*?)】";
+            Regex regex = new Regex(pattern);
+            MatchCollection matches = regex.Matches(formula);
+            foreach (Match match in matches)
+            {
+                if (!replacements.ContainsKey(match.Value))
+                {
+                    replacements.Add(match.Value, 999M);
+                }
+            }
+
+            // 相关变量会默认代入999,主要验证运算逻辑是否合理即可。避免除数为0即可
+            foreach (var item in replacements)
+            {
+                formula = formula.Replace(item.Key, "(" + item.Value + ")");
+            }
+
+            if (LJExprParser.CheckFormula(formula,out var arg_msg))
+            {
+                rslt.ErrMsg = $"解析表达式{request.formula}失败:{arg_msg}";
+            }
+        }
+    }
+}

+ 94 - 0
JLHHJSvr/Excutor/GetFormulaVarListExcutor.cs

@@ -0,0 +1,94 @@
+using System;
+using System.Collections.Generic;
+using System.Data.SqlClient;
+using System.Linq;
+using JLHHJSvr.BLL;
+using JLHHJSvr.Com;
+using JLHHJSvr.Com.Model;
+using LJLib.DAL.SQL;
+using LJLib.Net.SPI.Server;
+using Newtonsoft.Json.Linq;
+
+namespace JLHHJSvr.Excutor
+{
+    internal sealed class GetFormulaVarListExcutor : ExcutorBase<GetFormulaVarListRequest, GetFormulaVarListResponse>
+    {
+        protected override void ExcuteInternal(GetFormulaVarListRequest request, object state, GetFormulaVarListResponse rslt)
+        {
+            var tokendata = BllHelper.GetToken(request.token);
+            if (tokendata == null)
+            {
+                rslt.ErrMsg = "会话已经中断,请重新登录";
+                return;
+            }
+
+            rslt.recursionList = new List<Recursion2>();
+            // 变量定义
+            var varRecusionChilds = new List<Recursion2>();
+            rslt.recursionList.Add(new Recursion2() { text = "变量定义", value = 0, children = varRecusionChilds });
+            // 工艺加点设置
+            var workShipChilds = new List<Recursion2>();
+            rslt.recursionList.Add(new Recursion2() { text = "工艺加点设置", value = 0, children = workShipChilds });
+            // 固定参数
+            var fixedVarChilds = new List<Recursion2>();
+            rslt.recursionList.Add(new Recursion2() { text = "固定变量", value = 0, children = fixedVarChilds });
+
+            using (var con = new SqlConnection(GlobalVar.ConnectionString))
+            using (var cmd = con.CreateCommand())
+            {
+                con.Open();
+
+                var selectStr = string.Empty;
+                var param = new Dictionary<string, object>();
+                var outputFields = string.Empty;
+
+                // 变量定义
+                selectStr = "SELECT varname FROM u_bednet_var";
+                outputFields = "varname";
+                var bednetVarList = new List<u_bednet_var>();
+                DbSqlHelper.SelectJoin(cmd, selectStr, null, param, "varid", outputFields, 0, 0, bednetVarList);
+                foreach(var child in bednetVarList)
+                {
+                    varRecusionChilds.Add(new Recursion2()
+                    {
+                        text = child.varname,
+                        value = 1
+                    });
+                }
+
+                // 加点工艺
+                selectStr = "SELECT workmanshipname FROM u_workmanship_add";
+                outputFields = "workmanshipname";
+                var workshipList = new List<u_workmanship_add>();
+                DbSqlHelper.SelectJoin(cmd, selectStr, null, param, "workmanshipid", outputFields, 0, 0, workshipList);
+                foreach (var child in workshipList)
+                {
+                    workShipChilds.Add(new Recursion2()
+                    {
+                        text = child.workmanshipname,
+                        value = 1
+                    });
+                }
+            }
+
+            var queryparams = new JObject();
+            var commonExcutor = new CommonDynamicSelectExcutor();
+            var commonRequest = new CommonDynamicSelectRequest()
+            {
+                token = request.token,
+                dsname = "_Mapper_fixedParamters",
+                queryparams = queryparams
+            };
+
+            var billResponse = commonExcutor.Excute(commonRequest, state) as CommonDynamicSelectResponse;
+            if (billResponse != null && billResponse.datatable != null && billResponse.datatable.Any())
+            {
+                var fiexdList = billResponse.datatable;
+                foreach(var item in fiexdList)
+                {
+                    fixedVarChilds.Add(new Recursion2() { text = Convert.ToString(item["label"]),value = Convert.ToInt32(item["value"]) });
+                }
+            }
+        }
+    }
+}

+ 1 - 1
JLHHJSvr/Excutor/GetUserListExcutor.cs

@@ -29,7 +29,7 @@ namespace JLHHJSvr.Excutor
 
                 rslt.userList = new List<u_user_jlhprice>();
                 DbSqlHelper.Select(cmd, "u_user_jlhprice", null, null, null, 0, 0, rslt.userList, null,
-                    "empid, userid, username, rightstring, descrp, deptstr, usermode, outrepstr, pricelist_seestr, pricelist_editstr");
+                    "empid, userid, username, rightstring, descrp, deptstr, usermode, outrepstr, pricelist_seestr, pricelist_editstr,access_failed_count,last_failed_attempt_time");
             }
         }
     }

+ 79 - 29
JLHHJSvr/Excutor/LoginExcutor.cs

@@ -6,7 +6,9 @@ using System.Linq;
 using System.Text;
 using JLHHJSvr.BLL;
 using JLHHJSvr.Com;
+using JLHHJSvr.Com.Model;
 using JLHHJSvr.DBA.DBModle;
+using JLHHJSvr.LJException;
 using LJLib.DAL.SQL;
 using LJLib.Net.SPI.Server;
 using LJLib.Tools.DEncrypt;
@@ -35,40 +37,88 @@ namespace JLHHJSvr.Excutor
             using (var cmd = con.CreateCommand())
             {
                 con.Open();
-
-                if (DbSqlHelper.SelectOne(cmd, "u_user_jlhprice", "userid = @usercode",
-                    new Dictionary<string, object>() {{"@usercode", request.usercode}}, stUser,
-                    "userid, empid, username, usermode, psw") != 1)
+                
+                try
                 {
-                    rslt.ErrMsg = "用户名不存在或密码错误";
-                    return;
-                }
+                    if (DbSqlHelper.SelectOne(cmd, "u_user_jlhprice", "userid = @usercode",
+                    new Dictionary<string, object>() { { "@usercode", request.usercode } }, stUser,
+                    "userid, empid, username, usermode, psw, access_failed_count, last_failed_attempt_time") != 1)
+                    {
+                        rslt.ErrMsg = "用户名不存在或密码错误";
+                        return;
+                    }
+
+                    // 判断是否lock
+                    if (stUser.isLocked)
+                    {
+                        throw new LJCommonException("登录连续错误5次,账号已锁定,请联系管理员解锁!");
+                    }
+
+                    psw_bczh3 pswhelper = new psw_bczh3();
+                    if (pswhelper.GetEntrypt(request.psw, 0, "123457851239866") != stUser.psw)
+                    {
+                        using (cmd.Transaction = con.BeginTransaction())
+                        {
+                            try
+                            {
+                                cmd.CommandText = @"UPDATE u_user_jlhprice SET access_failed_count = CASE 
+                                                    WHEN last_failed_attempt_time < @failedDate THEN 1 
+                                                    ELSE access_failed_count + 1 
+                                                 END,
+                                                 last_failed_attempt_time = GETUTCDATE()
+                                                WHERE u_user_jlhprice.empid = @empid";
+                                cmd.Parameters.Clear();
+                                cmd.Parameters.AddWithValue("@failedDate", DateTime.UtcNow.AddMonths(-1));
+                                cmd.Parameters.AddWithValue("@empid", stUser.empid);
+                                cmd.ExecuteNonQuery();
+
+                                cmd.Transaction.Commit();
+                            }
+                            catch (Exception e)
+                            {
+                                cmd.Transaction.Rollback();
+                            }
+                        }
+                        
+                        throw new LJCommonException($"密码错误,剩余尝试次数:{5 - stUser.access_failed_count + 1} 次(共 5 次)");
+                    }
 
-                psw_bczh3 pswhelper = new psw_bczh3();
-                if (pswhelper.GetEntrypt(request.psw, 0, "123457851239866") != stUser.psw)
+                    string token = Guid.NewGuid().ToString();
+                    rslt.token = token;
+                    rslt.username = stUser.username;
+                    rslt.usercode = stUser.userid;
+                    rslt.empid = stUser.empid;
+                    rslt.usermode = stUser.usermode;
+                    rslt.rsltFunids = UserHelper.FilterMyFunids(cmd, stUser.empid);
+                    var tokenData = new TokenData
+                    {
+                        empid = stUser.empid,
+                        usercode = stUser.userid,
+                        userid = stUser.empid,
+                        username = stUser.username,
+                        usermode = stUser.usermode
+                    };
+                    BllHelper.SetToken(token, tokenData);
+
+                    // 登录成功,清除错误次数
+                    using (cmd.Transaction = con.BeginTransaction())
+                    {
+                        try
+                        {
+                            UserHelper.UnLock(cmd, new List<int>() { stUser.empid });
+                            cmd.Transaction.Commit();
+                        }
+                        catch (Exception e)
+                        {
+                            cmd.Transaction.Rollback();
+                        }
+                    }
+                }
+                catch(LJCommonException ex)
                 {
-                    rslt.ErrMsg = "用户名不存在或密码错误";
-                    return;
+                    rslt.ErrMsg = ex.Message;
                 }
-
-                rslt.rsltFunids = UserHelper.FilterMyFunids(cmd, stUser.empid);
             }
-
-            string token = Guid.NewGuid().ToString();
-            rslt.token = token;
-            rslt.username = stUser.username;
-            rslt.usercode = stUser.userid;
-            rslt.empid = stUser.empid;
-            rslt.usermode = stUser.usermode;
-            var tokenData = new TokenData
-            {
-                empid = stUser.empid,
-                usercode = stUser.userid,
-                userid = stUser.empid,
-                username = stUser.username,
-                usermode = stUser.usermode
-            };
-            BllHelper.SetToken(token,tokenData);
         }
     }
 }

+ 53 - 0
JLHHJSvr/Excutor/UnLockUserExcutor.cs

@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.Data.SqlClient;
+using System.Linq;
+using System.Text;
+using JLHHJSvr.BLL;
+using JLHHJSvr.Com;
+using JLHHJSvr.DBA.DBModle;
+using LJLib.DAL.SQL;
+using LJLib.Net.SPI.Server;
+using LJLib.Tools.DEncrypt;
+using LJLib.Tools.Encry;
+
+namespace JLHHJSvr.Excutor
+{
+    internal sealed class UnLockUserExcutor : ExcutorBase<UnLockUserRequest, UnLockUserResponse>
+    {
+        protected override void ExcuteInternal(UnLockUserRequest request, object state, UnLockUserResponse rslt)
+        {
+            var tokendata = BllHelper.GetToken(request.token);
+            if (tokendata == null)
+            {
+                rslt.ErrMsg = "会话已经中断,请重新登录";
+                return;
+            }
+            if (request.useridList == null || request.useridList.Count == 0)
+            {
+                rslt.ErrMsg = "至少提交一条需要解锁的记录";
+                return;
+            }
+
+            using (var con = new SqlConnection(GlobalVar.ConnectionString))
+            using (var cmd = con.CreateCommand())
+            {
+                con.Open();
+
+                using (cmd.Transaction = con.BeginTransaction())
+                {
+                    try
+                    {
+                        UserHelper.UnLock(cmd, request.useridList);
+                        cmd.Transaction.Commit();
+                    }
+                    catch (Exception e)
+                    {
+                        cmd.Transaction.Rollback();
+                        rslt.ErrMsg = e.Message;
+                    }
+                }
+            }
+        }
+    }
+}

+ 3 - 0
JLHHJSvr/GlobalVar/GlobalVar.cs

@@ -139,6 +139,7 @@ namespace JLHHJSvr
                 excutorManager.AddMap("GetUserList", typeof(GetUserListRequest), new GetUserListExcutor());//获取用户列表
                 excutorManager.AddMap("SaveUserList", typeof(SaveUserListRequest), new SaveUserListExcutor());//保存用户列表
                 excutorManager.AddMap("DelUserList", typeof(DelUserListRequest), new DelUserListExcutor());//删除用户列表
+                excutorManager.AddMap("UnLockUser", typeof(UnLockUserRequest), new UnLockUserExcutor());//删除用户列表
                 excutorManager.AddMap("GetSysFuncPwr", typeof(GetSysFuncPwrRequest), new GetSysFuncPwrExcutor());//获取权限列表
                 excutorManager.AddMap("GetDept", typeof(GetDeptRequest), new GetDeptExcutor());//获取部门列表
                 excutorManager.AddMap("GetPriceList", typeof(GetPriceListRequest), new GetPriceListExcutor());//获取价格表列表
@@ -229,6 +230,8 @@ namespace JLHHJSvr
                 excutorManager.AddMap("SaveMattressExtraType", typeof(SaveMattressExtraTypeRequest), new SaveMattressExtraTypeExcutor());// 保存床网分区类型
                 excutorManager.AddMap("DeleteMattressExtraType", typeof(DeleteMattressExtraTypeRequest), new DeleteMattressExtraTypeExcutor());// 删除床网分区类型
 
+                excutorManager.AddMap("GetFormulaVarList",typeof(GetFormulaVarListRequest),new GetFormulaVarListExcutor());
+                excutorManager.AddMap("FormulaCheck", typeof(FormulaCheckRequest),new FormulaCheckExcutor());
             }
             catch (Exception ex)
             {

+ 7 - 4
JLHHJSvr/JLHHJSvr.csproj

@@ -102,12 +102,15 @@
     <Compile Include="Com\DeleteMattressExtraType.cs" />
     <Compile Include="Com\DeleteMattressExtra.cs" />
     <Compile Include="Com\Model\replacement_bednet.cs" />
+    <Compile Include="Com\FormulaCheck.cs" />
+    <Compile Include="Com\GetFormulaVarList.cs" />
     <Compile Include="Com\Model\u_mattress_computed.cs" />
     <Compile Include="Com\Model\u_mattress_extra_type.cs" />
     <Compile Include="Com\Model\u_mattress_extra.cs" />
     <Compile Include="Com\Model\u_mattress_mx_extra.cs" />
     <Compile Include="Com\SaveMattressExtraType.cs" />
     <Compile Include="Com\SaveMattressExtra.cs" />
+    <Compile Include="Com\UnLockUser.cs" />
     <Compile Include="Com\UpdateMtrlPrice.cs" />
     <Compile Include="Com\DelCarList.cs" />
     <Compile Include="Com\DeleteBedNet.cs" />
@@ -136,7 +139,6 @@
     <Compile Include="Com\GetComputeSpring.cs" />
     <Compile Include="Com\GetERPMtrlTypeList.cs" />
     <Compile Include="Com\GetMattressImportDW2.cs" />
-    <Compile Include="Com\GetSemiFinishedMxList.cs" />
     <Compile Include="Com\GetMattressPackagMx.cs" />
     <Compile Include="Com\GetComputeMattress.cs" />
     <Compile Include="Com\GetComputeBednet.cs" />
@@ -152,7 +154,6 @@
     <Compile Include="Com\GetPriceList.cs" />
     <Compile Include="Com\GetSysFuncPwr.cs" />
     <Compile Include="Com\GetFormulaCompute.cs" />
-    <Compile Include="Com\ImportSpring.cs" />
     <Compile Include="Com\MattressJS2Audit.cs" />
     <Compile Include="Com\MattressJSAudit.cs" />
     <Compile Include="Com\MattressYWAudit.cs" />
@@ -292,8 +293,11 @@
     <Compile Include="Excutor\CopyMtrlDefExcutor.cs" />
     <Compile Include="Excutor\DeleteMattressExtraTypeExcutor.cs" />
     <Compile Include="Excutor\DeleteMattressExtraExcutor.cs" />
+    <Compile Include="Excutor\FormulaCheckExcutor.cs" />
+    <Compile Include="Excutor\GetFormulaVarListExcutor.cs" />
     <Compile Include="Excutor\SaveMattressExtraTypeExcutor.cs" />
     <Compile Include="Excutor\SaveMattressExtraExcutor.cs" />
+    <Compile Include="Excutor\UnLockUserExcutor.cs" />
     <Compile Include="Excutor\UpdateMtrlPriceExcutor.cs" />
     <Compile Include="Excutor\DelMtrlPfExcutor.cs" />
     <Compile Include="Excutor\DelCarListExcutor.cs" />
@@ -323,7 +327,6 @@
     <Compile Include="Excutor\GetCarListExcutor.cs" />
     <Compile Include="Excutor\GetComputeSpringExcutor.cs" />
     <Compile Include="Excutor\GetERPMtrlTypeListExcutor.cs" />
-    <Compile Include="Excutor\GetSemiFinishedMxListExcutor.cs" />
     <Compile Include="Excutor\GetMattressPackagMxExcutor.cs" />
     <Compile Include="Excutor\GetMattressImportDW2Excutor.cs" />
     <Compile Include="Excutor\GetComputeMattressExcutor.cs" />
@@ -346,7 +349,6 @@
     <Compile Include="Excutor\GetUserListExcutor.cs" />
     <Compile Include="Excutor\GetUserPowerExcutor.cs" />
     <Compile Include="Excutor\HelloWordExcutor.cs" />
-    <Compile Include="Excutor\ImportSpringExcutor.cs" />
     <Compile Include="Excutor\LoginExcutor.cs" />
     <Compile Include="Excutor\MattressJS2AuditExcutor.cs" />
     <Compile Include="Excutor\MattressJSAuditExcutor.cs" />
@@ -391,6 +393,7 @@
     <Compile Include="Excutor\SetSysUserFileStringExcutor.cs" />
     <Compile Include="Excutor\SetOptionExcutor.cs" />
     <Compile Include="Helper\BedNetHelper.cs" />
+    <Compile Include="Helper\CacheHelper.cs" />
     <Compile Include="Helper\ERPHelper.cs" />
     <Compile Include="Helper\InterfaceHelper.cs" />
     <Compile Include="Helper\MattressHelper.cs" />

+ 7 - 0
JLHHJSvr/LJFrameWork/Tools/LJExprParser.cs

@@ -11,6 +11,13 @@ namespace JLHHJSvr.LJFramework.Tools
 {
     public class LJExprParser
     {
+        public static bool CheckFormula(string expr,out string arg_msg)
+        {
+            var parser = new TExprParserChina(expr);
+            // 
+            arg_msg = parser.ErrorMessage;
+            return parser.HasError;
+        }
         public static TExprParserEx Parse(string expr)
         {
             if (string.IsNullOrEmpty(expr)) return new TExprParserChina("");

+ 3 - 1
JLHWEB/package.json

@@ -40,6 +40,7 @@
     "@vue-office/excel": "^1.7.8",
     "@vue-office/pdf": "^2.0.2",
     "@vueuse/core": "^10.1.2",
+    "@vxe-ui/plugin-render-element": "4.0.7",
     "@wangeditor/editor": "^5.1.23",
     "@wangeditor/editor-for-vue": "^5.1.12",
     "axios": "^1.4.0",
@@ -68,7 +69,8 @@
     "vue-router": "^4.2.1",
     "vue-toastification": "2.0.0-rc.5",
     "vuedraggable": "^4.1.0",
-    "vxe-table": "4.6.21",
+    "vxe-pc-ui": "4.2.0",
+    "vxe-table": "4.7.81",
     "xe-utils": "^3.5.11"
   },
   "devDependencies": {

+ 3 - 0
JLHWEB/src/api/interface/index.ts

@@ -1189,6 +1189,9 @@ export namespace Basicinfo {
     successMsg: string;
     taskcodeList: any[];
   }
+  export interface ReqFormulaCheck {
+    formula: string;
+  }
 }
 
 export namespace SalePrice {

+ 18 - 0
JLHWEB/src/api/modules/basicinfo.ts

@@ -15,6 +15,10 @@ export const delUserList = (params: Basicinfo.ResUserList) => {
   return http.post<Basicinfo.ResUserList>(PORT1 + `/DelUserList`, params);
 };
 
+export const UnLockUser = (params: Basicinfo.ResUserList) => {
+  return http.post<Basicinfo.ResUserList>(PORT1 + `/UnLockUser`, params);
+};
+
 export const getSysFuncPwr = () => {
   return http.post<SalePrice.ResPriceList>(PORT1 + `/GetSysFuncPwr`, {});
 };
@@ -508,3 +512,17 @@ export const SaveMattressExtraType = (params: Basicinfo.ReqSaveMattressExtra) =>
 export const DeleteMattressExtraType = (params: Basicinfo.ReqDeleteBasicinfo) => {
   return http.post(PORT1 + `/DeleteMattressExtraType`, params);
 };
+
+/**
+ * @name 获取 公式
+ */
+export const GetFormulaVarList = () => {
+  return http.post(PORT1 + `/GetFormulaVarList`, {});
+};
+
+/**
+ * @name 检查 公式
+ */
+export const FormulaCheck = (params: Basicinfo.ReqFormulaCheck) => {
+  return http.post(PORT1 + `/FormulaCheck`, params);
+};

+ 10 - 5
JLHWEB/src/components/LjDetail/index.vue

@@ -1199,11 +1199,16 @@ onMounted(async () => {
 
   if (props.requestAuto) {
     getTableList().then(() => {
-      console.log("detail onMountedData", tableData.value);
-      console.log("_mainData :>> ", _mainData.value);
-      console.log("props.data :>> ", props.data);
-      console.log("onMounted detail end!!!! :>> ");
-      emit("afterMounted", _mainData.value);
+      console.log("getTableList orderStatus.value :>> ", orderStatus.value, tableData.value);
+      if (props.requestApi && !tableData.value.length && orderStatus.value != "new") {
+        ElNotification({
+          title: t("sys.api.selectFailed"),
+          message: t("sys.api.selectFailedMessage"),
+          type: "warning"
+        });
+      } else {
+        emit("afterMounted", _mainData.value);
+      }
     });
   } else {
     console.log("onMounted detail end!!!! :>> ");

+ 3 - 1
JLHWEB/src/languages/modules/zh-cn/sys.json

@@ -28,7 +28,9 @@
     "sueccessToDel": "成功删除",
     "sueccessToRefresh": "成功刷新",
     "sueccessToSave": "成功保存",
-    "timeoutMessage": "登录超时,请重新登录!"
+    "timeoutMessage": "登录超时,请重新登录!",
+    "selectFailed": "查询失败",
+    "selectFailedMessage": "无法查询到数据"
   },
   "app": {
     "logoutMessage": "是否确认退出系统?",

+ 153 - 83
JLHWEB/src/main.ts

@@ -87,48 +87,74 @@ import {
 // vxe-table
 // import XEUtils from "xe-utils";
 
+// import {
+//   // 全局对象
+//   VXETable,
+
+//   // 表格功能
+//   Filter,
+//   Edit,
+//   Menu,
+//   Export,
+//   Keyboard,
+//   Validator,
+
+//   // 可选组件
+//   Icon,
+//   Column,
+//   Colgroup,
+//   Grid,
+//   Tooltip,
+//   Toolbar,
+//   Pager,
+//   Form,
+//   FormItem,
+//   FormGather,
+//   Checkbox,
+//   CheckboxGroup,
+//   Radio,
+//   RadioGroup,
+//   RadioButton,
+//   Switch,
+//   Input,
+//   Select,
+//   Optgroup,
+//   Option,
+//   Textarea,
+//   Button,
+//   Modal,
+//   List,
+//   Pulldown,
+
+//   // 表格
+//   Table
+// } from "vxe-table";
+
 import {
-  // 全局对象
-  VXETable,
-
-  // 表格功能
-  Filter,
-  Edit,
-  Menu,
-  Export,
-  Keyboard,
-  Validator,
-
-  // 可选组件
-  Icon,
-  Column,
-  Colgroup,
-  Grid,
-  Tooltip,
-  Toolbar,
-  Pager,
-  Form,
-  FormItem,
-  FormGather,
-  Checkbox,
-  CheckboxGroup,
-  Radio,
-  RadioGroup,
-  RadioButton,
-  Switch,
-  Input,
-  Select,
-  Optgroup,
-  Option,
-  Textarea,
-  Button,
-  Modal,
-  List,
-  Pulldown,
-
-  // 表格
-  Table
-} from "vxe-table";
+  VxeUI,
+  VxeButton,
+  VxeButtonGroup,
+  VxeDrawer,
+  VxeForm,
+  VxeFormGroup,
+  VxeFormItem,
+  VxeIcon,
+  VxeInput,
+  VxeLoading,
+  VxeModal,
+  VxePager,
+  VxePrint,
+  VxeSelect,
+  VxeTooltip,
+  VxeUpload
+} from "vxe-pc-ui";
+
+import { VxeTable, VxeColumn, VxeColgroup, VxeGrid, VxeToolbar } from "vxe-table";
+
+// 导入主题变量,也可以重写主题变量
+import "vxe-table/styles/cssvar.scss";
+import "vxe-pc-ui/styles/cssvar.scss";
+
 // import "vxe-table/styles/variable.scss";
 // import "vxe-table/styles/cssvar.scss";
 // import "vxe-table/lib/style.css";
@@ -137,53 +163,59 @@ import "@/styles/var/vant-variable.scss";
 
 import { Tab as VanTab, Tabs as VanTabs, Cell, CellGroup, Empty as VanEmpty } from "vant";
 
+import VxeUIPluginRenderElement from "@vxe-ui/plugin-render-element";
+import "@vxe-ui/plugin-render-element/dist/style.css";
+
+VxeUI.use(VxeUIPluginRenderElement);
+
 // 按需加载的方式默认是不带国际化的,自定义国际化需要自行解析占位符 '{0}',例如:
-VXETable.setup({
-  // @ts-ignore
-  i18n: (key, args) => I18n.global.t(key, args),
-  // 重置vxetable的图标
-  icon: {
-    TABLE_FILTER_NONE: "iconfont iconfilter-funnel-01",
-    TABLE_FILTER_MATCH: "iconfont iconfilter-funnel-01-fill"
-  }
-});
+// VXETable.setup({
+//   // @ts-ignore
+//   i18n: (key, args) => I18n.global.t(key, args),
+//   // 重置vxetable的图标
+//   icon: {
+//     TABLE_FILTER_NONE: "iconfont iconfilter-funnel-01",
+//     TABLE_FILTER_MATCH: "iconfont iconfilter-funnel-01-fill"
+//   }
+// });
 
 import "@/components/LjVxeTable/interface/plugins.tsx";
 
 function useTable(app: App) {
-  // 表格功能
-  app.use(Filter).use(Edit).use(Menu).use(Export).use(Keyboard).use(Validator);
-
-  // 可选组件
-  app
-    .use(Icon)
-    .use(Column)
-    .use(Colgroup)
-    .use(Grid)
-    .use(Tooltip)
-    .use(Toolbar)
-    .use(Pager)
-    .use(Form)
-    .use(FormItem)
-    .use(FormGather)
-    .use(Checkbox)
-    .use(CheckboxGroup)
-    .use(Radio)
-    .use(RadioGroup)
-    .use(RadioButton)
-    .use(Switch)
-    .use(Input)
-    .use(Select)
-    .use(Optgroup)
-    .use(Option)
-    .use(Textarea)
-    .use(Button)
-    .use(Modal)
-    .use(List)
-    .use(Pulldown)
-
-    // 安装表格
-    .use(Table);
+  app.use(lazyVxeUI).use(lazyVxeTable);
+  //   // 表格功能
+  //   app.use(Filter).use(Edit).use(Menu).use(Export).use(Keyboard).use(Validator);
+
+  //   // 可选组件
+  //   app
+  //     .use(Icon)
+  //     .use(Column)
+  //     .use(Colgroup)
+  //     .use(Grid)
+  //     .use(Tooltip)
+  //     .use(Toolbar)
+  //     .use(Pager)
+  //     .use(Form)
+  //     .use(FormItem)
+  //     .use(FormGather)
+  //     .use(Checkbox)
+  //     .use(CheckboxGroup)
+  //     .use(Radio)
+  //     .use(RadioGroup)
+  //     .use(RadioButton)
+  //     .use(Switch)
+  //     .use(Input)
+  //     .use(Select)
+  //     .use(Optgroup)
+  //     .use(Option)
+  //     .use(Textarea)
+  //     .use(Button)
+  //     .use(Modal)
+  //     .use(List)
+  //     .use(Pulldown)
+
+  //     // 安装表格
+  //     .use(Table);
 }
 
 // import VXETable from "vxe-table";
@@ -198,6 +230,44 @@ function useTable(app: App) {
 //   app.use(VXETable);
 // }
 
+// 可选组件
+function lazyVxeUI(app: App) {
+  app.use(VxeButton);
+  app.use(VxeButtonGroup);
+  app.use(VxeDrawer);
+  app.use(VxeForm);
+  app.use(VxeFormGroup);
+  app.use(VxeFormItem);
+  app.use(VxeIcon);
+  app.use(VxeInput);
+  app.use(VxeLoading);
+  app.use(VxeModal);
+  app.use(VxePager);
+  app.use(VxePrint);
+  app.use(VxeSelect);
+  app.use(VxeTooltip);
+  app.use(VxeUpload);
+}
+
+function lazyVxeTable(app: App) {
+  app.use(VxeTable);
+  app.use(VxeColumn);
+  app.use(VxeColgroup);
+  app.use(VxeGrid);
+  app.use(VxeToolbar);
+}
+VxeUI.setConfig({
+  zIndex: 3000,
+  // 对组件内置的提示语进行国际化翻译
+  i18n: (key: any, args: any) => I18n.global.t(key, args)
+});
+
+// 重置vxetable的图标
+VxeUI.setIcon({
+  TABLE_FILTER_NONE: "iconfont iconfilter-funnel-01",
+  TABLE_FILTER_MATCH: "iconfont iconfilter-funnel-01-fill"
+});
+
 const app = createApp(MainApp);
 
 app.config.errorHandler = errorHandler;

+ 3 - 1
JLHWEB/src/styles/var/vxe-variable.scss

@@ -1,5 +1,7 @@
 // @import "./color.scss";
-@import "vxe-table/styles/index.scss";
+// @import "vxe-table/styles/index.scss";
+@import "vxe-pc-ui/lib/style.css";
+@import "vxe-table/lib/style.css";
 
 /*font*/
 

+ 15 - 1
JLHWEB/src/views/baseinfo/bednetvar/index.vue

@@ -20,6 +20,7 @@
           <el-button-group>
             <el-button @click="handleOpenNewTable">{{ $t("common.add") }}</el-button>
             <el-button @click="fDelete">{{ $t("common.delText") }}</el-button>
+            <el-button @click="handleClickFormula">编辑公式</el-button>
           </el-button-group>
           <el-tabs v-model="initParams.varkind" class="tableheader-tabs">
             <el-tab-pane label="床网" :name="0"></el-tab-pane>
@@ -66,11 +67,13 @@
       <Detail class="flx-1" :data="mainData" :status="orderStatus" :enum="enumMap" />
     </div>
   </LjDialog>
+
+  <FormulaEditorDialog ref="formulaEditorRef" @confirm="handleFormulaConfirm" />
 </template>
 
 <script setup lang="ts" name="baseinfo_bednetvarlist">
 import { ref, onMounted, provide } from "vue";
-import { getBedNetVarList, getBedNetVarMxList } from "@/api/modules/basicinfo";
+import { getBedNetVarList, getBedNetVarMxList, GetFormulaVarList } from "@/api/modules/basicinfo";
 import { ColumnProps } from "@/components/LjVxeTable/interface";
 import LjDrawer from "@/components/LjDrawer/index.vue";
 import Detail from "./detail.vue";
@@ -81,6 +84,7 @@ import { useI18n } from "vue-i18n";
 import { useAuthButtons } from "@/hooks/useAuthButtons";
 import { cloneDeep } from "lodash-es";
 import LjFoldLayout from "@/components/LjFoldLayout/index.vue";
+import FormulaEditorDialog from "@/views/system/formula-editor/index.vue";
 
 const dwname = "web_bednetvarlist";
 const dwname_mx = "web_bednetvarmxlist";
@@ -109,6 +113,8 @@ const layoutSetting = {
     hidden: false
   }
 };
+const showEditor = ref(false);
+const formulaEditorRef = ref();
 
 const { t } = useI18n();
 const { VxeTableRef, LjDetailRef, VxeTableMxRef, columns, columns_mx, fDelete, fSave, fChangeBedNetType } = useHooks(t, null);
@@ -153,6 +159,10 @@ const orderEditAction = [
   })
 ];
 
+const handleClickFormula = () => {
+  formulaEditorRef.value.show(true);
+};
+
 /*
  * @description 抽屉默认属性
  */
@@ -243,6 +253,10 @@ const handleClickTable = ({ row, rowIndex, $rowIndex, column, columnIndex, $colu
   fChangeBedNetType(parseInt(row.varclass));
 };
 
+const handleFormulaConfirm = (formula: string) => {
+  console.log(formula);
+};
+
 // 返回绑定的事件
 const tableEvents = {
   "cell-dblclick": handleDBlClickTable,

+ 11 - 1
JLHWEB/src/views/baseinfo/user/detail.vue

@@ -212,7 +212,7 @@ const deptData = inject("deptData", ref([]));
 const priceListData = inject("priceListData", ref([]));
 const userListData = inject("userListData", ref([]));
 
-const emit = defineEmits(["edit", "del", "cancel", "goto"]);
+const emit = defineEmits(["edit", "del", "cancel", "goto", "unlock"]);
 
 /**
  * @description 是否可编辑
@@ -250,6 +250,16 @@ const orderDefaultAction = [
 
       emit("del", mainData.value[0]);
     }
+  }),
+  buttonDefault({
+    label: "解锁",
+    icon: "iconUnlock_light",
+    clickFunc: () => {
+      emit("unlock", mainData.value[0]);
+    },
+    limited: () => {
+      return !mainData.value[0].isLocked;
+    }
   })
 ];
 

+ 23 - 2
JLHWEB/src/views/baseinfo/user/index.vue

@@ -45,6 +45,7 @@
         @cancel="handleCancel"
         @del="handleDel"
         @goto="autoGotoUser"
+        @unlock="handleUnLock"
       />
     </el-col>
   </el-row>
@@ -52,7 +53,7 @@
 
 <script setup lang="tsx" name="saleprice_priclist">
 import { ref, onMounted, provide, nextTick } from "vue";
-import { getUserList, getSysFuncPwr, getDept, delUserList } from "@/api/modules/basicinfo";
+import { getUserList, getSysFuncPwr, getDept, delUserList, UnLockUser } from "@/api/modules/basicinfo";
 import { getPriceList } from "@/api/modules/saleprice";
 import { ColumnProps } from "@/components/LjVxeTable/interface";
 import LjDrawer from "@/components/LjDrawer/index.vue";
@@ -134,7 +135,10 @@ provide("priceListData", priceListData);
 const fileClassListLayout = ref<any>({
   header: [
     {
-      value: "username"
+      value: "username",
+      icon: (item: any) => {
+        return item.isLocked ? <i class={["iconfont", "iconLock_light"]} /> : "";
+      }
     },
     {
       value: "descrp",
@@ -316,6 +320,23 @@ const searchEvent = () => {
     showUserList.value = userListData.value;
   }
 };
+
+const handleUnLock = (data: any) => {
+  if (data.empid == 0) {
+    ElMessage.error("系统管理员账号,不需要解锁");
+    return;
+  }
+  ElMessageBox.confirm(`确认需要解锁用户:${data.username}?`, t("sys.app.warning"), {
+    confirmButtonText: t("common.okText"),
+    cancelButtonText: t("common.cancelText"),
+    type: "error"
+  }).then(async () => {
+    await UnLockUser({ useridList: [data.empid] });
+
+    await getData();
+    handleSelectItem(userListData.value[0]);
+  });
+};
 </script>
 
 <style lang="scss">

+ 44 - 30
JLHWEB/src/views/erpapi/mattressInterface/detail.vue

@@ -198,38 +198,52 @@ const orderDefaultAction = [
     label: t("common.saveText"),
     loading: () => loadingStatus.save,
     limited: () => !orderStatus.value,
-    clickFunc: item => {
-      ElMessageBox.confirm("是否确定要保存吗?", "询问", {
-        confirmButtonText: "是",
-        cancelButtonText: "否",
-        type: "warning"
-      }).then(async () => {
-        try {
-          const res = await SaveMattressInterface({
-            mattress: LjDetailRef.value?._mainData,
-            interfaceList: mattressYWList.value,
-            qdList: mattressQDList.value
-          })
-            .then(res => {
-              ElNotification({
-                title: "温馨提示",
-                message: t("sys.api.sueccessToSave"),
-                type: "success"
-              });
-              tabRemove(route.fullPath);
-              router.push(
-                `/erpapi/mattressInterface/detail?id=${LjDetailRef.value?._mainData.mattressid}&code=${LjDetailRef.value?._mainData.mattresscode}`
-              );
-              loadingStatus.save = false;
+    clickFunc: async item => {
+      try {
+        await LjDetailRef.value.toValidateForm();
+
+        ElMessageBox.confirm("是否确定要保存吗?", "询问", {
+          confirmButtonText: "是",
+          cancelButtonText: "否",
+          type: "warning"
+        }).then(async () => {
+          loadingStatus.save = true;
+          try {
+            const res = await SaveMattressInterface({
+              mattress: LjDetailRef.value?._mainData,
+              interfaceList: mattressYWList.value,
+              qdList: mattressQDList.value
             })
-            .catch(error => {
-              console.log("error !! :>> ", error);
-              loadingStatus.save = false;
-            });
-        } catch (error) {
-          ElMessage.error(t("sys.api.operationFailed"));
+              .then(res => {
+                ElNotification({
+                  title: "温馨提示",
+                  message: t("sys.api.sueccessToSave"),
+                  type: "success"
+                });
+                tabRemove(route.fullPath);
+                router.push(
+                  `/erpapi/mattressInterface/detail?id=${LjDetailRef.value?._mainData.mattressid}&code=${LjDetailRef.value?._mainData.mattresscode}`
+                );
+                loadingStatus.save = false;
+              })
+              .catch(error => {
+                console.log("error !! :>> ", error);
+                loadingStatus.save = false;
+              });
+          } catch (error) {
+            loadingStatus.save = false;
+            ElMessage.error(t("sys.api.operationFailed"));
+          }
+        });
+      } catch (error) {
+        for (const key in error) {
+          if (Object.prototype.hasOwnProperty.call(error, key)) {
+            const element = error[key];
+            ElMessage.error(element[0].message);
+          }
         }
-      });
+        return false;
+      }
     }
   }),
   [

+ 10 - 4
JLHWEB/src/views/erpapi/mattressInterface/hooks/index.tsx

@@ -511,7 +511,8 @@ export const useHooks = (t?: any) => {
         editable: ALLOW_EDIT_STATE,
         group: "单据信息",
         order: 5,
-        span: 1
+        span: 1,
+        rules: [{ required: true, message: "物料单位不能为空" }]
       }
     },
     {
@@ -874,7 +875,10 @@ export const useHooks = (t?: any) => {
       // editRender: {
       //   name: "$input"
       // }
-      editRender: {},
+      editRender: {
+        // name: "$select"
+        autoFocus: "input"
+      },
       editColRender: (scope: any) => {
         console.log("mtrlname hooks render scope :>> ", scope);
         console.log("mtrlname hooks render LjDetailRef.value._mainData :>> ", state.LjDetailRef);
@@ -1018,11 +1022,12 @@ export const useHooks = (t?: any) => {
       isFilterEnum: true,
       editRender: {
         // name: "$select"
+        autoFocus: "input"
       },
       editColRender: (scope: any) => {
         console.log("formulakind editColRender scope :>> ", scope);
         return (
-          <el-select v-model={scope.row.wrkgrpid} onChange={val => rModelSetWrkgrp1(val, scope.row)}>
+          <el-select v-model={scope.row.wrkgrpid} filterable onChange={val => rModelSetWrkgrp1(val, scope.row)}>
             {{
               default: () => {
                 let rs = [];
@@ -1063,11 +1068,12 @@ export const useHooks = (t?: any) => {
       isFilterEnum: true,
       editRender: {
         // name: "$select"
+        autoFocus: "input"
       },
       editColRender: (scope: any) => {
         console.log("formulakind editColRender scope :>> ", scope);
         return (
-          <el-select v-model={scope.row.wrkgrpid2} onChange={val => rModelSetWrkgrp2(val, scope.row)}>
+          <el-select v-model={scope.row.wrkgrpid2} filterable onChange={val => rModelSetWrkgrp2(val, scope.row)}>
             {{
               default: () => {
                 let rs = [];

+ 2 - 2
JLHWEB/src/views/quote/bednetQuote/components/FormulaItem.vue

@@ -70,7 +70,7 @@ const RenderVariable = (data: any, rprops: any) => {
         return c;
       }
     });
-    if (_trgItem) {
+    if (_trgItem && !_trgItem.ifFold) {
       let sumVal = props.fields.find(f => {
         let _label = f.label.slice(1, -1);
         if (_label === fieldName) {
@@ -83,7 +83,7 @@ const RenderVariable = (data: any, rprops: any) => {
           <div class="flx-col flx-end formula-wrapper__item formula-wrapper__item-children">
             <div class="formula-wrapper__item-children-inner">
               <span class="value text-primary-text text-h5-b flx-end-end">
-                (<RenderFormulaItem data={_trgItem} fields={props.fields} />)
+                (<RenderFormulaItem data={_trgItem} fields={props.fields} ifFold={_trgItem.ifFold} />)
               </span>
               <div class="curly-brace">
                 <div class="triangle-arrow"></div>

+ 2 - 2
JLHWEB/src/views/quote/mattressQuote/components/AllFormula.vue

@@ -36,7 +36,7 @@
           />
           <FormulaGroup :data="isNormalFormulasPart2" :fields="isFieldsReplace" />
         </el-tab-pane>
-        <el-tab-pane label="旧公式" name="second" v-if="!iforigin">
+        <el-tab-pane label="旧公式" name="second" v-if="iforigin">
           <FormulaGroup :data="isNormalFormulasOri" :fields="isFieldsReplaceOri" />
         </el-tab-pane>
       </el-tabs>
@@ -122,7 +122,7 @@ const isNormalFormulasBednet = computed(() => {
       if (matchArr.length > 0) {
         item.children = matchArr;
       }
-      // if (["【车间成本】"].includes(item.label)) item.ifFold = true;
+      if (["【大小单系数】"].includes(item.label)) item.ifFold = true;
       return item;
     })
     .filter((item: any) => item.type == 0);

+ 4 - 4
JLHWEB/src/views/quote/mattressQuote/components/Statistic.vue

@@ -1,8 +1,8 @@
 <template>
-  <div style="display: table" @click="handleClick">
+  <div style="display: table">
     <el-row class="statistic-row h-full" :gutter="8" style="width: 180px" :class="{ 'is-admin': userInfo.usermode === 0 }">
       <el-col :span="24" v-for="(item, index) in statisticData" :key="index">
-        <StatisticItem :data="item" v-bind="$attrs" :iforigin="props.iforigin" />
+        <StatisticItem :data="item" v-bind="$attrs" :iforigin="props.iforigin" @click="handleClick(item)" />
       </el-col>
     </el-row>
   </div>
@@ -165,9 +165,9 @@ watch(
   { immediate: true, deep: true }
 );
 
-const handleClick = () => {
+const handleClick = (item: any) => {
   if (userInfo.usermode === 0) {
-    emit("click");
+    emit("click", item);
   }
 };
 </script>

+ 11 - 5
JLHWEB/src/views/quote/mattressQuote/detail.vue

@@ -1593,9 +1593,9 @@ const getData = (params: any) => {
   // return [];
 };
 
-const gotoShowFormula = () => {
+const gotoShowFormula = (data: any) => {
   AllFormulaRef.value.open({
-    dannum_type: LjDetailRef.value._mainData.dannum_type,
+    dannum_type: data.type,
     formula: cmpFormulas.value,
     replace: cmpFormulaReplace.value,
     formula_ori: cmpFormulasOri.value,
@@ -1648,9 +1648,10 @@ const save = async () => {
               // 检查明细输入是否正确
               let _disabled = !(
                 (itm.if_inputqty == 0 && [50, 51, 52, 53, 54, 104, 114, 11, 12, 13, 14, 32, 33].includes(itm.formulakind)) ||
-                itm.formulakind == 7 ||
-                itm.formulakind == 202
+                // itm.formulakind == 202 ||
+                itm.formulakind == 7
               );
+              console.log("_disabled :>> ", _disabled);
               if (t.ref == "cushionsMxRef") {
                 _disabled = !(
                   (itm.if_inputqty == 0 && [50, 51, 52, 53, 54, 104, 114, 11, 14, 32, 33].includes(itm.formulakind)) ||
@@ -1660,10 +1661,15 @@ const save = async () => {
               } else if (t.ref == "innerClothLayerMxRef") {
                 _disabled = !(
                   (itm.if_inputqty == 0 && [50, 51, 52, 53, 54, 104, 114, 11, 14, 32, 33].includes(itm.formulakind)) ||
-                  itm.formulakind == 202 ||
+                  // itm.formulakind == 202 ||
                   itm.formulakind == 203
                 );
               }
+              console.log(
+                "itm.mtrlid > 0 && !_disabled && Number(itm.thickness) <= 0 :>> ",
+                _disabled,
+                itm.mtrlid > 0 && !_disabled && Number(itm.thickness) <= 0
+              );
               if (itm.mtrlid > 0 && !_disabled && Number(itm.thickness) <= 0) {
                 throw new Error(t.label + "相关厚度输入有误,请检查");
               }

+ 8 - 7
JLHWEB/src/views/quote/mattressQuote/hooks/cpQuote.ts

@@ -133,12 +133,13 @@ export const useHooksCpQuote = (t?: any) => {
 
   const setResultData = (target: any, label: string, value: any) => {
     let result = cloneDeep(target);
+    let _value = formatCutNumber({ val: value });
     result.label = label;
-    result.costamt = value;
-    result.costamt_1 = value;
-    result.costamt_2 = value;
-    result.costamt_3 = value;
-    result.costamt_4 = value;
+    result.costamt = _value;
+    result.costamt_1 = _value;
+    result.costamt_2 = _value;
+    result.costamt_3 = _value;
+    result.costamt_4 = _value;
     return result;
   };
 
@@ -844,8 +845,8 @@ export const useHooksCpQuote = (t?: any) => {
       }
 
       //胶水材料成本
-      if (bednet.pocket_around_fabrics_cost > 0) {
-        bednet_qingdan_item.push(setResultData(default_bednet_qingdan, "胶水材料成本", bednet.pocket_around_fabrics_cost));
+      if (bednet.glue_mtrl_cost > 0) {
+        bednet_qingdan_item.push(setResultData(default_bednet_qingdan, "胶水材料成本", bednet.glue_mtrl_cost));
       }
 
       //C钉/夹码材料

+ 5 - 3
JLHWEB/src/views/quote/mattressQuote/hooks/index.tsx

@@ -1427,6 +1427,7 @@ export const useHooks = (t?: any) => {
               formulaid: itm.formulaid,
               sortcode: data.label,
               chastr: data.label,
+              costamt: 0,
               formulakind: Number(itm.formulakind),
               formula: itm.formula,
               useformula: itm.useformula,
@@ -1450,6 +1451,7 @@ export const useHooks = (t?: any) => {
             formulaid: itm.formulaid,
             sortcode: data.label,
             chastr: data.label,
+            costamt: 0,
             formulakind: Number(itm.formulakind),
             formula: itm.formula,
             useformula: itm.useformula,
@@ -3443,8 +3445,8 @@ export const useHooks = (t?: any) => {
         const { $table, column, row, status } = scope;
         const _disabled = !(
           (row.if_inputqty == 0 && [50, 51, 52, 53, 54, 104, 114, 11, 12, 13, 14, 32, 33].includes(row.formulakind)) ||
-          row.formulakind == 7 ||
-          row.formulakind == 202
+          // row.formulakind == 202 ||
+          row.formulakind == 7
         );
 
         return <el-input v-model={scope.row.thickness} type="number" disabled={_disabled}></el-input>;
@@ -4469,7 +4471,7 @@ export const useHooks = (t?: any) => {
         const { $table, column, row, status } = scope;
         const _disabled = !(
           (row.if_inputqty == 0 && [50, 51, 52, 53, 54, 104, 114, 11, 14, 32, 33].includes(row.formulakind)) ||
-          row.formulakind == 202 ||
+          // row.formulakind == 202 ||
           row.formulakind == 203
         );
 

+ 396 - 0
JLHWEB/src/views/system/formula-editor/editor.vue

@@ -0,0 +1,396 @@
+<template>
+  <div class="formula-container">
+    <el-row class="formula-editor">
+      <!-- 左侧编辑面板 -->
+      <el-col :xs="24" :span="16" class="left-panel">
+        <div class="editor-area">
+          <div class="operator-toolbar">
+            <el-button
+              v-for="op in operatorMap"
+              :key="op.value"
+              v-wave
+              class="operator-btn"
+              :title="op.name"
+              @click="insertOperator(op.value)"
+            >
+              <span class="symbol">{{ op.symbol }}</span>
+            </el-button>
+            <el-dropdown @command="insertFunction" trigger="click">
+              <el-button type="primary" class="func-btn">
+                <span>函数列表</span>
+              </el-button>
+              <template #dropdown>
+                <el-dropdown-menu class="func-menu">
+                  <el-dropdown-item v-for="func in functions" :key="func" :command="func" class="func-item">
+                    <span class="func-name">{{ func }}</span>
+                  </el-dropdown-item>
+                </el-dropdown-menu>
+              </template>
+            </el-dropdown>
+          </div>
+
+          <el-input
+            ref="editorRef"
+            v-model="formulaValue"
+            type="textarea"
+            :rows="8"
+            placeholder="请输入公式,例如:SUM(【基础工资】*1.2 + 【绩效奖金】)"
+            resize="none"
+            class="editor-input"
+            spellcheck="false"
+          >
+          </el-input>
+
+          <!-- <div class="action-buttons">
+            <el-button type="default" @click="handleReset">清空</el-button>
+            <el-button type="warning" @click="handleCheck">检查公式</el-button>
+            <el-button type="primary" @click="handleConfirm">确认</el-button>
+          </div> -->
+        </div>
+      </el-col>
+
+      <!-- 右侧参数面板 -->
+      <el-col :xs="24" :span="8" class="right-panel">
+        <div class="param-tree">
+          <!-- <div class="header">
+            <span>参数选择</span>
+          </div> -->
+
+          <el-input
+            v-model="filterText"
+            placeholder="搜索参数..."
+            clearable
+            class="search-box"
+            @input="handleSearchInput"
+            @keyup.enter="handleSearchEnter"
+            @clear="handleSearchClear"
+          />
+
+          <div class="tree-container">
+            <el-tree
+              ref="treeRef"
+              :data="props.mockParameters"
+              :props="defaultProps"
+              :filter-node-method="filterNode"
+              @node-click="handleNodeClick"
+              default-expand-all
+              node-key="text"
+            />
+          </div>
+        </div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, watch, nextTick } from "vue";
+import { ElMessage, ElTree } from "element-plus";
+import { debounce, cloneDeepWith, isObject } from "lodash-es";
+import { pinyin } from "pinyin-pro";
+import { FormulaCheck } from "@/api/modules/basicinfo";
+
+interface TreeNode {
+  text: string;
+  value?: number;
+  children?: TreeNode[];
+}
+interface FormulaEditorProps {
+  /** 初始公式值 */
+  modelValue: string;
+  /** 参数树数据 */
+  mockParameters: TreeNode[];
+}
+
+// Props定义
+const props = withDefaults(defineProps<FormulaEditorProps>(), {
+  modelValue: "",
+  mockParameters: () => [] as TreeNode[]
+});
+
+// Emits定义
+const emits = defineEmits<{
+  "update:modelValue": [value: string]; // v-model标准事件
+}>();
+
+// 响应式状态
+const formulaValue = ref<string>(props.modelValue);
+const filterText = ref("");
+const treeRef = ref<InstanceType<typeof ElTree>>();
+// 调整默认props配置
+const defaultProps = {
+  children: "children",
+  label: "text" // 与ElementUI Tree组件默认字段对齐
+};
+// 监听本地修改
+watch(formulaValue, newVal => {
+  emits("update:modelValue", newVal);
+});
+
+// 常量
+const operatorMap = ref([
+  {
+    value: "+",
+    symbol: "+",
+    name: "加"
+  },
+  {
+    value: "-",
+    symbol: "-",
+    name: "减"
+  },
+  {
+    value: "*",
+    symbol: "×",
+    name: "乘"
+  },
+  {
+    value: "/",
+    symbol: "÷",
+    name: "除"
+  },
+  {
+    value: "(",
+    symbol: "(",
+    name: "左括号"
+  },
+  {
+    value: ")",
+    symbol: ")",
+    name: "右括号"
+  },
+  {
+    value: "**",
+    symbol: "^",
+    name: "幂"
+  },
+  {
+    value: "%",
+    symbol: "%",
+    name: "模"
+  }
+]);
+const functions = ["SUM", "AVG", "IF", "MAX", "MIN"];
+
+const filterNode = (value: string, data: TreeNode, node: any): boolean => {
+  // 空搜索时保留有效结构
+  if (!value) {
+    // 空搜索时:保留所有有效节点及其祖先路径
+    const selfValid = !!data.value || data.children !== undefined;
+    const keepForStructure = node.parent?.expanded || selfValid;
+    return keepForStructure;
+  }
+
+  // 核心匹配逻辑(不依赖pinyinFilterTree的树裁剪)
+  const isSelfMatch = [data.text].some(
+    text =>
+      text &&
+      (text.includes(value) ||
+        pinyin(text, { toneType: "none", type: "array" }).join("").includes(value.toLowerCase()) ||
+        pinyin(text, { pattern: "first", type: "array", toneType: "none" }).join("").includes(value.toLowerCase()))
+  );
+
+  // 子节点匹配状态(需要展开父节点)
+  const hasChildMatch = node.childNodes?.some(child => child.data && filterNode(value, child.data, child));
+
+  // 保留逻辑:自身匹配或子节点匹配
+  const shouldShow = isSelfMatch || hasChildMatch;
+
+  return shouldShow;
+};
+
+// 优化有效性判断
+const isNodeValid = (node: TreeNode): boolean => {
+  // 有效条件:有code 或 有有效子节点
+  return !!node.value || (node.children?.some(isNodeValid) ?? false);
+};
+
+// 搜索处理逻辑
+const handleSearchInput = debounce((value: string) => {
+  treeRef.value?.filter(value);
+}, 300);
+
+// 回车键立即搜索
+const handleSearchEnter = () => {
+  treeRef.value?.filter(filterText.value);
+};
+
+// 清空时立即刷新
+const handleSearchClear = () => {
+  treeRef.value?.filter("");
+};
+
+const insertOperator = (op: string) => {
+  handleInsertFormula(op);
+};
+
+const insertFunction = (func: string) => {
+  handleInsertFormula(`${func}()`, -1);
+};
+
+const handleNodeClick = (data: TreeNode) => {
+  if (!data.value) return;
+  handleInsertFormula(`【${data.text}】`);
+};
+
+const handleInsertFormula = (data: string, pm: number = 0) => {
+  if (!data) return;
+  const textarea = document.querySelector(".editor-input textarea") as HTMLTextAreaElement;
+  if (!textarea) return;
+  const start = textarea.selectionStart;
+  const end = textarea.selectionEnd;
+  // 更新公式值
+  formulaValue.value = formulaValue.value.slice(0, start) + data + formulaValue.value.slice(end);
+  // 更新光标位置
+  nextTick(() => {
+    // 计算新光标位置(插入内容末尾)
+    const newPos = start + data.length;
+    textarea.setSelectionRange(newPos + pm, newPos + pm);
+    textarea.focus();
+  });
+};
+
+const handleReset = () => {
+  formulaValue.value = "";
+};
+
+const handleCheck = async (): Promise<boolean> => {
+  if (!formulaValue.value) {
+    ElMessage.warning("请先输入公式");
+    return false;
+  }
+
+  try {
+    const result = await FormulaCheck({
+      formula: formulaValue.value
+    });
+
+    if (result) {
+      ElMessage.success("公式检查通过");
+      return true;
+    }
+    return false;
+  } catch (error) {
+    return false;
+  }
+};
+
+// 暴露方法供父组件调用
+defineExpose({
+  reset: handleReset,
+  check: handleCheck
+});
+</script>
+
+<style scoped lang="scss">
+.formula-container {
+  height: 100%;
+  width: 100%;
+  min-height: 510px;
+  --editor-border: #dcdfe6;
+  --panel-bg: #f8f9fa;
+
+  .formula-editor {
+    height: calc(100% - 20px);
+    margin-top: 12px;
+
+    .left-panel {
+      .editor-area {
+        // border-color: var(--editor-border);
+        // box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+      }
+
+      .operator-toolbar {
+        gap: 12px;
+        .operator-btn {
+          min-width: 36px;
+          padding: 8px 12px;
+        }
+      }
+    }
+
+    .right-panel {
+      .param-tree {
+        border-color: var(--editor-border);
+        padding: 8px;
+
+        .header {
+          padding: 12px 16px;
+          font-size: 14px;
+          background: white;
+        }
+
+        .el-tree-node__content:hover {
+          background: rgba(64, 158, 255, 0.08);
+        }
+      }
+    }
+
+    .right-panel {
+      .search-box {
+        padding: 0 12px;
+        flex-shrink: 0;
+      }
+
+      .tree-container {
+        .el-tree {
+          padding: 0px 12px;
+        }
+      }
+    }
+  }
+
+  .editor-area {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    // border: 1px solid var(--el-border-color);
+    // border-radius: 4px;
+    // background: var(--el-bg-color);
+    padding: 8px;
+  }
+
+  .param-tree {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    // border: 1px solid var(--el-border-color);
+    // border-radius: 4px;
+    // background: var(--el-bg-color);
+    height: 100%;
+
+    .header {
+      padding: 16px;
+      font-weight: 500;
+    }
+
+    .tree-container {
+      flex: 1;
+      min-height: 0; /* 关键修复 */
+      overflow: hidden;
+
+      .el-tree {
+        height: 100%;
+        :deep(.el-tree-node__content) {
+          padding: 6px 0;
+        }
+      }
+    }
+  }
+
+  .operator-toolbar {
+    display: flex;
+    gap: 8px;
+    flex-wrap: wrap;
+    padding-bottom: 16px;
+  }
+
+  .action-buttons {
+    margin-top: auto;
+    padding-top: 16px;
+    display: flex;
+    gap: 12px;
+    justify-content: flex-end;
+  }
+}
+</style>

+ 185 - 0
JLHWEB/src/views/system/formula-editor/index.vue

@@ -0,0 +1,185 @@
+<template>
+  <LjDialog
+    ref="LjSelectorRef"
+    class="is-selector lj-selector"
+    v-bind="{
+      ...drawerDefineProp
+    }"
+    :modal="false"
+    :style="{ height: '60%' }"
+  >
+    <template #header>
+      <div class="flx-1">
+        <span class="text-h5-b">{{ props.title }}</span>
+      </div>
+    </template>
+    <div class="flx-col h-full">
+      <LjHeaderMenu :update="dialogVisible" :data="initParams" :action="btnGroup" />
+      <LjFoldLayoutDouble v-bind="showLayout" class="flx-1 overflow-hidden">
+        <template #secondMain>
+          <div class="flx h-full">
+            <div class="flx-col w-full">
+              <FormulaEditor ref="formulaEditorRef" v-model="localFormula" :mock-parameters="mockParameters" />
+            </div>
+          </div>
+        </template>
+      </LjFoldLayoutDouble>
+    </div>
+  </LjDialog>
+</template>
+<script setup lang="ts">
+import { ref, computed, onMounted, onBeforeUnmount, nextTick, watch } from "vue";
+import LjDialog from "@/components/LjDialog/index.vue";
+import LjFoldLayoutDouble from "@/components/LjFoldLayoutDouble/index.vue";
+import FormulaEditor from "./editor.vue";
+import { GetFormulaVarList, FormulaCheck } from "@/api/modules/basicinfo";
+import { useAuthButtons } from "@/hooks/useAuthButtons";
+import { useI18n } from "vue-i18n";
+
+const { t } = useI18n();
+const { buttonDefault } = useAuthButtons(t);
+
+const props = defineProps({
+  title: {
+    type: String,
+    default: "公式编辑器"
+  }
+});
+
+const emit = defineEmits(["confirm"]);
+const localFormula = ref<string>("");
+const mockParameters = ref([]);
+
+const LjSelectorRef = ref();
+const dialogVisible = ref(false);
+
+const formulaEditorRef = ref();
+/*
+ * @description 抽屉默认属性
+ */
+const drawerDefineProp = {
+  draggable: true,
+  overflow: true,
+  width: "60%",
+  modalClass: "lj-file-dialog"
+};
+const showLayout = ref<any>(false);
+const initParams = ref<any>();
+const btnGroup = ref<any>([
+  buttonDefault({
+    label: "取消",
+    clickFunc: item => {
+      hide();
+    }
+  }),
+  buttonDefault({
+    label: "清空",
+    clickFunc: item => {
+      formulaEditorRef.value.reset();
+    }
+  }),
+  buttonDefault({
+    label: "检查公式",
+    clickFunc: item => {
+      formulaEditorRef.value.check();
+    }
+  }),
+  buttonDefault({
+    label: "确认",
+    clickFunc: item => {
+      handleConfirm();
+    }
+  })
+]);
+
+const handleConfirm = async () => {
+  // 调用子组件检查方法
+  const isValid = await formulaEditorRef.value?.check();
+
+  if (!isValid) {
+    return;
+  }
+
+  // 提交有效公式
+  emit("confirm", localFormula.value);
+  hide();
+};
+
+const getFormulaVarList = async () => {
+  const res = (await GetFormulaVarList()) as any;
+  mockParameters.value = res.recursionList;
+};
+
+/**
+ * @description 展示组件
+ * @param params 入参
+ * @param activeName 多选时 活动面板入参
+ */
+const show = (refresh?: boolean, formula: string = "") => {
+  localFormula.value = formula;
+  LjSelectorRef.value.show(refresh);
+  nextTick(() => {
+    dialogVisible.value = true;
+  });
+};
+
+/**
+ * @description cancel 取消
+ */
+const hide = () => {
+  // 关闭弹窗
+  LjSelectorRef.value.hide();
+  dialogVisible.value = false;
+};
+
+onMounted(() => {
+  getFormulaVarList();
+});
+
+defineExpose({
+  show,
+  hide
+});
+</script>
+
+<style lang="scss">
+.lj-selector {
+  padding: 0;
+  .el-tree {
+    background: unset;
+  }
+}
+.ljselector-aside-collapse {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  // background-color: $color-gray-2;
+
+  .el-collapse-item {
+    display: flex;
+    flex-direction: column;
+    overflow: auto;
+    height: auto;
+
+    &.is-active {
+      flex: 1;
+    }
+    &:not(.is-active) {
+      flex-shrink: 0;
+    }
+    .el-collapse-item__header {
+      flex-shrink: 0;
+    }
+    .el-collapse-item__wrap {
+      flex: 1;
+      overflow: auto;
+      background-color: $color-gray-2;
+      padding: 8px 4px;
+
+      .lj-infor-item {
+        background-color: $color-gray-1;
+      }
+    }
+  }
+}
+</style>