当前位置: 首页>后端>正文

uniapp与后端SESSION uniapp后端怎么开发


uni-app移动端开发技巧总结

  • 一.pages.json常用配置总结
  • 1. pages 设置页面路径及窗口表现
  • 2.tabBar 设置底部 tab 的表现
  • 3. globalStyle和style的常用属性
  • (1)app-plus常用属性:
  • titleNView的常用属性:
  • 二.常用功能和开发技巧总结
  • 1.关闭导航栏返回按钮
  • 在要关闭返回按钮的style中添加如下的代码:
  • 2.禁止屏幕旋转时横屏
  • 在App.vue中的onLaunch生命周期函数中添加如下的代码:
  • 3.设置应用的启动时间
  • 在App.vue中的onLaunch生命周期函数中添加如下的代码:
  • 4.禁止手机某页面右滑返回
  • 在对应页面的vue文件中添加onBackPress生命周期函数,并返回true。
  • 5.注册功能的总结
  • 消息提示框uni.showToast
  • 6.封装请求和API
  • 7.本地数据缓存
  • 8.生命周期相关
  • 9. 路由与页面跳转
  • (1) uni.navigateTo( OBJECT )
  • (2) uni.navigateBack( OBJECT )
  • (3)uni.redirectTo( OBJECT )
  • (4)uni.switchTab( OBJECT )
  • (5)uni.preloadPage(OBJECT)
  • 10.与界面相关的操作
  • (1)uni.showModal( OBJECT )
  • (2) uni.hideTabBar( )和uni.showTabBar()
  • (3)onPullDownRefresh
  • (4)onTabItemTap
  • (5)下拉刷新页面的实现原理
  • 三.常用的uniapp组件
  • 1.轮播图 swiper
  • 2.可滚动视图区域 scroll-view
  • 3.富文本 rich-text
  • 四.常用的uni-ui组件
  • 1. **uni-data-checkbox ** 选项组件
  • 2. uni-dateformat 日期格式化组件。
  • 3. uni-easyinput 增强输入框
  • 4. **uni-file-picker** 文件上传组件
  • 5. **uni-group** 分组组件
  • 6 . **uni-combox**


一.pages.json常用配置总结

pages.json 文件用来对 uni-app 进行全局配置,决定页面文件的路径窗口样式原生的导航栏、**底部的原生tabbar **等。

1. pages 设置页面路径及窗口表现

  • pages节点的第一项为应用入口页(即首页)
  • 应用中新增/减少页面,都需要对 pages 数组进行修改
  • 文件名不需要写后缀,框架会自动寻找路径下的页面资源

pages 节点接收一个数组,数组每个项都是一个对象。该对象的属性值有:

(1) path : 配置页面的路径,字符串类型

(2) style :配置该页面独有的一些窗口表现 ,对象类型

2.tabBar 设置底部 tab 的表现

该节点也是一个对象,里面的常用属性有:

(1) color :tab 上的文字初始的颜色

(2) selectColor :tab 上的文字选中时的颜色

(3) fontSize :文字大小,默认10px

(4) height :tabbar的高度,默认50px

(5)iconWidth 图标默认宽度

(6) list :tab 的列表,最少2个,最多5个 tab

list 接收一个数组,数组中的每个项都是一个对象。对象的属性有:

  • pagePath : 页面路径,必须在 pages 中先定义
  • text :tab 上按钮文字。不填的话,就是一个图标
  • iconPath :图片路径
  • selectedIconPath :选中时的图片路径

(7) midButton: 中间按钮

仅在 list 项为偶数时有效,有以下属性:

  • width :中间按钮的宽度
  • height :中间按钮的高度,可以大于 tabBar 高度,达到中间凸起的效果
  • text :中间按钮的文字
  • iconPath :中间按钮的图片路径
  • iconWidth :中间图标的宽度。(高度等比缩放)

!注意! :midButton没有pagePath,需监听点击事件,自行处理点击后的行为逻辑。监听点击事件为调用API:uni.onTabBarMidButtonTap

3. globalStyle和style的常用属性

globalStyle和style都是对象类型的节点,大部分的样式写在两个节点里都是可行的。主要用于设置窗口的表现,一个是全局的,一个是单独页面的。有如下的属性:

