3.2 发布和显示文章


对文章的管理主要是发布、删除和编辑。对于每个部分,都有一些详细的要求,本节就围绕这三个功能,开发管理文章的程序,如下图所示。

3.2.1 简单的文章发布

下面从简单的文章发布开始,讲解运行机制。

首先介绍一个预备知识slug,下面举例说明。

假如在数据库中有一篇标题为Learn Python in itdiffer.com的文章,要访问这篇文章,一种方法是使用这篇文章的id,比如http://www.itdiffer.com/article/231,231就是这篇文章在数据库表中的id;还有另外一种方法,是直接在URL中显示文章的标题(http://www.itdiffer.com/article/Learn Python in itdiffer.com),但实际上不能这样,因为在URL里面,空格都是用“%20”来表示的,所以上面那个地址最终表现形式应该是https://www.itdiffer.com/article/Learn%20Python%20in%20itdiffer.com。这种显示不是一个很友好的方案,这时slug就可以发挥作用了,它实现的结果是http://www.itdiffer.com/article/Learn-Python-in-itdiffer.com,这种方式对搜索引擎也是友好的。

Django中就提供了实现上述slug的方法。在本项目的根目录中执行python manage.py shell,进入到交互模式。

表现完美。不过我们有时也会用中文写标题,不总是用英文的。那么中文怎样转换呢?

这就不完美了,Django内置的这个方法不能处理中文,它返回的是空。还好,有Google协助,很容易就搜索到了一个能够解决中文的第三方库。

安装之后,还是在交互模式中,代码如下。

自动将汉字转换为拼音,这样就解决了前面遇到的汉字问题,并且英语依然适用。仔细观察,与Django自带的还稍微有一些区别呢!

这是一个好工具。

下面开始按套路出牌----套路就是规则,对于大的系统,按照规则来做,成本是最低的。

1、创建数据模型类

在./article/models.py文件中建立一个数据模型,命名为ArticlePost,作为文章的数据模型对象。

 1 from django.utils import timezone #①
 2 from django.core.urlresolvers import reverse
 3 from slugify import slugify
 4 
 5 class ArticlePost(models.Model):
 6     author = models.ForeignKey(User,related_name="article")
 7     title = models.CharField(max_length=200)
 8     slug = models.SlugField(max_length=500)
 9     column = models.ForeignKey(ArticleColumn,related_name="article_column")
10     body = models.TextField()
11     created = models.DateTimeField(default=timezone.now()) #②
12     updated = models.DateTimeField(auto_now=True)
13 
14     class Meta:
15         ordering = ("title",)
16         index_together = (('id','slug'),) #③
17 
18     def __str__(self):
19         return self.title
20 
21     def save(self, *args, **kargs): #④
22         self.slug = slugify(self.title) #⑤
23         super(ArticlePost,self).save(*args, **kargs)
24 
25     def get_absolute_url(self): #⑥
26         return reverse("article:article_detail",args=[self.id, self.slug])

在./testsite/settings.py文件中,有一个TIME_ZONE项,要确认一下是否使用TIME_ZONE=‘Asia/Shanghai’时区,语句①所引入的timezone将被用于语句②,timezone.now()即得到文章发布时的日期和时间。

语句③的作用是对数据库中这两个字段建立索引,在后面,会通过每篇文章的id和slug获取该文章对象,这样建立了索引之后,能提高读取文章对象的速度。

每个数据模型类都有一个save方法,语句④对此方法进行重写,其目的就是要实现语句⑤。

语句⑥是要获取谋篇文章对象的URL,暂时用不到。前面引入模块时看到的reverse就在这个方法中使用了。

新的数据模型建立后,就要迁移数据,从而建立数据库表。依次执行python manage.py makemigrations和python manage.py migrate两个命令。

2、创建表单类

编辑./article/forms.py文件,增加如下代码。

1 class ArticlePostForm(forms.ModelForm):
2     class Meta:
3         model = ArticlePost
4         fields = ("title","body")

3、创建视图函数

编辑./article/views.py文件,输入如下代码。

 1 @login_required(login_url='/account/login')
 2 @csrf_exempt
 3 def article_post(request):
 4     if request.method =="POST":
 5         article_post_form = ArticlePostForm(data=request.POST)
 6         if article_post_form.is_valid():
 7             cd = article_post_form.cleaned_data
 8             try:
 9                 new_article = article_post_form.save(commit=False)
