我在我的 _Layout.cshtml
中定义了此部分
@RenderSection("Scripts", false)
我可以很容易地从一个视图中使用它:
@section Scripts {
@*Stuff comes here*@
}
我正在努力解决的是如何从局部视图中将一些内容注入到本节中。
假设这是我的视图页面:
@section Scripts {
<script>
//code comes here
</script>
}
<div>
poo bar poo
</div>
<div>
@Html.Partial("_myPartial")
</div>
我需要在 _myPartial
部分视图的 Scripts
部分中注入一些内容。
我怎样才能做到这一点?
部分在局部视图中不起作用,这是设计使然。您可以使用 some custom helpers 来实现类似的行为,但老实说,包含必要的脚本是视图的责任,而不是部分的责任。我建议使用主视图的 @scripts 部分来执行此操作,而不是让部分担心脚本。
这是一个非常受欢迎的问题,所以我将发布我的解决方案。
我遇到了同样的问题,虽然它并不理想,但我认为它实际上工作得很好,并且不会使部分依赖于视图。
我的场景是一个动作可以自己访问,但也可以嵌入到一个视图中——一个谷歌地图。
在我的 _layout
中,我有:
@RenderSection("body_scripts", false)
在我的 index
视图中,我有:
@Html.Partial("Clients")
@section body_scripts
{
@Html.Partial("Clients_Scripts")
}
在我的 clients
视图中,我有(所有地图和 assoc.html):
@section body_scripts
{
@Html.Partial("Clients_Scripts")
}
我的 Clients_Scripts
视图包含要呈现到页面上的 javascript。
这样,我的脚本就被隔离了,并且可以在需要的地方呈现到页面中,body_scripts
标记仅在 razor 视图引擎找到它的第一次出现时呈现。
这让我把所有东西都分开了——这是一个对我来说效果很好的解决方案,其他人可能会遇到问题,但它确实修补了“按设计”的漏洞。
从 this thread 中的解决方案中,我想出了以下可能过于复杂的解决方案,它可以让您延迟在 using 块中呈现任何 html(也包括脚本)。
用法
创建“部分”
典型场景:在局部视图中,无论局部视图在页面中重复多少次,都只包含一次块:@using (Html.Delayed(isOnlyOne: "some unique name for this section")) { } 在局部视图中,每次使用局部时都包含块:@using (Html.Delayed()) { show me multiple times, @Model.Whatever }局部视图,无论局部重复多少次,只包含一次块,但稍后按名称具体呈现它 when-i-call-you: @using (Html.Delayed("when-i-call-you", isOnlyOne: "不同的唯一名称")) { 按名称显示一次 @Model.First().Value }
渲染“部分”
(即在父视图中显示延迟部分)
@Html.RenderDelayed(); // writes unnamed sections (#1 and #2, excluding #3)
@Html.RenderDelayed("when-i-call-you", false); // writes the specified block, and ignore the `isOnlyOne` setting so we can dump it again
@Html.RenderDelayed("when-i-call-you"); // render the specified block by name
@Html.RenderDelayed("when-i-call-you"); // since it was "popped" in the last call, won't render anything due to `isOnlyOne` provided in `Html.Delayed`
代码
public static class HtmlRenderExtensions {
/// <summary>
/// Delegate script/resource/etc injection until the end of the page
/// <para>@via https://stackoverflow.com/a/14127332/1037948 and http://jadnb.wordpress.com/2011/02/16/rendering-scripts-from-partial-views-at-the-end-in-mvc/ </para>
/// </summary>
private class DelayedInjectionBlock : IDisposable {
/// <summary>
/// Unique internal storage key
/// </summary>
private const string CACHE_KEY = "DCCF8C78-2E36-4567-B0CF-FE052ACCE309"; // "DelayedInjectionBlocks";
/// <summary>
/// Internal storage identifier for remembering unique/isOnlyOne items
/// </summary>
private const string UNIQUE_IDENTIFIER_KEY = CACHE_KEY;
/// <summary>
/// What to use as internal storage identifier if no identifier provided (since we can't use null as key)
/// </summary>
private const string EMPTY_IDENTIFIER = "";
/// <summary>
/// Retrieve a context-aware list of cached output delegates from the given helper; uses the helper's context rather than singleton HttpContext.Current.Items
/// </summary>
/// <param name="helper">the helper from which we use the context</param>
/// <param name="identifier">optional unique sub-identifier for a given injection block</param>
/// <returns>list of delayed-execution callbacks to render internal content</returns>
public static Queue<string> GetQueue(HtmlHelper helper, string identifier = null) {
return _GetOrSet(helper, new Queue<string>(), identifier ?? EMPTY_IDENTIFIER);
}
/// <summary>
/// Retrieve a context-aware list of cached output delegates from the given helper; uses the helper's context rather than singleton HttpContext.Current.Items
/// </summary>
/// <param name="helper">the helper from which we use the context</param>
/// <param name="defaultValue">the default value to return if the cached item isn't found or isn't the expected type; can also be used to set with an arbitrary value</param>
/// <param name="identifier">optional unique sub-identifier for a given injection block</param>
/// <returns>list of delayed-execution callbacks to render internal content</returns>
private static T _GetOrSet<T>(HtmlHelper helper, T defaultValue, string identifier = EMPTY_IDENTIFIER) where T : class {
var storage = GetStorage(helper);
// return the stored item, or set it if it does not exist
return (T) (storage.ContainsKey(identifier) ? storage[identifier] : (storage[identifier] = defaultValue));
}
/// <summary>
/// Get the storage, but if it doesn't exist or isn't the expected type, then create a new "bucket"
/// </summary>
/// <param name="helper"></param>
/// <returns></returns>
public static Dictionary<string, object> GetStorage(HtmlHelper helper) {
var storage = helper.ViewContext.HttpContext.Items[CACHE_KEY] as Dictionary<string, object>;
if (storage == null) helper.ViewContext.HttpContext.Items[CACHE_KEY] = (storage = new Dictionary<string, object>());
return storage;
}
private readonly HtmlHelper helper;
private readonly string identifier;
private readonly string isOnlyOne;
/// <summary>
/// Create a new using block from the given helper (used for trapping appropriate context)
/// </summary>
/// <param name="helper">the helper from which we use the context</param>
/// <param name="identifier">optional unique identifier to specify one or many injection blocks</param>
/// <param name="isOnlyOne">extra identifier used to ensure that this item is only added once; if provided, content should only appear once in the page (i.e. only the first block called for this identifier is used)</param>
public DelayedInjectionBlock(HtmlHelper helper, string identifier = null, string isOnlyOne = null) {
this.helper = helper;
// start a new writing context
((WebViewPage)this.helper.ViewDataContainer).OutputStack.Push(new StringWriter());
this.identifier = identifier ?? EMPTY_IDENTIFIER;
this.isOnlyOne = isOnlyOne;
}
/// <summary>
/// Append the internal content to the context's cached list of output delegates
/// </summary>
public void Dispose() {
// render the internal content of the injection block helper
// make sure to pop from the stack rather than just render from the Writer
// so it will remove it from regular rendering
var content = ((WebViewPage)this.helper.ViewDataContainer).OutputStack;
var renderedContent = content.Count == 0 ? string.Empty : content.Pop().ToString();
// if we only want one, remove the existing
var queue = GetQueue(this.helper, this.identifier);
// get the index of the existing item from the alternate storage
var existingIdentifiers = _GetOrSet(this.helper, new Dictionary<string, int>(), UNIQUE_IDENTIFIER_KEY);
// only save the result if this isn't meant to be unique, or
// if it's supposed to be unique and we haven't encountered this identifier before
if( null == this.isOnlyOne || !existingIdentifiers.ContainsKey(this.isOnlyOne) ) {
// remove the new writing context we created for this block
// and save the output to the queue for later
queue.Enqueue(renderedContent);
// only remember this if supposed to
if(null != this.isOnlyOne) existingIdentifiers[this.isOnlyOne] = queue.Count; // save the index, so we could remove it directly (if we want to use the last instance of the block rather than the first)
}
}
}
/// <summary>
/// <para>Start a delayed-execution block of output -- this will be rendered/printed on the next call to <see cref="RenderDelayed"/>.</para>
/// <para>
/// <example>
/// Print once in "default block" (usually rendered at end via <code>@Html.RenderDelayed()</code>). Code:
/// <code>
/// @using (Html.Delayed()) {
/// <b>show at later</b>
/// <span>@Model.Name</span>
/// etc
/// }
/// </code>
/// </example>
/// </para>
/// <para>
/// <example>
/// Print once (i.e. if within a looped partial), using identified block via <code>@Html.RenderDelayed("one-time")</code>. Code:
/// <code>
/// @using (Html.Delayed("one-time", isOnlyOne: "one-time")) {
/// <b>show me once</b>
/// <span>@Model.First().Value</span>
/// }
/// </code>
/// </example>
/// </para>
/// </summary>
/// <param name="helper">the helper from which we use the context</param>
/// <param name="injectionBlockId">optional unique identifier to specify one or many injection blocks</param>
/// <param name="isOnlyOne">extra identifier used to ensure that this item is only added once; if provided, content should only appear once in the page (i.e. only the first block called for this identifier is used)</param>
/// <returns>using block to wrap delayed output</returns>
public static IDisposable Delayed(this HtmlHelper helper, string injectionBlockId = null, string isOnlyOne = null) {
return new DelayedInjectionBlock(helper, injectionBlockId, isOnlyOne);
}
/// <summary>
/// Render all queued output blocks injected via <see cref="Delayed"/>.
/// <para>
/// <example>
/// Print all delayed blocks using default identifier (i.e. not provided)
/// <code>
/// @using (Html.Delayed()) {
/// <b>show me later</b>
/// <span>@Model.Name</span>
/// etc
/// }
/// </code>
/// -- then later --
/// <code>
/// @using (Html.Delayed()) {
/// <b>more for later</b>
/// etc
/// }
/// </code>
/// -- then later --
/// <code>
/// @Html.RenderDelayed() // will print both delayed blocks
/// </code>
/// </example>
/// </para>
/// <para>
/// <example>
/// Allow multiple repetitions of rendered blocks, using same <code>@Html.Delayed()...</code> as before. Code:
/// <code>
/// @Html.RenderDelayed(removeAfterRendering: false); /* will print */
/// @Html.RenderDelayed() /* will print again because not removed before */
/// </code>
/// </example>
/// </para>
/// </summary>
/// <param name="helper">the helper from which we use the context</param>
/// <param name="injectionBlockId">optional unique identifier to specify one or many injection blocks</param>
/// <param name="removeAfterRendering">only render this once</param>
/// <returns>rendered output content</returns>
public static MvcHtmlString RenderDelayed(this HtmlHelper helper, string injectionBlockId = null, bool removeAfterRendering = true) {
var stack = DelayedInjectionBlock.GetQueue(helper, injectionBlockId);
if( removeAfterRendering ) {
var sb = new StringBuilder(
#if DEBUG
string.Format("<!-- delayed-block: {0} -->", injectionBlockId)
#endif
);
// .count faster than .any
while (stack.Count > 0) {
sb.AppendLine(stack.Dequeue());
}
return MvcHtmlString.Create(sb.ToString());
}
return MvcHtmlString.Create(
#if DEBUG
string.Format("<!-- delayed-block: {0} -->", injectionBlockId) +
#endif
string.Join(Environment.NewLine, stack));
}
}
isOnlyOne
中的唯一键时呈现,但只有当您想按名称在特定位置呈现它时,您才提供标识符,否则将在 Html.RenderDelayed()
处转储。
如果您确实有从 partial
运行某些 js
的合法需要,那么您可以这样做,jQuery
是必需的:
<script type="text/javascript">
function scriptToExecute()
{
//The script you want to execute when page is ready.
}
function runWhenReady()
{
if (window.$)
scriptToExecute();
else
setTimeout(runWhenReady, 100);
}
runWhenReady();
</script>
遵循 unobtrusive 原则,“_myPartial”不需要将内容直接注入脚本部分。您可以将这些部分视图脚本添加到单独的 .js
文件中,并从父视图将它们引用到 @scripts 部分。
OP 的目标是他想将内联脚本定义到他的部分视图中,我假设该脚本仅特定于该部分视图,并将该块包含在他的脚本部分中。
我知道他想让那个局部视图是自包含的。这个想法类似于使用 Angular 时的组件。
我的方法是将脚本原样保留在部分视图中。现在的问题是当调用部分视图时,它可能会在所有其他脚本之前执行其中的脚本(通常添加到布局页面的底部)。在这种情况下,您只需让部分视图脚本等待其他脚本。有几种方法可以做到这一点。我之前使用过的最简单的方法是在 body
上使用一个事件。
在我的布局上,我会在底部有这样的东西:
// global scripts
<script src="js/jquery.min.js"></script>
// view scripts
@RenderSection("scripts", false)
// then finally trigger partial view scripts
<script>
(function(){
document.querySelector('body').dispatchEvent(new Event('scriptsLoaded'));
})();
</script>
然后在我的局部视图(底部):
<script>
(function(){
document.querySelector('body').addEventListener('scriptsLoaded', function() {
// .. do your thing here
});
})();
</script>
另一种解决方案是使用堆栈来推送所有脚本,并在最后调用每个脚本。如前所述,其他解决方案是 RequireJS/AMD 模式,它也非常有效。
我们对 Web 的看法存在一个根本性缺陷,尤其是在使用 MVC 时。缺陷在于 JavaScript 在某种程度上是视图的责任。视图是视图,JavaScript(行为或其他)是 JavaScript。在 Silverlight 和 WPF 的 MVVM 模式中,我们面临“视图优先”或“模型优先”。在 MVC 中,我们应该始终尝试从模型的角度进行推理,而 JavaScript 在许多方面都是该模型的一部分。
我建议使用 AMD 模式(我自己喜欢 RequireJS)。将您的 JavaScript 分离到模块中,定义您的功能并从 JavaScript 挂钩到您的 html,而不是依赖视图来加载 JavaScript。这将清理您的代码,分离您的顾虑并让生活更轻松。
window
对象上的自定义“命名空间”,并在任何部分之前包含库脚本。
您不需要在局部视图中使用部分。
包括在您的局部视图中。它在 jQuery 加载后执行该函数。您可以更改代码的 de 条件子句。
<script type="text/javascript">
var time = setInterval(function () {
if (window.jQuery != undefined) {
window.clearInterval(time);
//Begin
$(document).ready(function () {
//....
});
//End
};
}, 10); </script>
胡里奥·斯派德
这对我有用,允许我将 javascript 和 html 放在同一文件中以获取部分视图。有助于思考过程以在同一局部视图文件中查看 html 和相关部分。
在使用名为“_MyPartialView.cshtml”的局部视图的视图中
<div>
@Html.Partial("_MyPartialView",< model for partial view>,
new ViewDataDictionary { { "Region", "HTMLSection" } } })
</div>
@section scripts{
@Html.Partial("_MyPartialView",<model for partial view>,
new ViewDataDictionary { { "Region", "ScriptSection" } })
}
在部分视图文件中
@model SomeType
@{
var region = ViewData["Region"] as string;
}
@if (region == "HTMLSection")
{
}
@if (region == "ScriptSection")
{
<script type="text/javascript">
</script">
}
我能想到的第一个解决方案是使用 ViewBag 来存储必须呈现的值。
Onestly我从来没有尝试过这是否可以从部分角度来看,但它应该是imo。
ViewBag.RenderScripts = new List<string>();
,然后称为 @Html.Partial("_CreateUpdatePartial",Model,ViewData)
,然后放入 @section Scripts {@foreach (string script in ViewBag.RenderScripts) Scripts.Render(script); }}
。在部分视图中,我放入了 @{ViewBag.RenderScripts = ViewBag.RenderScripts ?? new List<string>();ViewBag.RenderScripts.Add("~/bundles/jquery");}
。
您可以使用这些扩展方法:(另存为 PartialWithScript.cs)
namespace System.Web.Mvc.Html
{
public static class PartialWithScript
{
public static void RenderPartialWithScript(this HtmlHelper htmlHelper, string partialViewName)
{
if (htmlHelper.ViewBag.ScriptPartials == null)
{
htmlHelper.ViewBag.ScriptPartials = new List<string>();
}
if (!htmlHelper.ViewBag.ScriptPartials.Contains(partialViewName))
{
htmlHelper.ViewBag.ScriptPartials.Add(partialViewName);
}
htmlHelper.ViewBag.ScriptPartialHtml = true;
htmlHelper.RenderPartial(partialViewName);
}
public static void RenderPartialScripts(this HtmlHelper htmlHelper)
{
if (htmlHelper.ViewBag.ScriptPartials != null)
{
htmlHelper.ViewBag.ScriptPartialHtml = false;
foreach (string partial in htmlHelper.ViewBag.ScriptPartials)
{
htmlHelper.RenderPartial(partial);
}
}
}
}
}
像这样使用:
部分示例: (_MyPartial.cshtml) 将 html 放在 if 中,将 js 放在 else 中。
@if (ViewBag.ScriptPartialHtml ?? true)
<p>I has htmls</p>
}
else {
<script type="text/javascript">
alert('I has javascripts');
</script>
}
在您的 _Layout.cshtml 中,或者您希望渲染部分脚本的任何地方,放置以下内容(一次):它将仅在此位置呈现当前页面上所有部分的 javascript。
@{ Html.RenderPartialScripts(); }
然后要使用您的部分,只需执行以下操作:它将仅在此位置呈现 html。
@{Html.RenderPartialWithScript("~/Views/MyController/_MyPartial.cshtml");}
有一种方法可以在局部视图中插入部分,尽管它并不漂亮。您需要从父视图访问两个变量。由于部分视图的部分目的是创建该部分,因此需要这些变量是有意义的。
下面是在局部视图中插入一个部分的样子:
@model KeyValuePair<WebPageBase, HtmlHelper>
@{
Model.Key.DefineSection("SectionNameGoesHere", () =>
{
Model.Value.ViewContext.Writer.Write("Test");
});
}
并在插入部分视图的页面中......
@Html.Partial(new KeyValuePair<WebPageBase, HtmlHelper>(this, Html))
您还可以使用此技术在任何类中以编程方式定义节的内容。
享受!
冥王星的想法以更好的方式:
CustomWebViewPage.cs:
public abstract class CustomWebViewPage<TModel> : WebViewPage<TModel> {
public IHtmlString PartialWithScripts(string partialViewName, object model) {
return Html.Partial(partialViewName: partialViewName, model: model, viewData: new ViewDataDictionary { ["view"] = this, ["html"] = Html });
}
public void RenderScriptsInBasePage(HelperResult scripts) {
var parentView = ViewBag.view as WebPageBase;
var parentHtml = ViewBag.html as HtmlHelper;
parentView.DefineSection("scripts", () => {
parentHtml.ViewContext.Writer.Write(scripts.ToHtmlString());
});
}
}
视图\web.config:
<pages pageBaseType="Web.Helpers.CustomWebViewPage">
看法:
@PartialWithScripts("_BackendSearchForm")
部分(_BackendSearchForm.cshtml):
@{ RenderScriptsInBasePage(scripts()); }
@helper scripts() {
<script>
//code will be rendered in a "scripts" section of the Layout page
</script>
}
布局页面:
@RenderSection("scripts", required: false)
使用 Mvc Core,您可以创建一个整洁的 TagHelper scripts
,如下所示。这可以很容易地变成一个 section
标记,您也可以在其中给它一个名称(或者名称取自派生类型)。请注意,需要为 IHttpContextAccessor
设置依赖注入。
添加脚本时(例如在部分中)
<scripts>
<script type="text/javascript">
//anything here
</script>
</scripts>
输出脚本时(例如在布局文件中)
<scripts render="true"></scripts>
代码
public class ScriptsTagHelper : TagHelper
{
private static readonly object ITEMSKEY = new Object();
private IDictionary<object, object> _items => _httpContextAccessor?.HttpContext?.Items;
private IHttpContextAccessor _httpContextAccessor;
public ScriptsTagHelper(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var attribute = (TagHelperAttribute)null;
context.AllAttributes.TryGetAttribute("render",out attribute);
var render = false;
if(attribute != null)
{
render = Convert.ToBoolean(attribute.Value.ToString());
}
if (render)
{
if (_items.ContainsKey(ITEMSKEY))
{
var scripts = _items[ITEMSKEY] as List<HtmlString>;
var content = String.Concat(scripts);
output.Content.SetHtmlContent(content);
}
}
else
{
List<HtmlString> list = null;
if (!_items.ContainsKey(ITEMSKEY))
{
list = new List<HtmlString>();
_items[ITEMSKEY] = list;
}
list = _items[ITEMSKEY] as List<HtmlString>;
var content = await output.GetChildContentAsync();
list.Add(new HtmlString(content.GetContent()));
}
}
}
我今天遇到了这个问题。我将添加一个使用 <script defer>
的解决方法,因为我没有看到其他答案提到它。
//on a JS file somewhere (i.e partial-view-caller.js)
(() => <your partial view script>)();
//in your Partial View
<script src="~/partial-view-caller.js" defer></script>
//you can actually just straight call your partial view script living in an external file - I just prefer having an initialization method :)
上面的代码是我对这个问题所做的quick post的摘录。
我解决了这个完全不同的路线(因为我很着急,不想实现一个新的 HtmlHelper):
我将部分视图包裹在一个大的 if-else 语句中:
@if ((bool)ViewData["ShouldRenderScripts"] == true){
// Scripts
}else{
// Html
}
然后,我使用自定义 ViewData 调用了 Partial 两次:
@Html.Partial("MyPartialView", Model,
new ViewDataDictionary { { "ShouldRenderScripts", false } })
@section scripts{
@Html.Partial("MyPartialView", Model,
new ViewDataDictionary { { "ShouldRenderScripts", true } })
}
@Html.Partial("MyPartialViewScripts")
我有一个类似的问题,我有一个母版页如下:
@section Scripts {
<script>
$(document).ready(function () {
...
});
</script>
}
...
@Html.Partial("_Charts", Model)
但部分视图依赖于脚本部分中的一些 JavaScript。我通过将部分视图编码为 JSON,将其加载到 JavaScript 变量中,然后使用它来填充 div 来解决它,所以:
@{
var partial = Html.Raw(Json.Encode(new { html = Html.Partial("_Charts", Model).ToString() }));
}
@section Scripts {
<script>
$(document).ready(function () {
...
var partial = @partial;
$('#partial').html(partial.html);
});
</script>
}
<div id="partial"></div>
您可以选择使用您的 Folder/index.cshtml 作为母版页,然后添加部分脚本。然后,在您的布局中,您有:
@RenderSection("scripts", required: false)
和你的 index.cshtml:
@section scripts{
@Scripts.Render("~/Scripts/file.js")
}
它会处理你所有的部分视图。它对我有用
我的解决方案是从布局页面加载脚本。然后在 javacript 中,检查 parial 视图中是否存在元素之一。如果该元素存在,则 javascript 知道该部分已包含在内。
$(document).ready(function () {
var joinButton = $("#join");
if (joinButton.length != 0) {
// the partial is present
// execute the relevant code
}
});
好吧,我猜其他海报为您提供了一种在您的部分中直接包含@section 的方法(通过使用第 3 方 html 助手)。
但是,我认为,如果您的脚本与您的部分紧密耦合,只需将您的 javascript 直接放在您的部分内的内联 <script>
标记中并完成它(请注意脚本重复如果您打算在单个视图中多次使用局部);
假设您有一个名为 _contact.cshtml 的局部视图,您的联系人可以是法律(姓名)或物理主体(名字、姓氏)。您的视图应该注意渲染的内容以及可以使用 javascript 实现的内容。因此可能需要延迟渲染和 JS 内视图。
我认为,如何省略它的唯一方法是,当我们创建一种不显眼的方式来处理此类 UI 问题时。
另请注意,MVC 6 将有一个所谓的视图组件,甚至 MVC 期货也有一些类似的东西,Telerik 也支持这样的东西......
我刚刚在我的局部视图中添加了这段代码并解决了这个问题,虽然不是很干净,但它可以工作。您必须确保您正在渲染的对象的 Id。
<script>
$(document).ready(function () {
$("#Profile_ProfileID").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
$("#TitleID_FK").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
$("#CityID_FK").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
$("#GenderID_FK").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
$("#PackageID_FK").selectmenu({ icons: { button: 'ui-icon-circle-arrow-s' } });
});
</script>
我有类似的问题用这个解决了它:
@section ***{
@RenderSection("****", required: false)
}
我猜那是一种很好的注入方式。