|
|
@@ -0,0 +1,435 @@
|
|
|
+local utils = require("utils")
|
|
|
+local fw = require("fastweb")
|
|
|
+local config = require("fwutils.config")
|
|
|
+local request = require("fastweb.request")
|
|
|
+local response = require("fastweb.response")
|
|
|
+local cjson = require("cjson")
|
|
|
+-- 允许的扩展名
|
|
|
+local allowed_extensions = {
|
|
|
+ "shtml",
|
|
|
+ "html",
|
|
|
+ "js"
|
|
|
+}
|
|
|
+local template_engine_bc_this_role = nil
|
|
|
+local cfg = nil
|
|
|
+
|
|
|
+local M = {}
|
|
|
+
|
|
|
+function file_get_contents(filepath)
|
|
|
+
|
|
|
+ local file, errmsg = io.open(fw.website_dir()..filepath, "r")
|
|
|
+ if not file then
|
|
|
+ err.server(errmsg)
|
|
|
+ end
|
|
|
+ local content = file:read("*a")
|
|
|
+ if content == nil or content == "" then
|
|
|
+ print("file_get_contents error: ",filepath)
|
|
|
+ return ""
|
|
|
+ end
|
|
|
+ local replaced,c2 = M.replace(content)
|
|
|
+ if replaced then
|
|
|
+ return c2
|
|
|
+ end
|
|
|
+ return content
|
|
|
+end
|
|
|
+function menu_top()
|
|
|
+ -- 当前请求路径
|
|
|
+ local request_path = request.filepath() or ""
|
|
|
+ -- 取配置
|
|
|
+ local menuData = require("fwutils.menu").get(string.format("%d",cfg.role_id()))
|
|
|
+ if not menuData then
|
|
|
+ return "<!-- no menu config -->"
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 提取并排序主菜单
|
|
|
+ local menuItems = {}
|
|
|
+ for name, item in pairs(menuData) do
|
|
|
+ table.insert(menuItems, {
|
|
|
+ name = name,
|
|
|
+ item = item,
|
|
|
+ sort = item.sort or 0
|
|
|
+ })
|
|
|
+ end
|
|
|
+ table.sort(menuItems, function(a, b)
|
|
|
+ return a.sort > b.sort
|
|
|
+ end)
|
|
|
+
|
|
|
+ local html = [[
|
|
|
+
|
|
|
+ <ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
|
|
+ ]]
|
|
|
+
|
|
|
+ -- 渲染主菜单
|
|
|
+ for _, entry in ipairs(menuItems) do
|
|
|
+ local name = entry.name
|
|
|
+ local item = entry.item
|
|
|
+ local icon = item.icon or ""
|
|
|
+ local path = item.path or "#"
|
|
|
+ local activeClass = ""
|
|
|
+
|
|
|
+ if item.children then
|
|
|
+ -- 有子菜单
|
|
|
+ local isMegamenu = item.megamenu and " dropdown-megamenu" or ""
|
|
|
+
|
|
|
+ -- 检查子菜单是否有激活
|
|
|
+ local hasActiveChild = false
|
|
|
+ local children = {}
|
|
|
+ for childName, childItem in pairs(item.children) do
|
|
|
+ table.insert(children, {
|
|
|
+ name = childName,
|
|
|
+ item = childItem,
|
|
|
+ sort = childItem.sort or 0
|
|
|
+ })
|
|
|
+ if not hasActiveChild and (request_path == (childItem.path or "")) then
|
|
|
+ hasActiveChild = true
|
|
|
+ end
|
|
|
+ end
|
|
|
+ table.sort(children, function(a, b)
|
|
|
+ return a.sort > b.sort
|
|
|
+ end)
|
|
|
+
|
|
|
+ -- 父菜单激活:当前页面是父菜单path,或是任一子菜单path
|
|
|
+ if request_path == path or hasActiveChild then
|
|
|
+ activeClass = "active-link"
|
|
|
+ end
|
|
|
+
|
|
|
+ html = html .. [[
|
|
|
+ <li class="nav-item dropdown ]] .. activeClass .. [[">
|
|
|
+ <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
|
|
+ <i class="]] .. icon .. [["></i> ]] .. name .. [[
|
|
|
+ </a>
|
|
|
+ <ul class="dropdown-menu]] .. isMegamenu .. [[">
|
|
|
+ ]]
|
|
|
+
|
|
|
+ -- 渲染子菜单
|
|
|
+ for _, child in ipairs(children) do
|
|
|
+ local childName = child.name
|
|
|
+ local childItem = child.item
|
|
|
+ local childPath = childItem.path or "#"
|
|
|
+
|
|
|
+ -- 子菜单不设置激活样式
|
|
|
+ html = html .. [[
|
|
|
+ <li>
|
|
|
+ <a class="dropdown-item" href="]] .. childPath .. [[">
|
|
|
+ <span>]] .. childName .. [[</span>
|
|
|
+ </a>
|
|
|
+ </li>
|
|
|
+ ]]
|
|
|
+ end
|
|
|
+
|
|
|
+ html = html .. [[
|
|
|
+ </ul>
|
|
|
+ </li>
|
|
|
+ ]]
|
|
|
+ else
|
|
|
+ -- 没有子菜单
|
|
|
+ -- 简单的“当前页面是否激活”检查
|
|
|
+ if request_path == path then
|
|
|
+ activeClass = "active-link"
|
|
|
+ end
|
|
|
+ html = html .. [[
|
|
|
+ <li class="nav-item ]] .. activeClass .. [[">
|
|
|
+ <a class="nav-link" href="]] .. path .. [[">
|
|
|
+ <i class="]] .. icon .. [["></i> ]] .. name .. [[
|
|
|
+ </a>
|
|
|
+ </li>
|
|
|
+ ]]
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ html = html .. [[
|
|
|
+ </ul>
|
|
|
+ ]]
|
|
|
+
|
|
|
+ return [=[
|
|
|
+ <nav class="navbar navbar-expand-lg">
|
|
|
+ <div class="container">
|
|
|
+ <div class="offcanvas offcanvas-end" id="MobileMenu">
|
|
|
+ <div class="offcanvas-header">
|
|
|
+ <h5 class="offcanvas-title semibold">Navigation</h5>
|
|
|
+ <button type="button" class="btn btn-danger btn-sm ms-auto" data-bs-dismiss="offcanvas">
|
|
|
+ <i class="icon-clear"></i>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ ]=]..html..[[
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </nav>
|
|
|
+ ]]
|
|
|
+end
|
|
|
+function template(filepath)
|
|
|
+ local path = "/public/user/template/"..config.agent[user_agent_id()].template.."/"..filepath
|
|
|
+ return file_get_contents(path)
|
|
|
+end
|
|
|
+
|
|
|
+function teacher_photos()
|
|
|
+ require("app.app")
|
|
|
+ local agent_id = user_agent_id()
|
|
|
+ local teacher = require("app.function.teacher")
|
|
|
+ local teacher_info = teacher.get_by_id(pint("id"))
|
|
|
+ if teacher_info == nil then
|
|
|
+ return ""
|
|
|
+ end
|
|
|
+ local photos = cjson.decode(teacher_info.photo)
|
|
|
+ local content = "<div><div class='home-img'><img src='".. teacher_info.avatar.."' class='img-fluid bg-img' alt=''></div></div>"
|
|
|
+ for i,v in ipairs(photos) do
|
|
|
+ if type(v) == "string" then
|
|
|
+ content = content..[[<div>
|
|
|
+ <div class="home-img">
|
|
|
+ <img src="]]..v..[[" class="img-fluid bg-img" alt="">
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ ]]
|
|
|
+ end
|
|
|
+ end
|
|
|
+ return content
|
|
|
+end
|
|
|
+-- 更新
|
|
|
+M.update = function(conn)
|
|
|
+ -- 查询权限表
|
|
|
+ local select = conn:select()
|
|
|
+ select:table("fw_template")
|
|
|
+ select:where_i32("enable","=",1)
|
|
|
+ local result = select:query()
|
|
|
+ local bc = {
|
|
|
+ public = {}
|
|
|
+ }
|
|
|
+ while result:next() do
|
|
|
+ local id = result:get("id")
|
|
|
+ local role_id = string.format("%d",result:get("role_id"))
|
|
|
+ local key = result:get("key")
|
|
|
+ local value = result:get("value")
|
|
|
+
|
|
|
+
|
|
|
+ if bc[role_id] == nil then
|
|
|
+ bc[role_id] = {}
|
|
|
+ end
|
|
|
+ if role_id == "0" then
|
|
|
+ bc["public"][key] = value
|
|
|
+ else
|
|
|
+ bc[role_id][key] = value
|
|
|
+ end
|
|
|
+ end
|
|
|
+ local code = "return " .. require("serpent").serialize(bc, {comment = false})
|
|
|
+ utils.save_file(fw.website_dir().."/"..(config.path.luabytecode:gsub("%.", "/")).."/template_engine_bc.lua",code)
|
|
|
+ return true
|
|
|
+end
|
|
|
+-- 处理
|
|
|
+-- @param static_content 静态内容
|
|
|
+-- @param cfg 配置
|
|
|
+-- @return 是否替换(TRUE则不需要继续处理,FALSE则继续处理)
|
|
|
+M.handle = function(__cfg)
|
|
|
+
|
|
|
+ local function has_ext(ext)
|
|
|
+ for _, v in ipairs(allowed_extensions) do
|
|
|
+ if v == ext then
|
|
|
+ return true
|
|
|
+ end
|
|
|
+ end
|
|
|
+ return false
|
|
|
+ end
|
|
|
+
|
|
|
+
|
|
|
+ cfg = __cfg
|
|
|
+ template_engine_bc_this_role = {
|
|
|
+ template = {
|
|
|
+ private = {},
|
|
|
+ public = {}
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ local ext = utils.ext(cfg.filepath())
|
|
|
+ local static_content = nil
|
|
|
+ if ext ~= nil then
|
|
|
+ if not has_ext(ext) then
|
|
|
+ return false
|
|
|
+ end
|
|
|
+ -- 读取资源文件
|
|
|
+ static_content = utils.read_file(fw.website_dir()..cfg.filepath())
|
|
|
+ if static_content == nil or static_content == "" then
|
|
|
+ return false
|
|
|
+ end
|
|
|
+ else
|
|
|
+ -- 无需替换的扩展名
|
|
|
+ return false
|
|
|
+ end
|
|
|
+ local template_engine_bc = require(config.path.luabytecode..".template_engine_bc")
|
|
|
+ template_engine_bc_this_role["template"]["private"] = template_engine_bc[string.format("%d",cfg.role_id())]
|
|
|
+ template_engine_bc_this_role["template"]["public"] = template_engine_bc["public"]
|
|
|
+
|
|
|
+
|
|
|
+ local replaced,content = M.replace(static_content)
|
|
|
+ if replaced then
|
|
|
+ static_content = content
|
|
|
+ end
|
|
|
+ -- 执行函数
|
|
|
+ static_content, n = static_content:gsub("%${<<<%s*(.-)%s*>>>}", function(code)
|
|
|
+ -- 尝试编译代码(Lua 5.2+ 使用 load;Lua 5.1 可用 loadstring)
|
|
|
+ local chunk, errmsg = load(code)
|
|
|
+ if not chunk then
|
|
|
+ fw.throw_string(errmsg)
|
|
|
+ end
|
|
|
+ -- 使用 pcall 安全执行代码块
|
|
|
+ local status, result = pcall(chunk)
|
|
|
+ if not status then
|
|
|
+ fw.throw_string(result)
|
|
|
+ end
|
|
|
+ -- 如果代码没有返回值,则替换为空字符串,否则转换成字符串返回
|
|
|
+ return tostring(result)
|
|
|
+ end)
|
|
|
+
|
|
|
+ if n > 0 then
|
|
|
+ replaced = true
|
|
|
+ end
|
|
|
+
|
|
|
+ if replaced then
|
|
|
+ if ext == "shtml" or ext == "html" then
|
|
|
+ response.header("Content-Type","text/html")
|
|
|
+ elseif ext == "js" then
|
|
|
+ response.header("Content-Type","application/javascript")
|
|
|
+ end
|
|
|
+ response.send(static_content)
|
|
|
+ return true
|
|
|
+ end
|
|
|
+ return false
|
|
|
+end
|
|
|
+M.replace = function (content)
|
|
|
+ if content == nil or content == "" then
|
|
|
+ return false
|
|
|
+ end
|
|
|
+ -- 支持多级kvs替换,如 ${people.age}
|
|
|
+ local function flatten_kvs(tbl, prefix, out)
|
|
|
+ if tbl == nil then
|
|
|
+ return {}
|
|
|
+ end
|
|
|
+ out = out or {}
|
|
|
+ prefix = prefix or ""
|
|
|
+ for k, v in pairs(tbl) do
|
|
|
+ local key = prefix ~= "" and (prefix .. "." .. k) or k
|
|
|
+ if type(v) == "table" then
|
|
|
+ flatten_kvs(v, key, out)
|
|
|
+ else
|
|
|
+ out[key] = v
|
|
|
+ end
|
|
|
+ end
|
|
|
+ return out
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 先提取 content 中所有需要替换的占位符
|
|
|
+ -- 需要正确处理 ${<<<...>>>} 块:块内部的 ${...} 需要替换,但块本身不替换
|
|
|
+ local placeholders = {}
|
|
|
+
|
|
|
+ -- 第一步:提取所有 ${<<<...>>>} 块,临时替换它们,并收集所有占位符
|
|
|
+ local blocks = {}
|
|
|
+ local block_index = 0
|
|
|
+ local temp_content = content:gsub("%${<<<%s*(.-)%s*>>>}", function(block_content)
|
|
|
+ block_index = block_index + 1
|
|
|
+ local placeholder = "${__TEMP_BLOCK_" .. block_index .. "__}"
|
|
|
+ -- 收集块内部的 ${...} 占位符
|
|
|
+ for ph in string.gmatch(block_content, "%${([^}]+)}") do
|
|
|
+ placeholders[ph] = true
|
|
|
+ end
|
|
|
+ blocks[block_index] = {
|
|
|
+ placeholder = placeholder,
|
|
|
+ content = block_content
|
|
|
+ }
|
|
|
+ return placeholder
|
|
|
+ end)
|
|
|
+
|
|
|
+ -- 第二步:提取外部(不在 ${<<<...>>>} 块中)的 ${...} 占位符
|
|
|
+ for placeholder in string.gmatch(temp_content, "%${([^}]+)}") do
|
|
|
+ -- 忽略临时占位符
|
|
|
+ if not placeholder:match("^__TEMP_BLOCK_%d+__$") then
|
|
|
+ placeholders[placeholder] = true
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ -- print("PLACEHOLDERS:",cjson.encode(placeholders))
|
|
|
+
|
|
|
+ -- 如果没有任何占位符,直接返回
|
|
|
+ if next(placeholders) == nil then
|
|
|
+ return false
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 合并所有数据源到一个查找表中(只 flatten 一次)
|
|
|
+ local value_map = {}
|
|
|
+
|
|
|
+ -- PUBLIC
|
|
|
+ local flat_kvs = flatten_kvs(template_engine_bc_this_role["template"]["public"])
|
|
|
+ for k, v in pairs(flat_kvs) do
|
|
|
+ value_map[k] = v
|
|
|
+ end
|
|
|
+ -- PRIVATE
|
|
|
+ flat_kvs = flatten_kvs(template_engine_bc_this_role["template"]["private"])
|
|
|
+ for k, v in pairs(flat_kvs) do
|
|
|
+ value_map[k] = v
|
|
|
+ end
|
|
|
+ -- REQUEST
|
|
|
+ flat_kvs = flatten_kvs(request.gets(), "request")
|
|
|
+ for k, v in pairs(flat_kvs) do
|
|
|
+ value_map[k] = v
|
|
|
+ end
|
|
|
+ -- TOKEN
|
|
|
+ flat_kvs = flatten_kvs(cfg.user_data(), "token")
|
|
|
+ for k, v in pairs(flat_kvs) do
|
|
|
+ value_map[k] = v
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 转义函数:将 Lua 模式特殊字符转义为字面匹配
|
|
|
+ local function escape_pattern(str)
|
|
|
+ return str:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 第三步:先处理 ${<<<...>>>} 块内部的占位符
|
|
|
+ local replaced = false
|
|
|
+ for i, block in ipairs(blocks) do
|
|
|
+ local block_content = block.content
|
|
|
+ for placeholder, _ in pairs(placeholders) do
|
|
|
+ if value_map[placeholder] ~= nil then
|
|
|
+ local escaped_placeholder = escape_pattern(placeholder)
|
|
|
+ local new_content, count = string.gsub(block_content, "%${" .. escaped_placeholder .. "}", tostring(value_map[placeholder]))
|
|
|
+ if count > 0 then
|
|
|
+ block_content = new_content
|
|
|
+ replaced = true
|
|
|
+ end
|
|
|
+ end
|
|
|
+ end
|
|
|
+ blocks[i].processed_content = block_content
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 第四步:替换外部的 ${...} 占位符(在临时内容中,此时块已被替换为临时占位符)
|
|
|
+ local n = 0
|
|
|
+ for placeholder, _ in pairs(placeholders) do
|
|
|
+ if value_map[placeholder] ~= nil then
|
|
|
+ local escaped_placeholder = escape_pattern(placeholder)
|
|
|
+ temp_content, n = string.gsub(temp_content, "%${" .. escaped_placeholder .. "}", tostring(value_map[placeholder]))
|
|
|
+ if n > 0 then
|
|
|
+ replaced = true
|
|
|
+ end
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 第五步:恢复 ${<<<...>>>} 块(使用处理后的内容)
|
|
|
+ for i, block in ipairs(blocks) do
|
|
|
+ local processed_block = "${<<<" .. blocks[i].processed_content .. ">>>}"
|
|
|
+ temp_content = temp_content:gsub(block.placeholder:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1"), processed_block)
|
|
|
+ end
|
|
|
+ content = temp_content
|
|
|
+
|
|
|
+ if replaced then
|
|
|
+ return true, content
|
|
|
+ end
|
|
|
+ return false
|
|
|
+end
|
|
|
+
|
|
|
+-- 检查内容中是否有TOKEN变量
|
|
|
+-- @return boolean
|
|
|
+M.hasToken = function()
|
|
|
+ if M.static_content == nil or M.static_content == "" then
|
|
|
+ return false
|
|
|
+ end
|
|
|
+ return M.static_content:match("%${token%.") ~= nil
|
|
|
+end
|
|
|
+
|
|
|
+return M
|