国际化国家
This commit is contained in:
10
.env.local
10
.env.local
@@ -4,8 +4,8 @@ NODE_ENV=development
|
|||||||
VITE_DEV=true
|
VITE_DEV=true
|
||||||
|
|
||||||
# 请求路径
|
# 请求路径
|
||||||
# VITE_BASE_URL='http://192.168.2.21:48080'
|
VITE_BASE_URL='http://192.168.2.22:48080'
|
||||||
VITE_BASE_URL='https://backstageapi.yolozs.com'
|
# VITE_BASE_URL='https://backstageapi.yolozs.com'
|
||||||
# VITE_BASE_URL='https://testapi.tknb.net'
|
# VITE_BASE_URL='https://testapi.tknb.net'
|
||||||
|
|
||||||
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务
|
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务
|
||||||
@@ -30,7 +30,11 @@ VITE_BASE_PATH=/
|
|||||||
VITE_MALL_H5_DOMAIN='http://localhost:3000'
|
VITE_MALL_H5_DOMAIN='http://localhost:3000'
|
||||||
|
|
||||||
# 验证码的开关
|
# 验证码的开关
|
||||||
VITE_APP_CAPTCHA_ENABLE=false
|
VITE_APP_CAPTCHA_ENABLE=true
|
||||||
|
|
||||||
|
# Turnstile 站点密钥(生产 Key,需要在 Cloudflare 添加 localhost 到域名白名单)
|
||||||
|
VITE_APP_TURNSTILE_SITE_KEY=0x4AAAAAACYSAf0bQMQ347Pz
|
||||||
|
# VITE_APP_TURNSTILE_SITE_KEY=1x00000000000000000000AA
|
||||||
|
|
||||||
# GoView域名
|
# GoView域名
|
||||||
VITE_GOVIEW_URL='http://127.0.0.1:3000'
|
VITE_GOVIEW_URL='http://127.0.0.1:3000'
|
||||||
233
CLAUDE.md
Normal file
233
CLAUDE.md
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
This is **yudao-ui-admin-vue3**, a Vue 3 admin dashboard built with Vite, TypeScript, and Element Plus. It's part of the 芋道 (Yudao) open-source admin system that supports multi-tenant SaaS scenarios and integrates with Spring Boot/Spring Cloud backends.
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
```bash
|
||||||
|
pnpm install # Install dependencies (pnpm is required, version >=8.6.0)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development
|
||||||
|
```bash
|
||||||
|
pnpm dev # Start dev server with env.local environment
|
||||||
|
pnpm dev-server # Start dev server with dev environment
|
||||||
|
pnpm ts:check # Run TypeScript type checking
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build
|
||||||
|
```bash
|
||||||
|
pnpm build:local # Build for local environment
|
||||||
|
pnpm build:dev # Build for dev environment
|
||||||
|
pnpm build:test # Build for test environment
|
||||||
|
pnpm build:stage # Build for staging environment
|
||||||
|
pnpm build:prod # Build for production environment
|
||||||
|
```
|
||||||
|
|
||||||
|
### Preview
|
||||||
|
```bash
|
||||||
|
pnpm serve:dev # Preview dev build
|
||||||
|
pnpm serve:prod # Preview production build
|
||||||
|
pnpm preview # Build local and preview
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linting & Formatting
|
||||||
|
```bash
|
||||||
|
pnpm lint:eslint # Fix ESLint issues in .js, .ts, .vue files
|
||||||
|
pnpm lint:format # Format code with Prettier
|
||||||
|
pnpm lint:style # Fix Stylelint issues in styles
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cleanup
|
||||||
|
```bash
|
||||||
|
pnpm clean # Remove node_modules
|
||||||
|
pnpm clean:cache # Clear node_modules cache
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Technology Stack
|
||||||
|
- **Framework**: Vue 3.5.12 (Composition API)
|
||||||
|
- **Build Tool**: Vite 5.1.4
|
||||||
|
- **UI Library**: Element Plus 2.9.1
|
||||||
|
- **Language**: TypeScript 5.3.3
|
||||||
|
- **State Management**: Pinia 2.1.7 with persistence (pinia-plugin-persistedstate)
|
||||||
|
- **Router**: Vue Router 4.4.5
|
||||||
|
- **I18n**: Vue I18n 9.10.2
|
||||||
|
- **HTTP Client**: Axios 1.9.0
|
||||||
|
- **CSS Framework**: UnoCSS 0.58.5
|
||||||
|
- **Icons**: Iconify 3.1.1
|
||||||
|
|
||||||
|
### Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── api/ # API service layer organized by business modules
|
||||||
|
│ ├── login/ # Authentication APIs
|
||||||
|
│ ├── system/ # System management (users, roles, menus, etc.)
|
||||||
|
│ ├── bpm/ # Business Process Management (workflow)
|
||||||
|
│ ├── infra/ # Infrastructure (code gen, files, jobs, etc.)
|
||||||
|
│ ├── pay/ # Payment system
|
||||||
|
│ ├── mall/ # E-commerce/Mall system
|
||||||
|
│ ├── crm/ # Customer Relationship Management
|
||||||
|
│ ├── erp/ # Enterprise Resource Planning
|
||||||
|
│ ├── ai/ # AI/LLM features
|
||||||
|
│ └── mp/ # WeChat Official Account
|
||||||
|
├── assets/ # Static assets (images, svgs)
|
||||||
|
├── components/ # Global reusable components
|
||||||
|
├── config/ # Configuration files
|
||||||
|
├── directives/ # Custom Vue directives
|
||||||
|
├── hooks/ # Composable functions
|
||||||
|
├── layout/ # Layout components
|
||||||
|
├── locales/ # I18n translation files
|
||||||
|
├── plugins/ # Plugin setup (Element Plus, UnoCSS, etc.)
|
||||||
|
├── router/ # Vue Router configuration
|
||||||
|
├── store/ # Pinia stores
|
||||||
|
│ └── modules/ # Store modules (user, permission, dict, etc.)
|
||||||
|
├── styles/ # Global styles (SCSS)
|
||||||
|
├── types/ # TypeScript type definitions
|
||||||
|
├── utils/ # Utility functions
|
||||||
|
├── views/ # Page components organized by features
|
||||||
|
├── App.vue # Root component
|
||||||
|
├── main.ts # Application entry point
|
||||||
|
└── permission.ts # Route permission guard
|
||||||
|
```
|
||||||
|
|
||||||
|
### Application Bootstrap (src/main.ts)
|
||||||
|
|
||||||
|
The application initializes in this order:
|
||||||
|
1. **I18n** setup (async - loads translations)
|
||||||
|
2. **Pinia** store setup
|
||||||
|
3. **Global components** registration
|
||||||
|
4. **Element Plus** setup
|
||||||
|
5. **Form Create** (form builder) setup
|
||||||
|
6. **Router** setup
|
||||||
|
7. **Directives** (auth, mounted-focus)
|
||||||
|
8. **VueDOMPurifyHTML** (XSS protection for v-html)
|
||||||
|
|
||||||
|
### Permission & Route System (src/permission.ts)
|
||||||
|
|
||||||
|
- **Dynamic Route Loading**: Routes are fetched from backend based on user permissions
|
||||||
|
- **Route Guards**:
|
||||||
|
- Checks authentication token before each route
|
||||||
|
- Loads user info and permissions on first access
|
||||||
|
- Loads dictionary data for the entire app
|
||||||
|
- Dynamically adds authorized routes using `router.addRoute()`
|
||||||
|
- **White List**: `/login`, `/social-login`, `/auth-redirect`, `/bind`, `/register`, `/oauthLogin/gitee`
|
||||||
|
|
||||||
|
### State Management Pattern
|
||||||
|
|
||||||
|
Stores are located in `src/store/modules/` and use Pinia:
|
||||||
|
- `user.ts` - User information and authentication
|
||||||
|
- `permission.ts` - User permissions and dynamic routes
|
||||||
|
- `dict.ts` - System dictionaries (cached)
|
||||||
|
- Store modules use the "WithOut" pattern for access outside setup: `useUserStoreWithOut()`
|
||||||
|
|
||||||
|
### API Layer Pattern
|
||||||
|
|
||||||
|
- APIs are organized by business domain in `src/api/`
|
||||||
|
- Each module has an `index.ts` (API functions) and `types.ts` (TypeScript interfaces)
|
||||||
|
- Axios is configured in `src/config/axios/` with interceptors for auth and error handling
|
||||||
|
- API calls should use the centralized request service, not raw axios
|
||||||
|
|
||||||
|
### Auto-Import System
|
||||||
|
|
||||||
|
The project uses unplugin-auto-import and unplugin-vue-components:
|
||||||
|
- Vue APIs (ref, computed, etc.) are auto-imported
|
||||||
|
- Element Plus components are auto-imported
|
||||||
|
- Custom composables from hooks are auto-imported
|
||||||
|
- Generated types are in `src/types/auto-imports.d.ts` and `src/types/auto-components.d.ts`
|
||||||
|
|
||||||
|
### Form Builder Integration
|
||||||
|
|
||||||
|
- Uses `@form-create/element-ui` and `@form-create/designer` for dynamic form building
|
||||||
|
- BPMN workflow designer uses `bpmn-js` and `bpmn-js-properties-panel`
|
||||||
|
|
||||||
|
### Environment Configuration
|
||||||
|
|
||||||
|
- Environment files: `.env`, `.env.local`, `.env.dev`, `.env.test`, `.env.stage`, `.env.prod`
|
||||||
|
- Key variables:
|
||||||
|
- `VITE_BASE_PATH` - Base URL path
|
||||||
|
- `VITE_BASE_URL` - API base URL
|
||||||
|
- `VITE_PORT` - Dev server port
|
||||||
|
- `VITE_OUT_DIR` - Build output directory
|
||||||
|
- `VITE_DROP_CONSOLE` - Remove console.log in production
|
||||||
|
|
||||||
|
### Build Configuration
|
||||||
|
|
||||||
|
- **Code Splitting**:
|
||||||
|
- `echarts` is split into separate chunk
|
||||||
|
- `@form-create/element-ui` is split separately
|
||||||
|
- `@form-create/designer` is split separately
|
||||||
|
- **Minification**: Uses Terser
|
||||||
|
- **Memory**: Build uses `--max_old_space_size=4096` for large builds
|
||||||
|
|
||||||
|
### Multi-Tenant (SaaS) Support
|
||||||
|
|
||||||
|
This system has built-in multi-tenant functionality:
|
||||||
|
- Tenant management in system modules
|
||||||
|
- Tenant packages with customizable menu/permission configurations
|
||||||
|
- APIs should be tenant-aware
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### Path Aliases
|
||||||
|
|
||||||
|
Use `@/` for imports, which resolves to `src/`:
|
||||||
|
```typescript
|
||||||
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
```
|
||||||
|
|
||||||
|
### TypeScript
|
||||||
|
|
||||||
|
- `noImplicitAny` is disabled - type annotations are not strictly required
|
||||||
|
- Use interfaces from `src/api/*/types.ts` for API request/response types
|
||||||
|
|
||||||
|
### I18n
|
||||||
|
|
||||||
|
- Translation files are in `src/locales/`
|
||||||
|
- Use the `useI18n()` composable (auto-imported)
|
||||||
|
- Support for Chinese and English
|
||||||
|
|
||||||
|
### Component Development
|
||||||
|
|
||||||
|
- Global components in `src/components/` are auto-registered
|
||||||
|
- Use Element Plus components (auto-imported, no manual import needed)
|
||||||
|
- DiyEditor mobile components are excluded from auto-import
|
||||||
|
|
||||||
|
### Icons
|
||||||
|
|
||||||
|
- SVG icons are in `src/assets/svgs/`
|
||||||
|
- Use Iconify for icon sets
|
||||||
|
- SVG icons are registered with `icon-[dir]-[name]` pattern
|
||||||
|
|
||||||
|
### Styling
|
||||||
|
|
||||||
|
- SCSS is the preprocessor
|
||||||
|
- Global variables are in `src/styles/variables.scss` (auto-injected)
|
||||||
|
- UnoCSS is used for atomic/utility CSS
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- XSS protection via `vue-dompurify-html` for v-html directives
|
||||||
|
- Auth tokens managed in `src/utils/auth`
|
||||||
|
- Permission directives for button-level access control
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
- **Node.js**: Requires >= 16.0.0
|
||||||
|
- **pnpm**: Must use pnpm >= 8.6.0 (not npm or yarn)
|
||||||
|
- **Backend**: Designed to work with Spring Boot (single) or Spring Cloud (microservices) backend
|
||||||
|
- **Route Debugging**: If routes aren't appearing, check the permission store and backend API responses
|
||||||
|
- **Dictionary Loading**: System dictionaries are loaded once on login and cached in the dict store
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
- Backend API docs (Swagger): Available at `/admin-api/swagger-ui.html` on backend server
|
||||||
|
- Official docs: https://doc.iocoder.cn
|
||||||
|
- Demo: http://dashboard-vue3.yudao.iocoder.cn
|
||||||
@@ -146,6 +146,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit" async defer></script>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import request from '@/config/axios'
|
import request from '@/config/axios'
|
||||||
import type { RegisterVO, UserLoginVO } from './types'
|
import type { RegisterVO, TenantRegisterVO, UserLoginVO } from './types'
|
||||||
|
|
||||||
export interface SmsCodeVO {
|
export interface SmsCodeVO {
|
||||||
mobile: string
|
mobile: string
|
||||||
@@ -16,11 +16,14 @@ export const login = (data: UserLoginVO) => {
|
|||||||
return request.post({ url: '/system/auth/login', data })
|
return request.post({ url: '/system/auth/login', data })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注册
|
// 用户注册
|
||||||
export const register = (data: RegisterVO) => {
|
export const register = (data: RegisterVO) => {
|
||||||
return request.post({ url: '/system/auth/register', data })
|
return request.post({ url: '/system/auth/register', data })
|
||||||
}
|
}
|
||||||
|
// 租户注册
|
||||||
|
export const tenantRegister = (data: TenantRegisterVO) => {
|
||||||
|
return request.post({ url: '/system/tenant/register', data })
|
||||||
|
}
|
||||||
// 使用租户名,获得租户编号
|
// 使用租户名,获得租户编号
|
||||||
export const getTenantIdByName = (name: string) => {
|
export const getTenantIdByName = (name: string) => {
|
||||||
return request.get({ url: '/system/tenant/get-id-by-name?name=' + name })
|
return request.get({ url: '/system/tenant/get-id-by-name?name=' + name })
|
||||||
|
|||||||
@@ -36,3 +36,12 @@ export type RegisterVO = {
|
|||||||
password: string
|
password: string
|
||||||
captchaVerification: string
|
captchaVerification: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TenantRegisterVO = {
|
||||||
|
name: string
|
||||||
|
contactName: string
|
||||||
|
contactMobile: string
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
captchaVerification: string
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,11 +32,7 @@
|
|||||||
<XTextButton title="预览JSON" @click="previewProcessJson" />
|
<XTextButton title="预览JSON" @click="previewProcessJson" />
|
||||||
</template>
|
</template>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip
|
<el-tooltip v-if="props.simulation" effect="light" :content="simulationStatus ? '退出模拟' : '开启模拟'">
|
||||||
v-if="props.simulation"
|
|
||||||
effect="light"
|
|
||||||
:content="simulationStatus ? '退出模拟' : '开启模拟'"
|
|
||||||
>
|
|
||||||
<XButton preIcon="ep:cpu" title="模拟" @click="processSimulation" />
|
<XButton preIcon="ep:cpu" title="模拟" @click="processSimulation" />
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</ElButtonGroup>
|
</ElButtonGroup>
|
||||||
@@ -47,11 +43,7 @@
|
|||||||
icon="el-icon-s-data"
|
icon="el-icon-s-data"
|
||||||
@click="elementsAlign('left')"
|
@click="elementsAlign('left')"
|
||||||
/> -->
|
/> -->
|
||||||
<XButton
|
<XButton preIcon="fa:align-left" class="align align-bottom" @click="elementsAlign('left')" />
|
||||||
preIcon="fa:align-left"
|
|
||||||
class="align align-bottom"
|
|
||||||
@click="elementsAlign('left')"
|
|
||||||
/>
|
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip effect="light" content="向右对齐">
|
<el-tooltip effect="light" content="向右对齐">
|
||||||
<!-- <el-button
|
<!-- <el-button
|
||||||
@@ -59,11 +51,7 @@
|
|||||||
icon="el-icon-s-data"
|
icon="el-icon-s-data"
|
||||||
@click="elementsAlign('right')"
|
@click="elementsAlign('right')"
|
||||||
/> -->
|
/> -->
|
||||||
<XButton
|
<XButton preIcon="fa:align-left" class="align align-top" @click="elementsAlign('right')" />
|
||||||
preIcon="fa:align-left"
|
|
||||||
class="align align-top"
|
|
||||||
@click="elementsAlign('right')"
|
|
||||||
/>
|
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip effect="light" content="向上对齐">
|
<el-tooltip effect="light" content="向上对齐">
|
||||||
<!-- <el-button
|
<!-- <el-button
|
||||||
@@ -71,11 +59,7 @@
|
|||||||
icon="el-icon-s-data"
|
icon="el-icon-s-data"
|
||||||
@click="elementsAlign('top')"
|
@click="elementsAlign('top')"
|
||||||
/> -->
|
/> -->
|
||||||
<XButton
|
<XButton preIcon="fa:align-left" class="align align-left" @click="elementsAlign('top')" />
|
||||||
preIcon="fa:align-left"
|
|
||||||
class="align align-left"
|
|
||||||
@click="elementsAlign('top')"
|
|
||||||
/>
|
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip effect="light" content="向下对齐">
|
<el-tooltip effect="light" content="向下对齐">
|
||||||
<!-- <el-button
|
<!-- <el-button
|
||||||
@@ -83,11 +67,7 @@
|
|||||||
icon="el-icon-s-data"
|
icon="el-icon-s-data"
|
||||||
@click="elementsAlign('bottom')"
|
@click="elementsAlign('bottom')"
|
||||||
/> -->
|
/> -->
|
||||||
<XButton
|
<XButton preIcon="fa:align-left" class="align align-right" @click="elementsAlign('bottom')" />
|
||||||
preIcon="fa:align-left"
|
|
||||||
class="align align-right"
|
|
||||||
@click="elementsAlign('bottom')"
|
|
||||||
/>
|
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip effect="light" content="水平居中">
|
<el-tooltip effect="light" content="水平居中">
|
||||||
<!-- <el-button
|
<!-- <el-button
|
||||||
@@ -96,11 +76,7 @@
|
|||||||
@click="elementsAlign('center')"
|
@click="elementsAlign('center')"
|
||||||
/> -->
|
/> -->
|
||||||
<!-- class="align align-center" -->
|
<!-- class="align align-center" -->
|
||||||
<XButton
|
<XButton preIcon="fa:align-left" class="align align-center" @click="elementsAlign('center')" />
|
||||||
preIcon="fa:align-left"
|
|
||||||
class="align align-center"
|
|
||||||
@click="elementsAlign('center')"
|
|
||||||
/>
|
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip effect="light" content="垂直居中">
|
<el-tooltip effect="light" content="垂直居中">
|
||||||
<!-- <el-button
|
<!-- <el-button
|
||||||
@@ -108,11 +84,7 @@
|
|||||||
icon="el-icon-s-data"
|
icon="el-icon-s-data"
|
||||||
@click="elementsAlign('middle')"
|
@click="elementsAlign('middle')"
|
||||||
/> -->
|
/> -->
|
||||||
<XButton
|
<XButton preIcon="fa:align-left" class="align align-middle" @click="elementsAlign('middle')" />
|
||||||
preIcon="fa:align-left"
|
|
||||||
class="align align-middle"
|
|
||||||
@click="elementsAlign('middle')"
|
|
||||||
/>
|
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</ElButtonGroup>
|
</ElButtonGroup>
|
||||||
<ElButtonGroup key="scale-control">
|
<ElButtonGroup key="scale-control">
|
||||||
@@ -122,11 +94,7 @@
|
|||||||
icon="el-icon-zoom-out"
|
icon="el-icon-zoom-out"
|
||||||
@click="processZoomOut()"
|
@click="processZoomOut()"
|
||||||
/> -->
|
/> -->
|
||||||
<XButton
|
<XButton preIcon="ep:zoom-out" @click="processZoomOut()" :disabled="defaultZoom < 0.2" />
|
||||||
preIcon="ep:zoom-out"
|
|
||||||
@click="processZoomOut()"
|
|
||||||
:disabled="defaultZoom < 0.2"
|
|
||||||
/>
|
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-button>{{ Math.floor(defaultZoom * 10 * 10) + '%' }}</el-button>
|
<el-button>{{ Math.floor(defaultZoom * 10 * 10) + '%' }}</el-button>
|
||||||
<el-tooltip effect="light" content="放大视图">
|
<el-tooltip effect="light" content="放大视图">
|
||||||
@@ -162,32 +130,16 @@
|
|||||||
</ElButtonGroup>
|
</ElButtonGroup>
|
||||||
</template>
|
</template>
|
||||||
<!-- 用于打开本地文件-->
|
<!-- 用于打开本地文件-->
|
||||||
<input
|
<input type="file" id="files" ref="refFile" style="display: none" accept=".xml, .bpmn"
|
||||||
type="file"
|
@change="importLocalFile" />
|
||||||
id="files"
|
|
||||||
ref="refFile"
|
|
||||||
style="display: none"
|
|
||||||
accept=".xml, .bpmn"
|
|
||||||
@change="importLocalFile"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="my-process-designer__container">
|
<div class="my-process-designer__container">
|
||||||
<div
|
<div class="my-process-designer__canvas" ref="bpmnCanvas" id="bpmnCanvas" style="width: 1680px; height: 800px">
|
||||||
class="my-process-designer__canvas"
|
</div>
|
||||||
ref="bpmnCanvas"
|
|
||||||
id="bpmnCanvas"
|
|
||||||
style="width: 1680px; height: 800px"
|
|
||||||
></div>
|
|
||||||
<!-- <div id="js-properties-panel" class="panel"></div> -->
|
<!-- <div id="js-properties-panel" class="panel"></div> -->
|
||||||
<!-- <div class="my-process-designer__canvas" ref="bpmn-canvas"></div> -->
|
<!-- <div class="my-process-designer__canvas" ref="bpmn-canvas"></div> -->
|
||||||
</div>
|
</div>
|
||||||
<Dialog
|
<Dialog title="预览" v-model="previewModelVisible" width="80%" :scroll="true" max-height="600px">
|
||||||
title="预览"
|
|
||||||
v-model="previewModelVisible"
|
|
||||||
width="80%"
|
|
||||||
:scroll="true"
|
|
||||||
max-height="600px"
|
|
||||||
>
|
|
||||||
<div>
|
<div>
|
||||||
<pre><code v-dompurify-html="highlightedCode(previewResult)" class="hljs"></code></pre>
|
<pre><code v-dompurify-html="highlightedCode(previewResult)" class="hljs"></code></pre>
|
||||||
</div>
|
</div>
|
||||||
@@ -261,13 +213,13 @@ const props = defineProps({
|
|||||||
translations: {
|
translations: {
|
||||||
// 自定义的翻译文件
|
// 自定义的翻译文件
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => {}
|
default: () => { }
|
||||||
},
|
},
|
||||||
additionalModel: [Object, Array], // 自定义model
|
additionalModel: [Object, Array], // 自定义model
|
||||||
moddleExtension: {
|
moddleExtension: {
|
||||||
// 自定义moddle
|
// 自定义moddle
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => {}
|
default: () => { }
|
||||||
},
|
},
|
||||||
onlyCustomizeAddi: {
|
onlyCustomizeAddi: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -542,9 +494,8 @@ const setEncoded = (type, data) => {
|
|||||||
const encodedData = encodeURIComponent(data)
|
const encodedData = encodeURIComponent(data)
|
||||||
return {
|
return {
|
||||||
filename: `${filename}.${type}`,
|
filename: `${filename}.${type}`,
|
||||||
href: `data:application/${
|
href: `data:application/${type === 'svg' ? 'text/xml' : 'bpmn20-xml'
|
||||||
type === 'svg' ? 'text/xml' : 'bpmn20-xml'
|
};charset=UTF-8,${encodedData}`,
|
||||||
};charset=UTF-8,${encodedData}`,
|
|
||||||
data: data
|
data: data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -642,7 +593,7 @@ const previewProcessJson = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------ 芋道源码 methods ------------------------------------------------------ */
|
/* ------------------------------------------------ YOLO methods ------------------------------------------------------ */
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initBpmnModeler()
|
initBpmnModeler()
|
||||||
createNewDiagram(props.value)
|
createNewDiagram(props.value)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Created by 芋道源码
|
* Created by YOLO
|
||||||
*
|
*
|
||||||
* 枚举类
|
* 枚举类
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -49,6 +49,8 @@
|
|||||||
<SSOLoginVue class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
|
<SSOLoginVue class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
|
||||||
<!-- 忘记密码 -->
|
<!-- 忘记密码 -->
|
||||||
<ForgetPasswordForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
|
<ForgetPasswordForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
|
||||||
|
<!-- 租户注册 -->
|
||||||
|
<TenantRegisterForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
@@ -63,7 +65,7 @@ import { useAppStore } from '@/store/modules/app'
|
|||||||
import { ThemeSwitch } from '@/layout/components/ThemeSwitch'
|
import { ThemeSwitch } from '@/layout/components/ThemeSwitch'
|
||||||
import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
|
import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
|
||||||
|
|
||||||
import { LoginForm, MobileForm, QrCodeForm, RegisterForm, SSOLoginVue, ForgetPasswordForm } from './components'
|
import { LoginForm, MobileForm, QrCodeForm, RegisterForm, SSOLoginVue, ForgetPasswordForm, TenantRegisterForm } from './components'
|
||||||
|
|
||||||
defineOptions({ name: 'Login' })
|
defineOptions({ name: 'Login' })
|
||||||
|
|
||||||
|
|||||||
@@ -50,46 +50,35 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
<Verify v-if="loginData.captchaEnable === 'true'" ref="verify" :captchaType="captchaType"
|
<Verify v-if="loginData.captchaEnable === 'true'" ref="verify" :captchaType="captchaType"
|
||||||
:imgSize="{ width: '400px', height: '200px' }" mode="pop" @success="handleLogin" />
|
:imgSize="{ width: '400px', height: '200px' }" mode="pop" @success="handleLogin" />
|
||||||
<!-- <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-row :gutter="5" justify="space-between" style="width: 100%">
|
<el-row :gutter="5" justify="space-between" style="width: 100%">
|
||||||
<el-col :span="8">
|
<!-- <el-col :span="8">
|
||||||
<XButton
|
<XButton :title="t('login.btnMobile')" class="w-[100%]" @click="setLoginState(LoginStateEnum.MOBILE)" />
|
||||||
:title="t('login.btnMobile')"
|
|
||||||
class="w-[100%]"
|
|
||||||
@click="setLoginState(LoginStateEnum.MOBILE)"
|
|
||||||
/>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="8">
|
<el-col :span="8">
|
||||||
<XButton
|
<XButton :title="t('login.btnQRCode')" class="w-[100%]" @click="setLoginState(LoginStateEnum.QR_CODE)" />
|
||||||
:title="t('login.btnQRCode')"
|
|
||||||
class="w-[100%]"
|
|
||||||
@click="setLoginState(LoginStateEnum.QR_CODE)"
|
|
||||||
/>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="8">
|
<el-col :span="24">
|
||||||
<XButton
|
<XButton :title="t('login.btnRegister')" class="w-[100%]"
|
||||||
:title="t('login.btnRegister')"
|
@click="setLoginState(LoginStateEnum.REGISTER)" />
|
||||||
class="w-[100%]"
|
</el-col> -->
|
||||||
@click="setLoginState(LoginStateEnum.REGISTER)"
|
|
||||||
/>
|
|
||||||
|
<!-- 租户注册 -->
|
||||||
|
<el-col :span="24" style="margin-top: 10px;">
|
||||||
|
<XButton title="租户注册" class="w-[100%]" @click="setLoginState(LoginStateEnum.TENANT_REGISTER)" />
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-divider content-position="center">{{ t('login.otherLogin') }}</el-divider>
|
<!-- <el-divider content-position="center">{{ t('login.otherLogin') }}</el-divider>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<div class="w-[100%] flex justify-between">
|
<div class="w-[100%] flex justify-between">
|
||||||
<Icon
|
<Icon v-for="(item, key) in socialList" :key="key" :icon="item.icon" :size="30"
|
||||||
v-for="(item, key) in socialList"
|
class="anticon cursor-pointer" color="#999" @click="doSocialLogin(item.type)" />
|
||||||
:key="key"
|
|
||||||
:icon="item.icon"
|
|
||||||
:size="30"
|
|
||||||
class="anticon cursor-pointer"
|
|
||||||
color="#999"
|
|
||||||
@click="doSocialLogin(item.type)"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|||||||
@@ -1,14 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-form
|
<el-form v-show="getShow" ref="formSmsLogin" :model="loginData.loginForm" :rules="rules" class="login-form"
|
||||||
v-show="getShow"
|
label-position="top" label-width="120px" size="large">
|
||||||
ref="formSmsLogin"
|
|
||||||
:model="loginData.loginForm"
|
|
||||||
:rules="rules"
|
|
||||||
class="login-form"
|
|
||||||
label-position="top"
|
|
||||||
label-width="120px"
|
|
||||||
size="large"
|
|
||||||
>
|
|
||||||
<el-row style="margin-right: -10px; margin-left: -10px">
|
<el-row style="margin-right: -10px; margin-left: -10px">
|
||||||
<!-- 租户名 -->
|
<!-- 租户名 -->
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
@@ -18,23 +10,15 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
<el-form-item v-if="loginData.tenantEnable === 'true'" prop="tenantName">
|
<el-form-item v-if="loginData.tenantEnable === 'true'" prop="tenantName">
|
||||||
<el-input
|
<el-input v-model="loginData.loginForm.tenantName" :placeholder="t('login.tenantNamePlaceholder')"
|
||||||
v-model="loginData.loginForm.tenantName"
|
:prefix-icon="iconHouse" type="primary" link />
|
||||||
:placeholder="t('login.tenantNamePlaceholder')"
|
|
||||||
:prefix-icon="iconHouse"
|
|
||||||
type="primary"
|
|
||||||
link
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<!-- 手机号 -->
|
<!-- 手机号 -->
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
<el-form-item prop="mobileNumber">
|
<el-form-item prop="mobileNumber">
|
||||||
<el-input
|
<el-input v-model="loginData.loginForm.mobileNumber" :placeholder="t('login.mobileNumberPlaceholder')"
|
||||||
v-model="loginData.loginForm.mobileNumber"
|
:prefix-icon="iconCellphone" />
|
||||||
:placeholder="t('login.mobileNumberPlaceholder')"
|
|
||||||
:prefix-icon="iconCellphone"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<!-- 验证码 -->
|
<!-- 验证码 -->
|
||||||
@@ -42,19 +26,11 @@
|
|||||||
<el-form-item prop="code">
|
<el-form-item prop="code">
|
||||||
<el-row :gutter="5" justify="space-between" style="width: 100%">
|
<el-row :gutter="5" justify="space-between" style="width: 100%">
|
||||||
<el-col :span="24">
|
<el-col :span="24">
|
||||||
<el-input
|
<el-input v-model="loginData.loginForm.code" :placeholder="t('login.codePlaceholder')"
|
||||||
v-model="loginData.loginForm.code"
|
:prefix-icon="iconCircleCheck">
|
||||||
:placeholder="t('login.codePlaceholder')"
|
|
||||||
:prefix-icon="iconCircleCheck"
|
|
||||||
>
|
|
||||||
<!-- <el-button class="w-[100%]"> -->
|
<!-- <el-button class="w-[100%]"> -->
|
||||||
<template #append>
|
<template #append>
|
||||||
<span
|
<span v-if="mobileCodeTimer <= 0" class="getMobileCode" style="cursor: pointer" @click="getSmsCode">
|
||||||
v-if="mobileCodeTimer <= 0"
|
|
||||||
class="getMobileCode"
|
|
||||||
style="cursor: pointer"
|
|
||||||
@click="getSmsCode"
|
|
||||||
>
|
|
||||||
{{ t('login.getSmsCode') }}
|
{{ t('login.getSmsCode') }}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="mobileCodeTimer > 0" class="getMobileCode" style="cursor: pointer">
|
<span v-if="mobileCodeTimer > 0" class="getMobileCode" style="cursor: pointer">
|
||||||
@@ -70,23 +46,13 @@
|
|||||||
<!-- 登录按钮 / 返回按钮 -->
|
<!-- 登录按钮 / 返回按钮 -->
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<XButton
|
<XButton :loading="loginLoading" :title="t('login.login')" class="w-[100%]" type="primary"
|
||||||
:loading="loginLoading"
|
@click="signIn()" />
|
||||||
:title="t('login.login')"
|
|
||||||
class="w-[100%]"
|
|
||||||
type="primary"
|
|
||||||
@click="signIn()"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<XButton
|
<XButton :loading="loginLoading" :title="t('login.backLogin')" class="w-[100%]" @click="handleBackLogin()" />
|
||||||
:loading="loginLoading"
|
|
||||||
:title="t('login.backLogin')"
|
|
||||||
class="w-[100%]"
|
|
||||||
@click="handleBackLogin()"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@@ -133,7 +99,7 @@ const loginData = reactive({
|
|||||||
},
|
},
|
||||||
loginForm: {
|
loginForm: {
|
||||||
uuid: '',
|
uuid: '',
|
||||||
tenantName: '芋道源码',
|
tenantName: 'YOLO',
|
||||||
mobileNumber: '',
|
mobileNumber: '',
|
||||||
code: ''
|
code: ''
|
||||||
}
|
}
|
||||||
@@ -202,7 +168,7 @@ const signIn = async () => {
|
|||||||
}
|
}
|
||||||
push({ path: redirect.value || permissionStore.addRouters[0].path })
|
push({ path: redirect.value || permissionStore.addRouters[0].path })
|
||||||
})
|
})
|
||||||
.catch(() => {})
|
.catch(() => { })
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
loginLoading.value = false
|
loginLoading.value = false
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@@ -1,14 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-form
|
<el-form v-show="getShow" ref="formLogin" :model="registerData.registerForm" :rules="registerRules" class="login-form"
|
||||||
v-show="getShow"
|
label-position="top" label-width="120px" size="large">
|
||||||
ref="formLogin"
|
|
||||||
:model="registerData.registerForm"
|
|
||||||
:rules="registerRules"
|
|
||||||
class="login-form"
|
|
||||||
label-position="top"
|
|
||||||
label-width="120px"
|
|
||||||
size="large"
|
|
||||||
>
|
|
||||||
<el-row style="margin-right: -10px; margin-left: -10px">
|
<el-row style="margin-right: -10px; margin-left: -10px">
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
@@ -17,81 +9,42 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
<el-form-item v-if="registerData.tenantEnable === 'true'" prop="tenantName">
|
<el-form-item v-if="registerData.tenantEnable === 'true'" prop="tenantName">
|
||||||
<el-input
|
<el-input v-model="registerData.registerForm.tenantName" :placeholder="t('login.tenantname')"
|
||||||
v-model="registerData.registerForm.tenantName"
|
:prefix-icon="iconHouse" link type="primary" size="large" />
|
||||||
:placeholder="t('login.tenantname')"
|
|
||||||
:prefix-icon="iconHouse"
|
|
||||||
link
|
|
||||||
type="primary"
|
|
||||||
size="large"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
<el-form-item prop="username">
|
<el-form-item prop="username">
|
||||||
<el-input
|
<el-input v-model="registerData.registerForm.username" :placeholder="t('login.username')" size="large"
|
||||||
v-model="registerData.registerForm.username"
|
:prefix-icon="iconAvatar" />
|
||||||
:placeholder="t('login.username')"
|
|
||||||
size="large"
|
|
||||||
:prefix-icon="iconAvatar"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
<el-form-item prop="username">
|
<el-form-item prop="username">
|
||||||
<el-input
|
<el-input v-model="registerData.registerForm.nickname" placeholder="昵称" size="large"
|
||||||
v-model="registerData.registerForm.nickname"
|
:prefix-icon="iconAvatar" />
|
||||||
placeholder="昵称"
|
|
||||||
size="large"
|
|
||||||
:prefix-icon="iconAvatar"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
<el-form-item prop="password">
|
<el-form-item prop="password">
|
||||||
<el-input
|
<el-input v-model="registerData.registerForm.password" type="password" auto-complete="off"
|
||||||
v-model="registerData.registerForm.password"
|
:placeholder="t('login.password')" size="large" :prefix-icon="iconLock" show-password />
|
||||||
type="password"
|
|
||||||
auto-complete="off"
|
|
||||||
:placeholder="t('login.password')"
|
|
||||||
size="large"
|
|
||||||
:prefix-icon="iconLock"
|
|
||||||
show-password
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
<el-form-item prop="confirmPassword">
|
<el-form-item prop="confirmPassword">
|
||||||
<el-input
|
<el-input v-model="registerData.registerForm.confirmPassword" type="password" size="large" auto-complete="off"
|
||||||
v-model="registerData.registerForm.confirmPassword"
|
:placeholder="t('login.checkPassword')" :prefix-icon="iconLock" show-password />
|
||||||
type="password"
|
|
||||||
size="large"
|
|
||||||
auto-complete="off"
|
|
||||||
:placeholder="t('login.checkPassword')"
|
|
||||||
:prefix-icon="iconLock"
|
|
||||||
show-password
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<XButton
|
<XButton :loading="loginLoading" :title="t('login.register')" class="w-[100%]" type="primary"
|
||||||
:loading="loginLoading"
|
@click="getCode()" />
|
||||||
:title="t('login.register')"
|
|
||||||
class="w-[100%]"
|
|
||||||
type="primary"
|
|
||||||
@click="getCode()"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<Verify
|
<Verify v-if="registerData.captchaEnable === 'true'" ref="verify" :captchaType="captchaType"
|
||||||
v-if="registerData.captchaEnable === 'true'"
|
:imgSize="{ width: '400px', height: '200px' }" mode="pop" @success="handleRegister" />
|
||||||
ref="verify"
|
|
||||||
:captchaType="captchaType"
|
|
||||||
:imgSize="{ width: '400px', height: '200px' }"
|
|
||||||
mode="pop"
|
|
||||||
@success="handleRegister"
|
|
||||||
/>
|
|
||||||
</el-row>
|
</el-row>
|
||||||
<XButton :title="t('login.hasUser')" class="w-[100%]" @click="handleBackLogin()" />
|
<XButton :title="t('login.hasUser')" class="w-[100%]" @click="handleBackLogin()" />
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|||||||
323
src/views/Login/components/TenantRegisterForm.vue
Normal file
323
src/views/Login/components/TenantRegisterForm.vue
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
<template>
|
||||||
|
<el-form v-show="getShow" ref="formLogin" :model="registerData.registerForm" :rules="registerRules" class="login-form"
|
||||||
|
label-position="top" label-width="120px" size="large">
|
||||||
|
<el-row style="margin-right: -10px; margin-left: -10px">
|
||||||
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
|
<el-form-item>
|
||||||
|
<LoginFormTitle style="width: 100%" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
|
<el-form-item prop="name">
|
||||||
|
<el-input v-model="registerData.registerForm.name" placeholder="租户名称" :prefix-icon="iconHouse" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
|
<el-form-item prop="contactName">
|
||||||
|
<el-input v-model="registerData.registerForm.contactName" placeholder="联系人" :prefix-icon="iconAvatar" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
|
<el-form-item prop="contactMobile">
|
||||||
|
<el-input v-model="registerData.registerForm.contactMobile" placeholder="联系手机" :prefix-icon="iconMobile" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
|
<el-form-item prop="username">
|
||||||
|
<el-input v-model="registerData.registerForm.username" :placeholder="t('login.username')"
|
||||||
|
:prefix-icon="iconAvatar" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
|
<el-form-item prop="password">
|
||||||
|
<el-input v-model="registerData.registerForm.password" type="password" auto-complete="off"
|
||||||
|
:placeholder="t('login.password')" :prefix-icon="iconLock" show-password />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
|
<el-form-item prop="confirmPassword">
|
||||||
|
<el-input v-model="registerData.registerForm.confirmPassword" type="password" auto-complete="off"
|
||||||
|
:placeholder="t('login.checkPassword')" :prefix-icon="iconLock" show-password />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
|
<el-form-item>
|
||||||
|
<XButton :loading="loginLoading" :title="t('login.register')" class="w-[100%]" type="primary"
|
||||||
|
@click="getCode()" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col v-if="registerData.captchaEnable === 'true'" :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
|
<el-form-item>
|
||||||
|
<div id="turnstile-container" class="w-[100%] flex justify-center"></div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<XButton :title="t('login.hasUser')" class="w-[100%]" @click="handleBackLogin()" />
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ElLoading } from 'element-plus'
|
||||||
|
import LoginFormTitle from './LoginFormTitle.vue'
|
||||||
|
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||||
|
import { useIcon } from '@/hooks/web/useIcon'
|
||||||
|
import * as authUtil from '@/utils/auth'
|
||||||
|
import { usePermissionStore } from '@/store/modules/permission'
|
||||||
|
import * as LoginApi from '@/api/login'
|
||||||
|
import { LoginStateEnum, useLoginState } from './useLogin'
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
turnstile: any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineOptions({ name: 'TenantRegisterForm' })
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const iconHouse = useIcon({ icon: 'ep:house' })
|
||||||
|
const iconAvatar = useIcon({ icon: 'ep:avatar' })
|
||||||
|
const iconLock = useIcon({ icon: 'ep:lock' })
|
||||||
|
const iconMobile = useIcon({ icon: 'ep:iphone' })
|
||||||
|
const formLogin = ref()
|
||||||
|
const { handleBackLogin, getLoginState } = useLoginState()
|
||||||
|
const { currentRoute, push } = useRouter()
|
||||||
|
const permissionStore = usePermissionStore()
|
||||||
|
const redirect = ref<string>('')
|
||||||
|
const loginLoading = ref(false)
|
||||||
|
// const verify = ref()
|
||||||
|
// const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字
|
||||||
|
|
||||||
|
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.TENANT_REGISTER)
|
||||||
|
|
||||||
|
const equalToPassword = (_rule, value, callback) => {
|
||||||
|
if (registerData.registerForm.password !== value) {
|
||||||
|
callback(new Error('两次输入的密码不一致'))
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const registerRules = {
|
||||||
|
name: [
|
||||||
|
{ required: true, trigger: 'blur', message: '请输入租户名称' },
|
||||||
|
{ min: 2, max: 20, message: '租户名称长度必须介于 2 和 20 之间', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
contactName: [
|
||||||
|
{ required: true, trigger: 'blur', message: '请输入联系人' }
|
||||||
|
],
|
||||||
|
contactMobile: [
|
||||||
|
{ required: true, trigger: 'blur', message: '请输入联系手机' },
|
||||||
|
{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
username: [
|
||||||
|
{ required: true, trigger: 'blur', message: '请输入您的账号' },
|
||||||
|
{ min: 4, max: 30, message: '用户账号长度必须介于 4 和 30 之间', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
password: [
|
||||||
|
{ required: true, trigger: 'blur', message: '请输入您的密码' },
|
||||||
|
{ min: 5, max: 20, message: '用户密码长度必须介于 5 和 20 之间', trigger: 'blur' },
|
||||||
|
{ pattern: /^[^<>"'|\\]+$/, message: '不能包含非法字符:< > " \' \\\ |', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
confirmPassword: [
|
||||||
|
{ required: true, trigger: 'blur', message: '请再次输入您的密码' },
|
||||||
|
{ required: true, validator: equalToPassword, trigger: 'blur' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const registerData = reactive({
|
||||||
|
captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE,
|
||||||
|
registerForm: {
|
||||||
|
name: '',
|
||||||
|
contactName: '',
|
||||||
|
contactMobile: '',
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
confirmPassword: '',
|
||||||
|
turnstileToken: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const turnstileToken = ref('')
|
||||||
|
const turnstileWidgetId = ref('')
|
||||||
|
|
||||||
|
const initTurnstile = () => {
|
||||||
|
const waitForTurnstile = (retries = 0) => {
|
||||||
|
if (retries > 50) {
|
||||||
|
console.error('Turnstile SDK 加载超时')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.turnstile && !turnstileWidgetId.value) {
|
||||||
|
const container = document.getElementById('turnstile-container')
|
||||||
|
if (!container) {
|
||||||
|
console.error('未找到 Turnstile 容器')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const siteKey = import.meta.env.VITE_APP_TURNSTILE_SITE_KEY || '0x4AAAAAAAK0rP_gCjP2oJDa'
|
||||||
|
console.log('使用 Site Key:', siteKey)
|
||||||
|
|
||||||
|
turnstileWidgetId.value = window.turnstile.render('#turnstile-container', {
|
||||||
|
sitekey: siteKey,
|
||||||
|
theme: 'light',
|
||||||
|
callback: function (token) {
|
||||||
|
console.log('Turnstile 验证成功,token:', token)
|
||||||
|
turnstileToken.value = token
|
||||||
|
},
|
||||||
|
'error-callback': function (error) {
|
||||||
|
console.error('Turnstile 验证失败:', error)
|
||||||
|
// 测试环境下,如果验证失败,使用测试 token
|
||||||
|
if (siteKey === '1x00000000000000000000AA') {
|
||||||
|
console.log('测试模式:使用模拟 token')
|
||||||
|
turnstileToken.value = 'XXXX.DUMMY.TOKEN.XXXX'
|
||||||
|
} else {
|
||||||
|
turnstileToken.value = ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'expired-callback': function () {
|
||||||
|
console.warn('Turnstile token 已过期')
|
||||||
|
turnstileToken.value = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
console.log('Turnstile 组件已渲染,Widget ID:', turnstileWidgetId.value)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Turnstile 渲染错误:', error)
|
||||||
|
}
|
||||||
|
} else if (!window.turnstile) {
|
||||||
|
// SDK 还未加载,100ms 后重试
|
||||||
|
setTimeout(() => waitForTurnstile(retries + 1), 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
waitForTurnstile()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置 Turnstile 验证码
|
||||||
|
const resetTurnstile = () => {
|
||||||
|
if (window.turnstile && turnstileWidgetId.value) {
|
||||||
|
window.turnstile.reset(turnstileWidgetId.value)
|
||||||
|
turnstileToken.value = ''
|
||||||
|
console.log('Turnstile 已重置')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交注册
|
||||||
|
const handleRegister = async () => {
|
||||||
|
loginLoading.value = true
|
||||||
|
try {
|
||||||
|
const form = unref(formLogin)
|
||||||
|
if (!form) return
|
||||||
|
const valid = await form.validate()
|
||||||
|
if (!valid) return
|
||||||
|
|
||||||
|
if (registerData.captchaEnable === 'true' && !turnstileToken.value) {
|
||||||
|
ElMessage.warning('请完成人机验证')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (registerData.captchaEnable) {
|
||||||
|
registerData.registerForm.turnstileToken = turnstileToken.value
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('提交注册,turnstileToken:', turnstileToken.value)
|
||||||
|
const res = await LoginApi.tenantRegister(registerData.registerForm)
|
||||||
|
if (!res) {
|
||||||
|
// 注册失败,重置验证码
|
||||||
|
resetTurnstile()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log(res, '注册成功返回')
|
||||||
|
|
||||||
|
// 注册成功,保存账号和租户名到缓存,用于自动填充登录表单
|
||||||
|
authUtil.setLoginForm({
|
||||||
|
tenantName: registerData.registerForm.name,
|
||||||
|
username: registerData.registerForm.username,
|
||||||
|
password: '',
|
||||||
|
rememberMe: true
|
||||||
|
})
|
||||||
|
|
||||||
|
// 提示注册成功
|
||||||
|
ElMessage.success('注册成功,请登录')
|
||||||
|
|
||||||
|
// 跳转回登录页
|
||||||
|
handleBackLogin()
|
||||||
|
} catch (error) {
|
||||||
|
// 请求异常时也要重置验证码
|
||||||
|
console.error('注册失败:', error)
|
||||||
|
resetTurnstile()
|
||||||
|
} finally {
|
||||||
|
loginLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取验证码 / 提交
|
||||||
|
const getCode = async () => {
|
||||||
|
// Just call handleRegister directly now, as Turnstile is on-page
|
||||||
|
await handleRegister()
|
||||||
|
}
|
||||||
|
|
||||||
|
const loading = ref() // ElLoading.service 返回的实例
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => currentRoute.value,
|
||||||
|
(route: RouteLocationNormalizedLoaded) => {
|
||||||
|
redirect.value = route?.query?.redirect as string
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => getShow.value,
|
||||||
|
(isShow) => {
|
||||||
|
console.log('getShow 变化:', isShow)
|
||||||
|
console.log('captchaEnable:', registerData.captchaEnable)
|
||||||
|
console.log('环境变量 CAPTCHA_ENABLE:', import.meta.env.VITE_APP_CAPTCHA_ENABLE)
|
||||||
|
console.log('环境变量 SITE_KEY:', import.meta.env.VITE_APP_TURNSTILE_SITE_KEY)
|
||||||
|
|
||||||
|
if (isShow && registerData.captchaEnable === 'true') {
|
||||||
|
console.log('准备初始化 Turnstile')
|
||||||
|
nextTick(() => {
|
||||||
|
console.log('nextTick 中初始化 Turnstile')
|
||||||
|
initTurnstile()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
console.log('组件已挂载')
|
||||||
|
console.log('getShow:', getShow.value)
|
||||||
|
console.log('captchaEnable:', registerData.captchaEnable)
|
||||||
|
console.log('window.turnstile:', window.turnstile)
|
||||||
|
|
||||||
|
// Check if already visible on mount (unlikely but possible)
|
||||||
|
if (getShow.value && registerData.captchaEnable === 'true') {
|
||||||
|
console.log('挂载时初始化 Turnstile')
|
||||||
|
initTurnstile()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
:deep(.anticon) {
|
||||||
|
&:hover {
|
||||||
|
color: var(--el-color-primary) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-code {
|
||||||
|
float: right;
|
||||||
|
width: 100%;
|
||||||
|
height: 38px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-width: 100px;
|
||||||
|
vertical-align: middle;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -5,5 +5,6 @@ import RegisterForm from './RegisterForm.vue'
|
|||||||
import QrCodeForm from './QrCodeForm.vue'
|
import QrCodeForm from './QrCodeForm.vue'
|
||||||
import SSOLoginVue from './SSOLogin.vue'
|
import SSOLoginVue from './SSOLogin.vue'
|
||||||
import ForgetPasswordForm from './ForgetPasswordForm.vue'
|
import ForgetPasswordForm from './ForgetPasswordForm.vue'
|
||||||
|
import TenantRegisterForm from './TenantRegisterForm.vue'
|
||||||
|
|
||||||
export { LoginForm, MobileForm, LoginFormTitle, RegisterForm, QrCodeForm, SSOLoginVue, ForgetPasswordForm }
|
export { LoginForm, MobileForm, LoginFormTitle, RegisterForm, QrCodeForm, SSOLoginVue, ForgetPasswordForm, TenantRegisterForm }
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Ref } from 'vue'
|
|||||||
export enum LoginStateEnum {
|
export enum LoginStateEnum {
|
||||||
LOGIN,
|
LOGIN,
|
||||||
REGISTER,
|
REGISTER,
|
||||||
|
TENANT_REGISTER,
|
||||||
RESET_PASSWORD,
|
RESET_PASSWORD,
|
||||||
MOBILE,
|
MOBILE,
|
||||||
QR_CODE,
|
QR_CODE,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Created by 芋道源码
|
* Created by YOLO
|
||||||
*
|
*
|
||||||
* AI 枚举类
|
* AI 枚举类
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Created by 芋道源码
|
* Created by YOLO
|
||||||
*
|
*
|
||||||
* AI 枚举类
|
* AI 枚举类
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<!--
|
<!--
|
||||||
- Copyright (C) 2018-2019
|
- Copyright (C) 2018-2019
|
||||||
- All rights reserved, Designed By www.joolun.com
|
- All rights reserved, Designed By www.joolun.com
|
||||||
芋道源码:
|
YOLO:
|
||||||
① 移除 avue 组件,使用 ElementUI 原生组件
|
① 移除 avue 组件,使用 ElementUI 原生组件
|
||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
@@ -21,12 +21,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 分页组件 -->
|
<!-- 分页组件 -->
|
||||||
<Pagination
|
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
|
||||||
:total="total"
|
@pagination="getMaterialPageFun" />
|
||||||
v-model:page="queryParams.pageNo"
|
|
||||||
v-model:limit="queryParams.pageSize"
|
|
||||||
@pagination="getMaterialPageFun"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- 类型:voice -->
|
<!-- 类型:voice -->
|
||||||
<div v-else-if="props.type === 'voice'">
|
<div v-else-if="props.type === 'voice'">
|
||||||
@@ -39,29 +35,18 @@
|
|||||||
<WxVoicePlayer :url="scope.row.url" />
|
<WxVoicePlayer :url="scope.row.url" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column label="上传时间" align="center" prop="createTime" width="180" :formatter="dateFormatter" />
|
||||||
label="上传时间"
|
|
||||||
align="center"
|
|
||||||
prop="createTime"
|
|
||||||
width="180"
|
|
||||||
:formatter="dateFormatter"
|
|
||||||
/>
|
|
||||||
<el-table-column label="操作" align="center" fixed="right">
|
<el-table-column label="操作" align="center" fixed="right">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button type="primary" link @click="selectMaterialFun(scope.row)"
|
<el-button type="primary" link @click="selectMaterialFun(scope.row)">选择
|
||||||
>选择
|
|
||||||
<Icon icon="ep:plus" />
|
<Icon icon="ep:plus" />
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<!-- 分页组件 -->
|
<!-- 分页组件 -->
|
||||||
<Pagination
|
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
|
||||||
:total="total"
|
@pagination="getPage" />
|
||||||
v-model:page="queryParams.pageNo"
|
|
||||||
v-model:limit="queryParams.pageSize"
|
|
||||||
@pagination="getPage"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- 类型:video -->
|
<!-- 类型:video -->
|
||||||
<div v-else-if="props.type === 'video'">
|
<div v-else-if="props.type === 'video'">
|
||||||
@@ -76,34 +61,18 @@
|
|||||||
<WxVideoPlayer :url="scope.row.url" />
|
<WxVideoPlayer :url="scope.row.url" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column label="上传时间" align="center" prop="createTime" width="180" :formatter="dateFormatter" />
|
||||||
label="上传时间"
|
<el-table-column label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
|
||||||
align="center"
|
|
||||||
prop="createTime"
|
|
||||||
width="180"
|
|
||||||
:formatter="dateFormatter"
|
|
||||||
/>
|
|
||||||
<el-table-column
|
|
||||||
label="操作"
|
|
||||||
align="center"
|
|
||||||
fixed="right"
|
|
||||||
class-name="small-padding fixed-width"
|
|
||||||
>
|
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button type="primary" link @click="selectMaterialFun(scope.row)"
|
<el-button type="primary" link @click="selectMaterialFun(scope.row)">选择
|
||||||
>选择
|
|
||||||
<Icon icon="akar-icons:circle-plus" />
|
<Icon icon="akar-icons:circle-plus" />
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<!-- 分页组件 -->
|
<!-- 分页组件 -->
|
||||||
<Pagination
|
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
|
||||||
:total="total"
|
@pagination="getMaterialPageFun" />
|
||||||
v-model:page="queryParams.pageNo"
|
|
||||||
v-model:limit="queryParams.pageSize"
|
|
||||||
@pagination="getMaterialPageFun"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- 类型:news -->
|
<!-- 类型:news -->
|
||||||
<div v-else-if="props.type === 'news'">
|
<div v-else-if="props.type === 'news'">
|
||||||
@@ -121,12 +90,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 分页组件 -->
|
<!-- 分页组件 -->
|
||||||
<Pagination
|
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
|
||||||
:total="total"
|
@pagination="getMaterialPageFun" />
|
||||||
v-model:page="queryParams.pageNo"
|
|
||||||
v-model:limit="queryParams.pageSize"
|
|
||||||
@pagination="getMaterialPageFun"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -229,7 +194,7 @@ onMounted(async () => {
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@media (width >= 992px) and (width <= 1300px) {
|
@media (width >=992px) and (width <=1300px) {
|
||||||
.waterfall {
|
.waterfall {
|
||||||
column-count: 3;
|
column-count: 3;
|
||||||
}
|
}
|
||||||
@@ -239,7 +204,7 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (width >= 768px) and (width <= 991px) {
|
@media (width >=768px) and (width <=991px) {
|
||||||
.waterfall {
|
.waterfall {
|
||||||
column-count: 2;
|
column-count: 2;
|
||||||
}
|
}
|
||||||
@@ -249,7 +214,7 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (width <= 767px) {
|
@media (width <=767px) {
|
||||||
.waterfall {
|
.waterfall {
|
||||||
column-count: 1;
|
column-count: 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<!--
|
<!--
|
||||||
- Copyright (C) 2018-2019
|
- Copyright (C) 2018-2019
|
||||||
- All rights reserved, Designed By www.joolun.com
|
- All rights reserved, Designed By www.joolun.com
|
||||||
芋道源码:
|
YOLO:
|
||||||
① 移除暂时用不到的 websocket
|
① 移除暂时用不到的 websocket
|
||||||
② 代码优化,补充注释,提升阅读性
|
② 代码优化,补充注释,提升阅读性
|
||||||
-->
|
-->
|
||||||
@@ -11,12 +11,9 @@
|
|||||||
<!-- 加载更多 -->
|
<!-- 加载更多 -->
|
||||||
<div v-loading="loading"></div>
|
<div v-loading="loading"></div>
|
||||||
<div v-if="!loading">
|
<div v-if="!loading">
|
||||||
<div class="el-table__empty-block" v-if="hasMore" @click="loadMore"
|
<div class="el-table__empty-block" v-if="hasMore" @click="loadMore"><span
|
||||||
><span class="el-table__empty-text">点击加载更多</span></div
|
class="el-table__empty-text">点击加载更多</span></div>
|
||||||
>
|
<div class="el-table__empty-block" v-if="!hasMore"><span class="el-table__empty-text">没有更多了</span></div>
|
||||||
<div class="el-table__empty-block" v-if="!hasMore"
|
|
||||||
><span class="el-table__empty-text">没有更多了</span></div
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 消息列表 -->
|
<!-- 消息列表 -->
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
- Copyright (C) 2018-2019
|
- Copyright (C) 2018-2019
|
||||||
- All rights reserved, Designed By www.joolun.com
|
- All rights reserved, Designed By www.joolun.com
|
||||||
【微信消息 - 图文】
|
【微信消息 - 图文】
|
||||||
芋道源码:
|
YOLO:
|
||||||
① 代码优化,补充注释,提升阅读性
|
① 代码优化,补充注释,提升阅读性
|
||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
@@ -12,11 +12,7 @@
|
|||||||
<a v-if="index === 0" :href="article.url" target="_blank">
|
<a v-if="index === 0" :href="article.url" target="_blank">
|
||||||
<div class="news-main">
|
<div class="news-main">
|
||||||
<div class="news-content">
|
<div class="news-content">
|
||||||
<el-image
|
<el-image :src="article.picUrl" class="material-img" style="width: 100%; height: 120px" />
|
||||||
:src="article.picUrl"
|
|
||||||
class="material-img"
|
|
||||||
style="width: 100%; height: 120px"
|
|
||||||
/>
|
|
||||||
<div class="news-content-title">
|
<div class="news-content-title">
|
||||||
<span>{{ article.title }}</span>
|
<span>{{ article.title }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<!--
|
<!--
|
||||||
- Copyright (C) 2018-2019
|
- Copyright (C) 2018-2019
|
||||||
- All rights reserved, Designed By www.joolun.com
|
- All rights reserved, Designed By www.joolun.com
|
||||||
芋道源码:
|
YOLO:
|
||||||
① 移除多余的 rep 为前缀的变量,让 message 消息更简单
|
① 移除多余的 rep 为前缀的变量,让 message 消息更简单
|
||||||
② 代码优化,补充注释,提升阅读性
|
② 代码优化,补充注释,提升阅读性
|
||||||
③ 优化消息的临时缓存策略,发送消息时,只清理被发送消息的 tab,不会强制切回到 text 输入
|
③ 优化消息的临时缓存策略,发送消息时,只清理被发送消息的 tab,不会强制切回到 text 输入
|
||||||
@@ -12,7 +12,9 @@
|
|||||||
<!-- 类型 1:文本 -->
|
<!-- 类型 1:文本 -->
|
||||||
<el-tab-pane :name="ReplyType.Text">
|
<el-tab-pane :name="ReplyType.Text">
|
||||||
<template #label>
|
<template #label>
|
||||||
<el-row align="middle"><Icon icon="ep:document" /> 文本</el-row>
|
<el-row align="middle">
|
||||||
|
<Icon icon="ep:document" /> 文本
|
||||||
|
</el-row>
|
||||||
</template>
|
</template>
|
||||||
<TabText v-model="reply.content" />
|
<TabText v-model="reply.content" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
@@ -20,7 +22,9 @@
|
|||||||
<!-- 类型 2:图片 -->
|
<!-- 类型 2:图片 -->
|
||||||
<el-tab-pane :name="ReplyType.Image">
|
<el-tab-pane :name="ReplyType.Image">
|
||||||
<template #label>
|
<template #label>
|
||||||
<el-row align="middle"><Icon icon="ep:picture" class="mr-5px" /> 图片</el-row>
|
<el-row align="middle">
|
||||||
|
<Icon icon="ep:picture" class="mr-5px" /> 图片
|
||||||
|
</el-row>
|
||||||
</template>
|
</template>
|
||||||
<TabImage v-model="reply" />
|
<TabImage v-model="reply" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
@@ -28,7 +32,9 @@
|
|||||||
<!-- 类型 3:语音 -->
|
<!-- 类型 3:语音 -->
|
||||||
<el-tab-pane :name="ReplyType.Voice">
|
<el-tab-pane :name="ReplyType.Voice">
|
||||||
<template #label>
|
<template #label>
|
||||||
<el-row align="middle"><Icon icon="ep:phone" /> 语音</el-row>
|
<el-row align="middle">
|
||||||
|
<Icon icon="ep:phone" /> 语音
|
||||||
|
</el-row>
|
||||||
</template>
|
</template>
|
||||||
<TabVoice v-model="reply" />
|
<TabVoice v-model="reply" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
@@ -36,7 +42,9 @@
|
|||||||
<!-- 类型 4:视频 -->
|
<!-- 类型 4:视频 -->
|
||||||
<el-tab-pane :name="ReplyType.Video">
|
<el-tab-pane :name="ReplyType.Video">
|
||||||
<template #label>
|
<template #label>
|
||||||
<el-row align="middle"><Icon icon="ep:share" /> 视频</el-row>
|
<el-row align="middle">
|
||||||
|
<Icon icon="ep:share" /> 视频
|
||||||
|
</el-row>
|
||||||
</template>
|
</template>
|
||||||
<TabVideo v-model="reply" />
|
<TabVideo v-model="reply" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
@@ -44,7 +52,9 @@
|
|||||||
<!-- 类型 5:图文 -->
|
<!-- 类型 5:图文 -->
|
||||||
<el-tab-pane :name="ReplyType.News">
|
<el-tab-pane :name="ReplyType.News">
|
||||||
<template #label>
|
<template #label>
|
||||||
<el-row align="middle"><Icon icon="ep:reading" /> 图文</el-row>
|
<el-row align="middle">
|
||||||
|
<Icon icon="ep:reading" /> 图文
|
||||||
|
</el-row>
|
||||||
</template>
|
</template>
|
||||||
<TabNews v-model="reply" :news-type="newsType" />
|
<TabNews v-model="reply" :news-type="newsType" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
@@ -52,7 +62,9 @@
|
|||||||
<!-- 类型 6:音乐 -->
|
<!-- 类型 6:音乐 -->
|
||||||
<el-tab-pane :name="ReplyType.Music">
|
<el-tab-pane :name="ReplyType.Music">
|
||||||
<template #label>
|
<template #label>
|
||||||
<el-row align="middle"><Icon icon="ep:service" />音乐</el-row>
|
<el-row align="middle">
|
||||||
|
<Icon icon="ep:service" />音乐
|
||||||
|
</el-row>
|
||||||
</template>
|
</template>
|
||||||
<TabMusic v-model="reply" />
|
<TabMusic v-model="reply" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
- Copyright (C) 2018-2019
|
- Copyright (C) 2018-2019
|
||||||
- All rights reserved, Designed By www.joolun.com
|
- All rights reserved, Designed By www.joolun.com
|
||||||
【微信消息 - 视频】
|
【微信消息 - 视频】
|
||||||
芋道源码:
|
YOLO:
|
||||||
① bug 修复:
|
① bug 修复:
|
||||||
1)joolun 的做法:使用 mediaId 从微信公众号,下载对应的 mp4 素材,从而播放内容;
|
1)joolun 的做法:使用 mediaId 从微信公众号,下载对应的 mp4 素材,从而播放内容;
|
||||||
存在的问题:mediaId 有效期是 3 天,超过时间后无法播放
|
存在的问题:mediaId 有效期是 3 天,超过时间后无法播放
|
||||||
@@ -20,18 +20,9 @@
|
|||||||
|
|
||||||
<!-- 弹窗播放 -->
|
<!-- 弹窗播放 -->
|
||||||
<el-dialog v-model="dialogVideo" title="视频播放" append-to-body>
|
<el-dialog v-model="dialogVideo" title="视频播放" append-to-body>
|
||||||
<video-player
|
<video-player v-if="dialogVideo" class="video-player vjs-big-play-centered" :src="props.url" poster=""
|
||||||
v-if="dialogVideo"
|
crossorigin="anonymous" controls playsinline :volume="0.6" :width="800"
|
||||||
class="video-player vjs-big-play-centered"
|
:playback-rates="[0.7, 1.0, 1.5, 2.0]" />
|
||||||
:src="props.url"
|
|
||||||
poster=""
|
|
||||||
crossorigin="anonymous"
|
|
||||||
controls
|
|
||||||
playsinline
|
|
||||||
:volume="0.6"
|
|
||||||
:width="800"
|
|
||||||
:playback-rates="[0.7, 1.0, 1.5, 2.0]"
|
|
||||||
/>
|
|
||||||
<!-- 事件,暫時沒用
|
<!-- 事件,暫時沒用
|
||||||
@mounted="handleMounted"-->
|
@mounted="handleMounted"-->
|
||||||
<!-- @ready="handleEvent($event)"-->
|
<!-- @ready="handleEvent($event)"-->
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
- Copyright (C) 2018-2019
|
- Copyright (C) 2018-2019
|
||||||
- All rights reserved, Designed By www.joolun.com
|
- All rights reserved, Designed By www.joolun.com
|
||||||
【微信消息 - 语音】
|
【微信消息 - 语音】
|
||||||
芋道源码:
|
YOLO:
|
||||||
① bug 修复:
|
① bug 修复:
|
||||||
1)joolun 的做法:使用 mediaId 从微信公众号,下载对应的 mp4 素材,从而播放内容;
|
1)joolun 的做法:使用 mediaId 从微信公众号,下载对应的 mp4 素材,从而播放内容;
|
||||||
存在的问题:mediaId 有效期是 3 天,超过时间后无法播放
|
存在的问题:mediaId 有效期是 3 天,超过时间后无法播放
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ const emit = defineEmits<{
|
|||||||
column-count: 5;
|
column-count: 5;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
|
|
||||||
/* 芋道源码:增加 10px,避免顶着上面 */
|
/* YOLO:增加 10px,避免顶着上面 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.waterfall-item {
|
.waterfall-item {
|
||||||
|
|||||||
@@ -55,8 +55,9 @@
|
|||||||
<el-form-item :label="t('newHosts.hostsCountryinfo')" prop="country">
|
<el-form-item :label="t('newHosts.hostsCountryinfo')" prop="country">
|
||||||
<el-select v-model="queryParams.country" :placeholder="t('newHosts.placeHostsCountry')" clearable
|
<el-select v-model="queryParams.country" :placeholder="t('newHosts.placeHostsCountry')" clearable
|
||||||
class="!w-240px">
|
class="!w-240px">
|
||||||
<el-option v-for="dict in countryinfoList" :key="dict.id" :label="dict.countryName"
|
<el-option v-for="dict in countryinfoList" :key="dict.id"
|
||||||
:value="dict.countryName" />
|
:label="t('newHosts.min') == '最小值' ? dict.countryName : dict.countryNameEnglish"
|
||||||
|
:value="t('newHosts.min') == '最小值' ? dict.countryName : dict.countryNameEnglish" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
@@ -313,11 +314,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<MobilePagination v-if="isMobile" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
|
<MobilePagination v-if="isMobile" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
|
||||||
:total="total" :page-sizes="[10, 20, 30, 50]" @size-change="getList()" @load="getList()" @load-pre="getList()" />
|
:total="total" :page-sizes="[10, 20, 30, 50]" @size-change="getList()" @load="getList()" @load-pre="getList()" />
|
||||||
<div v-if="isMobile" class="mobile-total">
|
<div v-if="isMobile" class="mobile-total">
|
||||||
<span class="mobile-total__label">共</span>
|
<span class="mobile-total__label">共</span>
|
||||||
<span class="mobile-total__value">{{ total }}</span>
|
<span class="mobile-total__value">{{ total }}</span>
|
||||||
<span class="mobile-total__suffix">条</span>
|
<span class="mobile-total__suffix">条</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- ✅ PC 显示分页,移动端隐藏 -->
|
<!-- ✅ PC 显示分页,移动端隐藏 -->
|
||||||
<Pagination v-if="!isMobile" :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
|
<Pagination v-if="!isMobile" :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
|
||||||
@pagination="getList" />
|
@pagination="getList" />
|
||||||
|
|||||||
@@ -53,8 +53,9 @@
|
|||||||
<el-form-item :label="t('newHosts.hostsCountryinfo')" prop="country">
|
<el-form-item :label="t('newHosts.hostsCountryinfo')" prop="country">
|
||||||
<el-select v-model="queryParams.country" :placeholder="t('newHosts.placeHostsCountry')" clearable
|
<el-select v-model="queryParams.country" :placeholder="t('newHosts.placeHostsCountry')" clearable
|
||||||
class="!w-240px">
|
class="!w-240px">
|
||||||
<el-option v-for="dict in countryinfoList" :key="dict.id" :label="dict.countryName"
|
<el-option v-for="dict in countryinfoList" :key="dict.id"
|
||||||
:value="dict.countryName" />
|
:label="t('newHosts.min') == '最小值' ? dict.countryName : dict.countryNameEnglish"
|
||||||
|
:value="t('newHosts.min') == '最小值' ? dict.countryName : dict.countryNameEnglish" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
|||||||
@@ -85,8 +85,9 @@
|
|||||||
<el-form-item :label="t('newHosts.hostsCountryinfo')" prop="country">
|
<el-form-item :label="t('newHosts.hostsCountryinfo')" prop="country">
|
||||||
<el-select v-model="queryParams.country" :placeholder="t('newHosts.placeHostsCountry')" clearable
|
<el-select v-model="queryParams.country" :placeholder="t('newHosts.placeHostsCountry')" clearable
|
||||||
class="!w-240px">
|
class="!w-240px">
|
||||||
<el-option v-for="dict in countryinfoList" :key="dict.id" :label="dict.countryName"
|
<el-option v-for="dict in countryinfoList" :key="dict.id"
|
||||||
:value="dict.countryName" />
|
:label="t('newHosts.min') == '最小值' ? dict.countryName : dict.countryNameEnglish"
|
||||||
|
:value="t('newHosts.min') == '最小值' ? dict.countryName : dict.countryNameEnglish" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog v-model="dialogVisible" :title="dialogTitle" width="800">
|
<Dialog v-model="dialogVisible" :title="dialogTitle" width="800">
|
||||||
<el-form
|
<el-form ref="formRef" v-loading="formLoading" :model="formData" :rules="formRules" label-width="80px">
|
||||||
ref="formRef"
|
|
||||||
v-loading="formLoading"
|
|
||||||
:model="formData"
|
|
||||||
:rules="formRules"
|
|
||||||
label-width="80px"
|
|
||||||
>
|
|
||||||
<el-form-item label="公告标题" prop="title">
|
<el-form-item label="公告标题" prop="title">
|
||||||
<el-input v-model="formData.title" placeholder="请输入公告标题" />
|
<el-input v-model="formData.title" placeholder="请输入公告标题" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -15,22 +9,21 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="公告类型" prop="type">
|
<el-form-item label="公告类型" prop="type">
|
||||||
<el-select v-model="formData.type" clearable placeholder="请选择公告类型">
|
<el-select v-model="formData.type" clearable placeholder="请选择公告类型">
|
||||||
<el-option
|
<el-option v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_NOTICE_TYPE)" :key="parseInt(dict.value as any)"
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_NOTICE_TYPE)"
|
:label="dict.label" :value="parseInt(dict.value as any)" />
|
||||||
:key="parseInt(dict.value as any)"
|
</el-select>
|
||||||
:label="dict.label"
|
</el-form-item>
|
||||||
:value="parseInt(dict.value as any)"
|
<el-form-item label="公告级别" prop="category">
|
||||||
/>
|
<el-select v-model="formData.category" clearable placeholder="请选择公告级别">
|
||||||
|
<el-option label="通知" value="info" />
|
||||||
|
<el-option label="警告" value="warning" />
|
||||||
|
<el-option label="紧急" value="danger" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="状态" prop="status">
|
<el-form-item label="状态" prop="status">
|
||||||
<el-select v-model="formData.status" clearable placeholder="请选择状态">
|
<el-select v-model="formData.status" clearable placeholder="请选择状态">
|
||||||
<el-option
|
<el-option v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" :key="parseInt(dict.value as any)"
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
:label="dict.label" :value="parseInt(dict.value as any)" />
|
||||||
:key="parseInt(dict.value as any)"
|
|
||||||
:label="dict.label"
|
|
||||||
:value="parseInt(dict.value as any)"
|
|
||||||
/>
|
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="备注" prop="remark">
|
<el-form-item label="备注" prop="remark">
|
||||||
@@ -60,6 +53,7 @@ const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
|||||||
const formData = ref({
|
const formData = ref({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
title: '',
|
title: '',
|
||||||
|
category: 'info',
|
||||||
type: undefined,
|
type: undefined,
|
||||||
content: '',
|
content: '',
|
||||||
status: CommonStatusEnum.ENABLE,
|
status: CommonStatusEnum.ENABLE,
|
||||||
@@ -68,6 +62,7 @@ const formData = ref({
|
|||||||
const formRules = reactive({
|
const formRules = reactive({
|
||||||
title: [{ required: true, message: '公告标题不能为空', trigger: 'blur' }],
|
title: [{ required: true, message: '公告标题不能为空', trigger: 'blur' }],
|
||||||
type: [{ required: true, message: '公告类型不能为空', trigger: 'change' }],
|
type: [{ required: true, message: '公告类型不能为空', trigger: 'change' }],
|
||||||
|
category: [{ required: true, message: '公告级别不能为空', trigger: 'change' }],
|
||||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }],
|
status: [{ required: true, message: '状态不能为空', trigger: 'change' }],
|
||||||
content: [{ required: true, message: '公告内容不能为空', trigger: 'blur' }]
|
content: [{ required: true, message: '公告内容不能为空', trigger: 'blur' }]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,55 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<!-- 搜索工作栏 -->
|
<!-- 搜索工作栏 -->
|
||||||
<el-form
|
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
|
||||||
class="-mb-15px"
|
|
||||||
:model="queryParams"
|
|
||||||
ref="queryFormRef"
|
|
||||||
:inline="true"
|
|
||||||
label-width="68px"
|
|
||||||
>
|
|
||||||
<el-form-item label="公告标题" prop="title">
|
<el-form-item label="公告标题" prop="title">
|
||||||
<el-input
|
<el-input v-model="queryParams.title" placeholder="请输入公告标题" clearable @keyup.enter="handleQuery"
|
||||||
v-model="queryParams.title"
|
class="!w-240px" />
|
||||||
placeholder="请输入公告标题"
|
|
||||||
clearable
|
|
||||||
@keyup.enter="handleQuery"
|
|
||||||
class="!w-240px"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="公告状态" prop="status">
|
<el-form-item label="公告状态" prop="status">
|
||||||
<el-select
|
<el-select v-model="queryParams.status" placeholder="请选择公告状态" clearable class="!w-240px">
|
||||||
v-model="queryParams.status"
|
<el-option v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" :key="dict.value" :label="dict.label"
|
||||||
placeholder="请选择公告状态"
|
:value="dict.value" />
|
||||||
clearable
|
|
||||||
class="!w-240px"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
|
||||||
:key="dict.value"
|
|
||||||
:label="dict.label"
|
|
||||||
:value="dict.value"
|
|
||||||
/>
|
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
<el-button @click="handleQuery">
|
||||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
<Icon icon="ep:search" class="mr-5px" /> 搜索
|
||||||
<el-button
|
</el-button>
|
||||||
type="primary"
|
<el-button @click="resetQuery">
|
||||||
plain
|
<Icon icon="ep:refresh" class="mr-5px" /> 重置
|
||||||
@click="openForm('create')"
|
</el-button>
|
||||||
v-hasPermi="['system:notice:create']"
|
<el-button type="primary" plain @click="openForm('create')" v-hasPermi="['system:notice:create']">
|
||||||
>
|
|
||||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button type="danger" plain :disabled="checkedIds.length === 0" @click="handleDeleteBatch"
|
||||||
type="danger"
|
v-hasPermi="['system:notice:delete']">
|
||||||
plain
|
|
||||||
:disabled="checkedIds.length === 0"
|
|
||||||
@click="handleDeleteBatch"
|
|
||||||
v-hasPermi="['system:notice:delete']"
|
|
||||||
>
|
|
||||||
<Icon icon="ep:delete" class="mr-5px" /> 批量删除
|
<Icon icon="ep:delete" class="mr-5px" /> 批量删除
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -62,6 +36,15 @@
|
|||||||
<el-table-column type="selection" width="55" />
|
<el-table-column type="selection" width="55" />
|
||||||
<el-table-column label="公告编号" align="center" prop="id" />
|
<el-table-column label="公告编号" align="center" prop="id" />
|
||||||
<el-table-column label="公告标题" align="center" prop="title" />
|
<el-table-column label="公告标题" align="center" prop="title" />
|
||||||
|
<el-table-column label="公告级别" align="center" prop="category">
|
||||||
|
<template #default="scope">
|
||||||
|
<!-- 根据公告级别显示不同的标签 -->
|
||||||
|
<el-tag v-if="scope.row.category === 'info'" type="success" disable-transitions>通知</el-tag>
|
||||||
|
<el-tag v-else-if="scope.row.category === 'warning'" type="warning" disable-transitions>警告</el-tag>
|
||||||
|
<el-tag v-else-if="scope.row.category === 'danger'" type="danger" disable-transitions>紧急</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
<el-table-column label="公告类型" align="center" prop="type">
|
<el-table-column label="公告类型" align="center" prop="type">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<dict-tag :type="DICT_TYPE.SYSTEM_NOTICE_TYPE" :value="scope.row.type" />
|
<dict-tag :type="DICT_TYPE.SYSTEM_NOTICE_TYPE" :value="scope.row.type" />
|
||||||
@@ -72,29 +55,14 @@
|
|||||||
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
|
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column label="创建时间" align="center" prop="createTime" width="180" :formatter="dateFormatter" />
|
||||||
label="创建时间"
|
|
||||||
align="center"
|
|
||||||
prop="createTime"
|
|
||||||
width="180"
|
|
||||||
:formatter="dateFormatter"
|
|
||||||
/>
|
|
||||||
<el-table-column label="操作" align="center">
|
<el-table-column label="操作" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button
|
<el-button link type="primary" @click="openForm('update', scope.row.id)"
|
||||||
link
|
v-hasPermi="['system:notice:update']">
|
||||||
type="primary"
|
|
||||||
@click="openForm('update', scope.row.id)"
|
|
||||||
v-hasPermi="['system:notice:update']"
|
|
||||||
>
|
|
||||||
编辑
|
编辑
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['system:notice:delete']">
|
||||||
link
|
|
||||||
type="danger"
|
|
||||||
@click="handleDelete(scope.row.id)"
|
|
||||||
v-hasPermi="['system:notice:delete']"
|
|
||||||
>
|
|
||||||
删除
|
删除
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button link @click="handlePush(scope.row.id)" v-hasPermi="['system:notice:update']">
|
<el-button link @click="handlePush(scope.row.id)" v-hasPermi="['system:notice:update']">
|
||||||
@@ -104,12 +72,8 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<!-- 分页 -->
|
<!-- 分页 -->
|
||||||
<Pagination
|
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
|
||||||
:total="total"
|
@pagination="getList" />
|
||||||
v-model:page="queryParams.pageNo"
|
|
||||||
v-model:limit="queryParams.pageSize"
|
|
||||||
@pagination="getList"
|
|
||||||
/>
|
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
@@ -178,7 +142,7 @@ const handleDelete = async (id: number) => {
|
|||||||
message.success(t('common.delSuccess'))
|
message.success(t('common.delSuccess'))
|
||||||
// 刷新列表
|
// 刷新列表
|
||||||
await getList()
|
await getList()
|
||||||
} catch {}
|
} catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 批量删除按钮操作 */
|
/** 批量删除按钮操作 */
|
||||||
@@ -196,7 +160,7 @@ const handleDeleteBatch = async () => {
|
|||||||
message.success(t('common.delSuccess'))
|
message.success(t('common.delSuccess'))
|
||||||
// 刷新列表
|
// 刷新列表
|
||||||
await getList()
|
await getList()
|
||||||
} catch {}
|
} catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 推送按钮操作 */
|
/** 推送按钮操作 */
|
||||||
@@ -207,7 +171,7 @@ const handlePush = async (id: number) => {
|
|||||||
// 发起推送
|
// 发起推送
|
||||||
await NoticeApi.pushNotice(id)
|
await NoticeApi.pushNotice(id)
|
||||||
message.success(t('推送成功'))
|
message.success(t('推送成功'))
|
||||||
} catch {}
|
} catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
|
|||||||
@@ -43,14 +43,23 @@
|
|||||||
<el-date-picker v-model="formData.brotherExpireTime" clearable placeholder="请选择爬大哥过期时间" type="date"
|
<el-date-picker v-model="formData.brotherExpireTime" clearable placeholder="请选择爬大哥过期时间" type="date"
|
||||||
value-format="x" />
|
value-format="x" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item v-if="tenantLevel == 0" label="爬虫过期时间" prop="crawlExpireTime">
|
||||||
|
<el-date-picker v-model="formData.crawlExpireTime" clearable placeholder="请选择爬虫过期时间" type="date"
|
||||||
|
value-format="x" />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item v-if="tenantLevel == 0" label="爬虫后台过期时间" prop="expireTime">
|
|
||||||
|
<el-form-item v-if="tenantLevel == 0" label="后台过期时间" prop="expireTime">
|
||||||
<el-date-picker v-model="formData.expireTime" clearable placeholder="请选择过期时间" type="date" value-format="x" />
|
<el-date-picker v-model="formData.expireTime" clearable placeholder="请选择过期时间" type="date" value-format="x" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-else label="爬虫后台过期时间" prop="expireTime">
|
|
||||||
|
<el-form-item v-else label="后台过期时间" prop="expireTime">
|
||||||
<el-date-picker v-model="formData.expireTime" clearable placeholder="请选择过期时间" disabled type="date"
|
<el-date-picker v-model="formData.expireTime" clearable placeholder="请选择过期时间" disabled type="date"
|
||||||
value-format="x" />
|
value-format="x" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<el-form-item label="绑定域名" prop="website">
|
<el-form-item label="绑定域名" prop="website">
|
||||||
<el-input v-model="formData.website" placeholder="请输入绑定域名" />
|
<el-input v-model="formData.website" placeholder="请输入绑定域名" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -208,6 +217,7 @@ const formData = ref({
|
|||||||
remark: undefined as string | undefined,
|
remark: undefined as string | undefined,
|
||||||
aiExpireTime: undefined as number | undefined,
|
aiExpireTime: undefined as number | undefined,
|
||||||
brotherExpireTime: undefined as number | undefined,
|
brotherExpireTime: undefined as number | undefined,
|
||||||
|
crawlExpireTime: undefined as number | undefined,
|
||||||
tenantType: undefined as number | undefined,
|
tenantType: undefined as number | undefined,
|
||||||
parentId: undefined as number | undefined,
|
parentId: undefined as number | undefined,
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user