abp使用文档

具体入门与开发原理可参考此地址

https://blog.csdn.net/wulex/article/category/7256369/3

abp入门系列

//www.greatytc.com/p/a6e9ace79345

我们以通知公告为示例

  1. 首先我们有一个公告信息的表结构,如下,像是否删除、新增时间等七个字段只需要继承FullAuditedEntity类即可
image.png

项目目录位置,在Domain/Plat建立NoticeItems目录,以类+s设置文件夹。

image.png

TelSCode.Domain.Plat.NoticeItems 此命名空间下的类如下,

 public class NoticeItem : FullAuditedEntity
    {
        /// <summary>
        /// 公告类别ID
        /// </summary>
        [StringLength(50)]
        public string BaseItemId { get; set; }
        /// <summary>
        /// 标题
        /// </summary>
        [StringLength(100)]
        public string Title { get; set; }
        /// <summary>
        /// 内容 
        /// </summary>
        public string Content { get; set; }
        /// <summary>
        ///  打开次数
        /// </summary>
        public int Times { get; set; }
        /// <summary>
        /// 新增人姓名
        /// </summary>

        [StringLength(50)]
        public string CreationUserName { get; set; }
        /// <summary>
        /// 部门
        /// </summary>
        [StringLength(50)]
        public string DepName { get; set; }
        /// <summary>
        /// 是否置顶
        /// </summary>

        public bool IsTop { get; set; }

        /// <summary>
        /// 置顶时间
        /// </summary>
        public DateTime? TopTime { get; set; }
        /// <summary>
        /// 发布时间
        /// </summary>
        public DateTime? DeployTime { get; set; }
        public bool IsImg { get; set; }
        /// <summary>
        /// 状态
        /// </summary>
        public string Status { get; set; }
        /// <summary>
        /// 图片地址
        /// </summary>
        public string ImgUrl { get; set; }
        /// <summary>
        /// 附件地址
        /// </summary>
        public string FileUrl { get; set; }

        /// <summary>
        /// 阅读量+1
        /// </summary>
        public void NewlyTimes()
        {
            this.Times++;
        }

    }

在此文件夹下把相关权限配置好

image.png

具体配置如下

image.png

数据以树的形式存放,公告信息这个菜单放到基础资料管理下,TypeCode为permission时,在菜单下不展示,是菜单下的权限配置,EnCode不能出现重复值,修改菜单时应在此位置修改对应的文字与排序方式、地址。如果是图标,将不自动更新。

new SysMenu {  DisplayName = "公告信息管理", Icon = "icon-standard-date-add", EnCode = "Plat.NoticeItem", LinkUrl = "/Plat/NoticeItem/Index", TypeCode = menu, SortCode = 20 ,
Childrens = new List<SysMenu>()
{
new SysMenu { DisplayName = "新增公告", EnCode = "Plat.NoticeItem.Add", TypeCode = permission, SortCode = 1 },
    new SysMenu { DisplayName = "编辑公告", EnCode = "Plat.NoticeItem.Edit", TypeCode = permission, SortCode = 2},
    new SysMenu { DisplayName = "删除公告", EnCode = "Plat.NoticeItem.Delete", TypeCode = permission, SortCode = 3 },
    new SysMenu { DisplayName = "公告列表", EnCode = "Plat.NoticeItem.GetGrid", TypeCode = permission, SortCode = 4 }
}
}

然后在EntityFramework的TelSCodeDbContext中增加一行

        public virtual IDbSet<NoticeItem> NoticeItem { get; set; }
image.png

在Application的Plat区域增加NoticeItems文件夹,我们以类名后缀加s建立文件夹,

image.png

NoticeItemInput.cs文件夹,一般情况下只需要将NoticeItem中的字段复制过来即可

   [AutoMap(typeof(NoticeItem))]
    public class NoticeItemInput : EntityDto
    {
        public string BaseItemId { get; set; }
        [StringLength(100,ErrorMessage = "标题仅限100个字符")]
        public string Title { get; set; }
        public string Content { get; set; }
        public int Times { get; set; }
        public string CreationUserName { get; set; }
        public string DepName { get; set; }
        public bool IsTop { get; set; }
        public bool IsImg { get; set; }
        public string Status { get; set; }
        public string ImgUrl { get; set; }
        public string FileUrl { get; set; }
        public DateTime? TopTime { get; set; }
        public DateTime? DeployTime { get; set; }
    }

NoticeItemListDto 为列表上展示的数据字段

    [AutoMapFrom(typeof(NoticeItem))]
    public class NoticeItemListDto : EntityDto, IHasCreationTime
    {
        public string BaseItemId { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }
        public int Times { get; set; }
        /// <summary>
        /// 回复次数
        /// </summary>
        public int CommentTimes { get; set; }
        public string CreationUserName { get; set; }
        public DateTime CreationTime { get; set; }

        public DateTime? DeployTime { get; set; }

        public bool IsTop { get; set; }
        public string StatusCode { get; set; }
    }

NoticeItemSearchDto为查询条件,继承PageDto即可,需要增加查询条件,则在此类中增加对应的属性。

    public class NoticeItemSearchDto : PageDto
    {
        public string BaseItemEnCode { get; set; }
        public string Title { get; set; }
    }

INoticeItemAppService .cs文件 夹

    public interface INoticeItemAppService : IUsualCrudAppService<NoticeItemInput, NoticeItemSearchDto, NoticeItemInput, int>
    {

    }

NoticeItemAppService.cs文件,一般情况下,此类继承UsualCrudAppService,继承接口INoticeItemAppService,即可拥有增、删、改、查的功能,如果想自定义查询实现,需要重写父类的CreateFilteredQuery方法,由于公告信息中业务要求,有置顶和置顶时间字段,需要根据最后置顶的时间倒序取数据,所以GetGridByCondition方法需要override下,即可解决,权限配置包括二部分,一种是给父类继承的UsualCurdAppService传相应的权限编码,分别:

base.DeletePermissionName="Plat.NoticeItem.Delete";
 base.CreatePermissionName = "Plat.NoticeItem.Add";
base.UpdatePermissionName = "Plat.NoticeItem.Edit";

在方法名上使用此权限属性配置该方法对应的权限信息。

     [AbpAuthorize("Plat.NoticeItem.GetGrid")]

调用 base.CreateOrUpdate方法时,即会判断用户是否有Plat.NoticeItem.Add权限

  public class NoticeItemAppService : UsualCrudAppService<NoticeItem, NoticeItemInput, NoticeItemSearchDto, NoticeItemInput, int>, INoticeItemAppService
    {

        #region 构造函数
        private readonly IRepository<NoticeItem> _noticeItemRepository;
        private RoleManager RoleManager;
        private readonly ISqlExecuter _iSqlExecuter;
        public NoticeItemAppService(IRepository<NoticeItem> noticeItemRepository, ISqlExecuter iSqlExecuter, RoleManager roleManager) : base(noticeItemRepository)
        {
            this._noticeItemRepository = noticeItemRepository;
            _iSqlExecuter = iSqlExecuter;
            RoleManager = roleManager;
            base.DeletePermissionName = "Plat.NoticeItem.Delete";
            base.CreatePermissionName = "Plat.NoticeItem.Add";
            base.UpdatePermissionName = "Plat.NoticeItem.Edit";
        }
        #endregion

        public override async Task CreateOrUpdate(NoticeItemInput input)
        {
            if (input.IsTop)
            {
                input.TopTime = DateTime.Now;
            }
            else
            {
                input.TopTime = null;
            }

            if (input.Status == StatusCode.Submit.ToString())
            {
                input.DeployTime = DateTime.Now;
            }
            await base.CreateOrUpdate(input);
        }

        protected override IQueryable<NoticeItem> CreateFilteredQuery(NoticeItemSearchDto input)
        {
            return base.CreateFilteredQuery(input)
                .WhereIf(!string.IsNullOrWhiteSpace(input.Title), m => m.Title.Contains(input.Title));
        }

        [AbpAuthorize("Plat.NoticeItem.GetGrid")]
        public override EasyUiListResultDto<NoticeItemInput> GetGridByCondition(NoticeItemSearchDto input)
        {
            var rows = this.CreateFilteredQuery(input).OrderBy(r => r.IsTop).PageEasyUiBy(input).OrderByDescending(r => r.TopTime).MapTo<List<NoticeItemInput>>();

            return new EasyUiListResultDto<NoticeItemInput>(input.Total, rows);
        }

NoticeItemController.cs 此类注入IAbpFileManager 去解析保存的文件,供前台编辑页面时使用。


    public class NoticeItemController : TelSCodeControllerBase
    {
        #region 构造函数
        private readonly INoticeItemAppService _noticeitemAppService;
        private readonly IAbpFileManager _abpFileManager;
        public NoticeItemController(INoticeItemAppService noticeitemAppService, IAbpFileManager abpFileManager)
        {
            this._abpFileManager = abpFileManager;
            this._noticeitemAppService = noticeitemAppService;
        }
        #endregion

        #region 视图
        public ActionResult Index()
        {
            return View();
        }

        [AbpMvcAuthorize("Plat.NoticeItem.Add", "Plat.NoticeItem.Edit")]
        public ActionResult CreateOrUpdateModal()
        {
            return View();
        }
   
        #region 数据
        public async Task<JsonResult> GetInfoForEdit(int id)
        {
            var output = await _noticeitemAppService.GetInfoForEdit(id);

            if (id == 0)
            {
                output.CreationUserName = AbpSession.GetLoginName();
            }

            NoticeItemViewModel noticeItemViewModel = new NoticeItemViewModel(
                  _abpFileManager.GetFileOutput(output.FileUrl),
                  _abpFileManager.GetFileOutput(output.ImgUrl),
                  output
                );

            return Json(noticeItemViewModel);
        }

        [AbpMvcAuthorize("Plat.NoticeItem.GetGrid")]
        public JsonResult GetGridByCondition(NoticeItemSearchDto input)
        {
            var gridData = _noticeitemAppService.GetGridByCondition(input);
            return Json(gridData);
        }

        #endregion
    }

公告信息因为有文件上传,所以需要新建一个NoticeItemViewModel 类
在TelSCode.Web中Plat区域Models新建文件夹NoticeItems,新建类NoticeItemViewModel.cs

image.png
   [AutoMapFrom(typeof(NoticeItemInput))]
    public class NoticeItemViewModel : NoticeItemInput
    {
        public List<AbpFileOutput> AbpFileOutput { get; set; }
        public List<AbpFileOutput> AbpImgFileOutput { get; set; }
        public NoticeItemViewModel(List<AbpFileOutput> fileUrlOutputs, List<AbpFileOutput> imgFileUrlOutputs, NoticeItemInput noticeItemInput)
        {
            AbpFileOutput = fileUrlOutputs;
            AbpImgFileOutput = imgFileUrlOutputs;

            noticeItemInput.MapTo(this);
        }
    }

接下来是界面,新增编辑在一个界面中,一个页面对应一个js,使用@Html.InCludeScript引用,不会有缓存问题,发布之后会生成版本号。

image.png

Index.cshtml

@using Abp.Web.Mvc.Extensions
@{
    ViewBag.Title = "通知公告";
}

@section scripts{
    @Html.IncludeScript("~/bower_components/webuploader/webuploader.min.js")
    @Html.IncludeScript("~/bower_components/staticfile/libs/abp.webuploader.js")
    @Html.IncludeScript("~/bower_components/wangEditor/wangEditor.min.js")
    @Html.IncludeScript("~/bower_components/wangEditor/wangEditor-plugin.js")


    <script>
        var gridUI = gridUI ||
            {
                BaseItemEnCode: $.util.request['name'] == undefined ? "" : $.util.request['name']
            };
    </script>

    @Html.IncludeScript("~/Areas/Plat/Views/NoticeItem/Index.js")

}

@section styles{
    <link href="~/bower_components/webuploader/webuploader.css" rel="stylesheet" />
    <link href="~/bower_components/wangEditor/wangEditor-plugin.css" rel="stylesheet" />
    @Html.IncludeStyle("~/Areas/Plat/Views/NoticeItem/Index.js")
}

<div class="easyui-layout" data-options="fit:true">
    <div data-options="region:'north',border:false" style="height: 54px; overflow: hidden;">
        <form id="searchForm">
            <table class="kv-table no-border">
                <tr>
                    <th class="kv-label" style="text-align:center">标题</th>
                    <td class="kv-content">
                        <input name="Title" id="Title" class="easyui-textbox" />
                        <a href="javascript:void(0);" class="easyui-linkbutton" data-options="iconCls:'icon-search'" onclick="com.filter('#searchForm', '#dgGrid');">查询</a>
                        <a href="javascript:void(0);" class="easyui-linkbutton" data-options="iconCls:'icon-undo'" onclick="com.clear('#searchForm', '#dgGrid')">清空</a>
                    </td>
                </tr>
            </table>
        </form>
    </div>
    <div data-options="region:'center',border:false">
        <table id="dgGrid"></table>
    </div>
</div>

同目录下建一个Index.js ,我们使用闭包的形式来组织代码结构,将可配置项放在了上面,不强制要求,这里只为方便修改。使用时,将NoticeItem替换相应的类名,noticeServcice替换成相应的xxxservcie。abp.services.app.noticeItem中的noticeItem中n是首字母变成小写,这是后台生成的service,要按照此规定使用。

var gridUI = gridUI || {};
(function () {
    var noticeService = abp.services.app.noticeItem;
    var gridUrl = '/Plat/NoticeItem/GetGridByCondition?BaseItemEnCode=' + gridUI.BaseItemEnCode;
    var editModalUrl = '/Plat/NoticeItem/CreateOrUpdateModal';
    var readModalUrl = '/Plat/NoticeItem/ReadModal';
    var dgGrid, dgGridId = "#dgGrid";

    $.extend(gridUI,
        {
            loadGrid: function () {
                var baseEnCode = 'Plat.NoticeItem.';

                var toolbar = [{ text: "刷新", iconCls: "icon-reload", handler: function () { com.btnRefresh(dgGridId); } },
                { text: "新增", EnCode: baseEnCode + 'Add', iconCls: "icon-add", handler: gridUI.btnAdd },
                { text: "编辑", EnCode: baseEnCode + 'Edit', iconCls: "icon-edit", handler: gridUI.btnEdit },
                { text: "删除", EnCode: baseEnCode + 'Delete', iconCls: "icon-remove", handler: gridUI.btnDelete }];
                toolbar = com.authorizeButton(toolbar);
                if (gridUI.BaseItemEnCode != "") {
                    toolbar = [];
                }

                dgGrid = $(dgGridId).datagrid({
                    url: gridUrl,
                    toolbar: toolbar,
                    columns: [[
                        {
                            field: 'Id', title: '查看', width: 20, align: 'center', formatter: function (value, row) {
                                return String.format('<button class="btn btn-default btn-xs" type="button" onclick="gridUI.showDetails(\'{0}\')"><i class="fa fa-search"></i></button>', value);
                            }
                        },
                        { field: 'Title', title: '标题', width: 80 },
                        {
                            field: 'BaseItemId', title: '类别', width: 80, formatter: function (value) {
                                if (top.clients.dataItems['NoticeItem']) {
                                    return top.clients.dataItems['NoticeItem'][value];
                                } else {
                                    return '';
                                }
                            }
                        },
                        { field: 'CreationUserName', title: '发布人姓名', width: 160 },
                        { field: 'IsTop', title: '是否置顶', width: 50, formatter: com.formatYes },
                        {
                            field: 'Status', title: '状态', width: 50, formatter: function (value) {
                                var objMsg = {
                                    "primary": {
                                        text: "发布",
                                        'case': ['Submit']
                                    },
                                    "info": {
                                        text: "暂存",
                                        'case': ['TempSave']
                                    }
                                };
                                return com.formatMsg(value, objMsg);
                            }
                        }
                    ]]
                });
            },
            editInfo: function (title, icon, id) {
                var pDialog = com.dialog({
                    title: title,
                    width: '100%',
                    height: '100%',
                    href: editModalUrl,
                    iconCls: icon,
                    buttons: [
                        {
                            text: '发布',
                            iconCls: 'icon-ok',
                            handler: function () {
                                gridUI.submit(pDialog, "Submit");
                            }
                        }, {
                            text: '暂存',
                            iconCls: 'icon-save',
                            handler: function () {
                                gridUI.submit(pDialog, "TempSave");
                            }

                        }
                    ],
                    onLoad: function () {
                        editUI.setForm(id);
                    }
                });
            },
            showDetails: function (id) {
                com.dialog({
                    title: "详情",
                    width: 1500,
                    height: 800,
                    href: readModalUrl,
                    queryParams: {
                        id: id
                    }
                });
            },
            btnAdd: function () {
                gridUI.editInfo('新增公告', 'icon-add');
            },
            btnEdit: function () {
                com.edit(dgGridId, function (id) {
                    gridUI.editInfo("编辑公告", 'icon-edit', id);
                });
            },
            btnDelete: function () {
                com.deleted(noticeService, dgGridId);
            },
            submit: function (pDialog, status) {
                var f = $("#editForm");
                var isValid = f.form('validate');
                if (!isValid) {
                    return;
                }

                var objForm = f.formSerialize();
                objForm.Content = editor.txt.html();
                objForm.Status = status;

                com.setBusy(pDialog, true);
                noticeService.createOrUpdate(objForm, { showMsg: true })
                    .done(function () {
                        com.btnRefresh();
                        pDialog.dialog('close');
                    })
                    .always(function () {
                        com.setBusy(pDialog, false);
                    });
            }
        });

    $(function () {
        gridUI.loadGrid();
    });

})();

CreateOrUpdate.cshtml

@using Abp.Web.Mvc.Extensions
@{
    Layout = null;
    ViewBag.Title = "通知公告管理";
}
@Html.IncludeScript("/Areas/Plat/Views/NoticeItem/CreateOrUpdateModal.js")
<div class="layui-layer-content">
    <form id="editForm">
        <table class="kv-table">
            <tr>
                <th class="kv-label"><span class="red">*</span>标题</th>
                <td class="kv-content" colspan="3">
                    <input id="Title" name="Title" type="text" class="easyui-textbox" data-options="required:true" style="width:569px" />
                    <input id="Id" name="Id" type="hidden" />
                    <input type="hidden" id="CreationUserName" name="CreationUserName" />
                    <input type="hidden" name="BaseItemCode" value="" />
                </td>
            </tr>
            <tr>
                <th class="kv-label">置顶</th>
                <td class="kv-content" style="width:215px">
                    <input name="IsTop" id="IsTop" class="easyui-switchbutton">
                </td>
                <th class="kv-label">公告类别</th>
                <td class="kv-content">
                    <input name="BaseItemId" id="BaseItemId" />
                </td>
            </tr>
            <tr>
                <th>附件</th>
                <td colspan="3">
                    <div id="fileUrl"></div>
                </td>
            </tr>
            @*<tr>
                    <th class="kv-label">是否图片新闻</th>
                    <td class="kv-content" colspan="3">
                        <input name="IsImg" id="IsImg" class="easyui-switchbutton">
                    </td>
                </tr>*@
            <tr id="IsImgNews">
                <th>上传图片</th>
                <td colspan="3">
                    <input name="IsImg" id="IsImg" type="hidden" value="true">
                    <div id="imgUrl" style="position: relative;"></div>
                </td>
            </tr>
            <tr>
                <th class="kv-label">内容</th>
                <td class="kv-content" colspan="3">
                    <div id="Content" style="position: relative;"></div>
                </td>
            </tr>
        </table>
    </form>
</div>

CreateOrUpdate.js

var Img;
var editor;
var editUI = {
    setForm: function (id) {

        var E = window.wangEditor;
        editor = new E('#Content');
        editor.customConfig = com.editor.customConfig;
        editor.create();
        E.plugins.init({
            elem: '#Content',
            plugins: ['fullscreen']
        });


        //实例化文件上传
        $("#imgUrl").powerWebUpload({
            uploadType: 'img'
        });
        $("#fileUrl").powerWebUpload();

        $('#BaseItemId').combobox({
            url: com.baseUrl + '/baseItem/GetComBoJson?enCode=NoticeItem',
            required: true,
            validType: "comboxValidate['请选择公告类别']"
        });

        //function changeIsImg(checked) {
        //    if (checked) {
        //        $('#IsImgNews').css('display', '');
        //    } else {
        //        $('#IsImgNews').css('display', 'none');
        //    }
        //}

        //$('#IsImg').switchbutton({
        //    onChange: function (checked) {
        //        changeIsImg(checked);
        //    }
        //});

        com.setForm(id, function (data) {
            var f = $("#editForm");
            if (id) {
                setTimeout(function () {
                    editor.txt.html(data.Content);
                }, 666);
            }



            webuploader.loadFile({
                elem: '#imgUrl',
                rows: data.AbpImgFileOutput
            });

            webuploader.loadFile({
                elem: '#fileUrl',
                rows: data.AbpFileOutput
            });
            com.loadSwithButton($('.layui-layer-content'), data);
            //changeIsImg(data.IsImg);
        });
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,348评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,122评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,936评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,427评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,467评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,785评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,931评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,696评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,141评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,483评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,625评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,291评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,892评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,492评论 2 348