From cd3350bd2c05d0501e44df017af3e1479f71b9f1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=90=91=E6=96=87=E5=8F=AF?= <1041367524@qq.com>
Date: Fri, 1 Apr 2022 10:46:11 +0800
Subject: [PATCH] =?UTF-8?q?refactor:=20API=E8=B0=83=E7=94=A8=E9=87=8D?=
=?UTF-8?q?=E6=9E=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.env.beta | 2 +
.gitlab-ci.yml | 4 +
README.md | 11 +-
TODO.md | 12 ++
package.json | 7 +-
src/api/auth.js | 4 +-
src/api/system/user.js | 78 +++------
src/components/ElUploadImage.vue | 147 +++++++++++++++++
src/components/TableList.vue | 4 +-
src/components/extra/ElImage.vue | 75 ++++++---
src/components/extra/ElUploadImage.vue | 212 -------------------------
src/layouts/components/aside.vue | 16 +-
src/layouts/components/menu.vue | 18 ++-
src/layouts/components/view.vue | 14 +-
src/plugins/global-api.js | 24 +++
src/router/index.js | 2 +-
src/router/modules/system.js | 16 +-
src/store/modules/{ => core}/auth.js | 0
src/store/modules/{ => core}/layout.js | 4 +-
src/store/modules/{ => core}/local.js | 0
src/store/modules/demo.js | 20 ---
src/store/modules/system/user.js | 17 +-
src/utils/request.js | 46 +++---
src/views/global/login.vue | 43 ++---
src/views/home/index.vue | 38 +----
src/views/system/user/form.vue | 102 ++++++------
src/views/system/user/index.vue | 117 ++++++--------
vite.config.js | 3 +-
28 files changed, 490 insertions(+), 546 deletions(-)
create mode 100644 .env.beta
create mode 100644 .gitlab-ci.yml
create mode 100644 src/components/ElUploadImage.vue
delete mode 100644 src/components/extra/ElUploadImage.vue
rename src/store/modules/{ => core}/auth.js (100%)
rename src/store/modules/{ => core}/layout.js (95%)
rename src/store/modules/{ => core}/local.js (100%)
delete mode 100644 src/store/modules/demo.js
diff --git a/.env.beta b/.env.beta
new file mode 100644
index 0000000..96f5616
--- /dev/null
+++ b/.env.beta
@@ -0,0 +1,2 @@
+VITE_BASE_URL=https://gateway.mashibing.cn
+VITE_REQUEST_TIMEOUT=20000
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..6b3218f
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,4 @@
+include:
+ - project: 'yunwei/cicd/xuexipingtai/entrypoint'
+ ref: main
+ file: '/main.yml'
diff --git a/README.md b/README.md
index b8e6920..78bd51f 100644
--- a/README.md
+++ b/README.md
@@ -163,13 +163,16 @@ proxy.$copy('hello world');
| copy | 复制文本 |
| download | 下载指定地址文件 |
| excel | 导出 excel 文件并下载 |
+| dict | 字典查询 |
+| name | 昵称和姓名拼接显示 |
## 全局组件
-| 名称 | 功能介绍 |
-| --------- | ------------ |
-| ElEditor | 富文本编辑器 |
-| TableList | 通用列表组件 |
+| 名称 | 功能介绍 |
+| ------------- | ------------ |
+| ElEditor | 富文本编辑器 |
+| ElUploadImage | 图片上传 |
+| TableList | 通用列表组件 |
## 心得总结
diff --git a/TODO.md b/TODO.md
index f14ba7d..12c9623 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,3 +1,9 @@
+# 待解决
+
+## 多个路由引用同一个 vue 页面时,打开这几个路由后就会有多个 TAB 页,切换 TAB 页时因为这几个路由引用的其实是同一个组件,所以初始数据会保持一致。比如新增页面应该是没有数据的,编辑数据需要回显,但是先打开新增页面再打开编辑页面,新增页面就会同步为编辑页面。
+
+# 踩坑日记
+
## perttier 保存时不会自动格式化属性排序、需要执行命令才能格式化
> xwk 2022.3.23
@@ -20,6 +26,12 @@
> xwk 0328
> el-tabs 使用 label 插槽,应该是 ElementPlus 的 BUG,[Github 已有 Issues](https://github.com/element-plus/element-plus/issues/6839)
+> ElementPlus 已在 2.1.7 版本修复了这个 BUG
+
+## Uncaught (in promise) TypeError: Cannot read properties of null (reading 'shapeFlag')
+
+> xwk 0330
+> 对 props 使用对象展开符可能会展开 null
## 偶现 SVG 图标颜色渲染异常
diff --git a/package.json b/package.json
index ce04835..b28dd06 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,7 @@
"scripts": {
"dev": "vite",
"build:test": "vite build --mode test",
- "build:preview": "vite build --mode preview",
+ "build:beta": "vite build --mode beta",
"build:prod": "vite build --mode prod",
"preview": "vite preview",
"prepare": "husky install",
@@ -20,14 +20,15 @@
"@vueup/vue-quill": "^1.0.0-beta.8",
"axios": "^0.26.1",
"dayjs": "^1.11.0",
- "element-plus": "^2.1.2",
+ "element-plus": "^2.1.7",
"lodash": "^4.17.21",
"qs": "^6.10.3",
"quill-image-uploader": "^1.2.2",
"sortablejs": "^1.14.0",
"vue": "^3.2.25",
"vue-router": "^4.0.14",
- "vuex": "^4.0.2"
+ "vuex": "^4.0.2",
+ "vuex-persistedstate": "^4.1.0"
},
"devDependencies": {
"@commitlint/cli": "^13.2.1",
diff --git a/src/api/auth.js b/src/api/auth.js
index 0f2d6bb..976174c 100644
--- a/src/api/auth.js
+++ b/src/api/auth.js
@@ -11,7 +11,7 @@ export function sendSmsCode(params) {
// 登录
export async function login(data) {
return request({
- url: '/uaa/sso/appManageLogin',
+ url: '/user/loginByPasswordToB',
method: 'post',
data,
});
@@ -19,7 +19,7 @@ export async function login(data) {
// 获取用户信息
export function getUserInfo() {
return request({
- url: '/uc/user/v1/info/token',
+ url: '/user/current',
method: 'get',
});
}
diff --git a/src/api/system/user.js b/src/api/system/user.js
index 7512a7b..25b0474 100644
--- a/src/api/system/user.js
+++ b/src/api/system/user.js
@@ -1,59 +1,29 @@
-const mock = (data) =>
- new Promise((resolve) => {
- setTimeout(() => {
- resolve(data);
- }, Math.random() * 1500 + 500);
+import request from '@/utils/request.js';
+export const search = (params) => {
+ return request({
+ url: '/employee',
+ method: 'get',
+ params,
});
-let list = [
- {
- id: 1,
- username: 'user001',
- nickname: '张三',
- sex: 1,
- avatar: 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fup.enterdesk.com%2Fedpic%2Ffd%2Ff1%2Fda%2Ffdf1dacb8ff0b8f13ed29bcbee42f328.jpeg&refer=http%3A%2F%2Fup.enterdesk.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1650201540&t=ba213738d8f11e79302fab71602856f2',
- loginTime: Date.now(),
- enabled: true,
- },
- {
- id: 2,
- username: 'user003',
- nickname: '李四',
- sex: 0,
- avatar: 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fup.enterdesk.com%2Fedpic%2Ffd%2Ff1%2Fda%2Ffdf1dacb8ff0b8f13ed29bcbee42f328.jpeg&refer=http%3A%2F%2Fup.enterdesk.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1650201540&t=ba213738d8f11e79302fab71602856f2',
- loginTime: Date.now(),
- enabled: true,
- },
- {
- id: 3,
- username: 'user003',
- nickname: '王五',
- sex: 1,
- avatar: 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fup.enterdesk.com%2Fedpic%2Ffd%2Ff1%2Fda%2Ffdf1dacb8ff0b8f13ed29bcbee42f328.jpeg&refer=http%3A%2F%2Fup.enterdesk.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1650201540&t=ba213738d8f11e79302fab71602856f2',
- loginTime: Date.now(),
- enabled: false,
- },
-];
-export const findUserList = (data) => {
- console.info(data);
- return mock({ content: list, totalElements: list.length });
};
-export const createUser = (data) => {
- data = {
- id: new Date().getTime(),
- ...data,
- };
- list.push(data);
- return mock(data);
-};
-export const updateUser = (data) => {
- let old = list.find((item) => item.id === data.id);
- Object.assign(old, data);
- return mock(old);
+export const create = (data) => {
+ return request({
+ url: '/employee',
+ method: 'post',
+ data,
+ });
};
-export const removeUser = (ids) => {
- list = list.filter((item) => !ids.includes(item.id));
- return mock(true);
+export const update = (data) => {
+ return request({
+ url: '/employee',
+ method: 'put',
+ data,
+ });
};
-export const getUserDetail = (id) => {
- return mock(list.find((item) => item.id === id));
+export const remove = (params) => {
+ return request({
+ url: '/employee',
+ method: 'delete',
+ params,
+ });
};
diff --git a/src/components/ElUploadImage.vue b/src/components/ElUploadImage.vue
new file mode 100644
index 0000000..43c61fd
--- /dev/null
+++ b/src/components/ElUploadImage.vue
@@ -0,0 +1,147 @@
+
+
+
+
+
+
+
支持小于 {{ fmtSize }} 的 文件
+
+
+
+
diff --git a/src/components/TableList.vue b/src/components/TableList.vue
index 7ac12e1..23c6a20 100644
--- a/src/components/TableList.vue
+++ b/src/components/TableList.vue
@@ -2,8 +2,8 @@
-
diff --git a/src/components/extra/ElUploadImage.vue b/src/components/extra/ElUploadImage.vue
deleted file mode 100644
index 60ef9d7..0000000
--- a/src/components/extra/ElUploadImage.vue
+++ /dev/null
@@ -1,212 +0,0 @@
-
-
-
-
-
diff --git a/src/layouts/components/aside.vue b/src/layouts/components/aside.vue
index 90ba8a4..438832c 100644
--- a/src/layouts/components/aside.vue
+++ b/src/layouts/components/aside.vue
@@ -12,7 +12,7 @@
diff --git a/src/layouts/components/menu.vue b/src/layouts/components/menu.vue
index ceeaf69..c8dbad3 100644
--- a/src/layouts/components/menu.vue
+++ b/src/layouts/components/menu.vue
@@ -10,8 +10,8 @@
-
+
diff --git a/src/plugins/global-api.js b/src/plugins/global-api.js
index 1b620ce..1fecb3d 100644
--- a/src/plugins/global-api.js
+++ b/src/plugins/global-api.js
@@ -64,7 +64,31 @@ export const excel = (fileName, data) => {
navigator.msSaveBlob(blob, fileName);
}
};
+/**
+ * 字典查询
+ * @param {*} dict 字典数据
+ * @param {*} value 字典值
+ * @param {*} opts 配置
+ * @returns 字典标签
+ */
+export const dict = (dict, value, opts = {}) => {
+ opts = { label: 'label', value: 'value', default: '未知', ...opts };
+ return (dict ? dict.value || dict : []).find((item) => item[opts.value] === value)?.[opts.label] || opts.default;
+};
+/**
+ * 显示姓名
+ * @param {*} nickname 昵称
+ * @param {*} realname 真实姓名
+ * @returns 组合显示
+ */
+export const name = (nickname, realname) => {
+ return realname ? `${nickname}(${realname})` : nickname;
+};
export default (app) => {
app.config.globalProperties.$copy = copy;
+ app.config.globalProperties.$download = download;
+ app.config.globalProperties.$excel = excel;
+ app.config.globalProperties.$dict = dict;
+ app.config.globalProperties.$name = name;
};
diff --git a/src/router/index.js b/src/router/index.js
index fb85255..40a8c9e 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -81,10 +81,10 @@ router.onError((error, to) => {
router.beforeEach(async (to, from, next) => {
if (store.state.local.token) {
if (!store.state.auth.permission.length) {
+ await store.dispatch('auth/getUserInfo');
if (config.useLocalRouter) {
store.commit('auth/setPermission', routes);
} else {
- await store.dispatch('auth/getUserInfo');
await store.dispatch('auth/getPermission');
}
next({ ...to, replace: true });
diff --git a/src/router/modules/system.js b/src/router/modules/system.js
index dd51137..7a38efc 100644
--- a/src/router/modules/system.js
+++ b/src/router/modules/system.js
@@ -15,7 +15,7 @@ export default [
component: () => import('@/views/system/user/index.vue'),
meta: {
title: '用户管理',
- icon: 'user-1-fill',
+ icon: 'Avatar',
},
children: [
{
@@ -24,7 +24,7 @@ export default [
component: () => import('@/views/system/user/form.vue'),
meta: {
title: '创建用户',
- icon: 'user-1-fill',
+ icon: 'Avatar',
hidden: true,
},
},
@@ -34,17 +34,7 @@ export default [
component: () => import('@/views/system/user/form.vue'),
meta: {
title: '编辑用户',
- icon: 'user-1-fill',
- hidden: true,
- },
- },
- {
- path: 'detail/:id',
- name: 'UserDetail',
- component: () => import('@/views/system/user/form.vue'),
- meta: {
- title: '用户详情',
- icon: 'user-1-fill',
+ icon: 'Avatar',
hidden: true,
},
},
diff --git a/src/store/modules/auth.js b/src/store/modules/core/auth.js
similarity index 100%
rename from src/store/modules/auth.js
rename to src/store/modules/core/auth.js
diff --git a/src/store/modules/layout.js b/src/store/modules/core/layout.js
similarity index 95%
rename from src/store/modules/layout.js
rename to src/store/modules/core/layout.js
index b99a0d4..ed987fd 100644
--- a/src/store/modules/layout.js
+++ b/src/store/modules/core/layout.js
@@ -16,9 +16,9 @@ const getters = {
return rootState.auth.permission.filter((item) => item.meta.global !== true);
},
menuList: (state) => {
- return state.menuList.filter((item) => item.meta.hidden !== true);
+ return state.menuList?.filter((item) => item.meta.hidden !== true);
},
- collapseMenu: (state, getters) => state.collapseMenu || getters.menuList.length < 2,
+ collapseMenu: (state, getters) => state.collapseMenu || getters.menuList?.length < 2,
activeAsideName: (state, getters) => getters.asideList.find((item) => item.name === state.activeAside)?.meta.title,
returnUrl: (state) => {
let res = state.returnUrl || '/';
diff --git a/src/store/modules/local.js b/src/store/modules/core/local.js
similarity index 100%
rename from src/store/modules/local.js
rename to src/store/modules/core/local.js
diff --git a/src/store/modules/demo.js b/src/store/modules/demo.js
deleted file mode 100644
index 2819b02..0000000
--- a/src/store/modules/demo.js
+++ /dev/null
@@ -1,20 +0,0 @@
-const state = () => ({
- count: 0,
-});
-const getters = {
- doubleCount: (state) => state.count * 2,
-};
-const mutations = {
- add: (state, num = 1) => (state.count += num),
-};
-const actions = {
- clear: ({ state, commit }) => {
- commit('add', state.count * -1);
- },
-};
-export default {
- state,
- getters,
- mutations,
- actions,
-};
diff --git a/src/store/modules/system/user.js b/src/store/modules/system/user.js
index c864f45..e376a9f 100644
--- a/src/store/modules/system/user.js
+++ b/src/store/modules/system/user.js
@@ -20,7 +20,7 @@ const mutations = {
};
const actions = {
search: async ({ state, commit, rootGetters }) => {
- let res = await api.findUserList({ ...rootGetters['local/page'](state.code), ...state.condition });
+ let res = await api.search({ ...rootGetters['local/page'](state.code), ...state.condition });
if (res) {
commit('setList', res.content);
commit('setTotal', res.totalElements);
@@ -39,15 +39,18 @@ const actions = {
],
});
},
- detail: async (context, id) => {
- let res = await api.getUserDetail(id);
+ detail: async ({ dispatch }, id) => {
+ if (!state.list.length) {
+ await dispatch('search');
+ }
+ let res = state.list.find((item) => item.id === id);
if (!res) {
ElMessage.error('加载详情失败');
}
return res;
},
save: async ({ dispatch }, data) => {
- let save = data.id ? api.updateUser : api.createUser;
+ let save = data.id ? api.update : api.create;
let res = await save(data);
if (res) {
ElMessage.success('保存成功');
@@ -57,13 +60,13 @@ const actions = {
}
return res;
},
- remove: async ({ dispatch }, ids) => {
- if (!ids.length) {
+ remove: async ({ dispatch }, idList) => {
+ if (!idList.length) {
ElMessage.warning('请选择要删除的数据');
} else {
try {
await ElMessageBox.confirm('数据删除后无法恢复,确定要删除吗?', '危险操作');
- let res = await api.removeUser(ids);
+ let res = await api.remove({ idList });
if (res) {
ElMessage.success('删除成功');
dispatch('search');
diff --git a/src/utils/request.js b/src/utils/request.js
index aae5e0a..b53e97c 100644
--- a/src/utils/request.js
+++ b/src/utils/request.js
@@ -1,34 +1,44 @@
import config from '@/configs';
+import { ElMessage } from '@/plugins/element-plus';
import store from '@/store';
import qs from 'qs';
-import { ElMessage } from '@/plugins/element-plus';
-const handleResponse = async ({ config, headers, data, status }) => {
+const handleResponse = async ({ config: requestConfig, headers, data, status }) => {
if (
['application/octet-stream', 'application/zip'].indexOf(headers['content-type']) !== -1 ||
- config['down'] === 'file'
+ requestConfig['down'] === 'file'
) {
return data;
}
let code = data.code || status;
- console.info('[api]', code, config.method, config.url, data.data);
- if (code !== 200) {
- ElMessage.error(data.msg || '服务器异常');
+ console.info('[api]', code, requestConfig.method, requestConfig.url, data.data);
+ if (code !== 'SUCCESS') {
+ ElMessage.error(data.message || '服务器异常');
switch (code) {
- case 500:
- case 501:
+ case 'SYSTEM_ERROR': //系统异常
+ case 'INTERFACE_SYSTEM_ERROR': // 外部接口调用异常
+ case 'CONNECT_TIME_OUT': // 业务连接处理超时
+ case 'NULL_ARGUMENT': // 参数为空
+ case 'ILLEGAL_ARGUMENT': // 参数不合法
+ case 'ILLEGAL_CONFIGURATION': // 非法配置
+ case 'ILLEGAL_STATE': // 非法状态
+ case 'ENUM_CODE_ERROR': // 错误的枚举编码
+ case 'LOGIC_ERROR': // 逻辑错误
+ case 'CONCURRENT_ERROR': // 并发异常
+ case 'ILLEGAL_OPERATION': // 非法操作
+ case 'REPETITIVE_OPERATION': // 重复操作
+ case 'RESOURCE_NOT_FOUND': // 资源不存在
+ case 'RESOURCE_ALREADY_EXIST': // 资源已存在
+ case 'TYPE_UN_MATCH': // 类型不匹配
+ case 'FILE_NOT_EXIST': // 文件不存在
+ case 'LIMIT_BLOCK': // 请求被限流
+ case 'BIZ_ERROR': // 业务处理异常
break;
- case 524:
- case 525:
- case 50008:
- case 50012:
- case 50014:
+ case 'TOKEN_EXPIRE': // TOKEN过期
+ case 'TOKEN_FAIL': // TOKEN失效
+ case 'NO_OPERATE_PERMISSION': // 无操作权限
store.dispatch('auth/logout');
break;
- case 404:
- case 9999:
- console.warn('接口9999', config);
- break;
default:
}
}
@@ -47,7 +57,7 @@ instance.interceptors.request.use(
(config) => {
const token = store.state.local.token;
if (token) {
- config.headers['Authorization'] = `Bearer ${token}`;
+ config.headers['Authorization'] = token;
}
if (config.data && config.headers['Content-Type'] === 'application/x-www-form-urlencoded;charset=UTF-8') {
config.data = qs.stringify(config.data);
diff --git a/src/views/global/login.vue b/src/views/global/login.vue
index 68993cc..04d0a2f 100644
--- a/src/views/global/login.vue
+++ b/src/views/global/login.vue
@@ -21,7 +21,7 @@
-
+
立即登录
@@ -42,40 +42,41 @@
+
-
+
diff --git a/src/views/system/user/form.vue b/src/views/system/user/form.vue
index 0c7e9a0..86f07d7 100644
--- a/src/views/system/user/form.vue
+++ b/src/views/system/user/form.vue
@@ -4,39 +4,38 @@
ref="refsForm"
v-loading="loading"
class="form-content"
- :disabled="route.name === 'UserDetail'"
label-width="100px"
:model="form"
:rules="rules"
>
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
@@ -52,46 +51,47 @@
const submitting = ref(false);
const refsForm = ref(null);
const form = reactive({
- username: null,
- nickname: null,
- sex: 0,
- avatar: null,
- enabled: true,
+ id: null,
+ name: null,
+ platform: null,
+ voucherClueUrls: [],
+ phone: null,
+ qqNo: null,
+ wechatNo: null,
+ addFriendsTime: null,
+ remarks: null,
});
const rules = reactive({
- username: [{ required: true, message: '用户名不能为空' }],
- nickname: [{ required: true, message: '昵称不能为空' }],
- sex: [{ required: true, message: '性别不能为空' }],
- avatar: [{ required: true, message: '头像不能为空' }],
- enabled: [{ required: true, message: '状态不能为空' }],
+ name: [{ required: true, message: '客户名称不能为空' }],
});
- const opts = computed(() => store.state.user.opts);
+ const opts = computed(() => store.state.customerPool.opts);
if (!unref(opts).init) {
- store.dispatch('user/load');
+ store.dispatch('customerPool/load');
}
- /* 详情 */
- watch(
- () => [route.name, route.params.id],
- async (value) => {
- // 不论当前是哪个路由只要名字相同的参数改变就会触发watch,所以需要加上当前路由名字判断,或者可以取独一无二的路由参数名
- if (['UpdateUser', 'UserDetail'].includes(value[0]) && value[1]) {
- loading.value = true;
- let res = await store.dispatch('user/detail', +value[1]);
- if (res) {
- Object.assign(form, res);
- }
- loading.value = false;
+ /* 数据 */
+ const handleLoad = async () => {
+ if (route.params.id) {
+ const id = +route.params.id;
+ if (form.id !== id) {
+ let res = await store.dispatch('customerPool/detail', id);
+ Object.assign(form, res);
}
- },
- { immediate: true }
- );
+ }
+ };
+ onActivated(handleLoad);
/* 交互 */
const handleSave = async () => {
submitting.value = true;
try {
await unref(refsForm).validate();
- let res = await store.dispatch('user/save', unref(form));
+ let data = { ...unref(form) };
+ data.voucherClueUrls = data.voucherClueUrls || [];
+ data.voucherClueUrl = data.voucherClueUrls.join(',');
+ let res = await store.dispatch('customerPool/save', data);
if (res) {
+ if (!data.id) {
+ unref(refsForm).resetFields();
+ }
handleClose();
}
} catch (e) {
@@ -108,7 +108,7 @@
}
};
const handleClose = () => {
- router.push({ name: 'UserManagement' });
+ router.push({ name: 'CustomerPool' });
};
diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue
index 94389ea..82c2bd3 100644
--- a/src/views/system/user/index.vue
+++ b/src/views/system/user/index.vue
@@ -4,6 +4,7 @@
:code="code"
:config="config"
:data="list"
+ :operation="['create', 'search']"
:reset="handleReset"
title="用户"
:total="total"
@@ -13,8 +14,11 @@
>
-
-
+
+
+
+
+
@@ -22,6 +26,7 @@