图片上传七牛云成功
This commit is contained in:
parent
523d86d6e9
commit
6e73e36644
189
README_IMAGE_UPLOAD.md
Normal file
189
README_IMAGE_UPLOAD.md
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
# 单图上传功能说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
本项目提供了完整的单图上传功能,支持选择图片、上传到七牛云、返回图片URL。
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
├── api/upload.js # 上传相关API
|
||||
├── pages/image-upload/ # 上传页面
|
||||
│ └── image-upload.vue
|
||||
├── pages/image-upload-demo/ # 演示页面
|
||||
│ └── image-upload-demo.vue
|
||||
└── examples/ # 使用示例
|
||||
└── single-image-upload-usage.vue
|
||||
```
|
||||
|
||||
## 核心功能
|
||||
|
||||
### 1. API接口 (`api/upload.js`)
|
||||
|
||||
#### 获取七牛云上传Token
|
||||
```javascript
|
||||
import { getQiniuUploadToken } from '@/api/upload.js'
|
||||
|
||||
const res = await getQiniuUploadToken()
|
||||
if (res.code === 200) {
|
||||
const token = res.data?.token || res.data?.uploadToken || res.token || res.data
|
||||
}
|
||||
```
|
||||
|
||||
#### 上传图片到七牛云
|
||||
```javascript
|
||||
import { uploadToQiniu } from '@/api/upload.js'
|
||||
|
||||
const result = await uploadToQiniu(filePath, token, key)
|
||||
const imageUrl = `https://api.ccttiot.com/${result.key}`
|
||||
```
|
||||
|
||||
### 2. 上传页面 (`pages/image-upload/image-upload.vue`)
|
||||
|
||||
完整的图片上传页面,包含:
|
||||
- 图片选择功能
|
||||
- 预览功能
|
||||
- 上传进度显示
|
||||
- 错误处理
|
||||
- 结果展示
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 方法一:跳转到上传页面
|
||||
|
||||
```javascript
|
||||
// 跳转到上传页面
|
||||
uni.navigateTo({
|
||||
url: '/pages/image-upload/image-upload'
|
||||
})
|
||||
|
||||
// 监听上传成功事件
|
||||
uni.$on('image-upload-success', (imageUrl) => {
|
||||
console.log('上传成功:', imageUrl)
|
||||
})
|
||||
```
|
||||
|
||||
### 方法二:内嵌上传功能
|
||||
|
||||
```javascript
|
||||
import { getQiniuUploadToken, uploadToQiniu } from '@/api/upload.js'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
selectedImage: '',
|
||||
qiniuToken: '',
|
||||
uploading: false
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 选择图片
|
||||
chooseImage() {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
success: (res) => {
|
||||
this.selectedImage = res.tempFilePaths[0]
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 上传图片
|
||||
async uploadImage() {
|
||||
if (!this.selectedImage) return
|
||||
|
||||
this.uploading = true
|
||||
|
||||
try {
|
||||
// 获取token
|
||||
if (!this.qiniuToken) {
|
||||
const res = await getQiniuUploadToken()
|
||||
this.qiniuToken = res.data?.token || res.data?.uploadToken || res.token || res.data
|
||||
}
|
||||
|
||||
// 生成文件名
|
||||
const key = `uploads/${Date.now()}_${Math.random().toString(36).slice(2)}.jpg`
|
||||
|
||||
// 上传
|
||||
const result = await uploadToQiniu(this.selectedImage, this.qiniuToken, key)
|
||||
const imageUrl = `https://api.ccttiot.com/${result.key}`
|
||||
|
||||
console.log('上传成功:', imageUrl)
|
||||
|
||||
} catch (error) {
|
||||
console.error('上传失败:', error)
|
||||
} finally {
|
||||
this.uploading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 页面配置
|
||||
|
||||
确保在 `pages.json` 中注册了相关页面:
|
||||
|
||||
```json
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/image-upload/image-upload",
|
||||
"style": {
|
||||
"navigationBarTitleText": "图片上传",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/image-upload-demo/image-upload-demo",
|
||||
"style": {
|
||||
"navigationBarTitleText": "图片上传演示",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 功能特点
|
||||
|
||||
### ✅ 已实现功能
|
||||
- [x] 图片选择和预览
|
||||
- [x] 七牛云上传
|
||||
- [x] 上传进度显示
|
||||
- [x] 错误处理和提示
|
||||
- [x] 结果URL展示
|
||||
- [x] URL复制功能
|
||||
- [x] 多种使用方式
|
||||
- [x] 完整的示例代码
|
||||
|
||||
### 🔧 技术特性
|
||||
- 支持相册和相机选择
|
||||
- 自动获取七牛云上传Token
|
||||
- 智能Token字段解析
|
||||
- 唯一文件名生成
|
||||
- 完整的错误处理
|
||||
- 响应式UI设计
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **Token获取**:确保后端API `/common/qiniu/uploadInfo` 正常工作
|
||||
2. **域名配置**:七牛云上传域名已配置为 `https://up-z2.qiniup.com`
|
||||
3. **图片域名**:图片访问域名配置为 `https://api.ccttiot.com`
|
||||
4. **文件格式**:支持 JPG、PNG 等常见图片格式
|
||||
5. **文件大小**:建议图片大小不超过 5MB
|
||||
|
||||
## 调试
|
||||
|
||||
如果遇到上传问题,可以:
|
||||
|
||||
1. 检查控制台日志
|
||||
2. 验证Token获取是否成功
|
||||
3. 确认网络连接正常
|
||||
4. 检查七牛云配置是否正确
|
||||
|
||||
## 示例页面
|
||||
|
||||
访问以下页面查看完整示例:
|
||||
- `/pages/image-upload-demo/image-upload-demo` - 演示页面
|
||||
- `/examples/single-image-upload-usage.vue` - 使用示例
|
||||
44
api/upload.js
Normal file
44
api/upload.js
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import request from '@/utils/request.js'
|
||||
|
||||
/**
|
||||
* 获取七牛云上传token
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function getQiniuUploadToken() {
|
||||
return request({
|
||||
url: '/common/qiniuToken',
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传图片到七牛云
|
||||
* @param {string} filePath - 文件路径
|
||||
* @param {string} token - 七牛云上传token
|
||||
* @param {string} key - 文件key
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function uploadToQiniu(filePath, token, key) {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.uploadFile({
|
||||
url: 'https://up-z2.qiniup.com',
|
||||
filePath: filePath,
|
||||
name: 'file',
|
||||
formData: {
|
||||
token: token,
|
||||
key: key,
|
||||
},
|
||||
success: (res) => {
|
||||
try {
|
||||
const data = JSON.parse(res.data)
|
||||
resolve(data)
|
||||
} catch (error) {
|
||||
reject(new Error('响应数据解析失败'))
|
||||
}
|
||||
},
|
||||
fail: (error) => {
|
||||
reject(error)
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
|
||||
318
examples/single-image-upload-usage.vue
Normal file
318
examples/single-image-upload-usage.vue
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
<template>
|
||||
<view class="usage-example">
|
||||
<view class="example-header">
|
||||
<text class="title">单图上传使用示例</text>
|
||||
<text class="desc">展示如何在页面中集成图片上传功能</text>
|
||||
</view>
|
||||
|
||||
<!-- 方法一:跳转到上传页面 -->
|
||||
<view class="method-section">
|
||||
<text class="method-title">方法一:跳转到上传页面</text>
|
||||
<text class="method-desc">跳转到专门的上传页面,上传完成后返回</text>
|
||||
|
||||
<button class="method-btn" @click="method1">跳转上传</button>
|
||||
|
||||
<view v-if="method1Result" class="result-box">
|
||||
<text class="result-label">上传结果:</text>
|
||||
<image :src="method1Result" mode="widthFix" class="result-image"></image>
|
||||
<text class="result-url">{{ method1Result }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 方法二:内嵌上传组件 -->
|
||||
<view class="method-section">
|
||||
<text class="method-title">方法二:内嵌上传组件</text>
|
||||
<text class="method-desc">在当前页面直接使用上传组件</text>
|
||||
|
||||
<view class="inline-upload">
|
||||
<view class="upload-area" @click="chooseImage">
|
||||
<view v-if="!selectedImage" class="upload-placeholder">
|
||||
<text>点击选择图片</text>
|
||||
</view>
|
||||
<image v-else :src="selectedImage" mode="aspectFit" class="preview-image"></image>
|
||||
</view>
|
||||
|
||||
<button
|
||||
class="upload-btn"
|
||||
@click="uploadSelectedImage"
|
||||
:disabled="!selectedImage || uploading"
|
||||
>
|
||||
{{ uploading ? '上传中...' : '上传' }}
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view v-if="method2Result" class="result-box">
|
||||
<text class="result-label">上传结果:</text>
|
||||
<image :src="method2Result" mode="widthFix" class="result-image"></image>
|
||||
<text class="result-url">{{ method2Result }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getQiniuUploadToken, uploadToQiniu } from '@/api/upload.js'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
// 方法一结果
|
||||
method1Result: '',
|
||||
|
||||
// 方法二数据
|
||||
selectedImage: '',
|
||||
uploading: false,
|
||||
method2Result: '',
|
||||
qiniuToken: '',
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
// 监听上传成功事件
|
||||
uni.$on('image-upload-success', this.onImageUploadSuccess)
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
// 移除事件监听
|
||||
uni.$off('image-upload-success', this.onImageUploadSuccess)
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 方法一:跳转到上传页面
|
||||
method1() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/image-upload/image-upload'
|
||||
})
|
||||
},
|
||||
|
||||
// 监听上传成功事件
|
||||
onImageUploadSuccess(imageUrl) {
|
||||
this.method1Result = imageUrl
|
||||
uni.showToast({
|
||||
title: '图片上传成功',
|
||||
icon: 'success'
|
||||
})
|
||||
},
|
||||
|
||||
// 方法二:选择图片
|
||||
chooseImage() {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: (res) => {
|
||||
this.selectedImage = res.tempFilePaths[0]
|
||||
this.method2Result = ''
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 方法二:上传选中的图片
|
||||
async uploadSelectedImage() {
|
||||
if (!this.selectedImage) {
|
||||
uni.showToast({
|
||||
title: '请先选择图片',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.uploading = true
|
||||
|
||||
try {
|
||||
// 获取token
|
||||
if (!this.qiniuToken) {
|
||||
await this.getQiniuToken()
|
||||
}
|
||||
|
||||
// 生成文件名
|
||||
const key = `examples/${Date.now()}_${Math.random().toString(36).slice(2)}.jpg`
|
||||
|
||||
// 上传
|
||||
const result = await uploadToQiniu(this.selectedImage, this.qiniuToken, key)
|
||||
this.method2Result = `https://api.ccttiot.com/${result.key}`
|
||||
|
||||
uni.showToast({
|
||||
title: '上传成功',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('上传失败:', error)
|
||||
uni.showToast({
|
||||
title: '上传失败',
|
||||
icon: 'none'
|
||||
})
|
||||
} finally {
|
||||
this.uploading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 获取七牛云token
|
||||
async getQiniuToken() {
|
||||
try {
|
||||
const res = await getQiniuUploadToken()
|
||||
if (res.code === 200) {
|
||||
const token = res.data?.token || res.data?.uploadToken || res.token || res.data
|
||||
if (token) {
|
||||
this.qiniuToken = token
|
||||
} else {
|
||||
throw new Error('Token字段不存在')
|
||||
}
|
||||
} else {
|
||||
throw new Error(res.msg || '获取Token失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取Token失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.usage-example {
|
||||
padding: 30rpx;
|
||||
background: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
|
||||
.example-header {
|
||||
text-align: center;
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
.title {
|
||||
display: block;
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.desc {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.method-section {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.method-title {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.method-desc {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.method-btn {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
background: #007aff;
|
||||
color: #fff;
|
||||
border-radius: 12rpx;
|
||||
font-size: 28rpx;
|
||||
border: none;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
&:active {
|
||||
background: #0056cc;
|
||||
}
|
||||
}
|
||||
|
||||
.inline-upload {
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.upload-area {
|
||||
width: 100%;
|
||||
height: 200rpx;
|
||||
background: #f8f8f8;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 20rpx;
|
||||
border: 2rpx dashed #ddd;
|
||||
|
||||
.upload-placeholder {
|
||||
text-align: center;
|
||||
|
||||
text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-btn {
|
||||
width: 100%;
|
||||
height: 70rpx;
|
||||
line-height: 70rpx;
|
||||
background: #52c41a;
|
||||
color: #fff;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
border: none;
|
||||
|
||||
&:active {
|
||||
background: #389e0d;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background: #ccc;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.result-box {
|
||||
border-top: 1rpx solid #eee;
|
||||
padding-top: 20rpx;
|
||||
|
||||
.result-label {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.result-image {
|
||||
width: 100%;
|
||||
border-radius: 8rpx;
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.result-url {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: #007aff;
|
||||
word-break: break-all;
|
||||
line-height: 1.4;
|
||||
background: #f8f8f8;
|
||||
padding: 15rpx;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
69
pages.json
69
pages.json
|
|
@ -1,17 +1,17 @@
|
|||
{
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
{
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "设备租赁",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/login",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/login",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/lease/lease",
|
||||
|
|
@ -80,22 +80,33 @@
|
|||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path" : "pages/announcementList/announcementList",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText": "公告列表"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path" : "pages/announcementList/announcementDetail",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText" : "公告详细"
|
||||
}
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"path": "pages/announcementList/announcementList",
|
||||
"style": {
|
||||
"navigationBarTitleText": "公告列表"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/announcementList/announcementDetail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "公告详细"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/image-upload/image-upload",
|
||||
"style": {
|
||||
"navigationBarTitleText": "图片上传"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/image-upload-demo/image-upload-demo",
|
||||
"style": {
|
||||
"navigationBarTitleText": "图片上传演示",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tabBar": {
|
||||
"color": "#999999",
|
||||
"selectedColor": "#ff6b6b",
|
||||
|
|
@ -121,11 +132,11 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "设备租赁",
|
||||
"navigationBarBackgroundColor": "#fff"
|
||||
},
|
||||
},
|
||||
"uniIdRouter": {},
|
||||
"mp-weixin": {
|
||||
"requiredPrivateInfos": [
|
||||
|
|
|
|||
174
pages/image-upload-demo/image-upload-demo.vue
Normal file
174
pages/image-upload-demo/image-upload-demo.vue
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
<template>
|
||||
<view class="demo-page">
|
||||
<u-navbar
|
||||
:is-back="true"
|
||||
title="图片上传演示"
|
||||
title-color="#000"
|
||||
:border-bottom="false"
|
||||
:background="true"
|
||||
></u-navbar>
|
||||
|
||||
<view class="content">
|
||||
<view class="demo-section">
|
||||
<text class="section-title">图片上传功能演示</text>
|
||||
<text class="section-desc">点击下方按钮跳转到图片上传页面</text>
|
||||
</view>
|
||||
|
||||
<view class="button-section">
|
||||
<button class="demo-btn" @click="goToUpload">开始上传图片</button>
|
||||
</view>
|
||||
|
||||
<view v-if="uploadedImageUrl" class="result-section">
|
||||
<text class="result-title">上传结果</text>
|
||||
<image
|
||||
:src="uploadedImageUrl"
|
||||
mode="widthFix"
|
||||
class="result-image"
|
||||
></image>
|
||||
<text class="result-url">{{ uploadedImageUrl }}</text>
|
||||
<button class="copy-btn" @click="copyImageUrl">复制图片URL</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
uploadedImageUrl: '', // 上传成功的图片URL
|
||||
}
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
// 如果从上传页面返回,获取上传结果
|
||||
if (options.imageUrl) {
|
||||
this.uploadedImageUrl = decodeURIComponent(options.imageUrl)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 跳转到上传页面
|
||||
goToUpload() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/image-upload/image-upload'
|
||||
})
|
||||
},
|
||||
|
||||
// 复制图片URL
|
||||
copyImageUrl() {
|
||||
if (this.uploadedImageUrl) {
|
||||
uni.setClipboardData({
|
||||
data: this.uploadedImageUrl,
|
||||
success: () => {
|
||||
uni.showToast({
|
||||
title: '图片URL已复制',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.demo-page {
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
|
||||
.content {
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.demo-section {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
text-align: center;
|
||||
|
||||
.section-title {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.section-desc {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
.button-section {
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.demo-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
background: #007aff;
|
||||
color: #fff;
|
||||
border-radius: 12rpx;
|
||||
font-size: 32rpx;
|
||||
border: none;
|
||||
|
||||
&:active {
|
||||
background: #0056cc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.result-section {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
|
||||
.result-title {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.result-image {
|
||||
width: 100%;
|
||||
border-radius: 8rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.result-url {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #007aff;
|
||||
word-break: break-all;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 20rpx;
|
||||
background: #f8f8f8;
|
||||
padding: 20rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
width: 100%;
|
||||
height: 70rpx;
|
||||
line-height: 70rpx;
|
||||
background: #f0f0f0;
|
||||
color: #333;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
border: none;
|
||||
|
||||
&:active {
|
||||
background: #e0e0e0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
339
pages/image-upload/image-upload.vue
Normal file
339
pages/image-upload/image-upload.vue
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
<template>
|
||||
<view class="image-upload-page">
|
||||
<view class="content">
|
||||
<!-- 上传区域 -->
|
||||
<view class="upload-area" @click="chooseImage">
|
||||
<view v-if="!imageUrl" class="upload-placeholder">
|
||||
<u-icon color="#ccc" name="camera" size="60"></u-icon>
|
||||
<text class="upload-text">点击选择图片</text>
|
||||
<text class="upload-hint">支持 JPG、PNG 格式</text>
|
||||
</view>
|
||||
<image v-else :src="imageUrl" class="preview-image" mode="aspectFit"></image>
|
||||
</view>
|
||||
|
||||
<!-- 上传按钮 -->
|
||||
<button
|
||||
:disabled="!selectedImagePath || uploading"
|
||||
:loading="uploading"
|
||||
class="upload-btn"
|
||||
@click="uploadImage"
|
||||
>
|
||||
{{ uploading ? '上传中...' : '上传图片' }}
|
||||
</button>
|
||||
|
||||
<!-- 结果展示 -->
|
||||
<view v-if="uploadResult" class="result-section">
|
||||
<view class="result-item">
|
||||
<text class="result-label">上传状态:</text>
|
||||
<text class="result-value success">成功</text>
|
||||
</view>
|
||||
<view class="result-item">
|
||||
<text class="result-label">图片URL:</text>
|
||||
<text class="result-url" @click="copyUrl">{{ uploadResult }}</text>
|
||||
</view>
|
||||
<button class="copy-btn" @click="copyUrl">复制URL</button>
|
||||
</view>
|
||||
|
||||
<!-- 错误信息 -->
|
||||
<view v-if="errorMessage" class="error-section">
|
||||
<text class="error-text">{{ errorMessage }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getQiniuUploadToken, uploadToQiniu } from '@/api/upload.js'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
selectedImagePath: '', // 选择的图片路径
|
||||
imageUrl: '', // 预览图片URL
|
||||
uploadResult: '', // 上传成功后的完整URL
|
||||
uploading: false, // 上传状态
|
||||
errorMessage: '', // 错误信息
|
||||
qiniuToken: '', // 七牛云上传token
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.getQiniuToken()
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 选择图片
|
||||
chooseImage() {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: res => {
|
||||
this.selectedImagePath = res.tempFilePaths[0]
|
||||
this.imageUrl = res.tempFilePaths[0]
|
||||
this.uploadResult = ''
|
||||
this.errorMessage = ''
|
||||
},
|
||||
fail: err => {
|
||||
console.error('选择图片失败:', err)
|
||||
uni.showToast({
|
||||
title: '选择图片失败',
|
||||
icon: 'none',
|
||||
})
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
// 上传图片
|
||||
async uploadImage() {
|
||||
if (!this.selectedImagePath) {
|
||||
uni.showToast({
|
||||
title: '请先选择图片',
|
||||
icon: 'none',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.uploading = true
|
||||
this.errorMessage = ''
|
||||
|
||||
try {
|
||||
// 确保有token
|
||||
if (!this.qiniuToken) {
|
||||
await this.getQiniuToken()
|
||||
}
|
||||
|
||||
// 生成唯一文件名
|
||||
const key = `uploads/${Date.now()}_${Math.random().toString(36).slice(2)}.jpg`
|
||||
|
||||
// 上传到七牛云
|
||||
const result = await uploadToQiniu(this.selectedImagePath, this.qiniuToken, key)
|
||||
|
||||
// 构建完整的图片URL
|
||||
this.uploadResult = `https://api.ccttiot.com/${result.key}`
|
||||
|
||||
uni.showToast({
|
||||
title: '上传成功',
|
||||
icon: 'success',
|
||||
})
|
||||
|
||||
// 返回上一页并传递图片URL
|
||||
const pages = getCurrentPages()
|
||||
const prevPage = pages[pages.length - 2]
|
||||
|
||||
if (prevPage) {
|
||||
// 如果上一页存在,可以通过事件总线或其他方式传递数据
|
||||
// 这里我们通过URL参数的方式返回
|
||||
uni.navigateBack({
|
||||
success: () => {
|
||||
// 延迟执行,确保页面已经返回
|
||||
setTimeout(() => {
|
||||
uni.$emit('image-upload-success', this.uploadResult)
|
||||
}, 100)
|
||||
},
|
||||
})
|
||||
} else {
|
||||
// 如果没有上一页,直接返回URL参数
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
success: () => {
|
||||
// 通过URL参数传递结果
|
||||
const currentPage = getCurrentPages()[getCurrentPages().length - 1]
|
||||
if (currentPage && currentPage.onLoad) {
|
||||
currentPage.onLoad({
|
||||
imageUrl: encodeURIComponent(this.uploadResult),
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('上传失败:', error)
|
||||
this.errorMessage = error.message || '上传失败'
|
||||
uni.showToast({
|
||||
title: '上传失败',
|
||||
icon: 'none',
|
||||
})
|
||||
} finally {
|
||||
this.uploading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 获取七牛云token
|
||||
async getQiniuToken() {
|
||||
try {
|
||||
const res = await getQiniuUploadToken()
|
||||
console.log('Token响应:', res)
|
||||
|
||||
if (res.code === 200) {
|
||||
// 尝试不同的可能字段名
|
||||
const token = res.data?.token || res.data?.uploadToken || res.token || res.data
|
||||
if (token) {
|
||||
this.qiniuToken = token
|
||||
console.log('Token获取成功:', token.substring(0, 20) + '...')
|
||||
} else {
|
||||
throw new Error('Token字段不存在')
|
||||
}
|
||||
} else {
|
||||
throw new Error(res.msg || '获取Token失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取Token失败:', error)
|
||||
this.errorMessage = `获取上传凭证失败: ${error.message}`
|
||||
uni.showToast({
|
||||
title: '获取上传凭证失败',
|
||||
icon: 'none',
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 复制URL
|
||||
copyUrl() {
|
||||
if (this.uploadResult) {
|
||||
uni.setClipboardData({
|
||||
data: this.uploadResult,
|
||||
success: () => {
|
||||
uni.showToast({
|
||||
title: 'URL已复制',
|
||||
icon: 'success',
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.image-upload-page {
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
|
||||
.content {
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.upload-area {
|
||||
width: 100%;
|
||||
height: 400rpx;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 30rpx;
|
||||
border: 2rpx dashed #ddd;
|
||||
|
||||
.upload-placeholder {
|
||||
text-align: center;
|
||||
|
||||
.upload-text {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
margin-top: 20rpx;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.upload-hint {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
background: #007aff;
|
||||
color: #fff;
|
||||
border-radius: 12rpx;
|
||||
font-size: 32rpx;
|
||||
border: none;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
&:active {
|
||||
background: #0056cc;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background: #ccc;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.result-section {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.result-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.result-label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
margin-right: 20rpx;
|
||||
min-width: 120rpx;
|
||||
}
|
||||
|
||||
.result-value {
|
||||
font-size: 28rpx;
|
||||
|
||||
&.success {
|
||||
color: #52c41a;
|
||||
}
|
||||
}
|
||||
|
||||
.result-url {
|
||||
flex: 1;
|
||||
font-size: 24rpx;
|
||||
color: #007aff;
|
||||
word-break: break-all;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
width: 100%;
|
||||
height: 70rpx;
|
||||
line-height: 70rpx;
|
||||
background: #f0f0f0;
|
||||
color: #333;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
border: none;
|
||||
|
||||
&:active {
|
||||
background: #e0e0e0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.error-section {
|
||||
background: #fff2f0;
|
||||
border: 1rpx solid #ffccc7;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
|
||||
.error-text {
|
||||
font-size: 26rpx;
|
||||
color: #ff4d4f;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue
Block a user