Nginx反向代理配置

代理到 /api 的操作配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server {
listen 9099;
server_name localhost;

location / {
root /opt/service/manager/frontend;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}

location ^~ /api {
proxy_pass http://xxxxxxx/;
}
}

Nginx同个端口配置多个项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server {
listen 9099;
server_name localhost;

# 官网
location / {
root /opt/service/manager/frontend/web;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}

# 后台
location /super/admin {
alias /opt/service/manager/frontend/admin;
index index.html index.htm;
try_files $uri $uri/ /super/admin/index.html;
}

location ^~ /api {
# 代理到后端接口地址
proxy_pass http://11.111.111.111:8080/;
}
}

这样就可以使用 http://11.111.111.111:9099 访问官网,使用 http://11.111.111.111:9099/super/admin 访问后台了。


Nginx同个端口配置移动端与PC端项目

我们想要在一个端口上配置PC端与移动端项目,并在手机上打开显示移动端页面,在PC端打开显示PC端页面。

可以在 Nginx 下做如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server {
listen 8080;
server_name localhost;
index index.html;
root /opt/service/frontend/pc; #PC端的路径

location / {
root /opt/service/frontend/pc; #PC端的路径
if ( $http_user_agent ~ "(MIDP)|(WAP)|(UP.Browser)|(Smartphone)|(Obigo)|(Mobile)|(AU.Browser)|(wxd.Mms)|(WxdB.Browser)|(CLDC)|(UP.Link)|(KM.Browser)|(UCWEB)|(SEMC\-Browser)|(Mini)|(Symbian)|(Palm)|(Nokia)|(Panasonic)|(MOT\-)|(SonyEricsson)|(NEC\-)|(Alcatel)|(Ericsson)|(BENQ)|(BenQ)|(Amoisonic)|(Amoi\-)|(Capitel)|(PHILIPS)|(SAMSUNG)|(Lenovo)|(Mitsu)|(Motorola)|(SHARP)|(WAPPER)|(LG\-)|(LG/)|(EG900)|(CECT)|(Compal)|(kejian)|(Bird)|(BIRD)|(G900/V1.0)|(Arima)|(CTL)|(TDG)|(Daxian)|(DAXIAN)|(DBTEL)|(Eastcom)|(EASTCOM)|(PANTECH)|(Dopod)|(Haier)|(HAIER)|(KONKA)|(KEJIAN)|(LENOVO)|(Soutec)|(SOUTEC)|(SAGEM)|(SEC\-)|(SED\-)|(EMOL\-)|(INNO55)|(ZTE)|(iPhone)|(Android)|(Windows CE)|(Wget)|(Java)|(curl)|(Opera)" ){
root /opt/service/frontend/h5; #移动端的路径
}
try_files $uri $uri/ /index.html;
index index.html index.htm;
}

# 下面还可以配置一些代理等等
}

Nginx配置ws

http 下新增:

1
2
3
4
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

server 下做如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
server {
listen 80;
server_name 域名/localhost;

location / {
root /opt/service/...;
index index.html index.htm;
try_files $uri $uri/ /index.html;

proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 3600s;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}

location ^~ /api {
proxy_pass http://xxx/;
}
}

在项目的 .env.production 文件可以配置 ws 路径:

1
2
3
4
5
6
7
ENV = 'production'

# base api
VUE_APP_BASE_API = '/api'

# ws
VUE_APP_WS_API = 'ws://xxx:9000'

预览文件功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<template>
<div class="preview">
<el-button @click="getfileSrc()">预览</el-button>
<el-dialog
:visible.sync="fileshow"
:close-on-click-modal="false"
class="pdfshow-dialog"
title="预览"
width="90%"
top="2%"
>
<iframe
:src='fileSrc'
width="100%"
height="100%"
frameborder="0"
scrolling="auto"
style="position:absolute;left: 0px;z-index:1000"
v-if="isImg == false"
>
</iframe>
<div v-else-if="isImg == true">
<el-image
style="width: 10%; height: 10%"
:src="fileSrc"
:preview-src-list="srcList"
>
</el-image>
</div>
<span
slot="footer"
class="dialog-footer"
>
</span>
</el-dialog>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<script>
export default {
name: 'preview',
props: ['fileUrl'],
data() {
return {
fileshow: false,
fileSrc: '',
isImg: false,
srcList: []
}
},
methods: {
getfileSrc() {
if (/\.(xlsx|xls|doc|docx)$/.test(this.fileUrl)) {
//Word,Excel 文件
this.fileSrc =
'https://view.officeapps.live.com/op/view.aspx?src=' + this.fileUrl
this.fileshow = true
} else if (/\.(pdf|PDF)$/.test(this.fileUrl)) {
//pdf文件
this.fileshow = true
this.fileSrc = this.fileUrl
} else if (/\.(png|jpg|webp|jpeg)$/.test(this.fileUrl)) {
this.isImg = true
this.fileshow = true
this.fileSrc = this.fileUrl
this.srcList.push(this.fileUrl)
} else {
this.$message.warning('该格式暂不支持预览,请直接下载查看')
}
}
}
}
</script>

使用:

1
<preview :fileUrl="url地址"></preview>

file-online-preview


word、pdf文件内容比较

Draftable


element引入骨架屏

这里使用 vue-elementui-skeleton

安装:

1
npm i vue-elementui-skeleton

引入:

1
2
3
4
5
6
7
8
9
10
11
12
import VueElementUISkeleton from 'vue-elementui-skeleton';
Vue.use(VueElementUISkeleton);

// 可以设置选项的全局默认值和指令名称
/*
Vue.use(VueElementUISkeleton, {
directiveName: 'my-skeleton',
rows: 10,
radius: 3,
bg: 'red'
});
*/

使用:

1
<el-table v-skeleton="loading"></el-table>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export default {
data() {
return {
loading: false
};
},
mounted() {
this.loading = true;
this.getList()
},
methods: {
getList(){
this.loading = false;
}
}
};

实现语音播报功能

这里我们使用 百度TTS 来实现。

首先在 utils 文件夹下新建 voicePrompt.js

1
2
3
4
5
6
7
function voicePrompt(text) {
new Audio('http://tts.baidu.com/text2audio?lan=zh&ie=UTF-8&spd=6&text=' + text).play();
}

export {
voicePrompt
}

main.js 文件中导入:

1
2
import * as voicePromptFun from './utils/voicePrompt' 
Vue.prototype.voicePrompt = voicePromptFun.voicePrompt

使用:

1
this.voicePrompt('哈喽');

百度TTS 相关参数解释:

  1. lan=zh:语言,英文则为 lan=en
  2. ie=UTF-8:文字格式;
  3. spd=6:语速,范围是 1~9 的数字;
  4. text=:需要播报的文字;

element表格复制每一行特定内容

使用 vue-clipboard2 来实现此功能。

安装:

1
npm install --save vue-clipboard2

main.js 引入:

1
2
import VueClipboard from 'vue-clipboard2'
Vue.use(VueClipboard)

使用:

1
2
3
4
5
6
7
8
9
10
11
12
<el-table :data="dataList">
<el-table-column
prop="content"
label="复制内容"
>
<template scope="scope">
<div @click="copyContent(scope.row.content)">
{{scope.row.content}}
</div>
</template>
</el-table-column>
</el-table>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<script>
export default {
methods: {
// 复制订单号
copyContent(content) {
this.$copyText(content)
.then(res => {
this.$message({
showClose: true,
message: '已复制内容: ' + content,
type: 'info'
})
})
.catch(err => {
this.$message({
showClose: true,
message: '复制失败,请手动复制',
type: 'error'
})
})
},
}
}
</script>

element-admin打包去除log打印

编辑 vue.config.js 文件:

1
2
3
4
5
6
7
8
9
module.exports = {
chainWebpack(config) {
// 去除log
config.optimization.minimizer('terser').tap(options => {
options[0].terserOptions.compress.drop_console = true;
return options;
})
}
}

el-tabs切换刷新数据并跳转到指定的tab上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<el-tabs
tab-position="left"
v-model="activeName"
@tab-click="handleTabClick"
>
<el-tab-pane
label="Demo1"
name="demo1"
>
<DemoOne v-if="tabRefresh.demo1" />
</el-tab-pane>
<el-tab-pane
label="Demo2"
name="demo2"
>
<DemoTwo v-if="tabRefresh.demo2" />
</el-tab-pane>
</el-tabs>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
export default {
data() {
return {
activeName: "demo1",
tabRefresh: {
demo1: true,
demo2: false
}
};
},
created() {
this.jumpTab();
},
methods: {
jumpTab() {
if (this.$route.query.activeName) {
this.activeName = this.$route.query.activeName;
for (let i in this.tabRefresh) {
if (i == this.$route.query.activeName) {
this.switchTab(i);
}
}
}
},
handleTabClick(tab, event) {
switch (this.activeName) {
case "demo1":
this.switchTab("demo1");
break;
case "demo2":
this.switchTab("demo2");
break;
default:
}
},
switchTab(tab) {
for (let [key, value] of Object.entries(this.tabRefresh)) {
if (key == tab) {
this.tabRefresh[key] = true;
} else {
this.tabRefresh[key] = false;
}
}
}
}
};

封装axios

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import axios from 'axios'
import { MessageBox, Message, Loading } from 'element-ui'
import { getToken } from '@/utils/auth'

const service = axios.create({
baseURL: process.env.NODE_ENV === 'production' ? process.env.VUE_APP_BASE_API : '/api',
// withCredentials: true, // 跨域请求时发送cookies
timeout: 60000
})

// 请求拦截器
service.interceptors.request.use(
config => {

// 在发送请求之前做一些事情
if (getToken()) {
config.headers['X-Token'] = getToken()
}
config.headers['Content-Type'] = 'application/json'
return config
},
error => {
console.log(error)
return Promise.reject(error)
}
)

// 响应拦截器
service.interceptors.response.use(
response => {
// 通过自定义代码确定请求状态
const res = response.data

// 获取文件流直接返回...
if (res.type == 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') {
return response
}

// 如果状态码不是 0,则判断为错误。根据需求自行修改
if (res.code !== 0) {
// ......
return Promise.reject(new Error(res.msg || 'Error'))
} else {
return res
}
},
error => {
console.log('err' + error)
if (error.toString().indexOf('Error: timeout') !== -1) {
Message({
message: '网络请求超时',
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
} else {
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
}
return Promise.reject(error)
}
)

/**
* 文件上传封装
* @param url
* @param data
*/
export function upload(url, file, data) {
const formData = new FormData()
formData.append('file', file)

// 附加数据
if (data) {
Object.keys(data).forEach((key) => {
formData.append(key, data[key])
})
}

return new Promise((resolve, reject) => {
// 打开
const loading = Loading.service({
text: '正在上传数据...',
background: 'rgba(0, 0, 0, 0.7)'
})

service.request({
url: url,
method: 'post',
data: formData,
timeout: 1200000
}).then(response => {
loading.close()
resolve(response)
}).catch(err => {
loading.close()
reject(err)
})
})
}

/**
* 下载模板封装
* @param url
* @param data
*/
export function downloadTemplate(url, data, fileName) {
return new Promise((resolve, reject) => {
// 打开
const loading = Loading.service({
text: '正在下载数据...',
background: 'rgba(0, 0, 0, 0.7)'
})

service.request({
url: url,
method: 'post',
data: data,
timeout: 1200000,
responseType: 'blob'
}).then(res => {
loading.close()

// 文件下载
const blob = new Blob([res.data], {
// 下载xlsx文件
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})

// 获得文件名称
let link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.setAttribute('download', fileName)
link.click()
link = null
Message.success('模板下载成功!')

}).catch(err => {
loading.close()
reject(err)
})
})
}

export default service

文件上传的使用方法:

  1. 创建 /src/api/file.js 文件:

    1
    2
    3
    4
    5
    import { upload } from '@/utils/request'

    export function importExcelApi(file) {
    return upload('/non/ledger/import', file)
    }
  2. 在需要的页面引入并使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <div>
    <el-button
    type="warning"
    size="small"
    icon="el-icon-upload"
    @click="chooseFile"
    >上传导入</el-button>
    <input
    ref="upFile"
    class="file"
    name="file"
    type="file"
    style="display: none;"
    @change="doImport"
    >
    </div>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    import { importExcelApi } from "@/api/file";
    export default {
    methods: {
    // 上传导入
    chooseFile: function() {
    this.$refs.upFile.dispatchEvent(new MouseEvent("click"));
    },
    doImport(e) {
    const file = e.target.files[0];

    importExcelApi(file).then(res => {
    console.log(res);
    if (res.code !== 0) {
    this.$alert(res.msg, "导入信息", {
    dangerouslyUseHTMLString: true
    });
    } else {
    this.$message({
    message: "数据导入成功!",
    type: "success"
    });
    }
    });
    }
    }
    };

element中Select选择器实现可选可输

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<el-select
v-model="form.bindValue"
placeholder="请选择"
default-first-option
filterable
@blur="selectBlur($event)"
@focus="selectFocus($event)"
ref="selectRef"
>
<el-option
v-for="item in list"
:key="item.id"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
export default {
data() {
return {
list: [],
form: {
bindValue: "",
}
};
},
methods: {
selectBlur(e) {
if (e.target.value) {
this.$set(this.form, "bindValue", e.target.value);
}
},
selectFocus(e) {
let value = e.target.value;
setTimeout(() => {
let input = this.$refs.selectRef.$children[0].$refs.input;
input.value = value;
});
}
}
};


持续更新中!