10                 new_article.author = request.user
11                 new_article.column = request.user.article_column.get(id=request.POST['column_id'])
12                 new_article.save()
13                 return HttpResponse("1")
14             except:
15                 return HttpResponse("2")
16         else:
17             return HttpResponse("3")
18     else:
19         article_post_form = ArticlePostForm()
20         article_columns = request.user.article_column.all() #⑦
21         return render(request,"article/column/article_post.html",{"article_post_form":article_post_form,"article_columns":article_columns})

在视图函数中会用到ArticlePost和ArticlePostForm类,不要忘记在本文件头部引入。

语句⑦得到的是该登录用户已经设置的栏目对象,但为什么没有使用比较熟悉的article_columns = ArticleColumn.objects.filter(user=request.user)呢?这么写当然可以,在此使用request.user.article_column.all()的目的在于引起读者关注ArticleColumn数据模型类中的user = models.ForeignKey(User,on_delete=models.CASCADE,related_name='article_column'),这里有一个参数related_name,为了理解它的作用。

当一个页面被请求时,Django创建一个包含请求元数据的HttpRequest对象,然后把HttpRequest作为视图函数的第一个参数传入,每个视图要负责返回一个HttpResponse对象。这个HttpResponse对象有很多属性,其中包括熟知的method、GET和POST。user也是一个属性,当用户登录之后,request.user即为User类的一个实例。

4、设置URL

编辑./article/urls.py文件,添加如下URL配置语句。

1 path('article-post/',views.article_post,name="article_post"),

5、编写模板

在./templates/article/column目录中创建一个名为article_post.html的文件,其代码如下。

 1 {% extends "article/base.html" %}
 2 {% load staticfiles %}
 3 {% block title %}article column{% endblock %}
 4 {% block content %}
 5 <div style="margin-left:10px">
 6     <form class="form-horizontal" action="." method="post">{% csrf_token %}
 7         <div class="row" style="margin-top:10px;">
 8             <div class="col-md-2 text-right"><span>标题:</span></div>
 9             <div class="col-md-10 text-left">{{article_post_form.title}}</div>
10         </div>
11         <div class="row" style="margin-top:10px;">
12             <div class="col-md-2 text-right"><span>栏目:</span></div>
13             <div class="col-md-10 text-left">
14                 <select id="which_column">
15                     {% for column in article_columns %}
16                     <option value="{{column.id}}">{{column.column}}</option>
17                     {% endfor %}
18                 </select>
19             </div>
20         </div>
21         <div class="row" style="margin-top:10px;">
22             <div class="col-md-2 text-right"><span>内容:</span></div>
23             <div class="col-md-10 text-left">{{article_post_form.body}}</div>
24         </div>
25         <div class="row">
26             <input type="button" class="btn btn-primary btn-lg" value="发布" onclick="publish_article()">
27         </div>
28     </form>
29 </div>
30 <script type="text/javascript" src='{% static "js/jquery-3.3.1.js" %}'></script>
31 <script type="text/javascript" src='{% static "js/layer.js" %}'></script>
32 <script type="text/javascript">
33     function publish_article(){
34         var title = $("#id_title").val();
35         var column_id = $("#which_column").val();
36         var body = $("#id_body").val();
37         $.ajax({
38             url:"{% url 'article:article_post' %}",
39             type:"POST",
40             data:{"title":title, "body":body, "column_id":column_id},
41             success:function(e){
42                 if(e=="1"){
43                     layer.msg("successful");
44                 }else if(e=="2"){
45                     layer.msg("sorry.");
46                 }else{
47                     layer.msg("项目名称必须写,不能空。");
48                 }
49             },
50         });
51     }
52 </script>
53 {% endblock %}

前端模板还没有结束,还要修改./templates/article/leftslider.html文件,目的是设置一个发布文章的入口。

1 <p><a href="{% url 'article:article_post' %}">发布文章</a> </p>

将上面这行代码放在leftslider.html文件的合适位置。

6、测试本功能

确保Django服务已运行,在浏览器的地址栏中输入http://127.0.0.1:8000/article/article-post/,然后在打开的页面中输入要发布的内容,如下图所示。

界面的确很朴素,连按钮的位置都没有好好安排,而且在文本框中只能输入文本。不过,重点是看功能是否实现,单击“发布”按钮看一下效果。

弹出一个对话框,上面有"successful”,表示成功了;如果报错,要仔细排查,对照上述代码,必要时“请”出Google,或者根据本书最开始列出的其他方式求助。

经过上面6个步骤,就完成了简单的文章发布功能的开发。

接下来就可以美化一下了。

3.2.2 使用Markdown

Markdown是一种轻量级的标记语言,由Jhon Gruber于2004年创立。索然是一种“标记语言”,但是用起来并不难,因为它语法简单、标记符数量少,更重要的是可以完全可视化操作。

在本项目中,我们使用这样一个Markdown编辑器。

1、部署相关文件

2、修改模板

这个插件主要是基于前端的,所以只需要修改./tenplates/article/column/article_post.html文件。首先,在文件中引入两个CSS文件。

1 <link rel="stylesheet" href="{% static 'editor/css/style.css' %}">
2 <link rel="stylesheet" href="{% static 'editor/css/editormd.css' %}">

其中,editormd.css文件是所下载的插件自带的,另外一个style.css文件是笔者根据所下载的插件中的例子改写的,代码如下。

 1 #layout {
 2     text-align: left;
 3 }
 4 
 5 #layout > header, .btns {
 6     padding: 15px 0;
 7     width: 90%;
 8     margin: 0 auto;
 9 }
10 
11 #layout > header > h1 {
12     font-size: 20px;
13     margin-bottom: 10px;
14 }

在此模板文件中找到如下代码。

1 <div class="col-md-10 text-left">{{article_post_form.body}}</div>

用下面的代码替换上面找到的代码.

1 <div id="editormd" class="col-md-10 text-left">
2    <textarea style="display:none;" id="id_body"></textarea>
3</div>

最后在模板文件尾部引入JavaScript文件和相应的脚本代码。

 1 <script type="text/javascript" src="{% static 'editor/editormd.min.js' %}"></script>
 2 <script type="text/javascript">
 3     $(function(){
 4         var editor = editormd("editormd",{
 5             width:"100%",
 6             height:640,
 7             syncScrolling:"single",
 8             path:"{% static 'editor/lib/' %}"
 9         });
10     });
11 </script>

可以查看效果了。确保Django服务在运行并且用户处于登录状态,比如还是使用前面已经出现的账号登录。在浏览器的地址栏中输入http://127.0.0.1:8000/article/article-post/地址(如果用户没有登录,系统会提示登录),就可以看到发布文章的界面,如下图所示。

呈现在显示器上的是一个功能丰富的图文混排的编辑器,当然这个编辑器是我们所使用的Editor插件中最简单的。根据Markdown的语法输入内容,在左边输入后,在右边可以看到显示效果。

通过这种方式发布的文章,就不再是简单的文本了,而是可以包含图片、文字、音频、视频等多媒体文档。特别推荐使用Markdown的标记符号,能够让你编辑文档更快捷。

通过点击“发布”按钮,就能够把文章发布,但是不能浏览,接下来就要显示文章及其标题列表。

3.2.3 文章标题列表

1、简单的标题列表

编辑./article/views.py文件,编写一个简单的视图函数。

1 from .models import ArticleColumn,ArticlePost
2 
3 @login_required(login_url='/account/login/')
4 def article_list(request):
5     articles = ArticlePost.objects.filter(author=request.user) #①
6     return render(request,"article/column/article_list.html",{"articles":articles})

用语句①筛选出用户的所有文章对象,并将该对象渲染到模板。

创建模板文件./templates/article/column/article_list.html,输入如下代码。

 1 {% extends "article/base.html" %}
 2 {% load staticfiles %}
 3 {% block title %}articles list{% endblock %}
 4 {% block content %}
 5 <div>
 6     <table class="table table-hover">
 7         <tr>
 8             <td>序号</td>
 9             <td>标题</td>
10             <td>栏目</td>
11             <td>操作</td>
12         </tr>
13         {% for article in articles %}
14         <tr id={{ article.id }}>
15             <td>{{ forloop.counter }}</td>
16             <td>{{ article.title }}</td>
17             <td>{{ article.column }}</td>
18             <td>--</td>
19         </tr>
20         {% endfor %}
21     </table>
22 </div>
23 {% endblock %}

上述的模板代码跟文章栏目名称列表代码类似,实现了简单的标题列表。最后一步就是设置URL,编辑./article/urls.py文件,增加下面的代码。

1 path('article-list/',views.article_list,name="article_list"),

因为增加了文件,所以要重启Django服务,在浏览器的地址栏中输入http://127.0.0.1:8000/article/article-list/,效果如下图所示。

还可以看一下数据库,对数据的存储有更直观的了解。

多发布几篇文章,别满足于看文章标题列表页,可以将数据库中的记录顺序和列表页显示的文章标题顺序进行对比,可以发现,文章标题列表中的文章顺序没有按照数据库表中的顺序排列(也不是倒序),是按照什么排列规则排列的呢?能看出来吗?

请翻阅前面写的ArticlePost类,其中有下面的代码。

1  class Meta:
2         ordering = ("title",)
3         index_together = (('id','slug'),)

如果以前没有体会到ordering = ("title",)的作用,仔细观察文章标题列表的结果,是不是按照标题名称的顺序排列的?的确是。

如果要修改为按照文章编辑/发布的时间倒序排列,就应该修改ordering的值,代码如下。

1 ordering = ("-updated",)

保存之后,再次刷新页面,就看到所期望的结果了。

为了访问方便,左侧的功能栏当然要增加显示文章标题的入口,编辑./templates/article/leftslider.html文件,在已经熟悉的位置增加下面的代码。

1 <p><a href="{% url 'article:article_list' %}">文章列表</a> </p>

再次刷新浏览器页面,效果如下图所示、

2、查看文章

文章标题列出来了,接下来就是为其设置超链接,单击标题后就能查看文章内容。

再次阅读ArticlePost类的代码,里面有如下内容:

1     def get_absolute_url(self):
2         return reverse("article:article_detail",args=[self.id, self.slug])

这段代码中使用了reverse()函数,先进行望文生义,reverse有“颠倒、倒转”之意。

reverse()函数的调用方式是:

1 reverse(viewname,urlconf=None,args=None,kwargs=None,current_app=None)

参数viewname就是在每个应用的urls.py中设置URL时name的值。比如,前面刚刚在./article/urls.py中增加的path('article-list/',views.article_list,name="article_list"),配置中的name="article_list"。

还是以上述为例,如果要向该视图函数发出请求,可以使用如127.0.0.1:8000/article/article-list/的形式,其中127.0.0.1:8000是ip地址和端口部分,而/article/article-list/就是路径部分,严格说是请求资源的URI。

在模板文件中,遇到了需要使用链接地址时,可以使用{% url viewsname %}的形式,比如:

1 <a href="{% url 'article:article_list' %}">文章列表</a>

其实上述代码也可以写成如下形式:

1 <a href= '/article/article_list/' >文章列表</a>

为了避免硬编码问题,所以采用前述方式,只要规定的名称不变化,路径设置的修改不会影响这里的超链接对象的访问。

这是在模板中,如果在视图函数中呢?比如:

1 HttpResponseRedirect('/article/article-list/')

如果这样写,也是“硬编码”的风格,要避免。代替它的就是使用reverse()函数。

1 HttpResponseRedirect(reverse('article:article_list'))

通过reverse('article:article_list')实现了'/article/article-list/',即从name到path,而在URL配置中是从path到name,因此reverse()起到了“逆向、颠倒”的作用。

reverse()参数中除viewsname外,还可以传入其他数值,比如前面的reverse("article:article_detail",args=[self.id, self.slug])

在ArticlePost类中的get_absolute_url()方法,就是要得到相应文章的路径,因此可以将其用于模板文件./templates/article/column/article_list.html的<a>标签中。

1 <td><a href="{{ article.get_absolute_url }}">{{ article.title }}</a> </td>

这样修改模板文件,实现的效果是让每个标题都实现超链接,链接对象是该文章的详细内容。

刷新页面,如果Django服务已经运行,那么就会看到如下图所示的报错信息,这没有什么意外。

NoReverseMatch at /article/article-list/,这意味着URL配置存在问题。要增加article_detail的URL配置,用来显示文章的详情。在./article/urls.py中增加如下URL配置代码。

1 from django.shortcuts import get_object_or_404
2 
3 @login_required(login_url='/account/login/')
4 def article_detail(request,id,slug):
5     article = get_object_or_404(ArticlePost,id=id,slug=slug)
6     return render(request, "article/column/article_detail.html", {"article": article})

再次编写./templates/article/column/article_detail.html模板文件,为了快速查看效果,先写一个简单的。

 1 {% extends "article/base.html" %}
 2 {% block title %}article list{% endblock %}
 3 {% block content %}
 4 <div>
 5     <h1>{{ article.title }}</h1>
 6     <p>{{user.username }}</p>
 7 <div>
 8     {{ article.body }}
 9 </div>
10 </div>
11 {% endblock %}

刷新文章列表页面,可以看到每个文章标题都已经做好了超链接,如下图所示。

点击某个标题,可以查看该标题所对应的文章,如下图所示。

虽然文章内容的显示方式没有进行美化,但不要在乎其外表,还是要看本质,已经实现了几本功能。此外,还要观察一下URL的特点,与我们所涉及的模式正好符合。

3、按排版格式显示

尽管已经能够看到所发布的内容了,但是在显示方式上并没有按照发布文章时所设定的格式显示。所以,还需要继续编辑显示文章内容的模板文件./templates/article/column/article-detail.html,主要是将Markdown标记符转换为HTML标记符并显示在网页上,重新编辑之后的代码如下。

 1 {% extends "article/base.html" %}
 2 {% block title %}article list{% endblock %}
 3 {% block content %}
 4 <div>
 5     <header>
 6         <h1>{{ article.title }}</h1>
 7         <p>{{user.username }}</p>
 8     </header>
 9 
10     <link rel="stylesheet" href="{% static 'editor/css/editormd.preview.css' %}" />
11     <div id="editormd-view">
12         <textarea id="append-test" style="display:none;">
13             {{ article.body }}
14         </textarea>
15     </div>
16 
17 </div>
18 <script src="{% static 'js/jquery-3.3.1.js' %}"></script>
19 <script src="{% static 'editor/lib/marked.min.js' %}"></script>
20 <script src="{% static 'editor/lib/prettify.min.js' %}"></script>
21 <script src="{% static 'editor/lib/raphael.min.js' %}"></script>
22 <script src="{% static 'editor/lib/underscore.js' %}"></script>
23 <script src="{% static 'editor/lib/sequence-diagram.min.js' %}"></script>
24 <script src="{% static 'editor/lib/flowchart.min.js' %}"></script>
25 <script src="{% static 'editor/lib/jquery.flowchart.min.js' %}"></script>
26 <script src="{% static 'editor/editormd.js' %}"></script>
27 
28 <script type="text/javascript">
29     $(function(){
30         editormd.markdownToHTML("editormd-view",{
31             htmlDecode:"style,script,iframe",  // you can filter decode
32             emoji:true,
33             taskList:true,
34             tex:true,  //默认不解析
35             flowChart:true,  //默认不解析
36             sequenceDiagram:true,  //默认不解析
37         });
38     });
39 </script>
40 {% endblock %}

对于上述代码,在测试过程中就遇到一个“坑”,用了好长时间才解决,那就是写{{ article.body }}代码时不要像编写HTML代码时那样缩进,前面不能有空格,因为它带过来的Markdown标记,不能按照HTML的编码格式写。读者可以试试,看缩进几个空格是什么效果。

上述代码引入了大量的JavaScript文件,都是我们所使用的Markdown插件所提供的。

再次刷新页面,所看到的就应该是一篇按照排版要求显示的页面了,如下图所示。

然而,“发布文章”的功能还要优化,目前成功发布一篇文章之后,仅仅是提示成功,页面并没有跳转。

4、发布文章后页面跳转

  文章发布之后,页面应该跳转到“文章列表”,这样就可以通过列表中的文章标题查看该文章的内容,当然也可以跳转到文章内容页,下面演示的是跳转到文章标题列表页面,读者可以在学习了下面的内容之后,自己实现跳转到该文章内容页的功能。

  需要修改的仅仅是./templates/article/column/article_post.html中的JavaScript部分代码。

 1 <script type="text/javascript">
 2     function publish_article(){
 3         var title = $("#id_title").val();
 4         var column_id = $("#which_column").val();
 5         var body = $("#id_body").val();
 6         $.ajax({
 7             url:"{% url 'article:article_post' %}",
 8             type:"POST",
 9             data:{"title":title, "body":body, "column_id":column_id},
10             success:function(e){
11                 if(e=="1"){
12                     layer.msg("successful");
13                     location.href = "{% url 'article:article_list' %}"; #①
14                 }else if(e=="2"){
15                     layer.msg("sorry.");
16                 }else{
17                     layer.msg("项目名称必须写,不能空。");
18                 }
19             },
20         });
21     }
22 </script>

在视图文件中,文章被保存后向前端反馈,前端则通过Javascript脚本捕获反馈信息并进行判断,语句①就是根据文章已经被正确保存的反馈信息作出的页面跳转操作。

完成上述编码之后,自行测试,看看能否实现预想的功能。

查看已经做好的“栏目管理”、“发布文章”和“文章列表”三个功能,然后查看文章详情。一个不错的具有内容发布和管理功能的系统已经有了雏形------关键这是一个刚刚开始学习Dajngo的你做的。眼前的代码,还有功能没有完成,“文章列表”中的“操作”一项下面还是空的,要补充对文章的删除和编辑功能-----这就是程序员的工作。

3.2.4 知识点

1、关于slug

slug的目的在于为每条记录生成一个URL,并且让这个URL更易读。比如一篇文章的URL,有的是http://www.itdiffer.com/course/38,这里使用文章在数据库中的id(id=38),这中方式虽然能够显示对应内容,但是不容易阅读;如果用类似http://www.itdiffer.com/course/learn-django-with-laoqi的样式,那么这个URL的可读性就很好了。当然,不能仅仅如此,因为文章标题会重复,最好是http://www.itdiffer.com/course/38/learn-django-with-laoqi。这里的“learn-django-with-laoqi”就是要保存在数据库中的slug。为此,在数据模型的字段属性中有专门的一个属性models.SlugField(),这个属性的源码读者可以阅读https://docs.djangoproject.com/en/1.10/_modules/django/db/models/fields/#SlugField,它其实也是CharField的子类。Django这么安排,对发布文章的永久链接是非常友好的。

但是,上述的所有阐述是针对英文标题的,如果在中文环境中,则可以使用本节中的方法转换为汉语拼音。

2、模型:“一对多”

“一对多”这种说法翻译自“one-to-many”,不管翻译还是原文,误以为有方向性,即误以为“一对多”和“多对一”是有区别的,而真实的“一对多”没有方向性。比如用户和文章的关系,一个用户可以发表多篇文章和多篇文章可以对应一个用户,是一样的关系。

在数据库中,为了建立两个表之间的“一对多”的关联,要使用外键。所谓外键(Foreign Key),是用于建立两个数据库表之间的链接的一个或者多个字段。比如有一个数据库表A和用户数据库表User建立了外键关系,在表A中有一个名为user_id的字段,它就是表A的外键。当表A的数据被保存时,会自动在user_id记录中保存User表中用户的id,用这种方式创建了数据库表A和用户表User之间的链接,也就明确了表A中的每条记录是属于哪一个用户的。

Django的数据模型类中的某个字段的属性被设置为某个表的外键后,在数据库表中,以“字段名”+“_id”的方式命名一个记录,并保存关联数据库表中记录的id值(或者理解为关联数据模型类实例的id)。如下图所示显示的就是本节所创建的数据模型类ArticlcePost对应的数据库表结构。

外键有几个常用的参数,能够为数据访问带来很多便利,下面仅列出两个。

  • limit_choices_to,专门用于为字段设置选项,例如staff_member = models.ForeignKey(User,limit_choices_to={'is_staff':True})(此示例引自官方文档)
  • related_name,用于关联对象的反向查询,比如在ArticlePost类中的author = models.ForeignKey(User,related_name="article"),可以通过User实例的article属性得到该用户的所有ArticlePost实例(某个用户的所有文章)。这种反向查询在本书后续项目中会经常用到,读者可在实践中逐渐理解。如果在这里定义了related_name,那么使用“_set”进行的查询就不能使用了,这一点请读者注意。

优质内容筛选与推荐>>
1、rhel7 学习第十九课
2、rsync 远程同步工具
3、linux日常---1、linux下安装、查看、卸载包常用命令
4、exel数据可视化
5、面试经验大全


长按二维码向我转账

受苹果公司新规定影响,微信 iOS 版的赞赏功能被关闭,可通过二维码转账支持公众号。

    阅读
    好看
    已推荐到看一看
    你的朋友可以在“发现”-“看一看”看到你认为好看的文章。
    已取消,“好看”想法已同步删除
    已推荐到看一看 和朋友分享想法
    最多200字,当前共 发送

    已发送

    朋友将在看一看看到

    确定
    分享你的想法...
    取消

    分享想法到看一看

    确定
    最多200字,当前共

    发送中

    网络异常,请稍后重试

    微信扫一扫
    关注该公众号





    联系我们

    欢迎来到TinyMind。

    关于TinyMind的内容或商务合作、网站建议,举报不良信息等均可联系我们。

    TinyMind客服邮箱:support@tinymind.net.cn