提交 7df325a6 authored 作者: 王鹏飞's avatar 王鹏飞

chore: update

上级 080c2348
差异被折叠。
......@@ -14,14 +14,16 @@
"cert": "node ./cert.js"
},
"dependencies": {
"@vueuse/core": "^9.6.0",
"@vueuse/core": "^9.7.0",
"axios": "^1.2.1",
"blueimp-md5": "^2.19.0",
"element-plus": "^2.2.26",
"element-plus": "^2.2.27",
"lodash-es": "^4.17.21",
"pinia": "^2.0.28",
"qrcode.vue": "^3.3.3",
"qs": "^6.11.0",
"vue": "^3.2.45",
"vue-qrcode": "^2.2.0",
"vue-router": "^4.1.6"
},
"devDependencies": {
......@@ -34,12 +36,12 @@
"@vue/tsconfig": "^0.1.3",
"ali-oss": "^6.17.1",
"chalk": "^5.2.0",
"eslint": "^8.29.0",
"eslint": "^8.30.0",
"eslint-plugin-vue": "^9.8.0",
"sass": "^1.56.2",
"sass": "^1.57.0",
"typescript": "~4.9.4",
"unplugin-auto-import": "^0.12.1",
"vite": "^4.0.1",
"vue-tsc": "^1.0.13"
"vite": "^4.0.2",
"vue-tsc": "^1.0.14"
}
}
<script lang="ts" setup>
import type { UploadProps, UploadUserFile } from 'element-plus'
import { ElMessage } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import type { UploadProps, UploadUserFile, UploadFile, UploadFiles, UploadInstance } from 'element-plus'
import md5 from 'blueimp-md5'
import axios from 'axios'
import { getSignature } from '@/api/base'
interface Props {
modelValue: string | { name: string; url: string }[]
modelValue: string | { name: string; url: string }[] | File
prefix?: string
size?: number
limit?: number
beforeUpload?: (file: any) => void
beforeUpload?: UploadProps['beforeUpload']
onChange?: (uploadFile: UploadFile, uploadFiles: UploadFiles, uploadRef?: UploadInstance) => void
}
const props = withDefaults(defineProps<Props>(), {
......@@ -19,95 +20,92 @@ const props = withDefaults(defineProps<Props>(), {
const emit = defineEmits(['update:modelValue', 'success'])
const uploadData = ref()
const uploadRef = ref<UploadInstance>()
const fileList = ref<UploadUserFile[]>([])
watchEffect(() => {
fileList.value = Array.isArray(props.modelValue) ? props.modelValue.map(item => ({ ...item })) : []
})
watch(
() => props.modelValue,
value => {
fileList.value = Array.isArray(value) ? value.map(item => ({ ...item })) : []
}
)
const showFileList = computed(() => {
return Array.isArray(props.modelValue)
})
// 上传之前
const handleBeforeUpload = async (file: any) => {
if (props.limit && fileList.value.length >= props.limit && props.limit > 1) {
ElMessage.error(`只能上传${props.limit}个文件`)
return false
// 自定义上传
const handleHttpRequest: UploadProps['httpRequest'] = async xhr => {
const name = xhr.file.name
const key = `${props.prefix}${md5(name + Date.now())}.${name.split('.').pop()}`
const signature: Record<string, any> = await getSignature()
const params = {
key,
OSSAccessKeyId: signature.accessid,
policy: signature.policy,
signature: signature.signature,
success_action_status: '200',
url: `${signature.host}/${key}`,
file: xhr.file
}
return axios
.post('https://webapp-pub.oss-cn-beijing.aliyuncs.com', Object.assign(params, xhr.data), {
headers: { 'Content-Type': 'multipart/form-data' },
onUploadProgress(progress: any) {
progress.percent = progress.total > 0 ? (progress.loaded / progress.total) * 100 : 0
xhr.onProgress(progress)
}
})
.then(() => {
return params
})
}
// 文件改变
const handleChange: UploadProps['onChange'] = (uploadFile, uploadFiles) => {
props.onChange && props.onChange(uploadFile, uploadFiles, uploadRef.value)
}
// 上传之前
const handleBeforeUpload: UploadProps['beforeUpload'] = file => {
if (props.size && file.size > props.size) {
ElMessage.error(`文件大小不能超过${props.size / 1024 / 1024}M`)
return false
}
const fileName = file.name
const key = props.prefix + md5(fileName + new Date().getTime()) + fileName.substr(fileName.lastIndexOf('.'))
const response: Record<string, any> = await getSignature()
uploadData.value = {
key,
OSSAccessKeyId: response.accessid,
policy: response.policy,
signature: response.signature,
success_action_status: '200',
url: `${response.host}/${key}`
}
file.url = `${response.host}/${key}`
if (props.beforeUpload) {
return props.beforeUpload(file)
}
}
// 上传成功
const handleSuccess = (response: any, file: any, files: any) => {
if (!files.every((item: any) => item.status === 'success')) return
if (showFileList.value) {
if (props.limit && props.limit === 1) {
const last = files[files.length - 1]
emit('update:modelValue', [
{
name: last.name,
url: last.url || last.raw?.url,
size: last.size || last.raw?.size,
type: last.type || last.raw?.type
}
])
} else {
emit(
'update:modelValue',
files.map((item: any) => {
return {
name: item.name,
url: item.url || item.raw?.url,
size: item.size || item.raw?.size,
type: item.type || item.raw?.type
}
})
)
}
} else {
emit('update:modelValue', file.raw.url)
}
emit('success', file, files)
}
// 上传限制
const handleExceed: UploadProps['onExceed'] = () => {
ElMessage.warning('文件超出个数限制')
}
// 删除
const handleRemove: UploadProps['onRemove'] = (file, files) => {
if (showFileList.value) {
emit(
'update:modelValue',
files.map((item: any) => {
return { name: item.name, url: item.url || item.raw.url }
// 上传成功
const handleSuccess: UploadProps['onSuccess'] = (response, uploadFile: any, uploadFiles) => {
if (!uploadFiles.every(item => item.status === 'success')) return
uploadFile.type = uploadFile.raw?.type
uploadFile.url = response.url
const value = showFileList.value
? uploadFiles.map((item: any) => {
return {
name: item.name,
url: item.url,
size: item.size || item.raw?.size,
type: item.type || item.raw?.type
}
})
)
} else {
emit('update:modelValue', '')
}
: response.url
emit('update:modelValue', value)
emit('success', uploadFile, uploadFiles)
}
// 删除
const handleRemove: UploadProps['onRemove'] = (uploadFile, uploadFiles) => {
const value = showFileList.value ? uploadFiles : ''
emit('update:modelValue', value)
}
// 预览
......@@ -119,22 +117,24 @@ const handlePreview: UploadProps['onPreview'] = uploadFile => {
<template>
<el-upload
action="https://webapp-pub.oss-cn-beijing.aliyuncs.com"
:data="uploadData"
:show-file-list="showFileList"
:http-request="handleHttpRequest"
:before-upload="handleBeforeUpload"
:on-change="handleChange"
:on-exceed="handleExceed"
:on-remove="handleRemove"
:on-preview="handlePreview"
:on-success="handleSuccess"
:file-list="fileList"
class="uploader">
class="uploader"
ref="uploadRef">
<slot>
<template v-if="showFileList">
<template v-if="$attrs['list-type'] === 'picture-card'">
<el-icon><Plus /></el-icon>
</template>
<template v-else>
<el-button type="primary" plain round>本地文件</el-button>
<el-button size="default" round>点击上传</el-button>
</template>
</template>
<div class="avatar-uploader" v-else>
......
import httpRequest from '@/utils/axios'
import type { ChannelRequestParams, ChannelListSearch } from './types'
import type { ChannelRequestParams } from './types'
// https://gitlab.ezijing.com/root/api-documents/-/blob/master/%E6%96%B0%E7%9A%84zws%E7%B3%BB%E7%BB%9F/%E6%B8%A0%E9%81%93%E7%AE%A1%E7%90%86.md
......@@ -14,7 +14,18 @@ export function getRoles() {
}
// 获取渠道列表
export function getChannelList(params?: ChannelListSearch) {
export function getChannelList(params?: {
channel_id?: string
title?: string
company_short_name?: string
tags?: string
service_dialog_status?: string
case_withdraw_status?: string
distribution_status?: string
channel_quality?: string
'per-page'?: number
page?: number
}) {
return httpRequest.get('/api/zws/v1/backend/channel/list', { params })
}
......@@ -25,12 +36,21 @@ export function getChannelDetail(params: { id: string }) {
// 创建渠道
export function createChannel(data: ChannelRequestParams) {
return httpRequest.post('/api/zws/v1/backend/channel/create', data)
return httpRequest.post('/api/zws/v1/backend/channel/create', data, {
headers: { 'Content-Type': 'multipart/form-data' }
})
}
// 更新渠道
export function updateChannel(data: { id: string } & ChannelRequestParams) {
return httpRequest.post('/api/zws/v1/backend/channel/update', data)
return httpRequest.post('/api/zws/v1/backend/channel/update', data, {
headers: { 'Content-Type': 'multipart/form-data' }
})
}
// 删除渠道
export function deleteChannel(data: { id: string }) {
return httpRequest.post('/api/zws/v1/backend/channel/delete', data)
}
// 获取成员列表
......@@ -55,15 +75,29 @@ export function getProjectList(params: { id: string; 'per-page'?: number; page?:
// 新增渠道项目
export function addProject(data: any) {
return httpRequest.post('/api/zws/v1/backend/channel/project-create', data)
return httpRequest.post('/api/zws/v1/backend/channel/project-create', data, {
headers: { 'Content-Type': 'multipart/form-data' }
})
}
// 更新渠道项目
export function updateProject(data: any) {
return httpRequest.post('/api/zws/v1/backend/channel/project-update', data)
return httpRequest.post('/api/zws/v1/backend/channel/project-update', data, {
headers: { 'Content-Type': 'multipart/form-data' }
})
}
// 删除渠道项目
export function deleteProject(data: { id: string; contract_id: string }) {
return httpRequest.post('/api/zws/v1/backend/channel/project-delete', data)
}
// 批量设置标签
export function bindTag(data: { channels_id: string; tags: string }) {
return httpRequest.post('/api/zws/v1/backend/channel/tag-create', data)
}
// 生成二维码
export function getQrcode(params: { id: string; project_id: string }) {
return httpRequest.get('/api/zws/v1/backend/channel/qrcode', { params })
}
<script setup lang="ts">
import type { UploadFile } from 'element-plus'
import type { ChannelFormData } from '../types'
import { provideForm } from '../util'
import AppUpload from '@/components/base/AppUpload.vue'
const form = inject(provideForm) as ChannelFormData
function handleSuccess(uploadFile: UploadFile, key: string) {
form.files[key] = uploadFile.raw
}
</script>
<template>
......@@ -86,17 +90,28 @@ const form = inject(provideForm) as ChannelFormData
<el-row :gutter="40">
<el-col :span="6">
<el-form-item label="法人身份证(国徽面)">
<AppUpload v-model="form.partner.company_legal_representative_id_card_front" accept="image/*"></AppUpload>
<AppUpload
v-model="form.partner.company_legal_representative_id_card_front"
accept="image/*"
@success="
uploadFile => handleSuccess(uploadFile, 'company_legal_representative_id_card_front')
"></AppUpload>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="法人身份证(头像面)">
<AppUpload v-model="form.partner.company_legal_representative_id_card_back" accept="image/*"></AppUpload>
<AppUpload
v-model="form.partner.company_legal_representative_id_card_back"
accept="image/*"
@success="uploadFile => handleSuccess(uploadFile, 'company_legal_representative_id_card_back')"></AppUpload>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="营业执照">
<AppUpload v-model="form.partner.business_license" accept="image/*"></AppUpload>
<AppUpload
v-model="form.partner.business_license"
accept="image/*"
@success="uploadFile => handleSuccess(uploadFile, 'business_license')"></AppUpload>
</el-form-item>
</el-col>
</el-row>
......
<script setup lang="ts">
import type { FormInstance, FormRules } from 'element-plus'
import { useMap } from '../composables/useMap'
const { options } = await useMap()
const emit = defineEmits<{
(e: 'update:modelValue', visible: boolean): void
(e: 'update:modelValue', modelValue: boolean): void
(e: 'submit', data: any): void
}>()
const options = [
{
value: 'Option1',
label: 'O1'
},
{
value: 'Option2',
label: 'Option2'
},
{
value: 'Option3',
label: 'Option3'
},
{
value: 'Option4',
label: 'Option4'
},
{
value: 'Option5',
label: 'Option5'
}
]
const formInline = reactive({
tag: ''
const formRef = $ref<FormInstance>()
const form = reactive({ tag: [] })
const rules = reactive<FormRules>({
tag: { type: 'array', required: true, message: '请选择', trigger: 'change' }
})
// 选择完成
function submit() {
formRef?.validate().then(() => {
emit('submit', form)
})
}
</script>
<template>
<el-dialog title="选择标签" width="400px" :close-on-click-modal="false">
<el-form style="display: block; width: 80%; margin: 0 auto" :model="formInline" class="demo-form-inline">
<el-form-item label="渠道标签">
<el-select style="width: 200px" v-model="formInline.tag" multiple placeholder="Select">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
<el-form :model="form" :rules="rules" ref="formRef">
<el-form-item label="渠道标签" prop="tag">
<el-select style="width: 100%" v-model="form.tag" multiple>
<el-option v-for="item in options.channelTags" :key="item" :value="item" />
</el-select>
</el-form-item>
<!-- <el-form-item>
<el-button type="primary" @click="onSubmit">Query</el-button>
</el-form-item> -->
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="emit('update:modelValue', false)">取消</el-button>
<el-button type="primary"> 确定 </el-button>
</span>
<el-button auto-insert-space @click="emit('update:modelValue', false)">取消</el-button>
<el-button auto-insert-space type="primary" @click="submit">确定</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts"></script>
<script setup lang="ts">
import type { ChannelItem } from '../types'
import type { FormInstance } from 'element-plus'
import QrcodeVue from 'qrcode.vue'
import { getQrcode } from '../api'
import { useMap } from '../composables/useMap'
const { options } = await useMap()
const props = defineProps<{
data: ChannelItem
}>()
const formRef = $ref<FormInstance>()
const form = reactive({ project_id: '' })
let qrcodeValue = $ref('')
function fetchQrcode() {
getQrcode({ id: props.data.id, project_id: form.project_id }).then(res => {
qrcodeValue = res.data.url
})
}
</script>
<template>
<el-dialog title="生成二维码" width="400px">
<el-form style="display:block;width: 80%; margin: 0 auto">
<el-form :model="form" ref="formRef">
<el-form-item label="选择项目">
<el-select></el-select>
<el-select v-model="form.project_id" @change="fetchQrcode" style="width: 100%">
<el-option
v-for="item in options.projects"
:key="item.project_id"
:label="item.title"
:value="item.project_id"></el-option>
</el-select>
</el-form-item>
</el-form>
<el-row justify="center" style="padding: 20px 0">
<QrcodeVue :value="qrcodeValue" :size="200" v-if="qrcodeValue" />
</el-row>
<p>
<a :href="qrcodeValue" target="_blank">{{ qrcodeValue }}</a>
</p>
</el-dialog>
</template>
......@@ -30,6 +30,7 @@ const options = ref<{
agreementType: State[]
channelTaxRate: State[]
projects: { project_id: string; title: string }[]
channels: { channel_id: string; title: string }[]
}>({
channelType: [],
channelQuality: [],
......@@ -44,7 +45,8 @@ const options = ref<{
contractSecondaryDivisionType: [],
agreementType: [],
channelTaxRate: [],
projects: []
projects: [],
channels: []
})
// 领导
......@@ -70,7 +72,8 @@ export async function useMap() {
contractSecondaryDivisionType: data.contract_secondary_division_type_map || [],
agreementType: data.agreement_type_map || [],
channelTaxRate: data.channel_tax_rate_map || [],
projects: data.projects || []
projects: data.projects || [],
channels: data.channels || []
}
authList.value = data.channel_auth_map || []
tags.value = data.tags || {}
......
export interface ChannelListSearch {
channel_id?: string
title?: string
company_short_name?: string
tags?: string
service_dialog_status?: string
case_withdraw_status?: string
distribution_status?: string
channel_quality?: string
['per-page']?: string
page?: string
export interface ChannelItem {
id: string
title: string
channel_id: string
channel_type: string
tags: string[]
service_phone: string
service_dialog_status: string
landing_page: string
summary: string
project_id: string
channel_status: string
comment: string
status: string
operator: string
created_time: string
updated_time: string
channel_quality: string
distribution_status: string
case_withdraw_status: string
call_status: string
channel_owner_user_id: string
channel_type_name: string
channel_status_name: string
channel_owner_user_id_name: string
}
export interface ChannelRequestParams {
......@@ -30,6 +44,15 @@ export interface ChannelRequestParams {
projects?: string
}
export type ChannelFormData = Omit<ChannelRequestParams, 'tags' | 'members' | 'projects'> & {
tags: string[]
channel_id: string
channel_owner_user_id_name?: string
members: ChannelMember[]
projects: ChannelProject[]
files: any
}
// 渠道成员
export interface ChannelMember {
user_id: string
......@@ -81,14 +104,6 @@ export interface ChannelProject {
secondary_division_proportion_name: string
}
export type ChannelFormData = Omit<ChannelRequestParams, 'members' | 'projects'> & {
tags: string[]
channel_id: string
channel_owner_user_id_name: string
members: ChannelMember[]
projects: ChannelProject[]
}
// 用户信息
export interface User {
id: string
......
<script setup lang="ts">
import type { ChannelItem } from '../types'
import { ElMessage, ElMessageBox } from 'element-plus'
import AppList from '@/components/base/AppList.vue'
import { useMap } from '../composables/useMap'
import { getChannelList } from '../api'
import { getChannelList, deleteChannel, bindTag } from '../api'
const { options } = await useMap()
const { options, hasAuth } = await useMap()
const Label = defineAsyncComponent(() => import('../components/Label.vue'))
const QRCode = defineAsyncComponent(() => import('../components/QRCode.vue'))
......@@ -33,7 +35,7 @@ const listOptions = $computed(() => {
type: 'select',
prop: 'tags',
placeholder: '渠道标签',
options: options.value.channelTags.reduce((a: any, b: string) => a.push({ label: b, value: b }) && a, [])
options: options.value.channelTags.map(item => ({ label: item, value: item }))
},
{
type: 'select',
......@@ -63,7 +65,7 @@ const listOptions = $computed(() => {
type: 'select',
prop: 'channel_quality',
placeholder: '渠道质量',
options: options.value.channelQuality.reduce((a: any, b: number) => a.push({ label: b, value: b }) && a, [])
options: options.value.channelQuality.map(item => ({ label: item, value: item }))
}
]
}
......@@ -75,8 +77,8 @@ const columns = $computed(() => {
{
label: '渠道标签',
prop: 'tags',
computed(row: any) {
return Array.isArray(row.row.tags) ? row.row.tags.toString() : row.row.tags
computed({ row }: { row: ChannelItem }) {
return row.tags.join(',')
}
},
{
......@@ -99,22 +101,40 @@ function toggleSelection() {
appList?.tableRef.clearSelection()
}
let multipleSelection = $ref([])
let multipleSelection = $ref<any[]>([])
function handleSelectionChange(value: any) {
multipleSelection = value
}
const labelVisible = $ref(false)
let labelVisible = $ref(false)
// 打标签
function handleSubmitLabel(data: any) {
const params = {
channels_id: multipleSelection.map(item => item.channel_id).join(','),
tags: data.tag.join(',')
}
bindTag(params).then(() => {
labelVisible = false
appList?.refetch()
ElMessage({ message: '添加成功', type: 'success' })
})
}
let currentRow = $ref()
let currentRow = $ref<ChannelItem>()
// 二维码
let qrcodeVisible = $ref(false)
function handleQRCode(row: any) {
function handleQRCode(row: ChannelItem) {
currentRow = row
qrcodeVisible = true
}
// 删除
function handleRemove(row: any) {
function handleRemove(row: ChannelItem) {
ElMessageBox.confirm('确定要删除该渠道吗?', '提示').then(() => {
deleteChannel({ id: row.id }).then(() => {
ElMessage({ message: '删除成功', type: 'success' })
appList?.refetch()
})
})
currentRow = row
}
</script>
......@@ -129,25 +149,39 @@ function handleRemove(row: any) {
<el-button type="primary" :disabled="!multipleSelection.length" @click="labelVisible = true"
>选择标签</el-button
>
<el-button type="primary" @click="toggleSelection">取消</el-button>
<el-button type="primary" plain @click="toggleSelection">取消</el-button>
</template>
</template>
<template #table-x="{ row }">
<el-button text>
<el-button text v-if="hasAuth('channel-view', row.channel_id)">
<router-link :to="{ name: 'channelView', params: { id: row.id }, query: { channel_id: row.channel_id } }"
>查看</router-link
>
</el-button>
<el-button text style="--el-button-text-color: #3276fc">
<el-button text style="--el-button-text-color: #3276fc" v-if="hasAuth('channel-update', row.channel_id)">
<router-link :to="{ name: 'channelUpdate', params: { id: row.id }, query: { channel_id: row.channel_id } }"
>编辑</router-link
>
</el-button>
<el-button text style="--el-button-text-color: #f59a23" @click="handleQRCode(row)">二维码</el-button>
<el-button text style="--el-button-text-color: #d9001b" @click="handleRemove(row)">删除</el-button>
<el-button
text
style="--el-button-text-color: #f59a23"
@click="handleQRCode(row)"
v-if="hasAuth('channel-qrcode', row.channel_id)"
>二维码</el-button
>
<el-button
text
style="--el-button-text-color: #d9001b"
@click="handleRemove(row)"
v-if="hasAuth('channel-delete', row.channel_id)"
>删除</el-button
>
</template>
</AppList>
</AppCard>
<Label v-model="labelVisible" :data="multipleSelection"></Label>
<QRCode v-model="qrcodeVisible" :data="currentRow"></QRCode>
<!-- 选择标签 -->
<Label v-model="labelVisible" @submit="handleSubmitLabel" v-if="labelVisible"></Label>
<!-- 生成二维码 -->
<QRCode v-model="qrcodeVisible" :data="currentRow" v-if="qrcodeVisible && currentRow"></QRCode>
</template>
......@@ -48,7 +48,8 @@ const form: ChannelFormData = reactive({
leaders_id: '',
partner: {},
members: [],
projects: []
projects: [],
files: {}
})
provide(provideForm, form)
watchEffect(() => {
......@@ -112,6 +113,7 @@ function handleAdd(params: ChannelRequestParams) {
}
// 修改
function handleUpdate(params: ChannelRequestParams) {
console.log(params)
updateChannel({ id: props.id, ...params }).then(() => {
ElMessage({ message: '更新成功', type: 'success' })
router.push('/base/channel')
......
......@@ -126,7 +126,7 @@ const handleDelete = function (row: any) {
<el-button type="primary" @click="toggleSelection" v-if="!selectionVisible">添加成员</el-button>
<template v-else>
<el-button type="primary" :disabled="!multipleSelection.length" @click="addProjectMember">选择成员</el-button>
<el-button type="primary" @click="toggleSelection">取消</el-button>
<el-button type="primary" plain @click="toggleSelection">取消</el-button>
</template>
</template>
<template #table-x="{ row }">
......
......@@ -16,13 +16,6 @@ httpRequest.interceptors.request.use(
if (config.headers?.['Content-Type'] === 'application/x-www-form-urlencoded') {
config.data = qs.stringify(config.data, { skipNulls: true })
}
if (config.headers?.['Content-Type'] === 'multipart/form-data') {
const formData = new window.FormData()
for (const key in config.data) {
formData.append(key, config.data[key])
}
config.data = formData
}
return config
},
function (error) {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论