Commit a91c605a authored by 朱招明's avatar 朱招明

客户管理

parent 657c985b
......@@ -2,4 +2,4 @@
ENV = 'development'
# base api
VUE_APP_BASE_API = 'http://www.oa.com/api'
VUE_APP_BASE_API = 'http://www.oa.com'
......@@ -16,7 +16,8 @@
"dependencies": {
"axios": "0.18.1",
"core-js": "3.6.5",
"element-ui": "2.13.2",
"element-ui": "2.15.14",
"file-saver": "2.0.1",
"fuse.js": "^7.0.0",
"js-cookie": "2.2.0",
"normalize.css": "7.0.0",
......@@ -25,7 +26,8 @@
"screenfull": "^6.0.2",
"vue": "2.6.10",
"vue-router": "3.0.6",
"vuex": "3.1.0"
"vuex": "3.1.0",
"xlsx": "0.16.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "4.4.4",
......
import request from '@/utils/request'
export function getList(params) {
return request({
url: '/customer/opportunity/list',
method: 'get',
params
})
}
export function getOption(params) {
return request({
url: '/customer/opportunity/data_maps',
method: 'get',
params
})
}
export function getDetail(id, params) {
return request({
url: '/customer/opportunity/' + id + '/show',
method: 'get',
params
})
}
export function addOpportunity(params) {
return request({
url: '/customer/opportunity/add',
method: 'post',
data: params
})
}
export function editOpportunity(id, params) {
return request({
url: '/customer/opportunity/' + id + '/edit',
method: 'put',
data: params
})
}
import request from '@/utils/request'
export function getList(params) {
return request({
url: '/customer/private/list',
method: 'get',
params
})
}
export function getDetail(id, params) {
return request({
url: '/customer/private/' + id + '/show',
method: 'get',
params
})
}
export function getOption(params) {
return request({
url: '/customer/private/data_maps',
method: 'get',
params
})
}
export function addImport(params) {
return request({
url: '/customer/private/add_import',
method: 'post',
data: params
})
}
export function addCustomer(params) {
return request({
url: '/customer/private/add',
method: 'post',
data: params
})
}
export function editCustomer(id, params) {
return request({
url: '/customer/private/' + id + '/edit',
method: 'put',
data: params
})
}
export function editCustomerPrivate(id, params) {
return request({
url: '/customer/private/' + id + '/edit_private',
method: 'put',
data: params
})
}
export function backPublic(params) {
return request({
url: '/customer/private/back_public',
method: 'put',
data: params
})
}
export function followCustomer(id, params) {
return request({
url: '/customer/private/' + id + '/follow',
method: 'put',
data: params
})
}
export function followList(id, params) {
return request({
url: '/customer/private/' + id + '/follow_list',
method: 'get',
params: params
})
}
import request from '@/utils/request'
export function getList(params) {
return request({
url: '/customer/public/list',
method: 'get',
params
})
}
export function getDetail(id, params) {
return request({
url: '/customer/public/' + id + '/show',
method: 'get',
params
})
}
export function getOption(params) {
return request({
url: '/customer/public/data_maps',
method: 'get',
params
})
}
export function addImport(params) {
return request({
url: '/customer/public/add_import',
method: 'post',
data: params
})
}
export function addCustomer(params) {
return request({
url: '/customer/public/add',
method: 'post',
data: params
})
}
export function editCustomer(id, params) {
return request({
url: '/customer/public/' + id + '/edit',
method: 'put',
data: params
})
}
export function editCustomerPrivate(id, params) {
return request({
url: '/customer/public/' + id + '/edit_private',
method: 'put',
data: params
})
}
export function receivePrivate(params) {
return request({
url: '/customer/public/receive_private',
method: 'put',
data: params
})
}
export function followCustomer(id, params) {
return request({
url: '/customer/public/' + id + '/follow',
method: 'put',
data: params
})
}
export function followList(id, params) {
return request({
url: '/customer/public/' + id + '/follow_list',
method: 'get',
params: params
})
}
<template>
<div :class="computedClasses" class="material-input__component">
<div :class="{iconClass:icon}">
<i v-if="icon" :class="['el-icon-' + icon]" class="el-input__icon material-input__icon" />
<input
v-if="type === 'email'"
v-model="currentValue"
:name="name"
:placeholder="fillPlaceHolder"
:readonly="readonly"
:disabled="disabled"
:autocomplete="autoComplete"
:required="required"
type="email"
class="material-input"
@focus="handleMdFocus"
@blur="handleMdBlur"
@input="handleModelInput"
>
<input
v-if="type === 'url'"
v-model="currentValue"
:name="name"
:placeholder="fillPlaceHolder"
:readonly="readonly"
:disabled="disabled"
:autocomplete="autoComplete"
:required="required"
type="url"
class="material-input"
@focus="handleMdFocus"
@blur="handleMdBlur"
@input="handleModelInput"
>
<input
v-if="type === 'number'"
v-model="currentValue"
:name="name"
:placeholder="fillPlaceHolder"
:step="step"
:readonly="readonly"
:disabled="disabled"
:autocomplete="autoComplete"
:max="max"
:min="min"
:minlength="minlength"
:maxlength="maxlength"
:required="required"
type="number"
class="material-input"
@focus="handleMdFocus"
@blur="handleMdBlur"
@input="handleModelInput"
>
<input
v-if="type === 'password'"
v-model="currentValue"
:name="name"
:placeholder="fillPlaceHolder"
:readonly="readonly"
:disabled="disabled"
:autocomplete="autoComplete"
:max="max"
:min="min"
:required="required"
type="password"
class="material-input"
@focus="handleMdFocus"
@blur="handleMdBlur"
@input="handleModelInput"
>
<input
v-if="type === 'tel'"
v-model="currentValue"
:name="name"
:placeholder="fillPlaceHolder"
:readonly="readonly"
:disabled="disabled"
:autocomplete="autoComplete"
:required="required"
type="tel"
class="material-input"
@focus="handleMdFocus"
@blur="handleMdBlur"
@input="handleModelInput"
>
<input
v-if="type === 'text'"
v-model="currentValue"
:name="name"
:placeholder="fillPlaceHolder"
:readonly="readonly"
:disabled="disabled"
:autocomplete="autoComplete"
:minlength="minlength"
:maxlength="maxlength"
:required="required"
type="text"
class="material-input"
@focus="handleMdFocus"
@blur="handleMdBlur"
@input="handleModelInput"
>
<span class="material-input-bar" />
<label class="material-label">
<slot />
</label>
</div>
</div>
</template>
<script>
export default {
name: 'MdInput',
props: {
/* eslint-disable */
icon: String,
name: String,
type: {
type: String,
default: 'text'
},
value: [String, Number],
placeholder: String,
readonly: Boolean,
disabled: Boolean,
min: String,
max: String,
step: String,
minlength: Number,
maxlength: Number,
required: {
type: Boolean,
default: true
},
autoComplete: {
type: String,
default: 'off'
},
validateEvent: {
type: Boolean,
default: true
}
},
data() {
return {
currentValue: this.value,
focus: false,
fillPlaceHolder: null
}
},
computed: {
computedClasses() {
return {
'material--active': this.focus,
'material--disabled': this.disabled,
'material--raised': Boolean(this.focus || this.currentValue) // has value
}
}
},
watch: {
value(newValue) {
this.currentValue = newValue
}
},
methods: {
handleModelInput(event) {
const value = event.target.value
this.$emit('input', value)
if (this.$parent.$options.componentName === 'ElFormItem') {
if (this.validateEvent) {
this.$parent.$emit('el.form.change', [value])
}
}
this.$emit('change', value)
},
handleMdFocus(event) {
this.focus = true
this.$emit('focus', event)
if (this.placeholder && this.placeholder !== '') {
this.fillPlaceHolder = this.placeholder
}
},
handleMdBlur(event) {
this.focus = false
this.$emit('blur', event)
this.fillPlaceHolder = null
if (this.$parent.$options.componentName === 'ElFormItem') {
if (this.validateEvent) {
this.$parent.$emit('el.form.blur', [this.currentValue])
}
}
}
}
}
</script>
<style lang="scss" scoped>
// Fonts:
$font-size-base: 16px;
$font-size-small: 18px;
$font-size-smallest: 12px;
$font-weight-normal: normal;
$font-weight-bold: bold;
$apixel: 1px;
// Utils
$spacer: 12px;
$transition: 0.2s ease all;
$index: 0px;
$index-has-icon: 30px;
// Theme:
$color-white: white;
$color-grey: #9E9E9E;
$color-grey-light: #E0E0E0;
$color-blue: #2196F3;
$color-red: #F44336;
$color-black: black;
// Base clases:
%base-bar-pseudo {
content: '';
height: 1px;
width: 0;
bottom: 0;
position: absolute;
transition: $transition;
}
// Mixins:
@mixin slided-top() {
top: - ($font-size-base + $spacer);
left: 0;
font-size: $font-size-base;
font-weight: $font-weight-bold;
}
// Component:
.material-input__component {
margin-top: 36px;
position: relative;
* {
box-sizing: border-box;
}
.iconClass {
.material-input__icon {
position: absolute;
left: 0;
line-height: $font-size-base;
color: $color-blue;
top: $spacer;
width: $index-has-icon;
height: $font-size-base;
font-size: $font-size-base;
font-weight: $font-weight-normal;
pointer-events: none;
}
.material-label {
left: $index-has-icon;
}
.material-input {
text-indent: $index-has-icon;
}
}
.material-input {
font-size: $font-size-base;
padding: $spacer $spacer $spacer - $apixel * 10 $spacer / 2;
display: block;
width: 100%;
border: none;
line-height: 1;
border-radius: 0;
&:focus {
outline: none;
border: none;
border-bottom: 1px solid transparent; // fixes the height issue
}
}
.material-label {
font-weight: $font-weight-normal;
position: absolute;
pointer-events: none;
left: $index;
top: 0;
transition: $transition;
font-size: $font-size-small;
}
.material-input-bar {
position: relative;
display: block;
width: 100%;
&:before {
@extend %base-bar-pseudo;
left: 50%;
}
&:after {
@extend %base-bar-pseudo;
right: 50%;
}
}
// Disabled state:
&.material--disabled {
.material-input {
border-bottom-style: dashed;
}
}
// Raised state:
&.material--raised {
.material-label {
@include slided-top();
}
}
// Active state:
&.material--active {
.material-input-bar {
&:before,
&:after {
width: 50%;
}
}
}
}
.material-input__component {
background: $color-white;
.material-input {
background: none;
color: $color-black;
text-indent: $index;
border-bottom: 1px solid $color-grey-light;
}
.material-label {
color: $color-grey;
}
.material-input-bar {
&:before,
&:after {
background: $color-blue;
}
}
// Active state:
&.material--active {
.material-label {
color: $color-blue;
}
}
// Errors:
&.material--has-errors {
&.material--active .material-label {
color: $color-red;
}
.material-input-bar {
&:before,
&:after {
background: transparent;
}
}
}
}
</style>
<template>
<div :style="{height:height+'px',zIndex:zIndex}">
<div
:class="className"
:style="{top:(isSticky ? stickyTop +'px' : ''),zIndex:zIndex,position:position,width:width,height:height+'px'}"
>
<slot>
<div>sticky</div>
</slot>
</div>
</div>
</template>
<script>
export default {
name: 'Sticky',
props: {
stickyTop: {
type: Number,
default: 0
},
zIndex: {
type: Number,
default: 1
},
className: {
type: String,
default: ''
}
},
data() {
return {
active: false,
position: '',
width: undefined,
height: undefined,
isSticky: false
}
},
mounted() {
this.height = this.$el.getBoundingClientRect().height
window.addEventListener('scroll', this.handleScroll)
window.addEventListener('resize', this.handleResize)
},
activated() {
this.handleScroll()
},
destroyed() {
window.removeEventListener('scroll', this.handleScroll)
window.removeEventListener('resize', this.handleResize)
},
methods: {
sticky() {
if (this.active) {
return
}
this.position = 'fixed'
this.active = true
this.width = this.width + 'px'
this.isSticky = true
},
handleReset() {
if (!this.active) {
return
}
this.reset()
},
reset() {
this.position = ''
this.width = 'auto'
this.active = false
this.isSticky = false
},
handleScroll() {
const width = this.$el.getBoundingClientRect().width
this.width = width || 'auto'
const offsetTop = this.$el.getBoundingClientRect().top
if (offsetTop < this.stickyTop) {
this.sticky()
return
}
this.handleReset()
},
handleResize() {
if (this.isSticky) {
this.width = this.$el.getBoundingClientRect().width + 'px'
}
}
}
}
</script>
<template>
<div>
<input ref="excel-upload-input" class="excel-upload-input" type="file" accept=".xlsx, .xls" @change="handleClick">
<div class="drop" @drop="handleDrop" @dragover="handleDragover" @dragenter="handleDragover">
拖动excel文件到此处或者
<el-button :loading="loading" style="margin-left:16px;" icon="el-icon-upload" size="mini" type="primary" @click="handleUpload">
上传
</el-button>
</div>
</div>
</template>
<script>
import XLSX from 'xlsx'
export default {
props: {
beforeUpload: Function, // eslint-disable-line
onSuccess: Function// eslint-disable-line
},
data() {
return {
loading: false,
excelData: {
header: null,
results: null
}
}
},
methods: {
generateData({ header, results }) {
this.excelData.header = header
this.excelData.results = results
this.onSuccess && this.onSuccess(this.excelData)
},
handleDrop(e) {
e.stopPropagation()
e.preventDefault()
if (this.loading) return
const files = e.dataTransfer.files
if (files.length !== 1) {
this.$message.error('Only support uploading one file!')
return
}
const rawFile = files[0] // only use files[0]
if (!this.isExcel(rawFile)) {
this.$message.error('Only supports upload .xlsx, .xls, .csv suffix files')
return false
}
this.upload(rawFile)
e.stopPropagation()
e.preventDefault()
},
handleDragover(e) {
e.stopPropagation()
e.preventDefault()
e.dataTransfer.dropEffect = 'copy'
},
handleUpload() {
this.$refs['excel-upload-input'].click()
},
handleClick(e) {
const files = e.target.files
const rawFile = files[0] // only use files[0]
if (!rawFile) return
this.upload(rawFile)
},
upload(rawFile) {
this.$refs['excel-upload-input'].value = null // fix can't select the same excel
if (!this.beforeUpload) {
this.readerData(rawFile)
return
}
const before = this.beforeUpload(rawFile)
if (before) {
this.readerData(rawFile)
}
},
readerData(rawFile) {
this.loading = true
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = e => {
const data = e.target.result
const workbook = XLSX.read(data, { type: 'array' })
const firstSheetName = workbook.SheetNames[0]
const worksheet = workbook.Sheets[firstSheetName]
const header = this.getHeaderRow(worksheet)
const results = XLSX.utils.sheet_to_json(worksheet)
this.generateData({ header, results })
this.loading = false
resolve()
}
reader.readAsArrayBuffer(rawFile)
}).finally(() => { this.loading = false })
},
getHeaderRow(sheet) {
const headers = []
const range = XLSX.utils.decode_range(sheet['!ref'])
let C
const R = range.s.r
/* start in the first row */
for (C = range.s.c; C <= range.e.c; ++C) { /* walk every column in the range */
const cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })]
/* find the cell in the first row */
let hdr = 'UNKNOWN ' + C // <-- replace with your desired default
if (cell && cell.t) hdr = XLSX.utils.format_cell(cell)
headers.push(hdr)
}
return headers
},
isExcel(file) {
return /\.(xlsx|xls|csv)$/.test(file.name)
}
}
}
</script>
<style scoped>
.excel-upload-input{
display: none;
z-index: -9999;
}
.drop{
border: 2px dashed #bbb;
width: 600px;
height: 160px;
line-height: 160px;
margin: 0 auto;
font-size: 24px;
border-radius: 5px;
text-align: center;
color: #bbb;
position: relative;
}
</style>
import waves from './waves'
const install = function(Vue) {
Vue.directive('waves', waves)
}
if (window.Vue) {
window.waves = waves
Vue.use(install); // eslint-disable-line
}
waves.install = install
export default waves
.waves-ripple {
position: absolute;
border-radius: 100%;
background-color: rgba(0, 0, 0, 0.15);
background-clip: padding-box;
pointer-events: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-transform: scale(0);
-ms-transform: scale(0);
transform: scale(0);
opacity: 1;
}
.waves-ripple.z-active {
opacity: 0;
-webkit-transform: scale(2);
-ms-transform: scale(2);
transform: scale(2);
-webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
transition: opacity 1.2s ease-out, transform 0.6s ease-out;
transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out;
}
\ No newline at end of file
import './waves.css'
const context = '@@wavesContext'
function handleClick(el, binding) {
function handle(e) {
const customOpts = Object.assign({}, binding.value)
const opts = Object.assign({
ele: el, // 波纹作用元素
type: 'hit', // hit 点击位置扩散 center中心点扩展
color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
},
customOpts
)
const target = opts.ele
if (target) {
target.style.position = 'relative'
target.style.overflow = 'hidden'
const rect = target.getBoundingClientRect()
let ripple = target.querySelector('.waves-ripple')
if (!ripple) {
ripple = document.createElement('span')
ripple.className = 'waves-ripple'
ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'
target.appendChild(ripple)
} else {
ripple.className = 'waves-ripple'
}
switch (opts.type) {
case 'center':
ripple.style.top = rect.height / 2 - ripple.offsetHeight / 2 + 'px'
ripple.style.left = rect.width / 2 - ripple.offsetWidth / 2 + 'px'
break
default:
ripple.style.top =
(e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop ||
document.body.scrollTop) + 'px'
ripple.style.left =
(e.pageX - rect.left - ripple.offsetWidth / 2 - document.documentElement.scrollLeft ||
document.body.scrollLeft) + 'px'
}
ripple.style.backgroundColor = opts.color
ripple.className = 'waves-ripple z-active'
return false
}
}
if (!el[context]) {
el[context] = {
removeHandle: handle
}
} else {
el[context].removeHandle = handle
}
return handle
}
export default {
bind(el, binding) {
el.addEventListener('click', handleClick(el, binding), false)
},
update(el, binding) {
el.removeEventListener('click', el[context].removeHandle, false)
el.addEventListener('click', handleClick(el, binding), false)
},
unbind(el) {
el.removeEventListener('click', el[context].removeHandle, false)
el[context] = null
delete el[context]
}
}
......@@ -2,10 +2,34 @@
export const constantRouterComponents = {
// 权限管理
'role.index': () => import('@/views/system/power/role/index'),
'menu.index': () => import('@/views/system/power/menu/index'),
// 员工管理
'user.index': () => import('@/views/system/company/user/index'),
'user.add': () => import('@/views/system/company/user/add'),
'user.edit': () => import('@/views/system/company/user/edit'),
'menu.index': () => import('@/views/system/power/menu/index'),
// 部门管理
'department.index': () => import('@/views/system/company/department/index'),
'announcement.index': () => import('@/views/system/announcement/index')
// 公告管理
'announcement.index': () => import('@/views/system/announcement/index'),
// 客户-我的客户
'private.index': () => import('@/views/customer/private/index'),
'private.add': () => import('@/views/customer/private/add'),
'private.edit': () => import('@/views/customer/private/edit'),
'private.import': () => import('@/views/customer/private/import'),
// 客户-公海客户
'public.index': () => import('@/views/customer/public/index'),
'public.add': () => import('@/views/customer/public/add'),
'public.edit': () => import('@/views/customer/public/edit'),
'public.import': () => import('@/views/customer/public/import'),
// 客户-商机管理
'opportunity.index': () => import('@/views/customer/opportunity/index'),
'opportunity.add': () => import('@/views/customer/opportunity/add'),
'opportunity.edit': () => import('@/views/customer/opportunity/edit')
}
......@@ -68,23 +68,35 @@ export const generator = (routerMap, parent) => {
}
}
if (item.key.includes('.edit')) {
currentRouter.meta.noTag = true
}
if (item.is_menu === 0) {
currentRouter.hidden = true
if (parent.redirect === 'noRedirect') {
currentRouter.meta.activeMenu = parent.path + '/' + parent.name + '.index'
} else {
currentRouter.meta.activeMenu = parent.path
}
}
// 是否有子菜单,并递归处理
if (item.children && item.children.length > 0) {
// Recursion
currentRouter.children = generator(item.children, currentRouter)
const index_view = item.children.find(item => item.key === currentRouter.name + '.index')
if (item.children.find(item => item.is_menu === 1)) {
currentRouter.component = RouteView
currentRouter.redirect = 'noRedirect'
const index_view = item.children.find(item => item.key === currentRouter.name + '.index')
if (index_view) {
} else if (index_view) {
currentRouter.component = RouteView
currentRouter.redirect = currentRouter.path + '/' + index_view.key
} else {
currentRouter.component = () => import('@/views/404')
}
// Recursion
currentRouter.children = generator(item.children, currentRouter)
}
return currentRouter
})
......
......@@ -6,11 +6,13 @@ const state = {
const mutations = {
ADD_VISITED_VIEW: (state, view) => {
if (state.visitedViews.some(v => v.path === view.path)) return
if (!view.meta.noTag) {
state.visitedViews.push(
Object.assign({}, view, {
title: view.meta.title || 'no-name'
})
)
}
},
ADD_CACHED_VIEW: (state, view) => {
if (state.cachedViews.includes(view.name)) return
......
......@@ -355,3 +355,27 @@ export function removeClass(ele, cls) {
ele.className = ele.className.replace(reg, ' ')
}
}
export function findKeyByValue(object, value) {
const keys = Object.keys(object)
const key = keys.find(key => object[key] === value)
return key || null // Value not found in the object
}
export function formatImportData(results, headerFiled) {
const data = []
results.forEach((item) => {
const date_item = {}
Object.keys(item).forEach(key => {
const value = item[key]
const filed = findKeyByValue(headerFiled, key)
if (filed) {
date_item[filed] = value
}
})
data.push(date_item)
})
return data
}
......@@ -5,9 +5,9 @@ import { getToken } from '@/utils/auth'
import router from '@/router'
// create an axios instance
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
baseURL: '/api', // url = base url + request url
// withCredentials: true, // send cookies when cross-domain requests
timeout: 5000 // request timeout
timeout: 30000 // request timeout
})
// request interceptor
service.interceptors.request.use(
......
/* eslint-disable */
import { saveAs } from 'file-saver'
import XLSX from 'xlsx'
function generateArray(table) {
var out = [];
var rows = table.querySelectorAll('tr');
var ranges = [];
for (var R = 0; R < rows.length; ++R) {
var outRow = [];
var row = rows[R];
var columns = row.querySelectorAll('td');
for (var C = 0; C < columns.length; ++C) {
var cell = columns[C];
var colspan = cell.getAttribute('colspan');
var rowspan = cell.getAttribute('rowspan');
var cellValue = cell.innerText;
if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue;
//Skip ranges
ranges.forEach(function (range) {
if (R >= range.s.r && R <= range.e.r && outRow.length >= range.s.c && outRow.length <= range.e.c) {
for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null);
}
});
//Handle Row Span
if (rowspan || colspan) {
rowspan = rowspan || 1;
colspan = colspan || 1;
ranges.push({
s: {
r: R,
c: outRow.length
},
e: {
r: R + rowspan - 1,
c: outRow.length + colspan - 1
}
});
};
//Handle Value
outRow.push(cellValue !== "" ? cellValue : null);
//Handle Colspan
if (colspan)
for (var k = 0; k < colspan - 1; ++k) outRow.push(null);
}
out.push(outRow);
}
return [out, ranges];
};
function datenum(v, date1904) {
if (date1904) v += 1462;
var epoch = Date.parse(v);
return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
}
function sheet_from_array_of_arrays(data, opts) {
var ws = {};
var range = {
s: {
c: 10000000,
r: 10000000
},
e: {
c: 0,
r: 0
}
};
for (var R = 0; R != data.length; ++R) {
for (var C = 0; C != data[R].length; ++C) {
if (range.s.r > R) range.s.r = R;
if (range.s.c > C) range.s.c = C;
if (range.e.r < R) range.e.r = R;
if (range.e.c < C) range.e.c = C;
var cell = {
v: data[R][C]
};
if (cell.v == null) continue;
var cell_ref = XLSX.utils.encode_cell({
c: C,
r: R
});
if (typeof cell.v === 'number') cell.t = 'n';
else if (typeof cell.v === 'boolean') cell.t = 'b';
else if (cell.v instanceof Date) {
cell.t = 'n';
cell.z = XLSX.SSF._table[14];
cell.v = datenum(cell.v);
} else cell.t = 's';
ws[cell_ref] = cell;
}
}
if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range);
return ws;
}
function Workbook() {
if (!(this instanceof Workbook)) return new Workbook();
this.SheetNames = [];
this.Sheets = {};
}
function s2ab(s) {
var buf = new ArrayBuffer(s.length);
var view = new Uint8Array(buf);
for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
return buf;
}
export function export_table_to_excel(id) {
var theTable = document.getElementById(id);
var oo = generateArray(theTable);
var ranges = oo[1];
/* original data */
var data = oo[0];
var ws_name = "SheetJS";
var wb = new Workbook(),
ws = sheet_from_array_of_arrays(data);
/* add ranges to worksheet */
// ws['!cols'] = ['apple', 'banan'];
ws['!merges'] = ranges;
/* add worksheet to workbook */
wb.SheetNames.push(ws_name);
wb.Sheets[ws_name] = ws;
var wbout = XLSX.write(wb, {
bookType: 'xlsx',
bookSST: false,
type: 'binary'
});
saveAs(new Blob([s2ab(wbout)], {
type: "application/octet-stream"
}), "test.xlsx")
}
export function export_json_to_excel({
multiHeader = [],
header,
data,
filename,
merges = [],
autoWidth = true,
bookType = 'xlsx'
} = {}) {
/* original data */
filename = filename || 'excel-list'
data = [...data]
data.unshift(header);
for (let i = multiHeader.length - 1; i > -1; i--) {
data.unshift(multiHeader[i])
}
var ws_name = "SheetJS";
var wb = new Workbook(),
ws = sheet_from_array_of_arrays(data);
if (merges.length > 0) {
if (!ws['!merges']) ws['!merges'] = [];
merges.forEach(item => {
ws['!merges'].push(XLSX.utils.decode_range(item))
})
}
if (autoWidth) {
/*设置worksheet每列的最大宽度*/
const colWidth = data.map(row => row.map(val => {
/*先判断是否为null/undefined*/
if (val == null) {
return {
'wch': 10
};
}
/*再判断是否为中文*/
else if (val.toString().charCodeAt(0) > 255) {
return {
'wch': val.toString().length * 2
};
} else {
return {
'wch': val.toString().length
};
}
}))
/*以第一行为初始值*/
let result = colWidth[0];
for (let i = 1; i < colWidth.length; i++) {
for (let j = 0; j < colWidth[i].length; j++) {
if (result[j]['wch'] < colWidth[i][j]['wch']) {
result[j]['wch'] = colWidth[i][j]['wch'];
}
}
}
ws['!cols'] = result;
}
/* add worksheet to workbook */
wb.SheetNames.push(ws_name);
wb.Sheets[ws_name] = ws;
var wbout = XLSX.write(wb, {
bookType: bookType,
bookSST: false,
type: 'binary'
});
saveAs(new Blob([s2ab(wbout)], {
type: "application/octet-stream"
}), `${filename}.${bookType}`);
}
/* eslint-disable */
import { saveAs } from 'file-saver'
import JSZip from 'jszip'
export function export_txt_to_zip(th, jsonData, txtName, zipName) {
const zip = new JSZip()
const txt_name = txtName || 'file'
const zip_name = zipName || 'file'
const data = jsonData
let txtData = `${th}\r\n`
data.forEach((row) => {
let tempStr = ''
tempStr = row.toString()
txtData += `${tempStr}\r\n`
})
zip.file(`${txt_name}.txt`, txtData)
zip.generateAsync({
type: "blob"
}).then((blob) => {
saveAs(blob, `${zip_name}.zip`)
}, (err) => {
alert('导出失败')
})
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
<template>
<div class="app-container">
<div class="button-container">
<el-button :loading="loading" type="success" @click="submitForm">
提交
</el-button>
</div>
<el-form ref="addForm" label-width="80px" :model="form" :rules="rules">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>基本信息</span>
</div>
<el-row>
<el-col :span="8">
<el-form-item prop="name" label-width="100px" label="客户名称:">
<el-input v-model="form.name" placeholder="客户名称" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="company" label-width="100px" label="公司名称:">
<el-input v-model="form.company" placeholder="公司名称" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="industry" label-width="100px" label="行业:">
<el-input v-model="form.industry" placeholder="行业" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item prop="department" label-width="100px" label="部门:">
<el-input v-model="form.department" placeholder="部门" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="duties" label-width="100px" label="职位:">
<el-input v-model="form.duties" placeholder="职位" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item prop="from" label-width="100px" label="客户来源:">
<el-select v-model="form.from" placeholder="客户来源" clearable class="filter-item" style="width: 130px">
<el-option v-for="item in fromOptions" :key="item.key" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="type" label-width="100px" label="客户类型:">
<el-select v-model="form.type" placeholder="客户类型" clearable class="filter-item" style="width: 130px">
<el-option v-for="item in typeOptions" :key="item.key" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-card>
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>联系方式</span>
</div>
<el-row>
<el-col :span="8">
<el-form-item prop="tel_phone" label-width="100px" label="电话:">
<el-input v-model="form.tel_phone" placeholder="电话" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="mobile_phone" label-width="100px" label="手机号:">
<el-input v-model="form.mobile_phone" placeholder="手机号" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="wechat" label-width="100px" label="微信号:">
<el-input v-model="form.wechat" placeholder="微信号" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item prop="dy" label-width="100px" label="抖音号:">
<el-input v-model="form.dy" placeholder="抖音号" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="email" label-width="100px" label="邮箱:">
<el-input v-model="form.email" placeholder="邮箱" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="address" label-width="100px" label="地址:">
<el-input v-model="form.address" placeholder="地址" />
</el-form-item>
</el-col>
</el-row>
</el-card>
</el-form>
</div>
</template>
<script>
import { getOption, addCustomer } from '@/api/customer/private'
import { Message } from 'element-ui' // 粘性header组件
export default {
components: { },
data() {
return {
form: {
'name': '',
'company': '',
'industry': '',
'department': '',
'duties': '',
'tel_phone': '',
'mobile_phone': '',
'wechat': '',
'dy': '',
'email': '',
'address': '',
'follow_status': '',
'from': '',
'type': ''
},
rules: {
name: [{ required: true, message: '客户名称不能为空', trigger: 'blur' }],
company: [{ required: true, message: '公司名称不能为空', trigger: 'blur' }],
industry: [{ required: true, message: '行业不能为空', trigger: 'blur' }],
department: [{ required: true, message: '部门不能为空', trigger: 'blur' }],
duties: [{ required: true, message: '职位不能为空', trigger: 'blur' }]
},
loading: false,
typeOptions: [],
fromOptions: [],
followStatusOptions: []
}
},
created() {
this.getOption()
},
methods: {
getOption() {
getOption().then((res) => {
const { type_maps, from_maps, follow_status_maps } = res.data
this.typeOptions = type_maps
this.fromOptions = from_maps
this.followStatusOptions = follow_status_maps
})
},
submitForm() {
this.$refs['addForm'].validate((valid, fields) => {
if (valid) {
this.loading = true
addCustomer(this.form).then(() => {
Message.success('添加成功')
this.$refs['addForm'].clearValidate()
this.$refs['addForm'].resetFields()
}).finally(() => { this.loading = false })
}
})
}
}
}
</script>
<style lang="scss" scoped>
.button-container{
display: flex;
justify-content: space-between;
}
.box-card{
margin: 15px 0;
}
</style>
<template>
<div>
<el-form ref="form" :model="from" :rules="rules" label-width="80px" label-position="left">
<el-form-item label="营业执照" prop="business_license">
<el-input v-model="from.business_license" placeholder="营业执照" />
</el-form-item>
<el-form-item label="身份证" prop="identity">
<el-input v-model="from.identity" placeholder="身份证" />
</el-form-item>
<el-form-item label="银行账户" prop="bank_account">
<el-input v-model="from.bank_account" placeholder="银行账户" />
</el-form-item>
<el-form-item label="手机号码" prop="mobile_phone">
<el-input v-model="from.mobile_phone" placeholder="手机号码" />
</el-form-item>
</el-form>
</div>
</template>
<script>
import { editCustomerPrivate } from '@/api/customer/private'
export default {
name: 'EditPrivate',
props: {
from: {
type: Object,
default: null
}
},
data() {
return {
rules: { }
}
},
methods: {
validate() {
this.$refs['form'].validate((valid, fields) => {
return !!valid
}).then((valid) => {
return !!valid
})
return false
},
submit() {
return new Promise((resolve, reject) => {
editCustomerPrivate(this.form.id, this.form).then(() => {
resolve()
}).catch((e) => { reject(e) })
})
}
}
}
</script>
<style scoped>
</style>
<template>
<div class="app-container">
<div class="button-container">
<el-button @click="$router.go(-1)">
返回
</el-button>
<el-button :loading="loading" type="success" @click="submitForm">
提交
</el-button>
</div>
<el-form ref="editForm" label-width="80px" :model="form" :rules="rules">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>基本信息</span>
</div>
<el-row>
<el-col :span="8">
<el-form-item prop="name" label-width="100px" label="客户名称:">
<el-input v-model="form.name" placeholder="客户名称" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="company" label-width="100px" label="公司名称:">
<el-input v-model="form.company" placeholder="公司名称" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="industry" label-width="100px" label="行业:">
<el-input v-model="form.industry" placeholder="行业" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item prop="department" label-width="100px" label="部门:">
<el-input v-model="form.department" placeholder="部门" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="duties" label-width="100px" label="职位:">
<el-input v-model="form.duties" placeholder="职位" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item prop="from" label-width="100px" label="客户来源:">
<el-select v-model="form.from" placeholder="客户来源" clearable class="filter-item" style="width: 130px">
<el-option v-for="item in fromOptions" :key="item.key" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="type" label-width="100px" label="客户类型:">
<el-select v-model="form.type" placeholder="客户类型" clearable class="filter-item" style="width: 130px">
<el-option v-for="item in typeOptions" :key="item.key" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-card>
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>联系方式</span>
</div>
<el-row>
<el-col :span="8">
<el-form-item prop="tel_phone" label-width="100px" label="电话:">
<el-input v-model="form.tel_phone" placeholder="电话" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="mobile_phone" label-width="100px" label="手机号:">
<el-input v-model="form.mobile_phone" placeholder="手机号" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="wechat" label-width="100px" label="微信号:">
<el-input v-model="form.wechat" placeholder="微信号" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item prop="dy" label-width="100px" label="抖音号:">
<el-input v-model="form.dy" placeholder="抖音号" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="email" label-width="100px" label="邮箱:">
<el-input v-model="form.email" placeholder="邮箱" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="address" label-width="100px" label="地址:">
<el-input v-model="form.address" placeholder="地址" />
</el-form-item>
</el-col>
</el-row>
</el-card>
</el-form>
</div>
</template>
<script>
import { getOption, editCustomer } from '@/api/customer/private'
import { Message } from 'element-ui' // 粘性header组件
export default {
components: { },
data() {
return {
form: {
'name': '',
'company': '',
'industry': '',
'department': '',
'duties': '',
'tel_phone': '',
'mobile_phone': '',
'wechat': '',
'dy': '',
'email': '',
'address': '',
'follow_status': '',
'from': '',
'type': ''
},
rules: {
name: [{ required: true, message: '客户名称不能为空', trigger: 'blur' }],
company: [{ required: true, message: '公司名称不能为空', trigger: 'blur' }],
industry: [{ required: true, message: '行业不能为空', trigger: 'blur' }],
department: [{ required: true, message: '部门不能为空', trigger: 'blur' }],
duties: [{ required: true, message: '职位不能为空', trigger: 'blur' }]
},
loading: false,
typeOptions: [],
fromOptions: [],
followStatusOptions: []
}
},
created() {
this.form = Object.assign({}, this.$route.params && this.$route.params.row)
if (!this.form.id) {
this.$router.push({ path: '/' })
}
this.getOption()
},
beforeRouteLeave(to, from, next) {
this.$store.dispatch('tagsView/delView', from)
next()
},
methods: {
getOption() {
getOption().then((res) => {
const { type_maps, from_maps, follow_status_maps } = res.data
this.typeOptions = type_maps
this.fromOptions = from_maps
this.followStatusOptions = follow_status_maps
})
},
submitForm() {
this.$refs['editForm'].validate((valid, fields) => {
if (valid) {
this.loading = true
editCustomer(this.form.id, this.form).then(() => {
Message.success('编辑成功')
this.$router.go(-1)
}).finally(() => { this.loading = false })
}
})
}
}
}
</script>
<style lang="scss" scoped>
.button-container{
display: flex;
justify-content: space-between;
}
.box-card{
margin: 15px 0;
}
</style>
<template>
<div class="app-container">
<upload-excel-component :on-success="handleSuccess" :before-upload="beforeUpload" />
<el-button v-waves :loading="downloadLoading" class="filter-item" type="primary" icon="el-icon-download" @click="handleImportDemo">
模板下载
</el-button>
<el-button v-waves :loading="loading" class="filter-item" type="primary" icon="el-icon-upload2" @click="handleImport">
导入
</el-button>
<el-table max-height="500" :data="excelData" style="width: 100%;margin-top:30px;" border @selection-change="handleSelectionChange">
<el-table-column
fixed="left"
align="center"
type="selection"
width="55"
/>
<el-table-column fixed="left" width="100" align="center" label="客户名称">
<template slot-scope="scope">
{{ scope.row.name }}
</template>
</el-table-column>
<el-table-column fixed="left" width="100" align="center" label="公司名称">
<template slot-scope="scope">
{{ scope.row.company }}
</template>
</el-table-column>
<el-table-column width="100" align="center" label="行业">
<template slot-scope="scope">
{{ scope.row.industry }}
</template>
</el-table-column>
<el-table-column width="100" align="center" label="部门">
<template slot-scope="scope">
{{ scope.row.department }}
</template>
</el-table-column>
<el-table-column width="100" align="center" label="职位">
<template slot-scope="scope">
{{ scope.row.duties }}
</template>
</el-table-column>
<el-table-column width="150" align="center" label="电话">
<template slot-scope="scope">
{{ scope.row.tel_phone }}
</template>
</el-table-column>
<el-table-column width="150" align="center" label="手机号码">
<template slot-scope="scope">
{{ scope.row.mobile_phone }}
</template>
</el-table-column>
<el-table-column width="200" align="center" label="微信号">
<template slot-scope="scope">
{{ scope.row.wechat }}
</template>
</el-table-column>
<el-table-column width="200" align="center" label="抖音号">
<template slot-scope="scope">
{{ scope.row.dy }}
</template>
</el-table-column>
<el-table-column width="200" align="center" label="邮箱">
<template slot-scope="scope">
{{ scope.row.email }}
</template>
</el-table-column>
<el-table-column width="300" align="center" label="地址">
<template slot-scope="scope">
{{ scope.row.address }}
</template>
</el-table-column>
<el-table-column width="100" align="center" label="跟进状态">
<template slot-scope="scope">
{{ scope.row.follow_status_text }}
</template>
</el-table-column>
<el-table-column width="100" align="center" label="客户来源">
<template slot-scope="scope">
{{ scope.row.from_text }}
</template>
</el-table-column>
<el-table-column width="100" align="center" label="客户类型">
<template slot-scope="scope">
{{ scope.row.type_text }}
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import { getOption, addImport } from '@/api/customer/private'
import UploadExcelComponent from '@/components/UploadExcel/index.vue'
import { formatImportData } from '@/utils'
import waves from '@/directive/waves/index.js'
import { Message } from 'element-ui' // 水波纹指令
export default {
components: { UploadExcelComponent },
directives: {
waves
},
data() {
return {
downloadLoading: false,
importDemo: {
filename: '客户列表demo',
autoWidth: true,
bookType: 'xlsx'
},
loading: false,
typeOptions: [],
fromOptions: [],
followStatusOptions: [],
excelData: [],
multipleSelection: [],
headerFiled: {
'name': '客户名称',
'company': '公司名称',
'industry': '行业',
'department': '部门',
'duties': '职位',
'tel_phone': '电话',
'mobile_phone': '手机号码',
'wechat': '微信号',
'dy': '抖音号',
'email': '邮箱',
'address': '地址',
'follow_status': '跟进状态',
'from': '客户来源',
'type': '客户类型'
}
}
},
computed: {
},
created() {
this.getOption()
},
methods: {
getOption() {
getOption().then((res) => {
const { type_maps, from_maps, follow_status_maps } = res.data
this.typeOptions = type_maps
this.fromOptions = from_maps
this.followStatusOptions = follow_status_maps
})
},
handleSelectionChange(val) {
this.multipleSelection = val
},
handleImport() {
if (this.multipleSelection.length <= 0) {
Message.warning('请选择要导入的数据')
return
}
this.loading = true
addImport({ data: this.multipleSelection }).then(() => {
this.multipleSelection = []
this.excelData = []
Message.success('导入成功')
}).finally(() => {
this.loading = false
})
},
handleImportDemo() {
this.downloadLoading = true
import('@/vendor/Export2Excel').then(excel => {
const tHeader = Object.values(this.headerFiled)
const data = []
excel.export_json_to_excel({
header: tHeader,
data,
filename: this.importDemo.filename,
autoWidth: this.importDemo.autoWidth,
bookType: this.importDemo.bookType
})
this.downloadLoading = false
})
},
beforeUpload(file) {
const isLt1M = file.size / 1024 / 1024 < 1
if (isLt1M) {
return true
}
this.$message({
message: 'Please do not upload files larger than 1m in size.',
type: 'warning'
})
return false
},
handleSuccess({ results, header }) {
const data = formatImportData(results, this.headerFiled)
data.forEach((item) => {
const typeOption = this.typeOptions.find((option) => option.name === item.type)
if (typeOption) {
item.type = typeOption.id
item.type_text = typeOption.name
} else {
item.type = ''
item.type_text = ''
}
const fromOption = this.fromOptions.find((option) => option.name === item.from)
if (fromOption) {
item.from = fromOption.id
item.from_text = fromOption.name
} else {
item.from = ''
item.from_text = ''
}
const followStatusOption = this.followStatusOptions.find((option) => option.name === item.follow_status)
if (followStatusOption) {
item.follow_status = followStatusOption.id
item.follow_status_text = followStatusOption.name
} else {
item.follow_status = ''
item.follow_status_text = ''
}
})
this.excelData = data
}
}
}
</script>
<style lang="scss" scoped>
.app-container {
.roles-table {
margin-top: 30px;
}
.permission-tree {
margin-bottom: 30px;
}
}
</style>
This diff is collapsed.
<template>
<div class="app-container">
<div class="button-container">
<el-button :loading="loading" type="success" @click="submitForm">
提交
</el-button>
</div>
<el-form ref="addForm" label-width="80px" :model="form" :rules="rules">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>基本信息</span>
</div>
<el-row>
<el-col :span="8">
<el-form-item prop="name" label-width="100px" label="客户名称:">
<el-input v-model="form.name" placeholder="客户名称" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="company" label-width="100px" label="公司名称:">
<el-input v-model="form.company" placeholder="公司名称" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="industry" label-width="100px" label="行业:">
<el-input v-model="form.industry" placeholder="行业" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item prop="department" label-width="100px" label="部门:">
<el-input v-model="form.department" placeholder="部门" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="duties" label-width="100px" label="职位:">
<el-input v-model="form.duties" placeholder="职位" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item prop="from" label-width="100px" label="客户来源:">
<el-select v-model="form.from" placeholder="客户来源" clearable class="filter-item" style="width: 130px">
<el-option v-for="item in fromOptions" :key="item.key" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="type" label-width="100px" label="客户类型:">
<el-select v-model="form.type" placeholder="客户类型" clearable class="filter-item" style="width: 130px">
<el-option v-for="item in typeOptions" :key="item.key" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-card>
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>联系方式</span>
</div>
<el-row>
<el-col :span="8">
<el-form-item prop="tel_phone" label-width="100px" label="电话:">
<el-input v-model="form.tel_phone" placeholder="电话" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="mobile_phone" label-width="100px" label="手机号:">
<el-input v-model="form.mobile_phone" placeholder="手机号" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="wechat" label-width="100px" label="微信号:">
<el-input v-model="form.wechat" placeholder="微信号" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item prop="dy" label-width="100px" label="抖音号:">
<el-input v-model="form.dy" placeholder="抖音号" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="email" label-width="100px" label="邮箱:">
<el-input v-model="form.email" placeholder="邮箱" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="address" label-width="100px" label="地址:">
<el-input v-model="form.address" placeholder="地址" />
</el-form-item>
</el-col>
</el-row>
</el-card>
</el-form>
</div>
</template>
<script>
import { getOption, addCustomer } from '@/api/customer/public'
import { Message } from 'element-ui' // 粘性header组件
export default {
components: { },
data() {
return {
form: {
'name': '',
'company': '',
'industry': '',
'department': '',
'duties': '',
'tel_phone': '',
'mobile_phone': '',
'wechat': '',
'dy': '',
'email': '',
'address': '',
'follow_status': '',
'from': '',
'type': ''
},
rules: {
name: [{ required: true, message: '客户名称不能为空', trigger: 'blur' }],
company: [{ required: true, message: '公司名称不能为空', trigger: 'blur' }],
industry: [{ required: true, message: '行业不能为空', trigger: 'blur' }],
department: [{ required: true, message: '部门不能为空', trigger: 'blur' }],
duties: [{ required: true, message: '职位不能为空', trigger: 'blur' }]
},
loading: false,
typeOptions: [],
fromOptions: [],
followStatusOptions: []
}
},
created() {
this.getOption()
},
methods: {
getOption() {
getOption().then((res) => {
const { type_maps, from_maps, follow_status_maps } = res.data
this.typeOptions = type_maps
this.fromOptions = from_maps
this.followStatusOptions = follow_status_maps
})
},
submitForm() {
this.$refs['addForm'].validate((valid, fields) => {
if (valid) {
this.loading = true
addCustomer(this.form).then(() => {
Message.success('添加成功')
this.$refs['addForm'].clearValidate()
this.$refs['addForm'].resetFields()
}).finally(() => { this.loading = false })
}
})
}
}
}
</script>
<style lang="scss" scoped>
.button-container{
display: flex;
justify-content: space-between;
}
.box-card{
margin: 15px 0;
}
</style>
<template>
<div>
<el-form ref="form" :model="from" :rules="rules" label-width="80px" label-position="left">
<el-form-item label="营业执照" prop="business_license">
<el-input v-model="from.business_license" placeholder="营业执照" />
</el-form-item>
<el-form-item label="身份证" prop="identity">
<el-input v-model="from.identity" placeholder="身份证" />
</el-form-item>
<el-form-item label="银行账户" prop="bank_account">
<el-input v-model="from.bank_account" placeholder="银行账户" />
</el-form-item>
<el-form-item label="手机号码" prop="mobile_phone">
<el-input v-model="from.mobile_phone" placeholder="手机号码" />
</el-form-item>
</el-form>
</div>
</template>
<script>
import { editCustomerPrivate } from '@/api/customer/private'
export default {
name: 'EditPrivate',
props: {
from: {
type: Object,
default: null
}
},
data() {
return {
rules: { }
}
},
methods: {
validate() {
this.$refs['form'].validate((valid, fields) => {
return !!valid
}).then((valid) => {
return !!valid
})
return false
},
submit() {
return new Promise((resolve, reject) => {
editCustomerPrivate(this.form.id, this.form).then(() => {
resolve()
}).catch((e) => { reject(e) })
})
}
}
}
</script>
<style scoped>
</style>
<template>
<div class="app-container">
<div class="button-container">
<el-button @click="$router.go(-1)">
返回
</el-button>
<el-button :loading="loading" type="success" @click="submitForm">
提交
</el-button>
</div>
<el-form ref="editForm" label-width="80px" :model="form" :rules="rules">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>基本信息</span>
</div>
<el-row>
<el-col :span="8">
<el-form-item prop="name" label-width="100px" label="客户名称:">
<el-input v-model="form.name" placeholder="客户名称" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="company" label-width="100px" label="公司名称:">
<el-input v-model="form.company" placeholder="公司名称" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="industry" label-width="100px" label="行业:">
<el-input v-model="form.industry" placeholder="行业" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item prop="department" label-width="100px" label="部门:">
<el-input v-model="form.department" placeholder="部门" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="duties" label-width="100px" label="职位:">
<el-input v-model="form.duties" placeholder="职位" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item prop="from" label-width="100px" label="客户来源:">
<el-select v-model="form.from" placeholder="客户来源" clearable class="filter-item" style="width: 130px">
<el-option v-for="item in fromOptions" :key="item.key" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="type" label-width="100px" label="客户类型:">
<el-select v-model="form.type" placeholder="客户类型" clearable class="filter-item" style="width: 130px">
<el-option v-for="item in typeOptions" :key="item.key" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-card>
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>联系方式</span>
</div>
<el-row>
<el-col :span="8">
<el-form-item prop="tel_phone" label-width="100px" label="电话:">
<el-input v-model="form.tel_phone" placeholder="电话" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="mobile_phone" label-width="100px" label="手机号:">
<el-input v-model="form.mobile_phone" placeholder="手机号" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="wechat" label-width="100px" label="微信号:">
<el-input v-model="form.wechat" placeholder="微信号" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item prop="dy" label-width="100px" label="抖音号:">
<el-input v-model="form.dy" placeholder="抖音号" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="email" label-width="100px" label="邮箱:">
<el-input v-model="form.email" placeholder="邮箱" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="address" label-width="100px" label="地址:">
<el-input v-model="form.address" placeholder="地址" />
</el-form-item>
</el-col>
</el-row>
</el-card>
</el-form>
</div>
</template>
<script>
import { getOption, editCustomer } from '@/api/customer/public'
import { Message } from 'element-ui' // 粘性header组件
export default {
components: { },
data() {
return {
form: {
'name': '',
'company': '',
'industry': '',
'department': '',
'duties': '',
'tel_phone': '',
'mobile_phone': '',
'wechat': '',
'dy': '',
'email': '',
'address': '',
'follow_status': '',
'from': '',
'type': ''
},
rules: {
name: [{ required: true, message: '客户名称不能为空', trigger: 'blur' }],
company: [{ required: true, message: '公司名称不能为空', trigger: 'blur' }],
industry: [{ required: true, message: '行业不能为空', trigger: 'blur' }],
department: [{ required: true, message: '部门不能为空', trigger: 'blur' }],
duties: [{ required: true, message: '职位不能为空', trigger: 'blur' }]
},
loading: false,
typeOptions: [],
fromOptions: [],
followStatusOptions: []
}
},
created() {
this.form = Object.assign({}, this.$route.params && this.$route.params.row)
if (!this.form.id) {
this.$router.push({ path: '/' })
}
this.getOption()
},
beforeRouteLeave(to, from, next) {
this.$store.dispatch('tagsView/delView', from)
next()
},
methods: {
getOption() {
getOption().then((res) => {
const { type_maps, from_maps, follow_status_maps } = res.data
this.typeOptions = type_maps
this.fromOptions = from_maps
this.followStatusOptions = follow_status_maps
})
},
submitForm() {
this.$refs['editForm'].validate((valid, fields) => {
if (valid) {
this.loading = true
editCustomer(this.form.id, this.form).then(() => {
Message.success('编辑成功')
this.$router.go(-1)
}).finally(() => { this.loading = false })
}
})
}
}
}
</script>
<style lang="scss" scoped>
.button-container{
display: flex;
justify-content: space-between;
}
.box-card{
margin: 15px 0;
}
</style>
<template>
<div class="app-container">
<upload-excel-component :on-success="handleSuccess" :before-upload="beforeUpload" />
<el-button v-waves :loading="downloadLoading" class="filter-item" type="primary" icon="el-icon-download" @click="handleImportDemo">
模板下载
</el-button>
<el-button v-waves :loading="loading" class="filter-item" type="primary" icon="el-icon-upload2" @click="handleImport">
导入
</el-button>
<el-table max-height="500" :data="excelData" style="width: 100%;margin-top:30px;" border @selection-change="handleSelectionChange">
<el-table-column
fixed="left"
align="center"
type="selection"
width="55"
/>
<el-table-column fixed="left" width="100" align="center" label="客户名称">
<template slot-scope="scope">
{{ scope.row.name }}
</template>
</el-table-column>
<el-table-column fixed="left" width="100" align="center" label="公司名称">
<template slot-scope="scope">
{{ scope.row.company }}
</template>
</el-table-column>
<el-table-column width="100" align="center" label="行业">
<template slot-scope="scope">
{{ scope.row.industry }}
</template>
</el-table-column>
<el-table-column width="100" align="center" label="部门">
<template slot-scope="scope">
{{ scope.row.department }}
</template>
</el-table-column>
<el-table-column width="100" align="center" label="职位">
<template slot-scope="scope">
{{ scope.row.duties }}
</template>
</el-table-column>
<el-table-column width="150" align="center" label="电话">
<template slot-scope="scope">
{{ scope.row.tel_phone }}
</template>
</el-table-column>
<el-table-column width="150" align="center" label="手机号码">
<template slot-scope="scope">
{{ scope.row.mobile_phone }}
</template>
</el-table-column>
<el-table-column width="200" align="center" label="微信号">
<template slot-scope="scope">
{{ scope.row.wechat }}
</template>
</el-table-column>
<el-table-column width="200" align="center" label="抖音号">
<template slot-scope="scope">
{{ scope.row.dy }}
</template>
</el-table-column>
<el-table-column width="200" align="center" label="邮箱">
<template slot-scope="scope">
{{ scope.row.email }}
</template>
</el-table-column>
<el-table-column width="300" align="center" label="地址">
<template slot-scope="scope">
{{ scope.row.address }}
</template>
</el-table-column>
<el-table-column width="100" align="center" label="跟进状态">
<template slot-scope="scope">
{{ scope.row.follow_status_text }}
</template>
</el-table-column>
<el-table-column width="100" align="center" label="客户来源">
<template slot-scope="scope">
{{ scope.row.from_text }}
</template>
</el-table-column>
<el-table-column width="100" align="center" label="客户类型">
<template slot-scope="scope">
{{ scope.row.type_text }}
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import { getOption, addImport } from '@/api/customer/public'
import UploadExcelComponent from '@/components/UploadExcel/index.vue'
import { formatImportData } from '@/utils'
import waves from '@/directive/waves/index.js'
import { Message } from 'element-ui' // 水波纹指令
export default {
components: { UploadExcelComponent },
directives: {
waves
},
data() {
return {
downloadLoading: false,
importDemo: {
filename: '客户列表demo',
autoWidth: true,
bookType: 'xlsx'
},
loading: false,
typeOptions: [],
fromOptions: [],
followStatusOptions: [],
excelData: [],
multipleSelection: [],
headerFiled: {
'name': '客户名称',
'company': '公司名称',
'industry': '行业',
'department': '部门',
'duties': '职位',
'tel_phone': '电话',
'mobile_phone': '手机号码',
'wechat': '微信号',
'dy': '抖音号',
'email': '邮箱',
'address': '地址',
'follow_status': '跟进状态',
'from': '客户来源',
'type': '客户类型'
}
}
},
computed: {
},
created() {
this.getOption()
},
methods: {
getOption() {
getOption().then((res) => {
const { type_maps, from_maps, follow_status_maps } = res.data
this.typeOptions = type_maps
this.fromOptions = from_maps
this.followStatusOptions = follow_status_maps
})
},
handleSelectionChange(val) {
this.multipleSelection = val
},
handleImport() {
if (this.multipleSelection.length <= 0) {
Message.warning('请选择要导入的数据')
return
}
this.loading = true
addImport({ data: this.multipleSelection }).then(() => {
this.multipleSelection = []
this.excelData = []
Message.success('导入成功')
}).finally(() => {
this.loading = false
})
},
handleImportDemo() {
this.downloadLoading = true
import('@/vendor/Export2Excel').then(excel => {
const tHeader = Object.values(this.headerFiled)
const data = []
excel.export_json_to_excel({
header: tHeader,
data,
filename: this.importDemo.filename,
autoWidth: this.importDemo.autoWidth,
bookType: this.importDemo.bookType
})
this.downloadLoading = false
})
},
beforeUpload(file) {
const isLt1M = file.size / 1024 / 1024 < 1
if (isLt1M) {
return true
}
this.$message({
message: 'Please do not upload files larger than 1m in size.',
type: 'warning'
})
return false
},
handleSuccess({ results, header }) {
const data = formatImportData(results, this.headerFiled)
data.forEach((item) => {
const typeOption = this.typeOptions.find((option) => option.name === item.type)
if (typeOption) {
item.type = typeOption.id
item.type_text = typeOption.name
} else {
item.type = ''
item.type_text = ''
}
const fromOption = this.fromOptions.find((option) => option.name === item.from)
if (fromOption) {
item.from = fromOption.id
item.from_text = fromOption.name
} else {
item.from = ''
item.from_text = ''
}
const followStatusOption = this.followStatusOptions.find((option) => option.name === item.follow_status)
if (followStatusOption) {
item.follow_status = followStatusOption.id
item.follow_status_text = followStatusOption.name
} else {
item.follow_status = ''
item.follow_status_text = ''
}
})
this.excelData = data
}
}
}
</script>
<style lang="scss" scoped>
.app-container {
.roles-table {
margin-top: 30px;
}
.permission-tree {
margin-bottom: 30px;
}
}
</style>
This diff is collapsed.
......@@ -113,11 +113,6 @@ export default {
created() {
this.init()
},
beforeRouteEnter(to, from, next) {
// 在路由进入前执行操作
// 无法访问组件实例的 this
next()
},
beforeRouteLeave(to, from, next) {
this.$store.dispatch('tagsView/delView', from)
next()
......@@ -126,8 +121,7 @@ export default {
init() {
const id = this.$route.params && this.$route.params.id
if (!id) {
const parentMatchedRoute = this.$route.matched[this.$route.matched.length - 2]
this.$router.push({ name: parentMatchedRoute.name })
this.$router.push({ path: '/' })
return
}
this.tempRoute = Object.assign({}, this.$route)
......
......@@ -106,6 +106,23 @@ export default {
create: 'Create'
},
treeData: [],
columns: [
{
label: 'ID',
key: 'id',
expand: true
},
{
label: 'Event',
key: 'event',
width: 200,
align: 'left'
},
{
label: 'Scope',
key: 'scope'
}
],
treeSelectData: [],
form: {
parent_id: 0,
......@@ -153,7 +170,7 @@ export default {
const childrenNav = []
this.listToTree(menus, childrenNav, 0)
rootRouter.children = childrenNav
this.treeData = [rootRouter]
this.treeData = childrenNav
const rootRouterSelect = {
value: 0,
......
......@@ -36,7 +36,13 @@ module.exports = {
warnings: false,
errors: true
},
before: require('./mock/mock-server.js')
// before: require('./mock/mock-server.js'),
proxy: {
'/api': {
target: process.env.VUE_APP_BASE_API,
changeOrigin: true
}
}
},
configureWebpack: {
// provide the app's title in webpack's name field, so that
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment