1、用户管理新增和编辑用户时设置工单为必填项
2、新增终端登录、首页、设备点检、故障上报、上下班打卡、停机原因维护页面(未与后端联调且客户未确定页面设计)并调整全局路由守卫相关逻辑
3、调整设备结构树设备层级和车间层级区分的判断条件
4、调整电子样板检索与部件借用弹窗列表与搜索区域样式
已添加10个文件
已修改8个文件
1417 ■■■■■ 文件已修改
src/assets/operator-login-bg.png 补丁 | 查看 | 原始文档 | blame | 历史
src/components/layouts/TerminalLayout.vue 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/layouts/index.js 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/config/router.config.js 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/permission.js 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dnc/base/OperatorLogin.vue 268 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dnc/base/TerminalIndex.vue 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dnc/base/modules/DeviceStructure/DeviceStructureTree.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dnc/base/modules/ProductStructure/Document/NcComponentBorrowModal.vue 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dnc/base/modules/ProductStructure/Document/NcDocumentSearchModal.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dnc/base/modules/TerminalIndex/EquipmentSpotCheck.vue 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dnc/base/modules/TerminalIndex/EquipmentStartWork.vue 128 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dnc/base/modules/TerminalIndex/ReportEquipmentClose.vue 175 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dnc/base/modules/TerminalIndex/ReportEquipmentClose/MaintainShutdownModal.vue 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dnc/base/modules/TerminalIndex/ReportEquipmentClose/SplitShutdownInfoModal.vue 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dnc/base/modules/TerminalIndex/ReportEquipmentFault.vue 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/modules/UserModal.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/user/Login.vue 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/operator-login-bg.png
src/components/layouts/TerminalLayout.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,126 @@
<template>
  <div class="full-screen-container">
    <router-view class="router-view">
      <template v-slot:function>
        <a-space>
          <button class="button" @click="handleLogout">切换用户</button>
          <button class="button" @click="backToIndex">返回主页</button>
          <button class="button">设置</button>
        </a-space>
      </template>
    </router-view>
    <div class="footer" v-if="$route.path!=='/terminal/login'">
      <div>姓名:{{nickname()}}</div>
      <div>当前时间:{{currentDateAndTime}}</div>
    </div>
  </div>
</template>
<script>
  import { mapActions, mapGetters } from 'vuex'
  import moment from 'moment'
  export default {
    name: 'TerminalLayout',
    data() {
      return {
        currentDateAndTime: null,
        getDateAndTimeInterval: null
      }
    },
    watch: {
      '$route.path': {
        handler(val) {
          if (val === '/terminal/index' || val === '/terminal/login') document.title = 'MDC智慧车间'
        }
      }
    },
    created() {
      this.getCurrentDateAndTime()
    },
    beforeDestroy() {
      if (this.getDateAndTimeInterval) {
        clearInterval(this.getDateAndTimeInterval)
        this.getDateAndTimeInterval = null
      }
    },
    methods: {
      ...mapActions(['Logout']),
      ...mapGetters(['nickname']),
      handleLogout() {
        const that = this
        this.$confirm({
          title: '提示',
          content: '确定要切换用户吗 ?',
          onOk() {
            return that.Logout({}).then(() => {
              window.location.reload()
            }).catch(err => {
              that.$message.error({
                title: '错误',
                description: err.message
              })
            })
          },
          onCancel() {
          }
        })
      },
      backToIndex() {
        if (this.$route.path !== '/terminal/index') this.$router.push('/terminal/index')
      },
      // èŽ·å–å½“å‰æ—¥æœŸå’Œæ—¶é—´ï¼ˆ1秒更新1次)
      getCurrentDateAndTime() {
        this.getDateAndTimeInterval = setInterval(() => this.currentDateAndTime = moment().format('YYYY-MM-DD HH:mm:ss'), 1000)
      }
    }
  }
</script>
<style scoped lang="less">
  .full-screen-container {
    display: flex;
    flex-direction: column;
    height: 100vh;
    .router-view {
      flex: 1;
      padding: 24px;
      display: flex;
      flex-direction: column;
    }
  }
  .button {
    font-weight: bold;
    padding: 15px 15px;
    border: 1px solid rgba(0, 0, 0, .2);
    border-radius: 5px;
    cursor: pointer;
    box-shadow: 6px 6px 16px rgba(0, 0, 0, 0.2),
      -6px -6px 16px rgba(255, 255, 255, 0.8),
    inset 0 0 0 transparent;
    &:hover {
      box-shadow: 0 0 0 transparent,
      inset 6px 6px 12px rgba(0, 0, 0, 0.2),
        inset -6px -6px 12px rgba(255, 255, 255, 0.8);
    }
  }
  .footer {
    font-size: 16px;
    padding: 12px 24px;
    color: #000;
    display: flex;
    justify-content: space-between;
  }
</style>
src/components/layouts/index.js
@@ -4,5 +4,6 @@
import RouteView from '@/components/layouts/RouteView'
import PageView from '@/components/layouts/PageView'
import TabLayout from '@/components/layouts/TabLayout'
import TerminalLayout from '@/components/layouts/TerminalLayout'
export { UserLayout, BasicLayout, BlankLayout, RouteView, PageView, TabLayout }
export { UserLayout, BasicLayout, BlankLayout, RouteView, PageView, TabLayout, TerminalLayout }
src/config/router.config.js
@@ -1,4 +1,4 @@
import { UserLayout, TabLayout, RouteView, BlankLayout, PageView } from '@/components/layouts'
import { UserLayout, TabLayout, RouteView, BlankLayout, PageView, TerminalLayout } from '@/components/layouts'
/**
 * èµ°èœå•,走权限控制
@@ -49,7 +49,7 @@
        path: 'alteration',
        name: 'alteration',
        component: () => import(/* webpackChunkName: "user" */ '@/views/user/alteration/Alteration')
      },
      }
    ]
  },
  {
@@ -63,7 +63,7 @@
        path: 'login',
        name: 'oauth2-app-login',
        component: () => import(/* webpackChunkName: "oauth2-app.login" */ '@/views/user/oauth2/OAuth2Login')
      },
      }
    ]
  },
