瀏覽代碼

luci-app-ssr-plus: Fix and optimized code. (#1787)

* luci-app-ssr-plus: Fix and optimized code.

* luci-app-ssr-plus: transport protocols `raw flow` enables xudp.

---------

Co-authored-by: zxlhhyccc <[email protected]>
zxl hhyccc 1 月之前
父節點
當前提交
e343d464f2

+ 3 - 3
luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua

@@ -367,7 +367,7 @@ if is_finded("xray") then
 	o.default = "10-20"
 	o:depends("fragment", true)
 
-	o = s:option(Value, "fragment_maxsplit", translate("Fragment maxSplit"), translate("Fragmented maxSplit (byte)"))
+	o = s:option(Value, "fragment_maxsplit", translate("Max Split"), translate("Limit the maximum number of splits."))
 	o.default = "100-200"
 	o:depends("fragment", true)
 
@@ -421,9 +421,9 @@ if is_finded("xray") then
 	o.datatype = "or(uinteger,portrange)"
 	o.rmempty = false
 
-	o = s:option(Value, "applyto", translate("ApplyTo (IP type)"))
+	o = s:option(Value, "applyto", translate("IP Type"))
 	o.default = "IP"
-	o:value("IP", "IP")
+	o:value("IP", "ALL")
 	o:value("IPV4", "IPv4")
 	o:value("IPV6", "IPv6")
 	o.rmempty = false

+ 21 - 19
luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua

@@ -477,7 +477,7 @@ o.datatype = "uinteger"
 o.rmempty = true
 o.default = "8388608"
 
-o = s:option(Value, "maxstreamseceivewindow", translate("QUIC maxStreamReceiveWindow"))
+o = s:option(Value, "maxstreamreceivewindow", translate("QUIC maxStreamReceiveWindow"))
 o:depends({type = "hysteria2", flag_quicparam = "1"})
 o.datatype = "uinteger"
 o.rmempty = true
@@ -689,7 +689,7 @@ o:depends({type = "v2ray", v2ray_protocol = "vless"})
 o = s:option(Value, "vless_encryption", translate("VLESS Encryption"))
 o.rmempty = true
 o.default = "none"
-o:value("none")
+o:value("none") 
 o:depends({type = "v2ray", v2ray_protocol = "vless"})
 
 -- 加密方式
@@ -851,18 +851,6 @@ o.validate = function(self, value)
     return value
 end
 
--- XHTTP ALPN
-o = s:option(ListValue, "xhttp_alpn", translate("XHTTP ALPN"))
-o.default = ""
-o:value("", translate("Default"))
-o:value("h3")
-o:value("h2")
-o:value("h3,h2")
-o:value("http/1.1")
-o:value("h2,http/1.1")
-o:value("h3,h2,http/1.1")
-o:depends({transport = "xhttp", tls = true})
-
 -- [[ H2部分 ]]--
 
 -- H2域名
@@ -1179,10 +1167,24 @@ o:depends("xtls", true)
 o:depends("reality", true)
 o.rmempty = true
 
+-- TLS ALPN
 o = s:option(ListValue, "tls_alpn", translate("TLS ALPN"))
 o.default = ""
 o:value("", translate("Default"))
 o:value("h3")
+o:value("h2")
+o:value("h3,h2")
+o:value("http/1.1")
+o:value("h2,http/1.1")
+o:value("h3,h2,http/1.1")
+o:depends({type = "hysteria2", tls = true})
+o:depends({transport = "xhttp", tls = true})
+
+-- TUIC ALPN
+o = s:option(ListValue, "tuic_alpn", translate("TUIC ALPN"))
+o.default = ""
+o:value("", translate("Default"))
+o:value("h3")
 o:value("spdy/3.1")
 o:value("h3,spdy/3.1")
 o:depends("type", "tuic")
@@ -1196,19 +1198,18 @@ o.description = translate("If true, allowss insecure connection at TLS client, e
 
 -- [[ Hysteria2 TLS pinSHA256 ]] --
 o = s:option(Value, "pinsha256", translate("Certificate fingerprint"))
-o:depends({type = "hysteria2", insecure = true })
+o:depends("type", "hysteria2")
 o.rmempty = true
 
-
 -- [[ Mux.Cool ]] --
 o = s:option(Flag, "mux", translate("Mux"), translate("Enable Mux.Cool"))
 o.rmempty = false
 o.default = false
-o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "raw"})
+o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "raw", tls_flow = "none"})
 o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "ws"})
 o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "kcp"})
 o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "httpupgrade"})
