Browse Source

Merge branch 'dev' of https://git.citupro.com/zhengnaiwen_citu/menduner into dev

lifanagju_citu 1 năm trước cách đây
mục cha
commit
59eb25caba

+ 26 - 1
.env.development

@@ -2,6 +2,31 @@ NODE_ENV = 'development'
 
 
 VITE_APP_TITLE = 门墩儿直聘
 VITE_APP_TITLE = 门墩儿直聘
 
 
+# 请求路径
 VITE_BASE_URL = 'http://192.168.3.80'
 VITE_BASE_URL = 'http://192.168.3.80'
 
 
-VITE_TENANTCODE = '155'
+# 接口地址
+VITE_API_URL = '/admin-api'
+
+VITE_TENANTCODE = '155'
+
+# 是否删除debugger
+VITE_DROP_DEBUGGER = false
+
+# 是否删除console.log
+VITE_DROP_CONSOLE = false
+
+# 是否sourcemap
+VITE_SOURCEMAP = true
+
+# 打包路径
+VITE_BASE_PATH = '/'
+
+# 输出路径
+VITE_OUT_DIR = 'dist'
+
+# 商城H5会员端域名
+VITE_MALL_H5_DOMAIN = 'http://mall.yudao.iocoder.cn'
+
+# 验证码的开关
+VITE_APP_CAPTCHA_ENABLE = false

+ 1 - 1
package.json

@@ -14,8 +14,8 @@
     "js-cookie": "^3.0.5",
     "js-cookie": "^3.0.5",
     "pinia": "^2.1.7",
     "pinia": "^2.1.7",
     "pinia-plugin-persistedstate": "^3.2.1",
     "pinia-plugin-persistedstate": "^3.2.1",
-    "qs": "^6.12.1",
     "pnpm": "^9.1.0",
     "pnpm": "^9.1.0",
+    "qs": "^6.12.1",
     "roboto-fontface": "*",
     "roboto-fontface": "*",
     "vue": "^3.4.0",
     "vue": "^3.4.0",
     "vue-i18n": "9",
     "vue-i18n": "9",

+ 32 - 13
src/api/common/index.js

@@ -1,31 +1,50 @@
-import http from '@/utils/request'
+import request from '@/config/axios'
 
 
 // 发送验证码
 // 发送验证码
-export const sendSmsCode = (params) => {
-  return http.post('/app-api/menduner/system/auth/send-sms-code', params)
+export const sendSmsCode = async (data) => {
+  return await request.post({
+    url: '/app-api/menduner/system/auth/send-sms-code',
+    data
+  })
 }
 }
 
 
 // 验证码登录
 // 验证码登录
-export const smsLogin = (params) => {
-  return http.post('/app-api/menduner/system/auth/sms-login', params)
+export const smsLogin = async (data) => {
+  return await request.post({
+    url: '/app-api/menduner/system/auth/sms-login',
+    data
+  })
 }
 }
 
 
+
 // 密码登录
 // 密码登录
-export const passwordLogin = (params) => {
-  return http.post('/app-api/menduner/system/auth/login', params)
+export const passwordLogin = async (data) => {
+  return await request.post({
+    url: '/app-api/menduner/system/auth/login',
+    data
+  })
 }
 }
 
 
 // 退出登录
 // 退出登录
-export const logout = () => {
-  return http.post('/app-api/menduner/system/auth/logout')
+export const logout = async () => {
+  return await request.post({
+    url: '/app-api/menduner/system/auth/logout'
+  })
 }
 }
 
 
+
 // 修改密码
 // 修改密码
-export const updatePassword = (params) => {
-  return http.put('/app-api/menduner/system/mde-user/update-password', params)
+export const updatePassword = async (data) => {
+  return await request.post({
+    url: '/app-api/menduner/system/mde-user/update-password',
+    data
+  })
 }
 }
 
 
 // 重置密码
 // 重置密码
-export const resetPassword = (params) => {
-  return http.put('/admin-api/menduner/system/mde-user/update-password', params)
+export const resetPassword = async (data) => {
+  return await request.post({
+    url: '/admin-api/menduner/system/mde-user/update-password',
+    data
+  })
 }
 }

+ 6 - 3
src/api/personal/user.js

@@ -1,6 +1,9 @@
-import http from '@/utils/request'
+import request from '@/config/axios'
 
 
 // 获取用户信息
 // 获取用户信息
-export const getUserInfo = (params) => {
-  return http.get('/app-api/menduner/system/mde-user/get', params)
+export const getUserInfo = async (params) => {
+  return await request.get({
+    url: '/app-api/menduner/system/mde-user/get',
+    params
+  })
 }
 }

+ 169 - 0
src/components/Position/item.vue

@@ -0,0 +1,169 @@
+<template>
+  <div class="position-box">
+    <div class="sub-li" v-for="(item, index) in list" :key="index">
+      <div class="job-info">
+        <div class="sub-li-top">
+          <div class="sub-li-info">
+            <p class="name">{{ item.recruitName }}</p>
+            <v-chip size="x-small" color="error" label variant="outlined" class="mr-1">急聘</v-chip>
+            <v-chip size="x-small" color="warning" label variant="outlined">NEW</v-chip>
+          </div>
+          <p class="salary">{{ item.salary }}</p>
+        </div>
+        <v-chip size="x-small" label v-for="(k, i) in item.welfareList" :key="i" class="mr-1" color="#666">{{ k }}</v-chip>
+      </div>
+      <div class="sub-li-bottom">
+        <div class="user-info">
+          <div class="d-flex align-center">
+            <v-img :src="item.company.headImg" width="40" style="height: 40px;" />
+            <span class="names ml-2" style="font-size: 14px">{{ item.company.enterpriseName }}</span>
+          </div>
+          <p class="names float-right">
+            <span>{{ item.company.industry }}</span>
+            <span class="vline"></span>
+            <span>{{ item.company.scale }}</span>
+          </p>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+defineOptions({ name: 'position-card-item' })
+const list = [
+  {
+    recruitName: '产品经理',
+    salary: '6-11k',
+    welfareList: ['广州','本科','1-3年'],
+    company: {
+      headImg: 'https://img.bosszhipin.com/beijin/mcs/banner/06123cabdf75ed08313530ec8a42aef2cfcd208495d565ef66e7dff9f98764da.jpg',
+      enterpriseName: '卓越教育',
+      industry: '互联网行业',
+      scale: '0-20人'
+    }
+  },
+  {
+    recruitName: '产品经理',
+    salary: '6-11k',
+    welfareList: ['广州','本科','1-3年'],
+    company: {
+      headImg: 'https://img.bosszhipin.com/beijin/mcs/banner/06123cabdf75ed08313530ec8a42aef2cfcd208495d565ef66e7dff9f98764da.jpg',
+      enterpriseName: '卓越教育',
+      industry: '互联网行业',
+      scale: '0-20人'
+    }
+  },
+  {
+    recruitName: '产品经理',
+    salary: '6-11k',
+    welfareList: ['广州','本科','1-3年'],
+    company: {
+      headImg: 'https://img.bosszhipin.com/beijin/mcs/banner/06123cabdf75ed08313530ec8a42aef2cfcd208495d565ef66e7dff9f98764da.jpg',
+      enterpriseName: '卓越教育',
+      industry: '互联网行业',
+      scale: '0-20人'
+    }
+  },
+  {
+    recruitName: '产品经理',
+    salary: '6-11k',
+    welfareList: ['广州','本科','1-3年'],
+    company: {
+      headImg: 'https://img.bosszhipin.com/beijin/mcs/banner/06123cabdf75ed08313530ec8a42aef2cfcd208495d565ef66e7dff9f98764da.jpg',
+      enterpriseName: '卓越教育',
+      industry: '互联网行业',
+      scale: '0-20人'
+    }
+  }
+]
+</script>
+
+<style lang="scss" scoped>
+.position-box {
+  display: flex;
+  flex-wrap: wrap;
+}
+.sub-li {
+  width: calc((100% - 24px) / 3);
+  min-width: calc((100% - 24px) / 3);
+  max-width: calc((100% - 24px) / 3);
+  margin: 0 12px 12px 0;
+  height: 150px;
+  border-radius: 12px;
+  padding: 0;
+  overflow: hidden;
+  transition: all .2s linear;
+  background-color: #fff;
+  &:nth-child(3n) {
+    margin-right: 0;
+  }
+}
+.job-info {
+  padding: 16px 20px;
+}
+.sub-li-top {
+  display: flex;
+  width: 100%;
+  align-items: center;
+  margin-bottom: 12px;
+}
+.sub-li-info {
+  display: flex;
+  align-items: center;
+  flex-wrap: wrap;
+  height: 22px;
+  overflow: hidden;
+  flex: 1;
+}
+.salary {
+  font-size: 16px;
+  font-weight: 700;
+  color: #fe574a;
+  line-height: 22px;
+  flex: none;
+}
+.job-text {
+  white-space: normal;
+  padding-right: 0;
+  height: 22px;
+  line-height: 22px;
+  overflow: hidden;
+  word-break: break-all;
+  max-width: none;
+}
+.sub-li-info .name {
+  position: relative;
+  max-width: 200px;
+  margin-right: 8px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  font-weight: 600;
+}
+.sub-li-bottom {
+  background: linear-gradient(90deg, #f5fcfc 0, #fcfbfa 100%);
+  margin-top: 0;
+  padding-top: 0;
+  display: block;
+  border: none;
+}
+.user-info {
+  display: flex;
+  padding: 12px 20px;
+  align-items: center;
+  justify-content: space-between;
+}
+.names {
+  color: #666;
+  font-size: 13px;
+}
+.vline {
+  display: inline-block;
+  width: 1px;
+  height: 10px;
+  vertical-align: middle;
+  background-color: #e0e0e0;
+  margin: 0 10px;
+}
+</style>

+ 6 - 0
src/config/axios/errorCode.js

@@ -0,0 +1,6 @@
+export default {
+  '401': '认证失败,无法访问系统资源',
+  '403': '当前操作没有权限',
+  '404': '访问资源不存在',
+  default: '系统未知错误,请反馈给管理员'
+}

+ 28 - 8
src/config/axios/index.js

@@ -19,13 +19,33 @@ const request = (option) => {
   })
   })
 }
 }
 export default {
 export default {
-  get: async (url, params, headersType, responseType) => {
-    return request({
-      url: url,
-      method: 'get',
-      params: params,
-      headersType: headersType,
-      responseType: responseType
-    })
+  get: async (option) => {
+    const res = await request({ method: 'GET', ...option })
+    return res.data
+  },
+  post: async (option) => {
+    const res = await request({ method: 'POST', ...option })
+    return res.data
+  },
+  postOriginal: async (option) => {
+    const res = await request({ method: 'POST', ...option })
+    return res
+  },
+  delete: async (option) => {
+    const res = await request({ method: 'DELETE', ...option })
+    return res.data
+  },
+  put: async (option) => {
+    const res = await request({ method: 'PUT', ...option })
+    return res.data
+  },
+  download: async (option) => {
+    const res = await request({ method: 'GET', responseType: 'blob', ...option })
+    return res
+  },
+  upload: async (option) => {
+    option.headersType = 'multipart/form-data'
+    const res = await request({ method: 'POST', ...option })
+    return res
   }
   }
 }
 }

+ 213 - 0
src/config/axios/service.js

@@ -0,0 +1,213 @@
+import axios from 'axios'
+import Snackbar from '@/plugins/snackbar'
+import Confirm from '@/plugins/confirm'
+import qs from 'qs'
+import { config } from '@/config/axios/config'
+import { getToken, getRefreshToken, getTenantId, setToken } from '@/utils/auth'
+// import { getToken, getRefreshToken, getTenantId, removeToken, setToken } from '@/utils/auth'
+import errorCode from './errorCode'
+
+import { useI18n } from '@/hooks/web/useI18n'
+
+// import { resetRouter } from '@/router'
+// import { deleteUserCache } from '@/hooks/web/useCache'
+
+const { t } = useI18n()
+
+const tenantEnable = import.meta.env.VITE_APP_TENANT_ENABLE
+const { result_code, base_url, request_timeout } = config
+
+// 需要忽略的提示。忽略后,自动 Promise.reject('error')
+const ignoreMsgs = [
+  '无效的刷新令牌', // 刷新令牌被删除时,不用提示
+  '刷新令牌已过期' // 使用刷新令牌,刷新获取新的访问令牌时,结果因为过期失败,此时需要忽略。否则,会导致继续 401,无法跳转到登出界面
+]
+// 是否显示重新登录
+export const isRelogin = { show: false }
+// Axios 无感知刷新令牌,参考 https://www.dashingdog.cn/article/11 与 https://segmentfault.com/a/1190000020210980 实现
+// 请求队列
+let requestList = []
+// 是否正在刷新中
+let isRefreshToken = false
+// 请求白名单,无须token的接口
+const whiteList = ['/login', '/refresh-token']
+
+// 创建axios实例
+const service = axios.create({
+  baseURL: base_url, // api 的 base_url
+  timeout: request_timeout, // 请求超时时间
+  withCredentials: false // 禁用 Cookie 等信息
+})
+
+// request拦截器
+service.interceptors.request.use(
+  (config) => {
+    // 是否需要设置 token
+    let isToken = (config.headers || {}).isToken === false
+    whiteList.some((v) => {
+      if (config.url) {
+        config.url.indexOf(v) > -1
+        return (isToken = false)
+      }
+    })
+    if (getToken() && !isToken) {
+      ;(config).headers.Authorization = 'Bearer ' + getToken() // 让每个请求携带自定义token
+    }
+    // 设置租户
+    if (tenantEnable && tenantEnable === 'true') {
+      const tenantId = getTenantId()
+      if (tenantId) (config).headers['tenant-id'] = tenantId
+    }
+    const params = config.params || {}
+    const data = config.data || false
+    if (
+      config.method?.toUpperCase() === 'POST' &&
+      (config.headers)['Content-Type'] ===
+        'application/x-www-form-urlencoded'
+    ) {
+      config.data = qs.stringify(data)
+    }
+    // get参数编码
+    if (config.method?.toUpperCase() === 'GET' && params) {
+      config.params = {}
+      const paramsStr = qs.stringify(params, { allowDots: true })
+      if (paramsStr) {
+        config.url = config.url + '?' + paramsStr
+      }
+    }
+    return config
+  },
+  (error) => {
+    // Do something with request error
+    console.log(error) // for debug
+    Promise.reject(error)
+  }
+)
+
+// response 拦截器
+service.interceptors.response.use(
+  async (response) => {
+    // import { i18n } from '@/plugins/vueI18n'
+
+    // console.log(i18n)
+    // const { t } = i18n
+    let { data } = response
+    const config = response.config
+    if (!data) {
+      // 返回“[HTTP]请求没有返回值”;
+      throw new Error()
+    }
+    // 未设置状态码则默认成功状态
+    // 二进制数据则直接返回,例如说 Excel 导出
+    if (
+      response.request.responseType === 'blob' ||
+      response.request.responseType === 'arraybuffer'
+    ) {
+      // 注意:如果导出的响应为 json,说明可能失败了,不直接返回进行下载
+      if (response.data.type !== 'application/json') {
+        return response.data
+      }
+      data = await new Response(response.data).json()
+    }
+    const code = data.code || result_code
+    // 获取错误信息
+    const msg = data.msg || errorCode[code] || errorCode['default']
+    if (ignoreMsgs.indexOf(msg) !== -1) {
+      // 如果是忽略的错误码,直接返回 msg 异常
+      return Promise.reject(msg)
+    } else if (code === 401) {
+      // 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了
+      if (!isRefreshToken) {
+        isRefreshToken = true
+        // 1. 如果获取不到刷新令牌,则只能执行登出操作
+        if (!getRefreshToken()) {
+          return handleAuthorized()
+        }
+        // 2. 进行刷新访问令牌
+        try {
+          const refreshTokenRes = await refreshToken()
+          // 2.1 刷新成功,则回放队列的请求 + 当前请求
+          setToken((await refreshTokenRes).data.data)
+          config.headers.Authorization = 'Bearer ' + getToken()
+          requestList.forEach((cb) => {
+            cb()
+          })
+          requestList = []
+          return service(config)
+        } catch (e) {
+          // 为什么需要 catch 异常呢?刷新失败时,请求因为 Promise.reject 触发异常。
+          // 2.2 刷新失败,只回放队列的请求
+          requestList.forEach((cb) => {
+            cb()
+          })
+          // 提示是否要登出。即不回放当前请求!不然会形成递归
+          return handleAuthorized()
+        } finally {
+          requestList = []
+          isRefreshToken = false
+        }
+      } else {
+        // 添加到队列,等待刷新获取到新的令牌
+        return new Promise((resolve) => {
+          requestList.push(() => {
+            config.headers.Authorization = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
+            resolve(service(config))
+          })
+        })
+      }
+    } else if (code === 500) {
+      Snackbar.error(t('sys.api.errMsg500'))
+      return Promise.reject(new Error(msg))
+    } else if (code === 901) {
+      Snackbar.error(t('sys.api.errMsg901'))
+      return Promise.reject(new Error(msg))
+    } else if (code !== 200) {
+      if (msg === '无效的刷新令牌') {
+        // hard coding:忽略这个提示,直接登出
+        console.log(msg)
+      } else {
+        Snackbar.error({ title: msg })
+      }
+      return Promise.reject('error')
+    } else {
+      return data
+    }
+  },
+  (error) => {
+    console.log('err' + error) // for debug
+    let { message } = error
+    if (message === 'Network Error') {
+      message = t('sys.api.errorMessage')
+    } else if (message.includes('timeout')) {
+      message = t('sys.api.apiTimeoutMessage')
+    } else if (message.includes('Request failed with status code')) {
+      message = t('sys.api.apiRequestFailed') + message.substr(message.length - 3)
+    }
+    Snackbar.error(message)
+    return Promise.reject(error)
+  }
+)
+
+const refreshToken = async () => {
+  axios.defaults.headers.common['tenant-id'] = getTenantId()
+  return await axios.post(base_url + '/system/auth/refresh-token?refreshToken=' + getRefreshToken())
+}
+const handleAuthorized = () => {
+  if (!isRelogin.show) {
+    // 如果已经到重新登录页面则不进行弹窗提示
+    if (window.location.href.includes('login?redirect=')) {
+      return
+    }
+    isRelogin.show = true
+    Confirm(t('sys.api.timeoutMessage'), t('common.confirmTitle')).then(() => {
+      // resetRouter() // 重置静态路由表
+      // deleteUserCache() // 删除用户缓存
+      // removeToken()
+      isRelogin.show = false
+      // 干掉token后再走一次路由让它过router.beforeEach的校验
+      location.reload()
+    })
+  }
+  return Promise.reject(t('sys.api.timeoutMessage'))
+}
+export { service }

+ 38 - 0
src/hooks/web/useI18n.js

@@ -0,0 +1,38 @@
+import { i18n } from '@/plugins/vueI18n'
+
+const getKey = (namespace, key) => {
+  if (!namespace) {
+    return key
+  }
+  if (key.startsWith(namespace)) {
+    return key
+  }
+  return `${namespace}.${key}`
+}
+
+export const useI18n = ( namespace )=> {
+  const normalFn = {
+    t: (key) => {
+      return getKey(namespace, key)
+    }
+  }
+
+  if (!i18n) {
+    return normalFn
+  }
+
+  const { t, ...methods } = i18n.global
+
+  const tFn = (key, ...arg) => {
+    if (!key) return ''
+    if (!key.includes('.') && !namespace) return key
+    //@ts-ignore
+    return t(getKey(namespace, key), ...arg)
+  }
+  return {
+    ...methods,
+    t: tFn
+  }
+}
+
+export const t = (key) => key

+ 0 - 4
src/main.js

@@ -17,8 +17,6 @@ import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' // pinia 持
 
 
 import router from './router'
 import router from './router'
 
 
-import { setupI18n } from '@/plugins/vueI18n'
-
 const pinia = createPinia()
 const pinia = createPinia()
 
 
 pinia.use(piniaPluginPersistedstate)
 pinia.use(piniaPluginPersistedstate)
@@ -31,6 +29,4 @@ app.use(router)
 
 
 registerPlugins(app)
 registerPlugins(app)
 
 
-setupI18n(app)
-
 app.mount('#app')
 app.mount('#app')

+ 50 - 0
src/plugins/confirm/confirm.vue

@@ -0,0 +1,50 @@
+<script setup>
+import { ref } from 'vue'
+
+defineProps({
+  title: String,
+  text: String,
+  cancel: Function,
+  sure: Function
+})
+
+defineOptions({ name: 'ct-confirm' })
+
+const dialog = ref(true)
+// const show = () => {
+//   dialog.value = true
+// }
+// defineExpose({ show })
+</script>
+
+<template>
+  <v-app>
+    <v-dialog
+      v-model="dialog"
+      max-width="400"
+      persistent
+    >
+
+      <v-card
+        :text="text"
+        :title="title"
+      >
+        <template #prepend>
+          <v-icon color="warning">mdi-alert-circle-outline</v-icon>
+        </template>
+        <template v-slot:actions>
+          <v-spacer></v-spacer>
+
+          <v-btn @click="cancel">
+            取消
+          </v-btn>
+
+          <v-btn color="success" @click="sure">
+            确认
+          </v-btn>
+        </template>
+      </v-card>
+    </v-dialog>
+  </v-app>
+  
+</template>

+ 38 - 0
src/plugins/confirm/index.js

@@ -0,0 +1,38 @@
+import { createApp } from 'vue'
+import confirm from './confirm.vue'
+import vuetify from '@/plugins/vuetify'
+
+
+const toastMessage  = (title, text, option = {})  => {
+  return new Promise((resolve, reject) => {
+    const rootNode = document.createElement("div")
+      document.querySelector('.v-application').appendChild(rootNode)
+      const app = createApp(confirm, {
+        title,
+        text,
+        cancel () {
+          app.unmount()
+          rootNode.remove()
+          if (option.cancelCallback) {
+            reject()
+          }
+        },
+        sure () {
+          app.unmount()
+          rootNode.remove()
+          resolve()
+        }
+      })
+      app.use(vuetify)
+      app.mount(rootNode)
+  })
+  
+  
+}
+
+// 注册插件app.use()会自动执行install函数
+toastMessage.install = (app) => {
+  app.config.globalProperties.Confirm = toastMessage
+}
+
+export default toastMessage

+ 7 - 0
src/plugins/index.js

@@ -7,6 +7,13 @@
 // Plugins
 // Plugins
 import vuetify from './vuetify'
 import vuetify from './vuetify'
 
 
+import { setupI18n } from '@/plugins/vueI18n'
+
+// import { setupSnackbar } from './snackbar'
+
 export function registerPlugins (app) {
 export function registerPlugins (app) {
   app.use(vuetify)
   app.use(vuetify)
+  setupI18n(app)
+
+  // setupSnackbar(app)
 }
 }

+ 36 - 0
src/plugins/snackbar/index.js

@@ -0,0 +1,36 @@
+import { createApp } from 'vue'
+import ToastMessage from './message.vue'
+import vuetify from '@/plugins/vuetify'
+
+var timeId
+const toastMessage  = (options)  => {
+  const rootNode = document.createElement("div")
+  document.querySelector('.v-application').appendChild(rootNode)
+  const app = createApp(ToastMessage, options)
+  app.use(vuetify)
+  app.mount(rootNode)
+  const { timeout } = options || {}
+  clearTimeout(timeId)
+  timeId = setTimeout(() => {
+      app.unmount()
+      rootNode.remove()
+  }, timeout || 3000)
+}
+toastMessage.success = (message, variant) => {
+  toastMessage({ message, color: 'success', variant })
+}
+toastMessage.error = (message, variant) => {
+  toastMessage({ message, color: 'error', variant })
+}
+toastMessage.info = (message, variant) => {
+  toastMessage({ message, color: 'info', variant })
+}
+toastMessage.warning = (message, variant) => {
+  toastMessage({ message, color: 'warning', variant })
+}
+// 注册插件app.use()会自动执行install函数
+toastMessage.install = (app) => {
+  app.config.globalProperties.Snackbar = toastMessage
+}
+
+export default toastMessage

+ 40 - 0
src/plugins/snackbar/message.vue

@@ -0,0 +1,40 @@
+<template>
+  <v-app :full-height="false">
+    <v-snackbar
+      timeout="-1"
+      location="top"
+      :variant="variant || elevated"
+      :color="color"
+      v-model="snackbar"
+    >{{message}}
+    <template v-slot:actions>
+          <v-btn
+            color="white"
+            variant="text"
+            @click="snackbar = false"
+          >
+            关闭
+          </v-btn>
+        </template>
+    </v-snackbar>
+  </v-app>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+defineProps({
+  variant: String,
+  color: String,
+  message: String
+})
+defineOptions({ name: 'snackbar-message' })
+
+const snackbar = ref(true)
+
+</script>
+<style scoped>
+::v-deep .v-application__wrap {
+  height: 0;
+  min-height: 0;
+}
+</style>

+ 1 - 1
src/plugins/vuetify.js

@@ -20,7 +20,7 @@ const myCustomLightTheme = {
     surface: '#FFFFFF',
     surface: '#FFFFFF',
     primary: '#00897B',
     primary: '#00897B',
     secondary: '#26A69A',
     secondary: '#26A69A',
-    error: '#B00020',
+    error: '#ff5252',
     info: '#2196F3',
     info: '#2196F3',
     success: '#4CAF50',
     success: '#4CAF50',
     warning: '#FB8C00',
     warning: '#FB8C00',

+ 7 - 1
src/utils/auth.js

@@ -11,4 +11,10 @@ export const setToken = (token) => {
 // 清除token
 // 清除token
 export const deleteToken = () => {
 export const deleteToken = () => {
   return localStorage.removeItem('access_token')
   return localStorage.removeItem('access_token')
-}
+}
+// ?
+export const getRefreshToken = () => {}
+
+// 租户ID
+export const getTenantId = () => {}
+

+ 5 - 6
src/views/Home/personal/components/hotPromotedPositions.vue

@@ -5,23 +5,22 @@
       <v-tab :value="2">最新职位</v-tab>
       <v-tab :value="2">最新职位</v-tab>
       <v-tab :value="3">急聘职位</v-tab>
       <v-tab :value="3">急聘职位</v-tab>
     </v-tabs>
     </v-tabs>
-    <v-window v-model="tab" class="mt-9">
-        <!-- 推荐职位 -->
+    <v-window v-model="tab" class="mt-3">
       <v-window-item :value="1">
       <v-window-item :value="1">
-        111
+        <PositionCard></PositionCard>
       </v-window-item>
       </v-window-item>
-        <!-- 最新职位 -->
       <v-window-item :value="2">
       <v-window-item :value="2">
-        222
+        <PositionCard></PositionCard>
       </v-window-item>
       </v-window-item>
       <v-window-item :value="3">
       <v-window-item :value="3">
-        333
+        <PositionCard></PositionCard>
       </v-window-item>
       </v-window-item>
     </v-window>
     </v-window>
   </div>
   </div>
 </template>
 </template>
 
 
 <script setup name="hotPromotedPositions">
 <script setup name="hotPromotedPositions">
+import PositionCard from '@/components/Position/item.vue'
 import {ref } from 'vue'
 import {ref } from 'vue'
 const tab = ref(0)
 const tab = ref(0)
 </script>
 </script>

+ 3 - 0
src/views/login/index.vue

@@ -53,8 +53,11 @@ import { ref, reactive } from 'vue'
 import passwordFrom from './components/passwordPage.vue'
 import passwordFrom from './components/passwordPage.vue'
 import phoneFrom from '@/components/VerificationCode'
 import phoneFrom from '@/components/VerificationCode'
 import qrCode from './components/qrCode.vue'
 import qrCode from './components/qrCode.vue'
+
 import { userLocaleStore } from '@/store/user'
 import { userLocaleStore } from '@/store/user'
 import { useRouter } from 'vue-router'
 import { useRouter } from 'vue-router'
+// import Confirm from '@/plugins/confirm'
+// import Snackbar from '@/plugins/snackbar'
 defineOptions({ name: 'login-index' })
 defineOptions({ name: 'login-index' })
 
 
 const router = useRouter()
 const router = useRouter()