@@ -84,8 +84,45 @@
    component: () => import('@/views/mdc/base/MdcWorkshopSignage.vue')
  },
  {
    path: '/terminal',
    redirect: '/terminal/login',
    component: TerminalLayout,
    children: [
      {
        path: 'login',
        name: 'operatorLogin',
        component: () => import('@/views/dnc/base/OperatorLogin.vue')
      },
      {
        path: 'index',
        name: 'terminalIndex',
        component: () => import('@/views/dnc/base/TerminalIndex.vue')
      },
      {
        path: 'work',
        name: 'equipmentStartWork',
        component: () => import('@/views/dnc/base/modules/TerminalIndex/EquipmentStartWork.vue')
      },
      {
        path: 'fault',
        name: 'reportEquipmentFault',
        component: () => import('@/views/dnc/base/modules/TerminalIndex/ReportEquipmentFault.vue')
      },
      {
        path: 'close',
        name: 'reportEquipmentClose',
        component: () => import('@/views/dnc/base/modules/TerminalIndex/ReportEquipmentClose.vue')
      },
      {
        path: 'spotCheck',
        name: 'equipmentSpotCheck',
        component: () => import('@/views/dnc/base/modules/TerminalIndex/EquipmentSpotCheck.vue')
      }
    ]
  },
  {
    path: '/404',
    component: () => import(/* webpackChunkName: "fail" */ '@/views/exception/404')
  },
  }
]
src/permission.js
@@ -4,12 +4,12 @@
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import notification from 'ant-design-vue/es/notification'
import { ACCESS_TOKEN,INDEX_MAIN_PAGE_PATH, OAUTH2_LOGIN_PAGE_PATH } from '@/store/mutation-types'
import { ACCESS_TOKEN, INDEX_MAIN_PAGE_PATH, OAUTH2_LOGIN_PAGE_PATH } from '@/store/mutation-types'
import { generateIndexRouter, isOAuth2AppEnv } from '@/utils/util'
NProgress.configure({ showSpinner: false }) // NProgress Configuration
const whiteList = ['/user/login', '/user/register', '/user/register-result','/user/alteration'] // no redirect whitelist
const whiteList = ['/user/login', '/user/register', '/user/register-result', '/user/alteration', '/terminal/login'] // no redirect whitelist
whiteList.push(OAUTH2_LOGIN_PAGE_PATH)
router.beforeEach((to, from, next) => {
@@ -21,8 +21,8 @@
    to.matched.splice(2, to.matched.length - 3)
  }
  //update-end---author:scott ---date::2022-10-13  for:[jeecg-boot/issues/4091]多级路由缓存问题 #4091--------------
  NProgress.start() // start progress bar
  if (Vue.ls.get(ACCESS_TOKEN)) {
@@ -30,57 +30,75 @@
    if (to.path === '/user/login' || to.path === OAUTH2_LOGIN_PAGE_PATH) {
      next({ path: INDEX_MAIN_PAGE_PATH })
      NProgress.done()
    } else {
    }
    else {
      if (store.getters.permissionList.length === 0) {
        store.dispatch('GetPermissionList').then(res => {
              const menuData = res.result.menu;
              //console.log(res.message)
              if (menuData === null || menuData === "" || menuData === undefined) {
                return;
          const menuData = res.result.menu
          //console.log(res.message)
          if (menuData === null || menuData === '' || menuData === undefined) {
            return
          }
          let constRoutes = []
          constRoutes = generateIndexRouter(menuData)
          // æ·»åŠ ä¸»ç•Œé¢è·¯ç”±
          store.dispatch('UpdateAppRouter', { constRoutes }).then(() => {
            // æ ¹æ®roles权限生成可访问的路由表
            // åŠ¨æ€æ·»åŠ å¯è®¿é—®è·¯ç”±è¡¨
            router.addRoutes(store.getters.addRouters)
            const redirect = decodeURIComponent(from.query.redirect || to.path)
            if (to.path === redirect) {
              // hack方法 ç¡®ä¿addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
              next({ ...to, replace: true })
            } else {
              // è·³è½¬åˆ°ç›®çš„路由
              if (to.path !== '/terminal/login' && from.path !== '/terminal/login' && redirect.split('/')[1] === 'terminal') {
                next({ path: '/terminal/login' })
              } else {
                next({ path: redirect })
              }
              let constRoutes = [];
              constRoutes = generateIndexRouter(menuData);
              // æ·»åŠ ä¸»ç•Œé¢è·¯ç”±
              store.dispatch('UpdateAppRouter',  { constRoutes }).then(() => {
                // æ ¹æ®roles权限生成可访问的路由表
                // åŠ¨æ€æ·»åŠ å¯è®¿é—®è·¯ç”±è¡¨
                router.addRoutes(store.getters.addRouters)
                const redirect = decodeURIComponent(from.query.redirect || to.path)
                if (to.path === redirect) {
                  // hack方法 ç¡®ä¿addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
                  next({ ...to, replace: true })
                } else {
                  // è·³è½¬åˆ°ç›®çš„路由
                  next({ path: redirect })
                }
              })
            })
            }
          })
        })
          .catch(() => {
           /* notification.error({
              message: '系统提示',
              description: '请求用户信息失败,请重试!'
            })*/
            /* notification.error({
               message: '系统提示',
               description: '请求用户信息失败,请重试!'
             })*/
            store.dispatch('Logout').then(() => {
              next({ path: '/user/login', query: { redirect: to.fullPath } })
            })
          })
      } else {
        next()
      }
      else {
        // è·³è½¬åˆ°ç›®çš„路由
        if (to.path !== '/terminal/login' && from.path !== '/' && from.path.split('/') [1] !== 'terminal' && to.path.split('/')[1] === 'terminal') {
          next({ path: '/terminal/login' })
        } else {
          next()
        }
      }
    }
  } else {
  }
  else {
    if (whiteList.indexOf(to.path) !== -1) {
      // åœ¨å…ç™»å½•白名单,如果进入的页面是login页面并且当前是OAuth2app环境,就进入OAuth2登录页面
      if (to.path === '/user/login' && isOAuth2AppEnv()) {
        next({path: OAUTH2_LOGIN_PAGE_PATH})
        next({ path: OAUTH2_LOGIN_PAGE_PATH })
      } else {
        // åœ¨å…ç™»å½•白名单,直接进入
        next()
      }
      NProgress.done()
    } else {
    }
    else {
      // å¦‚果当前是在OAuth2APP环境,就跳转到OAuth2登录页面
      let path = isOAuth2AppEnv() ? OAUTH2_LOGIN_PAGE_PATH : '/user/login'
      let path
      if (isOAuth2AppEnv()) path = OAUTH2_LOGIN_PAGE_PATH
      else {
        if (to.path.split('/')[1] !== 'terminal') path = '/user/login'
        else path = '/terminal/login'
      }
      next({ path: path, query: { redirect: to.fullPath } })
      NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it
    }
src/views/dnc/base/OperatorLogin.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,268 @@
<template>
  <div class="login-bg">
    <div class="login-container">
      <a-card class="login-card">
        <h2 class="title">登录</h2>
        <a-form-model :model="model" ref="form" :rules="validateRules" @keyup.enter.native="handleLogin">
          <!-- åˆ·å¡ç™»å½•输入框 -->
          <a-form-model-item prop="workNo" :has-feedback="feedbackConfig.workNoFeedback">
            <a-input v-model="model.workNo" placeholder="请刷卡或输入卡号" size="large" autocomplete="off"
                     @change="clearAnotherLoginInfo('workNo')">
              <a-icon slot="prefix" type="credit-card"/>
            </a-input>
          </a-form-model-item>
          <div class="divider">
            <span class="line"></span>
            <span class="text">或</span>
            <span class="line"></span>
          </div>
          <!-- å¸¸è§„登录表单 -->
          <a-form-model-item prop="username" :has-feedback="feedbackConfig.usernameFeedback">
            <a-input
              v-model="model.username"
              placeholder="用户名"
              size="large"
              @change="clearAnotherLoginInfo('username')"
              autocomplete="off"
            >
              <a-icon slot="prefix" type="user"/>
            </a-input>
          </a-form-model-item>
          <a-form-model-item prop="password" :has-feedback="feedbackConfig.passwordFeedback">
            <a-input-password
              v-model="model.password"
              placeholder="密码"
              size="large"
              @change="clearAnotherLoginInfo('password')"
              autocomplete="off"
            >
              <a-icon slot="prefix" type="lock"/>
            </a-input-password>
          </a-form-model-item>
          <a-button type="primary" size="large" block :loading="loading" @click="handleLogin">登录</a-button>
        </a-form-model>
        <!--<div class="footer">-->
        <!--<a @click="handleRegister">注册账号</a>-->
        <!--<a @click="handleForget">忘记密码</a>-->
        <!--</div>-->
      </a-card>
    </div>
  </div>
</template>
<script>
  import { mapActions } from 'vuex'
  import { timeFix } from '@/utils/util'
  export default {
    name: 'OperatorLogin',
    data() {
      return {
        model: {
          isOperator: true // åˆ¤æ–­æ˜¯å“ªä¸ªç™»å½•页登录
        },
        feedbackConfig: {
          workNoFeedback: true,
          usernameFeedback: true,
          passwordFeedback: true
        },
        loading: false,
        validateRules: {
          workNo: [
            { validator: this.checkWorkNo, trigger: 'blur' }
          ],
          username: [
            { validator: this.checkUsername, trigger: 'blur' }
          ],
          password: [
            { validator: this.checkPassword, trigger: 'blur' }
          ]
        }
      }
    },
    methods: {
      ...mapActions(['Login']),
      handleLogin() {
        this.$refs.form.validate(valid => {
          if (valid) {
            this.loading = true
            if (this.model.workNo) this.$refs.form.clearValidate(['username', 'password'])
            else this.$refs.form.clearValidate('workNo')
            console.log('登录信息:', this.model)
            this.Login(this.model)
              .then((res) => {
                this.loginSuccess(res.result)
              })
              .catch((err) => {
                this.loginFailed(err, this.model.username)
              })
          } else {
            return false
          }
        })
      },
      loginSuccess() {
        this.$router.push({ path: '/terminal/index' }).catch(() => {
        })
        this.$notification.success({
          message: '欢迎',
          description: `${timeFix()},欢迎回来`
        })
        this.loading = false
      },
      //登录后台失败
      loginFailed(err, username) {
        let description = ((err.response || {}).data || {}).message || err.message || '请求出现错误,请稍后再试'
        this.$notification.error({
          message: '登录失败',
          description: description
        })
        this.loading = false
      },
      /**
       * è¾“入框值改变时触发
       * @param inputProp è¾“入框对应属性
       */
      clearAnotherLoginInfo(inputProp) {
        this.feedbackConfig[inputProp + 'Feedback'] = true
        if (inputProp === 'workNo') {
          delete this.model.username
          delete this.model.password
          this.$refs.form.clearValidate(['username', 'password'])
        } else {
          delete this.model.workNo
          this.$refs.form.clearValidate('workNo')
        }
      },
      checkWorkNo(rule, value, callback) {
        if (!this.model.username && !this.model.password) {
          if (!value) {
            callback(new Error('请刷卡或输入卡号!'))
            this.feedbackConfig.usernameFeedback = this.feedbackConfig.passwordFeedback = true
          } else {
            callback()
          }
        } else {
          this.feedbackConfig.workNoFeedback = false
          callback()
        }
      },
      checkUsername(rule, value, callback) {
        if (!this.model.workNo) {
          if (!value) {
            callback(new Error('请输入用户名!'))
            if (!this.model.password) this.feedbackConfig.workNoFeedback = true
          } else {
            callback()
          }
        } else {
          this.feedbackConfig.usernameFeedback = false
          callback()
        }
      },
      checkPassword(rule, value, callback) {
        if (!this.model.workNo) {
          if (!value) {
            callback(new Error('请输入密码!'))
            if (!this.model.username) this.feedbackConfig.workNoFeedback = true
          } else {
            callback()
          }
        } else {
          this.feedbackConfig.passwordFeedback = false
          callback()
        }
      }
      // handleRegister() {
      //   this.$router.push('/register')
      // },
      // handleForget() {
      //   this.$message.info('请联系管理员重置密码')
      // }
    }
  }
</script>
<style lang="less" scoped>
  .login-bg {
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
    background: linear-gradient(rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.7)),
    url('../../../assets/operator-login-bg.png') no-repeat center;
    background-size: cover;
    .login-container {
      width: 100%;
      max-width: 420px;
      padding: 0 20px;
      .login-card {
        border-radius: 8px;
        box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3);
        background: rgba(255, 255, 255, 0.95);
        .title {
          margin-bottom: 24px;
          text-align: center;
          color: #1890ff;
          font-size: 24px;
          font-weight: 500;
        }
        .divider {
          display: flex;
          align-items: center;
          margin: 20px 0;
          .line {
            flex: 1;
            height: 1px;
            background: rgba(0, 0, 0, 0.15);
          }
          .text {
            padding: 0 16px;
            color: rgba(0, 0, 0, 0.45);
          }
        }
        .footer {
          display: flex;
          justify-content: space-between;
          margin-top: 16px;
          a {
            color: #1890ff;
            cursor: pointer;
            &:hover {
              text-decoration: underline;
            }
          }
        }
      }
    }
  }