navigationBarBackgroundColor :导航栏背景颜色(同状态栏背景色)。默认#000000(即黑色)

navigationBarTextStyle :导航栏标题颜色及状态栏前景颜色,仅支持 black/white。默认white(白色)

navigationBarTitleText : 导航栏标题文字内容

bounce :页面回弹效果,设置为 “none” 时关闭效果。(建议在全局设为"none")

scrollIndicator : 右侧滚动条显示策略,设置为 “none” 时不显示滚动条。按情况设置。

navigationStyle : 导航栏样式,仅支持 default/custom。默认为"default"。"custom"即取消默认的原生导航栏,使用自定义导航栏。

app-plus :设置编译到 App 平台的特定样式。是一个对象类型的属性

(1)app-plus常用属性:

bounce : 页面回弹效果,设置为 “none” 时关闭效果。

titleNView : 导航栏。对象格式。如果取为false,则取消导航栏。

titleNView的常用属性:

backgroundColor :导航栏的背景颜色,会覆盖掉navigationBarBackgroundColor

titleColor :标题文字颜色,可以设置更多rgb的颜色。

titleText :标题文字内容

titleSize :标题文字字体大小

autoBackButton :标题栏控件是否显示左侧返回按钮

titleIcon :标题图标,位于标题的左部

titleIconRadius : 标题图标圆角,取值格式为"XXpx",其中XX为像素值(逻辑像素),如"10px"表示10像素半径圆角。

titleAlign :标题在导航栏上的位置。可取值: (“center”-居中对齐; “left”-居左对齐;)

autoBackButton :是否显示左侧返回按钮 ,默认为true,取消返回按钮,设为false

二.常用功能和开发技巧总结

1.关闭导航栏返回按钮

在要关闭返回按钮的style中添加如下的代码:
"app-plus":{
				"titleNView":{
					"autoBackButton":false
				}
			}

2.禁止屏幕旋转时横屏

在App.vue中的onLaunch生命周期函数中添加如下的代码:
onLaunch: function () {  
    // 锁定竖屏  
    plus.screen.lockOrientation("portrait-primary");  
}

3.设置应用的启动时间

在App.vue中的onLaunch生命周期函数中添加如下的代码:
setTimeout(() => {
    plus.navigator.closeSplashscreen();
}, 2000);

4.禁止手机某页面右滑返回

在对应页面的vue文件中添加onBackPress生命周期函数,并返回true。
onBackPress(e) {//禁止返回
			return true;
		}

5.注册功能的总结

当点击注册按钮时,先要判断账号密码的格式是否符合要求。如果判断后,发现格式不符合要求的话,就要弹出Toast消息提示框,提示相应的错误。

消息提示框uni.showToast
uni.showToast({
						title:'提示信息',
						icon:"none",//提示框的类型,一般都为none
						position:"bottom",//提示框的位置。
    					duration:600//消息显示时间的毫秒数
					})

如果注册填写的账号密码格式填写正确,点击注册按钮时就向服务器发送请求,如果注册成功的话,就显示Toast消息提示框,消息提示框的icon不用填,设置一个duration显示时间。之后设置一个定时器,用于页面的跳转,跳转到login登录页面。如果账号注册失败也要显示失败的Toast提示。

示例代码如下:

// 发送注册的请求
					this.$post(api.USER_REGISTER,{
						userName:this.username,
						password:this.password,
						phonenumber:this.phoneNum,
						sex:this.sex
					}).then(res =>{
						//请求成功
						if(res.code == 200){
						//显示成功的Toast
							uni.showToast({
								title:'注册成功!',
								duration:600
							})
							//跳转到注册页面
							setTimeout(function(){
								uni.navigateTo({
									url:'./login'
								})
							},600)
						}else{
							//显示错误的Toast信息
							uni.showToast({
								title:res.msg,
								icon:"none",
								position:'bottom'
							})
						}
					})

6.封装请求和API

uni-app有提供发起http请求的功能的api但是,应用各页面很多时候要发起非常多的请求,为了简化代码,所以要封装请求和API。把封装的代码放在根目录下的common目录下。

uni-app发起请求的方法uni.request(OBJECT)

uni.request({
    method:'请求的方式', // GET,POST,PUT等
    url:'请求的url地址',
    header:{}, //请求头,是一个对象类型
    data:{}, //请求的参数
    dataType:'json', //一般都设为json,会尝试对返回的数据做一次 JSON.parse
    success:function(){}, //接口请求成功时执行的回调函数
    fail:function(){} // 接口调用失败时执行时执行的回调函数
})

把请求的代码封装在common下的request.js模块下:

下面是示例代码:

//把模块内定义的方法暴露出去
export default{
	// 封装get请求的发送
	get(url,data){
		// 同步获取本地的token
		let token = uni.getStorageSync('token');
		// 封装get请求
		return new Promise((resolve,reject)=>{
			uni.request({
				method:'GET',
				url:url,
				data:data,
				header:{
					'authorization':token
				},
				dataType:'json',
				success: (res) => {
					console.log("get--success")
					// 401:未授权(登录已过期)
					if(res.data.code === 401){
						// 移除本地的token
						uni.removeStorageSync('token');
						// 显示模态弹窗
						uni.showModal({
							title:'提示',
							content:'登录认证已过期,请重新登录!',
							success: (res) => {
								// 1.如果用户点击了确认,跳转到登录页面
								if(res.confirm){
									uni.navigateTo({
										url:'/pages/startUp/login.vue'
									});
								}else if(res.cancel){
									// 用户点击了取消,则什么也不做
									console.log("用户点击了取消")
								}
							}
						});
					}
					resolve(res.data);
				},
				fail: (err) => {
					console.log(err)
					uni.showToast({
						title:"网络连接超时,请下拉刷新!",
						icon:"none",
						duration:1500
					});
					reject(err)
				}
			})
		});
	},
	//封装一个post请求
	post(url,data){
		// 同步获取本地的token指令
		let token = uni.getStorageSync('token')
		return new Promise((resolve,reject)=>{
			uni.request({
				method:'POST',
				url:url,
				data:data,
				header:{
					'content-type':'application/json',
					'authorization':token
				},
				dataType:'json',
				success:function(res){
					console.log("post---success")
					console.log("post---url:"+url)
					console.log("post---code:"+res.data.code)
					if(res.data.code === 401){
						uni.removeStorageSync('token');
						uni.showModal({
							title:'提示',
							content:"登录已过期,请重新登录!",
							success: (res) => {
								if(res.confirm){
									uni.navigateTo({
										url:'/pages/startUp/login.vue'
									})
								}else if(res.cancel){
									console.log('用户点击取消');
								}
							}
						});
					}
					resolve(res.data)
				},
				fail: (err) => {
					console.log("失败")
					uni.showToast({
						title:"接口请求失败,请稍后再试!",
						icon:"none"
					})
					reject(err)
				}
			});
		});
	},
	// 封装一个put请求
	put(url,data){
		let token = uni.getStorageSync('token');
		return new Promise((resolve,reject)=>{
			uni.request({
				method:'PUT',
				url:url,
				data:data,
				header:{
					'content-type':'application/json',
					'authorization':token
				},
				dataType:'json',
				success: (res) => {
					if(res.data.code === 401){
						uni.removeStorageSync('token')
						uni.showModal({
							title:'提示',
							content:'登录已过期,请重新登录!',
							success: (res) => {
								if(res.confirm){
									uni.navigateTo({
										url:'/pages/startUp/login.vue'
									})
								}else if(res.cancel){
									console.log("用户点击取消")
								}
							}
						});
					}
					resolve(res.data)
				},
				fail: (err) => {
					uni.showToast({
						title:"接口请求失败,请稍后再试",
						icon:'none'
					})
					reject(err)
				}
			})
		})
	}
}

其中的每一个请求的方法都返回promise。

之后,还要根据接口文档来封装一个apiUtil.js的接口模块,下面是示例代码:

var ip = uni.getStorageSync('serverIp');
var baseUrl = 'http://'+ ip;

export default{
	SERVER_BASE:baseUrl,
	//用户登录
	USER_LOGIN:baseUrl + '/prod-api/api/login',
	//注册
	USER_REGISTER:baseUrl + '/prod-api/api/register',
	// 获取用户个人信息
	GET_USER_INFO:baseUrl+ '/prod-api/api/common/user/getInfo',
	// 获取首页轮播图
	GET_INDEX_BANNER_IMG:baseUrl+'/prod-api/api/rotation/list',
	// 获取所有服务信息
	GET_SERVICE_INFO:baseUrl+'/prod-api/api/service/list'
}

ip是每一个请求都有的IP地址,通过获取注册时的本地存储里面获取。baseurl对ip地址和http请求拼接一下。

最后就是暴露当前封装的API接口。

封装好这两个模块后,还好把这两个模块在main.js中进行导入和对vue的原型进行绑定。示例代码如下

//导入封装好的模块
import request from './common/request.js'
import api from './common/apiUtil.js'

// 将起绑定到Vue的原型上面去
Vue.prototype.$api = api
Vue.prototype.$get = request.get
Vue.prototype.$post = request.post
Vue.prototype.$put = request.put

7.本地数据缓存

(1) uni.setStorage( OBJECT )

将数据存储在本地缓存中指定的 key 中,会覆盖掉原来该 key 对应的内容,这是一个异步接口。

OBJECT 参数说明:

参数名

参数类型

说明

key

string

本地缓存中的指定的 key

data

任何类型

需要存储的内容,只支持原生类型、及能够通过 JSON.stringify 序列化的对象

success

Function

接口调用成功的回调函数

fail

Function

接口调用失败的回调函数

示例代码:

uni.setStorage({
	key: 'storage_key',
	data: 'hello',
	success: function () {
		console.log('success');
	}
});

(2) uni.setStorageSync( KEY , DATA )

将 data 存储在本地缓存中指定的 key 中,会覆盖掉原来该 key 对应的内容,这是一个同步接口。

参数说明

参数名

参数类型

说明

key

string

本地缓存中的指定的 key

data

任何类型

需要存储的内容,只支持原生类型、及能够通过 JSON.stringify 序列化的对象

示例代码:

uni.setStorageSync('storage_key', 'hello');

(3) uni.getStorage( OBJECT )

从本地缓存中异步获取指定 key 对应的内容。

OBJECT 参数说明:

参数名

参数类型

说明

key

string

本地缓存中的指定的 key

success

Function

接口调用的回调函数

fail

Function

success函数传入的参数说明:

参数名

参数类型

说明

data

Any

key 对应的值

示例代码如下:

uni.getStorage({
	key: 'storage_key',
	success: function (res) {
		console.log(res.data);
	}
});

(4) uni.getStorageSync( KEY )

从本地缓存中同步获取指定 key 对应的内容。

参数说明:

参数名

参数类型

说明

key

string

本地缓存中的指定的 key

示例代码:

const value = uni.getStorageSync('storage_key');

(5) uni.removeStorage( OBJECT )

从本地缓存中异步移除指定 key。

OBJECT 参数说明:

参数

参数类型

说明

key

string

本地缓存中的指定的 key

success

Function

删除成功时的回调函数

fail

Function

删除失败时的回调函数

示例代码:

uni.removeStorage({
	key: 'storage_key',
	success: function (res) {
		console.log('success');
	}
});

(6) **uni.removeStorageSync( KEY ) **

从本地缓存中同步移除指定 key。

参数说明:

参数名

参数类型

说明

key

string

本地缓存中的指定的 key

8.生命周期相关

生命周期有分应用生命周期页面生命周期

(1) 应用生命周期

函数名

说明

onLaunch

uni-app 初始化完成时触发(全局只触发一次

onShow

uni-app 启动,或从后台进入前台显示

onHide

uni-app 从前台进入后台

onError

uni-app 报错时触发

onPageNotFound

页面不存在监听函数

(2) 页面的生命周期其实就是vue的组件的生命周期

9. 路由与页面跳转

(1) uni.navigateTo( OBJECT )

保留当前页面,跳转到应用内的某个页面,使用uni.navigateBack可以返回到原页面。

OBJECT参数说明:

参数

类型

参数说明

url

String

需要跳转的应用内非 tabBar 的页面的路径 , 路径后可以带参数。参数与路径之间使用?分隔,参数键与参数值用=相连,不同参数用&分隔;如 ‘path?key=value&key2=value2’,path为下一个页面的路径,下一个页面的onLoad函数可得到传递的参数

success

Function

成功后的回调函数

注意:

  • 页面跳转路径有层级限制,不能无限制跳转新页面
  • 跳转到 tabBar 页面只能使用 switchTab 跳转
(2) uni.navigateBack( OBJECT )

关闭当前页面,返回上一页面或多级页面。可通过 getCurrentPages() 获取当前的页面栈,决定需要返回几层。

OBJECT参数说明:

参数

类型

说明

delta

number

返回的页面数,如果 delta 大于现有页面数,则返回到首页。

success

Function

成功时的回调函数

(3)uni.redirectTo( OBJECT )

关闭当前页面,跳转到应用内的某个页面。

OBJECT参数说明:

参数

类型

说明

url

string

要跳转的页面,路径后可以带参数

(4)uni.switchTab( OBJECT )

跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面。注意: 如果调用了 **uni.preloadPage(OBJECT) **不会关闭,仅触发生命周期 onHide

OBJECT参数说明:

参数

类型

说明

url

string

要跳转的tabbar页的路径,路径后不能带参数

(5)uni.preloadPage(OBJECT)

预加载页面,是一种性能优化技术。被预载的页面,在打开时速度更快。

OBJECT参数说明:

参数

类型

参数说明

url

string

要预加载的路径

10.与界面相关的操作

(1)uni.showModal( OBJECT )

显示模态弹窗,可以只有一个确定按钮,也可以同时有确定和取消按钮

OBJECT参数说明

参数名

参数类型

说明

title

string

提示的标题

content

string

提示的内容

showCancel

bool

是否显示取消按钮,默认true

success

function

成功时的回调函数

success返回参数说明

参数

类型

说明

cancel

bool

是否点击取消

confirm

bool

是否点击确认

(2) uni.hideTabBar( )和uni.showTabBar()

隐藏 tabBar和显示tabBar

(3)onPullDownRefresh

在 js 中定义 onPullDownRefresh 处理函数(和onLoad等生命周期函数同级),监听该页面用户下拉刷新事件。

  • 需要在 pages.json 里,找到的当前页面的pages节点,并在 style 选项中开启 enablePullDownRefresh
  • 当处理完数据刷新后,**uni.stopPullDownRefresh **可以停止当前页面的下拉刷新。
(4)onTabItemTap

当点击本页tabBar的item时触发的函数

如下案例:

onTabItemTap: function(e) {
			if(e.index==2) {
				uni.setTabBarStyle({
				  selectedColor: '#D9001B'
				})
			} else {
				uni.setTabBarStyle({
				  selectedColor: '#007AFF'
				})
			}
		}
(5)下拉刷新页面的实现原理

(1)首先要开启该页面的下拉刷新的功能

(2)然后在该页面添加的OnPullDownRefresh(e){ } 里面监听下拉刷新,并在里面调用获取页面数据的代码,然后就要在里面使用vue的**this.$forceUpdate()**方法来重新渲染页面。

示例代码如下:

onPullDownRefresh(e) {
			this.getBannerImage()
			this.getServiceInfo()
			this.getNewsList()
			this.$forceUpdate()
			uni.stopPullDownRefresh()
		}

最后别忘了调用uni.stopPullDownRefresh()停止刷新。

三.常用的uniapp组件

1.轮播图 swiper

注意滑动切换和滚动的区别,滑动切换是一屏一屏的切换。swiper下的每个swiper-item是一个滑动切换区域,不能停留在2个滑动区域之间。

常用属性说明:

属性

说明

indicator-dots

是否显示面板指示点,默认为false

indicator-color

指示点颜色,rgb颜色

indicator-active-color

当前选中的指示点颜色

autoplay

是否自动切换,默认为false

interval

自动切换时的时间间隔,默认5000

duration

滑动时动画时长,默认500

@change

current 改变时会触发 change 事件,event.detail = {current: current, source: source}

2.可滚动视图区域 scroll-view

属性说明

属性

说明

scroll-x

允许横向滚动。默认为false

scroll-with-animation

在设置滚动条位置时使用动画过渡

scroll-view里面放view。并且一定要给scroll-view的样式加上white-space: nowrap

下面是使用示例:

<view class="scroll-box">
			<scroll-view scroll-x="true" scroll-with-animation="true">
				<view class="scroll-view-item" v-for="item in newsfl">
					<text>{{item.name}}</text>
				</view>
			</scroll-view>
	</view>

如果要点击各view换颜色,就给view添加一个hover-class的属性。

3.富文本 rich-text

富文本的用处非常的大,请求过来的数据很多带html标签,使用富文本可以对这些标签解析渲染。

常用属性:

属性

说明

nodes

要加载的文本(html string)

四.常用的uni-ui组件

1. **uni-data-checkbox ** 选项组件

本组件是基于uni-app基础组件checkbox的封装,这个组件可以用于单选项和多选项。

下面是基本的使用:

(1)单选按钮

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pSLuNTO3-1647782363222)(D:\Pictures\截图\Snipaste_2022-03-12_10-01-20.png)]

<template>
	<view>
		<uni-data-checkbox v-model="value" :localdata="range" @change="change"></uni-data-checkbox>
	</view>
</template>
export default {
	data() { 
		return {
			value: 0,
			range: [{"value": 0,"text": "篮球"	},{"value": 1,"text": "足球"},{"value": 2,"text": "游泳"}]
		}
	},
	methods: {
		change(e){
			console.log(e);
		}
	}
}

v-model 为表单的双向数据绑定,绑定value的值。

localdata 为要渲染的数据,属性的格式为数组,数组内每项是对象,对象的格式需为{ “value” : 选中后的值 ,“text” : 显示的文本 }

@change 选中状态改变时触发事件

2. uni-dateformat 日期格式化组件。

基本用法如下:

<uni-dateformat date="2020/10/20 20:20:20" format="yyyy年MM月dd日" ></uni-dateformat>

date 传入要格式化的时间。format指定格式化的模式(如yyyy-MM-dd hh:mm:ss)

3. uni-easyinput 增强输入框

最基础的用法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8gqGbNTr-1647782363222)(D:\Pictures\截图\Snipaste_2022-03-12_11-16-06.png)]

<uni-easyinput v-model="value" placeholder="请输入内容"></uni-easyinput>

输入框带左右图标

可以用于设置搜索框,但是可能没有这个必要。而且也不是非常好看

设置 suffixIcon 属性来显示输入框的尾部图标

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ksseOCLw-1647782363223)(D:\Pictures\截图\Snipaste_2022-03-12_11-19-50.png)]

<uni-easyinput suffixIcon="search"  v-model="value" placeholder="请输入内容" @iconClick="onClick"></uni-easyinput>

可以设置输入框的类型

type属性,值有:password密码框,textarea多行文本输入框,text单行文本框,number数字输入键盘

可以设置输入框的最大长度 :maxlength

设置键盘右下角的文字 confirmType

conformType有这些属性:

属性名

说明

send

发送

search

搜索

next

下一个

go

前往

done

完成

是否自动去除空格 trim

样式自定义 style

type=password 时,是否显示小眼睛图标 passwordIcon 默认为true

4. uni-file-picker 文件上传组件

文件选择上传组件,可以选择图片、视频等任意文件并上传到当前绑定的服务空间。项目里会有一个更换头像的功能。由于比赛的项目并没有提供上传头像的接口,所以只是本地app更换头像,用来示意一下。

<uni-file-picker v-model="value" file-mediatype="image" limit="1" @select="select()">
	<button>选择头像</button>
</uni-file-picker>

v-model绑定一个空数组 就行, file-mediatype指定文件类型 ,limit用于指定上传文件的个数。有一个选中时触发的方法@select

5. uni-group 分组组件
<uni-group title="分组1" top="20" mode="card">
    <view>分组1 的内容</view>
    <view>分组1 的内容</view>
</uni-group>

<uni-group title="分组2">
    <view>分组2 的内容</view>
    <view>分组2 的内容</view>
</uni-group>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sdmOMe62-1647782363223)(D:\Pictures\截图\Snipaste_2022-03-12_13-05-51.png)]

title用于指定分组的标题,top用于指定分组的间隔,mode为模式,有默认和card模式。

6 . uni-combox
<uni-combox label="所在城市" :candidates="candidates" placeholder="请选择所在城市" v-model="city"></uni-combox>



https://www.xamrdz.com/backend/3js1957534.html

相关文章: