Просмотр исходного кода

add.添加简单的用户登录和注册

chen xueyi 2 лет назад
Родитель
Сommit
d7b18be339

+ 3 - 0
components.d.ts

@@ -26,6 +26,9 @@ declare module 'vue' {
     ElTimeline: typeof import('element-plus/es')['ElTimeline']
     ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
     Layout: typeof import('./src/components/Layout.vue')['default']
+    Login: typeof import('./src/components/Login.vue')['default']
+    LoginView: typeof import('./src/components/Login.vue')['default']
+    Register: typeof import('./src/components/Register.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
     TCFinalList: typeof import('./src/components/TCFinalList.vue')['default']

+ 30 - 0
package-lock.json

@@ -13,6 +13,7 @@
         "dayjs": "^1.11.10",
         "echarts": "^5.5.0",
         "element-plus": "^2.6.0",
+        "file-saver": "^2.0.5",
         "form-data": "^4.0.0",
         "mammoth": "^1.7.1",
         "pinia": "^2.1.7",
@@ -25,6 +26,7 @@
         "@types/node": "^20.12.6",
         "@vitejs/plugin-vue": "^5.0.4",
         "chai": "^5.1.0",
+        "sass": "^1.77.1",
         "typescript": "^5.2.2",
         "unplugin-auto-import": "^0.17.5",
         "unplugin-vue-components": "^0.26.0",
@@ -1395,6 +1397,11 @@
         "reusify": "^1.0.4"
       }
     },
+    "node_modules/file-saver": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmmirror.com/file-saver/-/file-saver-2.0.5.tgz",
+      "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
+    },
     "node_modules/fill-range": {
       "version": "7.0.1",
       "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -1511,6 +1518,12 @@
       "resolved": "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz",
       "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
     },
+    "node_modules/immutable": {
+      "version": "4.3.5",
+      "resolved": "https://registry.npmmirror.com/immutable/-/immutable-4.3.5.tgz",
+      "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==",
+      "dev": true
+    },
     "node_modules/inherits": {
       "version": "2.0.4",
       "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
@@ -2112,6 +2125,23 @@
       "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz",
       "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
     },
+    "node_modules/sass": {
+      "version": "1.77.1",
+      "resolved": "https://registry.npmmirror.com/sass/-/sass-1.77.1.tgz",
+      "integrity": "sha512-OMEyfirt9XEfyvocduUIOlUSkWOXS/LAt6oblR/ISXCTukyavjex+zQNm51pPCOiFKY1QpWvEH1EeCkgyV3I6w==",
+      "dev": true,
+      "dependencies": {
+        "chokidar": ">=3.0.0 <4.0.0",
+        "immutable": "^4.0.0",
+        "source-map-js": ">=0.6.2 <2.0.0"
+      },
+      "bin": {
+        "sass": "sass.js"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
     "node_modules/scule": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz",

+ 2 - 0
package.json

@@ -14,6 +14,7 @@
     "dayjs": "^1.11.10",
     "echarts": "^5.5.0",
     "element-plus": "^2.6.0",
+    "file-saver": "^2.0.5",
     "form-data": "^4.0.0",
     "mammoth": "^1.7.1",
     "pinia": "^2.1.7",
@@ -26,6 +27,7 @@
     "@types/node": "^20.12.6",
     "@vitejs/plugin-vue": "^5.0.4",
     "chai": "^5.1.0",
+    "sass": "^1.77.1",
     "typescript": "^5.2.2",
     "unplugin-auto-import": "^0.17.5",
     "unplugin-vue-components": "^0.26.0",

+ 0 - 0
src/api/users.ts


+ 132 - 0
src/components/Login.vue

@@ -0,0 +1,132 @@
+<script lang="ts">
+import { defineComponent, ref } from 'vue';
+import router from "../router";
+import {ElButton, ElForm, ElFormItem, ElInput, ElMessage} from "element-plus";
+import axios from 'axios';
+// store 即 { token, saveToken }, 官网不建议直接解构使用
+
+export default defineComponent({
+  name: 'Login',
+  components: { ElForm, ElFormItem, ElInput, ElButton },
+  setup() {
+    const loginForm = ref({
+      username: '',
+      password: ''
+    });
+
+    const loginRules = ref({
+      username: [
+        { required: true, message: '用户名不能为空', trigger: 'blur' }
+      ],
+      password: [
+        { required: true, message: '密码不能为空', trigger: 'blur' }
+      ]
+    });
+
+    const login = async () => {
+      const isLoading = ref(false)
+
+      console.log(loginForm.value)
+      console.log("正式登录请求")
+
+      // 点击登录后,让登录按钮开始转圈圈(展示加载动画)
+      isLoading.value = true;
+      // 如果经过校验,账号密码都不为空,则发送请求到后端登录接口
+
+      // 使用 axios 将登录信息发送到后端
+      axios({
+        url: "http://localhost:8081/user/login",               // 请求地址
+        method: "post",                       // 请求方法
+        headers: {                            // 请求头
+          "Content-Type": "application/json",
+        },
+        params: {                             // 请求参数
+          username: loginForm.value.username,
+          password: loginForm.value.password,
+        },
+      }).then((res) => { // 当收到后端的响应时执行该括号内的代码,res 为响应信息,也就是后端返回的信息
+        if (res.data.code === "0") {  // 当响应的编码为 0 时,说明登录成功
+          // 将用户信息存储到sessionStorage中
+          sessionStorage.setItem("userInfo", JSON.stringify(res.data.data));
+          // 跳转页面到首页
+          router.push('/upload');
+          // 显示后端响应的成功信息
+          ElMessage.success("登录成功!")
+        } else {  // 当响应的编码不为 0 时,说明登录失败
+          // 显示后端响应的失败信息
+          ElMessage.error({
+            message: res.data.msg,
+            type: "warning",
+          });
+        }
+        // 不管响应成功还是失败,收到后端响应的消息后就不再让登录按钮显示加载动画了
+        isLoading.value = false;
+        console.log(res);
+      });
+    }
+
+
+      const gotoRegister = () => {
+      router.push('/register');
+    };
+
+
+    return {
+      loginForm,
+      loginRules,
+      gotoRegister,
+      login,
+    };
+  }
+});
+</script>
+
+<template>
+
+  <div class="login">
+    <el-form  :model="loginForm" :rules="loginRules"  label-width="120px" label-position="top" size="large">
+      <h2>登录</h2>
+      <el-form-item label="请输入用户名" prop="username">
+        <el-input v-model="loginForm.username" />
+      </el-form-item>
+      <el-form-item label="请输入密码" prop="password">
+        <el-input v-model="loginForm.password" type="password" show-password/>
+      </el-form-item>
+
+      <el-form-item>
+        <el-button type="primary" @click="login" :loading="isLoading">登录</el-button>
+        <el-button type="primary" @click="gotoRegister" >注册</el-button>
+      </el-form-item>
+    </el-form>
+
+  </div>
+</template>
+
+<style lang="scss" scoped>
+
+.login {
+  background-color: white;
+  height: 100vh;
+  width: 150vh;
+  // 弹性盒模型
+  display: flex;
+  // 水平居中
+  justify-content: center;
+  // 垂直居中
+  align-items: center;
+
+  .el-form {
+    width: 300px;
+    background-color: lightsteelblue;
+    padding: 30px;
+    border-radius: 10px;
+
+    .el-form-item {
+      margin-top: 30px;
+    }
+
+  }
+
+}
+
+</style>

+ 145 - 0
src/components/Register.vue

@@ -0,0 +1,145 @@
+<!-- Register.vue -->
+<template>
+  <div class="register">
+    <el-form  :model="registerForm" :rules="registerRules">
+      <h2>注册</h2>
+      <el-form-item label="用户名" prop="username">
+        <el-input v-model="registerForm.username"></el-input>
+      </el-form-item>
+      <el-form-item label="用户密码" prop="password">
+        <el-input v-model="registerForm.password" type="password" show-password></el-input>
+      </el-form-item>
+      <el-form-item label="确认密码" prop="confirm">
+        <el-input v-model="registerForm.confirm" type="confirm" show-password></el-input>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" @click="register">注册</el-button>
+        <el-button type="primary" @click="backToLogin">返回登陆</el-button>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, ref } from 'vue';
+import {ElForm, ElFormItem, ElInput, ElButton, ElMessage} from 'element-plus';
+import router from "../router";
+import axios from "axios";
+
+export default defineComponent({
+  name: 'Register',
+  components: { ElForm, ElFormItem, ElInput, ElButton },
+  setup() {
+    const registerForm = ref({
+      username: '',
+      password: '',
+      confirm: ''
+    });
+
+    const equalToPassword = (rule: any, value: any, callback: any) => {
+      if (registerForm.value.password !== value) {
+        callback(new Error("两次输入的密码不一致"));
+      } else {
+        callback();
+      }
+    };
+
+    const registerRules = ref({
+      username: [
+        { required: true, message: '请输入用户名', trigger: 'blur' }
+      ],
+      password: [
+        { required: true, message: '请输入密码', trigger: 'blur' }
+      ],
+      confirm: [
+        { required: true, message: '请再次输入密码', trigger: 'blur' },
+        { required: true, validator: equalToPassword, trigger: "blur" }
+      ]
+    });
+
+    const register = () => {
+      const isLoading = ref(false)
+
+      console.log(registerForm.value)
+      console.log("正式注册请求")
+
+      // 点击登录后,让登录按钮开始转圈圈(展示加载动画)
+      isLoading.value = true;
+      // 如果经过校验,账号密码都不为空,则发送请求到后端登录接口
+
+      // 使用 axios 将登录信息发送到后端
+      axios({
+        url: "http://localhost:8081/user/register",               // 请求地址
+        method: "post",                       // 请求方法
+        headers: {                            // 请求头
+          "Content-Type": "application/json",
+        },
+        data: {                             // 请求参数
+          username: registerForm.value.username,
+          password: registerForm.value.password,
+        },
+      }).then((res) => { // 当收到后端的响应时执行该括号内的代码,res 为响应信息,也就是后端返回的信息
+        if (res.data.code === "0") {  // 当响应的编码为 0 时,说明登录成功
+          // 将用户信息存储到sessionStorage中
+          sessionStorage.setItem("userInfo", JSON.stringify(res.data.data));
+          // 跳转页面到首页
+          router.push('/login');
+          // 显示后端响应的成功信息
+          ElMessage.success("注册成功!")
+        } else {  // 当响应的编码不为 0 时,说明登录失败
+          // 显示后端响应的失败信息
+          ElMessage.error({
+            message: res.data.msg,
+            type: "warning",
+          });
+        }
+        // 不管响应成功还是失败,收到后端响应的消息后就不再让登录按钮显示加载动画了
+        isLoading.value = false;
+        console.log(res);
+      });
+    };
+
+    const backToLogin = () => {
+      router.push('/login');
+    };
+
+
+    return {
+      registerForm,
+      registerRules,
+      register,
+      backToLogin
+    };
+  }
+});
+</script>
+
+
+<style lang="scss" scoped>
+
+.register {
+  background-color: white;
+  height: 100vh;
+  width: 150vh;
+  // 弹性盒模型
+  display: flex;
+  // 水平居中
+  justify-content: center;
+  // 垂直居中
+  align-items: center;
+
+  .el-form {
+    width: 300px;
+    background-color: lightsteelblue;
+    padding: 30px;
+    border-radius: 10px;
+
+    .el-form-item {
+      margin-top: 30px;
+    }
+
+  }
+
+}
+
+</style>

+ 81 - 47
src/components/TCFinalList.vue

@@ -14,6 +14,8 @@
 
 <script lang="ts" setup>
 import * as XLSX from 'xlsx'
+
+import FileSaver from 'file-saver'
 import { useRouter } from 'vue-router';
 import { InputVariable } from './Upload.vue'
 import {onMounted, ref} from "vue";
@@ -28,11 +30,7 @@ const docxList = ref<DocxList[]>([])
 docxList.value = useStore.DocxList
 const itemIndex = ref(0)
 
-interface Step {
-  步骤编号: String,
-  操作?: String,
-  预期结果?: String,
-}
+
 // type testCase = {
 //      编号: String,
 //      用例标题?: String,
@@ -47,19 +45,19 @@ type testCase = {
   预期结果?: String
 }
 const tableData = ref<any>([])
-
+//
 //测试版
 // const tableData = [{
 //   编号: "001",
 //   用例标题: "test",
 //   前置条件: "String",
-//   步骤描述: "String",
-//   预期结果: "String"
+//   步骤描述: "在首页点击登录按钮",
+//   预期结果: "成功登录"
 // },{
 //   编号: "002",
 //   用例标题: "test",
 //   前置条件: "String",
-//   步骤描述: "String",
+//   步骤描述: "在首页点击登录按钮",
 //   预期结果: "String"
 // }]
 const docxMessage = (index: number) => {
@@ -70,6 +68,12 @@ onMounted(() => {
   parse()
 
 });
+
+const router = useRouter();
+const gotoLastPage = () => {
+  router.push('/testcaseList');
+};
+
 function parse() {
   if(useStore.DocxList[itemIndex.value].testCaseList.length === 0){
     const loading = ElLoading.service({
@@ -126,33 +130,55 @@ function parse() {
   }
 }
 
+function descripRes(row: any) {
+  if(!Array.isArray(row.步骤描述)){
+    return row.步骤描述
+  }else{
+    let res = ''
+    for(const step of row.步骤描述){
+      res += (JSON.stringify(step).substring(1,JSON.stringify(step).indexOf(",")))
+      res += '\n'
+    }
+    return res;
+  }
+}
+
+function predictRes(row: any) {
+
+  if(row.hasOwnProperty('预期结果')){
+    return row.预期结果
+  }else{
+    let res = ''
+    for(const step of row.步骤描述){
+      res += (JSON.stringify(step).substring(JSON.stringify(step).indexOf(",")+1,JSON.stringify(step).length-1))
+      res += '\n'
+    }
+    return res;
+  }
+  // 解析预期结果的方法
+
+}
 
-const router = useRouter();
-const gotoLastPage = () => {
-  router.push('/testcaseList');
-};
 
 const exportTestCase = async () => {
+  const indexOfDot = useStore.DocxList[itemIndex.value].docxName.indexOf(".")
+  const wb = XLSX.utils.table_to_book(document.querySelector('#tableData'));//关联dom节点
+  /* get binary string as output */
+  const wbout = XLSX.write(wb, {
+    bookType: 'xlsx',
+    bookSST: true,
+    type: 'array'
+  })
+  try {
+    FileSaver.saveAs(new Blob([wbout], {
+      type: 'application/octet-stream'
+    }), useStore.DocxList[itemIndex.value].docxName.substring(0,indexOfDot+1)+'xlsx')//自定义文件名
+  } catch (e) {
+    if (typeof console !== 'undefined') console.log(e, wbout);
+  }
+  return wbout
+
 
-  // 导出 工作簿
-  // 创建一个新的工作簿
-   const wb = XLSX.utils.book_new();
-  // 将表格数据转换为工作表
-  const ws = XLSX.utils.json_to_sheet(tableData);
-  // 将工作表添加到工作簿中
-  XLSX.utils.book_append_sheet(wb, ws, 'Sheet1');
-  // 将工作簿写入一个数组
-  const wbout = XLSX.write(wb, { bookType: 'xlsx', type: 'array' });
-  // 创建一个blob对象,用于下载
-  const blob = new Blob([wbout], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8' });
-
-  // 创建一个链接元素并模拟点击进行下载
-  const link = document.createElement('a');
-        link.href = URL.createObjectURL(blob);
-        link.download = 'exported_data.xlsx';
-        document.body.appendChild(link);
-        link.click();
-        document.body.removeChild(link);
 }
 </script>
 
@@ -176,22 +202,25 @@ const exportTestCase = async () => {
     <el-container>
       <el-main>
         <el-card class="card">
-          <el-table :data="tableData" border height="500" style="width: 100%">
-            <el-table-column prop="编号" label="编号" width="120" />
-            <el-table-column prop="用例标题" label="用例标题" width="120" />
-            <el-table-column prop="前置条件" label="前置条件" width="300" />
-            <el-table-column prop="步骤描述" label="步骤描述" width="300" />
-            <el-table-column prop="预期结果" label="预期结果" width="300" />
-
-<!--            <el-table-column label="步骤描述">-->
+          <el-table :data="tableData" border height="500" style="width: 100%" id="tableData" >
+            <el-table-column prop="编号" label="编号" width="120" ></el-table-column>
+            <el-table-column prop="用例标题" label="用例标题" width="120"></el-table-column>
+            <el-table-column prop="前置条件" label="前置条件" width="120"></el-table-column>
+            <el-table-column  show-overflow-tooltip  prop="步骤描述" label="步骤描述" min-width="300" :formatter="descripRes" >
+<!--              <template #default="props">-->
+<!--                <div v-for="(step, index) in props.row.步骤描述" :key="index">-->
+<!--                  <p>{{ JSON.stringify(step).substring(1,JSON.stringify(step).indexOf(","))}}</p>-->
+<!--                </div>-->
+<!--              </template>-->
+            </el-table-column>
+            <el-table-column show-overflow-tooltip  prop="预期结果" label="预期结果" min-width="300" :formatter="predictRes" >
 <!--              <template #default="props">-->
-<!--                <el-table :data="props.row.步骤描述">-->
-<!--                  <el-table-column prop="步骤编号" label="步骤编号" />-->
-<!--                  <el-table-column prop="操作" label="操作" />-->
-<!--                  <el-table-column prop="预期结果" label="预期结果" />-->
-<!--                </el-table>-->
+<!--                <div v-for="(step, index) in props.row.步骤描述" :key="index">-->
+<!--                  <p>{{ JSON.stringify(step).substring(JSON.stringify(step).indexOf(",")+1,JSON.stringify(step).length-1) }}</p>-->
+<!--                </div>-->
 <!--              </template>-->
-<!--            </el-table-column>-->
+            </el-table-column>
+
 
           </el-table>
         </el-card>
@@ -210,11 +239,15 @@ const exportTestCase = async () => {
 
 .card {
   margin: 0;
-
   width: 100%;
+
 }
 
 
+::v-deep .el-table .cell {
+  white-space: pre-line;
+}
+
 .card-item {
   margin: 0;
   padding: 0;
@@ -224,6 +257,7 @@ const exportTestCase = async () => {
   text-align: center;
   background-color: #fcfcfc;
   cursor: pointer;
+  white-space: pre-line;
 }
 
 

+ 1 - 1
src/components/TCList.vue

@@ -296,7 +296,7 @@ function Showecharts() {
         data: [
           { value:useStore.DocxList.length, name: '.docx文件'},
           {value:5,name:'.excel文件'},
-          {value:6,name:'.ppd文件'}
+          {value:6,name:'.ppt文件'}
         ]
       }
     ]

+ 19 - 5
src/router/index.ts

@@ -1,18 +1,32 @@
 import { createRouter, createWebHistory } from "vue-router";
 import LayoutVue from '../components/Layout.vue'
+import LoginVue from '../components/Login.vue'
 import AppVue from '../App.vue'
+import RegisterVue from '../components/Register.vue'
 import UploadVue from '../components/Upload.vue'
 import TCListVue from '../components/TCList.vue'
 import TCFinalListVue from '../components/TCFinalList.vue'
 
 const router = createRouter({
     history: createWebHistory(import.meta.env.BASE_URL),
-    routes: [
+    routes: [{
+        path: '/',
+        redirect: '/login',
+        name: 'home',
+        component: AppVue,
+        children: []
+
+    },
+        {
+            path: '/register',
+            name: 'register',
+            component: RegisterVue,
+            children: []
+        },
         {
-            path: '/',
-            redirect: '/upload',
-            name: 'home',
-            component: AppVue,
+            path: '/login',
+            name: 'login',
+            component: LoginVue,
             children: []
         }, {
             path: '/upload',