• NOP源码分析 十 --页面分析,开始都做了什么。


    找到最顶层的Layout是:Layout = "~/Views/Shared/_Root.Head.cshtml";

    开始代码:

    @using Nop.Core.Domain.Common;
    @using Nop.Core.Infrastructure;
    @{
        var displayMiniProfiler = EngineContext.Current.Resolve<Nop.Core.Domain.StoreInformationSettings>().DisplayMiniProfilerInPublicStore;
    
        //resources
        Html.AppendCssFileParts("~/Content/jquery-ui-themes/smoothness/jquery-ui-1.10.3.custom.min.css");
    
        Html.AppendScriptParts("~/Scripts/public.ajaxcart.js");
        Html.AppendScriptParts("~/Scripts/public.common.js");
        Html.AppendScriptParts("~/Scripts/jquery-migrate-1.2.1.min.js");
        Html.AppendScriptParts("~/Scripts/jquery-ui-1.10.3.custom.min.js");
        Html.AppendScriptParts("~/Scripts/jquery.validate.unobtrusive.min.js");
        Html.AppendScriptParts("~/Scripts/jquery.validate.min.js");
        Html.AppendScriptParts("~/Scripts/jquery-1.10.2.min.js");
    
        //X-UA-Compatible tag
        var commonSettings = EngineContext.Current.Resolve<CommonSettings>();
        if (commonSettings.RenderXuaCompatible)
        {
            Html.AppendHeadCustomParts(string.Format("<meta http-equiv="X-UA-Compatible" content="{0}"/>", commonSettings.XuaCompatibleValue));
        }
    }

    第一句从Setting表获得DisplayMiniProfilerInPublicStore的值查询是false.

    第二句是HTMLHelper的扩展方法。整个类的代码:

    namespace Nop.Web.Framework.UI
    {
        public static class LayoutExtensions
        {
    
            /// <summary>
            /// Add title element to the <![CDATA[<head>]]>
            /// </summary>
            /// <param name="html">HTML helper</param>
            /// <param name="part">Title part</param>
            public static void AddTitleParts(this HtmlHelper html, string part)
            {
                var pageHeadBuilder  = EngineContext.Current.Resolve<IPageHeadBuilder>();
                pageHeadBuilder.AddTitleParts(part);
            }
            /// <summary>
            /// Append title element to the <![CDATA[<head>]]>
            /// </summary>
            /// <param name="html">HTML helper</param>
            /// <param name="part">Title part</param>
            public static void AppendTitleParts(this HtmlHelper html, string part)
            {
                var pageHeadBuilder  = EngineContext.Current.Resolve<IPageHeadBuilder>();
                pageHeadBuilder.AppendTitleParts(part);
            }
            /// <summary>
            /// Generate all title parts
            /// </summary>
            /// <param name="html">HTML helper</param>
            /// <param name="addDefaultTitle">A value indicating whether to insert a default title</param>
            /// <param name="part">Title part</param>
            /// <returns>Generated string</returns>
            public static MvcHtmlString NopTitle(this HtmlHelper html, bool addDefaultTitle, string part = "")
            {
                var pageHeadBuilder = EngineContext.Current.Resolve<IPageHeadBuilder>();
                html.AppendTitleParts(part);
                return MvcHtmlString.Create(html.Encode(pageHeadBuilder.GenerateTitle(addDefaultTitle)));
            }
    
    
            /// <summary>
            /// Add meta description element to the <![CDATA[<head>]]>
            /// </summary>
            /// <param name="html">HTML helper</param>
            /// <param name="part">Meta description part</param>
            public static void AddMetaDescriptionParts(this HtmlHelper html, string part)
            {
                var pageHeadBuilder = EngineContext.Current.Resolve<IPageHeadBuilder>();
                pageHeadBuilder.AddMetaDescriptionParts(part);
            }
            /// <summary>
            /// Append meta description element to the <![CDATA[<head>]]>
            /// </summary>
            /// <param name="html">HTML helper</param>
            /// <param name="part">Meta description part</param>
            public static void AppendMetaDescriptionParts(this HtmlHelper html, string part)
            {
                var pageHeadBuilder = EngineContext.Current.Resolve<IPageHeadBuilder>();
                pageHeadBuilder.AppendMetaDescriptionParts(part);
            }
            /// <summary>
            /// Generate all description parts
            /// </summary>
            /// <param name="html">HTML helper</param>
            /// <param name="part">Meta description part</param>
            /// <returns>Generated string</returns>
            public static MvcHtmlString NopMetaDescription(this HtmlHelper html, string part = "")
            {
                var pageHeadBuilder = EngineContext.Current.Resolve<IPageHeadBuilder>();
                html.AppendMetaDescriptionParts(part);
                return MvcHtmlString.Create(html.Encode(pageHeadBuilder.GenerateMetaDescription()));
            }
    
    
            /// <summary>
            /// Add meta keyword element to the <![CDATA[<head>]]>
            /// </summary>
            /// <param name="html">HTML helper</param>
            /// <param name="part">Meta keyword part</param>
            public static void AddMetaKeywordParts(this HtmlHelper html, string part)
            {
                var pageHeadBuilder = EngineContext.Current.Resolve<IPageHeadBuilder>();
                pageHeadBuilder.AddMetaKeywordParts(part);
            }
            /// <summary>
            /// Append meta keyword element to the <![CDATA[<head>]]>
            /// </summary>
            /// <param name="html">HTML helper</param>
            /// <param name="part">Meta keyword part</param>
            public static void AppendMetaKeywordParts(this HtmlHelper html, string part)
            {
                var pageHeadBuilder = EngineContext.Current.Resolve<IPageHeadBuilder>();
                pageHeadBuilder.AppendMetaKeywordParts(part);
            }
            /// <summary>
            /// Generate all keyword parts
            /// </summary>
            /// <param name="html">HTML helper</param>
            /// <param name="part">Meta keyword part</param>
            /// <returns>Generated string</returns>
            public static MvcHtmlString NopMetaKeywords(this HtmlHelper html, string part = "")
            {
                var pageHeadBuilder = EngineContext.Current.Resolve<IPageHeadBuilder>();
                html.AppendMetaKeywordParts(part);
                return MvcHtmlString.Create(html.Encode(pageHeadBuilder.GenerateMetaKeywords()));
            }
    
    
            /// <summary>
            /// Add script element
            /// </summary>
            /// <param name="html">HTML helper</param>
            /// <param name="part">Script part</param>
            /// <param name="excludeFromBundle">A value indicating whether to exclude this script from bundling</param>
            public static void AddScriptParts(this HtmlHelper html, string part, bool excludeFromBundle = false)
            {
                AddScriptParts(html, ResourceLocation.Head, part, excludeFromBundle);
            }
            /// <summary>
            /// Add script element
            /// </summary>
            /// <param name="html">HTML helper</param>
            /// <param name="location">A location of the script element</param>
            /// <param name="part">Script part</param>
            /// <param name="excludeFromBundle">A value indicating whether to exclude this script from bundling</param>
            public static void AddScriptParts(this HtmlHelper html, ResourceLocation location, string part, bool excludeFromBundle = false)
            {
                var pageHeadBuilder = EngineContext.Current.Resolve<IPageHeadBuilder>();
                pageHeadBuilder.AddScriptParts(location, part, excludeFromBundle);
            }
            /// <summary>
            /// Append script element
            /// </summary>
            /// <param name="html">HTML helper</param>
            /// <param name="part">Script part</param>
            /// <param name="excludeFromBundle">A value indicating whether to exclude this script from bundling</param>
            public static void AppendScriptParts(this HtmlHelper html, string part, bool excludeFromBundle = false)
            {
                AppendScriptParts(html, ResourceLocation.Head, part, excludeFromBundle);
            }
            /// <summary>
            /// Append script element
            /// </summary>
            /// <param name="html">HTML helper</param>
            /// <param name="location">A location of the script element</param>
            /// <param name="part">Script part</param>
            /// <param name="excludeFromBundle">A value indicating whether to exclude this script from bundling</param>
            public static void AppendScriptParts(this HtmlHelper html, ResourceLocation location, string part, bool excludeFromBundle = false)
            {
                var pageHeadBuilder = EngineContext.Current.Resolve<IPageHeadBuilder>();
                pageHeadBuilder.AppendScriptParts(location, part, excludeFromBundle);
            }
            /// <summary>
            /// Generate all script parts
            /// </summary>
            /// <param name="html">HTML helper</param>
            /// <param name="urlHelper">URL Helper</param>
            /// <param name="location">A location of the script element</param>
            /// <param name="bundleFiles">A value indicating whether to bundle script elements</param>
            /// <returns>Generated string</returns>
            public static MvcHtmlString NopScripts(this HtmlHelper html, UrlHelper urlHelper, 
                ResourceLocation location, bool? bundleFiles = null)
            {
                var pageHeadBuilder = EngineContext.Current.Resolve<IPageHeadBuilder>();
                return MvcHtmlString.Create(pageHeadBuilder.GenerateScripts(urlHelper, location, bundleFiles));
            }
    
    
    
            /// <summary>
            /// Add CSS element
            /// </summary>
            /// <param name="html">HTML helper</param>
            /// <param name="part">CSS part</param>
            public static void AddCssFileParts(this HtmlHelper html, string part)
            {
                AddCssFileParts(html, ResourceLocation.Head, part);
            }
            /// <summary>
            /// Add CSS element
            /// </summary>
            /// <param name="html">HTML helper</param>
            /// <param name="location">A location of the script element</param>
            /// <param name="part">CSS part</param>
            public static void AddCssFileParts(this HtmlHelper html, ResourceLocation location, string part)
            {
                var pageHeadBuilder = EngineContext.Current.Resolve<IPageHeadBuilder>();
                pageHeadBuilder.AddCssFileParts(location, part);
            }
            /// <summary>
            /// Append CSS element
            /// </summary>
            /// <param name="html">HTML helper</param>
            /// <param name="part">CSS part</param>
            public static void AppendCssFileParts(this HtmlHelper html, string part)
            {
                AppendCssFileParts(html, ResourceLocation.Head, part);
            }
            /// <summary>
            /// Append CSS element
            /// </summary>
            /// <param name="html">HTML helper</param>
            /// <param name="location">A location of the script element</param>
            /// <param name="part">CSS part</param>
            public static void AppendCssFileParts(this HtmlHelper html, ResourceLocation location, string part)
            {
                var pageHeadBuilder = EngineContext.Current.Resolve<IPageHeadBuilder>();
                pageHeadBuilder.AppendCssFileParts(location, part);
            }
            /// <summary>
            /// Generate all CSS parts
            /// </summary>
            /// <param name="html">HTML helper</param>
            /// <param name="urlHelper">URL Helper</param>
            /// <param name="location">A location of the script element</param>
            /// <param name="bundleFiles">A value indicating whether to bundle script elements</param>
            /// <returns>Generated string</returns>
            public static MvcHtmlString NopCssFiles(this HtmlHelper html, UrlHelper urlHelper,
                ResourceLocation location, bool? bundleFiles = null)
            {
                var pageHeadBuilder = EngineContext.Current.Resolve<IPageHeadBuilder>();
                return MvcHtmlString.Create(pageHeadBuilder.GenerateCssFiles(urlHelper, location, bundleFiles));
            }
    
    
            /// <summary>
            /// Add canonical URL element to the <![CDATA[<head>]]>
            /// </summary>
            /// <param name="html">HTML helper</param>
            /// <param name="part">Canonical URL part</param>
            public static void AddCanonicalUrlParts(this HtmlHelper html, string part)
            {
                var pageHeadBuilder = EngineContext.Current.Resolve<IPageHeadBuilder>();
                pageHeadBuilder.AddCanonicalUrlParts(part);
            }
            /// <summary>
            /// Append canonical URL element to the <![CDATA[<head>]]>
            /// </summary>
            /// <param name="html">HTML helper</param>
            /// <param name="part">Canonical URL part</param>
            public static void AppendCanonicalUrlParts(this HtmlHelper html, string part)
            {
                var pageHeadBuilder = EngineContext.Current.Resolve<IPageHeadBuilder>();
                pageHeadBuilder.AppendCanonicalUrlParts(part);
            }
            /// <summary>
            /// Generate all canonical URL parts
            /// </summary>
            /// <param name="html">HTML helper</param>
            /// <param name="part">Canonical URL part</param>
            /// <returns>Generated string</returns>
            public static MvcHtmlString NopCanonicalUrls(this HtmlHelper html, string part = "")
            {
                var pageHeadBuilder = EngineContext.Current.Resolve<IPageHeadBuilder>();
                html.AppendCanonicalUrlParts(part);
                return MvcHtmlString.Create(pageHeadBuilder.GenerateCanonicalUrls());
            }
    
            /// <summary>
            /// Add any custom element to the <![CDATA[<head>]]>
            /// </summary>
            /// <param name="html">HTML helper</param>
            /// <param name="part">The entire element. For example, <![CDATA[<meta name="msvalidate.01" content="123121231231313123123" />]]></param>
            public static void AddHeadCustomParts(this HtmlHelper html, string part)
            {
                var pageHeadBuilder = EngineContext.Current.Resolve<IPageHeadBuilder>();
                pageHeadBuilder.AddHeadCustomParts(part);
            }
            /// <summary>
            /// Append any custom element to the <![CDATA[<head>]]>
            /// </summary>
            /// <param name="html">HTML helper</param>
            /// <param name="part">The entire element. For example, <![CDATA[<meta name="msvalidate.01" content="123121231231313123123" />]]></param>
            public static void AppendHeadCustomParts(this HtmlHelper html, string part)
            {
                var pageHeadBuilder = EngineContext.Current.Resolve<IPageHeadBuilder>();
                pageHeadBuilder.AppendHeadCustomParts(part);
            }
            /// <summary>
            /// Generate all custom elements
            /// </summary>
            /// <param name="html">HTML helper</param>
            /// <returns>Generated string</returns>
            public static MvcHtmlString NopHeadCustom(this HtmlHelper html)
            {
                var pageHeadBuilder = EngineContext.Current.Resolve<IPageHeadBuilder>();
                return MvcHtmlString.Create(pageHeadBuilder.GenerateHeadCustom());
            }
        }
    }

    跟踪下这个方法:

    public static void AppendCssFileParts(this HtmlHelper html, string part)
            {
                AppendCssFileParts(html, ResourceLocation.Head, part);
            }

    ---》

    /// <summary>
            /// Append CSS element
            /// </summary>
            /// <param name="html">HTML helper</param>
            /// <param name="location">A location of the script element</param>
            /// <param name="part">CSS part</param>
            public static void AppendCssFileParts(this HtmlHelper html, ResourceLocation location, string part)
            {
                var pageHeadBuilder = EngineContext.Current.Resolve<IPageHeadBuilder>();
                pageHeadBuilder.AppendCssFileParts(location, part);
            }

    他是调用IPageHeadBuilder的实现类PageHeadBuilder实现的AppendCssFileParts方法:

    public virtual void AppendCssFileParts(ResourceLocation location, string part)
            {
                if (!_cssParts.ContainsKey(location))
                    _cssParts.Add(location, new List<string>());
    
                if (string.IsNullOrEmpty(part))
                    return;
                
                _cssParts[location].Insert(0, part);
            }

    cssParts是一个字典键值对,如果不包含这个location(是个枚举,传入的值是 ResourceLocation.head),则添加,值是一个string的List集合。

    最后调用_cssParts[location].Add(part);这句,将part添加到LIST里。

    下一句:

    Html.AppendScriptParts("~/Scripts/public.ajaxcart.js");

    public virtual void AppendScriptParts(ResourceLocation location, string part, bool excludeFromBundle)
            {
                if (!_scriptParts.ContainsKey(location))
                    _scriptParts.Add(location, new List<ScriptReferenceMeta>());
    
                if (string.IsNullOrEmpty(part))
                    return;
    
                _scriptParts[location].Insert(0, new ScriptReferenceMeta
                {
                    ExcludeFromBundle = excludeFromBundle,
                    Part = part
                });
            }

    其实和加CSS一样,只是值换成了ScriptReferenceMeta(只有2个值得对象)对象。

    后面代码:

    var commonSettings = EngineContext.Current.Resolve<CommonSettings>();
        if (commonSettings.RenderXuaCompatible)
        {
            Html.AppendHeadCustomParts(string.Format("<meta http-equiv="X-UA-Compatible" content="{0}"/>", commonSettings.XuaCompatibleValue));
        }

    数据库是false.        X-UA-Compatible是一种浏览器兼容模式,可百度:http://www.cnblogs.com/nidilzhang/archive/2010/01/09/1642887.html

    继续往下:<html @Html.Partial("LanguageAttributes")>

    这句就是LanguageAttributes的部件。找到LanguageAttributes.cshtm文件,代码:

    @if (this.ShouldUseRtlTheme())
    {
        <text>dir="rtl"</text>
        //<text>dir="rtl" xml:lang="he" lang="he"</text>
    }

    查看这个方法:

    /// <summary>
            /// Return a value indicating whether the working language and theme support RTL (right-to-left)
            /// </summary>
            /// <returns></returns>
            public bool ShouldUseRtlTheme()
            {
                var supportRtl = _workContext.WorkingLanguage.Rtl;
                if (supportRtl)
                {
                    //ensure that the active theme also supports it
                    var themeProvider = EngineContext.Current.Resolve<IThemeProvider>();
                    var themeContext = EngineContext.Current.Resolve<IThemeContext>();
                    supportRtl = themeProvider.GetThemeConfiguration(themeContext.WorkingThemeName).SupportRtl;
                }
                return supportRtl;
            }

    其实就是是否已支持 从右到左。类似中国 的字都是反写的。应该返回的是false,就不多研究了。

    下面   <title>@Html.NopTitle(true)</title>,查看源码:

    public static MvcHtmlString NopTitle(this HtmlHelper html, bool addDefaultTitle, string part = "")
            {
                var pageHeadBuilder = EngineContext.Current.Resolve<IPageHeadBuilder>();
                html.AppendTitleParts(part);
                return MvcHtmlString.Create(html.Encode(pageHeadBuilder.GenerateTitle(addDefaultTitle)));
            }

    实现类代码:

    public virtual void AppendTitleParts(string part)
            {
                if (string.IsNullOrEmpty(part))
                    return;
                
                _titleParts.Insert(0, part);
            }

    这里我们的part是“”,所以什么都没做。

    最后一句return MvcHtmlString.Create(html.Encode(pageHeadBuilder.GenerateTitle(addDefaultTitle))); 的代码:

    public virtual string GenerateTitle(bool addDefaultTitle)
            {
                string result = "";
                var specificTitle = string.Join(_seoSettings.PageTitleSeparator, _titleParts.AsEnumerable().Reverse().ToArray());
                if (!String.IsNullOrEmpty(specificTitle))
                {
                    if (addDefaultTitle)
                    {
                        //store name + page title
                        switch (_seoSettings.PageTitleSeoAdjustment)
                        {
                            case PageTitleSeoAdjustment.PagenameAfterStorename:
                                {
                                    result = string.Join(_seoSettings.PageTitleSeparator, _seoSettings.DefaultTitle, specificTitle);
                                }
                                break;
                            case PageTitleSeoAdjustment.StorenameAfterPagename:
                            default:
                                {
                                    result = string.Join(_seoSettings.PageTitleSeparator, specificTitle, _seoSettings.DefaultTitle);
                                }
                                break;
                                
                        }
                    }
                    else
                    {
                        //page title only
                        result = specificTitle;
                    }
                }
                else
                {
                    //store name only
                    result = _seoSettings.DefaultTitle;
                }
                return result;
            }

    大体意思就是返回一个title  title分隔符来自于数据库setting的seoSettings.PageTitleSeparator进行分割。

    如果为空就返回默认title(不研究了 ,应该简单)。

    下一句:

    <meta name="description" content="@(Html.NopMetaDescription())" />

    哦  太慢了,以后不用这么详细,能省略就省略了。。。最终是这样:

    public virtual string GenerateMetaDescription()
            {
                var metaDescription = string.Join(", ", _metaDescriptionParts.AsEnumerable().Reverse().ToArray());
                var result = !String.IsNullOrEmpty(metaDescription) ? metaDescription : _seoSettings.DefaultMetaDescription;
                return result;
            }

    就是返回逗号分割的string,或者数据库默认值。

    <meta name="keywords" content="@(Html.NopMetaKeywords())" />   不分析了 应该都一样。

    后面一句:

    public virtual string GenerateHeadCustom()
            {
                //use only distinct rows
                var distinctParts = _headCustomParts.Distinct().ToList();
                if (distinctParts.Count == 0)
                    return "";
    
                var result = new StringBuilder();
                foreach (var path in distinctParts)
                {
                    result.Append(path);
                    result.Append(Environment.NewLine);
                }
                return result.ToString();
            }

    输出自定义部件。

    @*This is used so that themes can inject content into the header*@
        @Html.Partial("Head")

    从注释可知,这里theme可以注入他们的内容。

    事实也是如此,在theme里的head是有内容的,而主路径下的只有注释。:

    @using Nop.Core;
    @using Nop.Core.Domain
    @using Nop.Core.Infrastructure
    @using Nop.Web.Framework.Themes
    @using Nop.Web.Framework.UI
    @{
        var supportRtl = EngineContext.Current.Resolve<IWorkContext>().WorkingLanguage.Rtl;
        var supportResponsive = EngineContext.Current.Resolve<StoreInformationSettings>().ResponsiveDesignSupported;
        var themeName = EngineContext.Current.Resolve<IThemeContext>().WorkingThemeName;
        
        //we do not support responsive for RTL yet
        if (supportRtl)
        {
            supportResponsive = false;
        }
        //add browser specific CSS files
        var browser = Request.Browser;
        if (browser.Browser == "IE" && browser.MajorVersion == 6)
        {
            Html.AppendCssFileParts(string.Format("~/Themes/{0}/Content/ie6.css", themeName));
        }
        else if (browser.Browser == "IE" && browser.MajorVersion == 7)
        {
            Html.AppendCssFileParts(string.Format("~/Themes/{0}/Content/ie7.css", themeName));
        }
        //responsive design
        if (supportResponsive)
        {
            Html.AppendCssFileParts(string.Format("~/Themes/{0}/Content/responsive.css", themeName));
        }
        //add main CSS file
        if (supportRtl)
        {
            Html.AppendCssFileParts(string.Format("~/Themes/{0}/Content/styles.rtl.css", themeName));
        }
        else
        {
            Html.AppendCssFileParts(string.Format("~/Themes/{0}/Content/styles.css", themeName));
        }
        //responsive design
        if (supportResponsive)
        {
            <meta name="viewport" content="width=device-width, initial-scale=1">
        }
    }

    第一二句,是获得RTL与响应式,注释说我们不支持响应式的RTL,也就是如果是RTL,就设置响应式为false.

    第三局,字面理解 获得工作时的皮肤名称。找到实现类ThemeContext的方法WorkingThemeName。:

    /// <summary>
            /// Get or set current theme system name
            /// </summary>
            public string WorkingThemeName
            {
                get
                {
                    if (_themeIsCached)
                        return _cachedThemeName;
    
                    string theme = "";
                    if (_storeInformationSettings.AllowCustomerToSelectTheme)
                    {
                        if (_workContext.CurrentCustomer != null)
                            theme = _workContext.CurrentCustomer.GetAttribute<string>(SystemCustomerAttributeNames.WorkingThemeName, _genericAttributeService, _storeContext.CurrentStore.Id);
                    }
    
                    //default store theme
                    if (string.IsNullOrEmpty(theme))
                        theme = _storeInformationSettings.DefaultStoreTheme;
    
                    //ensure that theme exists
                    if (!_themeProvider.ThemeConfigurationExists(theme))
                    {
                        var themeInstance = _themeProvider.GetThemeConfigurations()
                            .FirstOrDefault();
                        if (themeInstance == null)
                            throw new Exception("No theme could be loaded");
                        theme = themeInstance.ThemeName;
                    }
                    
                    //cache theme
                    this._cachedThemeName = theme;
                    this._themeIsCached = true;
                    return theme;
                }
                set
                {
                    if (!_storeInformationSettings.AllowCustomerToSelectTheme)
                        return;
    
                    if (_workContext.CurrentCustomer == null)
                        return;
    
                    _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, SystemCustomerAttributeNames.WorkingThemeName, value, _storeContext.CurrentStore.Id);
    
                    //clear cache
                    this._themeIsCached = false;
                }
            }
  • 相关阅读:
    查找代码行数和查看域名版本
    iOS10里的通知与推送
    计算有多少个岛屿
    java.lang.NoClassDefFoundError: Could not initialize class com.haoyao.shop.common.XXX
    Windows 版本Mongodb 启动
    安装第三方库 报错Python version 2.7 required, which was not found in the registry
    Python 爬虫 报错 403 HTTP Error 403: Forbidden
    廖雪峰 练习 把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字
    利用Python 2.7打印杨辉三角
    MAVEN实战 读书笔记 第二章
  • 原文地址:https://www.cnblogs.com/runit/p/4204041.html
Copyright © 2020-2023  润新知