| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435 |
- 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
|