</style>
src/views/dnc/base/TerminalIndex.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,109 @@
<template>
  <div class="page-view-container">
    <slot name="function"/>
    <div class="header-container">现场情况上报平台</div>
    <div class="content-container">
      <a-row>
        <a-col :span="8">
          <div class="button" @click="navigateTo('work','上班')">上班</div>
        </a-col>
        <a-col :span="8">
          <div class="button">下班</div>
        </a-col>
        <a-col :span="8">
          <div class="button" @click="navigateTo('fault','设备故障')">设备故障</div>
        </a-col>
      </a-row>
      <a-row>
        <a-col :span="8">
          <div class="button" @click="navigateTo('close','停机上报')">停机上报</div>
        </a-col>
        <a-col :span="8">
          <div class="button" @click="navigateTo('spotCheck','点检')">点检</div>
        </a-col>
        <a-col :span="8">
          <div class="button">合格率</div>
        </a-col>
      </a-row>
      <a-row>
        <a-col :span="8">
          <div class="button">程序呼叫</div>
        </a-col>
        <a-col :span="8">
          <div class="button">开工完工</div>
        </a-col>
        <a-col :span="8">
          <div class="button">程序回传</div>
        </a-col>
      </a-row>
    </div>
  </div>
</template>
<script>
  export default {
    name: 'TerminalIndex',
    methods: {
      navigateTo(url, projectTitle) {
        this.$router.push('/terminal/' + url)
        document.title = 'MDC智慧车间-' + projectTitle
      }
    }
  }
</script>
<style scoped lang="less">
  .page-view-container {
    background: linear-gradient(to bottom, #fff -15%, #000 55%,);
    .header-container {
      font-size: 50px;
      color: #eee;
      text-align: center;
      height: 15%;
      display: flex;
      justify-content: center;
      align-items: flex-end;
      -webkit-align-items: flex-end;
    }
    .content-container {
      flex: 1;
      display: flex;
      flex-direction: column;
      padding: 3% 10%;
      .ant-row {
        flex: 1;
        .ant-col {
          height: 100%;
          display: flex;
          justify-content: center;
          align-items: center;
          .button {
            width: 250px;
            height: 125px;
            display: flex;
            justify-content: center;
            align-items: center;
            background-color: #E3F2D9;
            font-size: 45px;
            border: 2px solid #33579D;
            box-shadow: inset -5px 5px 12px rgba(0, 0, 0, 0.6);
            cursor: pointer;
            transition: all .1s ease-in-out;
            &:hover {
              transform: scale(1.05);
            }
          }
        }
        /*display: flex;*/
        /*flex-direction: column;*/
      }
    }
  }
</style>
src/views/dnc/base/modules/DeviceStructure/DeviceStructureTree.vue
@@ -242,7 +242,7 @@
     * @param treeNode
     */
    setTreeNodeIcon(treeNode) {
      if (!treeNode.equipmentId) {
      if (+treeNode.type === 1) {
        treeNode.slots = { icon: 'workshop' }
      } else {
        treeNode.slots = { icon: 'device' }
src/views/dnc/base/modules/ProductStructure/Document/NcComponentBorrowModal.vue
@@ -12,44 +12,43 @@
            <div class="table-page-search-wrapper">
              <a-form layout="inline" @keyup.enter.native="searchQuery">
                <a-row :gutter="24">
                  <a-col :md="7" :sm="7">
                  <a-col :md="5" :sm="5">
                    <a-form-item label="部件名称">
                      <a-input placeholder="请输入部件名称" v-model="queryParam.componentName" allow-clear></a-input>
                    </a-form-item>
                  </a-col>
                  <a-col :md="7" :sm="7">
                  <a-col :md="5" :sm="5">
                    <a-form-item label="部件代号">
                      <a-input placeholder="请输入部件代号" v-model="queryParam.componentCode" allow-clear></a-input>
                    </a-form-item>
                  </a-col>
                  <a-col :md="7" :sm="7">
                  <a-col :md="5" :sm="5">
                    <a-form-item label="部件型号">
                      <a-input placeholder="请输入部件型号" v-model="queryParam.componentModel" allow-clear></a-input>
                    </a-form-item>
                  </a-col>
                  <a-col :md="7" :sm="7">
                  <a-col :md="4" :sm="4">
                    <a-form-item label="规格">
                      <a-input placeholder="请输入规格" v-model="queryParam.componentScale" allow-clear></a-input>
                    </a-form-item>
                  </a-col>
                  <a-col :md="7" :sm="7">
                  <a-col :md="4" :sm="4">
                    <a-form-item label="材质">
                      <a-input placeholder="请输入材质" v-model="queryParam.structureType" allow-clear></a-input>
                    </a-form-item>
                  </a-col>
                  <a-col :md="4" :sm="4">
                    <a-space>
                      <a-button type="primary" @click="searchQuery" icon="search">查询</a-button>
                      <a-button type="primary" @click="searchReset" icon="reload">重置</a-button>
                    </a-space>
                  </a-col>
                </a-row>
              </a-form>
            </div>
            <!-- æ“ä½œæŒ‰é’®åŒºåŸŸ -->
            <div class="table-operator">
              <a-button type="primary" @click="searchQuery" icon="search">查询</a-button>
              <a-button type="primary" @click="searchReset" icon="reload">重置</a-button>
            </div>
            <a-table :columns="columns" :data-source="dataSource" bordered :pagination="ipagination" :loading="loading"
@@ -137,13 +136,13 @@
          title: '创建时间',
          dataIndex: 'createTime',
          align: 'center',
          width: 150,
          width: 100,
        },
        {
          title: '创建人',
          dataIndex: 'createBy_dictText',
          align: 'center',
          width: 100,
          width: 60,
        }
      ],
      searchValue: '',
@@ -305,6 +304,10 @@
<style scoped lang="less">
/deep/ .ant-modal {
  .ant-modal-body{
      padding: 0 24px 12px;
  }
  .tabs-container {
    display: flex;
    justify-content: space-between;
src/views/dnc/base/modules/ProductStructure/Document/NcDocumentSearchModal.vue
@@ -44,7 +44,7 @@
                    </a-form-item>
                  </a-col>
                  <a-col :md="4" :sm="4">
                  <a-col :md="2" :sm="2">
                    <a-button type="primary" @click="searchQuery" icon="search">查询</a-button>
                  </a-col>
                </a-row>
@@ -173,11 +173,11 @@
          ]
        },
        {
          title: '状  æ€',
          title: '状 æ€',
          dataIndex: 'docDispatchStatus_dictText',
          key: 'docDispatchStatus',
          align: 'center',
          width: 60,
          width: 80,
          filters: [
            { text: '编制', value: 1 },
            { text: '校对', value: 2 },
@@ -498,6 +498,10 @@
<style scoped lang="less">
/deep/ .ant-modal {
  .ant-modal-body{
    padding: 0 24px 12px;
  }
  .tabs-container {
    display: flex;
    justify-content: space-between;
src/views/dnc/base/modules/TerminalIndex/EquipmentSpotCheck.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,169 @@
<template>
  <div>
    <slot name="function"/>
    <div class="content-container">
      <!-- æŸ¥è¯¢åŒºåŸŸ -->
      <div class="table-page-search-wrapper" style="width: 100%">
        <a-form-model ref="form" :model="model" layout="inline" :rules="validateRules">
          <a-row :gutter="64" type="flex" justify="center">
            <a-col :span="5">
              <a-form-model-item label="设备" prop="equipmentId">
                <a-select placeholder="请选择设备" v-model="model.equipmentId">
                  <a-select-option v-for="item in equipmentList" :key="item.key">
                    {{item.label}}
                  </a-select-option>
                </a-select>
              </a-form-model-item>
            </a-col>
            <a-col :span="5">
              <a-form-model-item label="点检日期" prop="checkDate">
                <a-date-picker style="width: 100%" placeholder="请选择开始时间" v-model="model.checkDate"/>
              </a-form-model-item>
            </a-col>
          </a-row>
        </a-form-model>
      </div>
      <div class="check-content-container">
        <div v-for="item in checkList" :key="item.id">
          <div>{{item.content}}</div>
          <div>
            <a-radio-group v-model="item.status">
              <a-radio :value="1">正常</a-radio>
              <a-radio :value="2">异常</a-radio>
              <a-radio :value="3">已维修</a-radio>
            </a-radio-group>
          </div>
        </div>
      </div>
      <div class="button-container">
        <a-button @click="handleSubmit" icon="check" :loading="loading">保存</a-button>
      </div>
    </div>
  </div>
</template>
<script>
  export default {
    name: 'EquipmentSpotCheck',
    data() {
      return {
        model: {},
        validateRules: {
          equipmentId: [{ required: true, message: '请选择设备!', trigger: 'change' }],
          checkDate: [{ required: true, message: '请选择点检日期!', trigger: 'change' }]
        },
        equipmentList: [
          {
            key: '3140221',
            label: '3140221'
          },
          {
            key: '3121542',
            label: '3121542'
          },
          {
            key: '3150324',
            label: '3150324'
          }
        ],
        checkList: [
          {
            content: '检查设备周围棉纱、工具、零件、工位器具等是否按规定定置摆放',
            status: 1
          },
          {
            content: '检查设备PE线接地是否完好无破损',
            status: 1
          },
          {
            content: '检查设备各开关是否灵活,可靠',
            status: 1
          },
          {
            content: '检查油箱油面是否在刻度线上、油是否变质、过滤网是否堵塞、油气管路是否漏油漏气',
            status: 1
          },
          {
            content: '保持设备表面清洁,检查机器上有无油污与异物,若有须及时清理',
            status: 1
          },
          {
            content: '检查设备各类行程限位,联锁保护装置、防护罩及其他保护装置完好,可靠',
            status: 1
          },
          {
            content: '检查设备仪器仪表设备状态标示是否在有效期内',
            status: 1
          },
          {
            content: '检查设备启动后各处运行(转)是否有异响异装',
            status: 1
          }
        ],
        loading: false
      }
    },
    methods: {
      handleSubmit() {
        const that = this
        this.$refs.form.validate(valid => {
          if (valid) {
            that.loading = true
            setTimeout(() => {
              that.loading = false
            }, 2000)
          } else {
            return false
          }
        })
      }
    }
  }
</script>
<style scoped lang="less">
  .content-container {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    .check-content-container {
      display: flex;
      flex-direction: column;
      margin: 40px auto;
      width: 70%;
      > div {
        display: flex;
        justify-content: center;
        border-bottom: 1px dashed #bbb;
        margin-bottom: 20px;
        padding-bottom: 5px;
        > div:first-child {
          flex: 0.5;
          text-align: right;
          margin-right: 30px;
        }
        > div:last-child {
          flex: 0.5;
          /deep/ .ant-radio-wrapper {
            margin-right: 30px;
          }
        }
      }
    }
    .button-container {
      text-align: center;
    }
  }
</style>
src/views/dnc/base/modules/TerminalIndex/EquipmentStartWork.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,128 @@
<template>
  <div class="full-screen-container">
    <slot name="function"/>
    <a-tabs default-active-key="1">
      <a-tab-pane tab="打卡上下班" key="1" dataset="first">
        <a-space>
          <div>设备名称:</div>
          <a-select style="width: 250px">
          </a-select>
        </a-space>
        <div class="button">上班</div>
      </a-tab-pane>
      <a-tab-pane tab="当前设备状态" key="2">
        <a-table :dataSource="dataSource" :columns="columns" rowKey="id" bordered :pagination="false"/>
      </a-tab-pane>
    </a-tabs>
  </div>
</template>
<script>
  export default {
    name: 'EquipmentStartWork',
    data() {
      return {
        columns: [
          {
            title: '用户编号',
            align: 'center',
            dataIndex: 'userId'
          },
          {
            title: '用户名称',
            align: 'center',
            dataIndex: 'username'
          },
          {
            title: '设备编号',
            align: 'center',
            dataIndex: 'equipmentId'
          },
          {
            title: '上班打卡时间',
            align: 'center',
            dataIndex: 'startWorkTime'
          },
          {
            title: '下班打卡时间',
            align: 'center',
            dataIndex: 'finishWorkTime'
          }
        ],
        dataSource: [
          {
            id: 1,
            equipmentId: '3140132',
            userId: '140016',
            username: '李骞',
            startWorkTime: '2023/11/13 9:29',
            finishWorkTime: ''
          },
          {
            id: 2,
            equipmentId: '3140130',
            userId: '140016',
            username: '李骞',
            startWorkTime: '2023/11/13 9:29',
            finishWorkTime: ''
          }
        ],
        url: {
          list: ''
        }
      }
    },
    created() {
    }
  }
</script>
<style scoped lang="less">
  .full-screen-container {
    padding: 24px;
    display: flex;
    flex-direction: column;
    /deep/ .ant-tabs {
      flex: 1;
      display: flex;
      flex-direction: column;
      .ant-tabs-content {
        width: 100%;
        flex: 1;
        .ant-tabs-tabpane[dataset='first'] {
          display: flex;
          flex-direction: column;
          justify-content: center;
          align-items: center;
          .button {
            font-weight: bold;
            padding: 40px 80px;
            border: 1px solid rgba(0, 0, 0, .2);
            border-radius: 10px;
            margin-top: 200px;
            cursor: pointer;
            box-shadow: 6px 6px 16px rgba(0, 0, 0, 0.2),
              -6px -6px 16px rgba(255, 255, 255, 0.8),
            inset 0 0 0 transparent;
            &:hover {
              box-shadow: 0 0 0 transparent,
              inset 6px 6px 12px rgba(0, 0, 0, 0.2),
                inset -6px -6px 12px rgba(255, 255, 255, 0.8);
            }
          }
        }
      }
    }
  }
</style>
src/views/dnc/base/modules/TerminalIndex/ReportEquipmentClose.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,175 @@
<template>
  <div class="page-view-container">
    <slot name="function"/>
    <a-card :bordered="false">
      <!-- æŸ¥è¯¢åŒºåŸŸ -->
      <div class="table-page-search-wrapper">
        <a-form layout="inline">
          <a-row :gutter="24">
            <a-col :span="4">
              <a-form-item label="设备">
                <a-select placeholder="请选择设备" v-model="queryParam.equipmentId"></a-select>
              </a-form-item>
            </a-col>
            <a-col :span="4">
              <a-form-item label="开始时间">
                <a-date-picker style="width: 100%" show-time placeholder="请选择开始时间" v-model="queryParam.startTime"/>
              </a-form-item>
            </a-col>
            <a-col :span="4">
              <a-form-item label="结束时间">
                <a-date-picker style="width: 100%" show-time placeholder="请选择结束时间" v-model="queryParam.endTime"/>
              </a-form-item>
            </a-col>
            <a-col :span="4">
              <a-form-item label="停机原因">
                <a-select placeholder="请选择停机原因" v-model="queryParam.closeReason"></a-select>
              </a-form-item>
            </a-col>
            <a-col :span="4">
              <a-form-item label="停机时间">
                <a-date-picker style="width: 100%" placeholder="请选择停机时间" v-model="queryParam.closeTime"/>
              </a-form-item>
            </a-col>
            <a-col :span="4">
              <a-space>
                <a-button type="primary" @click="searchQuery" icon="search">查询</a-button>
                <a-button @click="searchReset" icon="reload">重置</a-button>
              </a-space>
            </a-col>
          </a-row>
        </a-form>
      </div>
      <!-- æ“ä½œæŒ‰é’®åŒºåŸŸ -->
      <div class="table-operator">
        <a-button type="primary" @click="handleMaintainShutdown">维护停机</a-button>
        <a-button type="primary" @click="handleSplitShutdownInfo">拆分停机信息</a-button>
      </div>
      <a-table :dataSource="dataSource" :columns="columns" rowKey="id" bordered :pagination="false"
               :rowSelection="{selectedRowKeys, onChange: onSelectChange}"/>
    </a-card>
    <maintain-shutdown-modal ref="maintainShutdownModal"/>
    <split-shutdown-info-modal ref="splitShutdownInfoModal"/>
  </div>
</template>
<script>
  import { JeecgListMixin } from '@/mixins/JeecgListMixin'
  import MaintainShutdownModal from './ReportEquipmentClose/MaintainShutdownModal'
  import SplitShutdownInfoModal from './ReportEquipmentClose/SplitShutdownInfoModal'
  export default {
    name: 'ReportEquipmentClose',
    components: { SplitShutdownInfoModal, MaintainShutdownModal },
    mixins: [JeecgListMixin],
    data() {
      return {
        columns: [
          {
            title: '设备编号',
            align: 'center',
            dataIndex: 'equipmentId',
            width: 150
          },
          {
            title: '设备名称',
            align: 'center',
            dataIndex: 'equipmentName'
          },
          {
            title: '停机编号',
            align: 'center',
            dataIndex: 'shutdownId'
          },
          {
            title: '停机类型',
            align: 'center',
            dataIndex: 'shutdownType'
          },
          {
            title: '停机时间',
            align: 'center',
            width: 150,
            dataIndex: 'shutdownDuration'
          },
          {
            title: '开始时间',
            align: 'center',
            width: 200,
            dataIndex: 'startTime'
          },
          {
            title: '结束时间',
            align: 'center',
            width: 200,
            dataIndex: 'endTime'
          },
          {
            title: '录入类型',
            align: 'center',
            width: 100,
            dataIndex: 'recordType'
          }
        ],
        dataSource: [
          {
            id: 1,
            equipmentId: '3140221',
            equipmentName: '数控机床',
            shutdownDuration: 360,
            startTime: '2025-05-20 02:21:49',
            endTime: '2525-05-20 08:21:59',
            recordType: '自动上报'
          },
          {
            id: 2,
            equipmentId: '3140221',
            equipmentName: '数控机床',
            shutdownDuration: 360,
            startTime: '2025-05-20 02:21:49',
            endTime: '2525-05-20 08:21:59',
            recordType: '自动上报'
          },
          {
            id: 3,
            equipmentId: '3140221',
            equipmentName: '数控机床',
            shutdownDuration: 360,
            startTime: '2025-05-20 02:21:49',
            endTime: '2525-05-20 08:21:59',
            recordType: '自动上报'
          }
        ],
        url: {
          list: ''
        }
      }
    },
    methods: {
      handleMaintainShutdown() {
        this.$refs.maintainShutdownModal.visible = true
        this.$refs.maintainShutdownModal.model = {}
      },
      handleSplitShutdownInfo() {
        this.$refs.splitShutdownInfoModal.visible = true
      }
    }
  }
</script>
<style scoped lang="less">
</style>
src/views/dnc/base/modules/TerminalIndex/ReportEquipmentClose/MaintainShutdownModal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,68 @@
<template>
  <a-modal :visible="visible" title="维护停机" @ok="handleSubmit" @cancel="handleCancel">
    <a-form-model ref="form" :model="model" :rules="validateRules" :labelCol="{span:5}" :wrapperCol="{span:18}">
      <a-form-model-item prop="closeReason" label="停机原因">
        <a-select v-model="model.closeReason" placeholder="请选择停机原因">
          <a-select-option v-for="item in closeReasonList" :key="item.id">
            {{item.label}}
          </a-select-option>
        </a-select>
      </a-form-model-item>
    </a-form-model>
  </a-modal>
</template>
<script>
  export default {
    name: 'MaintainShutdownModal',
    data() {
      return {
        visible: false,
        model: {},
        validateRules: {
          closeReason: [{ required: true, message: '请选择停机原因!' }]
        },
        closeReasonList: [
          {
            id: 1,
            label: '吃饭时间休息'
          },
          {
            id: 2,
            label: '工作时间休息'
          },
          {
            id: 3,
            label: '计划性停电'
          },
          {
            id: 4,
            label: '待料停机'
          },
          {
            id: 5,
            label: '首件调试'
          },
          {
            id: 6,
            label: '刀量具准备'
          }
        ]
      }
    },
    methods: {
      handleSubmit() {
      },
      handleCancel() {
        this.$refs.form.clearValidate()
        this.visible = false
      }
    }
  }
</script>
<style scoped>
</style>
src/views/dnc/base/modules/TerminalIndex/ReportEquipmentClose/SplitShutdownInfoModal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,121 @@
<template>
  <a-modal :visible="visible" :width="800" title="拆分停机信息" @ok="handleSubmit" @cancel="handleCancel">
    <a-form-model ref="form" :model="model" :rules="validateRules" :labelCol="{span:8}" :wrapperCol="{span:12}">
      <a-row>
        <a-col :span="10">
          <a-form-model-item prop="startTime" label="开始时间">
            <a-date-picker show-time value-format="YYYY-MM-DD HH:mm:ss" v-model="model.startTime"/>
          </a-form-model-item>
        </a-col>
      </a-row>
      <div v-for="item in splitList" :key="item.title">
        <a-divider orientation="left">{{item.title}}</a-divider>
        <a-row>
          <a-col :span="10">
            <a-form-model-item prop="endTime" label="结束日期">
              <a-date-picker show-time value-format="YYYY-MM-DD HH:mm:ss" v-model="item.splitParams.endTime"/>
            </a-form-model-item>
          </a-col>
          <a-col :span="10">
            <a-form-model-item prop="closeReason" label="停机原因">
              <a-select v-model="item.splitParams.closeReason" placeholder="请选择停机原因">
                <a-select-option v-for="item in closeReasonList" :key="item.id">
                  {{item.label}}
                </a-select-option>
              </a-select>
            </a-form-model-item>
          </a-col>
          <a-col :span="4">
            <a-form-model-item label="选择">
              <a-checkbox @change="handleCheckboxChange(item,$event)"/>
            </a-form-model-item>
          </a-col>
        </a-row>
      </div>
    </a-form-model>
  </a-modal>
</template>
<script>
  export default {
    name: 'SplitShutdownInfoModal',
    data() {
      return {
        visible: false,
        model: {},
        validateRules: {
          startTime: [{ required: true, message: '请选择开始时间!', trigger: 'change' }]
        },
        closeReasonList: [
          {
            id: 1,
            label: '吃饭时间休息'
          },
          {
            id: 2,
            label: '工作时间休息'
          },
          {
            id: 3,
            label: '计划性停电'
          },
          {
            id: 4,
            label: '待料停机'
          },
          {
            id: 5,
            label: '首件调试'
          },
          {
            id: 6,
            label: '刀量具准备'
          }
        ],
        splitList: [
          {
            title: '拆分一段',
            splitParams: {}
          },
          {
            title: '拆分二段',
            splitParams: {}
          },
          {
            title: '拆分三段',
            splitParams: {}
          }
        ]
      }
    },
    methods: {
      handleCheckboxChange(record, event) {
        console.log('record', record)
        record.splitParams.checked = event.target.checked
      },
      handleSubmit() {
        this.$refs.form.validate(valid => {
          if (valid) {
          } else {
            return false
          }
        })
      },
      handleCancel() {
        this.$refs.form.clearValidate()
        this.visible = false
      }
    }
  }
</script>
<style scoped>
</style>
src/views/dnc/base/modules/TerminalIndex/ReportEquipmentFault.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,66 @@
<template>
  <div>
    <slot name="function"/>
    <div class="content-container">
      <a-form-model ref="form" :model="model" :rules="validateRules" :labelCol="{span:10}" :wrapperCol="{span:6}">
        <a-form-model-item label="设备名称" prop="equipmentId">
          <a-select placeholder="请选择设备" v-model="model.equipmentId"></a-select>
        </a-form-model-item>
        <a-form-model-item label="故障原因" prop="faultReasonId">
          <a-select placeholder="请选择故障原因" v-model="model.faultReasonId"></a-select>
        </a-form-model-item>
        <a-form-model-item label="故障描述" prop="faultDescription">
          <a-textarea placeholder="请输入故障描述" v-model="model.faultDescription"/>
        </a-form-model-item>
        <div style="text-align: center">
          <a-space>
            <a-button @click="handleReportFault">故障上报</a-button>
            <a-button>故障解除</a-button>
          </a-space>
        </div>
      </a-form-model>
    </div>
  </div>
</template>
<script>
  export default {
    name: 'ReportEquipmentFault',
    data() {
      return {
        model: {},
        validateRules: {
          equipmentId: [{ required: true, message: '请选择设备!' }],
          faultReasonId: [{ required: true, message: '请选择故障原因!' }],
          faultDescription: [{ required: true, message: '请输入故障描述!' }]
        }
      }
    },
    methods: {
      handleReportFault() {
        this.$refs.form.validate(valid => {
          if (valid) {
          } else {
            return false
          }
        })
      }
    }
  }
</script>
<style scoped lang="less">
  .content-container {
    flex: 1;
    display: flex;
    justify-content: center;
    align-items: center;
    /deep/ .ant-form {
      width: 100%;
    }
  }
</style>
src/views/system/modules/UserModal.vue
@@ -380,6 +380,7 @@
      disableSubmit: false,
      dateFormat: 'YYYY-MM-DD',
      validatorRules: {
        workNo:[{ required: true, message: '请输入工号!' }],
        username: [{ required: true, message: '请输入用户账号!' },
          { validator: this.validateUsername }],
        password: [
src/views/user/Login.vue
@@ -163,9 +163,13 @@
        // this.$router.push({ path: "/isps/userAnnouncement" }).catch(() => {
        //   console.log('登录跳转首页出错,这个错误从哪里来的')
        // })
        this.$router.push({ path: '/dashboard/analysis' }).catch(() => {
          console.log('登录跳转首页出错,这个错误从哪里来的')
        })
        if(this.$route.query.redirect.split('/')[1] === 'terminal') return
        this.$notification.success({
          message: '欢迎',
          description: `${timeFix()},欢迎回来`
@@ -181,7 +185,6 @@
                duration: 60,
                icon: <a-icon type = 'exclamation-circle'style = 'color:red' / >,
            })
            }
          }
        })