Code Samples
This page provides practical code examples and snippets to help you implement common features in uni-app development.
Basic Components
Custom Button Component
vue
<template>
<button
class="custom-button"
:class="[`button-${type}`, { 'button-disabled': disabled }]"
:disabled="disabled"
@click="handleClick"
>
<text class="button-text">{{ text }}</text>
</button>
</template>
<script>
export default {
name: 'CustomButton',
props: {
text: {
type: String,
default: 'Button'
},
type: {
type: String,
default: 'default',
validator: value => ['default', 'primary', 'success', 'warning', 'danger'].includes(value)
},
disabled: {
type: Boolean,
default: false
}
},
methods: {
handleClick() {
if (!this.disabled) {
this.$emit('click')
}
}
}
}
</script>
<style scoped>
.custom-button {
padding: 12rpx 24rpx;
border-radius: 8rpx;
border: none;
font-size: 28rpx;
cursor: pointer;
transition: all 0.3s ease;
}
.button-default {
background-color: #f8f9fa;
color: #333;
}
.button-primary {
background-color: #007bff;
color: white;
}
.button-success {
background-color: #28a745;
color: white;
}
.button-warning {
background-color: #ffc107;
color: #333;
}
.button-danger {
background-color: #dc3545;
color: white;
}
.button-disabled {
opacity: 0.6;
cursor: not-allowed;
}
.button-text {
font-weight: 500;
}
</style>
Loading Component
vue
<template>
<view v-if="visible" class="loading-overlay">
<view class="loading-container">
<view class="loading-spinner"></view>
<text class="loading-text">{{ text }}</text>
</view>
</view>
</template>
<script>
export default {
name: 'Loading',
props: {
visible: {
type: Boolean,
default: false
},
text: {
type: String,
default: 'Loading...'
}
}
}
</script>
<style scoped>
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.loading-container {
background-color: white;
padding: 40rpx;
border-radius: 16rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.loading-spinner {
width: 60rpx;
height: 60rpx;
border: 4rpx solid #f3f3f3;
border-top: 4rpx solid #007bff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
margin-top: 20rpx;
font-size: 28rpx;
color: #666;
}
</style>
Network Requests
API Service Class
javascript
// utils/api.js
class ApiService {
constructor() {
this.baseURL = 'https://api.example.com'
this.timeout = 10000
}
// Request interceptor
request(options) {
return new Promise((resolve, reject) => {
// Add loading
uni.showLoading({
title: 'Loading...'
})
// Add token to headers
const token = uni.getStorageSync('token')
if (token) {
options.header = {
...options.header,
'Authorization': `Bearer ${token}`
}
}
uni.request({
url: this.baseURL + options.url,
method: options.method || 'GET',
data: options.data,
header: {
'Content-Type': 'application/json',
...options.header
},
timeout: this.timeout,
success: (res) => {
uni.hideLoading()
if (res.statusCode === 200) {
if (res.data.code === 0) {
resolve(res.data)
} else {
this.handleError(res.data)
reject(res.data)
}
} else {
this.handleError({ message: 'Network error' })
reject(res)
}
},
fail: (err) => {
uni.hideLoading()
this.handleError({ message: 'Request failed' })
reject(err)
}
})
})
}
// Error handler
handleError(error) {
uni.showToast({
title: error.message || 'Unknown error',
icon: 'none',
duration: 2000
})
}
// GET request
get(url, params) {
return this.request({
url: params ? `${url}?${this.buildQuery(params)}` : url,
method: 'GET'
})
}
// POST request
post(url, data) {
return this.request({
url,
method: 'POST',
data
})
}
// PUT request
put(url, data) {
return this.request({
url,
method: 'PUT',
data
})
}
// DELETE request
delete(url) {
return this.request({
url,
method: 'DELETE'
})
}
// Build query string
buildQuery(params) {
return Object.keys(params)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
.join('&')
}
}
export default new ApiService()
Usage Example
javascript
// pages/index/index.vue
import api from '@/utils/api'
export default {
data() {
return {
userList: [],
loading: false
}
},
onLoad() {
this.fetchUsers()
},
methods: {
async fetchUsers() {
try {
this.loading = true
const response = await api.get('/users', { page: 1, limit: 10 })
this.userList = response.data
} catch (error) {
console.error('Failed to fetch users:', error)
} finally {
this.loading = false
}
},
async createUser(userData) {
try {
const response = await api.post('/users', userData)
uni.showToast({
title: 'User created successfully',
icon: 'success'
})
this.fetchUsers() // Refresh list
} catch (error) {
console.error('Failed to create user:', error)
}
}
}
}
Data Storage
Storage Utility
javascript
// utils/storage.js
class StorageUtil {
// Set item with expiration
setItem(key, value, expireTime) {
const data = {
value,
expireTime: expireTime ? Date.now() + expireTime : null
}
try {
uni.setStorageSync(key, JSON.stringify(data))
return true
} catch (error) {
console.error('Storage set error:', error)
return false
}
}
// Get item with expiration check
getItem(key) {
try {
const dataStr = uni.getStorageSync(key)
if (!dataStr) return null
const data = JSON.parse(dataStr)
// Check expiration
if (data.expireTime && Date.now() > data.expireTime) {
this.removeItem(key)
return null
}
return data.value
} catch (error) {
console.error('Storage get error:', error)
return null
}
}
// Remove item
removeItem(key) {
try {
uni.removeStorageSync(key)
return true
} catch (error) {
console.error('Storage remove error:', error)
return false
}
}
// Clear all storage
clear() {
try {
uni.clearStorageSync()
return true
} catch (error) {
console.error('Storage clear error:', error)
return false
}
}
// Get storage info
getInfo() {
try {
return uni.getStorageInfoSync()
} catch (error) {
console.error('Storage info error:', error)
return null
}
}
}
export default new StorageUtil()
Form Validation
Validation Utility
javascript
// utils/validator.js
class Validator {
constructor() {
this.rules = {
required: (value) => {
return value !== null && value !== undefined && value !== ''
},
email: (value) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return emailRegex.test(value)
},
phone: (value) => {
const phoneRegex = /^1[3-9]\d{9}$/
return phoneRegex.test(value)
},
minLength: (value, length) => {
return value && value.length >= length
},
maxLength: (value, length) => {
return value && value.length <= length
},
number: (value) => {
return !isNaN(value) && !isNaN(parseFloat(value))
},
url: (value) => {
const urlRegex = /^https?:\/\/.+/
return urlRegex.test(value)
}
}
}
validate(data, rules) {
const errors = {}
for (const field in rules) {
const fieldRules = rules[field]
const value = data[field]
for (const rule of fieldRules) {
const { type, message, ...params } = rule
if (this.rules[type]) {
const isValid = this.rules[type](value, ...Object.values(params))
if (!isValid) {
if (!errors[field]) {
errors[field] = []
}
errors[field].push(message)
break // Stop at first error for this field
}
}
}
}
return {
isValid: Object.keys(errors).length === 0,
errors
}
}
}
export default new Validator()
Form Component with Validation
vue
<template>
<view class="form-container">
<view class="form-item">
<text class="label">Email *</text>
<input
v-model="formData.email"
type="text"
placeholder="Enter your email"
class="input"
:class="{ 'input-error': errors.email }"
/>
<text v-if="errors.email" class="error-text">{{ errors.email[0] }}</text>
</view>
<view class="form-item">
<text class="label">Password *</text>
<input
v-model="formData.password"
type="password"
placeholder="Enter your password"
class="input"
:class="{ 'input-error': errors.password }"
/>
<text v-if="errors.password" class="error-text">{{ errors.password[0] }}</text>
</view>
<view class="form-item">
<text class="label">Phone</text>
<input
v-model="formData.phone"
type="number"
placeholder="Enter your phone number"
class="input"
:class="{ 'input-error': errors.phone }"
/>
<text v-if="errors.phone" class="error-text">{{ errors.phone[0] }}</text>
</view>
<button class="submit-btn" @click="handleSubmit">Submit</button>
</view>
</template>
<script>
import validator from '@/utils/validator'
export default {
data() {
return {
formData: {
email: '',
password: '',
phone: ''
},
errors: {},
validationRules: {
email: [
{ type: 'required', message: 'Email is required' },
{ type: 'email', message: 'Please enter a valid email' }
],
password: [
{ type: 'required', message: 'Password is required' },
{ type: 'minLength', length: 6, message: 'Password must be at least 6 characters' }
],
phone: [
{ type: 'phone', message: 'Please enter a valid phone number' }
]
}
}
},
methods: {
handleSubmit() {
const validation = validator.validate(this.formData, this.validationRules)
if (validation.isValid) {
this.errors = {}
this.submitForm()
} else {
this.errors = validation.errors
uni.showToast({
title: 'Please fix the errors',
icon: 'none'
})
}
},
async submitForm() {
try {
// Submit form data
console.log('Submitting form:', this.formData)
uni.showToast({
title: 'Form submitted successfully',
icon: 'success'
})
} catch (error) {
console.error('Form submission error:', error)
}
}
}
}
</script>
<style scoped>
.form-container {
padding: 40rpx;
}
.form-item {
margin-bottom: 40rpx;
}
.label {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 16rpx;
}
.input {
width: 100%;
height: 80rpx;
padding: 0 20rpx;
border: 2rpx solid #ddd;
border-radius: 8rpx;
font-size: 28rpx;
box-sizing: border-box;
}
.input-error {
border-color: #dc3545;
}
.error-text {
color: #dc3545;
font-size: 24rpx;
margin-top: 8rpx;
}
.submit-btn {
width: 100%;
height: 80rpx;
background-color: #007bff;
color: white;
border: none;
border-radius: 8rpx;
font-size: 32rpx;
margin-top: 40rpx;
}
</style>
Image Handling
Image Upload Component
vue
<template>
<view class="image-upload">
<view class="image-list">
<view
v-for="(image, index) in imageList"
:key="index"
class="image-item"
>
<image :src="image.url" class="image" mode="aspectFill" />
<view class="image-actions">
<text class="action-btn" @click="previewImage(index)">Preview</text>
<text class="action-btn delete" @click="removeImage(index)">Delete</text>
</view>
</view>
<view
v-if="imageList.length < maxCount"
class="upload-btn"
@click="chooseImage"
>
<text class="upload-icon">+</text>
<text class="upload-text">Add Image</text>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'ImageUpload',
props: {
maxCount: {
type: Number,
default: 9
},
maxSize: {
type: Number,
default: 5 * 1024 * 1024 // 5MB
}
},
data() {
return {
imageList: []
}
},
methods: {
chooseImage() {
uni.chooseImage({
count: this.maxCount - this.imageList.length,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
this.handleImageSelect(res.tempFilePaths)
}
})
},
handleImageSelect(tempFilePaths) {
tempFilePaths.forEach(path => {
// Check file size
uni.getFileInfo({
filePath: path,
success: (res) => {
if (res.size > this.maxSize) {
uni.showToast({
title: 'Image too large',
icon: 'none'
})
return
}
this.imageList.push({
url: path,
path: path
})
// Upload image
this.uploadImage(path)
}
})
})
},
uploadImage(filePath) {
uni.uploadFile({
url: 'https://api.example.com/upload',
filePath: filePath,
name: 'file',
header: {
'Authorization': `Bearer ${uni.getStorageSync('token')}`
},
success: (res) => {
const data = JSON.parse(res.data)
if (data.code === 0) {
// Update image URL with server URL
const index = this.imageList.findIndex(img => img.path === filePath)
if (index !== -1) {
this.imageList[index].url = data.data.url
}
}
},
fail: (err) => {
console.error('Upload failed:', err)
uni.showToast({
title: 'Upload failed',
icon: 'none'
})
}
})
},
previewImage(index) {
const urls = this.imageList.map(img => img.url)
uni.previewImage({
urls: urls,
current: index
})
},
removeImage(index) {
uni.showModal({
title: 'Confirm',
content: 'Are you sure you want to delete this image?',
success: (res) => {
if (res.confirm) {
this.imageList.splice(index, 1)
}
}
})
}
}
}
</script>
<style scoped>
.image-upload {
padding: 20rpx;
}
.image-list {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
}
.image-item {
position: relative;
width: 200rpx;
height: 200rpx;
}
.image {
width: 100%;
height: 100%;
border-radius: 8rpx;
}
.image-actions {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: space-around;
padding: 10rpx;
border-radius: 0 0 8rpx 8rpx;
}
.action-btn {
color: white;
font-size: 24rpx;
}
.delete {
color: #ff4757;
}
.upload-btn {
width: 200rpx;
height: 200rpx;
border: 2rpx dashed #ddd;
border-radius: 8rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.upload-icon {
font-size: 60rpx;
color: #999;
}
.upload-text {
font-size: 24rpx;
color: #999;
margin-top: 10rpx;
}
</style>
List Components
Infinite Scroll List
vue
<template>
<view class="list-container">
<view
v-for="(item, index) in list"
:key="item.id"
class="list-item"
@click="handleItemClick(item)"
>
<image :src="item.avatar" class="avatar" />
<view class="content">
<text class="title">{{ item.title }}</text>
<text class="description">{{ item.description }}</text>
<text class="time">{{ formatTime(item.createTime) }}</text>
</view>
</view>
<view v-if="loading" class="loading">
<text>Loading...</text>
</view>
<view v-if="!hasMore && list.length > 0" class="no-more">
<text>No more data</text>
</view>
<view v-if="list.length === 0 && !loading" class="empty">
<text>No data available</text>
</view>
</view>
</template>
<script>
export default {
data() {
return {
list: [],
loading: false,
hasMore: true,
page: 1,
pageSize: 20
}
},
onLoad() {
this.loadData()
},
onReachBottom() {
if (this.hasMore && !this.loading) {
this.loadMore()
}
},
onPullDownRefresh() {
this.refresh()
},
methods: {
async loadData() {
try {
this.loading = true
const response = await this.$api.get('/list', {
page: this.page,
pageSize: this.pageSize
})
if (this.page === 1) {
this.list = response.data
} else {
this.list.push(...response.data)
}
this.hasMore = response.data.length === this.pageSize
} catch (error) {
console.error('Load data error:', error)
} finally {
this.loading = false
uni.stopPullDownRefresh()
}
},
loadMore() {
this.page++
this.loadData()
},
refresh() {
this.page = 1
this.hasMore = true
this.loadData()
},
handleItemClick(item) {
uni.navigateTo({
url: `/pages/detail/detail?id=${item.id}`
})
},
formatTime(timestamp) {
const date = new Date(timestamp)
const now = new Date()
const diff = now - date
if (diff < 60000) {
return 'Just now'
} else if (diff < 3600000) {
return `${Math.floor(diff / 60000)} minutes ago`
} else if (diff < 86400000) {
return `${Math.floor(diff / 3600000)} hours ago`
} else {
return date.toLocaleDateString()
}
}
}
}
</script>
<style scoped>
.list-container {
padding: 20rpx;
}
.list-item {
display: flex;
padding: 30rpx;
background: white;
border-radius: 16rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
}
.avatar {
width: 80rpx;
height: 80rpx;
border-radius: 40rpx;
margin-right: 20rpx;
}
.content {
flex: 1;
display: flex;
flex-direction: column;
}
.title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 10rpx;
}
.description {
font-size: 28rpx;
color: #666;
margin-bottom: 10rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.time {
font-size: 24rpx;
color: #999;
}
.loading, .no-more, .empty {
text-align: center;
padding: 40rpx;
color: #999;
font-size: 28rpx;
}
</style>
Utility Functions
Common Utilities
javascript
// utils/common.js
export const utils = {
// Debounce function
debounce(func, wait) {
let timeout
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout)
func(...args)
}
clearTimeout(timeout)
timeout = setTimeout(later, wait)
}
},
// Throttle function
throttle(func, limit) {
let inThrottle
return function() {
const args = arguments
const context = this
if (!inThrottle) {
func.apply(context, args)
inThrottle = true
setTimeout(() => inThrottle = false, limit)
}
}
},
// Deep clone object
deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj
if (obj instanceof Date) return new Date(obj.getTime())
if (obj instanceof Array) return obj.map(item => this.deepClone(item))
if (typeof obj === 'object') {
const clonedObj = {}
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clonedObj[key] = this.deepClone(obj[key])
}
}
return clonedObj
}
},
// Format file size
formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes'
const k = 1024
const sizes = ['Bytes', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
},
// Generate UUID
generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0
const v = c === 'x' ? r : (r & 0x3 | 0x8)
return v.toString(16)
})
},
// Check if object is empty
isEmpty(obj) {
if (obj == null) return true
if (Array.isArray(obj) || typeof obj === 'string') return obj.length === 0
return Object.keys(obj).length === 0
},
// Format currency
formatCurrency(amount, currency = '$') {
return currency + parseFloat(amount).toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,')
}
}
Conclusion
These code samples provide practical examples for common uni-app development scenarios. You can use them as starting points and customize them according to your specific requirements. Remember to:
- Test code samples thoroughly in your environment
- Adapt the styling to match your app's design
- Add proper error handling for production use
- Consider performance implications for large datasets
- Follow uni-app best practices and guidelines
For more advanced examples and patterns, refer to the official uni-app documentation and community resources.