陪诊医疗系统(三)

效果:

#src/api/index

import request from '../utils/request'
// 发送验证码
export const getCode =(data) =>{
  return request.post('/get/code',data)
}

// https:/v3pz.itndedu.com/v3pz/user/authentication
// 注册用户
export const userAuthentication =(data)=>{
  return request.post('/user/authentication',data)
}

// https:/v3pz.itndedu.com/v3pz/login
// 登录
export const login=(data)=>{
  return request.post('/login',data)
}

// 权限管理列表  这段****
// https:/v3pz.itndedu.com/v3pz/auth/admin
export const authAdmin = (params)=>{
  return request.get('/auth/admin',{params})
}

// 数型菜单,权限管理
// https:/v3pz.itndedu.com/v3pz/user/getmenu
export const userGetmenu=()=>{
  return request.get('/user/getmenu')
}

// 菜单权限的修改
// https:/v3pz.itndedu.com/v3pz/user/setmenu
export const userSetmenu=(data)=>{
      return request.post('/user/setmenu',data)
}

// 菜单权限列表
export const menuList=(params) =>{
  return request.get('/menu/list',{params})
}

// 权限下拉列表
// menu/selectlist
export const menuSelectlist=()=>{
  return request.get('menu/selectlist')
}

// 用户数据修改
export const updateUser=(data) =>{
  return request.post('/update/user', data)
}

// 用户菜单权限
// menu/permissions
export const menuPermissions=()=>{
  return request.get('/menu/permissions')
}

// 获取陪护师头像列表
// https:/v3pz.itndedu.com/v3pz/photo/list
export const photoList = ()=>{
  return request.get('/photo/list')
}

// 陪护师创建
// https:/v3pz.itndedu.com/v3pz/companion
export const companion=(data)=>{
  return request.post('/companion',data)
}

// 陪护师列表
// https:/v3pz.itndedu.com/v3pz/companion/list
export const companionList=(params)=>{
  return request.get('companion/list',{params})
}

// 陪护师删除
//  https:/v3pz.itndedu.com/v3pz/delete/companion
export const deleteCompanion=(data)=>{
  return request.post('/delete/companion',data)
}
#src/component/aside.vue

<script setup>
import {
  Document,
  Menu as IconMenu,
  Location,
  Setting,
} from '@element-plus/icons-vue'
import TreeMenu from '../components/treeMenu.vue'
import { useRouter } from 'vue-router'
import { reactive,computed } from 'vue'
import { useStore } from 'vuex'
const router=useRouter()
// 获取展开侧边栏中的属性(存在store中)
const store=useStore()
const isCollapse=computed(()=>store.state.menu.isCollapse)
// 拿控制台的数据,将menu传给父组件
// const menuData = reactive(router.options.routes[0].children)
const menuData=computed(()=>store.state.menu.routerList)
// 默认选中颜色
const active=computed(()=>store.state.menu.menuActive)
// console.log(router);
const handleOpen = () => {
}
const handleClose = () => {
}
</script>
<template>
      <el-menu
        :style="{width: !isCollapse ? '230px': '64px'}"
        active-text-color="#ffd04b"
        background-color="#545c64"
        class="aside-container"
        text-color="#fff"
        @open="handleOpen"
        @close="handleClose"
        :collapse="isCollapse"
        :default-active="active"
      >
      <p class="logo-lg">{{ isCollapse ? 'DIDI':'DIDI陪诊' }}</p>
      <TreeMenu :index="1" :menuData="menuData" />

      <!-- 组件化 -->
        <!-- <el-sub-menu index="1">
          <template #title>
            <el-icon><location /></el-icon>
            <span>Navigator One</span>
          </template>
          <el-menu-item-group title="Group One">
            <el-menu-item index="1-1">item one</el-menu-item>
            <el-menu-item index="1-2">item two</el-menu-item>
          </el-menu-item-group>
          <el-menu-item-group title="Group Two">
            <el-menu-item index="1-3">item three</el-menu-item>
          </el-menu-item-group>
          <el-sub-menu index="1-4">
            <template #title>item four</template>
            <el-menu-item index="1-4-1">item one</el-menu-item>
          </el-sub-menu>
        </el-sub-menu>
        <el-menu-item index="2">
          <el-icon><icon-menu /></el-icon>
          <span>Navigator Two</span>
        </el-menu-item>
        <el-menu-item index="3" disabled>
          <el-icon><document /></el-icon>
          <span>Navigator Three</span>
        </el-menu-item>
        <el-menu-item index="4">
          <el-icon><setting /></el-icon>
          <span>Navigator Four</span>
        </el-menu-item> -->
      </el-menu>
</template>


<style lang="less" scoped>
.aside-container{
  height: 100%;
  .logo-lg {
    font-size: 20px;
    text-align: center;
    height: 50px;
    line-height: 50px;
    color: #fff;

  }
}
</style>
#src/component/navHead.vue

<script setup>
import { useStore } from 'vuex'
import {computed} from 'vue'
import { useRoute,useRouter } from 'vue-router'
// 当前路由对象
const route=useRoute()
const router=useRouter()
// 拿到store实例
const store=useStore()
// 通过computed拿到数据
const selectMenu=computed(()=>store.state.menu.selectMenu)

const userInfo = JSON.parse(localStorage.getItem('pz_userInfo'))
// 点击关闭tag
const closeTab=(item,index)=>{
  store.commit('closeMenu',item)
  // 删除非当前页tag
  if(route.path !== item.path){
    return
  }
  const selectMenuData = selectMenu.value
  // 删除最后一项
  if(index === selectMenuData.length){
    // 如果tag只有一个元素
    if(!selectMenuData.length){
      router.push('/')
    }else{
      router.push({
        path:selectMenuData[index-1].path
      })
    }
  }else{//删除的是中间位置tag
    router.push({
      path:selectMenuData[index].path
    })
  }

}

const handleClick =(command)=>{
    if(command === "cancel"){
      localStorage.removeItem('pz_token')
      localStorage.removeItem('pz_userInfo')
      // 清除持久化信息v
      localStorage.removeItem('pz_v3pz')
      window.location.href=window.location.origin
    }
}
</script>

<template>
  <div class="header-container">
    <div class="header-left flex-box">
      <el-icon class="icon" size="20" @click="store.commit('collapsMenu')"><Fold/></el-icon>
    <!-- 顶部标签 -->
      <ul class="flex-box">
      <li
      v-for="(item,index) in selectMenu"
      :key="item.path"
      :class="{selected : route.path === item.path}"
      class="tab flex-box">
      <el-icon size="12"><component :is="item.icon"/></el-icon>
      <!-- 点击跳转 -->
       <router-link class="text flex-box" to="{path: item.path}">
        {{ item.name }}
       </router-link>
      <!-- 关闭按钮 -->
      <el-icon size="12" class="close" @click="closeTab(item,index)"><Close/></el-icon>
    </li>
    </ul>
    </div>
    <div class="header-right">
      <el-dropdown @command="handleClick">
        <div class="el-dropdown-link flex-box" >
          <el-avatar
            :src="userInfo.avatar"
          />
          <p class="user-name">{{ userInfo.name }}</p>
        </div>
        <template #dropdown>
          <el-dropdown-menu >
            <el-dropdown-item command="cancel">退出</el-dropdown-item>
          </el-dropdown-menu>
        </template>
  </el-dropdown>
    </div>
  </div>
</template>
<style lang="less" scoped>
.flex-box{
  display: flex;
  align-items: center;
  height: 100%;
}

.header-container{
  display: flex;
  justify-content: space-between;
  align-items: center;
  height: 100%;
  background-color: #fff;
  padding-right: 25px;
  .header-left{
    height: 100%;
    .icon {
      width: 45px;
      height: 18px;
    }
    .con:hover {
      background-color: #f5f5f5;
      cursor: pointer;
    }
    .tab {
      padding: 0 10px;
      height: 100%;
      .text {
        margin: 0 5px;
      }
      .close{
        visibility: hidden;
      }
      &.selected {
        a{
          color: #409eff;
        }
        i{
          color: #409eff;
        }
        background-color: #f5f5f5;
      }
    }
    .tab:hover{
        background-color: #f5f5f5;
        .close {
          visibility: inherit;
          cursor: pointer;
          color: #000;
        }
      }
  }
  .header-right{
    .user-name{
      margin-left: 10px;
    }
  }
  a {
    height: 100%;
    color: #333;
    font-size: 15px;
  }
}

</style>
#panelHead.vue

<script setup>
const props=defineProps({
  route:{
    type:Object
  }
})
</script>

<template>
  <div class="panel-heading">
  <div class="panel-lead">
    <div class="title">{{ props.route.meta.name }}</div>
    <p class="description">{{ props.route.meta.describe }}</p>
  </div>
</div>
</template>



<style lang="less" scoped>
.panel-heading {
    padding: 15px;
    background: #e8edf0;
    border-color: #e8edf0;
    position: relative;
    .panel-lead {
      font-size: 14px;
      .title {
        font-weight: bold;
        font-style: normal;
      }
      .description {
        margin-top: 5px;
      }
    }
  }
</style>
#treeMenu.vue

<script setup>
import { useRouter } from 'vue-router'
import {useStore} from'vuex'

const props=defineProps(['menuData','index'])
const router=useRouter()
const store=useStore()
// console.log(props,'props');
// 点击侧边栏跳转
const handleClick=(item,active) =>{
  // console.log(item);
  store.commit('addMenu',item.meta)
  store.commit('updateMenuActive',active)
  router.push(item.meta.path)
}
</script>

<template>
  <!-- <el-sub-menu index="1">
          <template #title>
            <el-icon><location /></el-icon>
            <span>Navigator One</span>
          </template>
          <el-menu-item-group title="Group One">
            <el-menu-item index="1-1">item one</el-menu-item>
            <el-menu-item index="1-2">item two</el-menu-item>
          </el-menu-item-group>
          <el-menu-item-group title="Group Two">
            <el-menu-item index="1-3">item three</el-menu-item>
          </el-menu-item-group>
          <el-sub-menu index="1-4">
            <template #title>item four</template>
            <el-menu-item index="1-4-1">item one</el-menu-item>
          </el-sub-menu>
        </el-sub-menu>
        <el-menu-item index="2">
          <el-icon><icon-menu /></el-icon>
          <span>Navigator Two</span>
        </el-menu-item>
        <el-menu-item index="3" disabled>
          <el-icon><document /></el-icon>
          <span>Navigator Three</span>
        </el-menu-item> -->
        <template v-for="(item,index) in props.menuData">
          <!-- 没有子菜单的获取数组 -->
          <el-menu-item 
          @click="handleClick(item,`${props.index}-${item.meta.id}`)"
          v-if="!item.children || item.children.length ==0"
          :index="`${props.index}-${item.meta.id}`" 
          :key="`${props.index}-${item.meta.id}`">
          <!-- 图标 -->
          <el-icon size="20">
            <!-- vue的动态组件语法 -->
             <!-- 结合数据动态渲染图标 -->
            <component :is="item.meta.icon"></component>
          </el-icon>
          <!-- 菜单内容 -->
          <span>{{ item.meta.name }}</span>
          </el-menu-item>

          <!-- 有子菜单的 -->
          <el-sub-menu v-else :index="`${props.index}-${item.meta.id}`">
            <template #title>
              <el-icon size="20">
                  <component :is="item.meta.icon"></component>
              </el-icon>
              <span>{{ item.meta.name }}</span>
            </template>
            <!-- 有子菜单下面需要渲染的 -->
             <!-- 递归 -->
              <tree-menu :index="`${props.index}-${item.meta.id}`"
              :menu-data="item.children" />
          </el-sub-menu>

        </template>
        <!-- <el-menu-item index="4">
          <el-icon><setting /></el-icon>
          <span>Navigator Four</span>
        </el-menu-item> -->
