|
@@ -272,15 +272,87 @@ const getUsers = async () => {
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
**重要说明:**
|
|
**重要说明:**
|
|
|
-- 使用固定宽度(width)而不是最小宽度(min-width)可以避免标题栏分行
|
|
|
|
|
-- 序号列使用自定义模板实现连续序号,公式为`(currentPage - 1) * pageSize + $index + 1`,确保分页时序号连续
|
|
|
|
|
-- 序号列宽度使用60px,比原来的50px更合适,可以显示更大的页码
|
|
|
|
|
-- 操作列使用180px,足够容纳3个图标按钮
|
|
|
|
|
-- 状态列使用80px,足够显示状态标签
|
|
|
|
|
-- 用户名和账号列使用100px,适合大多数情况
|
|
|
|
|
-- 用户角色列使用150px,可以容纳多个角色名称(用逗号分隔)
|
|
|
|
|
-- 企业微信账号列使用120px,适合大多数微信号长度
|
|
|
|
|
-- 日期时间列使用160px,可以完整显示日期时间格式
|
|
|
|
|
|
|
+ - 使用固定宽度(width)而不是最小宽度(min-width)可以避免标题栏分行
|
|
|
|
|
+ - 序号列使用自定义模板实现连续序号,公式为`(currentPage - 1) * pageSize + $index + 1`,确保分页时序号连续
|
|
|
|
|
+ - 序号列宽度使用60px,比原来的50px更合适,可以显示更大的页码
|
|
|
|
|
+ - 操作列使用180px,足够容纳3个图标按钮
|
|
|
|
|
+ - 状态列使用80px,足够显示状态标签
|
|
|
|
|
+ - 用户名和账号列使用100px,适合大多数情况
|
|
|
|
|
+ - 用户角色列使用150px,可以容纳多个角色名称(用逗号分隔)
|
|
|
|
|
+ - 企业微信账号列使用120px,适合大多数微信号长度
|
|
|
|
|
+ - 日期时间列使用160px,可以完整显示日期时间格式
|
|
|
|
|
+
|
|
|
|
|
+### 2.4 图标按钮使用规范
|
|
|
|
|
+
|
|
|
|
|
+操作列应使用图标按钮,而不是文字按钮,使界面更加简洁美观。
|
|
|
|
|
+
|
|
|
|
|
+#### 2.4.1 图标按钮示例
|
|
|
|
|
+
|
|
|
|
|
+```vue
|
|
|
|
|
+<el-table-column label="操作" width="180" align="center" fixed="right">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-tooltip content="编辑" placement="top">
|
|
|
|
|
+ <el-button type="primary" :icon="Edit" circle size="small" @click="edit(row)"></el-button>
|
|
|
|
|
+ </el-tooltip>
|
|
|
|
|
+ <el-tooltip content="删除" placement="top">
|
|
|
|
|
+ <el-button type="danger" :icon="Delete" circle size="small" @click="delete(row.id)"></el-button>
|
|
|
|
|
+ </el-tooltip>
|
|
|
|
|
+ </template>
|
|
|
|
|
+</el-table-column>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 2.4.2 图标导入和使用
|
|
|
|
|
+
|
|
|
|
|
+```javascript
|
|
|
|
|
+import { Edit, Delete, User, Lock, Search } from '@element-plus/icons-vue'
|
|
|
|
|
+
|
|
|
|
|
+export default {
|
|
|
|
|
+ components: {
|
|
|
|
|
+ Edit,
|
|
|
|
|
+ Delete,
|
|
|
|
|
+ User,
|
|
|
|
|
+ Lock,
|
|
|
|
|
+ Search
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 2.4.3 常用图标说明
|
|
|
|
|
+
|
|
|
|
|
+| 图标 | 名称 | 用途 |
|
|
|
|
|
+|------|------|------|
|
|
|
|
|
+| `Edit` | 编辑 | 修改数据 |
|
|
|
|
|
+| `Delete` | 删除 | 删除数据 |
|
|
|
|
|
+| `User` | 用户 | 用户/人员管理 |
|
|
|
|
|
+| `Lock` | 锁定 | 锁定/重置密码 |
|
|
|
|
|
+| `Search` | 搜索 | 搜索/查询 |
|
|
|
|
|
+| `Plus` | 加号 | 新增 |
|
|
|
|
|
+| `View` | 查看 | 查看详情 |
|
|
|
|
|
+| `Download` | 下载 | 下载文件 |
|
|
|
|
|
+| `Upload` | 上传 | 上传文件 |
|
|
|
|
|
+| `Refresh` | 刷新 | 刷新数据 |
|
|
|
|
|
+
|
|
|
|
|
+#### 2.4.4 图标按钮属性说明
|
|
|
|
|
+
|
|
|
|
|
+- `type`:按钮类型,如`primary`、`success`、`warning`、`danger`、`info`
|
|
|
|
|
+- `:icon`:图标组件,如`:icon="Edit"`
|
|
|
|
|
+- `circle`:圆形按钮
|
|
|
|
|
+- `size`:按钮尺寸,推荐使用`small`
|
|
|
|
|
+- `@click`:点击事件
|
|
|
|
|
+
|
|
|
|
|
+#### 2.4.5 Tooltip使用
|
|
|
|
|
+
|
|
|
|
|
+使用`el-tooltip`为图标按钮提供功能提示:
|
|
|
|
|
+
|
|
|
|
|
+```vue
|
|
|
|
|
+<el-tooltip content="编辑" placement="top">
|
|
|
|
|
+ <el-button type="primary" :icon="Edit" circle size="small" @click="edit(row)"></el-button>
|
|
|
|
|
+</el-tooltip>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**Tooltip属性:**
|
|
|
|
|
+- `content`:提示文本
|
|
|
|
|
+- `placement`:提示位置,可选值:`top`、`bottom`、`left`、`right`
|
|
|
|
|
|
|
|
### 2.3 日期时间格式化
|
|
### 2.3 日期时间格式化
|
|
|
|
|
|
|
@@ -592,3 +664,171 @@ request.interceptors.request.use(config => {
|
|
|
点击
|
|
点击
|
|
|
</el-button>
|
|
</el-button>
|
|
|
```
|
|
```
|
|
|
|
|
+
|
|
|
|
|
+## 11. 搜索下拉框规范
|
|
|
|
|
+
|
|
|
|
|
+### 11.1 适用场景
|
|
|
|
|
+
|
|
|
|
|
+对于选项数量较多的下拉选择框(如船公司、代理公司、港口等),应使用可搜索的下拉框,并实现远程搜索和分页加载功能。
|
|
|
|
|
+
|
|
|
|
|
+### 11.2 搜索下拉框实现
|
|
|
|
|
+
|
|
|
|
|
+#### 11.2.1 模板部分
|
|
|
|
|
+
|
|
|
|
|
+```vue
|
|
|
|
|
+<el-form-item label="船公司">
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="searchForm.shipownerId"
|
|
|
|
|
+ placeholder="请选择"
|
|
|
|
|
+ style="width: 180px"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ filterable
|
|
|
|
|
+ remote
|
|
|
|
|
+ :remote-method="searchShipCompany"
|
|
|
|
|
+ :loading="shipCompanyLoading"
|
|
|
|
|
+ @visible-change="handleShipCompanyVisibleChange"
|
|
|
|
|
+ @scroll="handleShipCompanyScroll"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="company in shipCompanyList"
|
|
|
|
|
+ :key="company.companyId"
|
|
|
|
|
+ :label="company.companyName"
|
|
|
|
|
+ :value="company.companyId">
|
|
|
|
|
+ </el-option>
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+</el-form-item>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 11.2.2 数据属性
|
|
|
|
|
+
|
|
|
|
|
+```javascript
|
|
|
|
|
+data() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ searchForm: {
|
|
|
|
|
+ shipownerId: ''
|
|
|
|
|
+ },
|
|
|
|
|
+ shipCompanyList: [],
|
|
|
|
|
+ shipCompanyQuery: '',
|
|
|
|
|
+ shipCompanyPage: 1,
|
|
|
|
|
+ shipCompanyPageSize: 10,
|
|
|
|
|
+ shipCompanyLoading: false,
|
|
|
|
|
+ shipCompanyHasMore: true
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 11.2.3 方法实现
|
|
|
|
|
+
|
|
|
|
|
+```javascript
|
|
|
|
|
+methods: {
|
|
|
|
|
+ getShipCompanyList() {
|
|
|
|
|
+ this.getShipCompanyListByPage('', 1);
|
|
|
|
|
+ },
|
|
|
|
|
+ getShipCompanyListByPage(query, page) {
|
|
|
|
|
+ this.shipCompanyLoading = true;
|
|
|
|
|
+ this.$axios.get('/api/ship-company/list', {
|
|
|
|
|
+ params: {
|
|
|
|
|
+ companyName: query,
|
|
|
|
|
+ page: page,
|
|
|
|
|
+ pageSize: this.shipCompanyPageSize
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ .then(response => {
|
|
|
|
|
+ if (page === 1) {
|
|
|
|
|
+ this.shipCompanyList = response.data.content || [];
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.shipCompanyList = [...this.shipCompanyList, ...(response.data.content || [])];
|
|
|
|
|
+ }
|
|
|
|
|
+ this.shipCompanyHasMore = response.data.content && response.data.content.length >= this.shipCompanyPageSize;
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch(error => {
|
|
|
|
|
+ console.error('获取船公司列表失败:', error);
|
|
|
|
|
+ this.$message.error('获取船公司列表失败,请稍后重试');
|
|
|
|
|
+ })
|
|
|
|
|
+ .finally(() => {
|
|
|
|
|
+ this.shipCompanyLoading = false;
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ searchShipCompany(query) {
|
|
|
|
|
+ this.shipCompanyQuery = query || '';
|
|
|
|
|
+ this.shipCompanyPage = 1;
|
|
|
|
|
+ this.getShipCompanyListByPage(this.shipCompanyQuery, this.shipCompanyPage);
|
|
|
|
|
+ },
|
|
|
|
|
+ handleShipCompanyVisibleChange(visible) {
|
|
|
|
|
+ if (visible && !this.shipCompanyQuery) {
|
|
|
|
|
+ this.shipCompanyPage = 1;
|
|
|
|
|
+ this.getShipCompanyListByPage('', this.shipCompanyPage);
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ handleShipCompanyScroll() {
|
|
|
|
|
+ if (this.shipCompanyHasMore && !this.shipCompanyLoading) {
|
|
|
|
|
+ this.shipCompanyPage++;
|
|
|
|
|
+ this.getShipCompanyListByPage(this.shipCompanyQuery, this.shipCompanyPage);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 11.3 后端API规范
|
|
|
|
|
+
|
|
|
|
|
+#### 11.3.1 Controller实现
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+@GetMapping("/list")
|
|
|
|
|
+public Page<ShipCompanyDTO> list(
|
|
|
|
|
+ @RequestParam(required = false) String companyName,
|
|
|
|
|
+ @RequestParam(defaultValue = "1") int page,
|
|
|
|
|
+ @RequestParam(defaultValue = "10") int pageSize
|
|
|
|
|
+) {
|
|
|
|
|
+ Pageable pageable = PageRequest.of(page - 1, pageSize, Sort.by("companyName").ascending());
|
|
|
|
|
+ return shipCompanyService.searchCompanies(companyName, pageable);
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 11.3.2 Service实现
|
|
|
|
|
+
|
|
|
|
|
+```java
|
|
|
|
|
+@Service
|
|
|
|
|
+public class ShipCompanyService {
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private ShipCompanyRepository shipCompanyRepository;
|
|
|
|
|
+
|
|
|
|
|
+ public Page<ShipCompanyDTO> searchCompanies(String companyName, Pageable pageable) {
|
|
|
|
|
+ Specification<ShipCompany> spec = (root, query, cb) -> {
|
|
|
|
|
+ List<Predicate> predicates = new ArrayList<>();
|
|
|
|
|
+
|
|
|
|
|
+ if (companyName != null && !companyName.isEmpty()) {
|
|
|
|
|
+ predicates.add(cb.like(root.get("companyName"), "%" + companyName + "%"));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ predicates.add(cb.equal(root.get("recordStatus"), 1));
|
|
|
|
|
+
|
|
|
|
|
+ return cb.and(predicates.toArray(new Predicate[0]));
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ Page<ShipCompany> companies = shipCompanyRepository.findAll(spec, pageable);
|
|
|
|
|
+ return companies.map(ShipCompanyDTO::fromEntity);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 11.4 关键属性说明
|
|
|
|
|
+
|
|
|
|
|
+| 属性 | 说明 |
|
|
|
|
|
+|------|------|
|
|
|
|
|
+| filterable | 启用本地过滤功能 |
|
|
|
|
|
+| remote | 启用远程搜索 |
|
|
|
|
|
+| remote-method | 远程搜索方法,当用户输入时触发 |
|
|
|
|
|
+| loading | 加载状态,用于显示加载动画 |
|
|
|
|
|
+| visible-change | 下拉框显示/隐藏时触发 |
|
|
|
|
|
+| scroll | 下拉列表滚动时触发,用于实现瀑布加载 |
|
|
|
|
|
+
|
|
|
|
|
+### 11.5 注意事项
|
|
|
|
|
+
|
|
|
|
|
+1. **分页大小**:建议每页加载10-20条记录
|
|
|
|
|
+2. **防抖处理**:远程搜索建议添加防抖,避免频繁请求
|
|
|
|
|
+3. **错误处理**:必须添加错误处理和用户提示
|
|
|
|
|
+4. **加载状态**:必须显示加载状态,提升用户体验
|
|
|
|
|
+5. **清空处理**:清空搜索条件时,应重置分页和列表
|
|
|
|
|
+6. **首次加载**:下拉框首次打开时,应加载第一页数据
|