-o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "splithttp"})
+o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "xhttp"})
 o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "h2"})
 o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "quic"})
 o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "grpc"})
@@ -1222,6 +1223,8 @@ o:depends({type = "v2ray", v2ray_protocol = "http"})
 o = s:option(Flag, "xmux", translate("Xudp Mux"), translate("Enable Xudp Mux"))
 o.rmempty = false
 o.default = false
+o:depends({type = "v2ray", v2ray_protocol = "vless", tls_flow = "xtls-rprx-vision"})
+o:depends({type = "v2ray", v2ray_protocol = "vless", tls_flow = "xtls-rprx-vision-udp443"})
 o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "xhttp"})
 
 -- [[ TCP 最大并发连接数 ]]--
@@ -1392,4 +1395,3 @@ if is_finded("kcptun-client") then
 end
 
 return m
-

+ 24 - 12
luci-app-ssr-plus/luasrc/view/shadowsocksr/ssrurl.htm

@@ -112,7 +112,7 @@ function import_ssr_url(btn, urlname, sid) {
 			document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = (ssu[0] === "hy2") ? "hysteria2" : ssu[0];
 			document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
 			document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = url.hostname;
-			document.getElementsByName('cbid.shadowsocksr.' + sid + '.server_port')[0].value = url.port || "80";
+			document.getElementsByName('cbid.shadowsocksr.' + sid + '.server_port')[0].value = url.port || "443";
 			if (params.get("lazy") === "1") { 
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.lazy_mode')[0].checked = true;
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.lazy_mode')[0].dispatchEvent(event);
@@ -142,18 +142,22 @@ function import_ssr_url(btn, urlname, sid) {
 			document.getElementsByName('cbid.shadowsocksr.' + sid + '.obfs_type')[0].value = params.get("obfs");
 			document.getElementsByName('cbid.shadowsocksr.' + sid + '.salamander')[0].value = params.get("obfs-password") || params.get("obfs_password");
 			}
-			if (params.get("sni")) {
+			if (params.get("sni") || params.get("alpn")) {
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].checked = true; // 设置 flag_obfs 为 true
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].dispatchEvent(event); // 触发事件
-
-			document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_host')[0].value = params.get("sni") || "";
+				if (params.get("sni")) {
+					document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_host')[0].value = params.get("sni") || "";
+				}
+				if (params.get("alpn")) {
+					document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_alpn')[0].value = params.get("alpn") || "";
+				}
 			}
 			if (params.get("insecure") === "1") { 
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.insecure')[0].checked = true;
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.insecure')[0].dispatchEvent(event);
-				if (params.get("sni")) {
-					document.getElementsByName('cbid.shadowsocksr.' + sid + '.pinsha256')[0].value = params.get("pinsha256") || "";
-				}
+			}
+			if (params.get("pinSHA256")) {
+				document.getElementsByName('cbid.shadowsocksr.' + sid + '.pinsha256')[0].value = params.get("pinSHA256") || "";
 			}
 			document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = url.hash ? decodeURIComponent(url.hash.slice(1)) : "";
 
@@ -368,11 +372,11 @@ function import_ssr_url(btn, urlname, sid) {
 				params.get("type") == "http" ? "h2" : 
 				(["xhttp", "splithttp"].includes(params.get("type")) ? "xhttp" : 
 				(["tcp", "raw"].includes(params.get("type")) ? "raw" : 
-				(params.get("type") || "tcp")));
+				(params.get("type") || "raw")));
 			document.getElementsByName('cbid.shadowsocksr.' + sid + '.transport')[0].dispatchEvent(event);
 			if (params.get("security") === "tls") {
 				if (params.get("type") == "xhttp" || params.get("type") == "splithttp") {
-					document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_alpn')[0].value = params.get("alpn") || "";
+					document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_alpn')[0].value = params.get("alpn") || "";
 				}
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.fingerprint')[0].value = params.get("fp") || "";
 			}
@@ -500,7 +504,7 @@ function import_ssr_url(btn, urlname, sid) {
 					document.getElementsByName('cbid.shadowsocksr.' + sid + '.fingerprint')[0].value = ssm.fp;
 				}
 				if (ssm.net == "xhttp" || ssm.net == "splithttp") {
-					document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_alpn')[0].value = ssm.alpn;
+					document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_alpn')[0].value = ssm.alpn;
 				}
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_host')[0].value = ssm.sni || ssm.host;
 				if (ssm.ech !== "" && ssm.ech !== undefined) {
@@ -508,6 +512,11 @@ function import_ssr_url(btn, urlname, sid) {
 					document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_ech')[0].dispatchEvent(event); // 触发事件
 					document.getElementsByName('cbid.shadowsocksr.' + sid + '.ech_config')[0].value = ssm.ech;
 				}
+				if (((ssm.allowInsecure !== undefined) ? ssm.allowInsecure : ssm.allowlnsecure) === "true" ||
+				((ssm.allowInsecure !== undefined) ? ssm.allowInsecure : ssm.allowlnsecure) === "1") {
+					document.getElementsByName('cbid.shadowsocksr.' + sid + '.insecure')[0].checked = true; // 设置 insecure 为 true
+					document.getElementsByName('cbid.shadowsocksr.' + sid + '.insecure')[0].dispatchEvent(event); // 触发事件
+				}
 			}
 			if (ssm.mux !== undefined) {
 				document.getElementsByName('cbid.shadowsocksr.' + sid + '.mux')[0].checked = true;
@@ -570,6 +579,10 @@ function import_ssr_url(btn, urlname, sid) {
 						dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.enable_ech', event); // 触发事件
 						setElementValue('cbid.shadowsocksr.' + sid + '.ech_config', params.get("ech") || "");
 					}
+					if (params.get("allowInsecure") === "1") {
+						setElementValue('cbid.shadowsocksr.' + sid + '.insecure', true); // 设置 insecure 为 true
+						dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.insecure', event); // 触发事件
+					}
 				}
 				if (params.get("security") === "reality") {
 					setElementValue('cbid.shadowsocksr.' + sid + '.reality_publickey', params.get("pbk") ? decodeURIComponent(params.get("pbk")) : "");
@@ -584,7 +597,7 @@ function import_ssr_url(btn, urlname, sid) {
 				setElementValue('cbid.shadowsocksr.' + sid + '.tls_flow', params.get("flow") || "none");
 				dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.tls_flow', event);
 
-				setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_alpn', params.get("alpn") || "");
+				setElementValue('cbid.shadowsocksr.' + sid + '.tls_alpn', params.get("alpn") || "");
 				setElementValue('cbid.shadowsocksr.' + sid + '.fingerprint', params.get("fp") || "");
 				setElementValue('cbid.shadowsocksr.' + sid + '.tls_host', params.get("sni") || "");
 			}
@@ -655,4 +668,3 @@ function import_ssr_url(btn, urlname, sid) {
 <input type="button" class="btn cbi-button cbi-button-apply" value="<%:Import%>" onclick="return import_ssr_url(this, '<%=self.option%>', '<%=self.value%>')" />
 <span id="<%=self.option%>-status"></span>
 <%+cbi/valuefooter%>
-

文件差異過大導致無法顯示
+ 166 - 166
luci-app-ssr-plus/po/templates/ssr-plus.pot


文件差異過大導致無法顯示
+ 166 - 166
luci-app-ssr-plus/po/zh_Hans/ssr-plus.po


+ 42 - 16
luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua

@@ -31,7 +31,7 @@ function vmess_vless()
 						id = server.vmess_id,
 						alterId = (server.v2ray_protocol == "vmess" or not server.v2ray_protocol) and tonumber(server.alter_id) or nil,
 						security = (server.v2ray_protocol == "vmess" or not server.v2ray_protocol) and server.security or nil,
-						encryption = (server.v2ray_protocol == "vless") and server.vless_encryption or "none",
+						encryption = (server.v2ray_protocol == "vless") and server.vless_encryption or "none", 
 						flow = (((server.xtls == '1') or (server.tls == '1') or (server.reality == '1')) and (((server.tls_flow ~= "none") and server.tls_flow) or ((server.xhttp_tls_flow ~= "none") and server.xhttp_tls_flow))) or nil
 					}
 				}
@@ -208,14 +208,14 @@ end
 			settings = outbound_settings,
 			-- 底层传输配置
 			streamSettings = (server.v2ray_protocol ~= "wireguard") and {
-				network = server.transport or "tcp",
+				network = server.transport or "raw",
 				security = (server.xtls == '1') and "xtls" or (server.tls == '1') and "tls" or (server.reality == '1') and "reality" or nil,
 				tlsSettings = (server.tls == '1') and {
 					-- tls
 					alpn = (server.transport == "xhttp") and (function()
 						local alpn = {}
-						if server.xhttp_alpn and server.xhttp_alpn ~= "" then
-							string.gsub(server.xhttp_alpn, '[^,]+', function(w)
+						if server.tls_alpn and server.tls_alpn ~= "" then
+							string.gsub(server.tls_alpn, '[^,]+', function(w)
 								table.insert(alpn, w)
 							end)
 						end
@@ -243,8 +243,8 @@ end
 				} or nil,
 				realitySettings = (server.reality == '1') and {
 					publicKey = server.reality_publickey,
-					shortId = server.reality_shortid,
-					spiderX = server.reality_spiderx,
+					shortId = server.reality_shortid or "",
+					spiderX = server.reality_spiderx or "",
 					fingerprint = server.fingerprint,
 					mldsa65Verify = (server.enable_mldsa65verify == '1') and server.reality_mldsa65verify or nil,
 					serverName = server.tls_host
@@ -274,8 +274,8 @@ end
 				} or nil,
 				wsSettings = (server.transport == "ws") and (server.ws_path or server.ws_host or server.tls_host) and {
 					-- ws
-					Host = server.ws_host or server.tls_host or nil,
-					path = server.ws_path,
+					host = server.ws_host or server.tls_host or nil,
+					path = server.ws_path or "/",
 					maxEarlyData = tonumber(server.ws_ed) or nil,
 					earlyDataHeaderName = server.ws_ed_header or nil
 				} or nil,
@@ -474,7 +474,7 @@ local hysteria2 = {
 	} or nil,
 	quic = (server.flag_quicparam == "1" ) and {
 		initStreamReceiveWindow = (server.initstreamreceivewindow and server.initstreamreceivewindow or nil),
-		maxStreamReceiveWindow = (server.maxstreamseceivewindow and server.maxstreamseceivewindow or nil),
+		maxStreamReceiveWindow = (server.maxstreamreceivewindow and server.maxstreamreceivewindow or nil),
 		initConnReceiveWindow = (server.initconnreceivewindow and server.initconnreceivewindow or nil),
 		maxConnReceiveWindow = (server.maxconnreceivewindow and server.maxconnreceivewindow or nil),
 		maxIdleTimeout = (tonumber(server.maxidletimeout) and tonumber(server.maxidletimeout) .. "s" or nil),
@@ -482,14 +482,41 @@ local hysteria2 = {
 		disablePathMTUDiscovery = (server.disablepathmtudiscovery == "1") and true or false
 	} or nil,
 	auth = server.hy2_auth,
-	tls = server.tls_host and {
+	tls = (server.tls_host and server.tls_host ~= "") and {
 		sni = server.tls_host,
-		--alpn = server.tls_alpn or nil,
+		alpn = (server.type == "hysteria2") and (function()
+			local alpn = {}
+			if server.tls_alpn and server.tls_alpn ~= "" then
+				string.gsub(server.tls_alpn, '[^,]+', function(w)
+					table.insert(alpn, w)
+				end)
+			end
+			if #alpn > 0 then
+				return alpn
+			else
+				return nil
+			end
+		end)() or nil,
+		--sni = server.tls_host or (server.tls_host and server.tls_alpn) or nil,
 		insecure = (server.insecure == "1") and true or false,
-		pinSHA256 = (server.insecure == "1") and server.pinsha256 or nil
+		pinSHA256 = server.pinsha256 or nil
 	} or {
 		sni = server.server,
-		insecure = (server.insecure == "1") and true or false
+		alpn = (server.type == "hysteria2") and (function()
+			local alpn = {}
+			if server.tls_alpn and server.tls_alpn ~= "" then
+				string.gsub(server.tls_alpn, '[^,]+', function(w)
+					table.insert(alpn, w)
+				end)
+			end
+			if #alpn > 0 then
+				return alpn
+			else
+				return nil
+			end
+		end)() or nil,
+		insecure = (server.insecure == "1") and true or false,
+		pinSHA256 = server.pinsha256 or nil
 	},
 	fast_open = (server.fast_open == "1") and true or false,
 	lazy = (server.lazy_mode == "1") and true or false
@@ -588,8 +615,8 @@ local tuic = {
 			gc_lifetime = server.gc_lifetime and server.gc_lifetime .. "s" or nil,
 			alpn = (server.type == "tuic") and (function()
 				local alpn = {}
-				if server.tls_alpn and server.tls_alpn ~= "" then
-					string.gsub(server.tls_alpn, '[^,]+', function(w)
+				if server.tuic_alpn and server.tuic_alpn ~= "" then
+					string.gsub(server.tuic_alpn, '[^,]+', function(w)
 						table.insert(alpn, w)
 					end)
 				end
@@ -683,4 +710,3 @@ function config:handleIndex(index)
 end
 local f = config:new()
 f:handleIndex(server.type)
-

+ 39 - 18
luci-app-ssr-plus/root/usr/share/shadowsocksr/subscribe.lua

@@ -201,7 +201,7 @@ local function processData(szType, content)
 		result.alias = url.fragment and UrlDecode(url.fragment) or nil
 		result.type = hy2_type
 		result.server = url.host
-		result.server_port = url.port
+		result.server_port = url.port or 443
 		if params.protocol then
 			result.flag_transport = "1"
 			result.transport_protocol = params.protocol or "udp"
@@ -209,24 +209,33 @@ local function processData(szType, content)
 		result.hy2_auth = url.user
 		result.uplink_capacity = tonumber((params.upmbps or ""):match("^(%d+)")) or nil
 		result.downlink_capacity = tonumber((params.downmbps or ""):match("^(%d+)")) or nil
+		if params.mport then
+			result.flag_port_hopping = "1"
+			result.port_range = params.mport
+		end
 		if params.obfs and params.obfs ~= "none" then
 			result.flag_obfs = "1"
 			result.obfs_type = params.obfs
 			result.salamander = params["obfs-password"] or params["obfs_password"]
 		end
-		if params.sni then
+		if (params.sni and params.sni ~= "") or (params.alpn and params.alpn ~= "") then
 			result.tls = "1"
-			result.tls_host = params.sni
-		end
-		if params.insecure then
-			result.insecure = "1"
 			if params.sni then
-				result.pinsha256 = params.pinSHA256
+				result.tls_host = params.sni
+			end
+			if params.alpn then
+				local alpn = {}
+				for v in params.alpn:gmatch("[^,]+") do
+					table.insert(alpn, v)
+				end
+				result.tls_alpn = alpn
 			end
 		end
-		if params.mport then
-			result.flag_port_hopping = "1"
-			result.port_range = params.mport
+		if params.insecure == "1" then
+			result.insecure = params.insecure
+		end
+		if params.pinSHA256 then
+			result.pinsha256 = params.pinSHA256
 		end
 	elseif szType == 'ssr' then
 		local dat = split(content, "/%?")
@@ -330,15 +339,20 @@ local function processData(szType, content)
 		if info.net == 'quic' then
 			result.quic_guise = info.type
 			result.quic_key = info.key
-			result.quic_security = info.securty
+			result.quic_security = info.security
 		end
 		if info.security then
 			result.security = info.security
 		end
 		if info.tls == "tls" or info.tls == "1" then
 			result.tls = "1"
+			result.fingerprint = info.fp
 			if info.alpn and info.alpn ~= "" then
-				result.xhttp_alpn = info.alpn
+				local alpn = {}
+				for v in info.alpn:gmatch("[^,]+") do
+					table.insert(alpn, v)
+				end
+				result.tls_alpn = alpn
 			end
 			if info.sni and info.sni ~= "" then
 				result.tls_host = info.sni
@@ -347,9 +361,11 @@ local function processData(szType, content)
 			end
 			if info.ech and info.ech ~= "" then
 				result.enable_ech = "1"
-				result.ech_config = params.ech
+				result.ech_config = info.ech
+			end
+			if (info.allowInsecure or info.allowlnsecure) == "true" or (info.allowInsecure or info.allowlnsecure) == "1" then
+				result.insecure = "1"
 			end
-			result.insecure = allow_insecure
 		else
 			result.tls = "0"
 		end
@@ -615,7 +631,7 @@ local function processData(szType, content)
 			-- 处理参数
 			if params.alpn then
 				-- 处理 alpn 参数
-				result.xhttp_alpn = params.alpn
+				result.tls_alpn = params.alpn
 			end
 
 			if params.sni then
@@ -716,7 +732,7 @@ local function processData(szType, content)
 		result.server_port = url.port
 		result.vmess_id = url.user
 		result.vless_encryption = params.encryption or "none"
-		result.transport = params.type or "tcp"
+		result.transport = params.type or "raw"
 		if result.transport == "tcp" then
 			result.transport = "raw"
 		end
@@ -724,7 +740,13 @@ local function processData(szType, content)
 			result.transport = "xhttp"
 		end
 		result.tls = (params.security == "tls" or params.security == "xtls") and "1" or "0"
-		result.xhttp_alpn = params.alpn or ""
+		if params.alpn and params.alpn ~= "" then
+			local alpn = {}
+			for v in params.alpn:gmatch("[^,]+") do
+				table.insert(alpn, v)
+			end
+			result.tls_alpn = alpn
+		end
 		result.tls_host = params.sni
 		result.tls_flow = (params.security == "tls" or params.security == "reality") and params.flow or nil
 		result.fingerprint = params.fp
@@ -1129,4 +1151,3 @@ if subscribe_url and #subscribe_url > 0 then
 		end
 	end)
 end
-

部分文件因文件數量過多而無法顯示