yb 2 долоо хоног өмнө
parent
commit
ffd4d22cdd

+ 1 - 1
.env.dev

@@ -8,7 +8,7 @@ VITE_DEV=true
 # 开发环境使用空字符串,通过 vite proxy 代理
 VITE_BASE_URL=
 # proxy 代理目标地址
-VITE_PROXY_TARGET=http://localhost:48080
+VITE_PROXY_TARGET=https://apidev.ifoodme.com
 
 # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
 VITE_UPLOAD_TYPE=server

+ 6 - 4
.env.local

@@ -4,9 +4,11 @@ NODE_ENV=development
 VITE_DEV=true
 
 # 请求路径
-VITE_BASE_URL='http://localhost:48081'
-#VITE_BASE_URL='http://111.229.149.174:8082'
-#VITE_BASE_URL='https://apidc.yixiang.co'
+# VITE_BASE_URL='http://localhost:48081'
+# 使用空字符串,通过 vite proxy 代理到远程服务器
+VITE_BASE_URL=
+# proxy 代理目标地址
+VITE_PROXY_TARGET=https://apidev.ifoodme.com
 
 # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务
 VITE_UPLOAD_TYPE=server
@@ -33,4 +35,4 @@ VITE_BASE_PATH=/
 VITE_MALL_H5_DOMAIN='http://localhost:3000'
 
 # 验证码的开关
-VITE_APP_CAPTCHA_ENABLE=true
+VITE_APP_CAPTCHA_ENABLE=false

BIN
.playwright-mcp/login-captcha.png


BIN
.playwright-mcp/login-success.png


BIN
.playwright-mcp/page-2026-01-11T18-24-43-173Z.png


BIN
.playwright-mcp/page-2026-01-11T18-25-13-669Z.png


+ 93 - 0
e2e/login.spec.ts

@@ -0,0 +1,93 @@
+import { test, expect } from '@playwright/test'
+
+/**
+ * 登录功能 E2E 测试
+ */
+test.describe('Login', () => {
+  test.beforeEach(async ({ page }) => {
+    // 每个测试前导航到登录页
+    await page.goto('/login')
+  })
+
+  test('should display login form', async ({ page }) => {
+    // 验证登录页面元素存在
+    await expect(page.getByRole('heading', { name: '登录' })).toBeVisible()
+    await expect(page.getByPlaceholder('请输入租户名称')).toBeVisible()
+    await expect(page.getByPlaceholder('请输入用户名')).toBeVisible()
+    await expect(page.getByPlaceholder('请输入密码')).toBeVisible()
+    await expect(page.getByRole('button', { name: '登录' })).toBeVisible()
+  })
+
+  test('should have default values filled', async ({ page }) => {
+    // 验证默认值
+    const tenantInput = page.getByPlaceholder('请输入租户名称')
+    const usernameInput = page.getByPlaceholder('请输入用户名')
+    const passwordInput = page.getByPlaceholder('请输入密码')
+
+    await expect(tenantInput).toHaveValue('yshop')
+    await expect(usernameInput).toHaveValue('admin')
+    await expect(passwordInput).toHaveValue('admin123')
+  })
+
+  test('should login successfully with valid credentials', async ({ page }) => {
+    // 点击登录按钮
+    await page.getByRole('button', { name: '登录' }).click()
+
+    // 等待登录成功并跳转到首页
+    await expect(page).toHaveURL(/\/index/, { timeout: 10000 })
+
+    // 验证首页元素 - 使用更精确的定位器
+    await expect(page.getByRole('link', { name: '首页' })).toBeVisible()
+    await expect(page.getByText('会员总数')).toBeVisible()
+  })
+
+  test('should show error with invalid tenant', async ({ page }) => {
+    // 清空并输入无效的租户名称
+    const tenantInput = page.getByPlaceholder('请输入租户名称')
+    await tenantInput.clear()
+    await tenantInput.fill('invalid_tenant')
+
+    // 点击登录按钮
+    await page.getByRole('button', { name: '登录' }).click()
+
+    // 应该显示错误提示(停留在登录页)
+    await expect(page).toHaveURL(/\/login/, { timeout: 5000 })
+  })
+
+  test('should toggle password visibility', async ({ page }) => {
+    const passwordInput = page.getByPlaceholder('请输入密码')
+
+    // 默认密码应该是隐藏的
+    await expect(passwordInput).toHaveAttribute('type', 'password')
+
+    // 点击显示密码图标(眼睛图标)
+    const toggleButton = page.locator('.el-input__suffix').filter({ has: passwordInput.locator('..') }).locator('img').last()
+
+    if (await toggleButton.isVisible()) {
+      await toggleButton.click()
+      // 密码应该变成可见
+      await expect(passwordInput).toHaveAttribute('type', 'text')
+    }
+  })
+
+  test('should remember me checkbox works', async ({ page }) => {
+    const rememberMeCheckbox = page.getByText('记住我')
+
+    // 验证记住我选项存在
+    await expect(rememberMeCheckbox).toBeVisible()
+  })
+
+  test('should redirect to original page after login', async ({ page }) => {
+    // 访问需要登录的页面
+    await page.goto('/index')
+
+    // 应该被重定向到登录页,带有 redirect 参数
+    await expect(page).toHaveURL(/\/login\?redirect/)
+
+    // 登录
+    await page.getByRole('button', { name: '登录' }).click()
+
+    // 登录后应该跳转回原页面
+    await expect(page).toHaveURL(/\/index/, { timeout: 10000 })
+  })
+})

+ 7 - 1
package.json

@@ -22,7 +22,12 @@
     "lint:eslint": "eslint --fix --ext .js,.ts,.vue ./src",
     "lint:format": "prettier --write --loglevel warn \"src/**/*.{js,ts,json,tsx,css,less,scss,vue,html,md}\"",
     "lint:style": "stylelint --fix \"./src/**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
-    "lint:lint-staged": "lint-staged -c "
+    "lint:lint-staged": "lint-staged -c ",
+    "test:e2e": "playwright test",
+    "test:e2e:ui": "playwright test --ui",
+    "test:e2e:headed": "playwright test --headed",
+    "test:e2e:debug": "playwright test --debug",
+    "test:e2e:report": "playwright show-report"
   },
   "dependencies": {
     "@element-plus/icons-vue": "^2.1.0",
@@ -78,6 +83,7 @@
     "@commitlint/config-conventional": "^19.0.0",
     "@iconify/json": "^2.2.187",
     "@intlify/unplugin-vue-i18n": "^2.0.0",
+    "@playwright/test": "^1.57.0",
     "@purge-icons/generated": "^0.9.0",
     "@types/lodash-es": "^4.17.12",
     "@types/node": "^20.11.21",

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 17 - 0
playwright-report/index.html


+ 62 - 0
playwright.config.ts

@@ -0,0 +1,62 @@
+import { defineConfig, devices } from '@playwright/test'
+
+/**
+ * Playwright E2E 测试配置
+ * @see https://playwright.dev/docs/test-configuration
+ */
+export default defineConfig({
+  // 测试目录
+  testDir: './e2e',
+
+  // 测试文件匹配模式
+  testMatch: '**/*.spec.ts',
+
+  // 并行执行
+  fullyParallel: true,
+
+  // CI 环境下禁止 only
+  forbidOnly: !!process.env.CI,
+
+  // 失败重试次数
+  retries: process.env.CI ? 2 : 0,
+
+  // 并行工作线程数
+  workers: process.env.CI ? 1 : undefined,
+
+  // 报告器
+  reporter: [
+    ['html', { outputFolder: 'playwright-report' }],
+    ['list']
+  ],
+
+  // 全局配置
+  use: {
+    // 基础 URL
+    baseURL: 'http://localhost:8000',
+
+    // 收集失败时的追踪信息
+    trace: 'on-first-retry',
+
+    // 截图
+    screenshot: 'only-on-failure',
+
+    // 视频录制
+    video: 'on-first-retry',
+  },
+
+  // 浏览器配置
+  projects: [
+    {
+      name: 'chromium',
+      use: { ...devices['Desktop Chrome'] },
+    },
+  ],
+
+  // 启动开发服务器
+  webServer: {
+    command: 'pnpm dev',
+    url: 'http://localhost:8000',
+    reuseExistingServer: !process.env.CI,
+    timeout: 120 * 1000,
+  },
+})

+ 38 - 0
pnpm-lock.yaml

@@ -162,6 +162,9 @@ importers:
       '@intlify/unplugin-vue-i18n':
         specifier: ^2.0.0
         version: 2.0.0(rollup@4.55.1)(vue-i18n@9.10.2(vue@3.4.21(typescript@5.3.3)))
+      '@playwright/test':
+        specifier: ^1.57.0
+        version: 1.57.0
       '@purge-icons/generated':
         specifier: ^0.9.0
         version: 0.9.0
@@ -1418,6 +1421,11 @@ packages:
     resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==}
     engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
 
+  '@playwright/test@1.57.0':
+    resolution: {integrity: sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==}
+    engines: {node: '>=18'}
+    hasBin: true
+
   '@polka/url@1.0.0-next.29':
     resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
 
@@ -3326,6 +3334,11 @@ packages:
   fs.realpath@1.0.0:
     resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
 
+  fsevents@2.3.2:
+    resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
+    engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+    os: [darwin]
+
   fsevents@2.3.3:
     resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
     engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -4477,6 +4490,16 @@ packages:
   pkg-types@2.3.0:
     resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
 
+  playwright-core@1.57.0:
+    resolution: {integrity: sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==}
+    engines: {node: '>=18'}
+    hasBin: true
+
+  playwright@1.57.0:
+    resolution: {integrity: sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==}
+    engines: {node: '>=18'}
+    hasBin: true
+
   pngjs@5.0.0:
     resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
     engines: {node: '>=10.13.0'}
@@ -6910,6 +6933,10 @@ snapshots:
 
   '@pkgr/core@0.2.9': {}
 
+  '@playwright/test@1.57.0':
+    dependencies:
+      playwright: 1.57.0
+
   '@polka/url@1.0.0-next.29': {}
 
   '@purge-icons/core@0.10.0':
@@ -9222,6 +9249,9 @@ snapshots:
 
   fs.realpath@1.0.0: {}
 
+  fsevents@2.3.2:
+    optional: true
+
   fsevents@2.3.3:
     optional: true
 
@@ -10322,6 +10352,14 @@ snapshots:
       exsolve: 1.0.8
       pathe: 2.0.3
 
+  playwright-core@1.57.0: {}
+
+  playwright@1.57.0:
+    dependencies:
+      playwright-core: 1.57.0
+    optionalDependencies:
+      fsevents: 2.3.2
+
   pngjs@5.0.0: {}
 
   posix-character-classes@0.1.1: {}

+ 468 - 0
src/locales/ja.ts

@@ -0,0 +1,468 @@
+export default {
+  common: {
+    inputText: '入力してください',
+    selectText: '選択してください',
+    startTimeText: '開始時間',
+    endTimeText: '終了時間',
+    login: 'ログイン',
+    required: 'この項目は必須です',
+    loginOut: 'ログアウト',
+    document: 'ドキュメント',
+    profile: 'プロフィール',
+    reminder: 'お知らせ',
+    loginOutMessage: 'システムからログアウトしますか?',
+    back: '戻る',
+    ok: '確定',
+    save: '保存',
+    cancel: 'キャンセル',
+    close: '閉じる',
+    reload: '再読み込み',
+    success: '成功',
+    closeTab: 'タブを閉じる',
+    closeTheLeftTab: '左側のタブを閉じる',
+    closeTheRightTab: '右側のタブを閉じる',
+    closeOther: '他のタブを閉じる',
+    closeAll: 'すべてのタブを閉じる',
+    prevLabel: '前へ',
+    nextLabel: '次へ',
+    skipLabel: 'スキップ',
+    doneLabel: '完了',
+    menu: 'メニュー',
+    menuDes: 'ルート構造でレンダリングされたメニューバー',
+    collapse: '展開/折りたたみ',
+    collapseDes: 'メニューバーの展開と折りたたみ',
+    tagsView: 'タブビュー',
+    tagsViewDes: 'ルート履歴を記録',
+    tool: 'ツール',
+    toolDes: 'システムカスタマイズ設定用',
+    query: '検索',
+    reset: 'リセット',
+    shrink: '折りたたむ',
+    expand: '展開',
+    confirmTitle: 'システム通知',
+    exportMessage: 'データをエクスポートしますか?',
+    importMessage: 'データをインポートしますか?',
+    createSuccess: '作成成功',
+    updateSuccess: '更新成功',
+    delMessage: '選択したデータを削除しますか?',
+    delDataMessage: 'データを削除しますか?',
+    delNoData: '削除するデータを選択してください',
+    delSuccess: '削除成功',
+    index: '番号',
+    status: 'ステータス',
+    createTime: '作成日時',
+    updateTime: '更新日時',
+    copy: 'コピー',
+    copySuccess: 'コピー成功',
+    copyError: 'コピー失敗'
+  },
+  lock: {
+    lockScreen: '画面ロック',
+    lock: 'ロック',
+    lockPassword: 'ロックパスワード',
+    unlock: 'クリックして解除',
+    backToLogin: 'ログインに戻る',
+    entrySystem: 'システムに入る',
+    placeholder: 'ロックパスワードを入力してください',
+    message: 'ロックパスワードが間違っています'
+  },
+  error: {
+    noPermission: `申し訳ございません、このページにアクセスする権限がありません。`,
+    pageError: '申し訳ございません、アクセスしたページは存在しません。',
+    networkError: '申し訳ございません、サーバーエラーが発生しました。',
+    returnToHome: 'ホームに戻る'
+  },
+  permission: {
+    hasPermission: `操作権限タグ値を設定してください`,
+    hasRole: `ロール権限タグ値を設定してください`
+  },
+  setting: {
+    projectSetting: 'プロジェクト設定',
+    theme: 'テーマ',
+    layout: 'レイアウト',
+    systemTheme: 'システムテーマ',
+    menuTheme: 'メニューテーマ',
+    interfaceDisplay: 'インターフェース表示',
+    breadcrumb: 'パンくずリスト',
+    breadcrumbIcon: 'パンくずアイコン',
+    collapseMenu: 'メニュー折りたたみ',
+    hamburgerIcon: '折りたたみアイコン',
+    screenfullIcon: 'フルスクリーンアイコン',
+    sizeIcon: 'サイズアイコン',
+    localeIcon: '多言語アイコン',
+    messageIcon: 'メッセージアイコン',
+    tagsView: 'タブビュー',
+    logo: 'ロゴ',
+    greyMode: 'グレーモード',
+    fixedHeader: 'ヘッダー固定',
+    headerTheme: 'ヘッダーテーマ',
+    cutMenu: 'メニュー分割',
+    copy: 'コピー',
+    clearAndReset: 'キャッシュをクリアしてリセット',
+    copySuccess: 'コピー成功',
+    copyFailed: 'コピー失敗',
+    footer: 'フッター',
+    uniqueOpened: 'アコーディオンメニュー',
+    tagsViewIcon: 'タブアイコン',
+    reExperienced: '再度ログアウトしてお試しください',
+    fixedMenu: 'メニュー固定'
+  },
+  size: {
+    default: 'デフォルト',
+    large: '大',
+    small: '小'
+  },
+  login: {
+    welcome: 'yshop注文システムへようこそ',
+    message: 'yshop注文システムへようこそ',
+    tenantname: 'テナント名',
+    username: 'ユーザー名',
+    password: 'パスワード',
+    code: '認証コード',
+    login: 'ログイン',
+    relogin: '再ログイン',
+    otherLogin: 'その他のログイン方法',
+    register: '登録',
+    checkPassword: 'パスワード確認',
+    remember: 'ログイン情報を記憶',
+    hasUser: 'アカウントをお持ちですか?ログインへ',
+    forgetPassword: 'パスワードを忘れた?',
+    tenantNamePlaceholder: 'テナント名を入力してください',
+    usernamePlaceholder: 'ユーザー名を入力してください',
+    passwordPlaceholder: 'パスワードを入力してください',
+    codePlaceholder: '認証コードを入力してください',
+    mobileTitle: '携帯電話でログイン',
+    mobileNumber: '携帯電話番号',
+    mobileNumberPlaceholder: '携帯電話番号を入力してください',
+    backLogin: '戻る',
+    getSmsCode: '認証コードを取得',
+    btnMobile: '携帯電話でログイン',
+    btnQRCode: 'QRコードでログイン',
+    qrcode: 'QRコードをスキャンしてログイン',
+    btnRegister: '登録',
+    SmsSendMsg: '認証コードを送信しました'
+  },
+  captcha: {
+    verification: 'セキュリティ認証を完了してください',
+    slide: '右にスライドして認証',
+    point: '順番にクリックしてください',
+    success: '認証成功',
+    fail: '認証失敗'
+  },
+  router: {
+    login: 'ログイン',
+    socialLogin: 'ソーシャルログイン',
+    home: 'ホーム',
+    analysis: '分析',
+    workplace: 'ワークスペース'
+  },
+  analysis: {
+    newUser: '新規ユーザー',
+    unreadInformation: '未読メッセージ',
+    transactionAmount: '取引金額',
+    totalShopping: 'ショッピング総量',
+    monthlySales: '月間売上',
+    userAccessSource: 'ユーザーアクセス元',
+    january: '1月',
+    february: '2月',
+    march: '3月',
+    april: '4月',
+    may: '5月',
+    june: '6月',
+    july: '7月',
+    august: '8月',
+    september: '9月',
+    october: '10月',
+    november: '11月',
+    december: '12月',
+    estimate: '予測',
+    actual: '実績',
+    directAccess: '直接アクセス',
+    mailMarketing: 'メールマーケティング',
+    allianceAdvertising: 'アフィリエイト広告',
+    videoAdvertising: '動画広告',
+    searchEngines: '検索エンジン',
+    weeklyUserActivity: '週間ユーザーアクティビティ',
+    activeQuantity: 'アクティブ数',
+    monday: '月曜日',
+    tuesday: '火曜日',
+    wednesday: '水曜日',
+    thursday: '木曜日',
+    friday: '金曜日',
+    saturday: '土曜日',
+    sunday: '日曜日'
+  },
+  workplace: {
+    welcome: 'こんにちは',
+    happyDay: '素敵な一日を!',
+    toady: '今日は晴れ',
+    notice: 'お知らせ',
+    project: 'プロジェクト数',
+    access: 'アクセス',
+    toDo: 'TODO',
+    introduction: '紹介',
+    shortcutOperation: 'ショートカット',
+    operation: '操作',
+    index: '指数',
+    personal: '個人',
+    team: 'チーム',
+    quote: '引用',
+    contribution: '貢献',
+    hot: '人気',
+    yield: '生産量',
+    dynamic: 'アクティビティ',
+    push: 'プッシュ',
+    follow: 'フォロー'
+  },
+  form: {
+    input: '入力',
+    inputNumber: '数値入力',
+    default: 'デフォルト',
+    icon: 'アイコン',
+    mixed: '複合型',
+    textarea: 'テキストエリア',
+    slot: 'スロット',
+    position: '位置',
+    autocomplete: 'オートコンプリート',
+    select: 'セレクト',
+    selectGroup: 'オプショングループ',
+    selectV2: 'バーチャルリストセレクト',
+    cascader: 'カスケーダー',
+    switch: 'スイッチ',
+    rate: '評価',
+    colorPicker: 'カラーピッカー',
+    transfer: 'トランスファー',
+    render: 'レンダラー',
+    radio: 'ラジオボタン',
+    button: 'ボタン',
+    checkbox: 'チェックボックス',
+    slider: 'スライダー',
+    datePicker: '日付選択',
+    shortcuts: 'ショートカット',
+    today: '今日',
+    yesterday: '昨日',
+    aWeekAgo: '1週間前',
+    week: '週',
+    year: '年',
+    month: '月',
+    dates: '日付',
+    daterange: '日付範囲',
+    monthrange: '月範囲',
+    dateTimePicker: '日時選択',
+    dateTimerange: '日時範囲',
+    timePicker: '時間選択',
+    timeSelect: '時間選択',
+    inputPassword: 'パスワード入力',
+    passwordStrength: 'パスワード強度',
+    operate: '操作',
+    change: '変更',
+    restore: '復元',
+    disabled: '無効',
+    disablement: '無効解除',
+    delete: '削除',
+    add: '追加',
+    setValue: '値を設定',
+    resetValue: '値をリセット',
+    set: '設定',
+    subitem: 'サブアイテム',
+    formValidation: 'フォーム検証',
+    verifyReset: '検証リセット',
+    remark: '備考'
+  },
+  watermark: {
+    watermark: '透かし'
+  },
+  table: {
+    table: 'テーブル',
+    index: '番号',
+    title: 'タイトル',
+    author: '作者',
+    createTime: '作成日時',
+    action: '操作',
+    pagination: 'ページネーション',
+    reserveIndex: '番号追加',
+    restoreIndex: '番号復元',
+    showSelections: '選択表示',
+    hiddenSelections: '選択非表示',
+    showExpandedRows: '展開行を表示',
+    hiddenExpandedRows: '展開行を非表示',
+    header: 'ヘッダー'
+  },
+  action: {
+    setPrint: 'プリンター設定',
+    check: '審査',
+    buyDetail: '購入履歴',
+    order: '注文',
+    qrcode: 'QRコード',
+    couponRecord: 'クーポン受取履歴',
+    yue: 'ポイント残高',
+    userDetail: 'ユーザー詳細',
+    refundOrder: '注文返金',
+    orderRecord: '注文履歴',
+    orderDetail: '注文詳細',
+    updateOrder: '注文修正',
+    orderSend: '注文発送',
+    remark: '備考',
+    sendInfo: '配送情報',
+    batchCreate: '一括作成',
+    create: '新規作成',
+    add: '追加',
+    del: '削除',
+    delete: '削除',
+    edit: '編集',
+    update: '編集',
+    preview: 'プレビュー',
+    more: 'もっと見る',
+    sync: '同期',
+    save: '保存',
+    detail: '詳細',
+    export: 'エクスポート',
+    import: 'インポート',
+    generate: '生成',
+    logout: '強制ログアウト',
+    test: 'テスト',
+    typeCreate: '辞書タイプ新規作成',
+    typeUpdate: '辞書タイプ編集',
+    dataCreate: '辞書データ新規作成',
+    dataUpdate: '辞書データ編集'
+  },
+  dialog: {
+    dialog: 'ダイアログ',
+    open: '開く',
+    close: '閉じる'
+  },
+  sys: {
+    api: {
+      operationFailed: '操作失敗',
+      errorTip: 'エラー',
+      errorMessage: '操作失敗、システムエラーが発生しました!',
+      timeoutMessage: 'ログインタイムアウト、再度ログインしてください!',
+      apiTimeoutMessage: 'APIリクエストタイムアウト、ページを更新して再試行してください!',
+      apiRequestFailed: 'リクエストエラー、後でもう一度お試しください',
+      networkException: 'ネットワーク異常',
+      networkExceptionMsg: 'ネットワーク異常、ネットワーク接続を確認してください!',
+      errMsg401: 'ユーザーの認証エラー(トークン、ユーザー名、パスワードが間違っています)!',
+      errMsg403: 'ユーザーは認証されましたが、アクセスは禁止されています!',
+      errMsg404: 'ネットワークリクエストエラー、リソースが見つかりません!',
+      errMsg405: 'ネットワークリクエストエラー、リクエストメソッドが許可されていません!',
+      errMsg408: 'ネットワークリクエストタイムアウト!',
+      errMsg500: 'サーバーエラー、管理者にお問い合わせください!',
+      errMsg501: 'ネットワーク未実装!',
+      errMsg502: 'ネットワークエラー!',
+      errMsg503: 'サービス利用不可、サーバーが一時的に過負荷またはメンテナンス中です!',
+      errMsg504: 'ネットワークタイムアウト!',
+      errMsg505: 'HTTPバージョンがこのリクエストをサポートしていません!',
+      errMsg901: 'デモモード、書き込み操作はできません!'
+    },
+    app: {
+      logoutTip: 'お知らせ',
+      logoutMessage: 'システムからログアウトしますか?',
+      menuLoading: 'メニュー読み込み中...'
+    },
+    exception: {
+      backLogin: 'ログインに戻る',
+      backHome: 'ホームに戻る',
+      subTitle403: '申し訳ございません、このページにアクセスする権限がありません。',
+      subTitle404: '申し訳ございません、アクセスしたページは存在しません。',
+      subTitle500: '申し訳ございません、サーバーエラーが発生しました。',
+      noDataTitle: 'このページにデータがありません',
+      networkErrorTitle: 'ネットワークエラー',
+      networkErrorSubTitle: '申し訳ございません、ネットワーク接続が切断されました。ネットワークを確認してください!'
+    },
+    lock: {
+      unlock: 'クリックして解除',
+      alert: 'ロックパスワードが間違っています',
+      backToLogin: 'ログインに戻る',
+      entry: 'システムに入る',
+      placeholder: 'ロックパスワードまたはユーザーパスワードを入力してください'
+    },
+    login: {
+      backSignIn: '戻る',
+      signInFormTitle: 'ログイン',
+      ssoFormTitle: 'サードパーティ認証',
+      mobileSignInFormTitle: '携帯電話ログイン',
+      qrSignInFormTitle: 'QRコードログイン',
+      signUpFormTitle: '登録',
+      forgetFormTitle: 'パスワードリセット',
+      signInTitle: 'すぐに使えるバックエンド管理システム',
+      signInDesc: '個人情報を入力して始めましょう!',
+      policy: 'xxx プライバシーポリシーに同意します',
+      scanSign: `スキャン後「確認」をクリックするとログインが完了します`,
+      loginButton: 'ログイン',
+      registerButton: '登録',
+      rememberMe: 'ログイン情報を記憶',
+      forgetPassword: 'パスワードを忘れた?',
+      otherSignIn: 'その他のログイン方法',
+      // notify
+      loginSuccessTitle: 'ログイン成功',
+      loginSuccessDesc: 'おかえりなさい',
+      // placeholder
+      accountPlaceholder: 'アカウントを入力してください',
+      passwordPlaceholder: 'パスワードを入力してください',
+      smsPlaceholder: '認証コードを入力してください',
+      mobilePlaceholder: '携帯電話番号を入力してください',
+      policyPlaceholder: 'チェック後に登録できます',
+      diffPwd: '2回入力したパスワードが一致しません',
+      userName: 'アカウント',
+      password: 'パスワード',
+      confirmPassword: 'パスワード確認',
+      email: 'メール',
+      smsCode: 'SMS認証コード',
+      mobile: '携帯電話番号'
+    }
+  },
+  profile: {
+    user: {
+      title: '個人情報',
+      username: 'ユーザー名',
+      nickname: 'ニックネーム',
+      mobile: '携帯電話番号',
+      email: 'メールアドレス',
+      dept: '所属部門',
+      posts: '役職',
+      roles: 'ロール',
+      sex: '性別',
+      man: '男性',
+      woman: '女性',
+      createTime: '作成日'
+    },
+    info: {
+      title: '基本情報',
+      basicInfo: '基本情報',
+      resetPwd: 'パスワード変更',
+      userSocial: 'ソーシャル情報'
+    },
+    rules: {
+      nickname: 'ニックネームを入力してください',
+      mail: 'メールアドレスを入力してください',
+      truemail: '正しいメールアドレスを入力してください',
+      phone: '携帯電話番号を入力してください',
+      truephone: '正しい携帯電話番号を入力してください'
+    },
+    password: {
+      oldPassword: '現在のパスワード',
+      newPassword: '新しいパスワード',
+      confirmPassword: 'パスワード確認',
+      oldPwdMsg: '現在のパスワードを入力してください',
+      newPwdMsg: '新しいパスワードを入力してください',
+      cfPwdMsg: 'パスワード確認を入力してください',
+      pwdRules: '6~20文字で入力してください',
+      diffPwd: '2回入力したパスワードが一致しません'
+    }
+  },
+  cropper: {
+    selectImage: '画像を選択',
+    uploadSuccess: 'アップロード成功',
+    modalTitle: 'アバターアップロード',
+    okText: '確認してアップロード',
+    btn_reset: 'リセット',
+    btn_rotate_left: '左回転',
+    btn_rotate_right: '右回転',
+    btn_scale_x: '水平反転',
+    btn_scale_y: '垂直反転',
+    btn_zoom_in: '拡大',
+    btn_zoom_out: '縮小',
+    preview: 'プレビュー'
+  },
+  'OAuth 2.0': 'OAuth 2.0'
+}

+ 7 - 1
src/store/modules/locale.ts

@@ -2,6 +2,7 @@ import { defineStore } from 'pinia'
 import { store } from '../index'
 import zhCn from 'element-plus/es/locale/lang/zh-cn'
 import en from 'element-plus/es/locale/lang/en'
+import ja from 'element-plus/es/locale/lang/ja'
 import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
 import { LocaleDropdownType } from '@/types/localeDropdown'
 
@@ -9,7 +10,8 @@ const { wsCache } = useCache()
 
 const elLocaleMap = {
   'zh-CN': zhCn,
-  en: en
+  en: en,
+  ja: ja
 }
 interface LocaleState {
   currentLocale: LocaleDropdownType
@@ -32,6 +34,10 @@ export const useLocaleStore = defineStore('locales', {
         {
           lang: 'en',
           name: 'English'
+        },
+        {
+          lang: 'ja',
+          name: '日本語'
         }
       ]
     }

+ 4 - 0
test-results/.last-run.json

@@ -0,0 +1,4 @@
+{
+  "status": "passed",
+  "failedTests": []
+}

+ 1 - 1
types/global.d.ts

@@ -12,7 +12,7 @@ declare global {
 
   type ComponentRef<T> = InstanceType<T>
 
-  type LocaleType = 'zh-CN' | 'en'
+  type LocaleType = 'zh-CN' | 'en' | 'ja'
 
   declare type TimeoutHandle = ReturnType<typeof setTimeout>
   declare type IntervalHandle = ReturnType<typeof setInterval>

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно