├── .browserslistrc
├── .gitignore
├── README.md
├── babel.config.js
├── config.json
├── package-lock.json
├── package.json
├── public
├── favicon.ico
└── index.html
├── src
├── App.vue
├── assets
│ ├── images
│ │ ├── 404.svg
│ │ ├── bg.svg
│ │ ├── default_avatar.png
│ │ └── logo.svg
│ └── style
│ │ ├── base.css
│ │ ├── element.css
│ │ └── normalize.css
├── components
│ └── content
│ │ ├── add-form
│ │ └── addForm.vue
│ │ ├── base-echarts
│ │ ├── baseEcharts.vue
│ │ └── data
│ │ │ ├── china.json
│ │ │ └── geoCoordMap.js
│ │ ├── edit-form
│ │ └── editFrom.vue
│ │ ├── el-card
│ │ └── elCard.vue
│ │ ├── nav-aside
│ │ └── navAside.vue
│ │ ├── nav-header
│ │ └── navHeader.vue
│ │ ├── search-form
│ │ └── searchForm.vue
│ │ └── table-form
│ │ └── tableForm.vue
├── main.js
├── plugins
│ ├── axios
│ │ ├── axios.js
│ │ └── index.js
│ ├── element
│ │ └── element.js
│ └── socket
│ │ └── socket.js
├── router
│ └── index.js
├── store
│ ├── global
│ │ └── index.js
│ ├── index.js
│ └── login
│ │ └── index.js
├── utils
│ └── index.js
└── views
│ ├── 404.vue
│ ├── login
│ ├── index.vue
│ └── web
│ │ ├── login.vue
│ │ └── register.vue
│ └── main
│ ├── admin
│ ├── api
│ │ └── admin-apis.js
│ ├── config
│ │ ├── feedback-config.js
│ │ ├── order-config.js
│ │ ├── shop-config.js
│ │ └── user-config.js
│ ├── index.vue
│ ├── router
│ │ └── admin-router.js
│ └── web
│ │ ├── feedback
│ │ └── feedback.vue
│ │ ├── order
│ │ └── order.vue
│ │ ├── shop
│ │ └── shop.vue
│ │ └── user
│ │ └── user.vue
│ ├── food
│ ├── api
│ │ └── food-apis.js
│ ├── config
│ │ └── food-config.js
│ ├── index.vue
│ ├── router
│ │ └── shop-router.js
│ └── web
│ │ ├── add
│ │ └── add.vue
│ │ └── info
│ │ ├── detailForm.vue
│ │ └── info.vue
│ ├── main.vue
│ ├── order
│ ├── api
│ │ └── readme.md
│ ├── index.vue
│ ├── router
│ │ └── order-router.js
│ └── web
│ │ ├── all
│ │ └── all.vue
│ │ └── today
│ │ └── today.vue
│ ├── overview
│ ├── overview.vue
│ └── router
│ │ └── overview-router.js
│ ├── profile
│ ├── api
│ │ └── user-apis.js
│ ├── index.vue
│ ├── router
│ │ └── profile-router.js
│ └── web
│ │ ├── feedback
│ │ ├── feedback.vue
│ │ ├── myFeedback.vue
│ │ └── 富文本.vue
│ │ └── myself
│ │ └── myself.vue
│ └── shop
│ ├── api
│ └── shop-apis.js
│ ├── config
│ ├── echarts-config.js
│ └── shop-config.js
│ ├── index.vue
│ ├── router
│ └── shop-router.js
│ └── web
│ ├── bill
│ └── bill.vue
│ ├── comment
│ └── comment.vue
│ ├── info
│ ├── drawerForm.vue
│ └── info.vue
│ └── register
│ └── register.vue
└── vue.config.js
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not dead
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 | pnpm-debug.log*
14 |
15 | # Editor directories and files
16 | .idea
17 | .vscode
18 | *.suo
19 | *.ntvs*
20 | *.njsproj
21 | *.sln
22 | *.sw?
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-front-cms
2 | 外卖点餐的后台管理系统, 对应的前台点餐系统: [uniapp-order](https://github.com/yg10323/uniapp-order) 和 后端接口: [koa-apis](https://github.com/yg10323/koa-apis)
3 |
4 | ## 安装依赖
5 | ```
6 | npm install
7 | ```
8 | ## 运行
9 | ```
10 | npm start
11 | ```
12 | ## 项目的一些介绍
13 | ### 一. 扫码登陆
14 | 扫码功能需配合 [uni-app的前台点餐项目](https://github.com/yg10323/uniapp-order) 使用
15 | 二维码由node后台生成, 扫码登陆功能由自己简单实现, 由socket.io、redis、BroadcastChannel配合完成
16 |
17 | 
18 | ### 二. 卖家相关的功能
19 | #### 2.1 总览
20 | 
21 | #### 2.2 店铺管理
22 | 店铺管理包括: 注册店铺、店铺信息查看编辑、店铺流水以及店铺评价
23 | ##### 2.2.1 注册店铺
24 | 一个卖家账号只允许注册一个店铺, 并且在注册店铺之前要进行实名登记(在个人中心中)
25 | (gif图像加载慢, 请耐心等待)
26 |
27 | 
28 | ##### 2.2.2 店铺信息管理
29 | 
30 | ##### 2.2.3 店铺流水
31 | 使用echarts做的图表展示
32 |
33 | 
34 | ##### 2.2.4 评价管理
35 | 卖家可对买家的评价进行评价
36 |
37 | 
38 | #### 2.3 食品管理
39 | 食品管理包括: 新增食品信息和食品信息查看、编辑
40 | ##### 2.3.1新增食品
41 | 
42 | ##### 2.3.2 食品信息管理
43 | 
44 | #### 2.4 订单管理
45 | 订单管理包括: 今日订单和往日订单
46 | 因为数据的展示是一样的, 只是订单生成的日期不同, 所以下面图片只展示一个
47 |
48 | 
49 | #### 2.5 个人中心
50 | 个人中心包括: 个人信息、工单反馈、个人工单
51 | ##### 2.5.1 个人信息
52 | 账号刚注册成功之后, 是需要实名认证以后才能注册店铺
53 |
54 | 
55 | ##### 2.5.2 工单反馈
56 | 
57 | ##### 2.5.3 我的工单
58 | 可以与管理员端进行互相的信息反馈
59 |
60 | 
61 | ### 3. 管理员相关的功能
62 | 管理员主要有四大功能: 用户管理、店铺管理、订单管理、工单管理
63 | #### 3.1 用户管理
64 | 
65 | #### 3.2 店铺管理
66 | 
67 | #### 3.3 订单管理
68 | 
69 | #### 3.4 工单管理
70 | 
71 |
72 |
73 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ],
5 | plugins: [
6 | [
7 | "component",
8 | {
9 | "libraryName": "element-ui",
10 | "styleLibraryName": "theme-chalk"
11 | }
12 | ]
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "axios_config": {
3 | "host": "http://124.70.20.215",
4 | "port": 2140
5 | },
6 | "web_socket_config": {
7 | "host": "http://124.70.20.215",
8 | "port": 1070
9 | }
10 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-front-cms",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "start": "npm run serve"
9 | },
10 | "dependencies": {
11 | "axios": "^0.24.0",
12 | "core-js": "^3.6.5",
13 | "echarts": "^5.2.2",
14 | "element-ui": "^2.15.6",
15 | "socket.io-client": "^4.4.0",
16 | "vue": "^2.6.11",
17 | "vue-router": "^3.2.0",
18 | "vuex": "^3.4.0"
19 | },
20 | "devDependencies": {
21 | "@vue/cli-plugin-babel": "~4.5.0",
22 | "@vue/cli-plugin-router": "~4.5.0",
23 | "@vue/cli-plugin-vuex": "~4.5.0",
24 | "@vue/cli-service": "~4.5.0",
25 | "babel-plugin-component": "^1.1.1",
26 | "sass": "^1.26.5",
27 | "sass-loader": "^8.0.2",
28 | "vue-template-compiler": "^2.6.11"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yg10323/vue-cms/cd0fa2da6ea63bf7c908d4b84d891272c1b9fbba/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | <%= htmlWebpackPlugin.options.title %>
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
10 |
15 |
--------------------------------------------------------------------------------
/src/assets/images/bg.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/images/default_avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yg10323/vue-cms/cd0fa2da6ea63bf7c908d4b84d891272c1b9fbba/src/assets/images/default_avatar.png
--------------------------------------------------------------------------------
/src/assets/images/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/style/base.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | }
5 |
6 | html, body, #app {
7 | height: 100%;
8 | width: 100%;
9 | }
10 |
11 | #app {
12 | background: url(../images/bg.svg);
13 | }
14 |
15 | li {
16 | list-style: none;
17 | }
18 |
19 | /* 滚动条样式 */
20 | ::-webkit-scrollbar {
21 | /* display: none; */
22 | width: .5rem;
23 | height: .5rem;
24 | background: hsla(0, 0%, 100%, 0.6);
25 | }
26 |
27 | ::-webkit-scrollbar-track {
28 | border-radius: 0;
29 | }
30 |
31 | ::-webkit-scrollbar-thumb {
32 | border-radius: 0;
33 | background-color: rgba(95, 95, 95, .4);
34 | transition: all .2s;
35 | border-radius: .5rem;
36 | }
37 |
38 | .v-modal {
39 | position: static !important;
40 | }
--------------------------------------------------------------------------------
/src/assets/style/element.css:
--------------------------------------------------------------------------------
1 | /* 对element-ui的一些样式做全局修改 */
2 |
3 |
4 | /* 水平滚动条 */
5 | .el-scrollbar__wrap {
6 | overflow-x: hidden !important;
7 | }
--------------------------------------------------------------------------------
/src/assets/style/normalize.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
2 |
3 | /* Document
4 | ========================================================================== */
5 |
6 | /**
7 | * 1. Correct the line height in all browsers.
8 | * 2. Prevent adjustments of font size after orientation changes in iOS.
9 | */
10 |
11 | html {
12 | line-height: 1.15; /* 1 */
13 | -webkit-text-size-adjust: 100%; /* 2 */
14 | }
15 |
16 | /* Sections
17 | ========================================================================== */
18 |
19 | /**
20 | * Remove the margin in all browsers.
21 | */
22 |
23 | body {
24 | margin: 0;
25 | }
26 |
27 | /**
28 | * Render the `main` element consistently in IE.
29 | */
30 |
31 | main {
32 | display: block;
33 | }
34 |
35 | /**
36 | * Correct the font size and margin on `h1` elements within `section` and
37 | * `article` contexts in Chrome, Firefox, and Safari.
38 | */
39 |
40 | h1 {
41 | font-size: 2em;
42 | margin: 0.67em 0;
43 | }
44 |
45 | /* Grouping content
46 | ========================================================================== */
47 |
48 | /**
49 | * 1. Add the correct box sizing in Firefox.
50 | * 2. Show the overflow in Edge and IE.
51 | */
52 |
53 | hr {
54 | box-sizing: content-box; /* 1 */
55 | height: 0; /* 1 */
56 | overflow: visible; /* 2 */
57 | }
58 |
59 | /**
60 | * 1. Correct the inheritance and scaling of font size in all browsers.
61 | * 2. Correct the odd `em` font sizing in all browsers.
62 | */
63 |
64 | pre {
65 | font-family: monospace, monospace; /* 1 */
66 | font-size: 1em; /* 2 */
67 | }
68 |
69 | /* Text-level semantics
70 | ========================================================================== */
71 |
72 | /**
73 | * Remove the gray background on active links in IE 10.
74 | */
75 |
76 | a {
77 | background-color: transparent;
78 | }
79 |
80 | /**
81 | * 1. Remove the bottom border in Chrome 57-
82 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
83 | */
84 |
85 | abbr[title] {
86 | border-bottom: none; /* 1 */
87 | text-decoration: underline; /* 2 */
88 | text-decoration: underline dotted; /* 2 */
89 | }
90 |
91 | /**
92 | * Add the correct font weight in Chrome, Edge, and Safari.
93 | */
94 |
95 | b,
96 | strong {
97 | font-weight: bolder;
98 | }
99 |
100 | /**
101 | * 1. Correct the inheritance and scaling of font size in all browsers.
102 | * 2. Correct the odd `em` font sizing in all browsers.
103 | */
104 |
105 | code,
106 | kbd,
107 | samp {
108 | font-family: monospace, monospace; /* 1 */
109 | font-size: 1em; /* 2 */
110 | }
111 |
112 | /**
113 | * Add the correct font size in all browsers.
114 | */
115 |
116 | small {
117 | font-size: 80%;
118 | }
119 |
120 | /**
121 | * Prevent `sub` and `sup` elements from affecting the line height in
122 | * all browsers.
123 | */
124 |
125 | sub,
126 | sup {
127 | font-size: 75%;
128 | line-height: 0;
129 | position: relative;
130 | vertical-align: baseline;
131 | }
132 |
133 | sub {
134 | bottom: -0.25em;
135 | }
136 |
137 | sup {
138 | top: -0.5em;
139 | }
140 |
141 | /* Embedded content
142 | ========================================================================== */
143 |
144 | /**
145 | * Remove the border on images inside links in IE 10.
146 | */
147 |
148 | img {
149 | border-style: none;
150 | }
151 |
152 | /* Forms
153 | ========================================================================== */
154 |
155 | /**
156 | * 1. Change the font styles in all browsers.
157 | * 2. Remove the margin in Firefox and Safari.
158 | */
159 |
160 | button,
161 | input,
162 | optgroup,
163 | select,
164 | textarea {
165 | font-family: inherit; /* 1 */
166 | font-size: 100%; /* 1 */
167 | line-height: 1.15; /* 1 */
168 | margin: 0; /* 2 */
169 | }
170 |
171 | /**
172 | * Show the overflow in IE.
173 | * 1. Show the overflow in Edge.
174 | */
175 |
176 | button,
177 | input { /* 1 */
178 | overflow: visible;
179 | }
180 |
181 | /**
182 | * Remove the inheritance of text transform in Edge, Firefox, and IE.
183 | * 1. Remove the inheritance of text transform in Firefox.
184 | */
185 |
186 | button,
187 | select { /* 1 */
188 | text-transform: none;
189 | }
190 |
191 | /**
192 | * Correct the inability to style clickable types in iOS and Safari.
193 | */
194 |
195 | button,
196 | [type="button"],
197 | [type="reset"],
198 | [type="submit"] {
199 | -webkit-appearance: button;
200 | }
201 |
202 | /**
203 | * Remove the inner border and padding in Firefox.
204 | */
205 |
206 | button::-moz-focus-inner,
207 | [type="button"]::-moz-focus-inner,
208 | [type="reset"]::-moz-focus-inner,
209 | [type="submit"]::-moz-focus-inner {
210 | border-style: none;
211 | padding: 0;
212 | }
213 |
214 | /**
215 | * Restore the focus styles unset by the previous rule.
216 | */
217 |
218 | button:-moz-focusring,
219 | [type="button"]:-moz-focusring,
220 | [type="reset"]:-moz-focusring,
221 | [type="submit"]:-moz-focusring {
222 | outline: 1px dotted ButtonText;
223 | }
224 |
225 | /**
226 | * Correct the padding in Firefox.
227 | */
228 |
229 | fieldset {
230 | padding: 0.35em 0.75em 0.625em;
231 | }
232 |
233 | /**
234 | * 1. Correct the text wrapping in Edge and IE.
235 | * 2. Correct the color inheritance from `fieldset` elements in IE.
236 | * 3. Remove the padding so developers are not caught out when they zero out
237 | * `fieldset` elements in all browsers.
238 | */
239 |
240 | legend {
241 | box-sizing: border-box; /* 1 */
242 | color: inherit; /* 2 */
243 | display: table; /* 1 */
244 | max-width: 100%; /* 1 */
245 | padding: 0; /* 3 */
246 | white-space: normal; /* 1 */
247 | }
248 |
249 | /**
250 | * Add the correct vertical alignment in Chrome, Firefox, and Opera.
251 | */
252 |
253 | progress {
254 | vertical-align: baseline;
255 | }
256 |
257 | /**
258 | * Remove the default vertical scrollbar in IE 10+.
259 | */
260 |
261 | textarea {
262 | overflow: auto;
263 | }
264 |
265 | /**
266 | * 1. Add the correct box sizing in IE 10.
267 | * 2. Remove the padding in IE 10.
268 | */
269 |
270 | [type="checkbox"],
271 | [type="radio"] {
272 | box-sizing: border-box; /* 1 */
273 | padding: 0; /* 2 */
274 | }
275 |
276 | /**
277 | * Correct the cursor style of increment and decrement buttons in Chrome.
278 | */
279 |
280 | [type="number"]::-webkit-inner-spin-button,
281 | [type="number"]::-webkit-outer-spin-button {
282 | height: auto;
283 | }
284 |
285 | /**
286 | * 1. Correct the odd appearance in Chrome and Safari.
287 | * 2. Correct the outline style in Safari.
288 | */
289 |
290 | [type="search"] {
291 | -webkit-appearance: textfield; /* 1 */
292 | outline-offset: -2px; /* 2 */
293 | }
294 |
295 | /**
296 | * Remove the inner padding in Chrome and Safari on macOS.
297 | */
298 |
299 | [type="search"]::-webkit-search-decoration {
300 | -webkit-appearance: none;
301 | }
302 |
303 | /**
304 | * 1. Correct the inability to style clickable types in iOS and Safari.
305 | * 2. Change font properties to `inherit` in Safari.
306 | */
307 |
308 | ::-webkit-file-upload-button {
309 | -webkit-appearance: button; /* 1 */
310 | font: inherit; /* 2 */
311 | }
312 |
313 | /* Interactive
314 | ========================================================================== */
315 |
316 | /*
317 | * Add the correct display in Edge, IE 10+, and Firefox.
318 | */
319 |
320 | details {
321 | display: block;
322 | }
323 |
324 | /*
325 | * Add the correct display in all browsers.
326 | */
327 |
328 | summary {
329 | display: list-item;
330 | }
331 |
332 | /* Misc
333 | ========================================================================== */
334 |
335 | /**
336 | * Add the correct display in IE 10+.
337 | */
338 |
339 | template {
340 | display: none;
341 | }
342 |
343 | /**
344 | * Add the correct display in IE 10.
345 | */
346 |
347 | [hidden] {
348 | display: none;
349 | }
350 |
--------------------------------------------------------------------------------
/src/components/content/add-form/addForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
96 |
97 |
98 |
135 |
136 |
--------------------------------------------------------------------------------
/src/components/content/base-echarts/baseEcharts.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
49 |
50 |
--------------------------------------------------------------------------------
/src/components/content/base-echarts/data/geoCoordMap.js:
--------------------------------------------------------------------------------
1 | export const geoCoordMap = {
2 | '黑龙江': [127.9688, 45.368],
3 | '北京': [116.4551, 40.2539],
4 | "辽宁": [123.1238, 42.1216],
5 | "河北": [114.4995, 38.1006],
6 | "天津": [117.4219, 39.4189],
7 | "山西": [112.3352, 37.9413],
8 | "陕西": [109.1162, 34.2004],
9 | "甘肃": [103.5901, 36.3043],
10 | "宁夏": [106.3586, 38.1775],
11 | "青海": [101.4038, 36.8207],
12 | "新疆": [87.9236, 43.5883],
13 | "西藏": [91.11, 29.97],
14 | "四川": [103.9526, 30.7617],
15 | "重庆": [108.384366, 30.439702],
16 | "山东": [117.1582, 36.8701],
17 | "河南": [113.4668, 34.6234],
18 | "江苏": [118.8062, 31.9208],
19 | "安徽": [117.29, 32.0581],
20 | "湖北": [114.3896, 30.6628],
21 | "浙江": [119.5313, 29.8773],
22 | "福建": [119.4543, 25.9222],
23 | "江西": [116.0046, 28.6633],
24 | "湖南": [113.0823, 28.2568],
25 | "贵州": [106.6992, 26.7682],
26 | "云南": [102.9199, 25.4663],
27 | "广东": [113.12244, 23.009505],
28 | "广西": [108.479, 23.1152],
29 | "海南": [110.3893, 19.8516],
30 | '上海': [121.4648, 31.2891],
31 | };
--------------------------------------------------------------------------------
/src/components/content/edit-form/editFrom.vue:
--------------------------------------------------------------------------------
1 |
2 | 还没写
3 |
4 |
5 |
10 |
11 |
--------------------------------------------------------------------------------
/src/components/content/el-card/elCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
27 |
28 |
40 |
--------------------------------------------------------------------------------
/src/components/content/nav-aside/navAside.vue:
--------------------------------------------------------------------------------
1 |
2 |
49 |
50 |
51 |
84 |
85 |
143 |
144 |
--------------------------------------------------------------------------------
/src/components/content/nav-header/navHeader.vue:
--------------------------------------------------------------------------------
1 |
2 |
28 |
29 |
30 |
63 |
64 |
--------------------------------------------------------------------------------
/src/components/content/search-form/searchForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
68 |
69 |
70 |
118 |
119 |
--------------------------------------------------------------------------------
/src/components/content/table-form/tableForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 |
20 |
27 |
28 |
29 |
30 |
31 | {{ slotProps.row[propItem.prop] }}
32 |
33 |
34 |
35 |
38 |
39 |
40 |
41 |
42 |
55 |
56 |
57 |
58 |
89 |
90 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import router from './router'
4 | import store from './store'
5 | // import mavonEditor from 'mavon-editor'
6 | // import 'mavon-editor/dist/css/index.css'
7 | import registerElement from './plugins/element/element'
8 | import { SoClient } from './plugins/socket/socket'
9 | import { initStore } from './store'
10 | import api from './plugins/axios'
11 | import utils from './utils/index'
12 |
13 | Vue.config.productionTip = false
14 |
15 | initStore();//页面刷新后初始化vux中的数据
16 | registerElement(Vue);//注册element组件
17 |
18 |
19 | Vue.prototype.$socket = SoClient.getInstance;
20 | Vue.prototype.$api = api;
21 | Vue.prototype.$utils = utils
22 | // Vue.use(mavonEditor)
23 |
24 |
25 | new Vue({
26 | router,
27 | store,
28 | render: h => h(App)
29 | }).$mount('#app')
30 |
--------------------------------------------------------------------------------
/src/plugins/axios/axios.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import axios from "axios";
3 | import VueRouter from "vue-router";
4 | import { axios_config } from '../../../config.json'
5 |
6 | let loadingInstance;
7 |
8 | const instance = axios.create({
9 | baseURL: `${axios_config.host}:${axios_config.port}`,
10 | timeout: 60 * 1000,
11 | headers: {
12 | "Content-Type": "application/json;charset=UTF-8"
13 | },
14 | });
15 |
16 | instance.interceptors.request.use(
17 | config => {
18 | const reqObj = config;
19 | // 在发送请求之前做些什么
20 | // const reg = /\{(.+?)\}/g; // 匹配{}
21 | // if (reg.test(reqObj.url)) {
22 | // reqObj.url = reqObj.url.replace(reg, Object.values(reqObj.path)[0]);
23 | // }
24 | // 发送请求时携带token
25 | if (localStorage.getItem("token")) {
26 | reqObj.headers.Authorization = JSON.parse(localStorage.getItem("token"));
27 |
28 | }
29 | return reqObj;
30 | },
31 | err => Promise.reject(err)
32 | );
33 |
34 | instance.interceptors.response.use(
35 | response => {
36 | // console.log(response)
37 | // if (response.headers.token) {
38 | // sessionStorage.setItem("token", response.headers.token);
39 | // }
40 | const res = response.data;
41 | // 对响应数据做点什么
42 | if (res.code !== 200) {
43 | // 401 未认证
44 | if (res.code === 401) {
45 | localStorage.clear();
46 | setTimeout(() => location.reload(), 1000)
47 | }
48 | }
49 | return res;
50 | },
51 | err => Promise.reject(err)
52 | );
53 |
54 | Vue.prototype.$axios = instance
55 | export default instance;
56 |
--------------------------------------------------------------------------------
/src/plugins/axios/index.js:
--------------------------------------------------------------------------------
1 | import _axiosInstance from './axios';
2 |
3 | // 遍历,生成请求函数
4 | const trans = (apiObj) => {
5 | const obj = apiObj;
6 | Object.keys(obj).forEach((apiKey) => {
7 | const data = {
8 | ...obj[apiKey]
9 | }
10 | obj[apiKey] = (payload) => _axiosInstance({
11 | method: data.method,
12 | url: '/api' + data.url,
13 | ...payload
14 | });
15 | });
16 |
17 | return obj;
18 | };
19 |
20 | // -转驼峰
21 | function toCamelCase(str) {
22 | const pattern = /-([a-z])/g;
23 | return str.replace(pattern, (all, letter) => {
24 | // console.log(all, letter);
25 | return letter.toUpperCase();
26 | });
27 |
28 | }
29 |
30 | // https://webpack.docschina.org/guides/dependency-management/#requirecontext
31 | const apiAll = require.context('@/views', true, /-apis\.js$/);
32 | // console.log(apiAll);
33 | // key: 处理后的文件名, value: 文件内容中网络请求的方法名
34 | const moduleApis = {};
35 | // keys属性 {Function} -匹配成功模块的名字组成的数组
36 | apiAll.keys().map((key) => {
37 | // 先将-去掉,然后将小写a转换成大写A,最后取到 .的下标
38 | const suffixIndex = toCamelCase(key.match(/.*\/(.*\..*)/)[1]).indexOf('.');
39 | // 截取文件名
40 | const name = toCamelCase(key.match(/.*\/(.*\..*)/)[1]).substring(0, suffixIndex);
41 | moduleApis[name] = apiAll(key).default;
42 | return trans(moduleApis[name]);
43 | });
44 |
45 | export default moduleApis;
46 |
47 |
--------------------------------------------------------------------------------
/src/plugins/element/element.js:
--------------------------------------------------------------------------------
1 | // 导入组件
2 | import {
3 | Message,
4 | MessageBox,
5 | Notification,
6 | Button,
7 | Form,
8 | FormItem,
9 | Tabs,
10 | TabPane,
11 | Input,
12 | Empty,
13 | Container,
14 | Aside,
15 | Header,
16 | Main,
17 | Menu,
18 | MenuItem,
19 | MenuItemGroup,
20 | Submenu,
21 | Row,
22 | Col,
23 | Select,
24 | Option,
25 | DatePicker,
26 | Avatar,
27 | Dropdown,
28 | DropdownItem,
29 | DropdownMenu,
30 | Breadcrumb,
31 | BreadcrumbItem,
32 | Table,
33 | TableColumn,
34 | Pagination,
35 | Backtop,
36 | Divider,
37 | Link,
38 | Dialog,
39 | Scrollbar,
40 | Cascader,
41 | TimeSelect,
42 | Switch,
43 | Checkbox,
44 | CheckboxGroup,
45 | Radio,
46 | RadioGroup,
47 | Popover,
48 | Upload,
49 | Progress,
50 | Descriptions,
51 | DescriptionsItem,
52 | Tag,
53 | Result,
54 | Image,
55 | Collapse,
56 | CollapseItem,
57 | Drawer,
58 | Card,
59 | InputNumber,
60 | Popconfirm,
61 | Icon,
62 | RadioButton,
63 | Rate
64 | } from 'element-ui'
65 |
66 | const components = {
67 | Button,
68 | Form,
69 | FormItem,
70 | Tabs,
71 | TabPane,
72 | Input,
73 | Empty,
74 | Container,
75 | Aside,
76 | Header,
77 | Main,
78 | Menu,
79 | MenuItem,
80 | MenuItemGroup,
81 | Submenu,
82 | Row,
83 | Col,
84 | Select,
85 | Option,
86 | DatePicker,
87 | Avatar,
88 | Dropdown,
89 | DropdownItem,
90 | DropdownMenu,
91 | Breadcrumb,
92 | BreadcrumbItem,
93 | Table,
94 | TableColumn,
95 | Pagination,
96 | Backtop,
97 | Divider,
98 | Link,
99 | Dialog,
100 | Scrollbar,
101 | Cascader,
102 | TimeSelect,
103 | Switch,
104 | Checkbox,
105 | CheckboxGroup,
106 | Radio,
107 | RadioGroup,
108 | Popover,
109 | Upload,
110 | Progress,
111 | Descriptions,
112 | DescriptionsItem,
113 | Tag,
114 | Result,
115 | Image,
116 | Collapse,
117 | CollapseItem,
118 | Drawer,
119 | Card,
120 | InputNumber,
121 | Popconfirm,
122 | Icon,
123 | RadioButton,
124 | Rate
125 | }
126 |
127 |
128 | // 遍历注册, 将方法暴露
129 | export default function (Vue) {
130 | for (const el in components) {
131 | Vue.use(components[el])
132 | }
133 |
134 | Vue.prototype.$message = Message
135 | Vue.prototype.$messageBox = MessageBox
136 | Vue.prototype.$notify = Notification
137 | }
--------------------------------------------------------------------------------
/src/plugins/socket/socket.js:
--------------------------------------------------------------------------------
1 | import { io } from "socket.io-client";
2 | import { web_socket_config } from '../../../config.json'
3 |
4 | // socket客户端
5 | export class SoClient {
6 |
7 | constructor(action) {
8 | this.host = web_socket_config.host;
9 | this.port = web_socket_config.port;
10 | this.action = action || null;
11 | this.client = io(`${this.host}:${this.port}`)
12 | this.client.on("connect", () => {
13 | this.stid = this.client.id;
14 | console.log('scoket连接成功 ' + this.stid);
15 | })
16 |
17 | // 监听异常
18 | this.client.on('error', error => {
19 | console.log(error);
20 | })
21 |
22 | // 监听服务端是否断开
23 | this.client.on('disconnect', () => {
24 | console.log(`${this.stid} 连接已断开`);
25 | })
26 |
27 | this.initGetQrCode()
28 |
29 | // 启动心跳
30 | // this.heartCheck()
31 |
32 | return this.client;
33 | }
34 |
35 | // 用于某些刚连接好即需要通信的连接
36 | initGetQrCode() {
37 | if (this.action) {
38 | // console.log(222222222);
39 | this.client.emit(this.action);
40 | }
41 | }
42 |
43 | // 暂留 目前的服务器承受不住心跳包带来的压力, 所以暂时关闭心跳
44 | // 心跳包
45 | heartCheck() {
46 | this.heartTimer = setInterval(() => {
47 | this.client.emit('fromClient', JSON.stringify({
48 | cmd: "heart",
49 | content: {}
50 | }))
51 | }, 1000);
52 | }
53 |
54 | // 重置心跳
55 | resetHeartCheck() {
56 | this.heartTimer && clearInterval(this.heartTimer);
57 | this.heartCheck();
58 | }
59 |
60 | static getInstance(action) {
61 | return new SoClient(action);
62 | }
63 | };
64 |
65 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueRouter from 'vue-router'
3 |
4 |
5 | Vue.use(VueRouter)
6 |
7 | const routes = [
8 | {
9 | path: '/',
10 | redirect: '/main'
11 | },
12 | {
13 | path: "/main",
14 | name: "main",
15 | component: () => import('@/views/main/main')
16 | },
17 | {
18 | path: '/login',
19 | name: 'login',
20 | component: () => import('@/views/login/index')
21 | },
22 |
23 | {
24 | path: "*",
25 | name: "404",
26 | component: () => import('@/views/404')
27 | }
28 | ]
29 |
30 | const router = new VueRouter({
31 | // mode: 'history',
32 | base: process.env.BASE_URL,
33 | routes
34 | })
35 |
36 |
37 | // 避免冗余导航 (重复点击菜单栏报错问题)
38 | const originalPush = VueRouter.prototype.push;
39 | VueRouter.prototype.push = function push(location) {
40 | return originalPush.call(this, location).catch(err => err);
41 | };
42 |
43 | // 全局导航守卫
44 | router.beforeEach((to, from, next) => {
45 | // 前往非登录页之前判断有无登录
46 | if (to.path !== '/login') {
47 | const token = localStorage.getItem('token')
48 | if (!token) {
49 | next({ path: '/login' })
50 | }
51 |
52 | }
53 |
54 | // 重定向显示overview
55 | if (to.path === '/main') {
56 | const path = JSON.parse(localStorage.getItem('menus'))[0].path
57 | next({ path })
58 | }
59 |
60 | next()
61 | })
62 |
63 |
64 | export default router
65 |
--------------------------------------------------------------------------------
/src/store/global/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | state: {
3 | isCollapse: false,
4 | defaultActive: '/main/overview',
5 | },
6 | mutations: {
7 | // 控制侧边栏展开
8 | changeCollapse(state) {
9 | state.isCollapse = !state.isCollapse;
10 | },
11 | // 用户退出后清空缓存,初始化侧边栏选中状态
12 | clearStorage(state) {
13 | localStorage.clear();
14 | sessionStorage.clear();
15 | state.defaultActive = '1000'
16 | },
17 | // 侧边栏的选中状态
18 | setActive(state, active_id) {
19 | state.defaultActive = active_id;
20 | sessionStorage.setItem('defaultActive', active_id);
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import login from './login'
4 | import global from './global'
5 |
6 | Vue.use(Vuex)
7 |
8 | const store = new Vuex.Store({
9 | state: {},
10 | mutations: {},
11 | actions: {},
12 | modules: {
13 | login,
14 | global
15 | }
16 | })
17 |
18 | export default store
19 |
20 |
21 | export function initStore() {
22 | store.commit('loadLocalStorage')
23 | }
24 |
--------------------------------------------------------------------------------
/src/store/login/index.js:
--------------------------------------------------------------------------------
1 | import router from '@/router'
2 | import utils from '@/utils'
3 |
4 | export default {
5 | state: {
6 | token: '',
7 | userInfo: {},
8 | menus: []
9 | },
10 | mutations: {
11 | // 本地缓存token
12 | setToken(state, token) {
13 | state.token = token
14 | localStorage.setItem('token', JSON.stringify(token))
15 | },
16 | // 缓存用户信息
17 | setUserInfo(state, userInfo) {
18 | state.userInfo.account = userInfo.account
19 | state.userInfo.role_id = userInfo.role_id
20 | localStorage.setItem('userInfo', JSON.stringify(userInfo))
21 | },
22 | // 本地缓存菜单信息
23 | setMenus(state, menus) {
24 | state.menus = menus
25 | localStorage.setItem('menus', JSON.stringify(menus))
26 |
27 | // 匹配出该角色对应的路由配置并添加路由
28 | const routes = utils.mapMenus(menus);
29 | routes.forEach(route => router.addRoute('main', route))
30 | },
31 | // 页面刷新时, 为vuex重新赋值
32 | loadLocalStorage(state) {
33 | const token = JSON.parse(localStorage.getItem('token'))
34 | if (token) {
35 | state.token = token
36 | }
37 | const userInfo = JSON.parse(localStorage.getItem('userInfo'))
38 | if (userInfo) {
39 | state.userInfo = userInfo
40 | }
41 | const menus = JSON.parse(localStorage.getItem('menus'))
42 | if (menus) {
43 | // state.menus = menus
44 | this.commit('setMenus', menus)
45 | }
46 | const defaultActive = sessionStorage.getItem('defaultActive')
47 | if (defaultActive) {
48 | this.commit('setActive', defaultActive)
49 | }
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 |
2 | import { geoCoordMap } from '../components/content/base-echarts/data/geoCoordMap'
3 |
4 | export default {
5 | /**
6 | * 根据角色动态匹配路由
7 | * @param {Array} menus
8 | * @returns Array roleRoutes
9 | */
10 | mapMenus(menus) {
11 | const roleRoutes = [];
12 | const allRoutes = [];
13 |
14 | // 取出所有的路由配置信息
15 | const routeFiles = require.context('@/views', true, /-router\.js$/)
16 | routeFiles.keys().forEach(key => {
17 | const name = routeFiles(key).default.name
18 | const cpn = routeFiles(key).default
19 | // name: 一级菜单的name, cpn: 该name下所有的路由信息
20 | allRoutes.push({ name, cpn })
21 | })
22 |
23 | // 与后端返回的路由信息进行比对
24 | // 通过比对一级菜单的name
25 | for (const menu of menus) {
26 | const res = allRoutes.find(route => route.name === menu.name)
27 | if (res) {
28 | roleRoutes.push(res.cpn)
29 | }
30 | }
31 |
32 | return roleRoutes
33 | },
34 |
35 | /**
36 | * 级联菜单数据格式化
37 | * @param {Array} data
38 | * @returns Array final
39 | */
40 | setOptions(data) {
41 | const ps = new Map()
42 | const final = [];
43 | // 对一级菜单去重
44 | for (const obj of data) {
45 | ps.set(obj.op_id, obj.options)
46 | }
47 | // 将二级菜单添加至一级菜单中
48 | ps.forEach((value, key) => {
49 | const o = {
50 | value: key,
51 | label: value,
52 | children: []
53 | };
54 |
55 | for (const obj of data) {
56 | if (obj.op_id === key) {
57 | o.children.push({
58 | value: obj.ch_id,
59 | label: obj.child
60 | })
61 | }
62 | }
63 |
64 | final.push(o)
65 | })
66 |
67 | return final
68 | },
69 |
70 | /**
71 | * 对象递归删除空数据, 返回新对象
72 | * @param {any} data
73 | * @returns
74 | */
75 | deepRemove(data) {
76 | // 对非对象进行过滤
77 | if (typeof data !== 'object') return data
78 | // 对null undefined 进行过滤
79 | if (data == undefined) return data
80 | // 对 RegExp Date Function 进行过滤
81 | if (data instanceof RegExp) return new RegExp(data)
82 | if (data instanceof Date) return new Date(data)
83 | if (data instanceof Function) return new Function(data)
84 | // 创建对应类型的实例
85 | let newData = new data.constructor()
86 | // 递归: 清除所有空串、undefined、null、以及对象属性值为空的属性
87 | for (let key in data) {
88 | if (data.hasOwnProperty(key)) {
89 | let res = this.deepRemove(data[key])
90 | // 判断非空属性,并对其进行保留 (0也要保留)
91 | if ((res || res === 0) && JSON.stringify(res) !== '[]' && JSON.stringify(res) !== '{}') {
92 | newData[key] = res
93 | }
94 | }
95 | }
96 |
97 | return newData
98 | },
99 |
100 | // 对deepRemove返回的数据进一步处理
101 | // 调用此函数
102 | removeEmpty(data) {
103 | const res = this.deepRemove(data)
104 | let finalData;
105 | // data如果是数组, 可能会出现empty的元素, 对此进行清除
106 | if (res instanceof Array) {
107 | finalData = res.filter(item => item || item === 0)
108 | } else {
109 | // data是对象,不做处理
110 | finalData = res
111 | }
112 |
113 | return finalData
114 | },
115 |
116 | /**
117 | * 对象深拷贝
118 | * @param obj
119 | * @returns newObj
120 | */
121 | deepClone(obj) {
122 | // 对非对象进行过滤
123 | if (typeof obj !== 'object') return obj
124 |
125 | // 对null undefined 进行过滤
126 | if (obj == undefined) return obj
127 |
128 | // 对 RegExp Date Function 进行过滤
129 | if (obj instanceof RegExp) return new RegExp(obj)
130 | if (obj instanceof Date) return new Date(obj)
131 | if (obj instanceof Function) return new Function(obj)
132 |
133 | // 利用constructor进行实例化, 不管是对象还是数组
134 | let newObj = new obj.constructor()
135 | for (let key in obj) {
136 | if (obj.hasOwnProperty(key)) {
137 | newObj[key] = this.deepClone(obj[key])
138 | }
139 | }
140 |
141 | return newObj
142 | },
143 |
144 | /**
145 | * 格式化时间
146 | * @param {*} timestamp
147 | * @returns 年-月-日 时:分:秒
148 | */
149 | formatTime(timestamp) {
150 | const d = new Date(parseInt(timestamp)); //根据时间戳生成的时间对象
151 | const date = (d.getFullYear()) + "-" +
152 | (d.getMonth() + 1) + "-" +
153 | (d.getDate()) + " " +
154 | (d.getHours()) + ":" +
155 | (d.getMinutes()) + ":" +
156 | (d.getSeconds());
157 | return date;
158 | },
159 |
160 | /**
161 | * 合并相同订单(id在数据库经过排序)
162 | * @param {Object} orders
163 | * @returns Object data
164 | */
165 | dealOrderData(orders) {
166 | let data = this.deepClone(orders);
167 |
168 | let current_id, next_id;
169 | let foodInfoArray = [];
170 |
171 | for (let i = 0; i < data.length; i++) {
172 | current_id = data[i].id;
173 | // next_id为undefined表示数组只有一个元素或遍历到了最后一个元素
174 | // 当遍历到最后一个元素的时候, 同样需要往foodInfoArray添加一次
175 | if (data[i + 1]) {
176 | next_id = data[i + 1].id;
177 | }
178 | else {
179 | foodInfoArray.push(data[i].food_info)
180 | data[i].food_info = foodInfoArray
181 | return data;
182 | }
183 | // 记录重复id的food_info
184 | // 当id不相同时, 将foodInfoArray赋值给food_info
185 | if (current_id === next_id) {
186 | foodInfoArray.push(data[i].food_info)
187 | data.splice(i, 1)
188 | i = i - 1;//i减一为了保证下次遍历从当前下标开始
189 | } else {
190 | foodInfoArray.push(data[i].food_info)
191 | data[i].food_info = foodInfoArray
192 | foodInfoArray = [];
193 | }
194 |
195 | }
196 | },
197 |
198 | /**
199 | * 合并同一工单的回复
200 | * @param {Array} feedbackList
201 | * @returns new array
202 | */
203 | dealFeedback(feedbackList) {
204 | let data = this.deepClone(feedbackList)
205 | for (let i = 0; i < data.length; i++) {
206 | data[i].children = [];
207 | for (let j = 0; j < feedbackList.length; j++) {
208 | if (data[i].id == feedbackList[j].id) {
209 | continue;
210 | }
211 | if (data[i].id == feedbackList[j].belong) {
212 | data[i].children.push(feedbackList[j])
213 | feedbackList.splice(j, 1);
214 | data.splice(j, 1);
215 | j -= 1;//保证删除数据后的内层循环从当前j继续
216 | }
217 | }
218 | }
219 | return data
220 | },
221 |
222 | /**
223 | * echarts地图城市经纬度匹配
224 | * @param {Array} data
225 | * @returns new Array
226 | */
227 | convertData(data) {
228 | var res = [];
229 | for (var i = 0; i < data.length; i++) {
230 | var geoCoord = geoCoordMap[data[i].name];
231 | if (geoCoord) {
232 | res.push({
233 | name: data[i].name,
234 | value: geoCoord.concat(data[i].value)
235 | });
236 | }
237 | }
238 | return res;
239 | }
240 | }
--------------------------------------------------------------------------------
/src/views/404.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 返回
5 |
6 |
7 |
8 |
9 |
19 |
20 |
--------------------------------------------------------------------------------
/src/views/login/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |

5 |
6 |
外卖猫-后台管理系统
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
37 |
38 |
--------------------------------------------------------------------------------
/src/views/login/web/login.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
14 |
15 |
16 |
17 |
23 |
24 |
25 |
26 | 登录
32 | 重置
33 |
34 |
35 |
36 |
37 |
![]()
38 |
请使用外卖猫APP扫一扫
39 |
40 |
41 |
忘记密码
42 |
43 |
47 |
48 |
49 |
50 |
51 |
52 |
218 |
219 |
--------------------------------------------------------------------------------
/src/views/login/web/register.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 |
14 |
15 |
16 |
22 |
23 |
24 |
30 |
31 |
32 |
33 | 注册
39 | 重置
40 |
41 |
42 |
43 |
44 |
45 |
130 |
131 |
--------------------------------------------------------------------------------
/src/views/main/admin/api/admin-apis.js:
--------------------------------------------------------------------------------
1 | export default {
2 | getSellerMenu: {
3 | name: '获取role_id为2的菜单',
4 | url: "/role/2/menu",
5 | method: 'GET'
6 | },
7 | getAdminMenu: {
8 | name: '获取role_id为1的菜单',
9 | url: "/role/1/menu",
10 | method: 'GET'
11 | },
12 | adminLogin: {
13 | name: 'admin登录',
14 | url: '/role/login',
15 | method: 'POST'
16 | },
17 | deleteAdmin: {
18 | name: '根据id删除admin',
19 | url: '/role/admin/delete',
20 | method: 'POST'
21 | },
22 | deleteSeller: {
23 | name: '根据id删除seller',
24 | url: '/role/seller/delete',
25 | method: 'POST'
26 | },
27 | deleteBuyer: {
28 | name: '根据id删除buyer',
29 | url: '/role/buyer/delete',
30 | method: 'POST'
31 | },
32 | getUserByQuery: {
33 | name: '根据query获取admin/seller/buyer',
34 | url: '/role/user/by_query',
35 | method: 'POST'
36 | },
37 | changeUserUsable: {
38 | name: '更改user的usable',
39 | url: '/role/user/usable',
40 | method: 'POST'
41 | },
42 | addUser: {
43 | name: '添加user',
44 | url: '/role/user/add',
45 | method: 'POST'
46 | },
47 | getShopList: {
48 | name: '获取店铺信息',
49 | url: '/role/shop/list',
50 | method: 'POST'
51 | },
52 | changeShopUsable: {
53 | name: '更改shop的usable',
54 | url: '/role/shop/usable',
55 | method: 'POST'
56 | },
57 | deleteShop: {
58 | name: '根据id删除shop',
59 | url: '/role/shop/delete',
60 | method: 'POST'
61 | },
62 | addShop: {
63 | name: '添加店铺',
64 | url: '/role/shop/add',
65 | method: 'POST'
66 | },
67 | getOrderList: {
68 | name: '获取订单信息',
69 | url: '/role/order/list',
70 | method: 'POST'
71 | },
72 | changeOrderStatus: {
73 | name: '更改order的状态',
74 | url: '/role/order/status',
75 | method: 'POST'
76 | },
77 | changeOrderUpdateFlag: {
78 | name: '更改order的update_flag',
79 | url: '/role/order/update_flag',
80 | method: 'POST'
81 | },
82 | getFeedBackList: {
83 | name: '获取工单反馈',
84 | url: '/role/feedback/list',
85 | method: 'POST'
86 | },
87 | replyFeedback: {
88 | name: '回复工单',
89 | url: '/role/feedback/reply',
90 | method: 'POST'
91 | }
92 | }
--------------------------------------------------------------------------------
/src/views/main/admin/config/feedback-config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | // 搜索表单的配置
3 | FeedbackSearchFormConfig: [
4 | {
5 | type: 'input', label: '卖家id', prop: 'seller_id', placeholder: '输入查询的id'
6 | },
7 | { type: 'input', label: '反馈主题', prop: 'title', placeholder: '输入查询的主题' },
8 | {
9 | type: 'datepicker', label: '日期范围',
10 | otherOptions: {
11 | type: 'daterange',
12 | 'range-separator': '至',
13 | 'start-placeholder': '开始日期',
14 | 'end-placeholder': '结束日期'
15 | },
16 | prop: 'time'
17 | }
18 | ],
19 | // 表格表单的配置
20 | FeedbackTableFormConfig: {
21 | title: "反馈列表",
22 | isShowIndex: true,
23 | propList: [
24 | { prop: "id", label: "id", minWidth: "40", slotName: "id" },
25 | { prop: "seller_id", label: "卖家id", minWidth: "60", slotName: "seller_id" },
26 | { prop: "title", label: "主题", minWidth: "80", slotName: "title" },
27 | { prop: "content", label: "内容", minWidth: "120", slotName: "content" },
28 | { prop: "feedback_id", label: "回复反馈id", minWidth: "60", slotName: "feedback_id" },
29 | { prop: "belong", label: "所属主工单", minWidth: "60", slotName: "belong" },
30 | { prop: "type", label: "类型", minWidth: "70", slotName: "type" },
31 | {
32 | prop: "createTime",
33 | label: "创建时间",
34 | minWidth: "120",
35 | slotName: "createTime",
36 | },
37 | {
38 | prop: "updateTime",
39 | label: "最后更改",
40 | minWidth: "120",
41 | slotName: "updateTime",
42 | },
43 | {
44 | label: "操作",
45 | minWidth: "60",
46 | slotName: "handle",
47 | },
48 | ],
49 | },
50 | // 添加用户表单的配置
51 | FeedbackAddFormConfig: {
52 | options: ['feedback'],
53 | feedback: [
54 | {
55 | type: 'textarea', label: '回复', prop: 'content', placeholder: '输入回复内容...', clearable: true,
56 | rows: 10,
57 | rules: [
58 | { required: true, message: "内容不能为空", trigger: "blur" },
59 | ]
60 | },
61 | ],
62 | }
63 | }
--------------------------------------------------------------------------------
/src/views/main/admin/config/order-config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | // 搜索表单的配置
3 | OrderSearchFormConfig: [
4 | // {
5 | // type: 'input', label: '店铺名', prop: 'id', placeholder: '输入查询的id'
6 | // },
7 | { type: 'input', label: '订单号', prop: 'order_number', placeholder: '输入查询的订单' },
8 | {
9 | type: 'datepicker', label: '日期范围',
10 | otherOptions: {
11 | type: 'daterange',
12 | 'range-separator': '至',
13 | 'start-placeholder': '开始日期',
14 | 'end-placeholder': '结束日期'
15 | },
16 | prop: 'time'
17 | }
18 | ],
19 | // 表格表单的配置
20 | OrderTableFormConfig: {
21 | title: "订单列表",
22 | isShowIndex: true,
23 | propList: [
24 | { prop: "id", label: "id", minWidth: "40", slotName: "id" },
25 | { prop: "buyer_id", label: "买家id", minWidth: "60", slotName: "buyer_id" },
26 | {
27 | prop: "order_number",
28 | label: "订单编号",
29 | minWidth: "80",
30 | slotName: "order_number",
31 | },
32 | { prop: "pay_mode", label: "支付方式", minWidth: "80", slotName: "pay_mode" },
33 | { prop: "total_price", label: "订单金额", minWidth: "60", slotName: "total_price" },
34 | {
35 | prop: "pay_price",
36 | label: "支付金额",
37 | minWidth: "70",
38 | slotName: "pay_price",
39 | },
40 | {
41 | prop: "done",
42 | label: "订单状态",
43 | minWidth: "70",
44 | slotName: "done",
45 | },
46 | {
47 | prop: "update_flag",
48 | label: "日期归属",
49 | minWidth: "60",
50 | slotName: "update_flag",
51 | },
52 | {
53 | prop: "createTime",
54 | label: "下单时间",
55 | minWidth: "100",
56 | slotName: "createTime",
57 | },
58 | {
59 | prop: "updateTime",
60 | label: "完成时间",
61 | minWidth: "100",
62 | slotName: "updateTime",
63 | },
64 | {
65 | label: "操作",
66 | minWidth: "100",
67 | slotName: "handle",
68 | },
69 | ],
70 | }
71 | }
--------------------------------------------------------------------------------
/src/views/main/admin/config/shop-config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | // 搜索表单的配置
3 | ShopSearchFormConfig: [
4 | {
5 | type: 'input', label: 'id', prop: 'id', placeholder: '输入查询的id'
6 | },
7 | { type: 'input', label: '店铺名', prop: 'name', placeholder: '输入查询的店铺名' },
8 | {
9 | type: 'datepicker', label: '日期范围',
10 | otherOptions: {
11 | type: 'daterange',
12 | 'range-separator': '至',
13 | 'start-placeholder': '开始日期',
14 | 'end-placeholder': '结束日期'
15 | },
16 | prop: 'time'
17 | }
18 | ],
19 | // 表格表单的配置
20 | ShopTableFormConfig: {
21 | title: "店铺列表",
22 | isShowIndex: true,
23 | propList: [
24 | { prop: "id", label: "id", minWidth: "40", slotName: "id" },
25 | { prop: "name", label: "店铺名", minWidth: "80", slotName: "name" },
26 | {
27 | prop: "address",
28 | label: "店铺地址",
29 | minWidth: "80",
30 | slotName: "address",
31 | },
32 | { prop: "shop_avatar_url", label: "头像", minWidth: "80", slotName: "shop_avatar_url" },
33 | { prop: "usable", label: "状态", minWidth: "60", slotName: "usable" },
34 | {
35 | prop: "delivery",
36 | label: "配送",
37 | minWidth: "70",
38 | slotName: "delivery",
39 | },
40 | {
41 | prop: "start_time",
42 | label: "营业时间",
43 | minWidth: "70",
44 | slotName: "start_time",
45 | },
46 | { prop: "notice", label: "公告", minWidth: "60", slotName: "notice" },
47 | {
48 | prop: "updateTime",
49 | label: "最后更改",
50 | minWidth: "120",
51 | slotName: "updateTime",
52 | },
53 | {
54 | label: "操作",
55 | minWidth: "100",
56 | slotName: "handle",
57 | },
58 | ],
59 | }
60 | }
--------------------------------------------------------------------------------
/src/views/main/admin/config/user-config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | // 搜索表单的配置
3 | UserSearchFormConfig: [
4 | {
5 | type: 'input', label: 'id', prop: 'id', placeholder: '输入查询的id'
6 | },
7 | { type: 'input', label: '账号', prop: 'account', placeholder: '输入查询的账号' },
8 | {
9 | type: 'select', label: '角色', prop: 'role', placeholder: '选择查询角色',
10 | options: [
11 | // value需和数据库中的表名一致
12 | { label: '管理员', value: 'admin' },
13 | { label: '卖家', value: 'seller' },
14 | { label: '买家', value: 'buyer' },
15 | ],
16 | rules: [
17 | { required: true, message: "请选择要查询的角色", trigger: "blur" },
18 | ]
19 | },
20 | {
21 | type: 'datepicker', label: '日期范围',
22 | otherOptions: {
23 | type: 'daterange',
24 | 'range-separator': '至',
25 | 'start-placeholder': '开始日期',
26 | 'end-placeholder': '结束日期'
27 | },
28 | prop: 'time'
29 | }
30 | ],
31 | // 表格表单的配置
32 | UserTableFormConfig: {
33 | title: "用户列表",
34 | isShowIndex: true,
35 | propList: [
36 | { prop: "id", label: "id", minWidth: "40", slotName: "id" },
37 | { prop: "account", label: "用户名", minWidth: "80", slotName: "account" },
38 | {
39 | prop: "password",
40 | label: "密码",
41 | minWidth: "80",
42 | slotName: "password",
43 | },
44 | { prop: "usable", label: "状态", minWidth: "60", slotName: "usable" },
45 | { prop: "role_id", label: "角色", minWidth: "40", slotName: "role_id" },
46 | { prop: "level", label: "等级", minWidth: "40", slotName: "level" },
47 | {
48 | prop: "createTime",
49 | label: "创建时间",
50 | minWidth: "100",
51 | slotName: "createTime",
52 | },
53 | {
54 | prop: "updateTime",
55 | label: "更新时间",
56 | minWidth: "100",
57 | slotName: "updateTime",
58 | },
59 | {
60 | label: "操作",
61 | minWidth: "100",
62 | slotName: "handle",
63 | },
64 | ],
65 | },
66 | // 添加用户表单的配置
67 | UserAddFormConfig: {
68 | options: ['admin', "seller", "buyer"],
69 | admin: [
70 | {
71 | type: 'input', label: '账号', prop: 'account', placeholder: '输入账号', clearable: true,
72 | rules: [
73 | { required: true, message: "账号不能为空", trigger: "blur" },
74 | ]
75 | },
76 | {
77 | type: 'password', label: '密码', prop: 'password', placeholder: '输入密码', clearable: true,
78 | rules: [
79 | { required: true, message: "密码不能为空", trigger: "blur" },
80 | ]
81 | },
82 | {
83 | type: 'select', label: '角色等级', prop: 'level', placeholder: '选择管理员等级',
84 | options: [
85 | // value需和数据库中的表名一致
86 | { label: '等级1', value: '1' },
87 | { label: '等级2', value: '2' },
88 | ],
89 | rules: [
90 | { required: true, message: "账号等级不能为空", trigger: "blur" },
91 | ]
92 | },
93 | ],
94 | seller: [
95 | {
96 | type: 'input', label: '账号', prop: 'account', placeholder: '输入账号', clearable: true,
97 | rules: [
98 | { required: true, message: "账号不能为空", trigger: "blur" },
99 | ]
100 | },
101 | {
102 | type: 'password', label: '密码', prop: 'password', placeholder: '输入密码', clearable: true,
103 | rules: [
104 | { required: true, message: "密码不能为空", trigger: "blur" },
105 | ]
106 | },
107 | {
108 | type: 'input', label: '姓名', prop: 'name', placeholder: '输入姓名', clearable: true,
109 | rules: [
110 | { required: true, message: "姓名不能为空", trigger: "blur" },
111 | ]
112 | },
113 | {
114 | type: 'input', label: '身份证号', prop: 'iid', placeholder: '输入身份证号', clearable: true,
115 | rules: [
116 | { required: true, message: "身份证号不能为空", trigger: "blur" },
117 | ]
118 | },
119 | {
120 | type: 'input', label: '手机号', prop: 'phone', placeholder: '输入手机号', clearable: true,
121 | rules: [
122 | { required: true, message: "手机号不能为空", trigger: "blur" },
123 | ]
124 | },
125 | ],
126 | buyer: [
127 | {
128 | type: 'input', label: '账号', prop: 'account', placeholder: '输入账号', clearable: true,
129 | rules: [
130 | { required: true, message: "账号不能为空", trigger: "blur" },
131 | ]
132 | },
133 | {
134 | type: 'password', label: '密码', prop: 'password', placeholder: '输入密码', clearable: true,
135 | rules: [
136 | { required: true, message: "密码不能为空", trigger: "blur" },
137 | ]
138 | },
139 | {
140 | type: 'input', label: '手机号', prop: 'phone', placeholder: '输入手机号', clearable: true,
141 | rules: [
142 | { required: true, message: "手机号不能为空", trigger: "blur" },
143 | ]
144 | },
145 | ],
146 | }
147 | }
--------------------------------------------------------------------------------
/src/views/main/admin/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/src/views/main/admin/router/admin-router.js:
--------------------------------------------------------------------------------
1 | export default {
2 | path: 'admin',
3 | name: 'admin',
4 | meta: { name: '系统管理' },
5 | component: () => import('@/views/main/admin/index.vue'),
6 | children: [
7 | {
8 | path: 'user',
9 | name: 'user',
10 | meta: { name: '用户管理' },
11 | component: () => import('@/views/main/admin/web/user/user.vue')
12 | },
13 | {
14 | path: 'shop',
15 | name: 'shop',
16 | meta: { name: '店铺管理' },
17 | component: () => import('@/views/main/admin/web/shop/shop.vue')
18 | },
19 | {
20 | path: 'order',
21 | name: 'order',
22 | meta: { name: '订单管理' },
23 | component: () => import('@/views/main/admin/web/order/order.vue')
24 | },
25 | {
26 | path: 'feedback',
27 | name: 'feedback',
28 | meta: { name: '工单反馈' },
29 | component: () => import('@/views/main/admin/web/feedback/feedback.vue')
30 | },
31 | ]
32 | }
--------------------------------------------------------------------------------
/src/views/main/admin/web/feedback/feedback.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
18 |
19 |
20 | {{ $utils.formatTime(new Date(slotProps.row.createTime).getTime()) }}
21 |
22 |
23 | {{ $utils.formatTime(new Date(slotProps.row.updateTime).getTime()) }}
24 |
25 |
26 | 回复
33 |
34 |
35 |
36 |
37 |
52 |
53 |
54 |
55 |
148 |
149 |
--------------------------------------------------------------------------------
/src/views/main/admin/web/order/order.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
17 |
18 |
19 | {{ slotProps.row.done ? "进行中" : "已完成" }}
27 |
28 |
29 | {{ slotProps.row.update_flag ? "今日" : "往日" }}
37 |
38 |
39 | {{ $utils.formatTime(new Date(slotProps.row.createTime).getTime()) }}
40 |
41 |
42 | {{ $utils.formatTime(new Date(slotProps.row.updateTime).getTime()) }}
43 |
44 |
45 |
46 | 编辑
53 |
54 |
55 |
56 |
57 |
58 |
59 |
152 |
153 |
--------------------------------------------------------------------------------
/src/views/main/admin/web/shop/shop.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
18 |
19 |
20 | 店铺只允许卖家账号注册添加
21 |
22 |
23 |
24 |
25 |
26 |
27 | {{ slotProps.row.start_time + " - " + slotProps.row.end_time }}
28 |
29 |
30 | {{ slotProps.row.usable ? "正常" : "封禁" }}
38 |
39 |
40 | {{ $utils.formatTime(new Date(slotProps.row.updateTime).getTime()) }}
41 |
42 |
43 | 编辑
50 |
51 |
59 | 删除
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
169 |
170 |
--------------------------------------------------------------------------------
/src/views/main/admin/web/user/user.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
17 |
18 |
19 | 添加
25 |
26 |
27 |
28 | {{ slotProps.row.usable ? "正常" : "封禁" }}
36 |
37 |
38 | {{ slotProps.row.role_id == 1 ? "admin" : "user" }}
39 |
40 |
41 | {{ $utils.formatTime(new Date(slotProps.row.createTime).getTime()) }}
42 |
43 |
44 | {{ $utils.formatTime(new Date(slotProps.row.updateTime).getTime()) }}
45 |
46 |
47 | 编辑
54 |
55 |
63 | 删除
71 |
72 |
73 |
74 |
75 |
88 |
89 |
90 |
91 |
245 |
246 |
--------------------------------------------------------------------------------
/src/views/main/food/api/food-apis.js:
--------------------------------------------------------------------------------
1 | export default {
2 | addFood: {
3 | name: '添加食品',
4 | url: '/food/add',
5 | method: 'POST',
6 | },
7 | getFoodClassify: {
8 | name: '获取店铺的食品分类',
9 | url: '/food/classify',
10 | method: 'GET'
11 | },
12 | getFoodList: {
13 | name: '获取食品列表',
14 | url: '/food',
15 | method: 'GET'
16 | },
17 | deleteFood: {
18 | name: '删除单个食品',
19 | url: '/food/delete',
20 | method: 'POST'
21 | },
22 | updateFoodInfo: {
23 | name: '更新食品信息',
24 | url: '/food/update',
25 | method: 'POST'
26 | }
27 | }
--------------------------------------------------------------------------------
/src/views/main/food/config/food-config.js:
--------------------------------------------------------------------------------
1 | export default {
2 |
3 | add_config: {
4 | hasShop: '',
5 |
6 | // 显示食品分类
7 | options: [],
8 |
9 | // 图片验证
10 | food_avatar: true,
11 |
12 | // 除图片外的信息
13 | foodForm: {
14 | shop_id: '',
15 | name: '',
16 | cost: '',
17 | price: '',
18 | discount: '',
19 | extra: '',
20 | least: 1,
21 | single_point: false,
22 | foodClassify: [],
23 | },
24 | // 最终上传的数据
25 | fileFormData: "",
26 | // 验证规则
27 | rules: {
28 | name: [
29 | { required: true, message: "请输入食品名称", trigger: "blur" },
30 | { min: 2, max: 20, message: "长度在 2 到 20 个字符", trigger: "blur" },
31 | ],
32 | cost: [
33 | { required: true, message: "请输入食品成本", trigger: "change" },
34 | ],
35 | price: [
36 | { required: true, message: "请输入食品价格", trigger: "change" },
37 | ],
38 | foodClassify: [
39 | { required: true, message: "请选择/添加食品分类", trigger: "blur" },
40 | ],
41 | discount: [
42 | { required: true, message: "请输入食品折扣", trigger: "change" },
43 | ],
44 | extra: [
45 | { required: true, message: "请输入打包费", trigger: "change" },
46 | ],
47 |
48 | },
49 |
50 | // 请求头
51 | config: {
52 | headers: {
53 | "Content-Type": "multipart/form-data",
54 | },
55 | }
56 | },
57 |
58 | detail_config: {
59 |
60 |
61 |
62 | options: [],
63 | originData: "",
64 |
65 | fileFormData: "",
66 |
67 | foodForm: {
68 | name: '',
69 | cost: '',
70 | price: '',
71 | discount: '',
72 | extra: '',
73 | least: 0,
74 | single_point: false,
75 | foodClassify: [],
76 | desci: '',
77 | tips: '',
78 | package_content: '',
79 | main_material: '',
80 | secondary_material: '',
81 | meat_vegetable: '',
82 | weight: '',
83 | flavor: '',
84 | },
85 |
86 | axios_config: {
87 | headers: {
88 | "Content-Type": "multipart/form-data",
89 | },
90 | }
91 | }
92 | }
--------------------------------------------------------------------------------
/src/views/main/food/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/src/views/main/food/router/shop-router.js:
--------------------------------------------------------------------------------
1 | export default {
2 | path: 'food',
3 | name: 'food',
4 | meta: { name: '食品中心' },
5 | component: () => import('@/views/main/food/index.vue'),
6 | children: [
7 | {
8 | path: 'add',
9 | name: 'add',
10 | meta: { name: '新增食品' },
11 | component: () => import('@/views/main/food/web/add/add.vue')
12 | },
13 | {
14 | path: 'info',
15 | name: 'foodInfo',
16 | meta: { name: '食品列表' },
17 | component: () => import('@/views/main/food/web/info/info.vue')
18 | },
19 | ]
20 | }
--------------------------------------------------------------------------------
/src/views/main/food/web/add/add.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
18 |
19 |
20 |
21 |
22 |
30 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
47 |
48 |
49 |
50 |
51 |
56 |
57 |
58 |
59 |
60 |
65 |
66 |
67 |
68 |
69 |
74 |
75 |
76 |
77 |
78 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
96 |
106 |
107 |
108 |
109 |
110 |
111 |
112 | 立即创建
115 | 重置
116 |
117 |
118 |
119 |
还未注册店铺, 无法添加食品信息
120 |
121 |
122 |
123 |
213 |
214 |
--------------------------------------------------------------------------------
/src/views/main/food/web/info/detailForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
17 |
18 |
19 |
20 |
21 |
29 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
46 |
47 |
48 |
49 |
50 |
55 |
56 |
57 |
58 |
59 |
64 |
65 |
66 |
67 |
68 |
73 |
74 |
75 |
76 |
77 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
110 |
111 |
112 |
113 |
118 |
119 |
120 |
121 |
126 |
127 |
128 |
129 |
134 |
135 |
136 |
137 |
142 |
143 |
144 |
145 |
150 |
151 |
152 |
153 |
158 |
159 |
160 |
161 |
166 |
167 |
168 |
169 |
170 | 立即创建
173 | 重置
174 |
175 |
176 |
177 |
178 |
179 |
271 |
272 |
--------------------------------------------------------------------------------
/src/views/main/food/web/info/info.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | {{ props.row.name }}
11 |
12 |
13 | {{ props.row.cost }}
14 |
15 |
16 | {{ props.row.price }}
17 |
18 |
19 | {{ props.row.discount }}
20 |
21 |
22 | {{ props.row.extra }}
23 |
24 |
25 | {{ props.row.least }}
26 |
27 |
28 | {{ props.row.sold }}
29 |
30 |
31 | {{ props.row.good_comment }}
32 |
33 |
34 | {{ props.row.bad_comment }}
35 |
36 |
37 | {{ props.row.desci }}
38 |
39 |
40 | {{ props.row.package_content }}
41 |
42 |
43 | {{ props.row.main_material }}
44 |
45 |
46 | {{ props.row.secondary_material }}
47 |
48 |
49 | {{ props.row.meat_vegetable }}
50 |
51 |
52 | {{ props.row.flavor }}
53 |
54 |
55 | {{ props.row.tips }}
56 |
57 |
58 |
59 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | 编辑
81 | 删除
87 |
88 |
89 |
90 |
91 |
没有注册店铺, 所以没有食品信息
92 |
93 |
99 |
100 |
101 |
102 |
103 |
104 |
174 |
175 |
--------------------------------------------------------------------------------
/src/views/main/main.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
36 |
37 |
--------------------------------------------------------------------------------
/src/views/main/order/api/readme.md:
--------------------------------------------------------------------------------
1 | order相关的api在shop-apis.js中
--------------------------------------------------------------------------------
/src/views/main/order/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/src/views/main/order/router/order-router.js:
--------------------------------------------------------------------------------
1 | export default {
2 | path: 'order',
3 | name: 'order',
4 | meta: { name: '订单中心' },
5 | component: () => import('@/views/main/order/index.vue'),
6 | children: [
7 | {
8 | path: 'today',
9 | name: 'today',
10 | meta: { name: '今日订单' },
11 | component: () => import('@/views/main/order/web/today/today.vue')
12 | },
13 | {
14 | path: 'all',
15 | name: 'all',
16 | meta: { name: '全部订单' },
17 | component: () => import('@/views/main/order/web/all/all.vue')
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/src/views/main/order/web/all/all.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
名称: {{ info.name }}
18 |
成本: {{ info.cost }}
19 |
价格: {{ info.price }}
20 |
折扣: {{ info.discount }}
21 |
打包费: {{ info.extra }}
22 |
23 |
24 | 查看食品信息
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | 总价格: {{ scope.row.total_price }}
34 | 用户支付: {{ scope.row.pay_price }}
35 |
36 | 查看明细
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | {{ scope.row.done ? "进行中" : "已完成" }}
58 |
59 |
60 |
61 |
62 |
63 |
76 |
77 |
78 |
79 |
146 |
147 |
--------------------------------------------------------------------------------
/src/views/main/order/web/today/today.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
名称: {{ info.name }}
19 |
成本: {{ info.cost }}
20 |
价格: {{ info.price }}
21 |
折扣: {{ info.discount }}
22 |
打包费: {{ info.extra }}
23 |
24 |
25 | 查看食品信息
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | 总价格: {{ scope.row.total_price }}
35 | 用户支付: {{ scope.row.pay_price }}
36 |
37 | 查看明细
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | {{ scope.row.done ? "进行中" : "已完成" }}
59 |
60 |
61 |
62 |
63 |
64 |
77 |
78 |
79 |
80 |
147 |
148 |
--------------------------------------------------------------------------------
/src/views/main/overview/overview.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 | 项目名
13 |
14 | 外卖猫后台管理系统
15 |
16 |
17 |
18 |
19 | 前端
20 |
21 | Vue.js、uni-app
22 |
23 |
24 |
25 |
26 | 后端
27 |
28 | Node.js (Koa)
29 |
30 |
31 |
32 |
33 | 数据库
34 |
35 | Mysql、Redis
36 |
37 |
38 |
39 |
40 | 其他
41 |
42 | Element-UI、Axios、Socket.io、Echarts
43 |
44 |
45 |
46 |
47 | github
48 |
49 | https://github.com/yg10323
50 |
51 |
52 |
53 |
主要介绍
54 |
55 |
56 |
57 |
58 |
59 | 动态路由
60 |
61 | 1. 根据登录用户的角色向后端请求不同的菜单数据
62 | 2. 前端拿到菜单数据后跟本地的路由配置进行匹配
63 | 3. 匹配到的路由配置信息添加到main的子路由中
64 |
65 |
66 |
67 |
68 |
69 | axios封装
70 |
71 |
72 | 1.
73 | 通过webpack的require.context函数遍历出views目录下的*-apis.js文件
74 |
75 |
76 | 2. 对文件名进行转驼峰后,作为key 将文件中默认导出的对象作为value
77 |
78 | 3. 对value进行遍历,对value中的每个item赋值为axios实例
79 |
80 |
81 |
82 |
83 |
84 |
85 | 扫码登录
86 |
87 | 1. uniapp + socket.io + redis + broadcast-channel
88 | 2. socket.io : 负责二维码发送、扫码状态鉴别等
89 | 3. redis : 负责控制二维码过期时间
90 | 4. broadcast-channel : 在后端进行信息广播
91 |
92 |
93 |
94 |
95 |
96 |
次要介绍
97 |
98 |
99 |
100 |
101 |
102 | 未做的
103 |
104 | 1. 前后端性能优化
105 | 2. 数据库性能优化
106 | 3. 网络延迟导致的页面闪烁
107 |
108 |
109 |
110 |
111 |
112 |
113 | 安全方面
114 |
115 | 1. 使用jwt做token管理, token非对称加密
116 | ps. 还是使用session管理登录状态比较好😷
117 | 2. 密码使用md5进行加密
118 | 3. vue全局导航守卫, axios请求拦截
119 | 4. 后端多重中间件验证判断角色、账号状态等信息
120 |
121 |
122 |
123 |
124 |
125 |
126 | 其他
127 |
128 | 1.自己封装的一些小工具函数
129 | 2. 项目功能比较单一, 没有什么亮点
130 | 3. BUG较多
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
143 |
144 |
--------------------------------------------------------------------------------
/src/views/main/overview/router/overview-router.js:
--------------------------------------------------------------------------------
1 | export default {
2 | path: 'overview',
3 | name: 'overview',
4 | meta: { name: '系统总览' },
5 | component: () => import('@/views/main/overview/overview.vue')
6 | }
--------------------------------------------------------------------------------
/src/views/main/profile/api/user-apis.js:
--------------------------------------------------------------------------------
1 | export default {
2 | sellerLogin: {
3 | name: 'seller登录',
4 | url: "/seller/login",
5 | method: 'POST'
6 | },
7 | sellerRegister: {
8 | name: "seller注册",
9 | url: "/seller/register",
10 | method: 'POST'
11 | },
12 | getRealName: {
13 | name: '查询seller实名状态',
14 | url: "/seller/get_real_name",
15 | method: 'GET'
16 | },
17 | createShop: {
18 | name: '创建店铺',
19 | url: '/seller/create_shop',
20 | method: 'POST'
21 | },
22 | getSelfInfo: {
23 | name: '获取个人信息',
24 | url: '/seller/self_info',
25 | method: 'GET'
26 | },
27 | updateAuthInfo: {
28 | name: "跟新实名信息",
29 | url: '/seller/update/auth',
30 | method: 'POST'
31 | },
32 | deleteAccount: {
33 | name: '注销账号',
34 | url: '/seller/delete',
35 | method: 'DELETE'
36 | },
37 | postFeedBack: {
38 | name: '工单反馈',
39 | url: '/seller/feedback/post',
40 | method: 'POST'
41 | },
42 | getMyFeedback: {
43 | name: '获取我的工单',
44 | url: '/seller/feedback/self',
45 | method: 'GET'
46 | },
47 | replyFeedback: {
48 | name: '回复工单',
49 | url: '/seller/feedback/reply',
50 | method: 'POST'
51 | },
52 | replyEvaluate: {
53 | name: '回复评价',
54 | url: '/seller/evaluate/reply',
55 | method: 'POST'
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/views/main/profile/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/src/views/main/profile/router/profile-router.js:
--------------------------------------------------------------------------------
1 | export default {
2 | path: 'profile',
3 | name: 'profile',
4 | meta: { name: '个人中心' },
5 | component: () => import('@/views/main/profile/index.vue'),
6 | children: [
7 | {
8 | path: 'myself',
9 | name: 'myself',
10 | meta: { name: '我的信息' },
11 | component: () => import('@/views/main/profile/web/myself/myself.vue')
12 | },
13 | {
14 | path: 'feedback',
15 | name: 'feedback',
16 | meta: { name: '工单反馈' },
17 | component: () => import('@/views/main/profile/web/feedback/feedback.vue')
18 | },
19 | {
20 | path: 'my_feedback',
21 | name: 'my_feedback',
22 | meta: { name: '我的工单' },
23 | component: () => import('@/views/main/profile/web/feedback/myFeedback.vue')
24 | },
25 | ]
26 | }
--------------------------------------------------------------------------------
/src/views/main/profile/web/feedback/feedback.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 主题
5 |
6 |
7 |
8 |
9 |
15 |
16 |
17 |
24 |
25 |
26 |
27 |
74 |
75 |
--------------------------------------------------------------------------------
/src/views/main/profile/web/feedback/myFeedback.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
只是最简单的回复功能,没有做过多的逻辑判断
4 |
5 |
6 |
7 |
8 | {{ item.type }}
9 | {{ item.content }}
10 |
11 |
16 |
{{ childItem.type }}
17 |
{{ childItem.content }}
18 |
19 | 回复的为此条内容哦
22 |
23 |
27 |
34 |
35 | 提交
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
112 |
113 |
138 |
139 |
--------------------------------------------------------------------------------
/src/views/main/profile/web/feedback/富文本.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 主题
5 |
6 |
7 |
8 |
14 |
15 |
22 |
23 |
24 |
25 |
26 |
65 |
66 |
--------------------------------------------------------------------------------
/src/views/main/profile/web/myself/myself.vue:
--------------------------------------------------------------------------------
1 |
2 |
105 |
106 |
107 |
261 |
262 |
--------------------------------------------------------------------------------
/src/views/main/shop/api/shop-apis.js:
--------------------------------------------------------------------------------
1 | export default {
2 | getClassify: {
3 | name: '获取店铺分类',
4 | url: "/shop/get_classify",
5 | method: 'GET',
6 | },
7 | getShopExist: {
8 | name: '查询店铺名是否被注册',
9 | url: '/shop/shop_exist',
10 | method: 'POST',
11 | },
12 | getHasShop: {
13 | name: '查询是否已经注册过店铺',
14 | url: '/shop/has_shop',
15 | method: 'GET',
16 | },
17 | getShopInfo: {
18 | name: '获取店铺信息',
19 | url: '/shop/info',
20 | method: 'GET',
21 | },
22 | updateShopInfo: {
23 | name: '更新店铺信息',
24 | url: '/shop/update',
25 | method: 'POST'
26 | },
27 | updateActivities: {
28 | name: '更新店铺活动',
29 | url: '/shop/update/activities',
30 | method: 'POST'
31 | },
32 | getOrdersToday: {
33 | name: '获取店铺今日订单',
34 | url: '/shop/orders/today',
35 | method: 'GET'
36 | },
37 | getOrdersAll: {
38 | name: '获取店铺所有订单',
39 | url: '/shop/orders/all',
40 | method: 'GET'
41 | },
42 | getBill: {
43 | name: '获取流水等信息',
44 | url: '/shop/bill',
45 | method: 'GET'
46 | },
47 | getSold: {
48 | name: '获取店铺食品销售数量',
49 | url: "/shop/sold",
50 | method: 'GET'
51 | },
52 | getMapData: {
53 | name: '获取订单的分布以及数量',
54 | url: '/shop/map_data',
55 | method: 'GET'
56 | },
57 | getShopEvaluates: {
58 | name: '获取店铺评价',
59 | url: '/shop/evaluates',
60 | method: 'post'
61 | }
62 | }
--------------------------------------------------------------------------------
/src/views/main/shop/config/echarts-config.js:
--------------------------------------------------------------------------------
1 | // 食品销售数
2 | export const soldOption = {
3 | tooltip: {
4 | trigger: "axis",
5 | axisPointer: {
6 | type: "shadow",
7 | },
8 | },
9 | grid: {
10 | left: "3%",
11 | right: "4%",
12 | bottom: "3%",
13 | containLabel: true,
14 | },
15 | xAxis: [
16 | {
17 | type: "category",
18 | data: [],
19 | axisTick: {
20 | alignWithLabel: true,
21 | },
22 | },
23 | ],
24 | yAxis: [
25 | {
26 | type: "value",
27 | },
28 | ],
29 | series: [
30 | {
31 | name: "",
32 | type: "bar",
33 | barWidth: "60%",
34 | data: [],
35 | },
36 | ],
37 | };
38 |
39 | // 收入
40 | export const billOption = {
41 | title: {
42 | text: '收支明细',
43 | subtext: '汇总',
44 | left: 'center'
45 | },
46 | tooltip: {
47 | trigger: 'item'
48 | },
49 | legend: {
50 | orient: 'vertical',
51 | left: 'left'
52 | },
53 | series: [
54 | {
55 | type: 'pie',
56 | radius: '50%',
57 | data: [],
58 | emphasis: {
59 | itemStyle: {
60 | shadowBlur: 10,
61 | shadowOffsetX: 0,
62 | shadowColor: 'rgba(0, 0, 0, 0.5)'
63 | }
64 | }
65 | }
66 | ]
67 | };
68 |
69 | export const mapOption = {
70 | backgroundColor: {
71 | type: 'linear',
72 | x: 0,
73 | y: 0,
74 | x2: 1,
75 | y2: 1,
76 | colorStops: [{
77 | offset: 0, color: '#fff' // 0% 处的颜色
78 | }, {
79 | offset: 1, color: '#fff' // 100% 处的颜色
80 | }],
81 | globalCoord: false // 缺省为 false
82 | },
83 | title: {
84 | top: 20,
85 | text: '订单在全国的数量分布',
86 | subtext: '鼠标左键拖动,中间放大缩小',
87 | x: 'center',
88 | textStyle: {
89 | color: '#ccc'
90 | }
91 | },
92 |
93 | tooltip: {
94 | trigger: 'item',
95 | formatter: function (params) {
96 | if (typeof (params.value)[2] == "undefined") {
97 | return params.name + ' : ' + params.value;
98 | } else {
99 | return params.name + ' : ' + params.value[2];
100 | }
101 | }
102 | },
103 | /* legend: {
104 | orient: 'vertical',
105 | y: 'bottom',
106 | x: 'right',
107 | data:['pm2.5'],
108 | textStyle: {
109 | color: '#fff'
110 | }
111 | },*/
112 | legend: {
113 | orient: 'vertical',
114 | y: 'bottom',
115 | x: 'right',
116 | data: ['pm2.5'],
117 | textStyle: {
118 | color: '#fff'
119 | }
120 | },
121 | visualMap: {
122 | show: false,
123 | min: 0,
124 | max: 500,
125 | left: 'left',
126 | top: 'bottom',
127 | text: ['高', '低'], // 文本,默认为数值文本
128 | calculable: true,
129 | seriesIndex: [1],
130 | inRange: {
131 |
132 | }
133 | },
134 | geo: {
135 | map: 'china',
136 | show: true,
137 | roam: true,
138 | label: {
139 | normal: {
140 | show: false
141 | },
142 | emphasis: {
143 | show: false,
144 | }
145 | },
146 | itemStyle: {
147 | normal: {
148 | areaColor: '#3a7fd5',
149 | borderColor: '#0a53e9',//线
150 | shadowColor: '#092f8f',//外发光
151 | shadowBlur: 20
152 | },
153 | emphasis: {
154 | areaColor: '#0a2dae',//悬浮区背景
155 | }
156 | }
157 | },
158 | series: [
159 | {
160 |
161 | symbolSize: 5,
162 | label: {
163 | normal: {
164 | formatter: '{b}',
165 | position: 'right',
166 | show: true
167 | },
168 | emphasis: {
169 | show: true
170 | }
171 | },
172 | itemStyle: {
173 | normal: {
174 | color: '#fff'
175 | }
176 | },
177 | name: 'light',
178 | type: 'scatter',
179 | coordinateSystem: 'geo',
180 | data: [],
181 |
182 | },
183 | {
184 | type: 'map',
185 | map: 'china',
186 | geoIndex: 0,
187 | aspectScale: 0.75, //长宽比
188 | showLegendSymbol: false, // 存在legend时显示
189 | label: {
190 | normal: {
191 | show: false
192 | },
193 | emphasis: {
194 | show: false,
195 | textStyle: {
196 | color: '#fff'
197 | }
198 | }
199 | },
200 | roam: true,
201 | itemStyle: {
202 | normal: {
203 | areaColor: '#031525',
204 | borderColor: '#FFFFFF',
205 | },
206 | emphasis: {
207 | areaColor: '#2B91B7'
208 | }
209 | },
210 | animation: false,
211 | data: []
212 | },
213 | {
214 | name: 'Top 5',
215 | type: 'scatter',
216 | coordinateSystem: 'geo',
217 | symbol: 'pin',
218 | symbolSize: [50, 50],
219 | label: {
220 | normal: {
221 | show: true,
222 | textStyle: {
223 | color: '#fff',
224 | fontSize: 9,
225 | },
226 | formatter(value) {
227 | return value.data.value[2]
228 | }
229 | }
230 | },
231 | itemStyle: {
232 | normal: {
233 | color: '#D8BC58', //标志颜色
234 | }
235 | },
236 | data: [],
237 | showEffectOn: 'render',
238 | rippleEffect: {
239 | brushType: 'stroke'
240 | },
241 | hoverAnimation: true,
242 | zlevel: 1
243 | },
244 |
245 | ]
246 | };
--------------------------------------------------------------------------------
/src/views/main/shop/config/shop-config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | realName: false,// 实名状态
3 | shopExist: false,// 店铺名是否已被注册
4 | hasShop: false,//是否已经注册过店铺
5 | options: [],// 展示级联菜单的数据
6 | dialogImageUrl: "",//dialog显示的图片
7 | dialogVisible: false,
8 | percentage: 0,//控制进度条
9 | fileFormData: "",// 向服务器提交的数据
10 |
11 | // 对图片进行表单验证
12 | status: {
13 | shop_avatar: true,
14 | shop_business: true,
15 | shop_health: true,
16 | },
17 | // 收集除图片外的数据
18 | shopInfo: {
19 | seller_id: "",
20 | name: "",
21 | classify: { op_id: "", ch_id: "" },
22 | address: "",
23 | start_time: "",
24 | end_time: "",
25 | mountain_plan: true,
26 | service: [],
27 | delivery: "",
28 | activities: [{ value: "" }],
29 | },
30 | // 验证规则
31 | rules: {
32 | name: [
33 | { required: true, message: "请输入店铺名称", trigger: "blur" },
34 | { min: 2, max: 20, message: "长度在 2 到 20 个字符", trigger: "blur" },
35 | ],
36 | address: [
37 | { required: true, message: "请输入店铺地址", trigger: "change" },
38 | ],
39 | classify: [
40 | { required: true, message: "请选择店铺分类", trigger: "blur" },
41 | ],
42 | start_time: [
43 | {
44 | required: true,
45 | message: "请选择时间",
46 | trigger: "change",
47 | },
48 | ],
49 | end_time: [
50 | {
51 | required: true,
52 | message: "请选择时间",
53 | trigger: "change",
54 | },
55 | ],
56 | service: [
57 | {
58 | type: "array",
59 | required: true,
60 | message: "请至少选择一个服务",
61 | trigger: "change",
62 | },
63 | ],
64 | delivery: [
65 | { required: true, message: "请选择配送形式", trigger: "change" },
66 | ],
67 | },
68 |
69 | // 请求头
70 | config: {
71 | headers: {
72 | "Content-Type": "multipart/form-data",
73 | },
74 | },
75 |
76 | // 回复评价的表单配置
77 | commentAddFormConfig: {
78 | options: ['reply'],
79 | reply: [
80 | {
81 | type: 'textarea', label: '回复', prop: 'content', placeholder: '输入回复内容...', clearable: true,
82 | rows: 10,
83 | rules: [
84 | { required: true, message: "内容不能为空", trigger: "blur" },
85 | ]
86 | },
87 | ],
88 | }
89 | }
--------------------------------------------------------------------------------
/src/views/main/shop/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/src/views/main/shop/router/shop-router.js:
--------------------------------------------------------------------------------
1 | export default {
2 | path: 'shop',
3 | name: 'shop',
4 | meta: { name: '我的店铺' },
5 | component: () => import('@/views/main/shop/index.vue'),
6 | children: [
7 | {
8 | path: 'register',
9 | name: 'register',
10 | meta: { name: '注册店铺' },
11 | component: () => import('@/views/main/shop/web/register/register.vue')
12 | },
13 | {
14 | path: 'info',
15 | name: 'shopInfo',
16 | meta: { name: '店铺信息' },
17 | component: () => import('@/views/main/shop/web/info/info.vue')
18 | },
19 | {
20 | path: 'bill',
21 | name: 'bill',
22 | meta: { name: '店铺流水' },
23 | component: () => import('@/views/main/shop/web/bill/bill.vue')
24 | },
25 | {
26 | path: 'comment',
27 | name: 'comment',
28 | meta: { name: '店铺评价' },
29 | component: () => import('@/views/main/shop/web/comment/comment.vue')
30 | }
31 | ]
32 | }
--------------------------------------------------------------------------------
/src/views/main/shop/web/bill/bill.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
20 |
21 |
22 |
23 |
24 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
109 |
110 |
--------------------------------------------------------------------------------
/src/views/main/shop/web/comment/comment.vue:
--------------------------------------------------------------------------------
1 |
2 |
95 |
96 |
97 |
166 |
167 |
--------------------------------------------------------------------------------
/src/views/main/shop/web/info/drawerForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 修改基本信息
6 |
11 |
12 |
18 |
19 |
20 |
25 |
26 |
27 |
28 |
29 |
34 |
35 |
36 |
37 |
38 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
59 |
60 |
61 |
62 |
63 |
64 |
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 |
106 |
107 |
108 |
109 |
110 |
118 |
119 |
120 |
121 |
122 |
123 |
124 | 提交
125 | 重置
126 |
127 |
128 |
129 |
130 |
131 |
132 |
236 |
237 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | publicPath: process.env.NODE_ENV === 'production' ? './' : '/',
3 | productionSourceMap: process.env.NODE_ENV === 'production' ? false : true,
4 | devServer: {
5 | proxy: {
6 | '/api': {
7 | target: 'http://124.70.20.215:2140',
8 | },
9 | },
10 | // port: 8008
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
名称: {{ info.name }}
40 |用户名: {{ scope.row.buyer_info.name }}
56 |电话: {{ scope.row.buyer_info.phone }}
57 |地址: {{ scope.row.buyer_info.address }}
58 |