tangning 6 月之前
当前提交
a23e252b7e
共有 20 个文件被更改,包括 2691 次插入0 次删除
  1. 5 0
      .gitignore
  2. 5 0
      .vscode/settings.json
  3. 12 0
      index.html
  4. 28 0
      package.json
  5. 93 0
      src/App.vue
  6. 31 0
      src/axios/home.js
  7. 58 0
      src/axios/index.js
  8. 32 0
      src/locales/en.js
  9. 15 0
      src/locales/index.js
  10. 33 0
      src/locales/zh.js
  11. 12 0
      src/main.js
  12. 24 0
      src/router.js
  13. 31 0
      src/store/dome.js
  14. 9 0
      src/store/index.js
  15. 42 0
      src/store/user.js
  16. 46 0
      src/view/changelang.vue
  17. 240 0
      src/view/from.vue
  18. 222 0
      src/view/login.vue
  19. 44 0
      vite.config.ts
  20. 1709 0
      yarn.lock

+ 5 - 0
.gitignore

@@ -0,0 +1,5 @@
+node_modules
+.DS_Store
+dist
+dist-ssr
+*.local

+ 5 - 0
.vscode/settings.json

@@ -0,0 +1,5 @@
+{
+  "i18n-ally.localesPaths": [
+    "src/locales"
+  ]
+}

+ 12 - 0
index.html

@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>Vite App</title>
+</head>
+<body>
+  <div id="app"></div>
+  <script type="module" src="/src/main.js"></script>
+</body>
+</html>

+ 28 - 0
package.json

@@ -0,0 +1,28 @@
+{
+  "name": "vite",
+  "private": true,
+  "version": "0.0.0",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build"
+  },
+  "dependencies": {
+    "axios": "^1.7.9",
+    "js-base64": "^3.7.7",
+    "jsencrypt": "^3.3.2",
+    "pinia": "^2.3.1",
+    "pinia-plugin-persistedstate": "^4.2.0",
+    "vant": "^4.8.5",
+    "vue": "^3.4.21",
+    "vue-i18n": "^11.1.1",
+    "vue-router": "^4.5.0"
+  },
+  "devDependencies": {
+    "@vant/auto-import-resolver": "^1.1.0",
+    "@vitejs/plugin-vue": "^4.3.4",
+    "less": "^4.1.3",
+    "unplugin-auto-import": "^0.17.5",
+    "unplugin-vue-components": "^0.26.0",
+    "vite": "^5.1.6"
+  }
+}

+ 93 - 0
src/App.vue

@@ -0,0 +1,93 @@
+<template>
+  <div class="page">
+    <router-view />
+  </div>
+</template>
+
+<script setup>
+// 引入css
+import "vant/es/toast/style";
+import "vant/es/notify/style";
+// 确认框同理,其它不显示的样式同理引入相关css即可
+import "vant/es/dialog/style";
+import { ref, reactive, unref } from "vue";
+import { useRouter, useRoute } from "vue-router";
+import { useI18n } from "vue-i18n";
+const { t, locale } = useI18n();
+function getUrlParams(url) {
+  // 通过 ? 分割获取后面的参数字符串
+  let urlStr = url.split("?")[1];
+  // 创建空对象存储参数
+  let obj = {};
+  // 再通过 & 将每一个参数单独分割出来
+  let paramsArr = urlStr.split("&");
+  for (let i = 0, len = paramsArr.length; i < len; i++) {
+    // 再通过 = 将每一个参数分割为 key:value 的形式
+    let arr = paramsArr[i].split("=");
+    obj[arr[0]] = arr[1];
+  }
+  return obj;
+}
+import { useStore } from "../src/store/user";
+const router = getUrlParams(window.location.href);
+const route = useRoute();
+console.log("useRoute", router.rtkSnCode);
+const userState = useStore();
+router.rtkSnCode && userState.setRtkSnCode(router.rtkSnCode);
+// 切换
+handleLang(route.lang || 'zh')
+function handleLang(val) {
+  locale.value = val;
+  localStorage.setItem("language", val);
+}
+// console.log("route",unref(route).query,  route.query);
+</script>
+
+<style lang="less">
+* {
+  margin: 0;
+  padding: 0;
+}
+body,
+html {
+  margin: 0;
+  padding: 0;
+}
+.page {
+  background: #161a1a;
+  min-height: 100vh;
+  width: 100%;
+}
+.van-nav-bar__title {
+  color: #ffffff !important;
+}
+.van-nav-bar__content {
+  font-size: 17px;
+  color: #ffffff;
+  line-height: 23px;
+  text-align: left;
+  font-style: normal;
+}
+.van-field__value {
+  padding: 14px;
+  background: #252828;
+  border-radius: 6px;
+}
+.van-cell__right-icon {
+  line-height: 50px !important;
+}
+.van-field__control {
+  background: none !important;
+  color: #ffffff !important;
+}
+.input:-webkit-autofill {
+  -webkit-text-fill-color: #fff !important;
+  transition: background-color 5000s ease-in-out 0s;
+}
+:root {
+  --van-cell-background: #161a1a !important;
+  --van-field-label-color: #ffffff !important;
+  --van-button-primary-background: #00c8af !important;
+  --van-button-round-radius: 6px !important;
+}
+</style>

+ 31 - 0
src/axios/home.js

@@ -0,0 +1,31 @@
+import { computed } from "vue";
+import { getapi, postapi } from './index.js'
+import { useStore } from "../store/user";
+import { storeToRefs } from 'pinia'
+const userState = useStore();
+const rtkSnCode = computed(() => userState.getRtkSnCode);
+
+export const login = async (params) => {
+  const host = params.type === '中国服' ? '' : 'https://eur.4dkankan.com/ucenter'
+  const res = await postapi(`${host}/ucenter/sso/user/login`, params)
+  return res.data
+}
+
+export const getinfoByTk = async (params) => {
+  let headers  =  {
+    'Content-Type': 'application/json',
+    'rtk-sign': userState.getRtkSign(),
+  }
+  console.log(rtkSnCode.value, 'getRtkSnCode', headers)
+  const res = await getapi(`/service/manage/inner/infoByTk/${rtkSnCode.value}`, params, headers)
+  return res.data
+}
+
+export const updateRtkInfo = async (params) => {
+  let headers  =  {
+    'Content-Type': 'application/json;charset=utf-8',
+    'rtk-sign': userState.getRtkSign(),
+  }
+  const res = await postapi(`/service/manage/inner/updateRtkInfo`, params, headers)
+  return res.data
+}

+ 58 - 0
src/axios/index.js

@@ -0,0 +1,58 @@
+import axios from "axios";
+import { showLoadingToast, closeToast  } from "vant";
+// console.log("import.meta.env", import.meta.env.VITE_API_URL);
+
+//创建一个新的请求实例instance,instance.的用法和axios.的用法一致,可以使用instance({})、instance.get()、instance.post()
+const instace = axios.create({
+  baseURL: '/ucenter', //默认配置(这里不要用process.env,个人百度这个在vite中被废弃掉了,属性名必须以VITE_API_开头,否则 import.meta.env检测不到)
+  timeout: 5000, //超时时间
+});
+
+//配置请求拦截器,在请求之前的数据处理,比如在请求头添加token,所有的请求都会经过拦截器
+instace.interceptors.request.use(
+  //config:该参数表示当前请求的配置对象
+  (config) => {
+    //例如:
+    //在请求头统一添加token
+    //或者请求之前显示lodding图标(这里只演示这个)
+    //这里是vant组件库的loadding,安装和配置请查看此文档的vant组件库的配置https://blog.csdn.net/weixin_68658847/article/details/129331162
+    showLoadingToast ({
+      duration: 0,
+      message: "正在努力加载",
+    });
+    return config;
+  },
+  (err) => {
+    closeToast(); //请求失败关闭loadding
+    return Promise.reject(err); //将错误消息挂到promise的失败函数上
+  }
+);
+
+//配置响应拦截器
+// 响应拦截器:在请求响应之后对数据处理,比如:登录失败、请求数据失败的处理
+// instance.interceptors.response.use(response=>{l}, err=>{});
+// 响应成功:执行回调函数1;响应失败,执行回调函数2
+instace.interceptors.response.use(
+  (response) => {
+    closeToast(); //响应成功关闭loadding
+    return response; //这里的response就是请求成功后的res , response.data即是请求成功后回调函数内的参数res.data
+  },
+  (err) => {
+    closeToast(); //响应失败关闭loadding
+    return Promise.reject(err); //将错误消息挂到promise的失败函数上
+  }
+);
+
+//封装请求的api
+const callapi = (method = "GET", url, data = {}, headers) => {
+  return instace({
+    method,
+    url,
+    params: method === "GET" ? data : {},
+    data: method === "POST" ? data : {},
+    headers,
+  });
+};
+//封装GET请求函数
+export const getapi = (url, data, headers) => callapi("GET", url, data, headers);
+export const postapi = (url, data, headers) => callapi("POST", url, data, headers);

+ 32 - 0
src/locales/en.js

@@ -0,0 +1,32 @@
+export default {
+  login: {
+    name: "User Login",
+    type: "Server",
+    account: "4DAGE Account",
+    password: "Account Password",
+    loginBut: "Submit",
+  },
+  enter: '请输入',
+  from:{
+    name: 'Modify CROS Account',
+    BoardrtkSnCode:"RTK Equipment Board SN Number",
+    rtkSnCode:"RTK Equipment SN Number",
+    userName:"CROS Account",
+    password:"CROS Account Password",
+    ipAddr:"IP Address",
+    mountPoint:"Mount Point",
+    port:"Ports",
+  },
+  toast: {
+    ok: 'Successful submission, please re-switch the RTK function in the app',
+    error: 'Submission failed, please make sure the required fields are filled in correctly.',
+    error1: 'Modifications to the current RTK device are not allowed',
+    error2: "User doesn't exist,Please register first and try again",
+    3014: "Incorrect user name or password.",
+    3015: "User doesn't exist,Please register first and try again",
+    4007: 'Required fields cannot be empty',
+    50050: 'Length exceeds limit!',
+    50070: 'The board SN code does not exist or has not been stored in the warehouse',
+    4007: 'The current RTK device does not allow modification!',
+  }
+}

+ 15 - 0
src/locales/index.js

@@ -0,0 +1,15 @@
+import { createI18n } from "vue-i18n";
+import zh from "./zh";
+import en from "./en";
+ 
+const i18n = createI18n({
+  legacy: false, // 默认使用 composition API,不启用旧版的 API 兼容模式,是 vue-i18n v9 引入的,为了更好地与 Vue 3 的设计理念保持一致。使用 Composition API 可以提供更灵活的代码组织和更好的类型支持。
+  locale: "zh",  // 当前使用的语言
+  fallbackLocale: "en",  // 找不到对应的语言时,使用 fallbackLocale对应的语言
+  messages:{
+    zh, // 提供的中文语言
+    en, // 提供的英文语言
+  },
+});
+ 
+export default i18n; // 导出

+ 33 - 0
src/locales/zh.js

@@ -0,0 +1,33 @@
+export default {
+  login: {
+    name: "登录账号",
+    type: "所属服务器",
+    account: "四维官网账号",
+    password: "账号密码",
+    loginBut: "确定",
+    forget: "忘记密码",
+    register: "注册账号"
+  },
+  from: {
+    name: '修改CROS账号',
+    rtkSnCode: "RTK设备板卡SN号",
+    rtkSnCode: "RTK设备SN号",
+    userName: "CROS账号",
+    password: "密码",
+    ipAddr: "IP地址",
+    mountPoint: "挂载点",
+    port: "端口",
+  },
+  toast: {
+    ok: '提交成功,请在App中重新开关RTK功能',
+    error: '提交失败,请重新填写',
+    error1: '当前RTK设备不允许进行修改',
+    error2: '当前账号未注册,请先前往官网注册',
+    3014: "账号或密码不正确",
+    3015: '当前账号未注册,请先前往官网注册',
+    4007: '必填项不能为空',
+    50050: '长度超出限制!',
+    50070: '板卡SN码不存在,或未入库',
+    4007: '当前RTK设备不允许进行修改!',
+  }
+}

+ 12 - 0
src/main.js

@@ -0,0 +1,12 @@
+import { createApp } from 'vue';
+import App from './App.vue';
+import router from "./router"
+// 一:引入i18n
+import i18n from "./locales/index.js";
+import pinia from './store';
+
+const app = createApp(App);
+app.use(router)
+app.use(pinia)
+app.use(i18n)
+app.mount('#app')

+ 24 - 0
src/router.js

@@ -0,0 +1,24 @@
+import { createRouter, createWebHistory } from "vue-router"
+const routes = [
+  {
+    path: '/',
+    redirect: '/login',
+  },
+  {
+    path: '/login',
+    name: 'login',
+    component: () => import('./view/login.vue')
+  },
+  {
+    path: '/from',
+    name: 'from',
+    component: () => import('./view/from.vue')
+  }
+]
+const router = createRouter({
+  history: createWebHistory(),
+  routes
+})
+
+export default router
+

+ 31 - 0
src/store/dome.js

@@ -0,0 +1,31 @@
+import {defineStore} from "pinia"
+ 
+const domeStore = defineStore('dome', {
+    state: () => ({
+        name: '亚索',
+        qSkill: '斩钢闪'
+    }),
+    getters: {
+        // 箭头函数写法--不接收参数
+        getYasuoInfoNoParams: state => `${state.name},q技能叫做${state.qSkill}`,
+        // 箭头函数写法--接收参数
+        getYasuoInfoHaveParams: state => {
+            // console.log(this)  //使用箭头函数的话,这个地方的this是无效的 可以通过state.其他getters的名称 来访问别的getters
+            return (params) => `${state.name},q技能叫做${state.qSkill},${params}`
+        },
+        // 普通函数写法--不接收参数
+        getYasuoInfoNoParams1(state) {
+            // console.log(this)  //在此处this 和 state的值是一样的  可以通过this/state.其他getters的名称 来访问别的getters
+            // console.log(state)
+            return `${this.name},q技能叫做${this.qSkill}`
+        },
+        // 普通函数写法--接收参数
+        getYasuoInfoHaveParams1(state) {
+            return function (params) {
+                return `${state.name},q技能叫做${state.qSkill},${params}`
+            }
+        }
+    }
+})
+ 
+export default domeStore

+ 9 - 0
src/store/index.js

@@ -0,0 +1,9 @@
+import { createPinia } from 'pinia';
+import { createPersistedState } from 'pinia-plugin-persistedstate';
+
+const pinia = createPinia();
+pinia.use(createPersistedState({
+  storage: sessionStorage,
+}));
+
+export default pinia;

+ 42 - 0
src/store/user.js

@@ -0,0 +1,42 @@
+import { defineStore } from 'pinia';
+import JSEncrypt from "jsencrypt";
+
+export const useStore = defineStore('user', {
+  state: () => ({
+    count: 1,
+    account: '',
+    rtkSnCode: '',
+    lang: 'zh',
+    token: '',
+  }),
+  getters: {
+    getRtkSnCode: state => state.rtkSnCode,
+  },
+  actions: {
+    setRtkSnCode(val) {
+      this.rtkSnCode = val;
+      console.log('setRtkSnCode', val, this.rtkSnCode);
+    },
+    setAccount(val) {
+      this.account = val;
+    },
+    setToken(val) {
+      this.token = val;
+    },
+    getRtkSign() {
+      let rtkSign = {
+        time: new Date().getTime(),
+        rtkSnCode: this.rtkSnCode,
+        account: this.account,
+      }
+      const jsRsa = new JSEncrypt();
+      var publicKey = `-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+vVzV77vzYsJegQELP6O07D1FhHVsQih0klfZJ36A6AzbAhkCzzhhKYAaYTmE3PHtp/cmVrwSjLujELBKEIxUyz5u/EB/fi1+YVVkKRBJ6/wq8kmaw/68Cjc6VfYfN8dWQxKT4QWuhUh3kO3/Uyma7sp92Bkjf4CjHs0sWPtRDHgbN14fol4MZ7pe0cOxr5YrKsOpnWu/y92JfpVYBxNxhEcTeKF/8j+FNTZGBu0KqBdT54nIOxYGxl1V4PLoN1Hkpum8OZW5ta0KbK0n6o8ao+6NJr63jnuTRT7XH951DyjFuHx7AQWm2CrKi/zg9mW5oxm0Pq5HcsyEWdikyw/ywIDAQAB
+-----END PUBLIC KEY-----`;
+      jsRsa.setPublicKey(publicKey)
+      console.log('getRtkSnCode', JSON.stringify(rtkSign));
+      return jsRsa.encrypt(JSON.stringify(rtkSign));
+    },
+  },
+  persist: true, // 设置持久化
+});

+ 46 - 0
src/view/changelang.vue

@@ -0,0 +1,46 @@
+<template>
+  <div class="lang">
+      <div class="lang-item" @click="changeLang(item)" :class="{active: locale == item.value}" v-for="(item, index) in langList" :key="index">
+        {{ item.name }}
+      </div>
+  </div>
+</template>
+<script setup>
+import { ref, reactive } from "vue";
+import { useI18n } from "vue-i18n";
+const { t, locale } = useI18n();
+const langList = ref([
+  { name: "中文", value: "zh" },
+  { name: "English", value: "en" },
+])
+const changeLang = (item) => {
+  locale.value = item.value;
+}
+</script>
+<style lang="less" scoped>
+.lang{
+  width: 170px;
+  border-radius: 6px;
+  margin: 20px auto;
+  background: #252828;
+  display: flex;
+  text-align: center;
+  justify-content: center;
+  font-weight: 400;
+  font-size: 15px;
+  line-height: 22px;
+  text-align: left;
+  font-style: normal;
+  .lang-item{
+    width: 84px;
+    line-height: 31px;
+    color: #929494;
+    text-align: center;
+  }
+  .active{
+    color: #fff;
+    background: #3A3D3D;
+    border-radius: 6px;
+  }
+}
+</style>

+ 240 - 0
src/view/from.vue

@@ -0,0 +1,240 @@
+<template>
+  <div class="login">
+    <van-nav-bar style="background: #252828" title="修改CROS账号" />
+    <lang/>
+    <van-form @submit="onSubmit">
+      <div class="fromContent">
+        <van-field
+          v-model="detailInfo.rtkSnCode"
+          type="text"
+          :label="t('from.rtkSnCode')"
+          label-align="top"
+          :border="false"
+          :disabled="true"
+          required
+          :placeholder="t('enter') + t('from.rtkSnCode')"
+          :rules="[{ required: true, message: t('enter') + t('from.rtkSnCode') }]"
+        >
+        </van-field>
+        <van-field
+          v-model="detailInfo.sgRtkSn"
+          type="text"
+          :label="t('from.sgRtkSn')"
+          label-align="top"
+          :border="false"
+          required
+          :disabled="true"
+          :placeholder="t('enter') + t('from.sgRtkSn')"
+          :rules="[{ required: true, message: t('enter') + t('from.sgRtkSn') }]"
+        >
+        </van-field>
+        <template v-if="detailInfo.accountType == 1">
+        <van-field
+          v-model="detailInfo.userName"
+          type="text"
+          :label="t('from.userName')"
+          label-align="top"
+          :border="false"
+          required
+          :placeholder="t('enter') + t('from.userName')"
+          :rules="[{ required: true, message: t('enter') + t('from.userName') }]"
+        >
+        </van-field>
+        <van-field
+          v-model="detailInfo.password"
+          :type="type"
+          :label="t('from.password')"
+          label-align="top"
+          :border="false"
+          required
+          :placeholder="t('enter') + t('from.password')"
+          :rules="[{ required: true, message: t('enter') + t('from.password') }]"
+        >
+          <template #right-icon>
+            <van-icon
+              @click="showPassword"
+              :name="type == 'text' ? 'eye-o' : 'closed-eye'"
+            />
+            <!-- <van-icon name="closed-eye" /> -->
+          </template>
+        </van-field>
+
+        <van-field
+          v-model="detailInfo.ipAddr"
+          type="text"
+          :label="t('from.ipAddr')"
+          label-align="top"
+          :border="false"
+          required
+          :placeholder="t('enter') + t('from.ipAddr')"
+          :rules="[{ required: true, message: t('enter') + t('from.ipAddr') }]"
+        >
+        </van-field>
+
+        <van-field
+          v-model="detailInfo.mountPoint"
+          type="text"
+          :label="t('from.mountPoint')"
+          label-align="top"
+          :border="false"
+          required
+          :placeholder="t('enter') + t('from.mountPoint')"
+          :rules="[{ required: true, message: t('enter') + t('from.mountPoint') }]"
+        >
+        </van-field>
+        <van-field
+          v-model="detailInfo.port"
+          type="text"
+          :label="t('from.port')"
+          label-align="top"
+          :border="false"
+          required
+          :placeholder="t('enter') + t('from.port')"
+          :rules="[{ required: true, message: t('enter') + t('from.port') }]"
+        >
+        </van-field>
+      </template>
+        <!-- <van-field
+        v-model="detailInfo.coverableCities"
+        type="text"
+        label="四维官网账号"
+        label-align="top"
+ :border="false"
+
+        required
+        placeholder="请输入四维官网账号"
+      >
+      </van-field> -->
+      </div>
+      <div style="margin: 16px">
+        <van-button round block type="primary" native-type="submit">
+          提交
+        </van-button>
+      </div>
+    </van-form>
+    <van-popup v-model:show="showPicker" destroy-on-close position="bottom">
+      <van-picker
+        :columns="columns"
+        :model-value="pickerValue"
+        @confirm="onConfirm"
+        @cancel="showPicker = false"
+      />
+    </van-popup>
+  </div>
+</template>
+
+<script setup>
+import lang from "./changelang.vue";
+import { ref, reactive, computed } from "vue";
+import { useI18n } from "vue-i18n";
+import { useStore } from "../store/user";
+import domeStore from "../store/dome";
+import { showToast } from "vant";
+import { storeToRefs } from "pinia";
+const { t, locale } = useI18n();
+const zlLengedTxtOne = ref(t("name"));
+const zlLengedTxtTwo = ref(t("aa.bb"));
+import { getinfoByTk, updateRtkInfo } from "@/axios/home";
+const type = ref("password");
+const userState = useStore();
+const detailInfo = reactive({
+  rtkSnCode: "",
+  sgRtkSn: "",
+  userName: "",
+  port: "",
+  password: "",
+  ipAddr: "",
+  mountPoint: "",
+  accountType: 0,
+});
+const rtkSnCode = computed(() => userState.getRtkSnCode);
+console.log("domestore", rtkSnCode); // 中文显示姓名,英文显示name
+const showPassword = () => {
+  inputtype.value = type.value === "password" ? "text" : "password";
+};
+const onSubmit = (values) => {
+  console.log("submit", values);
+  updateRtkInfo(detailInfo).then((res) => {
+    console.log("updateRtkInfo", res);
+    if(res.code == 0){
+      showToast(t('toast.ok'))
+      setTimeout(() => {
+
+      }, 1000)
+    }else{
+      showToast(t('toast.error'))
+    }
+  });
+};
+// 可以通过locale切换语言包
+locale.value = "zh"; // 切换为中文
+// console.log('getRtkSnCode',storeToRefs(userState).getRtkSnCode()) // 中文显示姓名,英文显示name
+// const rtkSnCode = computed(() => userState.getRtkSnCode);
+// console.log('zlLengedTxtOne',rtkSnCode, userState.value) // 中文显示姓名,英文显示name
+getinfoByTk({}).then((res) => {
+  detailInfo.rtkSnCode = res.data.rtkSnCode;
+  detailInfo.sgRtkSn = res.data.sgRtkSn;
+  detailInfo.userName = res.data.userName;
+  detailInfo.port = res.data.port;
+  detailInfo.password = res.data.password;
+  detailInfo.ipAddr = res.data.ipAddr;
+  detailInfo.mountPoint = res.data.mountPoint;
+  detailInfo.accountType = res.data.accountType;
+  detailInfo.accountType == 0 &&  showToast(t('toast.error1'));
+  console.log("getinfoByTk", res); // 中文显示姓名,英文显示name
+});
+</script>
+
+<style lang="less">
+body {
+  font-size: 16px;
+  background-color: #f8f8f8;
+  -webkit-font-smoothing: antialiased;
+}
+
+.goods {
+  padding-bottom: 50px;
+
+  &-swipe {
+    img {
+      width: 100%;
+      display: block;
+    }
+  }
+
+  &-title {
+    font-size: 16px;
+  }
+
+  &-price {
+    color: #f44;
+  }
+
+  &-express {
+    color: #999;
+    font-size: 12px;
+    padding: 5px 15px;
+  }
+
+  &-cell-group {
+    margin: 15px 0;
+  }
+
+  &-tag {
+    margin-left: 5px;
+  }
+}
+.select {
+  width: 100%;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.login {
+  padding-bottom: 32px;
+}
+.fromContent {
+  height: calc(100vh - 188px);
+  overflow-y: scroll;
+}
+</style>

+ 222 - 0
src/view/login.vue

@@ -0,0 +1,222 @@
+<template>
+  <div class="login">
+    <van-nav-bar style="background: #252828" :title="t('login.name')" />
+    <lang/>
+    <van-form>
+      <van-field
+        v-model="form.type"
+        label-align="top"
+        required
+        :border="false"
+        :label="t('login.type')"
+        @click="showPicker = true"
+      >
+        <template #input>
+          <div class="select">
+            <span>{{ form.type }}</span>
+            <van-icon name="arrow-down" />
+          </div>
+        </template>
+      </van-field>
+      <van-field
+        v-model="form.account"
+        type="text"
+        :label="t('login.account')"
+        :border="false"
+        label-align="top"
+        required
+        :placeholder="t('enter') + t('login.account')"
+        :rules="[{ required: true, message: t('enter') + t('login.account') }]"
+      >
+      </van-field>
+      <van-field
+        v-model="form.password"
+        :type="inputtype"
+        :label="t('login.password')"
+        label-align="top"
+        :border="false"
+        required
+        :placeholder="t('enter') + t('login.password')"
+        :rules="[{ required: true, message: t('enter') + t('login.password') }]"
+      >
+        <template #right-icon>
+          <van-icon
+            @click="showPassword"
+            :name="inputtype == 'text' ? 'eye-o' : 'closed-eye'"
+          />
+          <!-- <van-icon name="closed-eye" /> -->
+        </template>
+      </van-field>
+      <!-- <van-field
+        v-model="detailInfo.coverableCities"
+        type="text"
+        label="四维官网账号"
+        label-align="top"
+        required
+        placeholder="请输入四维官网账号"
+      >
+      </van-field> -->
+    </van-form>
+    <div style="margin: 16px">
+      <van-button
+        round
+        block
+        type="primary"
+        @click="submit"
+        native-type="submit"
+      >
+        {{ t("login.loginBut") }}
+      </van-button>
+    </div>
+    <van-popup v-model:show="showPicker" destroy-on-close position="bottom">
+      <van-picker
+        :columns="columns"
+        :model-value="pickerValue"
+        @confirm="onConfirm"
+        @cancel="showPicker = false"
+      />
+    </van-popup>
+  </div>
+</template>
+
+<script setup>
+import { showToast } from "vant";
+import { ref, reactive } from "vue";
+import { useI18n } from "vue-i18n";
+import { useStore } from "../store/user";
+import { login } from "../axios/home";
+import { useRouter } from 'vue-router'
+import { Base64 } from 'js-base64'
+const router = useRouter();
+const { t, locale } = useI18n();
+const userState = useStore();
+const type = ref("password");
+const form = ref({
+  type: "中国服",
+  account: "",
+  password: "",
+});
+const zlLengedTxtOne = ref(t("aa.name"));
+const zlLengedTxtTwo = ref(t("aa.bb"));
+// 可以通过locale切换语言包
+locale.value = "zh"; // 切换为中文
+
+console.log("zlLengedTxtOne", zlLengedTxtOne.value); // 中文显示姓名,英文显示name
+console.log("zlLengedTxtTwo", zlLengedTxtTwo.value); // 中文显示你好,英文显示hello
+const submit = () => {
+  let params = {
+    phoneNum: form.value.account,
+    password: encodeStr(Base64.encode(form.value.password)),
+    randomcode: '1234',
+    rememberMe: false,
+    type: form.value.type,
+  };
+  login(params).then((res) => {
+    if(res.code == 0) {
+      userState.setAccount(form.value.account);
+      // 跳转
+      router.push('/from?account='+ form.value.account)
+    }else {
+      showToast(t(`toast.${res.code}`))
+    }
+    console.log("res", res);
+  })
+};
+const showPicker = ref(false);
+const pickerValue = ref([]);
+const columns = [
+  { text: "中国服", value: "中国服" },
+  { text: "国际服", value: "国际服" },
+];
+
+const onConfirm = ({ selectedValues }) => {
+  console.log("selectedValues", selectedValues);
+  form.value.type = selectedValues.join(",");
+  showPicker.value = false;
+  showToast(form.value.type)
+};
+let result = ref("请选择");
+const inputtype = ref("password");
+const showPassword = () => {
+  inputtype.value = type.value === "password" ? "text" : "password";
+};
+function randomWord (randomFlag, min, max) {
+  let str = ''
+  let range = min
+  let arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
+  // 随机产生
+  if (randomFlag) {
+    range = Math.round(Math.random() * (max - min)) + min
+  }
+  for (var i = 0; i < range; i++) {
+    let pos = Math.round(Math.random() * (arr.length - 1))
+    str += arr[pos]
+  }
+  return str
+}
+function encodeStr (str, strv = '') {
+    // 定义常量NUM为2
+    const NUM = 2
+    const front = randomWord(false, 8)
+    const middle = randomWord(false, 8)
+    const end = randomWord(false, 8)
+
+    let str1 = str.substring(0, NUM)
+    let str2 = str.substring(NUM)
+
+    if (strv) {
+      let strv1 = strv.substring(0, NUM)
+      let strv2 = strv.substring(NUM)
+      return [front + str2 + middle + str1 + end, front + strv2 + middle + strv1 + end]
+    }
+
+    return front + str2 + middle + str1 + end
+  }
+</script>
+
+<style lang="less">
+body {
+  font-size: 16px;
+  background-color: #f8f8f8;
+  -webkit-font-smoothing: antialiased;
+}
+
+.goods {
+  padding-bottom: 50px;
+
+  &-swipe {
+    img {
+      width: 100%;
+      display: block;
+    }
+  }
+
+  &-title {
+    font-size: 16px;
+  }
+
+  &-price {
+    color: #f44;
+  }
+
+  &-express {
+    color: #999;
+    font-size: 12px;
+    padding: 5px 15px;
+  }
+
+  &-cell-group {
+    margin: 15px 0;
+  }
+
+  &-tag {
+    margin-left: 5px;
+  }
+}
+.select {
+  width: 100%;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+</style>

+ 44 - 0
vite.config.ts

@@ -0,0 +1,44 @@
+import { defineConfig } from "vite";
+import vue from "@vitejs/plugin-vue";
+import AutoImport from "unplugin-auto-import/vite";
+import Components from "unplugin-vue-components/vite";
+import { VantResolver } from "@vant/auto-import-resolver";
+import { fileURLToPath, URL } from "node:url";
+
+export default defineConfig({
+  plugins: [
+    vue(),
+    AutoImport({
+      resolvers: [VantResolver()],
+    }),
+    Components({
+      resolvers: [VantResolver()],
+    }),
+  ],
+  resolve: {
+    alias: {
+      "@": fileURLToPath(new URL("./src", import.meta.url)),
+    },
+    extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json", ".vue"],
+  },
+  server: {
+    //方式二:设置多个代理
+    proxy: {
+      //这个路径为http://192.168.1.182:3000/douyu/wgapi/vod/front/vodrank/getTagVideos
+      "/ucenter": {
+        //target是代理的目标路径
+        target: "https://v4-uat.4dkankan.com",
+        changeOrigin: true, //必须要开启跨域
+        //pathRewrite重写请求的路径,实际请求的路径没有代理标识douyu,需要把斗鱼重置为空字符串
+        rewrite: (path) => path.replace(/\/ucenter/, ""), // 路径重写
+      },
+      "/service": {
+        //target是代理的目标路径
+        target: "https://v4-uat.4dkankan.com",
+        changeOrigin: true, //必须要开启跨域
+        //pathRewrite重写请求的路径,实际请求的路径没有代理标识douyu,需要把斗鱼重置为空字符串
+        rewrite: (path) => path.replace(/\/service/, ""), // 路径重写
+      },
+    },
+  },
+});

文件差异内容过多而无法显示
+ 1709 - 0
yarn.lock