Преглед на файлове

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

zhengnaiwen_citu преди 1 година
родител
ревизия
7314fa5429
променени са 44 файла, в които са добавени 1746 реда и са изтрити 389 реда
  1. 1 1
      .env.development
  2. 1 0
      .gitignore
  3. 4 12
      src/api/common/index.js
  4. 6 0
      src/api/personal/user.js
  5. BIN
      src/assets/logo.png
  6. 1 1
      src/components/CtDialog/index.vue
  7. 335 0
      src/components/CtForm/index copy.vue
  8. 28 270
      src/components/CtForm/index.vue
  9. 55 0
      src/components/FormUI/TextInput/index.vue
  10. 39 0
      src/components/FormUI/autocomplete/index.vue
  11. 141 0
      src/components/VerificationCode/index.vue
  12. 28 0
      src/layout/index.vue
  13. 75 0
      src/layout/personal/footer.vue
  14. 119 0
      src/layout/personal/navBar.vue
  15. 1 1
      src/locales/en.js
  16. 1 1
      src/locales/zh-CN.js
  17. 47 1
      src/router/modules/personal.js
  18. 26 8
      src/router/modules/remaining.js
  19. 52 9
      src/store/user.js
  20. 24 1
      src/styles/index.css
  21. 1 1
      src/styles/index.min.css
  22. 24 1
      src/styles/index.scss
  23. 155 0
      src/styles/personal/navBar.css
  24. 1 0
      src/styles/personal/navBar.min.css
  25. 136 0
      src/styles/personal/navBar.scss
  26. 2 2
      src/styles/protocol/index.css
  27. 1 1
      src/styles/protocol/index.min.css
  28. 2 2
      src/styles/protocol/index.scss
  29. 12 1
      src/utils/request.js
  30. 0 31
      src/views/Home/components/personal.vue
  31. 0 0
      src/views/Home/enterprise/index.vue
  32. 2 2
      src/views/Home/index.vue
  33. 38 0
      src/views/Home/personal/account/dynamic/accountBinding.vue
  34. 90 0
      src/views/Home/personal/account/dynamic/editPassword.vue
  35. 41 0
      src/views/Home/personal/account/dynamic/privacySettings.vue
  36. 24 0
      src/views/Home/personal/account/dynamic/realAuthentication.vue
  37. 64 0
      src/views/Home/personal/account/index.vue
  38. 48 0
      src/views/Home/personal/components/headCarousel.vue
  39. 27 0
      src/views/Home/personal/components/hotPromotedPositions.vue
  40. 51 0
      src/views/Home/personal/components/search.vue
  41. 19 0
      src/views/Home/personal/index.vue
  42. 0 32
      src/views/enterprise/components/register.vue
  43. 1 1
      src/views/login/components/phonePage.vue
  44. 23 10
      src/views/login/index.vue

+ 1 - 1
.env.development

@@ -1,6 +1,6 @@
 NODE_ENV = 'development'
 
-VITE_APP_TITLE = 门墩儿
+VITE_APP_TITLE = 门墩儿
 
 # 请求路径
 VITE_BASE_URL = 'http://192.168.3.80'

+ 1 - 0
.gitignore

@@ -21,3 +21,4 @@ pnpm-debug.log*
 *.njsproj
 *.sln
 *.sw?
+*.copy.vue

+ 4 - 12
src/api/common/index.js

@@ -2,19 +2,11 @@
 import request from '@/config/axios'
 
 // 发送验证码
-export const sendSmsCode = async (data) => {
-  return await request.post({
-    url: '/menduner/system/auth/send-sms-code',
-    data
-  })
-  // return http.post('/menduner/system/auth/send-sms-code', params)
+export const sendSmsCode = (params) => {
+  return http.post('/app-api/menduner/system/auth/send-sms-code', params)
 }
 
 // 验证码登录
-export const smsLogin = async (data) => {
-  return await request.post({
-    url: '/menduner/system/auth/sms-login',
-    data
-  })
-  // return http.post('/menduner/system/auth/sms-login', params)
+export const smsLogin = (params) => {
+  return http.post('/app-api/menduner/system/auth/sms-login', params)
 }

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

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

BIN
src/assets/logo.png


+ 1 - 1
src/components/CtDialog/index.vue

@@ -101,7 +101,7 @@ const dialogWidth = computed(() => {
   return arr[+props.widthType]
 })
 const handleClose = () => {
-  emits('update:visible', false)
+  // emits('update:visible', false)
   emits('close', false)
 }
 const handleSave = () => {

+ 335 - 0
src/components/CtForm/index copy.vue

@@ -0,0 +1,335 @@
+<template>
+  <v-form ref="form" v-model="valid" class="login-form" @submit.prevent>
+    <div>
+      <v-row dense no-gutters class="justify-space-between">
+        <template v-for="(item, index) in props.items.options">
+          <slot :name="item.prevSlot"></slot>
+          <v-col :key="item.key" v-if="!item.hide" :cols="item.col || '12'" class="position">
+            <template v-if="item.slotTitle">
+              <div :class="item.class" :style="item.slotTitleStyle">{{ item.slotTitle }}</div>
+            </template>
+            <div class="d-flex" :class="item.flexStyle || 'flex-row'">
+              <v-text-field
+                v-if="['text', 'password', 'number'].includes(item.type)"
+                :type="item.type"
+                v-model="item.value"
+                :rules="item.rules"
+                :disabled="item.disabled"
+                :density="item.dense || 'compact'"
+                :color="item.color"
+                :label="item.label"
+                :placeholder="item.placeholder || item.label"
+                variant="outlined"
+                :width="item.width"
+                :autofocus="item.autofocus"
+                :class="item.class"
+                :suffix="item.suffix"
+                :append-inner-icon="item.appendIcon"
+                :clearable="item.clearable"
+                :readonly="item.readonly"
+                :prepend-inner-icon="item.prependInnerIcon"
+                hide-spin-buttons
+                @wheel="$event => handleWheel($event, item)"
+                @keyup.enter="item.keyupEnterNative && item.keyupEnterNative(index)"
+                @click="item.click && item.click(index)"
+                @click:append-inner="item.clickAppendInner"
+                @change="handleChange(item)"
+              />
+              <v-autocomplete
+                v-if="item.type === 'autocomplete'"
+                :rules="item.rules"
+                v-model="item.value"
+                :attach="!item.noAttach"
+                :loading="item.loading"
+                :label="item.label"
+                :placeholder="item.placeholder || item.label"
+                :items="item.items"
+                :item-title="item.itemText || 'label'"
+                :item-value="item.itemValue || 'value'"
+                variant="outlined"
+                :density="item.dense || 'compact'"
+                :disabled="item.disabled"
+                :multiple="item.multiple"
+                :clearable="item.clearable"
+                :search="item.searchInput"
+                :hide-no-data="item.hideNoData"
+                :no-data-text="item.noDataText || 'No data available'"
+                :hide-selected="item.hideSelected"
+                @change="handleChange(item)"
+              >
+                <template v-if="item.slotAppendItem" v-slot:append-item>
+                  <slot :name="item.slotAppendItem" :item="item"></slot>
+                </template>
+                <template v-if="item.prependItem" #prepend-item>
+                  <slot :name="item.prependItem" :item="item"></slot>
+                </template>
+              </v-autocomplete>
+              <!-- autocomplete2 多选纸片样式 -->
+              <v-autocomplete
+                v-if="item.type === 'autocomplete2'"
+                v-model="item.value"
+                :rules="item.rules"
+                :attach="!item.noAttach"
+                :loading="item.loading"
+                :label="item.label"
+                :placeholder="item.placeholder || item.label"
+                :items="item.canCreate ? [inputUpdateValue, ...item.items].filter(Boolean) : item.items"
+                :item-title="item.itemText || 'label'"
+                :item-value="item.itemValue || 'value'"
+                variant="outlined"
+                :density="item.dense || 'compact'"
+                :multiple="item.multiple"
+                :clearable="item.clearable"
+                :search-input="item.searchInput"
+                :hide-no-data="item.hideNoData"
+                :hide-selected="item.hideSelected"
+                :readonly="item.readonly"
+                @change="handleChange(item)"
+                @update:search-input="$event => item.canCreate ? inputUpdateValue = $event : inputUpdateAutocomplete($event)"
+                :hide-details="!item.showDetails"
+                deletable-chips
+                cache-items
+                small-chips
+              ></v-autocomplete>
+              <v-combobox
+                v-if="item.type === 'combobox'"
+                :rules="item.rules"
+                v-model="item.value"
+                :attach="true"
+                :label="item.label"
+                :placeholder="item.placeholder || item.label"
+                :items="item.items"
+                :item-title="item.itemText || 'label'"
+                :item-value="item.itemValue || 'value'"
+                variant="outlined"
+                :density="item.dense || 'compact'"
+                :clearable="item.clearable"
+                :disabled="item.disabled"
+                @change="handleChange(item)"
+              >
+                <template v-if="item.hasIcon" v-slot:selection="data">
+                  <v-icon color="blue darken-2">{{ data.item.label }}</v-icon>
+                </template>
+                <!-- <template v-if="item.hasIcon" v-slot:item="data">
+                  <v-list-item-avatar>
+                    <v-icon>{{ data.item.label }}</v-icon>
+                  </v-list-item-avatar>
+                  <v-list-item-content>
+                    {{ data.item.label }}
+                  </v-list-item-content>
+                </template> -->
+              </v-combobox>
+              <v-textarea
+                v-if="item.type === 'textarea'"
+                :rules="item.rules"
+                v-model="item.value"
+                :label="item.label"
+                :placeholder="item.placeholder || item.label"
+                :no-resize="!item.resize"
+                variant="outlined"
+                :density="item.dense || 'compact'"
+                :rows="item.rows || 3"
+                :disabled="item.disabled"
+                @change="handleChange(item)"
+              ></v-textarea>
+              <v-radio-group
+                v-if="item.type === 'ifRadio'"
+                v-model="item.value"
+                :disabled="item.disabled"
+                mandatory
+                row
+                @change="handleChange(item)"
+              >
+                <template v-slot:label>
+                  <div :style="`width: ${item.width || 120}px;`">{{ item.label }}</div>
+                </template>
+                <v-radio
+                  v-for="radio in item.items"
+                  :key="`${item.key}_radio_${radio.label}`"
+                  :readonly="radio.readonly"
+                  :label="radio.label"
+                  :value="radio.value"
+                  class="mr-8"
+                ></v-radio>
+              </v-radio-group>
+              <template v-if="item.type === 'checkbox'">
+                <div style="width: 120px;" class="mt-4 label text-left">{{ item.label }}</div>
+                <div :style="item.style">
+                  <v-checkbox
+                    v-model="item.value"
+                    v-for="k in item.items"
+                    :key="k.key"
+                    :label="k.label"
+                    :color="item.color"
+                    :value="k.value"
+                    :readonly="k.readonly"
+                    hide-details
+                    :multiple="true"
+                    class="mr-3"
+                  ></v-checkbox>
+                </div>
+              </template>
+              <v-file-input
+                v-if="item.type === 'upload'"
+                :prepend-icon="item.prependIcon || ''"
+                :append-icon="item.appendIcon"
+                :append-outer-icon="item.appendOuterIcon"
+                :show-size="item.showSize"
+                variant="outlined"
+                :density="item.dense || 'compact'"
+                v-model="item.value"
+                :placeholder="item.placeholder || item.label"
+                :hint="item.hint"
+                :rules="item.rules"
+                :label="item.label"
+                :persistent-hint="item.persistentHint"
+                :loading= "item.loading"
+                :disabled="item.disabled"
+                :multiple="item.multiple"
+                :success="item.success"
+                :error="item.error"
+                :accept="item.accept || '.xlsx, .xls, .csv, .pdf, .txt, .doc'"
+                @change="handleChange(item)"
+              >
+                <template v-if="item.selfAppend" #append>
+                  <slot :name="item.selfAppend" :data="item.value"></slot>
+                </template>
+              </v-file-input>
+              <v-color-picker
+                v-if="item.type === 'colorPicker'"
+                class="mb-5"
+                v-model="item.value"
+                :elevation="item.elevation || 5"
+                :dot-size="item.dotSize || 25"
+                :show-swatches="item.showSwatches || false"
+                swatches-max-height="200"
+                :mode="item.mode || 'hexa'"
+                :hide-mode-switch="true"
+                @input="item.change"
+              />
+              <template v-if="item.type === 'switch'">
+                <span v-if="item.describe"> {{ item.describe }} </span>
+                <span class="ml-2" v-if="item.trueLabel"> {{ item.trueLabel }}</span>
+                <v-switch
+                  dense hide-details class="mt-0 ml-2 pa-0"
+                  v-model="item.value"
+                  :label="item.label"
+                  :disabled="item.disabled || false"
+                  :color="item.color || 'primary'"
+                  :true-value="(item.trueValue !== undefined) ? item.trueValue : true"
+                  :false-value="(item.falseValue !== undefined) ? item.falseValue : false"
+                ></v-switch>
+                <span v-if="item.falseLabel"> {{ item.falseLabel }} </span>
+              </template>
+              <template v-if="item.type === 'date'">
+                <div class="d-flex" style="margin-bottom: 22px;">
+                  <span class="label d-flex align-center" :style="`width: ${item.width || 120}px;`">{{ item.label }}</span>
+                  <!-- <date-picker
+                    :is-valid="isValid"
+                    :option="{ ...item.option, disabled: item.disabled }"
+                    v-model="item.value"
+                    :style="item.style"
+                    @change="item.value = $event; handleChange(item); handleCheck(item)"></date-picker> -->
+                </div>
+              </template>
+              <template v-if="item.slotName">
+                  <slot :name="item.slotName" :item="item"></slot>
+              </template>
+            </div>
+          </v-col>
+        </template>
+      </v-row>
+    </div>
+    <slot></slot>
+  </v-form>
+</template>
+
+<script setup>
+// import DatePicker from '@/components/Form/datePicker.vue'
+import { ref, defineEmits  } from 'vue'
+defineOptions({ name: 'components-ct-form-copy' })
+const props = defineProps({items: Object})
+const inputUpdateValue = ref('')
+const form = ref()
+const valid = ref(false)
+const isValid = ref(true)
+const emit = defineEmits(['inputUpdateAutocomplete', 'change'])
+
+const handleWheel = (event, item) => {
+  if (item.type !== 'number') return
+  event.preventDefault()
+  if (event.deltaY > 0) {
+    item.value--
+  } else {
+    item.value++
+  }
+  handleChange(item)
+}
+
+
+const handleCheck = (e) => {
+  if (e.type !== 'date' || e.hide || !e.rules) return
+  const rules = e.rules[0]
+  const check = rules(e.value)
+  if (typeof check === 'string') {
+    e.option.error = true
+    e.option.errorMsg = check
+    return
+  }
+  e.option.error = false
+  e.option.errorMsg = null
+}
+
+const validateTime = () => {
+  isValid.value = true
+  props.items.options.forEach((e) => {
+    if (e.type !== 'date' || e.hide || !e.rules) return
+    const rules = e.rules[0]
+    const check = rules(e.value)
+    if (typeof check === 'string') {
+      isValid.value = false
+      e.option.error = true
+      e.option.errorMsg = check
+    } else {
+      e.option.error = false
+      e.option.errorMsg = null
+    }
+  })
+  return isValid.value
+}
+
+
+const validate = () => {
+  const form = form.value.validate()
+  const time = validateTime()
+  return form && time
+}
+
+const inputUpdateAutocomplete = (val) => {
+  emit('inputUpdateAutocomplete', val)
+}
+
+
+const resetValidation = () => {
+  form.value.resetValidation()
+}
+
+const reset = () => {
+  form.value.reset()
+}
+const handleChange = (item) => {
+  if (item.type === 'date' && item.value) item.option.validate = false
+  if (item?.change) item.change(item.value, item)
+  emit('change', false)
+}
+</script>
+
+<style lang="scss" scoped>
+.position {
+  position: relative;
+}
+.label {
+  font-size: 14px;
+  color: rgba(0, 0, 0, .6);
+}
+</style>

+ 28 - 270
src/components/CtForm/index.vue

@@ -1,239 +1,29 @@
 <template>
-  <v-form ref="form" v-model="valid" class="login-form" @submit.prevent>
+  <v-form ref="formRef" v-model="valid" class="login-form" @submit.prevent>
     <div>
       <v-row dense no-gutters class="justify-space-between">
-        <template v-for="(item, index) in props.items.options">
+        <template v-for="(item) in props.items.options">
           <slot :name="item.prevSlot"></slot>
           <v-col :key="item.key" v-if="!item.hide" :cols="item.col || '12'" class="position">
             <template v-if="item.slotTitle">
               <div :class="item.class" :style="item.slotTitleStyle">{{ item.slotTitle }}</div>
             </template>
             <div class="d-flex" :class="item.flexStyle || 'flex-row'">
-              <v-text-field
+              <span>{{ item.value }}</span>
+              <textUI
                 v-if="['text', 'password', 'number'].includes(item.type)"
-                :type="item.type"
                 v-model="item.value"
-                :rules="item.rules"
-                :disabled="item.disabled"
-                :density="item.dense || 'compact'"
-                :color="item.color"
-                :label="item.label"
-                :placeholder="item.placeholder || item.label"
-                variant="outlined"
-                :width="item.width"
-                :autofocus="item.autofocus"
-                :class="item.class"
-                :suffix="item.suffix"
-                :append-inner-icon="item.appendIcon"
-                :clearable="item.clearable"
-                :readonly="item.readonly"
-                :prepend-inner-icon="item.prependInnerIcon"
-                hide-spin-buttons
-                @wheel="$event => handleWheel($event, item)"
-                @keyup.enter="item.keyupEnterNative && item.keyupEnterNative(index)"
-                @click="item.click && item.click(index)"
-                @click:append-inner="item.clickAppendInner"
+                :item="item"
                 @change="handleChange(item)"
-              />
-              <v-autocomplete
+              ></textUI>
+              <autocompleteUI
                 v-if="item.type === 'autocomplete'"
-                :rules="item.rules"
                 v-model="item.value"
-                :attach="!item.noAttach"
-                :loading="item.loading"
-                :label="item.label"
-                :placeholder="item.placeholder || item.label"
-                :items="item.items"
-                :item-text="item.itemText || 'label'"
-                :item-value="item.itemValue || 'value'"
-                variant="outlined"
-                :density="item.dense || 'compact'"
-                :disabled="item.disabled"
-                :multiple="item.multiple"
-                :clearable="item.clearable"
-                :search="item.searchInput"
-                :hide-no-data="item.hideNoData"
-                :no-data-text="item.noDataText || 'No data available'"
-                :hide-selected="item.hideSelected"
+                :item="item"
                 @change="handleChange(item)"
-              >
-                <template v-if="item.slotAppendItem" v-slot:append-item>
-                  <slot :name="item.slotAppendItem" :item="item"></slot>
-                </template>
-                <template v-if="item.prependItem" #prepend-item>
-                  <slot :name="item.prependItem" :item="item"></slot>
-                </template>
-              </v-autocomplete>
-              <!-- autocomplete2 多选纸片样式 -->
-              <v-autocomplete
-                v-if="item.type === 'autocomplete2'"
-                v-model="item.value"
-                :rules="item.rules"
-                :attach="!item.noAttach"
-                :loading="item.loading"
-                :label="item.label"
-                :placeholder="item.placeholder || item.label"
-                :items="item.canCreate ? [inputUpdateValue, ...item.items].filter(Boolean) : item.items"
-                :item-text="item.itemText || 'label'"
-                :item-value="item.itemValue || 'value'"
-                variant="outlined"
-                :density="item.dense || 'compact'"
-                :multiple="item.multiple"
-                :clearable="item.clearable"
-                :search-input="item.searchInput"
-                :hide-no-data="item.hideNoData"
-                :hide-selected="item.hideSelected"
-                :readonly="item.readonly"
-                @change="handleChange(item)"
-                @update:search-input="$event => item.canCreate ? inputUpdateValue = $event : inputUpdateAutocomplete($event)"
-                :hide-details="!item.showDetails"
-                deletable-chips
-                cache-items
-                small-chips
-              ></v-autocomplete>
-              <v-combobox
-                v-if="item.type === 'combobox'"
-                :rules="item.rules"
-                v-model="item.value"
-                :attach="true"
-                :label="item.label"
-                :placeholder="item.placeholder || item.label"
-                :items="item.items"
-                :item-text="item.itemText || 'label'"
-                :item-value="item.itemValue || 'value'"
-                variant="outlined"
-                :density="item.dense || 'compact'"
-                :clearable="item.clearable"
-                :disabled="item.disabled"
-                @change="handleChange(item)"
-              >
-                <template v-if="item.hasIcon" v-slot:selection="data">
-                  <v-icon color="blue darken-2">{{ data.item.label }}</v-icon>
-                </template>
-                <!-- <template v-if="item.hasIcon" v-slot:item="data">
-                  <v-list-item-avatar>
-                    <v-icon>{{ data.item.label }}</v-icon>
-                  </v-list-item-avatar>
-                  <v-list-item-content>
-                    {{ data.item.label }}
-                  </v-list-item-content>
-                </template> -->
-              </v-combobox>
-              <v-textarea
-                v-if="item.type === 'textarea'"
-                :rules="item.rules"
-                v-model="item.value"
-                :label="item.label"
-                :placeholder="item.placeholder || item.label"
-                :no-resize="!item.resize"
-                variant="outlined"
-                :density="item.dense || 'compact'"
-                :rows="item.rows || 3"
-                :disabled="item.disabled"
-                @change="handleChange(item)"
-              ></v-textarea>
-              <v-radio-group
-                v-if="item.type === 'ifRadio'"
-                v-model="item.value"
-                :disabled="item.disabled"
-                mandatory
-                row
-                @change="handleChange(item)"
-              >
-                <template v-slot:label>
-                  <div :style="`width: ${item.width || 120}px;`">{{ item.label }}</div>
-                </template>
-                <v-radio
-                  v-for="radio in item.items"
-                  :key="`${item.key}_radio_${radio.label}`"
-                  :readonly="radio.readonly"
-                  :label="radio.label"
-                  :value="radio.value"
-                  class="mr-8"
-                ></v-radio>
-              </v-radio-group>
-              <template v-if="item.type === 'checkbox'">
-                <div style="width: 120px;" class="mt-4 label text-left">{{ item.label }}</div>
-                <div :style="item.style">
-                  <v-checkbox
-                    v-model="item.value"
-                    v-for="k in item.items"
-                    :key="k.key"
-                    :label="k.label"
-                    :color="item.color"
-                    :value="k.value"
-                    :readonly="k.readonly"
-                    hide-details
-                    :multiple="true"
-                    class="mr-3"
-                  ></v-checkbox>
-                </div>
-              </template>
-              <v-file-input
-                v-if="item.type === 'upload'"
-                :prepend-icon="item.prependIcon || ''"
-                :append-icon="item.appendIcon"
-                :append-outer-icon="item.appendOuterIcon"
-                :show-size="item.showSize"
-                variant="outlined"
-                :density="item.dense || 'compact'"
-                v-model="item.value"
-                :placeholder="item.placeholder || item.label"
-                :hint="item.hint"
-                :rules="item.rules"
-                :label="item.label"
-                :persistent-hint="item.persistentHint"
-                :loading= "item.loading"
-                :disabled="item.disabled"
-                :multiple="item.multiple"
-                :success="item.success"
-                :error="item.error"
-                :accept="item.accept || '.xlsx, .xls, .csv, .pdf, .txt, .doc'"
-                @change="handleChange(item)"
-              >
-                <template v-if="item.selfAppend" #append>
-                  <slot :name="item.selfAppend" :data="item.value"></slot>
-                </template>
-              </v-file-input>
-              <v-color-picker
-                v-if="item.type === 'colorPicker'"
-                class="mb-5"
-                v-model="item.value"
-                :elevation="item.elevation || 5"
-                :dot-size="item.dotSize || 25"
-                :show-swatches="item.showSwatches || false"
-                swatches-max-height="200"
-                :mode="item.mode || 'hexa'"
-                :hide-mode-switch="true"
-                @input="item.change"
-              />
-              <template v-if="item.type === 'switch'">
-                <span v-if="item.describe"> {{ item.describe }} </span>
-                <span class="ml-2" v-if="item.trueLabel"> {{ item.trueLabel }}</span>
-                <v-switch
-                  dense hide-details class="mt-0 ml-2 pa-0"
-                  v-model="item.value"
-                  :label="item.label"
-                  :disabled="item.disabled || false"
-                  :color="item.color || 'primary'"
-                  :true-value="(item.trueValue !== undefined) ? item.trueValue : true"
-                  :false-value="(item.falseValue !== undefined) ? item.falseValue : false"
-                ></v-switch>
-                <span v-if="item.falseLabel"> {{ item.falseLabel }} </span>
-              </template>
-              <template v-if="item.type === 'date'">
-                <div class="d-flex" style="margin-bottom: 22px;">
-                  <span class="label d-flex align-center" :style="`width: ${item.width || 120}px;`">{{ item.label }}</span>
-                  <!-- <date-picker
-                    :is-valid="isValid"
-                    :option="{ ...item.option, disabled: item.disabled }"
-                    v-model="item.value"
-                    :style="item.style"
-                    @change="item.value = $event; handleChange(item); handleCheck(item)"></date-picker> -->
-                </div>
-              </template>
+              ></autocompleteUI>
               <template v-if="item.slotName">
-                  <slot :name="item.slotName" :item="item"></slot>
+                <slot :name="item.slotName" :item="item"></slot>
               </template>
             </div>
           </v-col>
@@ -243,40 +33,22 @@
     <slot></slot>
   </v-form>
 </template>
-
 <script setup>
-// import DatePicker from '@/components/Form/datePicker.vue'
-import { ref, defineEmits  } from 'vue'
-defineOptions({ name: 'components-ct-form' })
+defineOptions({ name:'components-ct-form'})
+import textUI from './../FormUI/TextInput'
+import autocompleteUI from './../FormUI/autocomplete'
+import { ref } from 'vue'
+const emit = defineEmits(['change', 'inputUpdateAutocomplete'])// 定义一个或多个自定义事件
 const props = defineProps({items: Object})
-const inputUpdateValue = ref('')
-const form = ref()
 const valid = ref(false)
 const isValid = ref(true)
-const emit = defineEmits(['inputUpdateAutocomplete', 'change'])
-
-const handleWheel = (event, item) => {
-  if (item.type !== 'number') return
-  event.preventDefault()
-  if (event.deltaY > 0) {
-    item.value--
-  } else {
-    item.value++
-  }
-  handleChange(item)
-}
+const formRef = ref()
 
-const handleCheck = (e) => {
-  if (e.type !== 'date' || e.hide || !e.rules) return
-  const rules = e.rules[0]
-  const check = rules(e.value)
-  if (typeof check === 'string') {
-    e.option.error = true
-    e.option.errorMsg = check
-    return
-  }
-  e.option.error = false
-  e.option.errorMsg = null
+const handleChange = (item) => {
+  console.log('handleChange', item)
+  if (item.type === 'date' && item.value) item.option.validate = false
+  if (item?.change) item.change(item.value, item)
+  emit('change', false)
 }
 
 const validateTime = () => {
@@ -294,32 +66,18 @@ const validateTime = () => {
       e.option.errorMsg = null
     }
   })
-  return isValid.value
 }
-
+  
 const validate = () => {
-  const form = form.value.validate()
+  console.log('点击调用子组件方法', 1)
+  const form = formRef.value.validate()
   const time = validateTime()
   return form && time
 }
-
-const inputUpdateAutocomplete = (val) => {
-  emit('inputUpdateAutocomplete', val)
-}
-
-const resetValidation = () => {
-  form.value.resetValidation()
-}
-const reset = () => {
-  form.value.reset()
-}
-const handleChange = (item) => {
-  if (item.type === 'date' && item.value) item.option.validate = false
-  if (item?.change) item.change(item.value, item)
-  emit('change', false)
-}
+defineExpose({
+  validate
+})
 </script>
-
 <style lang="scss" scoped>
 .position {
   position: relative;
@@ -328,4 +86,4 @@ const handleChange = (item) => {
   font-size: 14px;
   color: rgba(0, 0, 0, .6);
 }
-</style>
+</style>

+ 55 - 0
src/components/FormUI/TextInput/index.vue

@@ -0,0 +1,55 @@
+<template>
+  <div :style="{ width: item.width ? item.width + 'px' : '100%' }">
+    <v-text-field
+      v-model="value"
+      variant="outlined"
+      :density="item.dense || 'compact'"
+      :type="item.type"
+      :rules="item.rules"
+      :disabled="item.disabled"
+      :dense="item.dense"
+      :style="{width: item.width}"
+      :color="item.color"
+      :label="item.label"
+      :placeholder="item.placeholder || item.label"
+      :outlined="item.outlined"
+      :autofocus="item.autofocus"
+      :required="item.required"
+      :class="item.class"
+      :suffix="item.suffix"
+      :append-icon="item.appendIcon"
+      :clearable="item.clearable"
+      :readonly="item.readonly"
+      :prepend-inner-icon="item.prependInnerIcon"
+      hide-spin-buttons
+      @wheel="$event => handleWheel($event, item)"
+      @keyup.enter="item.keyupEnterNative && item.keyupEnterNative(index)"
+      @click="item.click && item.click(index)"
+      @click:append-inner="item.clickAppendInner"
+    ></v-text-field>
+  </div>
+</template>
+<script setup>
+import { computed } from 'vue';
+defineOptions({ name:'FormUI-v-text-field'})
+
+const props = defineProps({item: Object, modelValue: [String, Number]})
+const emit = defineEmits(['update:modelValue'])
+const item = props.item
+const value = computed({
+  get() { return props.modelValue },
+  set(value) { emit('update:modelValue', value) }
+})
+const handleWheel = (event, item) => {
+  if (item.type !== 'number') return
+  event.preventDefault()
+  if (event.deltaY > 0) {
+    item.value--
+  } else {
+    item.value++
+  }
+}
+</script>
+<style lang="scss" scoped>
+
+</style>

+ 39 - 0
src/components/FormUI/autocomplete/index.vue

@@ -0,0 +1,39 @@
+<template>
+  <div :style="{ width: item.width ? item.width + 'px' : '100%' }">
+    <v-autocomplete
+      v-model="value"
+      :rules="item.rules"
+      :attach="!item.noAttach"
+      :loading="item.loading"
+      :label="item.label"
+      :placeholder="item.placeholder || item.label"
+      :item-title="item.itemText || 'label'"
+      :item-value="item.itemValue || 'value'"
+      :items="item.items"
+      variant="outlined"
+      :density="item.dense || 'compact'"
+      :append-to-body="true"
+      :disabled="item.disabled"
+      :multiple="item.multiple"
+      :clearable="item.clearable"
+      :search="item.searchInput"
+      :hide-no-data="item.hideNoData"
+      :no-data-text="item.noDataText || 'No data available'"
+      :hide-selected="item.hideSelected"
+    ></v-autocomplete>
+  </div>
+</template>
+<script setup>
+import { computed } from 'vue';
+defineOptions({ name:'FormUI-v-autocomplete'})
+
+const props = defineProps({item: Object, modelValue: [String, Number]})
+const emit = defineEmits(['update:modelValue'])
+const item = props.item
+const value = computed({
+  get() { return props.modelValue },
+  set(value) { emit('update:modelValue', value) }
+})
+</script>
+<style lang="scss" scoped>
+</style>

+ 141 - 0
src/components/VerificationCode/index.vue

@@ -0,0 +1,141 @@
+<template>
+  <div>
+    <v-form @submit.prevent ref="phoneForm">
+      <v-text-field v-model="loginData.phone" placeholder="请输入手机号" color="#00897B" variant="outlined" density="compact" :rules="phoneRules" validate-on="input">
+        <template v-slot:prepend-inner>
+          <span class="d-flex">
+            <v-icon icon="mdi-cellphone" size="20"></v-icon>
+            <span class="d-flex" id="menu-activator">
+              <span class="phone-number">{{ currentArea }}</span>
+              <v-icon size="20">mdi-chevron-down</v-icon>
+            </span>
+            <v-menu activator="#menu-activator">
+              <v-list>
+                <v-list-item v-for="(item, index) in items" :key="index" :value="index" @click="handleChangeCurrentArea(item)">
+                  <v-list-item-title>{{ item.label }}</v-list-item-title>
+                </v-list-item>
+              </v-list>
+            </v-menu>
+          </span>
+        </template>
+      </v-text-field>
+      <v-text-field v-model="loginData.code" placeholder="请输入验证码" color="#00897B" variant="outlined" density="compact" prepend-inner-icon="mdi-security" :rules="[v=> !!v || '请填写验证码']">
+        <template #append-inner>
+          <span v-if="showCode" class="login-code" @click="handleCode">获取验证码</span>
+          <span v-else class="disable">重新获取{{ count }}s</span>
+        </template>
+      </v-text-field>
+    </v-form>
+    <v-snackbar v-model="tips.show" :color="tips.color" :timeout="1500" location="top">{{ tips.text }}</v-snackbar>
+  </div>
+</template>
+
+<script setup>
+defineOptions({ name: 'verification-code' })
+import { ref, reactive, defineExpose } from 'vue'
+import { setCodeTime } from '@/utils/code'
+import { sendSmsCode } from '@/api/common/index'
+
+const phoneRules = ref([
+  value => {
+    if (value) return true
+    return '请输入手机号'
+  },
+  value => {
+    if (value?.length <= 11 && /^1[3456789]\d{9}$/.test(value)) return true
+    return '请输入正确的手机号码'
+  }
+])
+
+const tips = reactive({
+  show: false,
+  color: '',
+  text: ''
+})
+
+// 手机号区域
+const currentArea = ref('0086')
+const items = [
+  { label: '中国大陆-0086', value: '0086' }
+]
+const handleChangeCurrentArea = (e) => {
+  currentArea.value = e.value
+}
+
+// 获取验证码
+const showCode = ref(true)
+const count = ref(0)
+const timer = ref(null)
+const handleCode = () => {
+  if (!loginData.phone) {
+    tips.text = '请输入手机号码'
+    tips.color = 'warning'
+    tips.show = true
+    return
+  }
+  count.value = 60
+  setTime()
+  getSmsCode()
+}
+const getSmsCode = async () => {
+  const query = {
+    phone: loginData.phone,
+    scene: 30
+  }
+  try {
+    await sendSmsCode(query)
+  } catch (error) {
+    console.log(error, 'error')
+  }
+}
+const setTime = () => {
+  showCode.value = false
+  timer.value = setInterval(() => {
+    let number = count.value
+    if (number > 0 && number <= 60) {
+      count.value--
+      setCodeTime(number - 1)
+    } else {
+      showCode.value = true
+      clearInterval(timer.value)
+      timer.value = null
+    }
+  }, 1000)
+}
+const autoTimer = () => {
+  count.value = 0
+  if(!count.value) return
+  setTime()
+}
+autoTimer()
+
+const loginData = reactive({
+  phone: '13229740091',
+  code: ''
+})
+
+const phoneForm = ref()
+
+defineExpose({
+  loginData, 
+  phoneForm
+})
+</script>
+
+<style lang="scss" scoped>
+.login-code {
+  width: 62px;
+  color: var(--v-primary-base); 
+  font-size: 12px; 
+  cursor: pointer;
+}
+.disable {
+  width: 72px;
+  color: grey;
+  font-size: 12px;
+}
+.phone-number {
+  width: 34px;
+  font-size: 12px;
+}
+</style>

+ 28 - 0
src/layout/index.vue

@@ -0,0 +1,28 @@
+<template>
+  <div class="parent">
+    <Headers></Headers>
+    <div>
+      <router-view></router-view>
+    </div>
+    <!-- <Footers class="footer"></Footers> -->
+  </div>
+</template>
+
+<script setup>
+import Headers from './personal/navBar.vue'
+// import Footers from './personal/footer.vue'
+defineOptions({ name: 'layout-index' })
+</script>
+
+<style lang="scss" scoped>
+.parent {
+  background-color: #eaf4fe;
+  height: 100vh;
+  position: relative;
+}
+.footer {
+  position: absolute;
+  bottom: 0;
+  left: 0;
+}
+</style>

+ 75 - 0
src/layout/personal/footer.vue

@@ -0,0 +1,75 @@
+<template>
+  <div class="box">
+    <div class="top wid d-flex justify-space-between">
+      <div class="left">
+        <h4>联系我们</h4>
+        <div class="mt-5 size second">
+          <div>广州辞图科技有限公司</div>
+          <div class="my-3">公司地址&nbsp;先烈中路100号大院8栋203室</div>
+          <div>服务热线/举报渠道&nbsp;4000000xxx</div>
+        </div>
+      </div>
+      <div class="center">
+        <h4>使用与帮助</h4>
+        <div class="mt-5 size second">
+          <div>协议与规则</div>
+          <div class="my-3">隐私协议</div>
+          <div>使用与帮助</div>
+        </div>
+      </div>
+      <div class="right size d-flex">
+        <div>
+          <v-img :width="100" cover aspect-ratio="16/9" src="https://minio.citupro.com/dev/static/qrcode.png" style="height: 100px;"></v-img>
+          <div class="mt-2" style="text-align: center;">微信公众号</div>
+        </div>
+        <div class="ml-5">
+          <v-img :width="100" cover aspect-ratio="16/9" src="https://minio.citupro.com/dev/static/emw.png" style="height: 100px;"></v-img>
+          <div class="mt-2" style="text-align: center;">小程序</div>
+        </div>
+      </div>
+    </div>
+    <div class="bottom wid mt-7">
+      <span class="size mr-7 second" v-for="(item, i) in list" :key="i">{{ item.label }}</span>
+    </div>
+  </div>
+</template>
+
+<script setup>
+defineOptions({ name: 'personal-footer' })
+const list = [
+  { label: 'Copyright © 2024招聘人才网', path: '' },
+  { label: '粤ICP备xxxx号-x', path: '' },
+  { label: '粤ICP备xxxx号-x', path: '' },
+  { label: '电子营业执照', path: '', img: '' },
+  { label: '粤公网安管 10110110110', path: '', img: '' },
+  { label: '人力资源服务许可证', path: '' }
+]
+</script>
+
+<style scoped lang="scss">
+.box {
+  width: 100%;
+  height: 225px;
+  color: #fff;
+  background-color: #313438;
+  padding: 30px 0;
+}
+.wid {
+  width: 1184px;
+  max-width: 1184px;
+  margin: auto;
+}
+.size {
+  color: #ffffff80;
+  font-size: 12px;
+}
+.second {
+  cursor: pointer;
+}
+.bottom span:hover {
+  color: var(--v-primary-base);
+}
+.second div:hover {
+  color: var(--v-primary-base);
+}
+</style>

+ 119 - 0
src/layout/personal/navBar.vue

@@ -0,0 +1,119 @@
+<template>
+  <div>
+    <div
+      class="banner"
+      density="compact"
+      style="padding-left: 0px;background-color: #fff;height: 50px;font-size: 14px;"
+    >
+      <div class="innerBox">
+        <div class="nav-logo">
+          <v-img src="../../assets/logo.png"  aspect-ratio="16/9" cover :width="90" style="height: 40px"></v-img>
+        </div>
+        <div class="nav-city">
+          <p class="nav-city-box">
+            <v-icon color="#00897B">mdi-map-marker</v-icon>
+            <span class="nav-city-selected">广州</span>
+            <span class="switchover-city nav-city-selected">[切换城市]</span>
+          </p>
+        </div>
+        <div class="nav">
+          <ul>
+            <li v-for="k in list" :key="k.text" class="mt-1">
+              <a :href="k.path" style="font-size: 14px;">{{ k.text }}</a>
+            </li>
+          </ul>
+        </div>
+        <div class="user-nav" v-if="!getToken()">
+          <div class="btns">
+            <span class="nav-resume-tools">
+              <a href="">我要找工作</a>
+              <a href="">我要招聘</a>
+            </span>
+            <v-btn class="half-button" color="primary" size="small" @click="handleLogin">登录/注册</v-btn>
+          </div>
+        </div>
+        <div v-else class="d-flex user-nav">
+          <span style="cursor: pointer;">消息</span>
+          <v-menu open-on-hover>
+            <template v-slot:activator="{ props }">
+              <div class="d-flex ml-5 align-center" v-bind="props">
+                <v-avatar>
+                  <v-img alt="John" src="https://cdn.vuetifyjs.com/images/john.jpg"></v-img>
+                </v-avatar>
+                <div class="ml-2">游客</div>
+              </div>
+            </template>
+
+            <v-list>
+              <v-list-item v-for="(item, index) in items" :key="index" @click="item.change">
+                <v-list-item-title>{{ item.title }}</v-list-item-title>
+              </v-list-item>
+            </v-list>
+          </v-menu>
+        </div>
+      </div>
+    </div>
+    <Dialog :visible="show" :footer="true" title="企业注册" widthType="1" @close="show = false">
+      <enterpriseRegister ref="enterpriseRegisterRef"></enterpriseRegister>
+    </Dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import { getToken } from '@/utils/auth'
+import { userLocaleStore } from '@/store/user'
+defineOptions({ name: 'personal-navbar' })
+import Dialog from '@/components/CtDialog'
+import enterpriseRegister from '@/views/enterprise/components/register.vue'
+defineProps({
+  sticky: {
+    type: Boolean,
+    default: true
+  }
+})
+const list = ref([
+  { text: '首页', path: '/home' },
+  { text: '职位', path: '' },
+  { text: '公司', path: '' }
+])
+
+import { useRouter } from 'vue-router'
+const router = useRouter()
+
+const userType = ref(1) // 0企业用户、1个人用户
+const show = ref(false)
+const changeRole = () => {
+  if (userType.value) {
+    show.value = true
+  } else {
+    router.push({ path: '/' })
+  }
+}
+
+// 退出登录
+const userStore = userLocaleStore()
+const handleLogout = async () => {
+  try {
+    await userStore.userLogout()
+    router.push({ path: '/login' })
+  } catch (error) {
+    console.log(error, 'error')
+  }
+}
+
+const items = ref([
+  { title: '账号设置', change: () => router.push({ path: '/personalAccount/accountBinding' }) },
+  { title: '切换为招聘者', change: changeRole },
+  { title: '退出登录', change: handleLogout }
+])
+
+const handleLogin = () => {
+  router.push({ path: '/login' })
+}
+
+</script>
+
+<style lang="scss" scoped>
+@import '@/styles/personal/navBar.scss'
+</style>

+ 1 - 1
src/locales/en.js

@@ -5,7 +5,7 @@ export default {
     password: 'Password',
     login: 'Sign in',
     reLogin: 'Sign in again',
-    register: 'Register',
+    register: 'Sign in or Register',
     checkPassword: 'Confirm password',
     mobileNumber: 'Mobile Number',
     mobileNumberPlaceholder: 'Please Enter Mobile Number',

+ 1 - 1
src/locales/zh-CN.js

@@ -5,7 +5,7 @@ export default {
     password: '密码',
     login: '登录',
     reLogin: '重新登录',
-    register: '注册',
+    register: '登录/注册',
     checkPassword: '确认密码',
     mobileNumber: '手机号码',
     mobileNumberPlaceholder: '请输入手机号码',

+ 47 - 1
src/router/modules/personal.js

@@ -1,4 +1,50 @@
 // 个人路由信息
 
-const personal = []
+import Layout from '@/layout'
+const personal = [
+  {
+    path: '/personalAccount',
+    component: Layout,
+    name: 'personalAccount',
+    children: [
+      {
+        path: '/personalAccount',
+        component: () => import('@/views/Home/personal/account/index'),
+        meta: {
+          title: '账号设置'
+        },
+        children: [
+          {
+            path: '/personalAccount/editPassword',
+            component: () => import('@/views/Home/personal/account/dynamic/editPassword'),
+            meta: {
+              title: '修改密码'
+            }
+          },
+          {
+            path: '/personalAccount/accountBinding',
+            component: () => import('@/views/Home/personal/account/dynamic/accountBinding'),
+            meta: {
+              title: '账号绑定'
+            }
+          },
+          {
+            path: '/personalAccount/realAuthentication',
+            component: () => import('@/views/Home/personal/account/dynamic/realAuthentication'),
+            meta: {
+              title: '实名认证'
+            }
+          },
+          {
+            path: '/personalAccount/privacySettings',
+            component: () => import('@/views/Home/personal/account/dynamic/privacySettings'),
+            meta: {
+              title: '隐私设置'
+            }
+          }
+        ]
+      }
+    ]
+  }
+]
 export default personal

+ 26 - 8
src/router/modules/remaining.js

@@ -1,5 +1,6 @@
 import personal from './personal'
 import enterprise from './enterprise'
+import Layout from '@/layout'
 
 const type  = 0
 const routeArray = [
@@ -8,8 +9,12 @@ const routeArray = [
 ]
 const items = routeArray[type]
 const remainingRouter = [
+  // {
+  //   path: '',
+  //   redirect: '/home'
+  // },
   {
-    path: '/',
+    path: '/login',
     component: () => import('@/views/login/index'),
     name: 'login',
     meta: {
@@ -35,14 +40,27 @@ const remainingRouter = [
       title: '隐私政策'
     }
   },
+  // {
+  //   path: '/home',
+  //   component: () => import('@/views/Home/index'),
+  //   name: 'home',
+  //   meta: {
+  //     hidden: true,
+  //     title: '首页'
+  //   }
+  // },
   {
-    path: '/home',
-    component: () => import('@/views/Home/index'),
-    name: 'home',
-    meta: {
-      hidden: true,
-      title: '首页'
-    }
+    path: '',
+    component: Layout,
+    children: [
+      {
+        path: '/home',
+        component: () => import('@/views/Home/index'),
+        meta: {
+          title: '首页'
+        }
+      }
+    ]
   },
   {
     path: '/register',

+ 52 - 9
src/store/user.js

@@ -1,29 +1,72 @@
 import { defineStore } from 'pinia'
 import { reactive } from 'vue'
-import { setToken } from '@/utils/auth'
-import { smsLogin } from '@/api/common/index'
-import { useRouter } from 'vue-router'
+import { setToken, deleteToken } from '@/utils/auth'
+import { smsLogin, passwordLogin } from '@/api/common/index'
+import { logout } from '@/api/common/index'
+import { getUserInfo } from '@/api/personal/user'
 
 export const userLocaleStore = defineStore('user',
   () => {
-    let userInfo = reactive({})
-    const handleLogin = async (data) => {
-      console.log(data, 'login-params')
+    let accountInfo = reactive({}) // 登录返回的信息
+    let userInfo = reactive({}) // 当前登录账号信息
+
+    // 短信登录
+    const handleSmsLogin = async (data) => {
       return new Promise((resolve, reject) => {
         smsLogin(data).then(res => {
           const { data } = res
           console.log(data, 'res-login')
           setToken(data.accessToken)
-          userInfo = data
+          accountInfo = data
+          localStorage.setItem('accountInfo', JSON.stringify(data))
+          localStorage.setItem('expiresTime', data.expiresTime) // token过期时间
+          getUserInfos()
+          resolve()
+        }).catch(err => { reject(err) })
+      })
+    }
+
+    // 密码登录
+    const handlePasswordLogin = async (data) => {
+      return new Promise((resolve, reject) => {
+        passwordLogin(data).then(res => {
+          const { data } = res
+          console.log(data, 'res-login-password')
+          setToken(data.accessToken)
+          accountInfo = data
+          localStorage.setItem('accountInfo', JSON.stringify(data))
           localStorage.setItem('expiresTime', data.expiresTime) // token过期时间
-          useRouter().push('/home')
+          getUserInfos()
           resolve()
         }).catch(err => { reject(err) })
       })
     }
+
+    // 获取当前登录账户信息
+    const getUserInfos = async () => {
+      try {
+        const { data } = await getUserInfo({ id: accountInfo.userId })
+        userInfo = data
+        localStorage.setItem('userInfo', JSON.stringify(data))
+      } catch (error) {
+        console.log(error, 'get-user-info')
+        alert(error.msg)
+      }
+    }
+
+    // 退出登录
+    const userLogout = async () => {
+      await logout()
+      deleteToken()
+      userInfo = {}
+      accountInfo = {}
+      localStorage.clear()
+    }
     return {
       userInfo,
-      handleLogin
+      handleSmsLogin,
+      userLogout,
+      handlePasswordLogin
     }
   },
   {

+ 24 - 1
src/styles/index.css

@@ -1,4 +1,27 @@
 :root {
   --zIndex-dialog: 9999;
-  --default-color: #00897B;
+  --v-primary-base: #00897B;
+  --v-primary-lighten1: #26A69A;
+  --v-primary-lighten2: #4DB6AC;
+  --v-primary-lighten3: #80CBC4;
+  --v-primary-lighten4: #B2DFDB;
+  --default-text: #666;
+}
+
+.buttons {
+  height: 36px;
+  width: 224px;
+}
+
+.half-button {
+  height: 36px;
+  width: 88px;
+}
+
+.default-width {
+  width: 1184px;
+  min-width: 1184px;
+  max-width: 1184px;
+  margin: 0 auto;
+  align-items: center;
 }

+ 1 - 1
src/styles/index.min.css

@@ -1 +1 @@
-:root{--zIndex-dialog:9999;--default-color:#00897B}
+:root{--zIndex-dialog:9999;--v-primary-base:#00897B;--v-primary-lighten1:#26A69A;--v-primary-lighten2:#4DB6AC;--v-primary-lighten3:#80CBC4;--v-primary-lighten4:#B2DFDB;--default-text:#666}.buttons{height:36px;width:224px}.half-button{height:36px;width:88px}.default-width{width:1184px;min-width:1184px;max-width:1184px;margin:0 auto;align-items:center}

+ 24 - 1
src/styles/index.scss

@@ -1,4 +1,27 @@
 :root {
   --zIndex-dialog: 9999;
-  --default-color: #00897B;
+  --v-primary-base: #00897B;
+  --v-primary-lighten1: #26A69A;
+  --v-primary-lighten2: #4DB6AC;
+  --v-primary-lighten3: #80CBC4;
+  --v-primary-lighten4: #B2DFDB;
+  --default-text: #666;
+}
+// 长按钮
+.buttons {
+  height: 36px;
+  width: 224px;
+}
+// 短按钮
+.half-button {
+  height: 36px;
+  width: 88px;
+}
+// 默认宽度
+.default-width {
+  width: 1184px;
+  min-width: 1184px;
+  max-width: 1184px;
+  margin: 0 auto;
+  align-items: center;
 }

+ 155 - 0
src/styles/personal/navBar.css

@@ -0,0 +1,155 @@
+.header-login-btn {
+  position: relative;
+}
+
+.header-login-btn:hover .header-login-desc {
+  display: block;
+}
+
+.header-login-btn:hover:after {
+  content: " ";
+  position: absolute;
+  left: 0;
+  right: 0;
+  bottom: -12px;
+  height: 12px;
+  z-index: 1;
+  background: transparent;
+}
+
+.user-nav .header-login-btn {
+  vertical-align: middle;
+}
+
+.user-nav .nav-resume-tools {
+  display: inline-block;
+  vertical-align: middle;
+}
+
+.user-nav .nav-resume-tools > a {
+  display: inline-block;
+  font-weight: 500;
+  color: var(--v-primary-base);
+  line-height: 20px;
+  height: auto;
+  padding: 3px 7px;
+  vertical-align: middle;
+  border-radius: 4px;
+  margin-right: 8px;
+  border: 1px solid transparent;
+}
+
+.banner {
+  z-index: var(--zIndex-nav) !important;
+  color: #fff;
+}
+
+.banner .left {
+  height: 100%;
+  display: flex;
+  align-items: center;
+  font-size: 20px;
+  cursor: pointer;
+}
+
+.hover:hover {
+  cursor: pointer;
+  background: rgba(0, 0, 0, 0.03);
+}
+
+.innerBox {
+  position: relative;
+  width: 1184px;
+  max-width: 1184px;
+  margin: 0 auto;
+  align-items: center;
+}
+
+.nav-logo {
+  float: left;
+}
+
+.nav-city {
+  float: left;
+  position: relative;
+  cursor: pointer;
+  height: 49px;
+  line-height: 49px;
+  color: var(--v-primary-base);
+  margin-left: 50px;
+}
+
+.nav-city-selected {
+  display: inline-block;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  max-width: 84px;
+  vertical-align: middle;
+}
+
+.switchover-city {
+  color: var(--v-primary-base);
+  margin-left: 10px;
+}
+
+.nav {
+  font-size: 0;
+  float: left;
+  margin-left: 50px;
+  height: 49px;
+  line-height: 49px;
+}
+
+.nav ul {
+  display: flex;
+  white-space: nowrap;
+  vertical-align: top;
+}
+
+.nav ul li {
+  text-align: center;
+  font-size: 14px;
+  margin: 0 5px;
+  list-style: none;
+}
+
+.nav li a {
+  display: block;
+  padding: 0 5px;
+  position: relative;
+  text-decoration: none;
+  color: var(--v-primary-base);
+}
+
+.user-nav {
+  position: absolute;
+  right: 0;
+  color: var(--v-primary-base);
+  height: 49px;
+  line-height: 49px;
+}
+
+.user-nav .btns .btn-outline {
+  border-radius: 8px;
+  text-align: center;
+  font-size: 14px;
+  height: 26px;
+  line-height: 26px;
+  min-width: 0;
+  padding: 0 11px;
+  margin-left: 14px;
+  background-color: transparent;
+}
+
+.user-nav .btn {
+  display: inline-block;
+  box-sizing: content-box;
+  border: 1px solid #00897B;
+  letter-spacing: 1px;
+  cursor: pointer;
+}
+
+.user-nav a {
+  text-decoration: none;
+}

+ 1 - 0
src/styles/personal/navBar.min.css

@@ -0,0 +1 @@
+.header-login-btn{position:relative}.header-login-btn:hover .header-login-desc{display:block}.header-login-btn:hover:after{content:" ";position:absolute;left:0;right:0;bottom:-12px;height:12px;z-index:1;background:transparent}.user-nav .header-login-btn{vertical-align:middle}.user-nav .nav-resume-tools{display:inline-block;vertical-align:middle}.user-nav .nav-resume-tools>a{display:inline-block;font-weight:500;color:var(--v-primary-base);line-height:20px;height:auto;padding:3px 7px;vertical-align:middle;border-radius:4px;margin-right:8px;border:1px solid transparent}.banner{z-index:var(--zIndex-nav) !important;color:#fff}.banner .left{height:100%;display:flex;align-items:center;font-size:20px;cursor:pointer}.hover:hover{cursor:pointer;background:rgba(0,0,0,0.03)}.innerBox{position:relative;width:1184px;max-width:1184px;margin:0 auto;align-items:center}.nav-logo{float:left}.nav-city{float:left;position:relative;cursor:pointer;height:49px;line-height:49px;color:var(--v-primary-base);margin-left:50px}.nav-city-selected{display:inline-block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:84px;vertical-align:middle}.switchover-city{color:var(--v-primary-base);margin-left:10px}.nav{font-size:0;float:left;margin-left:50px;height:49px;line-height:49px}.nav ul{display:flex;white-space:nowrap;vertical-align:top}.nav ul li{text-align:center;font-size:14px;margin:0 5px;list-style:none}.nav li a{display:block;padding:0 5px;position:relative;text-decoration:none;color:var(--v-primary-base)}.user-nav{position:absolute;right:0;color:var(--v-primary-base);height:49px;line-height:49px}.user-nav .btns .btn-outline{border-radius:8px;text-align:center;font-size:14px;height:26px;line-height:26px;min-width:0;padding:0 11px;margin-left:14px;background-color:transparent}.user-nav .btn{display:inline-block;box-sizing:content-box;border:1px solid #00897B;letter-spacing:1px;cursor:pointer}.user-nav a{text-decoration:none}

+ 136 - 0
src/styles/personal/navBar.scss

@@ -0,0 +1,136 @@
+.header-login-btn {
+  position: relative;
+}
+.header-login-btn:hover .header-login-desc {
+  display: block;
+}
+.header-login-btn:hover:after {
+  content: " ";
+  position: absolute;
+  left: 0;
+  right: 0;
+  bottom: -12px;
+  height: 12px;
+  z-index: 1;
+  background: transparent;
+}
+.user-nav .header-login-btn {
+  vertical-align: middle;
+}
+.user-nav .nav-resume-tools {
+  display: inline-block;
+  vertical-align: middle;
+}
+.user-nav .nav-resume-tools > a {
+  display: inline-block;
+  font-weight: 500;
+  color: var(--v-primary-base);
+  line-height: 20px;
+  height: auto;
+  padding: 3px 7px;
+  vertical-align: middle;
+  border-radius: 4px;
+  margin-right: 8px;
+  border: 1px solid transparent;
+}
+.banner {
+  z-index: var(--zIndex-nav) !important;
+  color: #fff;
+  .left {
+    height: 100%;
+    display: flex;
+    align-items: center;
+    font-size: 20px;
+    cursor: pointer;
+  }
+}
+.hover:hover {
+  cursor: pointer;
+  background: rgba(0, 0, 0, 0.03);
+}
+.innerBox {
+  position: relative;
+  width: 1184px;
+  max-width: 1184px;
+  margin: 0 auto;
+  align-items: center;
+}
+.nav-logo {
+  float: left;
+}
+.nav-city {
+  float: left;
+  position: relative;
+  cursor: pointer;
+  height: 49px;
+  line-height: 49px;
+  color: var(--v-primary-base);
+  margin-left: 50px;
+}
+.nav-city-selected {
+  display: inline-block;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  max-width: 84px;
+  vertical-align: middle;
+}
+.switchover-city {
+  // font-size: 12px;
+  color: var(--v-primary-base);
+  margin-left: 10px;
+}
+.nav {
+  font-size: 0;
+  float: left;
+  margin-left: 50px;
+  height: 49px;
+  line-height: 49px;
+}
+.nav ul {
+  display: flex;
+  white-space: nowrap;
+  vertical-align: top;
+}
+.nav ul li {
+  text-align: center;
+  font-size: 14px;
+  margin: 0 5px;
+  list-style: none;
+}
+.nav li a {
+  display: block;
+  padding: 0 5px;
+  position: relative;
+  text-decoration: none;
+  color: var(--v-primary-base);
+}
+.user-nav {
+  position: absolute;
+  right: 0;
+  // display: inline-block;
+  color: var(--v-primary-base);
+  height: 49px;
+  line-height: 49px;
+}
+.user-nav .btns .btn-outline {
+  border-radius: 8px;
+  text-align: center;
+  font-size: 14px;
+  height: 26px;
+  line-height: 26px;
+  min-width: 0;
+  padding: 0 11px;
+  margin-left: 14px;
+  background-color: transparent;
+}
+.user-nav .btn {
+  display: inline-block;
+  box-sizing: content-box;
+  border: 1px solid #00897B;
+  letter-spacing: 1px;
+  cursor: pointer;
+}
+.user-nav a {
+  text-decoration: none;
+}

+ 2 - 2
src/styles/protocol/index.css

@@ -58,7 +58,7 @@ del, ins, u, s, a, a:hover {
 .large {
   width: 100%;
   height: 1px;
-  background-color: var(--default-color);
+  background-color: var(--v-primary-base);
 }
 
 .Protocol {
@@ -87,7 +87,7 @@ del, ins, u, s, a, a:hover {
 }
 
 .subtitle, .crosshead {
-  color: var(--default-color);
+  color: var(--v-primary-base);
   font-size: 14px;
   line-height: 30px;
 }

+ 1 - 1
src/styles/protocol/index.min.css

@@ -1 +1 @@
-html,body,h1,h2,h3,h4,h5,h6,div,dl,dt,dd,ul,ol,li,p,blockquote,pre,hr,figure,table,caption,th,td,form,fieldset,legend,input,button,textarea,menu{margin:0;padding:0}header,footer,section,article,aside,nav,hgroup,address,figure,figcaption,menu,details{display:block}table{border-collapse:collapse;border-spacing:0}caption,th{text-align:left;font-weight:normal}html,body,fieldset,img,iframe,abbr{border:0}i,cite,em,var,address,dfn{font-style:normal}[hidefocus],summary{outline:0}li{list-style:none}h1,h2,h3,h4,h5,h6,small{font-size:100%}a,button{cursor:pointer}h1,h2,h3,h4,h5,h6,em,strong,b{font-weight:bold}del,ins,u,s,a,a:hover{text-decoration:none}.conter,.conttle{width:100%;overflow:hidden}.large{width:100%;height:1px;background-color:var(--default-color)}.Protocol{width:1040px;margin:0 auto;background-color:#fff;padding:40px 40px 90px;box-sizing:border-box;font-family:"微软雅黑"}.Protocol p{color:#333333;font-size:14px;line-height:30px}.segment{width:100%;font-size:22px;color:#333333;margin-top:45px;margin-bottom:32px;font-weight:700;text-align:center}.subtitle,.crosshead{color:var(--default-color);font-size:14px;line-height:30px}.crosshead{color:#333333}.text-color{margin-top:0px;margin-bottom:0px;padding:0px;color:#333;font-size:14px;line-height:30px;font-family:微软雅黑;white-space:normal}.text-font{font-family:宋体, SimSun}.text-size{margin:0px;padding:0px;font-size:14px;color:#00897B;line-height:30px;font-family:微软雅黑;white-space:normal}.text-p{text-align:justify;font-family:Calibri;font-size:14px;white-space:normal}
+html,body,h1,h2,h3,h4,h5,h6,div,dl,dt,dd,ul,ol,li,p,blockquote,pre,hr,figure,table,caption,th,td,form,fieldset,legend,input,button,textarea,menu{margin:0;padding:0}header,footer,section,article,aside,nav,hgroup,address,figure,figcaption,menu,details{display:block}table{border-collapse:collapse;border-spacing:0}caption,th{text-align:left;font-weight:normal}html,body,fieldset,img,iframe,abbr{border:0}i,cite,em,var,address,dfn{font-style:normal}[hidefocus],summary{outline:0}li{list-style:none}h1,h2,h3,h4,h5,h6,small{font-size:100%}a,button{cursor:pointer}h1,h2,h3,h4,h5,h6,em,strong,b{font-weight:bold}del,ins,u,s,a,a:hover{text-decoration:none}.conter,.conttle{width:100%;overflow:hidden}.large{width:100%;height:1px;background-color:var(--v-primary-base)}.Protocol{width:1040px;margin:0 auto;background-color:#fff;padding:40px 40px 90px;box-sizing:border-box;font-family:"微软雅黑"}.Protocol p{color:#333333;font-size:14px;line-height:30px}.segment{width:100%;font-size:22px;color:#333333;margin-top:45px;margin-bottom:32px;font-weight:700;text-align:center}.subtitle,.crosshead{color:var(--v-primary-base);font-size:14px;line-height:30px}.crosshead{color:#333333}.text-color{margin-top:0px;margin-bottom:0px;padding:0px;color:#333;font-size:14px;line-height:30px;font-family:微软雅黑;white-space:normal}.text-font{font-family:宋体, SimSun}.text-size{margin:0px;padding:0px;font-size:14px;color:#00897B;line-height:30px;font-family:微软雅黑;white-space:normal}.text-p{text-align:justify;font-family:Calibri;font-size:14px;white-space:normal}

+ 2 - 2
src/styles/protocol/index.scss

@@ -56,7 +56,7 @@ del,ins,u,s,a,a:hover {
 .large {
   width: 100%;
   height: 1px;
-  background-color: var(--default-color);
+  background-color: var(--v-primary-base);
 }
 
 .Protocol {
@@ -85,7 +85,7 @@ del,ins,u,s,a,a:hover {
 }
 
 .subtitle,.crosshead {
-  color: var(--default-color);
+  color: var(--v-primary-base);
   font-size: 14px;
   line-height: 30px;
 }

+ 12 - 1
src/utils/request.js

@@ -16,6 +16,7 @@ service.interceptors.request.use(
     config.headers['tenant-id'] = import.meta.env.VITE_TENANTCODE
     if (getToken()) {
       config.headers.token = `Bearer ${getToken()}`
+      config.headers.Authorization = `Bearer ${getToken()}`
     }
     config.headers.token = `Bearer ${getToken()}`
     return config
@@ -56,7 +57,7 @@ service.interceptors.response.use(
     // if (res.code === 60902) {
     //   return Promise.reject(res)
     // }
-    if (res.code !== 20000) {
+    if (res.code !== 0) {
       if (res.data) {
         return Promise.reject(res)
       }
@@ -90,6 +91,16 @@ const http = {
       }
     })
   },
+  put(url, params) {
+    return service.put(url, params, {
+      transformRequest: [(params) => {
+        return JSON.stringify(params)
+      }],
+      headers: {
+        'Content-Type': 'application/json'
+      }
+    })
+  },
   formData (url, params) {
     return service.post(url, params, {
       timeout: 600000,

+ 0 - 31
src/views/Home/components/personal.vue

@@ -1,31 +0,0 @@
-<template>
-  <div>
-    <div>个人用户首页</div>
-    <v-btn color="primary" class="white--text mt-2" min-width="298" @click="changeRole">切换为企业用户</v-btn>
-    <!-- 加入公司/新建公司 -->
-    <Dialog :visible="show" :footer="true" title="企业注册" widthType="1">
-      <enterpriseRegister ref="enterpriseRegisterRef"></enterpriseRegister>
-    </Dialog>
-  </div>
-</template>
-<script setup>
-import Dialog from '@/components/CtDialog'
-import enterpriseRegister from '@/views/enterprise/components/register.vue'
-import { ref } from 'vue';
-defineOptions({ name:'personal-index'})
-import { useRouter } from 'vue-router'
-const router = useRouter()
-
-const userType = ref(1) // 0企业用户、1个人用户
-const show = ref(false)
-const changeRole = () => {
-  if (userType.value) {
-    show.value = true
-  } else {
-    router.push({ path: '/' })
-  }
-}
-</script>
-<style lang="scss" scoped>
-
-</style>

+ 0 - 0
src/views/Home/components/enterprise.vue → src/views/Home/enterprise/index.vue


+ 2 - 2
src/views/Home/index.vue

@@ -7,8 +7,8 @@
 <script setup>
 defineOptions({ name:'home-index'})
 import { ref } from 'vue';
-import enterprise from './components/enterprise.vue'
-import personal from './components/personal.vue'
+import enterprise from './enterprise/index.vue'
+import personal from './personal/index.vue'
 const type = ref(0)
 </script>
 <style lang="scss" scoped>

+ 38 - 0
src/views/Home/personal/account/dynamic/accountBinding.vue

@@ -0,0 +1,38 @@
+<template>
+  <div>
+    <h3>账号绑定</h3>
+    <v-divider class="mb-4"></v-divider>
+    <div>
+      <div class="login-user">当前登录账号: <span>{{ userInfo.phone }}</span> <span class="activeText ml-3">改绑手机号</span></div>
+      <div class="tips mt-2 mb-1">账号即绑定的手机号,用于使用账号密码或获取验证码进行登录。</div>
+    </div>
+  </div>
+</template>
+
+<script setup name="accountBinding">
+// 当前登录的用户信息
+const userInfo = JSON.parse(localStorage.getItem('userInfo'))
+</script>
+
+<style lang="scss" scoped>
+h3 {
+  font-size: 20px;
+  text-align: left;
+  font-weight: 600;
+  padding-bottom: 25px;
+}
+.activeText {
+  font-size: 17px;
+  color: var(--v-primary-base);
+  cursor: pointer;
+}
+.tips {
+  color: grey;
+  font-size: 12px;
+  font-weight: 600;
+}
+.login-user {
+  color: grey;
+  font-weight: 600;
+}
+</style>

+ 90 - 0
src/views/Home/personal/account/dynamic/editPassword.vue

@@ -0,0 +1,90 @@
+<template>
+  <div>
+    <h3>修改密码</h3>
+    <v-divider class="mb-4"></v-divider>
+    <div class="login-user mb-4">当前登录账号: <span style="color: var(--v-primary-base);">{{ userInfo.phone }}</span></div>
+    <div style="width: 300px;">
+      <PhonePage ref="phoneRef"></PhonePage>
+      <v-form ref="passwordRef">
+        <v-text-field
+          v-model="query.password"
+          placeholder="请输入密码" 
+          variant="outlined" 
+          density="compact"
+          color="#00897B"
+          prepend-inner-icon="mdi-lock-outline" 
+          :append-inner-icon="passwordType ? 'mdi-eye-outline' : 'mdi-eye-off-outline'"
+          :type="passwordType ? 'text' : 'password'"
+          :rules="[v=> !!v || '请填写密码']"
+          @click:append-inner="passwordType = !passwordType"
+        ></v-text-field>
+        <v-text-field
+          v-model="query.checkPassword"
+          placeholder="请再次输入密码" 
+          variant="outlined" 
+          density="compact"
+          color="#00897B"
+          prepend-inner-icon="mdi-lock-outline" 
+          :append-inner-icon="show ? 'mdi-eye-outline' : 'mdi-eye-off-outline'"
+          :type="show ? 'text' : 'password'"
+          :rules="[v=> !!v || '请再次输入密码', passwordCheck]"
+          @click:append-inner="show = !show"
+        ></v-text-field>
+      </v-form>
+      <div class="text-center">
+        <v-btn class="buttons" color="primary" @click="handleSubmit" :loading="loading">确认密码</v-btn>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup name="editPassword">
+import PhonePage from '@/components/VerificationCode'
+import { updatePassword } from '@/api/common/index'
+import { ref, reactive, computed } from 'vue'
+
+const phoneRef = ref()
+const passwordRef = ref()
+const passwordType = ref(false)
+const show = ref(false)
+const loading = ref(false)
+const query = reactive({
+  password: '',
+  checkPassword: ''
+})
+const passwordCheck = computed(() => {
+  return query.checkPassword === query.password || '两次密码输入不一致'
+})
+
+// 当前登录的用户信息
+const userInfo = JSON.parse(localStorage.getItem('userInfo'))
+
+const handleSubmit = async () => {
+  const { valid: phoneValid } = await phoneRef.value.phoneForm.validate()
+  const { valid: passValid} = await passwordRef.value.validate()
+  if (!phoneValid || !passValid) return
+  loading.value = true
+  try {
+    await updatePassword({ password: query.password, code: phoneRef.value.loginData.code })
+    console.log('success')
+  } catch (error) {
+    console.log(error, 'error')
+    alert(error.msg)
+  } finally {
+    loading.value = false
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+h3 {
+  font-size: 20px;
+  text-align: left;
+  font-weight: 600;
+  padding-bottom: 25px;
+}
+.login-user {
+  color: var(--default-text);
+  font-weight: 600;
+}
+</style>

+ 41 - 0
src/views/Home/personal/account/dynamic/privacySettings.vue

@@ -0,0 +1,41 @@
+<template>
+  <div>
+    <h3>隐私设置</h3>
+    <v-divider class="mb-4"></v-divider>
+    <v-radio-group v-model="radios">
+      <v-radio v-for="k in items" :key="k.value" :label="k.label" :value="k.value" color="primary">
+        <template v-slot:label>
+          <div class="radio-text">{{ k.label }} <span class="radio-desc">{{ k.decs }}</span></div>
+        </template>
+      </v-radio>
+    </v-radio-group>
+    <v-divider class="mb-4"></v-divider>
+  </div>
+</template>
+
+<script setup name="privacySettings">
+import { ref } from 'vue'
+const radios = ref(0)
+const items = [
+  { label: '简历公开', decs: '正在找工作,企业可以看到我的简历', value: 0 },
+  { label: '仅投递公司可见', decs: '可投递简历,仅投递可见', value: 1 },
+  { label: '简历保密', decs: '没找工作,企业不能搜索到您的简历', value: 2 }
+]
+</script>
+
+<style lang="scss" scoped>
+h3 {
+  font-size: 20px;
+  text-align: left;
+  font-weight: 600;
+  padding-bottom: 25px;
+}
+::v-deep .radio-text {
+  font-weight: 700;
+  color: #000;
+}
+.radio-desc {
+  color: var(--default-text);
+  margin-left: 30px;
+}
+</style>

+ 24 - 0
src/views/Home/personal/account/dynamic/realAuthentication.vue

@@ -0,0 +1,24 @@
+<template>
+  <div>
+    <h3>实名认证</h3>
+    <v-divider class="mb-4"></v-divider>
+    <div class="tips mt-2 mb-1">一经实名认证后将无法修改,请填写真实的实名信息!</div>
+  </div>
+</template>
+
+<script setup name="realAuthentication">
+</script>
+
+<style lang="scss" scoped>
+h3 {
+  font-size: 20px;
+  text-align: left;
+  font-weight: 600;
+  padding-bottom: 25px;
+}
+.tips {
+  color: grey;
+  font-size: 14px;
+  font-weight: 600;
+}
+</style>

+ 64 - 0
src/views/Home/personal/account/index.vue

@@ -0,0 +1,64 @@
+<!-- 账号与安全中心 -->
+<template>
+  <div class="d-flex pa-3" style="height: 700px;">
+    <v-card class="left">
+      <h3>账号设置</h3>
+      
+      <v-list>
+        <v-list-item
+          v-for="item in items"
+          :key="item.value"
+          :to="item.path"
+          color="primary"
+        >
+          <v-list-item-title class="list-item">{{ item.title }}</v-list-item-title>
+        </v-list-item>
+      </v-list>
+    </v-card>
+
+    <v-card class="right ml-1" style="padding: 30px 60px">
+      <router-view></router-view>
+    </v-card>
+  </div>
+</template>
+
+<script setup>
+defineOptions({ name:'personal-account-index'})
+const items = [
+  { title: '账号绑定', path: '/personalAccount/accountBinding', },
+  { title: '实名认证', path: '/personalAccount/realAuthentication', },
+  { title: '修改密码', path: '/personalAccount/editPassword', },
+  { title: '隐私设置', path: '/personalAccount/privacySettings' }
+]
+</script>
+
+<style lang="scss" scoped>
+.left {
+  width: 220px;
+}
+.right {
+  flex: 1;
+}
+::v-deep .v-list-item {
+  padding: 0 !important;
+}
+.list-item {
+  height: 62px;
+  width: 100%;
+  line-height: 62px;
+  font-weight: 500;
+  // color: #666;
+  font-size: 16px;
+  cursor: pointer;
+  margin: 0;
+  padding-left: 40px;
+  text-align: left;
+  transition: all .3s;
+}
+h3 {
+  font-size: 20px;
+  padding: 30px 0 30px 40px;
+  text-align: left;
+  font-weight: 600;
+}
+</style>

+ 48 - 0
src/views/Home/personal/components/headCarousel.vue

@@ -0,0 +1,48 @@
+<template>
+  <div>
+    <v-carousel height="80" show-arrows="hover" hide-delimiters cycle>
+      <!-- <template v-slot:prev="{ props }">
+        <v-btn
+          tonal
+          plain
+          size="x-small"
+          icon="$prev"
+          variant="elevated"
+          @click="props.onClick"
+        ></v-btn>
+      </template>
+      <template v-slot:next="{ props }">
+        <v-btn
+          tonal
+          plain
+          size="x-small"
+          icon="$next"
+          variant="elevated"
+          @click="props.onClick"
+        ></v-btn>
+      </template> -->
+      <v-carousel-item
+        v-for="(item,i) in items"
+        :key="i"
+        :src="item.src"
+        cover
+      ></v-carousel-item>
+    </v-carousel>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+
+defineOptions({ name:'personal-headCarousel'})
+
+const items = ref([
+  { src: 'https://cdn.vuetifyjs.com/images/carousel/squirrel.jpg', },
+  { src: 'https://cdn.vuetifyjs.com/images/carousel/sky.jpg', },
+  { src: 'https://cdn.vuetifyjs.com/images/carousel/bird.jpg', },
+  { src: 'https://cdn.vuetifyjs.com/images/carousel/planet.jpg', },
+])
+</script>
+
+<style lang="scss" scoped>
+</style>

+ 27 - 0
src/views/Home/personal/components/hotPromotedPositions.vue

@@ -0,0 +1,27 @@
+<template>
+  <div>
+    <v-tabs v-model="tab" align-tabs="start" color="#00897B" bg-color="#fff">
+      <v-tab :value="1">推荐职位</v-tab>
+      <v-tab :value="2">最新职位</v-tab>
+      <v-tab :value="3">急聘职位</v-tab>
+    </v-tabs>
+    <v-window v-model="tab" class="mt-9">
+        <!-- 推荐职位 -->
+      <v-window-item :value="1">
+        111
+      </v-window-item>
+        <!-- 最新职位 -->
+      <v-window-item :value="2">
+        222
+      </v-window-item>
+      <v-window-item :value="3">
+        333
+      </v-window-item>
+    </v-window>
+  </div>
+</template>
+
+<script setup name="hotPromotedPositions">
+import {ref } from 'vue'
+const tab = ref(0)
+</script>

+ 51 - 0
src/views/Home/personal/components/search.vue

@@ -0,0 +1,51 @@
+<template>
+  <div class="search d-flex align-center">
+    <div class="jobBox d-flex pl-5">
+      <div>职位类型</div>
+      <span class="mdi mdi-chevron-down px-2" style="font-size: 18px;"></span>
+    </div>
+    <v-text-field
+      v-model="value"
+      placeholder="搜索职位/公司"
+      color="#00897B"
+      variant="plain"
+      density="comfortable"
+      :hide-details="true"
+      class="px-2"
+      style="height: 100%; line-height: 100%;"
+    ></v-text-field>
+    <div class="searchBtn">搜索</div>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+
+defineOptions({ name:'personal-search'})
+
+const value = ref('')
+</script>
+
+<style lang="scss" scoped>
+.search {
+  height: 60px;
+  width: 800px;
+  margin: 16px auto 8px auto;
+  border: 2px solid var(--v-primary-base);
+  border-radius: 5px;
+  .jobBox {
+    cursor: pointer;
+    &:hover {
+      color: var(--v-primary-base);
+    }
+  }
+  .searchBtn {
+    width: 100px;
+    height: 60px; line-height: 60px;
+    font-size: 18px;
+    text-align: center;
+    color: #fff;
+    background-color: var(--v-primary-base);
+  }
+}
+</style>

+ 19 - 0
src/views/Home/personal/index.vue

@@ -0,0 +1,19 @@
+<template>
+  <div>
+    <headCarousel></headCarousel>
+    <headSearch></headSearch>
+    <div class="default-width mt-3">
+      <hotPromotedPositions></hotPromotedPositions>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import headCarousel from './components/headCarousel.vue'
+import headSearch from './components/search.vue'
+import hotPromotedPositions from './components/hotPromotedPositions.vue'
+defineOptions({ name:'personal-index'})
+</script>
+
+<style lang="scss" scoped>
+</style>

+ 0 - 32
src/views/enterprise/components/register.vue

@@ -53,38 +53,6 @@
           <div>人员规模</div>
           <div>法人招聘授权书</div>
           <div>法人身份证照片</div>
-          <v-form v-model="valid">
-            <v-text-field
-              v-model="addInForm.username"
-              label="公司全称(需要与营业执照完全一致)"
-              placeholder="请输入姓名" 
-              color="#00897B" 
-              variant="outlined" 
-              density="compact" 
-              :rules="[v=> !!v || '请输入姓名']"
-            ></v-text-field>
-            <v-autocomplete
-              v-model="addInForm.company"
-              label="公司名称"
-              placeholder="请输入公司名称" 
-              color="#00897B" 
-              variant="outlined" 
-              density="compact" 
-              return-object
-              :items="[{value: 'Alibaba', title: '阿里巴巴', shortName: '阿里' }, {value: 'Tencent', title: '腾讯', shortName: '腾讯' }, {value: 'Tencent', title: '腾讯', shortName: '腾讯' }, {value: 'Tencent', title: '腾讯', shortName: '腾讯' }, {value: 'Tencent', title: '腾讯', shortName: '腾讯' }, {value: 'Tencent', title: '腾讯', shortName: '腾讯' }, {value: 'Tencent', title: '腾讯', shortName: '腾讯' }, {value: 'Tencent', title: '腾讯', shortName: '腾讯' }, {value: 'Tencent', title: '腾讯', shortName: '腾讯' }, {value: 'Tencent', title: '腾讯', shortName: '腾讯' }]"
-              :rules="[v=> !!v || '请输入公司名称']"
-              @update:search-input="companySearch"
-            ></v-autocomplete>
-            <v-text-field
-              v-model="addInForm.duties"
-              label="职务"
-              placeholder="请输入职务" 
-              variant="outlined" 
-              density="compact"
-              color="#00897B"
-              :rules="[v=> !!v || '请输入职务']"
-            ></v-text-field>
-          </v-form>
         </v-window-item>
       </v-window>
     <!-- 新建 -->

+ 1 - 1
src/views/login/components/phonePage.vue

@@ -124,7 +124,7 @@ defineExpose({
 <style lang="scss" scoped>
 .login-code {
   width: 62px;
-  color: var(--default-color); 
+  color: var(--v-primary-base); 
   font-size: 12px; 
   cursor: pointer;
 }

+ 23 - 10
src/views/login/index.vue

@@ -44,13 +44,14 @@
         </div>
       </div>
     </div>
+    <v-snackbar v-model="tips.show" :color="tips.color" :timeout="1500" location="top">{{ tips.text }}</v-snackbar>
   </div>
 </template>
 
 <script setup>
-import { ref } from 'vue'
+import { ref, reactive } from 'vue'
 import passwordFrom from './components/passwordPage.vue'
-import phoneFrom from './components/phonePage.vue'
+import phoneFrom from '@/components/VerificationCode'
 import qrCode from './components/qrCode.vue'
 
 import { userLocaleStore } from '@/store/user'
@@ -58,10 +59,9 @@ import { useRouter } from 'vue-router'
 // import Confirm from '@/plugins/confirm'
 // import Snackbar from '@/plugins/snackbar'
 defineOptions({ name: 'login-index' })
-// Snackbar.error('1222')
-// Confirm('标题', '是否确认').then(() => {
-//   console.log(123)
-// })
+
+const router = useRouter()
+
 const phone = ref()
 let isPhone = ref(false)
 const handlePhone = () => {
@@ -76,21 +76,34 @@ const phoneRef = ref()
 const passRef = ref()
 const loginLoading = ref(false)
 const userStore = userLocaleStore()
+const tips = reactive({
+  show: false,
+  color: '',
+  text: ''
+})
 const handleLogin = async () => {
-  const { valid } = await phoneRef.value.phoneForm.validate()
+  const { valid } = tab.value === 1 ? await phoneRef.value.phoneForm.validate() : await passRef.value.passwordForm.validate()
   if (!valid) return
   loginLoading.value = true
   try {
-    await userStore.handleLogin(phoneRef.value.loginData)
+    if (tab.value === 1) {
+      await userStore.handleSmsLogin(phoneRef.value.loginData)
+    } else {
+      await userStore.handlePasswordLogin(passRef.value.loginData)
+    }
+    tips.color = 'success'
+    tips.text = '登录成功'
+    tips.show = true
+    router.push({ path: '/home' })
   } catch (error) {
     console.log(error, 'error-login')
+    alert(error.msg)
   } finally {
     loginLoading.value = false
   }
 }
 
 // 隐私、用户协议
-const router = useRouter()
 const handleToUserAgreement = () => {
   router.push({ path: '/userAgreement' })
 }
@@ -179,6 +192,6 @@ const handlePrivacyPolicy = () => {
 }
 
 .color {
-  color: var(--default-color); 
+  color: var(--v-primary-base); 
 }
 </style>