sige 2 years ago
parent
commit
aa3669768f
  1. 5
      src/src/main/java/com/my/graphiteDigesterBg/diframe/DiActiveRecord.java
  2. 4
      src/src/main/java/com/my/graphiteDigesterBg/diframe/api/DiApiUser.java
  3. 16
      src/src/main/java/com/my/graphiteDigesterBg/diframe/configuration/WebConfig.java
  4. 15
      src/src/main/java/com/my/graphiteDigesterBg/diframe/model/DiMdbRole.java
  5. 5
      src/src/main/java/com/my/graphiteDigesterBg/diframe/model/DiMdbUser.java
  6. 73
      src/web/package-lock.json
  7. 5
      src/web/package.json
  8. 8
      src/web/src/App.vue
  9. 1
      src/web/src/assets/icon/logo.svg
  10. 1
      src/web/src/assets/icon/logout.svg
  11. BIN
      src/web/src/assets/img/login-pic.png
  12. 10
      src/web/src/main.js
  13. 64
      src/web/src/pages/login/Page.vue
  14. 69
      src/web/src/pages/main/Page.vue
  15. 43
      src/web/src/pages/main/contents/AcidManagement.vue
  16. 29
      src/web/src/pages/main/contents/History.vue
  17. 2
      src/web/src/pages/main/contents/Operation.vue
  18. 21
      src/web/src/pages/main/contents/TaskStepManagement.vue
  19. 33
      src/web/src/pages/main/contents/Test.vue
  20. 74
      src/web/src/pages/main/contents/UserManagement.vue
  21. 16
      src/web/src/stores/AppStore.js
  22. 38
      src/web/src/utils/ApiClient.js
  23. 7
      src/web/vue.config.js

5
src/src/main/java/com/my/graphiteDigesterBg/diframe/DiActiveRecord.java

@ -14,6 +14,11 @@ public class DiActiveRecord {
@JsonIgnore @JsonIgnore
public Boolean isNewRecord = true; public Boolean isNewRecord = true;
// to map
public Map<String,Object> toMap() {
return DiActiveRecord.exportModelData(this);
}
// save model // save model
public void save() { public void save() {
var context = DiApplicationContextProvider.getContext(); var context = DiApplicationContextProvider.getContext();

4
src/src/main/java/com/my/graphiteDigesterBg/diframe/api/DiApiUser.java

@ -17,10 +17,10 @@ public class DiApiUser extends DiApiControllerBase {
@ResponseBody @ResponseBody
@RequestMapping("/api/user/login") @RequestMapping("/api/user/login")
public DiApiResponse login(@RequestBody Map<String,Object> params) { public DiApiResponse login(@RequestBody Map<String,Object> params) {
Integer id = (Integer)params.get("id");
String account = (String)params.get("account");
String password = (String)params.get("password"); String password = (String)params.get("password");
var user = DiActiveRecord.findOne(DiMdbUser.class, id);
var user = DiActiveRecord.findOne(DiMdbUser.class, Map.of("account", account));
if ( null == user || !user.matchPassword(password) ) { if ( null == user || !user.matchPassword(password) ) {
return this.error("无效的账号或密码"); return this.error("无效的账号或密码");
} }

16
src/src/main/java/com/my/graphiteDigesterBg/diframe/configuration/WebConfig.java

@ -1,2 +1,16 @@
package com.my.graphiteDigesterBg.diframe.configuration;public class WebConfig {
package com.my.graphiteDigesterBg.diframe.configuration;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowCredentials(true)
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.exposedHeaders("*");
}
} }

15
src/src/main/java/com/my/graphiteDigesterBg/diframe/model/DiMdbRole.java

@ -1,2 +1,15 @@
package com.my.graphiteDigesterBg.diframe.model;public class DiMdbRole {
package com.my.graphiteDigesterBg.diframe.model;
import com.my.graphiteDigesterBg.diframe.DiActiveRecord;
public class DiMdbRole extends DiActiveRecord {
// id
public Integer id;
// name
public String name;
// permissions
public String permissions;
// get table name
public static String getTableName() {
return "app_roles";
}
} }

5
src/src/main/java/com/my/graphiteDigesterBg/diframe/model/DiMdbUser.java

@ -51,4 +51,9 @@ public class DiMdbUser extends DiActiveRecord {
String salt = this.salt; String salt = this.salt;
return DigestUtils.md5DigestAsHex((salt + password + salt).getBytes()); return DigestUtils.md5DigestAsHex((salt + password + salt).getBytes());
} }
// get role
public DiMdbRole getRole() {
return DiActiveRecord.findOne(DiMdbRole.class, this.roleId);
}
} }

73
src/web/package-lock.json

@ -8,9 +8,11 @@
"name": "vue3-demo", "name": "vue3-demo",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@ant-design/icons-vue": "^7.0.1",
"ant-design-vue": "^4.1.1", "ant-design-vue": "^4.1.1",
"axios": "^1.6.5", "axios": "^1.6.5",
"core-js": "^3.8.3", "core-js": "^3.8.3",
"pinia": "^2.1.7",
"vue": "^3.2.13" "vue": "^3.2.13"
}, },
"devDependencies": { "devDependencies": {
@ -3179,6 +3181,11 @@
"integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==",
"dev": true "dev": true
}, },
"node_modules/@vue/devtools-api": {
"version": "6.5.1",
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.5.1.tgz",
"integrity": "sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA=="
},
"node_modules/@vue/reactivity": { "node_modules/@vue/reactivity": {
"version": "3.4.15", "version": "3.4.15",
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.4.15.tgz", "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.4.15.tgz",
@ -8668,6 +8675,50 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/pinia": {
"version": "2.1.7",
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.1.7.tgz",
"integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==",
"dependencies": {
"@vue/devtools-api": "^6.5.0",
"vue-demi": ">=0.14.5"
},
"peerDependencies": {
"@vue/composition-api": "^1.4.0",
"typescript": ">=4.4.4",
"vue": "^2.6.14 || ^3.3.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
},
"typescript": {
"optional": true
}
}
},
"node_modules/pinia/node_modules/vue-demi": {
"version": "0.14.6",
"resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.6.tgz",
"integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/pirates": { "node_modules/pirates": {
"version": "4.0.6", "version": "4.0.6",
"resolved": "https://registry.npmmirror.com/pirates/-/pirates-4.0.6.tgz", "resolved": "https://registry.npmmirror.com/pirates/-/pirates-4.0.6.tgz",
@ -14335,6 +14386,11 @@
} }
} }
}, },
"@vue/devtools-api": {
"version": "6.5.1",
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.5.1.tgz",
"integrity": "sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA=="
},
"@vue/reactivity": { "@vue/reactivity": {
"version": "3.4.15", "version": "3.4.15",
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.4.15.tgz", "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.4.15.tgz",
@ -18743,6 +18799,23 @@
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
"dev": true "dev": true
}, },
"pinia": {
"version": "2.1.7",
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.1.7.tgz",
"integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==",
"requires": {
"@vue/devtools-api": "^6.5.0",
"vue-demi": ">=0.14.5"
},
"dependencies": {
"vue-demi": {
"version": "0.14.6",
"resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.6.tgz",
"integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
"requires": {}
}
}
},
"pirates": { "pirates": {
"version": "4.0.6", "version": "4.0.6",
"resolved": "https://registry.npmmirror.com/pirates/-/pirates-4.0.6.tgz", "resolved": "https://registry.npmmirror.com/pirates/-/pirates-4.0.6.tgz",

5
src/web/package.json

@ -8,9 +8,11 @@
"lint": "vue-cli-service lint" "lint": "vue-cli-service lint"
}, },
"dependencies": { "dependencies": {
"@ant-design/icons-vue": "^7.0.1",
"ant-design-vue": "^4.1.1", "ant-design-vue": "^4.1.1",
"axios": "^1.6.5", "axios": "^1.6.5",
"core-js": "^3.8.3", "core-js": "^3.8.3",
"pinia": "^2.1.7",
"vue": "^3.2.13" "vue": "^3.2.13"
}, },
"devDependencies": { "devDependencies": {
@ -38,7 +40,8 @@
"parser": "@babel/eslint-parser" "parser": "@babel/eslint-parser"
}, },
"rules": { "rules": {
"vue/multi-word-component-names": "off"
"vue/multi-word-component-names": "off",
"no-debugger": "off"
} }
}, },
"browserslist": [ "browserslist": [

8
src/web/src/App.vue

@ -1,6 +1,12 @@
<template> <template>
<page-main />
<page-login v-if="null === appStore.accessToken" />
<page-main v-else />
</template> </template>
<script setup> <script setup>
import PageMain from './pages/main/Page.vue' import PageMain from './pages/main/Page.vue'
import PageLogin from './pages/login/Page.vue'
import { useAppStore } from '@/stores/AppStore';
/** @var {AppStore} */
const appStore = useAppStore();
</script> </script>

1
src/web/src/assets/icon/logo.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="100" height="100" viewBox="0 0 100 100"><g><g><path d="M99.99997243041992,50.748648477783206C99.99997243041992,55.204608477783204,96.38627243041992,58.8169084777832,91.92857243041992,58.8169084777832C87.47047243041992,58.8169084777832,40.42767243041992,53.088008477783205,21.136272430419922,50.748648477783206C42.82147243041992,48.0742484777832,87.47047243041992,42.6804084777832,91.92857243041992,42.6804084777832C96.38627243041992,42.680417068953204,99.99997243041992,46.2926884777832,99.99997243041992,50.748648477783206Z" fill="#FAC03D" fill-opacity="1"/></g><g><path d="M89.9514084411621,78.40465869384765C89.9592084411621,80.82313869384765,88.00010844116211,82.78785869384765,85.58060844116211,82.78785869384765C83.1671084411621,82.78785869384765,57.69960844116211,79.69117869384766,47.25130844116211,78.41872869384765C58.99230844116211,76.96891869384766,83.1671084411621,74.04959869384766,85.58060844116211,74.04959869384766C87.9891084411621,74.04958151144766,89.9437084411621,75.99714869384766,89.9514084411621,78.40465869384765Z" fill="#FAC03D" fill-opacity="1"/></g><g><path d="M96.69639218139648,66.31107654296875C96.69799218139649,69.78085654296875,93.88449218139648,72.59445654296874,90.41339218139649,72.59445654296874C86.94369218139649,72.59445654296874,51.622192181396485,68.13248654296875,36.605892181396484,66.31106654296875C53.48659218139649,64.23066654296875,86.94369218139649,60.03045654296875,90.41339218139649,60.03045654296875C93.88339218139649,60.03045654296875,96.69639218139648,62.84238654296875,96.69639218139648,66.31107654296875Z" fill="#FAC03D" fill-opacity="1"/></g><g><path d="M50.0676,0.00000859117C14.8134,-0.00580763,-9.38025,35.4747,3.51251,68.2739C16.4053,101.073,58.2903,110.6,84.1106,86.6055C80.4635,86.2705,72.3921,85.288,58.3728,83.5454L56.4408,83.3089C38.0246,92.955,15.2725,85.8429,5.63499,67.4273C-4.00248,49.0118,3.12783,26.2734,21.5571,16.6522C39.9864,7.03092,62.7288,14.1738,72.3414,32.6023C75.1721,38.1034,81.9953,40.1716,87.4057,37.1684C92.8162,34.1652,94.667,27.2823,91.492,21.9723C82.1939,8.22286,66.6703,-0.0111685,50.0676,0.00000859117Z" fill="#275EFB" fill-opacity="1"/></g></g></svg>

1
src/web/src/assets/icon/logout.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="20" height="20" viewBox="0 0 20 20"><g><path d="M6.89248,0L18.9247,0C19.5161,0,20,0.473684,20,1.05263L20,18.9474C20,19.5263,19.5161,20,18.9247,20L6.89248,20C6.30108,20,5.81721,19.5263,5.81721,18.9474C5.81721,18.3684,6.30108,17.8947,6.89248,17.8947L17.8495,17.8947L17.8495,2.10526L6.89248,2.10526C6.30108,2.10526,5.81721,1.63158,5.81721,1.05263C5.81721,0.473684,6.30108,0,6.89248,0ZM3.67742,11.051L5.89247,13.1983C6.31473,13.605,6.31955,14.27,5.90322,14.6825C5.48359,15.0901,4.80673,15.0901,4.3871,14.6825L0.322581,10.7352C0.11828,10.5352,0,10.272,0,9.9878C0,9.70359,0.11828,9.44043,0.322581,9.24043L4.37634,5.30359C4.80645,4.90359,5.48387,4.90359,5.90322,5.32464C6.32258,5.73516,6.31183,6.39832,5.89247,6.80885L3.68817,8.94569L11.9785,8.94569C12.5699,8.94569,13.0538,9.41938,13.0538,9.99832C13.0538,10.5773,12.5699,11.051,11.9785,11.051L3.67742,11.051Z" fill-rule="evenodd" fill="#8799AB" fill-opacity="1"/></g></svg>

BIN
src/web/src/assets/img/login-pic.png

After

Width: 1028  |  Height: 762  |  Size: 201 KiB

10
src/web/src/main.js

@ -1,9 +1,19 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue' import App from './App.vue'
import Antd from 'ant-design-vue'; import Antd from 'ant-design-vue';
import * as AntdIcons from '@ant-design/icons-vue'
import "./diframe/style/tailwind.css" import "./diframe/style/tailwind.css"
import "./style/app.css" import "./style/app.css"
import 'ant-design-vue/dist/reset.css'; import 'ant-design-vue/dist/reset.css';
const app = createApp(App); const app = createApp(App);
app.use(createPinia());
app.use(Antd); app.use(Antd);
(() => {
let icons = AntdIcons;
for ( let icon in icons ) {
app.component(icon, icons[icon]);
}
})();
app.mount('#app'); app.mount('#app');

64
src/web/src/pages/login/Page.vue

@ -0,0 +1,64 @@
<template>
<a-row class="h-full">
<a-col :span="14" class="flex flex-row items-center">
<div class="p-10">
<img class="w-full" src="@/assets/img/login-pic.png" />
</div>
</a-col>
<a-col :span="10" class="flex flex-row items-center p-10">
<div class="bg-gray-100 p-20 rounded-2xl w-full">
<div class="text-center">
<img src="@/assets/icon/logo.svg" class="w-20" />
</div>
<div class="text-center my-8">
<span style="font-size:30px;font-weight:500;color:#8799AB;">长春黄金研究院有限公司</span>
</div>
<div class="p-10">
<div class="mb-2 ml-1">用户名</div>
<div>
<a-input class="rounded-full py-2 px-5"
v-model:value="account"
></a-input>
</div>
<div class="mb-2 mt-8 ml-1">密码</div>
<div>
<a-input-password class="rounded-full py-2 px-5"
v-model:value="password"
></a-input-password>
</div>
<div>
<a-button block type="primary" size="large" class="!rounded-full mt-8" @click="actionLogin">登录</a-button>
</div>
</div>
<div class="text-center text-xs mt-10 text-gray-400">CHANGCHUN GOLD RESEARCH INSTITUTE CO.,LTD.</div>
</div>
</a-col>
</a-row>
</template>
<script setup>
import { ref } from 'vue';
import { useAppStore } from '@/stores/AppStore';
import { Modal } from 'ant-design-vue';
import ApiClient from '@/utils/ApiClient';
/** @var {AppStore} */
const appStore = useAppStore();
/** @var {String} */
const account = ref('');
/** @var {String} */
const password = ref('');
// action login
async function actionLogin() {
try {
const client = ApiClient.getClient();
let response = await client.userLogin({
account: account.value,
password: password.value,
});
appStore.setAccessToken(response.accessToken);
} catch ( e ) {
Modal.error({title: '登录失败',content: e.message});
return ;
}
}
</script>

69
src/web/src/pages/main/Page.vue

@ -26,20 +26,32 @@
</a-layout-header> </a-layout-header>
<a-layout class="!bg-transparent"> <a-layout class="!bg-transparent">
<a-layout-content class="overflow-hidden"> <a-layout-content class="overflow-hidden">
<content-operation />
<content-operation v-if="'operation' === activeContent" />
<content-user-management v-else-if="'user' === activeContent"/>
<content-history v-else-if="'hisotry' === activeContent"/>
<content-task-step-management v-else-if="'preset' === activeContent"/>
<content-acid-management v-else-if="'acid' === activeContent"/>
<content-test v-else-if="'test' === activeContent"/>
</a-layout-content> </a-layout-content>
<a-layout-sider class="!bg-transparent p-1" width="150">
<a-layout-sider class="!bg-transparent p-1" width="200">
<div class="h-full flex flex-col justify-between"> <div class="h-full flex flex-col justify-between">
<div>
<div class="bg-blue-400 text-white px-5 py-3 text-xl rounded-full mb-1">实验操作</div>
<div class="text-gray-400 px-5 py-3 text-xl rounded-full mb-1">操作记录</div>
<div class="text-gray-400 px-5 py-3 text-xl rounded-full mb-1">预设管理</div>
<div class="text-gray-400 px-5 py-3 text-xl rounded-full mb-1">酸液管理</div>
<div class="text-gray-400 px-5 py-3 text-xl rounded-full mb-1">用户管理</div>
<div class="text-gray-400 px-5 py-3 text-xl rounded-full mb-1">功能测试</div>
<!-- content menu -->
<div class="page-menu">
<div v-for="content in contents" :key="content.key"
class="item py-3 text-xl rounded-full mb-3"
:class="{'active': content.key === activeContent}"
@click="actionSwitchContent(content.key)"
>
<component :is="`${content.icon}-outlined`" class="mr-5" />
<span>{{ content.title }}</span>
<a-badge v-if="content.key === activeContent" class="ml-1" color="#1EF3A8" />
</div>
</div> </div>
<div> <div>
<div class="text-gray-400 px-5 py-3 text-xl rounded-full mb-1">退出登录</div>
<div class="text-gray-400 px-5 py-3 text-xl rounded-full mb-1 text-center cursor-pointer" @click="actionUserLogout">
<img src="@/assets/icon/logout.svg" class="h-4 mr-1 align-baseline" />
退出登录
</div>
</div> </div>
</div> </div>
</a-layout-sider> </a-layout-sider>
@ -47,5 +59,42 @@
</a-layout> </a-layout>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue';
import { useAppStore } from '@/stores/AppStore';
import ContentOperation from './contents/Operation.vue' import ContentOperation from './contents/Operation.vue'
import ContentUserManagement from './contents/UserManagement.vue'
import ContentHistory from './contents/History.vue'
import ContentTaskStepManagement from './contents/TaskStepManagement.vue'
import ContentAcidManagement from './contents/AcidManagement.vue'
import ContentTest from './contents/Test.vue'
import ApiClient from '@/utils/ApiClient';
/** @var {AppStore} */
const appStore = useAppStore();
/** @var {Ref<string>} */
const activeContent = ref('operation');
/** @var {Array<{key:string,title:string}>} */
const contents = [
{key:'operation',title:'实验操作',icon:'dashboard'},
{key:'hisotry',title: '操作记录',icon:'file-text'},
{key:'preset', title: '预设管理',icon:'partition'},
{key:'acid', title: '酸液管理',icon:'experiment'},
{key:'user', title: '用户管理',icon:'user'},
{key:'test', title: '功能测试',icon:'issues-close'},
];
// action logout
async function actionUserLogout() {
let client = ApiClient.getClient();
await client.userLogout();
appStore.setAccessToken(null);
}
// action switch content
function actionSwitchContent(content) {
activeContent.value = content;
}
</script> </script>
<style scoped>
.page-menu .item.active {background: linear-gradient(270deg, #3556FA 0%, #007BFF 100%);color: white;}
.page-menu .item {cursor: pointer;color: #8799AB;padding-left: 1.25rem;padding-right: 1.25rem;}
</style>

43
src/web/src/pages/main/contents/AcidManagement.vue

@ -0,0 +1,43 @@
<template>
<a-row class="h-full p-1">
<a-col :span="10" class="p-5">
<div class="flex flex-col justify-center items-center h-full bg-gray-50 rounded-3xl">
<div class="text-center mb-10 text-3xl bg-blue-200 p-5 rounded-3xl w-32 text-gray-100">硫酸</div>
<div class="w-full relative">
<div class="h-full w-full text-center">
<img class="w-1/2" src="../../../assets/icon/bucket-full.svg" />
</div>
<div class="h-full w-full text-center absolute top-0 overflow-hidden" :style="{height:`${Math.random()*100}%`}">
<img class="w-1/2" src="../../../assets/icon/bucket-empty.svg" />
</div>
</div>
<div class="mx-2 p-2 mt-3 rounded-2xl text-3xl" style="background:#D2DFEF;color:#8799AB;">
<span class="inline-block p-1 rounded-2xl mr-5" style="background:#DCE8F7;">3000g</span>
<span class="inline-block py-1">5000g</span>
</div>
</div>
</a-col>
<a-col :span="10" class="p-5">
<div class="flex flex-col justify-center items-center h-full bg-gray-50 rounded-3xl">
<div class="text-center mb-10 text-3xl bg-blue-200 p-5 rounded-3xl w-32 text-gray-100">硫酸</div>
<div class="w-full relative">
<div class="h-full w-full text-center">
<img class="w-1/2" src="../../../assets/icon/bucket-full.svg" />
</div>
</div>
<div class="mx-2 p-2 mt-3 rounded-2xl text-3xl" style="background:#D2DFEF;color:#8799AB;">
<span class="inline-block p-1 rounded-2xl mr-5" style="background:#DCE8F7;">3000g</span>
<span class="inline-block py-1">5000g</span>
</div>
<div class="p-5 text-3xl bg-blue-500 text-white mt-10 rounded-3xl">加载</div>
</div>
</a-col>
<a-col :span="4" class="p-5">
<div class="flex flex-col justify-center items-center h-full bg-gray-50 rounded-3xl">
<div v-for="i in 8" :key="i" class="bg-white bg-gray-200 p-5 mb-5 w-3/4 text-center rounded-3xl text-xl text-gray-500" :class="{'!bg-blue-500 !text-white':i==2}">
硫酸
</div>
</div>
</a-col>
</a-row>
</template>

29
src/web/src/pages/main/contents/History.vue

@ -0,0 +1,29 @@
<template>
<div class="p-1">
<a-table :dataSource="dataSource" :columns="columns"></a-table>
</div>
</template>
<script setup>
import { ref } from 'vue';
/** @var {Array<Object>} */
const columns = [
{key:'account',dataIndex:'account',title:'用户',align:'center'},
{key:'slot',dataIndex:'slot',title:'试管架',align:'center'},
{key:'action',dataIndex:'action',title:'动作',align:'center'},
{key:'createdAt',dataIndex: 'createdAt',title:'时间',align:'center'},
];
/** @var {Array<Object>} */
const dataSource = ref([
{account:'admin',slot:'A-1',action:'加入试管',createdAt:'2021-08-01 12:00:00'},
{account:'admin',slot:'A-1',action:'加酸:硫酸',createdAt:'2021-08-01 12:00:00'},
{account:'admin',slot:'A-1',action:'加热: 270℃ 15分钟',createdAt:'2021-08-01 12:00:00'},
{account:'admin',slot:'A-1',action:'加酸:硫酸',createdAt:'2021-08-01 12:00:00'},
{account:'admin',slot:'A-1',action:'加热: 270℃ 15分钟',createdAt:'2021-08-01 12:00:00'},
{account:'admin',slot:'A-1',action:'加酸:硫酸',createdAt:'2021-08-01 12:00:00'},
{account:'admin',slot:'A-1',action:'加热: 270℃ 15分钟',createdAt:'2021-08-01 12:00:00'},
{account:'admin',slot:'A-1',action:'加酸:硫酸',createdAt:'2021-08-01 12:00:00'},
{account:'admin',slot:'A-1',action:'加热: 270℃ 15分钟',createdAt:'2021-08-01 12:00:00'},
{account:'admin',slot:'A-1',action:'加酸:硫酸',createdAt:'2021-08-01 12:00:00'},
{account:'admin',slot:'A-1',action:'加热: 270℃ 15分钟',createdAt:'2021-08-01 12:00:00'},
]);
</script>

2
src/web/src/pages/main/contents/Operation.vue

@ -86,7 +86,7 @@
</a-row> </a-row>
<div class="p-1"> <div class="p-1">
<div class="flex flex-row justify-evenly bg-white rounded-2xl p-5"> <div class="flex flex-row justify-evenly bg-white rounded-2xl p-5">
<div v-for="i in 9" :key="i">
<div v-for="i in 8" :key="i">
<div class="w-full relative"> <div class="w-full relative">
<div class="h-full w-full text-center"> <div class="h-full w-full text-center">
<img class="w-1/2" src="../../../assets/icon/bucket-full.svg" /> <img class="w-1/2" src="../../../assets/icon/bucket-full.svg" />

21
src/web/src/pages/main/contents/TaskStepManagement.vue

@ -0,0 +1,21 @@
<template>
<div class="p-1">
<a-table :dataSource="dataSource" :columns="columns"></a-table>
</div>
</template>
<script setup>
import { ref } from 'vue';
/** @var {Array<Object>} */
const columns = [
{key:'name',dataIndex:'name',title:'名称',align:'center'},
{key:'steps',dataIndex:'steps',title:'步骤',align:'left'},
{key:'action',dataIndex:'action',title:'操作',align:'center'},
];
/** @var {Array<Object>} */
const dataSource = ref([
{name:'预设001',steps:'加酸:硫酸 -> 加热: 270℃ 15分钟 -> 加酸:硫酸 -> 加热: 270℃ 15分钟 -> 加酸:硫酸 -> 加热: 270℃ 15分钟',action:'删除 编辑'},
{name:'预设002',steps:'加酸:盐酸 -> 加热: 270℃ 15分钟 -> 加酸:硫酸 -> 加热: 270℃ 15分钟 加热: 270℃ 15分钟',action:'删除 编辑'},
{name:'预设003',steps:'加酸:氢氟酸 -> 加热: 270℃ 15分钟 -> 加酸:盐酸 -> 加热: 270℃ 15分钟 -> 加酸:盐酸 -> 加热: 270℃ 15分钟',action:'删除 编辑'},
{name:'预设004',steps:'加酸:盐酸 -> 加热: 270℃ 15分钟 -> 加酸:氢氟酸 -> 加热: 270℃ 15分钟 -> 加酸:盐酸 -> 加热: 270℃ 15分钟',action:'删除 编辑'},
]);
</script>

33
src/web/src/pages/main/contents/Test.vue

@ -0,0 +1,33 @@
<template>
<div class="p-1">
<a-table :dataSource="dataSource" :columns="columns"></a-table>
</div>
</template>
<script setup>
import { ref } from 'vue';
/** @var {Array<Object>} */
const columns = [
{key:'name',dataIndex:'name',title:'名称',align:'left'},
{key:'action',dataIndex:'action',title:'操作',align:'right'},
];
/** @var {Array<Object>} */
const dataSource = ref([
{name:'加液机械臂',action:'复位 最小值 最大值'},
{name:'蠕动泵01',action:'复位 加液 回抽'},
{name:'蠕动泵02',action:'复位 加液 回抽'},
{name:'蠕动泵03',action:'复位 加液 回抽'},
{name:'蠕动泵04',action:'复位 加液 回抽'},
{name:'蠕动泵05',action:'复位 加液 回抽'},
{name:'蠕动泵06',action:'复位 加液 回抽'},
{name:'蠕动泵07',action:'复位 加液 回抽'},
{name:'蠕动泵08',action:'复位 加液 回抽'},
{name:'蠕动泵09',action:'复位 加液 回抽'},
{name:'蠕动泵10',action:'复位 加液 回抽'},
{name:'蠕动泵11',action:'复位 加液 回抽'},
{name:'蠕动泵12',action:'复位 加液 回抽'},
{name:'蠕动泵13',action:'复位 加液 回抽'},
{name:'蠕动泵14',action:'复位 加液 回抽'},
{name:'蠕动泵15',action:'复位 加液 回抽'},
{name:'蠕动泵16',action:'复位 加液 回抽'},
]);
</script>

74
src/web/src/pages/main/contents/UserManagement.vue

@ -0,0 +1,74 @@
<template>
<div class="p-1">
<a-table :dataSource="dataSource" :columns="columns">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'roleName'">
{{ record.role.name }}
</template>
<template v-else-if="'createdAt' === column.key">
{{ formatCreatedAt(record.createdAt) }}
</template>
<template v-else-if="'action' === column.key">
<a-popconfirm title="是否删除该用户?" @confirm="actionDelete(record)">
<a-button class="ml-1" disabled>删除</a-button>
</a-popconfirm>
<a-button class="ml-1" disabled>修改权限</a-button>
<a-button class="ml-1" disabled>修改密码</a-button>
</template>
</template>
</a-table>
</div>
</template>
<script setup>
import ApiClient from '@/utils/ApiClient';
import { onMounted, ref } from 'vue';
/** @var {Array<Object>} */
const columns = [
{key:'account',dataIndex:'account',title:'用户名',align:'center'},
{key:'roleName',title:'权限',align:'center'},
{key:'createdAt',dataIndex: 'createdAt',title:'创建时间',align:'center'},
{key:'action',title:'操作',align:'center'},
];
/** @var {Array<Object>} */
const dataSource = ref([]);
/** @var {ApiClient} */
let client = null;
// on mounted
onMounted(mounted);
// mounted
async function mounted() {
client = ApiClient.getClient();
await refresh();
}
// refresh
async function refresh() {
let response = await client.userList();
dataSource.value = structuredClone(response.list);
}
// format created at
function formatCreatedAt(createdAt) {
if ( 0 === createdAt ) {
return '';
}
let time = new Date(createdAt);
let timeString = [];
timeString.push(time.getFullYear() + '-');
timeString.push((time.getMonth() + 1).toString().padStart(2,'0') + '-');
timeString.push(time.getDate().toString().padStart(2,'0') + ' ');
timeString.push(time.getHours().toString().padStart(2,'0') + ':');
timeString.push(time.getMinutes().toString().padStart(2,'0') + ':');
timeString.push(time.getSeconds().toString().padStart(2,'0'));
return timeString.join('');
}
// action delete
async function actionDelete(record) {
await client.userDelete(record.id);
await refresh();
}
</script>

16
src/web/src/stores/AppStore.js

@ -0,0 +1,16 @@
import { defineStore } from 'pinia'
export const useAppStore = defineStore('AppStore', {
state: () => {
return {
accessToken : null,
};
},
// set access token
actions: {
// set access token
setAccessToken( token ) {
this.accessToken = token;
},
},
})

38
src/web/src/utils/ApiClient.js

@ -1,4 +1,5 @@
import axios from 'axios'; import axios from 'axios';
import { useAppStore } from '@/stores/AppStore';
export default class ApiClient { export default class ApiClient {
// client instance // client instance
static client = null; static client = null;
@ -21,8 +22,40 @@ export default class ApiClient {
// call api // call api
async call( name, params ) { async call( name, params ) {
const response = await axios.post(`/api/${name}`, params);
return response.data;
const appStore = useAppStore();
let headers = {};
headers['App-Access-Token'] = appStore.accessToken;
const response = await axios.post(`http://localhost:8080/api/${name}`, params, {
headers : headers,
});
if ( !response.data.success ) {
throw new Error(response.data.message);
}
return response.data.data;
}
// user login
async userLogin( params ) {
return await this.call('user/login', params);
}
// user logout
async userLogout() {
return await this.call('user/logout');
}
// user list
async userList() {
return await this.call('user/list');
}
// user delete
async userDelete( id ) {
return await this.call('user/delete', {id});
} }
// append task // append task
@ -33,6 +66,7 @@ export default class ApiClient {
}); });
} }
// task action execute
async taskActionExecute() { async taskActionExecute() {
return await this.call('task/task-action-execute'); return await this.call('task/task-action-execute');
} }

7
src/web/vue.config.js

@ -1,4 +1,9 @@
const { defineConfig } = require('@vue/cli-service') const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({ module.exports = defineConfig({
transpileDependencies: true
transpileDependencies: true,
// @link https://cli.vuejs.org/config/#devserver
devServer : {
port : 9001,
},
}) })
Loading…
Cancel
Save