</template>


<style lang="less" scoped>

</style>
#router/index.vue
import Layout from '../views/Main.vue'
import Login from '../views/login/index.vue'
import { createRouter, createWebHistory } from 'vue-router'
import Dashboard from '../views/dashboard/index.vue'
import Admin from '../views/auth/admin/index.vue'
import Group from '../views/auth/group/index.vue'
import Staff from '../views/vppz/staff/index.vue'
import Order from '../views/vppz/order/index.vue'

const localData = localStorage.getItem('pz_v3pz')

const router = createRouter({
  history: createWebHistory(),
  routes: [
    { 
      path: '/',
      component: Layout,
      name: 'main',
      redirect:to =>{
        if(localData){
          // 有子菜单情况
          const child=JSON.parse(localData).menu.routerList[0].children
          if(child){
            return child[0].meta.path
          }else{
            return JSON.parse(localData).menu.routerList[0].meta.path
          }
        }else{
          return '/'
        }
      },
      children: [
        // {
        //   path: 'dashboard',
        //   meta: { id: '1', name: '控制台', icon: 'Platform', path: '/dashboard', describe: '用于展示当前系统中的统计数据、统计报表及重要实时数据' },
        //   component: Dashboard
        // },
        // {
        //   path: 'auth',
        //   meta: { id: '2' ,name: '权限管理', icon: 'Grid' },
        //   children: [
        //     {
        //       path: '',
        //       alias: ['admin'],
        //       meta: { id: '1', name: '账号管理', icon: 'Avatar', path: '/auth/admin', describe: '管理员可以进行编辑,权限修改后需要登出才会生效' },
        //       component: Admin
        //     },
        //     {
        //       path: 'group',
        //       meta: { id: '2', name: '菜单管理', icon: 'Menu', path: '/auth/group', describe: '菜单规则通常对应一个控制器的方法,同时菜单栏数据也从规则中获取' },
        //       component: Group
        //     }
        //   ]
        // },
        // {
        //   path: 'vppz',
        //   meta: { id: '3', name: 'DIDI陪诊', icon: 'BellFilled' },
        //   children: [
        //     {
        //       path: '',
        //       alias: ['staff'],
        //       meta: { id: '1', name: '陪护管理', icon: 'Checked', path: '/vppz/staff', describe: '陪护师可以进行创建和修改,设置对应生效状态控制C端选择' },
        //       component: Staff
        //     },
        //     {
        //       path: 'order',
        //       meta: { id: '2', name: '订单管理', icon: 'List', path: '/vppz/order', describe: 'C端下单后可以查看所有订单状态,已支付的订单可以完成陪护状态修改' },
        //       component: Order
        //     }
        //   ]
        // }
      ]
    },
    {
      path: '/login',
      component: Login
    },
  ],
  // strict: true, // applies to all routes
})

export default router
#store/index.js
import { createStore } from "vuex"
import menu from "./menu"
// 导入持久化配置
import createPersistedstate from 'vuex-persistedstate'

// 返回store实例,对外暴露
export default createStore({
  plugins:[new createPersistedstate({
    key:'pz_v3pz'
  })],
  modules:{
    menu
  }
})

 

#menu.js

// 先看看本地有没有数据,没有再用初始化数据
const localData = localStorage.getItem('pz_v3pz')
const state= localData ? localData.menu:{
  isCollapse:false,
  selectMenu:[],
  routerList:[],
  menuActive:'1-1'
}
const mutations={
  collapsMenu(state){
    state.isCollapse=!state.isCollapse
  },
  addMenu(state,payload){
    // 对数据进行去重
    if(state.selectMenu.findIndex(item=>item.path === payload.path) === -1){
      state.selectMenu.push(payload)
    }
  },
  closeMenu(state,payload){
    // 找到点击数据的索引
    const index = state.selectMenu.findIndex(val=>val.name === payload.name)
    // 通过索引删除数组指定元素
    state.selectMenu.splice(index,1)
  },
  // dynamicMenu(state,payload){
  //   console.log(payload,'payload')
  //   // 通过glob导入文件
  //   const modules = import.meta.glob('../views/**/**/*.vue')
  //   console.log(modules,'modules')
  //   function routerSet(router){
  //     router.forEach(route=>{
  //       // 判断没有子路由  拼接路由数据
  //       if(!route.children){
  //         const url=`../views${route.meta.path}/index.vue`
  //         // 拿到获取的vue组件
  //         route.component=modules[url]
  //       }else{
  //         routerSet(route.children)
  //       }
  //     })
  //   }
  //   routerSet(payload)
  //   // // 拿到完整的路由数据
  //   state.routerList=payload
  // }
  dynamicMenu(state, payload) {
    const modules = import.meta.glob('../views/**/**/*.vue')
    function routerSet(router) {
      router.forEach(route => {
        if (!route.children) {
          const url = `../views${route.meta.path}/index.vue`
          route.component = modules[url]
        } else {
          routerSet(route.children)
        }
      })
    }
    routerSet(payload)
    state.routerList = payload
  },
  updateMenuActive(state, payload){
      state.menuActive = payload
  }
}
export default {state,mutations}

 

#utils/request.js
// 对axios进行简单的二次封装
import axios from "axios"
import { ElMessage } from "element-plus"
const instance = axios.create({
  baseURL: 'https:/v3pz.itndedu.com/v3pz',
  timeout: 10000,
});

// 添加请求拦截器
instance.interceptors.request.use(function (config) {
  // 在发送请求之前做些什么
  const token=localStorage.getItem('pz_token') //请求头
  // 不需要添加token的api
  const whiteUrl=['get/code','user/authentication','login']
  if(token && !whiteUrl.includes(config.url)){
    config.headers['x-token']=token
  }

  return config;
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error);
});

// // 添加响应拦截器
instance.interceptors.response.use(function (response) {
  // 对接口异常的数据,给用户提示
  if(response.data.code === -1){
    ElMessage.warning(response.data.message)
  }
  if(response.data.code === -2){
    localStorage.removeItem('pz_token')
    localStorage.removeItem('pz_userInfo')
    // 清除持久化信息v
    localStorage.removeItem('pz_v3pz')
    window.location.href=window.location.origin
  }
  // 2xx 范围内的状态码都会触发该函数。
  // 对响应数据做点什么
  return response;
}, function (error) {
  // 超出 2xx 范围的状态码都会触发该函数。
  // 对响应错误做点什么
  return Promise.reject(error);
});

export default instance

 

#admin/index.vue
<script setup>
import { authAdmin,menuSelectlist, updateUser }from '../../../api/index'
import dayjs from 'dayjs'
import { ref, reactive, onMounted } from 'vue'

import { useRoute } from 'vue-router'
const route=useRoute()
console.log(route,'route')

const paginationData = reactive({
  pageNum: 1,
  pageSize: 10
}) 
const handleSizeChange=(val)=>{
  paginationData.pageSize=val
  getListData()
}
const handleCurrentChange=(val)=>{
  paginationData.pageNum=val
  getListData()
}
// 编辑表单
const formRef=ref()
const form = reactive({
  name:'',
  permissions_id:''
})

// 表单提交
const confirm= async(formEI)=>{
  if(!formEI) return
  await formEI.validate((valid,fields)=>{
    if(valid){
      const { name, permissions_id} = form
      updateUser({name,permissions_id}).then(({data})=>{
        if(data.code === 10000){
          dialogFormVisable.value=false
          getListData()
        }
      })
    }else{
      console.log('error submit!',fields)
    }
  })

}
const options=ref([])

// 弹窗
const dialogFormVisable = ref(false)
// 关闭弹窗
const beforeClose =()=>{
  dialogFormVisable.value=false

}
const tableData=reactive({
  list:[],
  total:0
})

onMounted(()=>{
 getListData()
  menuSelectlist().then(({data})=>{
    options.value=data.data

  })
})
// 请求列表的封装
const getListData = ()=>{
  authAdmin(paginationData).then(({data})=>{
    console.log(data,'authAdmin')
    const { list,total} = data.data
    list.forEach(item =>{
      item.create_time=dayjs(item.create_time).format('YYYY-MM-DD')
    })
    tableData.list=list
    tableData.total=total
    
  })
}

// 根据权限id匹配权限名称
const permissionName = (id) =>{
  const data = options.value.find(el=>el.id === id)
  return data ? data.name : '超级管理员'
}

const open = (rowData)=>{
    dialogFormVisable.value=true
    Object.assign(form,{mobile:rowData.mobile,name:rowData.name,permissions_id:rowData.permissions_id})
}

// 规则
const rules = reactive({
  name:[{required: true,trigger: 'blur', message: '请填写昵称'}],
  permissions_id:[{required: true,trigger: 'blur', message: '请选择菜单权限'}]
})
</script>
<template>
  <panel-head :route="route"></panel-head>

  <el-table :data="tableData.list" style="width: 100%;">
  <el-table-column prop="id" label="id"/>
  <el-table-column prop="name" label="昵称"/>
  <el-table-column prop="permissionName" label="所属组别">
    <template #default="scope">
          {{ permissionName(scope.row.permissions_id)}}
    </template>
  </el-table-column>

  <el-table-column prop="mobile" label="手机号"/>

  <el-table-column prop="active" label="状态">
    <template #default="scope">
      <el-tag :type="scope.row.active ? 'success' : 'danger'">{{ scope.row.active? '正常':'失效' }}</el-tag>
    </template>
  </el-table-column>

  <el-table-column label="创建时间">
    <template #default="scope">
      <div class="flex-box">
        <el-icon><Clock/></el-icon>
      <span style="margin-left: 10px">{{ scope.row.create_time }}</span>
      </div>
      <!-- <el-tag :type="scope.row.active ? 'success' : 'danger'">{{ scope.row.active? '正常':'失效' }}</el-tag> -->
    </template>
  </el-table-column>

  <el-table-column label="操作">
    <template #default="scope">
          <el-button type="primary" @click="open(scope.row)">编辑</el-button>
    </template>
  </el-table-column>
</el-table>

<!-- 分页 -->
<div class="pagination-info">
  <el-pagination
      v-model:current-page="paginationData.pageNum"
      :page-size="paginationData.pageSize"
      size="small"
      :background="false"
      layout="total, prev, pager, next"
      :total="tableData.total"
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
    />
</div>

<!-- 弹窗 -->
<el-dialog
  v-model="dialogFormVisable"
  :before-close="beforeClose"
  title="添加权限"
  width="500">
  <!-- 表单 -->
  <el-form
    ref="formRef"
    label-width="100px"
    label-position="left"
    :model="form"
    :rules="rules">

    <el-form-item label="手机号" prop="mobile">
      <el-input v-model="form.mobile" disabled/>
    </el-form-item>
    <el-form-item label="昵称" prop="name">
      <el-input v-model="form.name"/>
    </el-form-item>

    <el-form-item label="菜单权限" prop="permissions_id">
      <el-select 
      v-model="form.permissions_id"
      placeholder="请选择菜单权限"
      style="width: 240px">
            <el-option
                v-for="item in options"
                :key="item.id"
                :label="item.name"
                :value="item.id" />
    </el-select>
    </el-form-item>
    </el-form>

    <template #footer>
      <div class="dialog-footer">
        <el-button type="primary" @click="confirm(formRef)">
          确认
        </el-button>
      </div>
    </template>
  </el-dialog>
</template>
<style lang="less" scoped>
.flex-box {
  display: flex;
  align-items: center;
}
</style>

 

#group/index.vue

<script setup>
import { nextTick, onMounted, reactive, ref } from 'vue'
import { userGetmenu,userSetmenu,menuList } from '../../../api/index.js'
import { Plus } from '@element-plus/icons-vue'

import { useRoute } from 'vue-router'
const route=useRoute()
onMounted(()=>{
  // 菜单数据
  userGetmenu().then(({data})=>{
    // console.log(data)
    permissionData.value=data.data
  })
  getListData()
})

// 列表数据
const tabelData = reactive({
  list:[],
  total:0
})
// 打开编辑弹窗
const open = (rowData={})=>{
    dialogFormVisable.value=true
    // 弹窗打开form是异步的
    nextTick(()=>{
      if(rowData){
        Object.assign(form,{id:rowData.id,name:rowData.name})
        treeRef.value.setCheckedKeys(rowData.permission)
      }
    })
}

const paginationData = reactive({
  pageNum: 1,
  pageSize: 10
}) 
const handleSizeChange=(val)=>{
  paginationData.pageSize=val
  getListData()
}
const handleCurrentChange=(val)=>{
  paginationData.pageNum=val
  getListData()
}

// 请求列表数据
const getListData =() =>{
  menuList(paginationData).then(({data})=>{
    // console.log(data,'data')
    const{list,total}=data.data
    tabelData.list=list
    tabelData.total=total

  })
}
const formRef=ref()
// form表单数据
const form = reactive({
  id:'',
  name:'',
  permissions:''
})
// 树型菜单权限数据
const permissionData = ref([])
// 弹窗的显示与隐藏
const dialogFormVisable= ref(false)

// 关闭弹窗的回调
const beforeClose=()=>{
  dialogFormVisable.value=false
  // 表单重置
  formRef.value.resetFields()
  // tree选中重置
  treeRef.value.setCheckedKeys(defaultKeys)
}
const treeRef=ref()

const rules= reactive({
  name:[{required:true, trigger:'blur',message:'请输入权限名称'}]
})

// 选中权限
const defaultKeys=[4,5]
// 表单提交
const confirm = async(formEI) =>{
  if(!formEI) return
  await formEI.validate((valid,fields)=>{
    if(valid){
// 获取到选择的checkbox数据
      const permissions=JSON.stringify(treeRef.value.getCheckedKeys())
      userSetmenu({name:form.name,permissions,id:form.id}).then(({data})=>{
        console.log(data)
        // 新增
        beforeClose()
        // 编辑
        getListData()
      })
      
    }else{
      console.log('error submit!',fields)
    }
  })
}
</script>
<template>
  <panel-head :route="route"/>
 <div class="btns">
  <el-button :icon="Plus" type="primary" @click="open(null)" size="small">新增</el-button>
 </div>
<!-- 表单数据 -->
<el-table :data="tabelData.list" style="width: 100%;">
  <el-table-column prop="id" label="id"/>
  <el-table-column prop="name" label="昵称"/>
  <el-table-column prop="permissionName" label="菜单权限" width="500px"/>
  <el-table-column label="操作">
    <template #default="scope">
          <el-button type="primary" @click="open(scope.row)">编辑</el-button>
    </template>
  </el-table-column>
</el-table>

<!-- 分页 -->
<div class="pagination-info">
  <el-pagination
      v-model:current-page="paginationData.pageNum"
      :page-size="paginationData.pageSize"
      size="small"
      :background="false"
      layout="total, prev, pager, next"
      :total="tabelData.total"
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
    />
</div>
  <!-- 弹窗 -->
  <el-dialog
  v-model="dialogFormVisable"
  :before-close="beforeClose"
  title="添加权限"
  width="500">
  <!-- form表单 -->
  <el-form
    ref="formRef"
    label-width="100px"
    label-position="left"
    :model="form"
    :rules="rules">

    <!-- 隐藏层 -->
     <el-form-item v-show="false" prop="id">
      <el-input v-model="form.id"></el-input>
     </el-form-item>

    <el-form-item label="名称" prop="name">
      <el-input v-model="form.name" placeholder="请填写权限名称"></el-input>
    </el-form-item>
    <el-form-item label="权限" prop="permissions">
      <el-tree
      ref="treeRef"
      style="max-width: 600px"
      :data="permissionData"
      node-key="id"
      show-checkbox
      :default-checked-keys="defaultKeys"
      :default-expanded-keys="[2]"
      />
    </el-form-item>
  </el-form>
  <template #footer>
      <div class="dialog-footer">
        <el-button type="primary" @click="confirm(formRef)">
          确认
        </el-button>
      </div>
    </template>
  </el-dialog>
</template>

<style lang="less" scoped>
.btns{
  padding: 10px 0 10px 10px;
  background-color: #fff;
}
</style>

 

 

#login/index.vue

<script setup>
// import { UserFilled } from '@element-plus/icons-vue/dist/types';
import { ElMessage } from 'element-plus';
import { computed, reactive, ref, toRaw } from 'vue'
import { getCode, userAuthentication, login,menuPermissions } from '../../api/index'
import { UserFilled, Lock} from '@element-plus/icons-vue'
import { useRouter } from 'vue-router'

import { useStore } from 'vuex'
const imgUrl = new URL('../../../public/login-head.png', import.meta.url).href
const formType=ref(0) //切换表单(0:登录,1:注册)

const store = useStore()
// 拿到的路由数据
const routerList = computed(()=>store.state.menu.routerList)

const handleChange=()=>{
  formType.value=formType.value ? 0:1
}

// 表单数据
const loginForm = reactive({
  userName:'',
  passWord:'',
  validCode:''
})
// 发送短信
const countdown = reactive({
  validText:'获取验证码',
  time:60
})
let flag=false
// 倒计时技术
const countdownChange =()=>{
  // 如果已发送,不做处理
  if(flag) return 
  // 判断手机号是否正确
  const phoneReg=/^1(3[0-9]|4[01456879]|5[0-35-9]|6[2567]|7[0-8]|8[0-9]|9[0-35-9])\d{8}$/
  if(!loginForm.userName || !phoneReg.test(loginForm.userName)){
    return ElMessage({
      message:'请检查手机号受否正确',
      type:'warning'
    })
  }
  // 倒计时
  const timer=setInterval(()=>{
    if(countdown.time<=0){
      countdown.time = 60
      countdown.validText='获取验证码'
      flag=false
      clearInterval(timer)
    }else{
      countdown.time -= 1
      countdown.validText=`剩余${countdown.time}s`
    }
  },1000)

  flag = true
    // 获取验证码,与接口连桥
     getCode({tel:loginForm.userName}).then((data)=>{
          // console.log(data,'data')
          if(data.code === 10000){
            ElMessage.success('发送成功')
          }
     })
    
}

// 账号校验
const validateUser =(rule, value, callback)=>{
      // 不能为空
      if(value === ''){
        callback(new Error('请输入账号'))
      }else{
        const phoneReg=/^1(3[0-9]|4[01456879]|5[0-35-9]|6[2567]|7[0-8]|8[0-9]|9[0-35-9])\d{8}$/
        phoneReg.test(value) ? callback():callback(new Error('手机格式合适不对'))
      }
}
// 密码校验
const validatePass = (rule, value, callback)=>{
  if(value === ''){
        callback(new Error('请输入密码'))
      }else{
        const passReg=/^[a-zA-Z0-9_-]{4,16}$/
        passReg.test(value) ? callback():callback(new Error('密码为4-16位字符'))
      }
}
// 表单校验
const rules=reactive({
  userName:[{validator: validateUser, trigger: 'blur' }],
  passWord:[{validator: validatePass, trigger: 'blur' }],
})

// 提交
const router=useRouter()

const loginFormRef=ref()
// const submitForm =async(formEl)=>{
//   if (!formEl) return
//   // 手动触发校验
//   await formEl.validate((valid,fields) => {
//     if (valid) {
//       // console.log('submit!')
//       // 注册页面
//       if(formType.value){
//         userAuthentication(loginForm).then(({data})=>{
//           if(data.code === 10000){
//             ElMessage.success('注册成功,请登录')
//             formType.value=0
//           }
//         })
//       }else{ 
//         // 登录页面
//         login(loginForm).then(({data})=>{
//           if(data.code === 10000){
//             ElMessage.success('登录成功') 
//             console.log(data)
//             // 将token和用户信息缓存到浏览器
//             localStorage.setItem('pz_token',data.data.token)
//             localStorage.setItem('pz_userInfo',JSON.stringify(data.data.userInfo))
//             // localStorage无法传引用数据类型,因此用stringify转成字符串
//             // 拿到菜单权限
//             menuPermissions().then(({data})=>{
//                 store.commit('dynamicMenu',data.data)
//                 console.log(routerList,'routerList')
//                 // toRaw将响应式数据===》普通路由数据
//                 toRaw(routerList.value).forEach(item =>{
//                   router.addRoute('main'.item)
//                 })
//                 router.push('/')
//             })
//             // router.push('/')
//           }
//         })

//       }
//     } else {
//       console.log('error submit!',fields)
//     }
//   })
// }

const submitForm = async (formEl) => {
  if (!formEl) return
  await formEl.validate((valid, fields) => {
    if (valid) {
      if (formType.value) {
        userAuthentication(loginForm).then(({ data }) => {
          if (data.code === 10000) {
            ElMessage.success('注册成功,请登录')
            formType.value = 0
          }
        })
      } else {
        login(loginForm).then(({ data }) => {
          if (data.code === 10000) {
            ElMessage.success('登录成功')
            localStorage.setItem('pz_token', data.data.token)
            localStorage.setItem('pz_userInfo', JSON.stringify(data.data.userInfo))
            menuPermissions().then(({ data }) => {
              store.commit('dynamicMenu', data.data)
              toRaw(routerList.value).forEach(item => {
                router.addRoute('main', item)
              })
              router.push('/')
            })
          }
        })
      }
    } else {
      console.log('error submit!', fields)
    }
  })
}



</script>
<template>
  <el-row class="login-container" justify="center" :align="'middle'">
    <el-card style="max-width: 480px">
      <template #header>
        <div class="card-header">
          <!-- 静态资源的引入 -->
          <img :src="imgUrl" alt="">
        </div>
      </template>
      <div class="jump-link">
        <el-link type="primary" @click="handleChange">{{ formType ? '返回登录':'注册账号' }}</el-link>
      </div>
      <!-- 表单 -->
       <el-form 
       ref="loginFormRef"
       :rules="rules"
       :model="loginForm" 
       style="max-width: 600px"
       class="demo-ruleForm">
        <el-form-item prop="userName">
          <el-input v-model="loginForm.userName" placeholder="手机号" :prefix-icon="UserFilled"></el-input>
        </el-form-item>
        <el-form-item prop="passWord">
          <el-input v-model="loginForm.passWord" type="password" placeholder="密码" :prefix-icon="Lock"></el-input>
        </el-form-item>
        <el-form-item prop="validCode" v-if="formType">
          <el-input v-model="loginForm.validCode" placeholder="验证码" :prefix-icon="Lock">
            <template #append>
            <span @click="countdownChange">{{ countdown.validText }}</span>
          </template>
          </el-input>
         
        </el-form-item>

        <!-- 登录按钮 -->
         <el-form-item>
          <el-button type="primary" :style="{width:'100%'}" @click="submitForm(loginFormRef)">
            {{ formType? '注册账号':'登录' }}
          </el-button>
         </el-form-item>


       </el-form>
    </el-card>
  </el-row>
</template>
<style lang="less" scoped>
:deep(.el-card__header) {
    padding: 0
  }
  .login-container {
    height: 100%;
    .card-header{
      background-color: #899fe1;
      img {
        width: 430px;
      }
    }
    .jump-link {
      text-align: right;
      margin-bottom: 10px;
    }
  }
</style>

 

 

#staff.vue

<script setup>
import { nextTick, onMounted, reactive, ref } from 'vue'
import { Plus, InfoFilled, Delete } from '@element-plus/icons-vue'
import { photoList, companion, companionList, deleteCompanion } from '../../../api/index'
import { ElMessage } from 'element-plus'
import { useRoute } from 'vue-router'
const route = useRoute()

onMounted(()=>{
  photoList().then(({data})=>{ 
    fileList.value=data.data
  })
  getListData()
})


const paginationData = reactive({
  pageNum: 1,
  pageSize: 10
}) 
// 列表数据
const tableData = reactive({
  list:[],
  total:0
})
const getListData=()=>{
  companionList(paginationData).then(({data})=>{
    const {list, total}=data.data
    tableData.list=list
    tableData.total=total
  })
}

const dialogTableVisible = ref(false)
const beforeClose = ()=>{
  dialogTableVisible.value=false
  formRef.value.resetFields()
}
const formRef = ref()
const form = reactive({
  id:'',
  mobile:'',
  active: 1,
  age:28,
  avatar:'',
  name:'',
  sex:''
})
const rules=reactive({
  name:[{required: true,trigger: 'blur', message: '请填写昵称'}],
  avatar:[{required: true,message: '请选择头像'}],
  sex:[{required: true,trigger: 'change', message: '请选择性别'}],
  mobile:[{required: true,trigger: 'blur', message: '请填写手机号'}]
})

// 表单提交
const confirm = async(formEI) =>{
  if(!formEI) return
  await formEI.validate((valid,fields)=>{
    if(valid){
      companion(form).then(({data})=>{
          if(data.code === 10000){
            ElMessage.success('成功')
            beforeClose()
            getListData()
          }else{
            ElMessage.error(data.message)
          }
      })
    }else{
      console.log('error submit!',fields)
    }
  })
}
const open=(rowData={})=>{
  dialogTableVisible.value=true
  // 异步操作
  nextTick(()=>{
    // 如果是编辑
    if(rowData){
      Object.assign(form,rowData)
    }
  })
}

// 选择图片弹窗
const dialogImageVisible = ref(false)
const fileList = ref([])
const selectIndex = ref(0)
// 确认图片上传
const confirmImage=()=>{
  form.avatar=fileList.value[selectIndex.value].url
  dialogImageVisible.value=false
}

const handleSizeChange=(val)=>{
  paginationData.pageSize=val
  getListData()
}
const handleCurrentChange=(val)=>{
  paginationData.pageNum=val
  getListData()
}

// 删除功能
const selectTableData = ref([])
// 批量勾选
const handleSelectionChange=(val)=>{
  selectTableData.value =val.map(item=>({id:item.id}))
}

const confirmEvent = ()=>{
  // 没有选择数据
  if(!selectTableData.value.length){
    return ElMessage.warning('请选择至少一项数据')
  }
  deleteCompanion({id: selectTableData.value}).then(({data})=>{
    if(data.code === 10000){
      getListData()
    }
  })
  
  
}

</script>

<template>
  <panel-head :route="route"/>
  <div class="btns">
  <el-button :icon="Plus" type="primary" @click="open(null)" size="small">新增</el-button>
  <el-popconfirm
    confirm-button-text="是"
    cancel-button-text="否"
    :icon="InfoFilled"
    icon-color="#626AEF"
    title="是否确认删除?"
    @confirm="confirmEvent"
  >
    <template #reference>
      <el-button :icon="Delete" type="danger" size="small">删除</el-button>
    </template>
  </el-popconfirm>
 </div>
        <!-- 回显数据表格 -->

  <el-table :data="tableData.list" style="width: 100%;" @selection-change="handleSelectionChange">
<!-- 批量勾选 -->
    <el-table-column type="selection" width="55" />

    <el-table-column prop="id" label="id"/>
      <el-table-column prop="name" label="昵称"/>
      <el-table-column label="头像">
        <template #default="scope">
              <el-image
                style="width: 50px; height: 50px;"
                :src="scope.row.avatar"
              />
        </template>
      </el-table-column>
      <el-table-column label="性别" prop="sex">
        <template #default="scope">
              {{ scope.row.sex=== '1' ? '男':'女' }}
        </template>
      </el-table-column>
      <el-table-column prop="mobile" label="手机号"/>
      <el-table-column label="状态" prop="active">
        <template #default="scope">
              <el-tag :type="scope.row.active ? 'success':'danger'">{{scope.row.active ? '正常':'失效'}}</el-tag>
        </template>
      </el-table-column>
      <el-table-column label="创建时间" >
        <template #default="scope">
            <div class="flex-box">
              <el-icon><Clock /></el-icon>
              <span style="margin-left: 10px">{{ scope.row.create_time }}</span>
            </div>
        </template>
      </el-table-column>

      <el-table-column label="操作">
        <template #default="scope">
              <el-tag :type="primary" @click="open(scope.row)">编辑</el-tag>
        </template>
      </el-table-column>


</el-table>

<!-- 分页 -->
<div class="pagination-info">
  <el-pagination
      v-model:current-page="paginationData.pageNum"
      :page-size="paginationData.pageSize"
      size="small"
      :background="false"
      layout="total, prev, pager, next"
      :total="tableData.total"
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
    />
</div>
  <el-dialog
  v-model="dialogTableVisible"
  :before-close="beforeClose"
  title="陪护师添加"
  width="500"
  >
  <!-- 表单 -->
    <el-form
    ref="formRef"
    label-width="100px"
    label-position="left"
    :model="form"
    :rules="rules"
    >
      <el-form-item v-show="false" prop="id">
        <el-input v-model="form.id"/>
      </el-form-item>
      <el-form-item label="昵称" prop="name">
        <el-input v-model="form.name" placeholder="请输入昵称"/>
      </el-form-item>

      <el-form-item label="头像" prop="avatar">
        <el-button v-if="!form.avatar" type="primary" @click="dialogImageVisible=true">点击上传</el-button>
        <el-image
        v-else 
        :src="form.avatar"
        style="width: 100px;height: 100px"
        />
      </el-form-item>

      <el-form-item label="性别" prop="sex">
         <el-select v-model="form.sex" placeholder="请选择性别">
            <el-option label="男" value="1"></el-option>
            <el-option label="女" value="2"></el-option>
         </el-select>
      </el-form-item>

      <el-form-item label="年龄" prop="age">
        <el-input-number v-model="form.age" :min="18" :max="50" />
      </el-form-item>

      <el-form-item label="手机号" prop="mobile">
        <el-input v-model="form.mobile" placeholder="请输入手机号"/>
      </el-form-item>

      <el-form-item label="是否生效" prop="active">
        <el-radio-group v-model="form.active">
          <el-radio :value="0">失效</el-radio>
          <el-radio :value="1">生效</el-radio>
        </el-radio-group>
      </el-form-item>
    </el-form>

    <template #footer>
      <div class="dialog-footer">
        <el-button type="primary" @click="confirm(formRef)">确认</el-button>
      </div>
    </template>

  </el-dialog>

  <!-- 图片上传 -->
  <el-dialog
    v-model="dialogImageVisible"
    :before-close="beforeClose"
    title="选择图片"
    width="680"
  >
    <div class="image-list">
      <div v-for="(item,index) in fileList" :key="item.name" class="img-box" @click="selectIndex = index">
          <div v-if="selectIndex === index" class="select">
            <el-icon color="#fff"><Check /></el-icon>
          </div>
          <el-image 
            style="width: 148px; height: 148px"
            :src="item.url"
          />
      </div>
    </div>
    <template #footer>
      <div class="dialog-footer">
        <el-button  @click="dialogImageVisible">取消</el-button>
        <el-button type="primary" @click="confirmImage">确认</el-button>
      </div>
    </template>
  </el-dialog>
</template>

<style lang="less" scoped>
.btns {
    padding: 10px 0 10px 10px;
    background-color: #fff;
}
.image-list {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  .img-box {
    position: relative;
    .select {
      position: absolute;
      left: 0px;
      top: 0px;
      width: 24px;
      height: 24px;
      background-color: #67c23a;
      z-index: 999;
      display: flex;
      justify-content: center;
      align-items: center;
    }
  }
  .el-image {
    margin-right: 10px;
    margin-bottom: 10px;
  }
}
</style>

 

#Main.vue
<script setup>
import Aside from '../components/aside.vue'
import Header from '../components/navHeader.vue'

</script>
<template>
    <div class="common-layout">
    <el-container>
      <!-- <el-aside width="200px">Aside</el-aside> -->
       <Aside></Aside>

     
      <el-container>

         <!-- 右侧 -->
        <el-header>
          <!-- 组件化 -->
           <Header></Header>
        </el-header>
        <el-main>
          <router-view/>
        </el-main>
      </el-container>
    </el-container>
  </div>
</template>
<style lang="less" scoped>
.common-layout {
  height: 100%;
  .el-container{
    height: 100%;
  }
}
</style>
#main.js
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
// 引入router
import router from './router'
// 下载图标
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
// 挂载vuex实例
import store from './store'
// 引入头部组件
import PanelHead from './components/panelHead.vue'

// 刷新后的动态路由添加
const localData=localStorage.getItem('pz_v3pz')
if(localData){
  store.commit('dynamicMenu',JSON.parse(localData).menu.routerList)
  store.state.menu.routerList.forEach(item => {
    router.addRoute('main',item)
  })
}

// 拦截器
router.beforeEach((to,from)=>{
    const token = localStorage.getItem('pz_token')
    // 非登录页面,token不存在
    if(!token && to.path !== '/login'){
      return '/login'
    }else if(token && to.path === '/login'){
      return '/'
    }else {
      return true
    }
})

// 路由挂载
// createApp(App).mount('#app')
const app=createApp(App)
app.use(router)
app.mount('#app')
app.use(store)
app.component('PanelHead',PanelHead)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}

// main.ts

// 如果您正在使用CDN引入,请删除下面一行。


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值