Skip to content

Commit

Permalink
feat(SPA): 动态话题 (cherry-pick from slimkit/master)
Browse files Browse the repository at this point in the history
issue #467
  • Loading branch information
mutoe committed Jan 4, 2019
1 parent 99a552b commit 7524508
Show file tree
Hide file tree
Showing 23 changed files with 1,466 additions and 226 deletions.
71 changes: 32 additions & 39 deletions resources/spa/CONTRIBUTING.md
Expand Up @@ -334,45 +334,6 @@ export default {

隐藏 dialog,缓慢下移淡出的动画

### 图片上传组件 ImagePoster

`@/components/ImagePoster.vue`

用于各页面中图片上传相关内容,使用方法详见 `@/pages/profile/Certificate.vue`

``` vue
<template>
<image-poster @uploaded="uploaded">
<span>点击上传反面身份证照片</span>
</image-poster>
</template>
<script>
import ImagePoster from "@/components/ImagePoster.vue";
export default {
components: { ImagePoster }
methods: {
uploaded(poster) {
console.log(poster);
}
}
}
</script>
```

#### `Slot`

含有一个匿名 slot,支持任何 html 标签,显示在上传组件的 icon 下方

#### `Event`

##### `uploaded`

图片上传成功后的回调方法,接受一个参数,值为已上传的图片信息。

##### `error`

图片上传失败的回调方法

### banner 轮播广告位 BannerAd

`@/components/advertisement/BannerAd.vue`
Expand Down Expand Up @@ -441,6 +402,38 @@ export default {

用于获取对应页面广告具体数据


### 文件上传 ImageUploader

`@/components/common/ImageUploader.vue`

用于新版本、老版本兼容的文件上传组件,参考话题封面上传

#### `Props`

##### `type` {string} [id]

- `id`
- `storage`
- `blob`
- `url`

##### `value` {*}

该属性的指取决于 `type` 的值
-`id` 时是老版本返回的文件 id
-`storage` 时是新版本返回的 file node 节点
-`blob` 时是临时的blob对象
-`url` 时是临时的 url 地址

#### `Events`

##### `@update:src`

该属性返回的是一个图片临时地址,用于前端展示

`@update:src="src = $event"`

## 表单组件 FormItem

表单组件用于快速构建样式和交互方式统一的表单项
Expand Down
2 changes: 1 addition & 1 deletion resources/spa/package.json
@@ -1,7 +1,7 @@
{
"name": "@slimkit/plus-small-screen-client",
"private": true,
"version": "4.2.3",
"version": "4.3.0",
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
Expand Down
36 changes: 36 additions & 0 deletions resources/spa/src/api/topic.js
@@ -0,0 +1,36 @@
import api from './api'

/**
* 获取话题列表
*
* @author mutoe <mutoe@foxmail.com>
* @export
* @param {Object} params
* @param {string} [params.q] 搜索关键字
* @param {number} [params.limit=15]
* @param {string} [params.direction=desc]
* @param {number} [params.index=0]
* @param {string} [params.only] 是否热门 'hot'
* @returns
*/
export function getTopicList (params) {
if (params.q === '') return Promise.resolve({ data: [] })
const url = '/feed/topics'
return api.get(url, { params, validateStatus: s => s === 200 })
}

/**
* 创建话题
*
* @author mutoe <mutoe@foxmail.com>
* @export
* @param {Object} data
* @param {string} data.name 话题名称
* @param {string} [data.desc] 描述
* @param {string} [data.logo] file node 节点
* @returns
*/
export function createTopic (data) {
const url = '/feed/topics'
return api.post(url, data, { validateStatus: s => s === 201 })
}
166 changes: 166 additions & 0 deletions resources/spa/src/components/common/ImageUploader.vue
@@ -0,0 +1,166 @@
<template>
<div class="c-image-uploader">
<input
ref="imagefile"
type="file"
class="hidden"
accept="image/jpeg,image/webp,image/jpg,image/png,image/bmp"
@change="selectPhoto"
>
</div>
</template>

<script>
import { hashFile } from '@/util/SendImage.js'
import { baseURL } from '@/api'
import * as uploadApi from '@/api/upload.js'
import { getFileUrl } from '@/util'
import getFirstFrameOfGif from '@/util/getFirstFrameOfGif.js'
/**
* Canvas toBlob
*/
if (!HTMLCanvasElement.prototype.toBlob) {
Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
value: function (callback, type, quality) {
const binStr = atob(this.toDataURL(type, quality).split(',')[1])
const len = binStr.length
const arr = new Uint8Array(len)
for (var i = 0; i < len; i++) {
arr[i] = binStr.charCodeAt(i)
}
callback(new Blob([arr], { type: type || 'image/png' }))
},
})
}
export default {
name: 'ImageUploader',
props: {
/**
* 文件类型
* @param {string} type enum{id: FileID, blob: Blob}
*/
type: {
type: String,
default: 'id',
validator (type) {
return ['blob', 'id', 'url', 'storage'].includes(type)
},
},
value: { type: null, default: null },
},
data () {
return {
avatarBlob: null,
}
},
computed: {
avatar () {
if (!this.value) return null
if (typeof this.value === 'string' && this.value.match(/^https?:/)) { return this.value }
switch (this.type) {
case 'id':
return `${baseURL}/files/${this.value}`
case 'blob':
return getFileUrl(this.value)
case 'storage':
// 获取初始头像资源
if (typeof this.value !== 'string') return this.value.url || null
// 否则获取修改过后的 blob 对象资源
return getFileUrl(this.avatarBlob)
default:
return null
}
},
filename () {
return this.$refs.imagefile.files[0].name
},
},
watch: {
avatar (src) {
this.$emit('update:src', src)
},
},
methods: {
select () {
if (this.readonly) return
this.$refs.imagefile.click()
},
async selectPhoto (e) {
let files = e.target.files || e.dataTransfer.files
if (!files.length) return
const cropperURL = await getFirstFrameOfGif(files[0])
this.$ImgCropper.show({
url: cropperURL,
round: false,
onCancel: () => {
this.$refs.imagefile.value = null
},
onOk: screenCanvas => {
screenCanvas.toBlob(async blob => {
this.uploadBlob(blob)
this.$refs.imagefile.value = null
}, 'image/png')
},
})
},
async uploadBlob (blob) {
if (this.type === 'id') {
// 如果需要得到服务器文件接口返回的 ID
const formData = new FormData()
formData.append('file', blob)
const id = await this.$store.dispatch('uploadFile', formData)
this.$Message.success('头像上传成功')
this.$emit('input', id)
} else if (this.type === 'blob') {
// 如果需要 Blob 对象
this.$emit('input', blob)
} else if (this.type === 'storage') {
// 如果需要新文件存储方式上传
this.avatarBlob = blob
const file = new File([blob], this.filename, {
type: blob.type,
lastModified: new Date(),
})
const hash = await hashFile(file)
const params = {
filename: this.filename,
hash,
size: blob.size,
mime_type: blob.type || 'image/png',
storage: { channel: 'public' },
}
const result = await uploadApi.createUploadTask(params)
uploadApi
.uploadImage({
method: result.method,
url: result.uri,
headers: result.headers,
blob,
})
.then(data => {
this.$emit('input', data.node)
})
.catch(() => {
this.$Message.error('文件上传失败,请检查文件系统配置')
})
}
},
},
}
</script>

<style lang="less" scoped>
.c-image-uploader {
.hidden {
display: none;
}
}
</style>
10 changes: 2 additions & 8 deletions resources/spa/src/components/common/SearchBar.vue
@@ -1,9 +1,6 @@
<template>
<header class="c-search-bar">
<form
class="input-wrap"
onsubmit="return false"
>
<form class="input-wrap" onsubmit="return false">
<svg class="m-icon-svg m-svg-small"><use xlink:href="#icon-search" /></svg>
<input
:value="value"
Expand All @@ -13,10 +10,7 @@
>
</form>

<a
class="btn-cancel"
@click.prevent.stop="onBackClick"
>
<a class="btn-cancel" @click.prevent.stop="onBackClick">
取消
</a>
</header>
Expand Down

0 comments on commit 7524508

Please sign in to comment.