feat: 更新和原型图一致

main
sankeyangshu 2024-11-27 16:53:39 +08:00
parent 6602e58d19
commit 1496a8b5b9
87 changed files with 3152 additions and 3263 deletions

View File

@ -7,5 +7,6 @@ NODE_ENV = 'development'
# 本地环境接口地址
# 如果没有跨域问题,直接在这里配置即可
VITE_APP_BASE_API = 'http://192.168.1.116:8080'
# VITE_APP_BASE_API = 'http://192.168.1.116:8080'
VITE_APP_BASE_API = 'http://121.41.36.72:8080'
# VITE_APP_BASE_API = 'https://3lp9319797lh.vicp.fun'

View File

@ -1,5 +1,5 @@
import { PageRowsResult, PageRowsType } from '@/types';
import { ApiManageType, StatisticsType } from '@/types/help';
import { PageRowsResult } from '@/types';
import { ApiManagePageType, ApiManageType, StatisticsType } from '@/types/help';
import http from '@/utils/request';
// api接口
@ -9,6 +9,7 @@ const api = {
apiPage: '/cargo/api/page', // 分页列表
apiSave: '/cargo/api/save', // 保存
statistics: '/cargo/statistics/', // 首页数据统计
adImage: '/cargo/adver/image', // 广告图
};
/**
@ -31,10 +32,10 @@ export function postApiGetAPI(data: { id: number | string }) {
/**
*
* @param {PageRowsType} data
* @param {ApiManagePageType} data
* @return
*/
export function postApiPageAPI(data: Partial<PageRowsType>) {
export function postApiPageAPI(data: Partial<ApiManagePageType>) {
return http.post<PageRowsResult<ApiManageType>>(api.apiPage, data);
}
@ -54,3 +55,11 @@ export function postApiSaveAPI(data: Partial<ApiManageType>) {
export function getStatisticsAPI() {
return http.get<StatisticsType>(api.statistics);
}
/**
* 广
* @return
*/
export function getAdImageAPI() {
return http.get<string>(api.adImage);
}

View File

@ -1,5 +1,5 @@
import { PageRowsResult } from '@/types';
import { BoatInfoPageType, BoatInfoType } from '@/types/boatInfo';
import { BoatInfoPageType, BoatInfoType, vinSearchType } from '@/types/boatInfo';
import http from '@/utils/request';
// api接口
@ -10,7 +10,10 @@ const api = {
sailScheduleSave: '/cargo/sail_schedule/save', // 保存
sailScheduleTmpExport: '/cargo/sail_schedule/tmp/export', // 船期模板下载
sailScheduleImport: '/cargo/sail_schedule/import', // 导入
historyList: '/cargo/sail_schedule/history/page', // 历史数据
sailScheduleExport: '/cargo/sail_schedule/export', // 船期导出
publishHistoryList: '/cargo/sail_schedule/history/publish/page', // 我发布的历史数据
receiveHistoryList: '/cargo/sail_schedule/history/receive/page', // 我接受的历史数据
vinSearch: '/cargo/sail_schedule/vin/query', // 车架号查询
};
/**
@ -73,10 +76,39 @@ export function getSailScheduleImportAPI(data: { file: File }) {
}
/**
*
*
* @param {BoatInfoPageType} data
* @return
*/
export function getHistoryListAPI(data: Partial<BoatInfoPageType>) {
return http.post<PageRowsResult<BoatInfoType>>(api.historyList, data);
export function getSailScheduleExportAPI(data: Partial<BoatInfoPageType>) {
return http.get<ArrayBuffer>(api.sailScheduleExport, data, {
responseType: 'arraybuffer',
});
}
/**
*
* @param {BoatInfoPageType} data
* @return
*/
export function getPublishHistoryListAPI(data: Partial<BoatInfoPageType>) {
return http.post<PageRowsResult<BoatInfoType>>(api.publishHistoryList, data);
}
/**
*
* @param {BoatInfoPageType} data
* @return
*/
export function getReceiveHistoryListAPI(data: Partial<BoatInfoPageType>) {
return http.post<PageRowsResult<BoatInfoType>>(api.receiveHistoryList, data);
}
/**
*
* @param {BoatInfoPageType} data
* @return
*/
export function getVinSearchAPI(data: { vin: string }) {
return http.get<vinSearchType[]>(api.vinSearch, data);
}

View File

@ -10,7 +10,8 @@ const api = {
employeeGet: '/cargo/employee/get', // 获取
employeeList: '/cargo/employee/list', // 字典列表
employeePage: '/cargo/employee/page', // 分页列表
employeeSave: '/cargo/enterprise/employee/add', // 保存
employeeAdd: '/cargo/enterprise/employee/add', // 保存
employeeSave: '/cargo/employee/save', // 编辑
};
/**
@ -72,6 +73,15 @@ export function postEmployeeListAPI(data: dictionaryType) {
* @param {EmployeeType} data
* @return
*/
export function postEmployeeAddAPI(data: Partial<EmployeeType>) {
return http.post<string>(api.employeeAdd, data);
}
/**
*
* @param {EmployeeType} data
* @return
*/
export function postEmployeeSaveAPI(data: Partial<EmployeeType>) {
return http.post<string>(api.employeeSave, data);
}

View File

@ -1,5 +1,5 @@
import { PageRowsResult, PageRowsType } from '@/types';
import { HelpType } from '@/types/help';
import { agreeTextType, HelpType } from '@/types/help';
import http from '@/utils/request';
// api接口
@ -8,6 +8,10 @@ const api = {
helpGet: '/cargo/help/get', // 获取
helpPage: '/cargo/help/page', // 分页列表
helpSave: '/cargo/help/save', // 保存
textAgree: '/cargo/text/agree', // 同意协议
textGet: '/cargo/text/get', // 获取协议
textSave: '/cargo/text/save', // 保存
textIsAgree: '/cargo/text/sign', // 是否同意
};
/**
@ -45,3 +49,36 @@ export function postHelpPageAPI(data: Partial<PageRowsType>) {
export function postHelpSaveAPI(data: Partial<HelpType>) {
return http.post<string>(api.helpSave, data);
}
/**
*
* @return
*/
export function postTextAgreeAPI() {
return http.post<string>(api.textAgree);
}
/**
*
* @return
*/
export function postTextGetAPI(data: { textType: string }) {
return http.get<agreeTextType>(api.textGet, data);
}
/**
*
* @param {HelpType} data
* @return
*/
export function postTextSaveAPI(data: Partial<agreeTextType>) {
return http.post<string>(api.textSave, data);
}
/**
*
* @return
*/
export function postTextIsAgreeAPI() {
return http.post<boolean>(api.textIsAgree);
}

View File

@ -10,6 +10,7 @@ const api = {
manifestSave: '/cargo/manifest/save', // 保存
manifestImport: '/cargo/manifest/import', // 舱单导入
manifestExport: '/cargo/manifest/tmp/export', // 下载模版
manifestFileExport: '/cargo/manifest/export', // 舱单导出
};
/**
@ -70,3 +71,13 @@ export function getManifestTmpExportAPI() {
}
);
}
/**
*
* @return
*/
export function getManifestFileExportAPI(data: Partial<ManifestPageType>) {
return http.get<ArrayBuffer>(api.manifestFileExport, data, {
responseType: 'arraybuffer',
});
}

View File

@ -21,5 +21,5 @@ export function getAppListAPI() {
* @return
*/
export function getMenuTreeAPI(data: menuParamsType) {
return http.post<menuType[]>(api.menuTree, data);
return http.postParams<menuType[]>(api.menuTree, data);
}

View File

@ -1,29 +1,14 @@
import { dictionaryListType, dictionaryType, PageRowsResult } from '@/types';
import { getRoleListType, rolePremType, roleType } from '@/types/role';
import { rolePremType } from '@/types/role';
import http from '@/utils/request';
// api接口
const api = {
roleSave: '/admin/role/save', // 角色保存
rolePremSave: '/admin/role/perm/save', // 添加角色操作权限
rolePage: '/admin/role/page', // 角色分页列表
roleList: '/admin/role/list', // 角色字典列表
roleGet: '/admin/role/get', // 角色详情
roleDelete: '/admin/role/delete', // 角色删除
roleMenuList: '/admin/role/perm/list', // 角色菜单列表
rolePremSave: '/admin/user/perm/save', // 添加角色操作权限
roleMenuList: '/admin/user/perm/list', // 用户菜单列表
};
/**
*
* @param {roleType} data
* @return
*/
export function postRoleSaveAPI(data: roleType) {
return http.post<string>(api.roleSave, data);
}
/**
*
*
* @param {rolePremType} data
* @return
*/
@ -32,46 +17,10 @@ export function postRolePremSaveAPI(data: rolePremType) {
}
/**
*
* @param {getUserListType} data
* @return
*/
export function postRolePageAPI(data: Partial<getRoleListType>) {
return http.post<PageRowsResult<roleType>>(api.rolePage, data);
}
/**
*
* @param {dictionaryType} data
* @return
*/
export function getRoleListAPI(data: dictionaryType) {
return http.postParams<dictionaryListType[]>(api.roleList, data);
}
/**
*
* @param {string|number} data id
* @return
*/
export function getRoleGetAPI(data: { id: number | string }) {
return http.postParams<roleType>(api.roleGet, data);
}
/**
*
* @param {number} data id
* @return
*/
export function getRoleDeleteAPI(data: number[]) {
return http.post<string>(api.roleDelete, data);
}
/**
*
*
* @param {string|number} data id
* @return
*/
export function getRoleMenuListAPI(data: { roleId: number | string }) {
export function getUserRoleMenuListAPI(data: { userId: number | string }) {
return http.postParams<string[]>(api.roleMenuList, data);
}

View File

@ -1,26 +1,11 @@
import { dictionaryListType, dictionaryType, PageRowsResult } from '@/types';
import { roleType } from '@/types/role';
import {
getUserListType,
loginDataType,
userInfoRepType,
userPasswordType,
userRoleType,
userType,
} from '@/types/user';
import { loginDataType, userInfoRepType, userPasswordType, userType } from '@/types/user';
import http from '@/utils/request';
// api接口
const api = {
login: '/admin/user/login', // 用户登录接口
userSave: '/admin/user/save', // 用户保存
userPassword: '/admin/user/password', // 用户修改密码
userPage: '/admin/user/page', // 用户分页列表
userList: '/admin/user/list', // 用户字典列表
userGet: '/admin/user/get', // 用户详情
userDelete: '/admin/user/delete', // 用户删除
userRole: '/admin/user/role/add', // 用户添加角色
userRoleList: '/admin/user/role/list', // 用户角色列表
};
/**
@ -36,15 +21,6 @@ export function postLoginAPI(data: loginDataType) {
});
}
/**
*
* @param {userType} data
* @return
*/
export function postUserSaveAPI(data: userType) {
return http.post<string>(api.userSave, data);
}
/**
*
* @param {userPasswordType} data
@ -54,24 +30,6 @@ export function postUserPasswordAPI(data: userPasswordType) {
return http.post<string>(api.userPassword, data);
}
/**
*
* @param {getUserListType} data
* @return
*/
export function postUserPageAPI(data: Partial<getUserListType>) {
return http.post<PageRowsResult<userType>>(api.userPage, data);
}
/**
*
* @param {dictionaryType} data
* @return
*/
export function getUserListAPI(data: dictionaryType) {
return http.postParams<dictionaryListType[]>(api.userList, data);
}
/**
*
* @param {string|number} data id
@ -80,30 +38,3 @@ export function getUserListAPI(data: dictionaryType) {
export function getUserGetAPI(data: { id: number | string }) {
return http.postParams<userType>(api.userGet, data);
}
/**
*
* @param {number} data id
* @return
*/
export function getUserDeleteAPI(data: number[]) {
return http.post<string>(api.userDelete, data);
}
/**
*
* @param {userRoleType} data
* @return
*/
export function postUserRoleAPI(data: userRoleType) {
return http.post<string>(api.userRole, data);
}
/**
*
* @param {string|number} data id
* @return
*/
export function getUserRoleListAPI(data: { userId: number | string }) {
return http.postParams<roleType[]>(api.userRoleList, data);
}

View File

@ -1,5 +1,5 @@
import { dictionaryListType, dictionaryType, PageRowsResult } from '@/types';
import { LoadWharfPageType, LoadWharfType } from '@/types/boatInfo';
import { LoadInfoType, LoadWharfPageType, LoadWharfType } from '@/types/boatInfo';
import http from '@/utils/request';
// api接口
@ -9,6 +9,8 @@ const api = {
wharfList: '/cargo/wharf/list', // 字典列表
wharfPage: '/cargo/wharf/page', // 分页列表
wharfSave: '/cargo/wharf/save', // 保存
wharfInfo: '/cargo/wharf/get/info', // 获取绑定的码头信息
wharfPhoto: '/cargo/wharf/image/', // 获取绑定的码头图片
};
/**
@ -55,3 +57,25 @@ export function postWharfPageAPI(data: Partial<LoadWharfPageType>) {
export function postWharfSaveAPI(data: Partial<LoadWharfType>) {
return http.post<string>(api.wharfSave, data);
}
/**
*
* @return
*/
export function postWharfInfoAPI() {
return http.get<LoadInfoType>(api.wharfInfo);
}
/**
*
* @return
*/
export function getWharfPhotoAPI(id: string | number) {
return http.get<ArrayBuffer>(
`${api.wharfPhoto}/${id}`,
{},
{
responseType: 'arraybuffer',
}
);
}

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="32px" height="29px" xmlns="http://www.w3.org/2000/svg">
<g transform="matrix(1 0 0 1 -84 -186 )">
<path d="M 31.8064516129032 16.1945266272189 C 32.1290322580645 15.7655325443787 31.9139784946237 15.0147928994083 31.2688172043011 14.6930473372781 L 26.9677419354839 12.8698224852071 L 26.9677419354839 7.40014792899408 C 26.9677419354839 6.86390532544379 26.6451612903226 6.43491124260355 26.2150537634409 6.22041420118343 L 18.4731182795699 3.21745562130178 L 18.4731182795699 2.46671597633136 C 18.4731182795699 1.07248520710059 17.3978494623656 0 16 0 C 14.6021505376344 0 13.5268817204301 1.07248520710059 13.5268817204301 2.46671597633136 L 13.5268817204301 3.21745562130178 L 5.67741935483871 6.32766272189349 C 5.13978494623656 6.54215976331361 4.9247311827957 6.97115384615385 4.9247311827957 7.40014792899408 L 4.9247311827957 12.8698224852071 L 0.731182795698925 14.6930473372781 C 0.0860215053763442 15.0147928994083 -0.129032258064516 15.7655325443787 0.301075268817205 16.301775147929 L 6.75268817204301 26.5976331360947 C 6.86021505376344 26.8121301775148 7.0752688172043 26.9193786982249 7.29032258064516 27.0266272189349 C 8.47311827956989 27.241124260355 9.44086021505376 27.7773668639053 10.3010752688172 28.6353550295858 L 10.4086021505376 28.7426035502959 C 10.7311827956989 29.064349112426 11.3763440860215 29.064349112426 11.6989247311828 28.7426035502959 L 11.8064516129032 28.6353550295858 C 12.8817204301075 27.5628698224852 14.3870967741935 26.9193786982248 16 26.9193786982248 C 17.6129032258065 26.9193786982248 19.1182795698925 27.5628698224852 20.1935483870968 28.6353550295858 C 20.5161290322581 28.957100591716 21.1612903225806 28.957100591716 21.4838709677419 28.6353550295858 C 22.3440860215054 27.7773668639053 23.5268817204301 27.133875739645 24.7096774193548 26.9193786982248 C 24.9247311827957 26.9193786982248 25.1397849462366 26.7048816568047 25.3548387096774 26.4903846153846 L 31.8064516129032 16.1945266272189 Z M 24.494623655914 11.7973372781065 L 16.8602150537634 8.47263313609468 C 16.3225806451613 8.25813609467456 15.6774193548387 8.25813609467456 15.1397849462366 8.47263313609468 L 7.29032258064516 11.7973372781065 L 7.29032258064516 8.25813609467456 L 16 4.93343195266272 L 24.494623655914 8.25813609467456 L 24.494623655914 11.7973372781065 Z " fill-rule="nonzero" fill="#aaaaaa" stroke="none" transform="matrix(1 0 0 1 84 186 )" />
<path d="M 31.8064516129032 16.1945266272189 C 32.1290322580645 15.7655325443787 31.9139784946237 15.0147928994083 31.2688172043011 14.6930473372781 L 26.9677419354839 12.8698224852071 L 26.9677419354839 7.40014792899408 C 26.9677419354839 6.86390532544379 26.6451612903226 6.43491124260355 26.2150537634409 6.22041420118343 L 18.4731182795699 3.21745562130178 L 18.4731182795699 2.46671597633136 C 18.4731182795699 1.07248520710059 17.3978494623656 0 16 0 C 14.6021505376344 0 13.5268817204301 1.07248520710059 13.5268817204301 2.46671597633136 L 13.5268817204301 3.21745562130178 L 5.67741935483871 6.32766272189349 C 5.13978494623656 6.54215976331361 4.9247311827957 6.97115384615385 4.9247311827957 7.40014792899408 L 4.9247311827957 12.8698224852071 L 0.731182795698925 14.6930473372781 C 0.0860215053763442 15.0147928994083 -0.129032258064516 15.7655325443787 0.301075268817205 16.301775147929 L 6.75268817204301 26.5976331360947 C 6.86021505376344 26.8121301775148 7.0752688172043 26.9193786982249 7.29032258064516 27.0266272189349 C 8.47311827956989 27.241124260355 9.44086021505376 27.7773668639053 10.3010752688172 28.6353550295858 L 10.4086021505376 28.7426035502959 C 10.7311827956989 29.064349112426 11.3763440860215 29.064349112426 11.6989247311828 28.7426035502959 L 11.8064516129032 28.6353550295858 C 12.8817204301075 27.5628698224852 14.3870967741935 26.9193786982248 16 26.9193786982248 C 17.6129032258065 26.9193786982248 19.1182795698925 27.5628698224852 20.1935483870968 28.6353550295858 C 20.5161290322581 28.957100591716 21.1612903225806 28.957100591716 21.4838709677419 28.6353550295858 C 22.3440860215054 27.7773668639053 23.5268817204301 27.133875739645 24.7096774193548 26.9193786982248 C 24.9247311827957 26.9193786982248 25.1397849462366 26.7048816568047 25.3548387096774 26.4903846153846 L 31.8064516129032 16.1945266272189 Z M 24.494623655914 11.7973372781065 L 16.8602150537634 8.47263313609468 C 16.3225806451613 8.25813609467456 15.6774193548387 8.25813609467456 15.1397849462366 8.47263313609468 L 7.29032258064516 11.7973372781065 L 7.29032258064516 8.25813609467456 L 16 4.93343195266272 L 24.494623655914 8.25813609467456 L 24.494623655914 11.7973372781065 Z " fill-rule="nonzero" stroke="none" transform="matrix(1 0 0 1 84 186 )" />
</g>
</svg>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="32px" height="32px" xmlns="http://www.w3.org/2000/svg">
<g transform="matrix(1 0 0 1 -84 -593 )">
<path d="M 32 21.3333333333333 C 32 27.2 27.7333333333333 32 22.4 32 C 17.0666666666667 32 12.8 27.2 12.8 21.3333333333333 C 12.8 15.4666666666667 17.0666666666667 10.6666666666667 22.4 10.6666666666667 C 27.7333333333333 10.6666666666667 32 15.4666666666667 32 21.3333333333333 Z M 27.2 19.5555555555556 L 24 19.5555555555556 L 24 16 C 24 15.1111111111111 23.2888888888889 14.2222222222222 22.4 14.2222222222222 C 21.5111111111111 14.2222222222222 20.8 14.9333333333333 20.8 16 L 20.8 21.3333333333333 C 20.8 22.2222222222222 21.5111111111111 23.1111111111111 22.4 23.1111111111111 L 27.2 23.1111111111111 C 28.0888888888889 23.1111111111111 28.8 22.4 28.8 21.3333333333333 C 28.8 20.2666666666667 28.0888888888889 19.5555555555556 27.2 19.5555555555556 Z M 0 2.66666666666667 C 0 1.24444444444444 1.06666666666667 0 2.31111111111111 0 L 29.5111111111111 0 C 30.9333333333333 0 32 1.24444444444444 32 2.66666666666667 C 32 4.08888888888889 30.9333333333333 5.33333333333333 29.6888888888889 5.33333333333333 L 2.31111111111111 5.33333333333333 C 1.06666666666667 5.33333333333333 0 4.08888888888889 0 2.66666666666667 Z M 0 11.5555555555556 C 0 10.1333333333333 1.06666666666667 8.88888888888889 2.31111111111111 8.88888888888889 L 10.3111111111111 8.88888888888889 C 11.7333333333333 8.88888888888889 12.6222222222222 10.1333333333333 12.6222222222222 11.5555555555556 C 12.6222222222222 12.9777777777778 11.7333333333333 14.2222222222222 10.4888888888889 14.2222222222222 L 2.31111111111111 14.2222222222222 C 1.06666666666667 14.2222222222222 0 12.9777777777778 0 11.5555555555556 Z M 0 20.4444444444444 C 0 19.0222222222222 1.06666666666667 17.7777777777778 2.31111111111111 17.7777777777778 L 7.11111111111111 17.7777777777778 C 8.53333333333333 17.7777777777778 9.42222222222222 19.0222222222222 9.42222222222222 20.4444444444444 C 9.6 21.8666666666667 8.53333333333333 23.1111111111111 7.11111111111111 23.1111111111111 L 2.31111111111111 23.1111111111111 C 1.06666666666667 23.1111111111111 0 21.8666666666667 0 20.4444444444444 Z M 0 29.3333333333333 C 0 27.9111111111111 1.06666666666667 26.6666666666667 2.31111111111111 26.6666666666667 L 3.91111111111111 26.6666666666667 C 5.33333333333333 26.6666666666667 6.22222222222222 27.9111111111111 6.22222222222222 29.3333333333333 C 6.22222222222222 30.7555555555555 5.33333333333333 32 4.08888888888889 32 L 2.31111111111111 32 C 1.06666666666667 32 0 30.7555555555555 0 29.3333333333333 Z " fill-rule="nonzero" fill="#aaaaaa" stroke="none" transform="matrix(1 0 0 1 84 593 )" />
<path d="M 32 21.3333333333333 C 32 27.2 27.7333333333333 32 22.4 32 C 17.0666666666667 32 12.8 27.2 12.8 21.3333333333333 C 12.8 15.4666666666667 17.0666666666667 10.6666666666667 22.4 10.6666666666667 C 27.7333333333333 10.6666666666667 32 15.4666666666667 32 21.3333333333333 Z M 27.2 19.5555555555556 L 24 19.5555555555556 L 24 16 C 24 15.1111111111111 23.2888888888889 14.2222222222222 22.4 14.2222222222222 C 21.5111111111111 14.2222222222222 20.8 14.9333333333333 20.8 16 L 20.8 21.3333333333333 C 20.8 22.2222222222222 21.5111111111111 23.1111111111111 22.4 23.1111111111111 L 27.2 23.1111111111111 C 28.0888888888889 23.1111111111111 28.8 22.4 28.8 21.3333333333333 C 28.8 20.2666666666667 28.0888888888889 19.5555555555556 27.2 19.5555555555556 Z M 0 2.66666666666667 C 0 1.24444444444444 1.06666666666667 0 2.31111111111111 0 L 29.5111111111111 0 C 30.9333333333333 0 32 1.24444444444444 32 2.66666666666667 C 32 4.08888888888889 30.9333333333333 5.33333333333333 29.6888888888889 5.33333333333333 L 2.31111111111111 5.33333333333333 C 1.06666666666667 5.33333333333333 0 4.08888888888889 0 2.66666666666667 Z M 0 11.5555555555556 C 0 10.1333333333333 1.06666666666667 8.88888888888889 2.31111111111111 8.88888888888889 L 10.3111111111111 8.88888888888889 C 11.7333333333333 8.88888888888889 12.6222222222222 10.1333333333333 12.6222222222222 11.5555555555556 C 12.6222222222222 12.9777777777778 11.7333333333333 14.2222222222222 10.4888888888889 14.2222222222222 L 2.31111111111111 14.2222222222222 C 1.06666666666667 14.2222222222222 0 12.9777777777778 0 11.5555555555556 Z M 0 20.4444444444444 C 0 19.0222222222222 1.06666666666667 17.7777777777778 2.31111111111111 17.7777777777778 L 7.11111111111111 17.7777777777778 C 8.53333333333333 17.7777777777778 9.42222222222222 19.0222222222222 9.42222222222222 20.4444444444444 C 9.6 21.8666666666667 8.53333333333333 23.1111111111111 7.11111111111111 23.1111111111111 L 2.31111111111111 23.1111111111111 C 1.06666666666667 23.1111111111111 0 21.8666666666667 0 20.4444444444444 Z M 0 29.3333333333333 C 0 27.9111111111111 1.06666666666667 26.6666666666667 2.31111111111111 26.6666666666667 L 3.91111111111111 26.6666666666667 C 5.33333333333333 26.6666666666667 6.22222222222222 27.9111111111111 6.22222222222222 29.3333333333333 C 6.22222222222222 30.7555555555555 5.33333333333333 32 4.08888888888889 32 L 2.31111111111111 32 C 1.06666666666667 32 0 30.7555555555555 0 29.3333333333333 Z " fill-rule="nonzero" stroke="none" transform="matrix(1 0 0 1 84 593 )" />
</g>
</svg>

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="32px" height="29px" xmlns="http://www.w3.org/2000/svg">
<g transform="matrix(1 0 0 1 -84 -394 )">
<path d="M 31.8318401748088 4.05227163775821 C 32.0982363040425 2.9064139719341 31.8318401748088 1.82791880937854 31.0327142188232 1.0190632354223 L 30.899547370064 0.884275037043493 C 30.0338067738411 0.142813562276649 28.8351178398627 -0.126794430401814 27.769626970501 0.277633356576309 L 1.66483533635087 10.5231162294082 C 0.732542531606056 10.8601499171969 0.133198064616825 11.7363995903425 0.0665834243795847 12.7474690577878 C 0 13.7585385252332 0.466146402372405 14.7022138934891 1.33188699859529 15.1740357796566 L 6.85915405025753 18.4768837705918 C 7.19213360387077 18.6790976640809 7.59169658186359 18.5442778697812 7.79147807086 18.2072441819925 C 7.99125955985641 17.8702420901246 7.85806149523958 17.4658143031465 7.525113157484 17.2636004096575 L 1.9978148899641 14.0281781138325 C 1.59825191197128 13.7585385252332 1.33188699859529 13.354110738255 1.39847042297487 12.8822888520875 C 1.46505384735446 12.41046696592 1.73144997658811 12.0060391789419 2.19759637896051 11.8038252854528 L 28.3023880131107 1.55831081670008 C 28.7019509911035 1.42349102240042 29.1015139690963 1.42349102240042 29.4344623068519 1.55831081670008 L 10.1888559388169 18.5442778697812 C 9.5229280474481 19.1509195502484 9.12336506945528 20.0271692233941 9.12336506945528 20.97084459165 L 9.12336506945528 28.1831506580668 C 9.12336506945528 28.5875784450449 9.38972998283128 28.8571864377234 9.7892929608241 28.8571864377234 C 10.1888559388169 28.8571864377234 10.4552208521929 28.5875784450449 10.4552208521929 28.1831506580668 L 10.4552208521929 20.97084459165 C 10.4552208521929 20.4315970103722 10.6550023411893 19.8923810250153 11.1211799594194 19.5553473372265 L 30.4333697518339 2.43456048984573 C 30.6331512408303 2.83898827682385 30.6331512408303 3.24341606380197 30.5665678164508 3.64784385078009 L 25.7052286561573 22.4537359452628 C 25.4388325269237 23.3974113135187 24.7729046355549 24.2062668874749 23.9071952551896 24.5433005752637 C 23.0414858748244 24.8803342630524 21.9759637896051 24.8803026671315 21.1102544092399 24.3410866817746 L 14.6506321211175 20.6338109038612 C 14.3176837833619 20.4315970103722 13.9181208053691 20.5664168046718 13.7183393163727 20.9034504924606 C 13.5185578273763 21.2404525843284 13.6517246761355 21.6448803713065 13.9847042297487 21.8470942647956 L 20.4442953020134 25.6217641418984 C 21.1102544092399 26.0261919288765 21.9093803652255 26.2284058223656 22.6418916809739 26.2284058223656 C 23.2412361479632 26.2284058223656 23.7739971905728 26.0935860280659 24.373341657562 25.8913721345768 C 25.6386140159201 25.3521561492199 26.6375214609021 24.2736609866643 26.9705010145154 22.8581637322409 L 31.8318401748088 4.05227163775821 Z " fill-rule="nonzero" fill="#aaaaaa" stroke="none" transform="matrix(1 0 0 1 84 394 )" />
<path d="M 31.8318401748088 4.05227163775821 C 32.0982363040425 2.9064139719341 31.8318401748088 1.82791880937854 31.0327142188232 1.0190632354223 L 30.899547370064 0.884275037043493 C 30.0338067738411 0.142813562276649 28.8351178398627 -0.126794430401814 27.769626970501 0.277633356576309 L 1.66483533635087 10.5231162294082 C 0.732542531606056 10.8601499171969 0.133198064616825 11.7363995903425 0.0665834243795847 12.7474690577878 C 0 13.7585385252332 0.466146402372405 14.7022138934891 1.33188699859529 15.1740357796566 L 6.85915405025753 18.4768837705918 C 7.19213360387077 18.6790976640809 7.59169658186359 18.5442778697812 7.79147807086 18.2072441819925 C 7.99125955985641 17.8702420901246 7.85806149523958 17.4658143031465 7.525113157484 17.2636004096575 L 1.9978148899641 14.0281781138325 C 1.59825191197128 13.7585385252332 1.33188699859529 13.354110738255 1.39847042297487 12.8822888520875 C 1.46505384735446 12.41046696592 1.73144997658811 12.0060391789419 2.19759637896051 11.8038252854528 L 28.3023880131107 1.55831081670008 C 28.7019509911035 1.42349102240042 29.1015139690963 1.42349102240042 29.4344623068519 1.55831081670008 L 10.1888559388169 18.5442778697812 C 9.5229280474481 19.1509195502484 9.12336506945528 20.0271692233941 9.12336506945528 20.97084459165 L 9.12336506945528 28.1831506580668 C 9.12336506945528 28.5875784450449 9.38972998283128 28.8571864377234 9.7892929608241 28.8571864377234 C 10.1888559388169 28.8571864377234 10.4552208521929 28.5875784450449 10.4552208521929 28.1831506580668 L 10.4552208521929 20.97084459165 C 10.4552208521929 20.4315970103722 10.6550023411893 19.8923810250153 11.1211799594194 19.5553473372265 L 30.4333697518339 2.43456048984573 C 30.6331512408303 2.83898827682385 30.6331512408303 3.24341606380197 30.5665678164508 3.64784385078009 L 25.7052286561573 22.4537359452628 C 25.4388325269237 23.3974113135187 24.7729046355549 24.2062668874749 23.9071952551896 24.5433005752637 C 23.0414858748244 24.8803342630524 21.9759637896051 24.8803026671315 21.1102544092399 24.3410866817746 L 14.6506321211175 20.6338109038612 C 14.3176837833619 20.4315970103722 13.9181208053691 20.5664168046718 13.7183393163727 20.9034504924606 C 13.5185578273763 21.2404525843284 13.6517246761355 21.6448803713065 13.9847042297487 21.8470942647956 L 20.4442953020134 25.6217641418984 C 21.1102544092399 26.0261919288765 21.9093803652255 26.2284058223656 22.6418916809739 26.2284058223656 C 23.2412361479632 26.2284058223656 23.7739971905728 26.0935860280659 24.373341657562 25.8913721345768 C 25.6386140159201 25.3521561492199 26.6375214609021 24.2736609866643 26.9705010145154 22.8581637322409 L 31.8318401748088 4.05227163775821 Z " fill-rule="nonzero" stroke="none" transform="matrix(1 0 0 1 84 394 )" />
</g>
</svg>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="32px" height="36px" xmlns="http://www.w3.org/2000/svg">
<g transform="matrix(1 0 0 1 -84 -488 )">
<path d="M 18.6666666545139 13.4556212879068 L 23.999999984375 13.4556212879068 L 23.999999984375 8.13017750092455 C 23.999999984375 7.63998393215635 24.3979690984866 7.24260352949335 24.8888888796296 7.24260352949335 C 25.3798086607727 7.24260352949335 25.7777777748843 7.63998393215635 25.7777777748843 8.13017750092455 L 25.7777777748843 13.4556212879068 L 31.1111111047454 13.4556212879068 C 31.6020308858885 13.4556212879068 32 13.8530016905698 32 14.343195259338 C 32 14.8333888281062 31.6020308858885 15.2307692307692 31.1111111047454 15.2307692307692 L 25.7777777748843 15.2307692307692 L 25.7777777748843 20.5562130177515 C 25.7777777748843 21.0464065865197 25.3798086607727 21.4437869891827 24.8888888796296 21.4437869891827 C 24.3979690984866 21.4437869891827 23.999999984375 21.0464065865197 23.999999984375 20.5562130177515 L 23.999999984375 15.2307692307692 L 18.6666666545139 15.2307692307692 C 18.1757468733708 15.2307692307692 17.7777777592593 14.8333888281062 17.7777777592593 14.343195259338 C 17.7777777592593 13.8530016905698 18.1757468733708 13.4556212879068 18.6666666545139 13.4556212879068 Z M 30 20.6840236686391 L 30.2222221886574 20.6831361085429 C 30.2222221886574 20.1929425397747 30.620191302769 19.7955621371117 31.1111110839121 19.7955621371117 C 31.6020308650551 19.7955621371117 31.9999999791667 20.1929425397747 31.9999999791667 20.6831361085429 L 32 33.2662721754808 C 31.9995564818193 34.7365396105881 30.8057791101131 35.928192063087 29.3333334351962 35.928192063087 C 28.8188541791035 35.928192063087 28.3153585793775 35.779587327387 27.8835555235133 35.5002958441198 L 16.5048888642546 28.1378698086169 C 16.2109336845382 27.9478214882398 15.8326218362668 27.9478214882398 15.5386666565504 28.1378698086169 L 4.11377778898842 35.5082840098003 C 3.68254972175569 35.7864761256624 3.1800650159474 35.9344576611834 2.66666655930324 35.9344576611834 C 1.19420033215083 35.9344576611834 0.000414442319860577 34.7427731863621 0 33.2724852209689 L 0 6.35502959966716 C 0 2.92367461115139 2.78578377079869 0.142011820451183 6.22222220428241 0.142011820451183 L 25.7777777748843 0.142011820451183 C 29.214216208368 0.142011820451183 31.9999999791667 2.92367461115139 31.9999999791667 6.35502955806213 L 31.9999999791667 8.18343193879438 C 31.9999999791667 8.67362550756258 31.6020308650551 9.07100591022559 31.1111110839121 9.07100591022559 C 30.620191302769 9.07100591022559 30.2222221886574 8.67362550756259 30.2222221886574 8.18343193879438 L 30.2222221886574 6.35502959966716 C 30.2222221886574 3.90406178356279 28.2323766528219 1.91715978629143 25.7777777748843 2 L 6.22222220428241 2 C 3.76762332634482 1.91715978629143 1.77777779050926 3.90406178356279 2 6.35502959966716 L 2 33.2724852209689 C 1.77777776933179 33.2725643174599 1.77777775874305 33.2726434139586 1.77777775874305 33.2727225104524 C 1.77777775874305 33.7629160677317 2.17574686352721 34.1602964610811 2.66666663316435 34.1602964610811 C 2.83791950301204 34.1602964610811 3.00552670215961 34.1109001074683 3.14933333128299 34.0180473511464 L 14.5742221988449 26.6467455482618 C 15.4564039377868 26.0777934671316 16.5908684453466 26.0788359085129 17.471999988625 26.6494082701553 L 28.8497777728843 34.0118343056583 C 28.9937759684371 34.1050539462454 29.1617203167725 34.1546583128729 29.3333336863881 34.1546583128729 C 29.8242534560252 34.1546583128729 30.2222225608094 33.7572779195235 30.2222225608094 33.2670843622442 C 30.2222225608094 33.2668136332811 30.2222224367587 33.2665429043305 30 33.2662721754808 L 30 20.6840236686391 Z " fill-rule="nonzero" fill="#aaaaaa" stroke="none" transform="matrix(1 0 0 1 84 488 )" />
<path d="M 18.6666666545139 13.4556212879068 L 23.999999984375 13.4556212879068 L 23.999999984375 8.13017750092455 C 23.999999984375 7.63998393215635 24.3979690984866 7.24260352949335 24.8888888796296 7.24260352949335 C 25.3798086607727 7.24260352949335 25.7777777748843 7.63998393215635 25.7777777748843 8.13017750092455 L 25.7777777748843 13.4556212879068 L 31.1111111047454 13.4556212879068 C 31.6020308858885 13.4556212879068 32 13.8530016905698 32 14.343195259338 C 32 14.8333888281062 31.6020308858885 15.2307692307692 31.1111111047454 15.2307692307692 L 25.7777777748843 15.2307692307692 L 25.7777777748843 20.5562130177515 C 25.7777777748843 21.0464065865197 25.3798086607727 21.4437869891827 24.8888888796296 21.4437869891827 C 24.3979690984866 21.4437869891827 23.999999984375 21.0464065865197 23.999999984375 20.5562130177515 L 23.999999984375 15.2307692307692 L 18.6666666545139 15.2307692307692 C 18.1757468733708 15.2307692307692 17.7777777592593 14.8333888281062 17.7777777592593 14.343195259338 C 17.7777777592593 13.8530016905698 18.1757468733708 13.4556212879068 18.6666666545139 13.4556212879068 Z M 30 20.6840236686391 L 30.2222221886574 20.6831361085429 C 30.2222221886574 20.1929425397747 30.620191302769 19.7955621371117 31.1111110839121 19.7955621371117 C 31.6020308650551 19.7955621371117 31.9999999791667 20.1929425397747 31.9999999791667 20.6831361085429 L 32 33.2662721754808 C 31.9995564818193 34.7365396105881 30.8057791101131 35.928192063087 29.3333334351962 35.928192063087 C 28.8188541791035 35.928192063087 28.3153585793775 35.779587327387 27.8835555235133 35.5002958441198 L 16.5048888642546 28.1378698086169 C 16.2109336845382 27.9478214882398 15.8326218362668 27.9478214882398 15.5386666565504 28.1378698086169 L 4.11377778898842 35.5082840098003 C 3.68254972175569 35.7864761256624 3.1800650159474 35.9344576611834 2.66666655930324 35.9344576611834 C 1.19420033215083 35.9344576611834 0.000414442319860577 34.7427731863621 0 33.2724852209689 L 0 6.35502959966716 C 0 2.92367461115139 2.78578377079869 0.142011820451183 6.22222220428241 0.142011820451183 L 25.7777777748843 0.142011820451183 C 29.214216208368 0.142011820451183 31.9999999791667 2.92367461115139 31.9999999791667 6.35502955806213 L 31.9999999791667 8.18343193879438 C 31.9999999791667 8.67362550756258 31.6020308650551 9.07100591022559 31.1111110839121 9.07100591022559 C 30.620191302769 9.07100591022559 30.2222221886574 8.67362550756259 30.2222221886574 8.18343193879438 L 30.2222221886574 6.35502959966716 C 30.2222221886574 3.90406178356279 28.2323766528219 1.91715978629143 25.7777777748843 2 L 6.22222220428241 2 C 3.76762332634482 1.91715978629143 1.77777779050926 3.90406178356279 2 6.35502959966716 L 2 33.2724852209689 C 1.77777776933179 33.2725643174599 1.77777775874305 33.2726434139586 1.77777775874305 33.2727225104524 C 1.77777775874305 33.7629160677317 2.17574686352721 34.1602964610811 2.66666663316435 34.1602964610811 C 2.83791950301204 34.1602964610811 3.00552670215961 34.1109001074683 3.14933333128299 34.0180473511464 L 14.5742221988449 26.6467455482618 C 15.4564039377868 26.0777934671316 16.5908684453466 26.0788359085129 17.471999988625 26.6494082701553 L 28.8497777728843 34.0118343056583 C 28.9937759684371 34.1050539462454 29.1617203167725 34.1546583128729 29.3333336863881 34.1546583128729 C 29.8242534560252 34.1546583128729 30.2222225608094 33.7572779195235 30.2222225608094 33.2670843622442 C 30.2222225608094 33.2668136332811 30.2222224367587 33.2665429043305 30 33.2662721754808 L 30 20.6840236686391 Z " fill-rule="nonzero" stroke="none" transform="matrix(1 0 0 1 84 488 )" />
</g>
</svg>

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

View File

@ -31,4 +31,9 @@ interface PaginationProps {
defineProps<PaginationProps>();
</script>
<style lang="scss" scoped></style>
<style lang="scss">
.el-pagination__total,
.el-pagination__jump {
color: #fff !important;
}
</style>

View File

@ -1,10 +1,10 @@
<template>
<div class="header-left">
<img class="logo" src="@/assets/images/logo.png" />
<!-- <div class="left-title">
<div class="left-title">
<div class="title">船货信息智慧共享服务平台</div>
<div class="text">Intelligent sharing service platform for ship and cargo information</div>
</div> -->
</div>
</div>
<div class="header-right">
<div
@ -54,7 +54,7 @@ const menuList = ref<menuListType[]>([
icon: 'API',
},
{
path: '/enterprise/employee',
path: '/setting',
title: '设置',
icon: 'Setting',
},

View File

@ -16,6 +16,6 @@
width: 100%;
height: 100%;
overflow: auto;
background-color: var(--el-bg-color-page);
background-color: #555;
}
</style>

View File

@ -1,45 +0,0 @@
<template>
<template v-for="subItem in menuList" :key="subItem.path">
<el-sub-menu v-if="subItem.children && subItem.children.length > 0" :index="subItem.path">
<template #title>
<el-icon v-if="subItem.meta?.native">
<component :is="subItem.meta?.icon"></component>
</el-icon>
<svg-icon :icon="subItem.meta?.icon" className="side-icon" v-else></svg-icon>
<span>{{ subItem.meta?.title }}</span>
</template>
<!-- 有children递归本次组件 -->
<SubMenu :menuList="subItem.children" />
</el-sub-menu>
<el-menu-item v-else :index="subItem.path">
<el-icon v-if="subItem.meta?.native">
<component :is="subItem.meta?.icon"></component>
</el-icon>
<svg-icon :icon="subItem.meta?.icon" className="side-icon" v-else></svg-icon>
<template #title>
<span>{{ subItem.meta?.title }}</span>
</template>
</el-menu-item>
</template>
</template>
<script lang="ts" setup>
import { RouteRecordRaw } from 'vue-router';
//
defineProps({
menuList: {
type: Array<RouteRecordRaw>,
default: () => [],
},
});
</script>
<style lang="scss" scoped>
.side-icon {
margin-right: 10px;
font-size: 18px;
text-align: center;
}
</style>

View File

@ -1,23 +1,62 @@
<template>
<div class="sidebar">
<img class="side-logo" src="@/assets/images/logo.png" />
<el-scrollbar>
<el-menu :default-active="activeMenu" background-color="#333" text-color="#fff" router>
<SubMenu :menuList="menuList"></SubMenu>
</el-menu>
</el-scrollbar>
<div
class="side-item"
v-for="item in menuList"
:key="item.path"
:class="{ 'side-item-active': activeMenu === item.path }"
@click="onClickMenu(item)"
>
<svg-icon :icon="item.icon" className="side-icon"></svg-icon>
<span>{{ item.title }}</span>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { computed, onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { filterRoutes, generateMenus } from '@/utils/routers';
import SubMenu from './components/SubMenu.vue';
import { getAdImageAPI } from '@/api/ApiManage';
//
const route = useRoute();
interface menuListType {
path: string;
title: string;
icon: string;
}
//
const menuList = ref<menuListType[]>([
{
path: '/boat',
title: '船舶信息',
icon: 'BoatInfo',
},
{
path: '/manifest',
title: '舱单信息',
icon: 'ManifestInfo',
},
{
path: '/send',
title: '发布信息',
icon: 'SendInfo',
},
{
path: '/subscribe',
title: '订阅信息',
icon: 'SubscribeInfo',
},
{
path: '/history',
title: '历史数据',
icon: 'History',
},
]);
//
const activeMenu = computed(() => {
const { path } = route;
@ -27,10 +66,19 @@ const activeMenu = computed(() => {
//
const router = useRouter();
//
const menuList = computed(() => {
const fRoutes = filterRoutes(router.getRoutes());
return generateMenus(fRoutes);
//
const onClickMenu = (item: menuListType) => {
router.push(item.path);
};
// 广
const getAdImage = async () => {
const { data } = await getAdImageAPI();
console.log(data);
};
onMounted(async () => {
await getAdImage();
});
</script>
@ -41,6 +89,8 @@ const menuList = computed(() => {
bottom: 0;
left: 0;
z-index: 999;
display: flex;
flex-direction: column;
width: 200px;
height: 100%;
overflow: hidden;
@ -50,5 +100,30 @@ const menuList = computed(() => {
width: 100%;
height: 170px;
}
.side-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100px;
font-size: 18px;
color: #d7d7d7;
background-color: #333;
border-bottom: 1px solid #555c64;
.side-icon {
margin-bottom: 6px;
font-size: 20px;
}
}
.side-item-active {
color: #ff0;
background-color: #555;
}
.side-item:hover {
color: #fff;
cursor: pointer;
background-color: #000;
}
}
</style>

View File

@ -43,5 +43,8 @@ import Sidebar from './Sidebar/index.vue';
color: #fff;
background-color: #9c000c;
}
.el-main {
background-color: #555;
}
}
</style>

View File

@ -1,24 +1,11 @@
import { App } from 'vue';
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
import Layout from '@/layouts/index.vue';
import boatRoutes from './modules/boat';
import enterpriseRoutes from './modules/enterprise';
import historyRoutes from './modules/history';
import manifestRoutes from './modules/manifest';
import sendRoutes from './modules/send';
import subscriptionRoutes from './modules/subscription';
/**
*
*/
export const asyncRoutes = [
...boatRoutes,
...manifestRoutes,
...enterpriseRoutes,
...sendRoutes,
...subscriptionRoutes,
...historyRoutes,
];
export const asyncRoutes = [];
/**
*
@ -68,19 +55,61 @@ export const constantRoutes: Array<RouteRecordRaw> = [
path: '/notice',
name: 'Notice',
component: () => import('@/views/Notice/index.vue'),
meta: { title: '公告', roles: ['test2'] },
meta: { title: '公告' },
},
{
path: '/api',
name: 'Api',
component: () => import('@/views/ApiManage/index.vue'),
meta: { title: 'API', roles: ['test2'] },
meta: { title: 'API' },
},
{
path: '/setting',
name: 'Setting',
component: () => import('@/views/Setting/index.vue'),
meta: { title: '设置' },
},
{
path: '/help',
name: 'HelpCenter',
component: () => import('@/views/Help/index.vue'),
meta: { title: '帮助', roles: ['test2'] },
meta: { title: '帮助' },
},
{
path: '/boat',
name: 'BoatInfo',
component: () => import('@/views/Boat/index.vue'),
meta: {
title: '船舶信息',
},
},
{
path: '/manifest',
name: 'Manifest',
component: () => import('@/views/Manifest/index.vue'),
meta: { title: '舱单信息' },
},
{
path: '/send',
name: 'sendInfo',
component: () => import('@/views/Send/index.vue'),
meta: { title: '发布信息' },
},
{
path: '/subscribe',
name: 'Subscribe',
component: () => import('@/views/Subscription/index.vue'),
meta: { title: '订阅信息' },
},
{
path: '/history',
name: 'History',
component: () => import('@/views/History/index.vue'),
meta: {
title: '历史数据',
icon: 'History',
native: false,
},
},
],
},

View File

@ -1,29 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import Layout from '@/layouts/index.vue';
/**
*
*/
const boatRoutes: Array<RouteRecordRaw> = [
{
path: '/boat',
component: Layout,
redirect: '/boat/info',
name: 'Boat',
children: [
{
path: '/boat/info',
name: 'BoatInfo',
component: () => import('@/views/Boat/Infos/index.vue'),
meta: {
title: '船期管理',
icon: 'BoatInfo',
native: false,
roles: ['test2'],
},
},
],
},
];
export default boatRoutes;

View File

@ -1,41 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import Layout from '@/layouts/index.vue';
/**
*
*/
const enterpriseRoutes: Array<RouteRecordRaw> = [
{
path: '/enterprise',
component: Layout,
redirect: '/enterprise/manage',
name: 'Enterprise',
// meta: {
// title: '企业中心',
// icon: 'Setting',
// roles: ['test2'], // 测试权限
// },
children: [
{
path: '/enterprise/manage',
name: 'CompanyManage',
component: () => import('@/views/Enterprise/Manage/index.vue'),
// meta: { title: '企业管理', icon: 'Menu', roles: ['test2'] },
},
{
path: '/enterprise/employee',
name: 'EmployeeManage',
component: () => import('@/views/Enterprise/Employee/index.vue'),
// meta: { title: '员工管理', icon: 'Menu', roles: ['test2'] },
},
// {
// path: '/enterprise/register',
// name: 'CompanyRegister',
// component: () => import('@/views/Enterprise/Company/index.vue'),
// meta: { title: '企业注册', icon: 'Menu', roles: ['test2'] },
// },
],
},
];
export default enterpriseRoutes;

View File

@ -1,24 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import Layout from '@/layouts/index.vue';
/**
*
*/
const helpRoutes: Array<RouteRecordRaw> = [
{
path: '/help',
component: Layout,
redirect: '/help/center',
name: 'help',
children: [
{
path: '/help/center',
name: 'HelpCenter',
component: () => import('@/views/Help/index.vue'),
meta: { title: '帮助信息', icon: 'Menu', roles: ['test2'] },
},
],
},
];
export default helpRoutes;

View File

@ -1,32 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import Layout from '@/layouts/index.vue';
/**
*
*/
const historyRoutes: Array<RouteRecordRaw> = [
{
path: '/history',
component: Layout,
redirect: '/history/mine',
// meta: {
// title: '历史数据',
// icon: 'History',
// native: false,
// },
children: [
{
path: '/history/mine',
name: 'Mine',
component: () => import('@/views/History/index.vue'),
meta: {
title: '历史数据',
icon: 'History',
native: false,
},
},
],
},
];
export default historyRoutes;

View File

@ -1,35 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import Layout from '@/layouts/index.vue';
/**
*
*/
const manifestRoutes: Array<RouteRecordRaw> = [
{
path: '/manifest',
component: Layout,
redirect: '/manifest/manage',
name: 'Manifest',
// meta: {
// title: '舱单管理',
// icon: 'Setting',
// roles: ['test2'], // 测试权限
// },
children: [
{
path: '/manifest/manage',
name: 'ManifestManage',
component: () => import('@/views/Manifest/Manage/index.vue'),
meta: { title: '舱单信息', icon: 'ManifestInfo', native: false },
},
{
path: '/manifest/detail',
name: 'ManifestDetail',
component: () => import('@/views/Manifest/Detail/index.vue'),
// meta: { title: '舱单明细', icon: 'Menu', roles: ['test2'] },
},
],
},
];
export default manifestRoutes;

View File

@ -1,31 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import Layout from '@/layouts/index.vue';
/**
*
*/
const subscriptionRoutes: Array<RouteRecordRaw> = [
{
path: '/subscribe',
component: Layout,
redirect: '/subscribe/manage',
name: 'Subscribe',
meta: { title: '订阅管理', icon: 'SubscribeInfo', native: false },
children: [
{
path: '/subscribe/publish',
name: 'SubscribePublish',
component: () => import('@/views/Subscription/Publish/index.vue'),
meta: { title: '我的订阅', icon: 'Menu', native: true, roles: ['test2'] },
},
{
path: '/subscribe/receive',
name: 'SubscribeReceive',
component: () => import('@/views/Subscription/Receive/index.vue'),
meta: { title: '收到订阅', icon: 'Menu', native: true, roles: ['test2'] },
},
],
},
];
export default subscriptionRoutes;

View File

@ -1,35 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import Layout from '@/layouts/index.vue';
/**
*
*/
const systemRoutes: Array<RouteRecordRaw> = [
{
path: '/system',
component: Layout,
redirect: '/system/user',
name: 'System',
meta: {
title: '系统管理',
icon: 'Setting',
roles: ['test2'], // 测试权限
},
children: [
{
path: '/system/user',
name: 'User',
component: () => import('@/views/System/Users/index.vue'),
meta: { title: '用户管理', icon: 'Menu', roles: ['test2'] },
},
{
path: '/system/role',
name: 'Roles',
component: () => import('@/views/System/Roles/index.vue'),
meta: { title: '角色管理', icon: 'Menu', roles: ['test2'] },
},
],
},
];
export default systemRoutes;

View File

@ -1,8 +1,24 @@
.el-table__header th {
font-weight: bold;
color: #252525;
background: #fafafa;
color: #fff;
background: #005478;
}
.el-table .el-table__header th {
background: var(--el-fill-color-light) !important;
background: #005478 !important;
}
.el-table,
.el-table__expanded-cell {
background-color: #555;
}
.el-table tr {
color: #fff;
background-color: #555;
}
.el-table__body tr.hover-row > td.el-table__cell {
color: #000;
background-color: #ffc;
}
.el-table--enable-row-hover .el-table__body tr:hover > td.el-table__cell {
color: #000;
background-color: #ffc;
}

View File

@ -45,12 +45,3 @@ body {
padding: 20px;
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
}
.card {
box-sizing: border-box;
padding: 20px;
overflow-x: hidden;
background-color: var(--el-bg-color);
border: 1px solid var(--el-border-color-light);
border-radius: 6px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
}

View File

@ -16,6 +16,8 @@ export interface EmployeeType {
nickname: string;
username: string;
phone: string;
dept: string;
job: string;
wharfId: number;
userId: number;
password: string;

View File

@ -25,6 +25,7 @@ export interface BoatInfoType {
shipStatus: string;
tradeType: string;
loadPortId: number | string;
loadPort: PortType;
dischargePortId: number | string;
ship: ShipType;
route: BoatRouteType;
@ -80,6 +81,31 @@ export interface LoadWharfType {
port: PortType;
}
/**
*
*/
export interface LoadInfoType {
id: number;
createBy: number;
createDate: string;
updateBy: number;
updateDate: string;
version: number;
name: string;
portId: number;
address: string;
longitude: number;
latitude: number;
berthageNum: number;
transitCapacity: number;
storageCapacity: number;
handlingCapacity: number;
photo: string;
intro: string;
status: string;
port: PortType;
}
/**
*
*/
@ -268,3 +294,18 @@ export interface ProvinceCityType {
code: string;
status: string;
}
/**
*
*/
export interface vinSearchType {
id: number;
createBy: number;
createDate: string;
updateBy: number;
updateDate: string;
version: number;
scheduleId: number;
shipStatus: string;
schedule: BoatInfoType;
}

View File

@ -1,3 +1,5 @@
import { PageRowsType } from '.';
/**
*
*/
@ -31,6 +33,13 @@ export interface ApiManageType {
resp: string;
}
/**
* api
*/
export interface ApiManagePageType extends PageRowsType {
apiType: '基础数据' | '信息发布' | '信息订阅' | '信息查询';
}
/**
*
*/
@ -60,3 +69,17 @@ export interface OutImportVoyages {
fields: any[];
datas: any[];
}
/**
*
*/
export interface agreeTextType {
id: number;
createBy: number;
createDate: string;
updateBy: number;
updateDate: string;
version: number;
content: string;
textType: string;
}

View File

@ -21,8 +21,8 @@ export interface roleType {
*
*/
export interface rolePremType {
roleId: number;
perms: any[];
userId: number | string;
perms: string[];
}
/**

View File

@ -51,6 +51,6 @@ export const checkStatus = (status: number, message?: string | Array<string>): v
if (message) {
errMsg = typeof message === 'string' ? message : message[0];
}
ElMessage.error('请求失败!');
ElMessage.error(errMsg || '请求失败!');
}
};

View File

@ -1,119 +1,82 @@
<template>
<el-dialog
v-model="dialogVisible"
:title="`${dialogProps.title}码头`"
width="80%"
@close="onCloseDialog"
>
<el-form
ref="ruleFormRef"
label-width="auto"
:rules="formRules"
:disabled="dialogProps.isView"
:model="dialogProps.row"
:hide-required-asterisk="dialogProps.isView"
>
<el-form-item label="名称" prop="name">
<el-input v-model="dialogProps.row!.name" placeholder="请输入名称" />
</el-form-item>
<!-- 接口地址 -->
<el-form-item label="接口地址" prop="url">
<el-input v-model="dialogProps.row!.url" placeholder="请输入接口地址" />
</el-form-item>
<!-- 功能描述 -->
<el-form-item label="功能描述" prop="remark">
<el-input v-model="dialogProps.row!.remark" placeholder="请输入功能描述" />
</el-form-item>
<!-- 接口参数 -->
<el-form-item label="接口参数" prop="req">
<el-input v-model="dialogProps.row!.req" placeholder="请输入接口参数" />
</el-form-item>
<!-- 接口返回 -->
<el-form-item label="接口返回" prop="resp">
<el-input v-model="dialogProps.row!.resp" placeholder="请输入接口返回" />
</el-form-item>
</el-form>
<el-dialog v-model="dialogVisible" title="查看接口" width="80%" @close="onCloseDialog">
<div class="content">
<div class="item">
<div class="title">接口名称</div>
<div class="text">{{ dialogProps?.name }}</div>
</div>
<div class="item">
<div class="title">接口地址</div>
<div class="text">{{ dialogProps?.url }}</div>
</div>
<div class="item">
<div class="title">接口功能</div>
<div class="text">{{ dialogProps?.remark }}</div>
</div>
<div class="item">
<div class="title">接口参数</div>
<div class="text">{{ dialogProps?.req }}</div>
</div>
<div class="item">
<div class="title">接口返回</div>
<div class="text">{{ dialogProps?.resp }}</div>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button v-show="!dialogProps.isView" type="primary" @click="onClickConfirm(ruleFormRef)">
确认
</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ElMessage, FormInstance, FormRules } from 'element-plus';
import { reactive, ref } from 'vue';
import {} from 'element-plus';
import { ref } from 'vue';
import { postApiGetAPI } from '@/api/ApiManage';
import { ApiManageType } from '@/types/help';
interface DialogPropsType {
title: string;
isView: boolean;
row: Partial<ApiManageType>;
api?: (params: any) => Promise<any>;
getTableList?: () => void;
}
// dialog
const dialogVisible = ref(false);
//
const dialogProps = ref<DialogPropsType>({
isView: false,
title: '',
row: {},
});
const dialogProps = ref<ApiManageType>();
//
const getApiManageData = async (id: string | number) => {
const { data } = await postApiGetAPI({ id });
dialogProps.value = data;
};
// dialog/
const isShowDialog = (params: DialogPropsType) => {
dialogProps.value = params;
const isShowDialog = async (params: ApiManageType) => {
params.id && (await getApiManageData(params.id));
dialogVisible.value = true;
};
//
defineExpose({ isShowDialog });
const formRules = reactive<FormRules>({
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
url: [{ required: true, message: '请输入接口地址', trigger: 'blur' }],
remark: [{ required: true, message: '请输入功能描述', trigger: 'blur' }],
req: [{ required: true, message: '请输入接口参数', trigger: 'blur' }],
resp: [{ required: true, message: '请输入接口返回', trigger: 'blur' }],
});
//
const ruleFormRef = ref<FormInstance>();
// dialog
const onCloseDialog = () => {
//
ruleFormRef.value?.resetFields();
};
// /
const onClickConfirm = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
try {
await dialogProps.value.api!(dialogProps.value.row);
ElMessage({
message: `${dialogProps.value.title}成功`,
type: 'success',
});
dialogProps.value.getTableList!();
} finally {
dialogVisible.value = false;
}
});
};
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.content {
width: 100%;
height: 100%;
.item {
display: flex;
align-items: center;
width: 100%;
font-size: 16px;
line-height: 27px;
.title {
font-weight: 700;
color: #000;
}
}
}
</style>

View File

@ -1,22 +1,30 @@
<template>
<div class="table">
<div class="footer">
<div class="footer-util">滚装船货信息平台API开放接口文档</div>
<div class="footer-util">
<div class="title">滚装船货信息平台API开放接口文档</div>
<div class="api-btn">
<div
class="btn"
:class="{ 'btn-active': currentApiType === item }"
v-for="item in apiTypeList"
:key="item"
@click="onClickChangeApiType(item)"
>
{{ item }}
</div>
</div>
</div>
<!-- 表格 -->
<div class="footer-table">
<el-table
v-loading="tableLoading"
:data="tableState.tableData"
border
stripe
style="width: 100%; height: 100%"
>
<el-table-column prop="name" label="名称" align="center" width="180" />
<el-table-column prop="url" label="接口地址" align="center" width="150" />
<el-table-column prop="remark" label="功能描述" align="center" width="200" />
<el-table-column prop="req" label="接口参数" align="center" width="150" />
<el-table-column prop="resp" label="接口返回" align="center" width="150" />
<el-table-column prop="createDate" label="创建时间" align="center" width="180" />
<el-table-column prop="remark" label="功能描述" align="center" />
<el-table-column prop="operator" label="操作" width="200px" align="center" fixed="right">
<template #default="scope">
<el-button
@ -24,7 +32,7 @@
size="small"
icon="View"
link
@click="onClickOpenDialog('查看', scope.row)"
@click="onClickOpenDialog(scope.row)"
>
查看
</el-button>
@ -47,18 +55,38 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { postApiPageAPI, postApiSaveAPI } from '@/api/ApiManage';
import { postApiPageAPI } from '@/api/ApiManage';
import Pagination from '@/components/Pagination/Pagination.vue';
import { useTable } from '@/hooks/useTable';
import { ApiManageType } from '@/types/help';
import ApiDialog from './ApiDialog.vue';
type apiInterface = '基础数据' | '信息发布' | '信息订阅' | '信息查询';
// api
const apiTypeList = ref<apiInterface[]>(['基础数据', '信息发布', '信息订阅', '信息查询']);
const currentApiType = ref<apiInterface>('基础数据');
const onClickChangeApiType = async (item: apiInterface) => {
if (item === currentApiType.value) return;
currentApiType.value = item;
//
tableState.value.searchParam = {
apiType: item,
};
await searchTable();
};
//
const { getTableList, tableState, tableChangeCurrent, tableChangeSize } = useTable({
const { getTableList, tableState, searchTable, tableChangeCurrent, tableChangeSize } = useTable({
api: postApiPageAPI,
});
onMounted(async () => {
tableState.value.searchInitParam = {
apiType: currentApiType.value,
};
await getTableList();
});
@ -66,15 +94,8 @@ const tableLoading = ref(false);
// /
const apiDialogRef = ref<InstanceType<typeof ApiDialog> | null>(null);
const onClickOpenDialog = (title: string, row: Partial<ApiManageType> = {}) => {
const params = {
title,
isView: title === '查看',
row: { ...row },
api: postApiSaveAPI,
getTableList,
};
apiDialogRef.value?.isShowDialog(params);
const onClickOpenDialog = (row: ApiManageType) => {
apiDialogRef.value?.isShowDialog(row);
};
//
@ -99,7 +120,6 @@ const handleCurrentChange = async (val: number) => {
display: flex;
padding: 16px 16px 0;
margin-bottom: 16px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
}
@ -111,18 +131,44 @@ const handleCurrentChange = async (val: number) => {
flex-direction: column;
padding: 16px;
overflow: hidden;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
.footer-util {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
justify-content: space-between;
margin-bottom: 15px;
font-size: 30px;
color: #fff;
.title {
font-size: 28px;
font-weight: 700;
}
.api-btn {
display: flex;
flex: 1;
align-items: center;
justify-content: space-around;
.btn {
display: flex;
align-items: center;
justify-content: center;
width: 150px;
height: 50px;
margin-left: 10px;
font-size: 18px;
color: #d7d7d7;
background-color: rgb(51 51 51 / 100%);
border-radius: 75px;
box-shadow: 5px 5px 5px rgb(0 0 0 / 34.9%);
}
.btn-active {
color: #000;
background-color: #f2f2f2;
box-shadow: none;
}
}
}
.footer-table {
position: relative;
flex: 1;

View File

@ -1,18 +0,0 @@
<template>
<div class="Manage">
<BoatTable />
</div>
</template>
<script lang="ts" setup>
import BoatTable from './components/BoatTable.vue';
</script>
<style lang="scss" scoped>
.manage {
display: flex;
flex-direction: row;
width: 100%;
height: 100%;
}
</style>

View File

@ -18,6 +18,9 @@
<el-button icon="Refresh" @click="onClickResetForm(tableFormRef)"></el-button>
</el-form-item>
</el-form>
<div>
<el-button type="success" @click="onClickExport"></el-button>
</div>
</div>
<div class="footer">
<!-- 表格 -->
@ -26,7 +29,6 @@
v-loading="tableLoading"
:data="tableState.tableData"
border
stripe
style="width: 100%; height: 100%"
>
<el-table-column prop="enterprise.name" label="企业" align="center" width="150" />
@ -61,7 +63,7 @@
<el-table-column prop="loadPort.name" label="装货港口" align="center" width="150" />
<el-table-column prop="dischargePort.name" label="卸货港口" align="center" width="150" />
<el-table-column prop="createDate" label="创建时间" align="center" width="180" />
<!-- <el-table-column prop="operator" label="操作" width="200px" align="center" fixed="right">
<el-table-column prop="operator" label="操作" width="200px" align="center" fixed="right">
<template #default="scope">
<el-button
type="primary"
@ -73,7 +75,7 @@
查看
</el-button>
</template>
</el-table-column> -->
</el-table-column>
</el-table>
</div>
<!-- 分页 -->
@ -90,9 +92,13 @@
<script lang="ts" setup>
import dayjs from 'dayjs';
import { FormInstance } from 'element-plus';
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
import { onMounted, reactive, ref } from 'vue';
import { getSailSchedulePageAPI } from '@/api/Boat/info';
import {
getSailScheduleExportAPI,
getSailScheduleGetAPI,
getSailSchedulePageAPI,
} from '@/api/Boat/info';
import { postShipListAPI } from '@/api/Ship';
import Pagination from '@/components/Pagination/Pagination.vue';
import RemoteSelect from '@/components/RemoteSelect/index.vue';
@ -151,11 +157,55 @@ const onClickResetForm = async (formEl: FormInstance | undefined) => {
formEl.resetFields();
};
const onClickExport = () => {
ElMessageBox.confirm(`你确定要导出船期信息吗?`, '温馨提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
draggable: true,
})
.then(async () => {
try {
let params: any = {
rows: tableState.value.pageAble.rows,
page: tableState.value.pageAble.page,
};
if (searchTableForm.shipId) {
params.shipId = searchTableForm.shipId;
}
if (searchTableForm.voyage) {
params.voyage = searchTableForm.voyage;
}
const { data } = await getSailScheduleExportAPI(params);
const blob = new Blob([data], {
type: 'application/vnd.ms-excel;charset=utf-8',
});
const downloadUrl = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = '船期信息.xlsx';
link.click();
ElMessage({
message: '导出成功',
type: 'success',
});
} catch (error) {
ElMessage({
message: '导出失败',
type: 'error',
});
}
})
.catch(() => {
console.log('用户点击了取消');
});
};
//
// const onClickOpenDetail = async (row: BoatInfoType) => {
// const { data } = await getSailScheduleGetAPI({ id: row.id });
// console.log('🚀 ~ file: BoatTable.vue:146 ~ onClickOpenDetail ~ data:', data);
// };
const onClickOpenDetail = async (row: BoatInfoType) => {
const { data } = await getSailScheduleGetAPI({ id: row.id });
console.log('🚀 ~ file: BoatTable.vue:146 ~ onClickOpenDetail ~ data:', data);
};
//
const handleSizeChange = async (val: number) => {
@ -177,11 +227,8 @@ const handleCurrentChange = async (val: number) => {
width: 100%;
.header {
display: flex;
padding: 16px 16px 0;
margin-bottom: 16px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
justify-content: space-between;
padding: 0 16px;
}
.footer {
position: relative;
@ -191,9 +238,6 @@ const handleCurrentChange = async (val: number) => {
flex-direction: column;
padding: 16px;
overflow: hidden;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
.footer-util {
display: flex;
flex-shrink: 0;

View File

@ -0,0 +1,92 @@
<template>
<div class="table">
<div class="header">
<div class="title">{{ currentSubscribeNav }}</div>
<div class="api-btn">
<div
class="btn"
:class="{ 'btn-active': currentSubscribeNav === item }"
v-for="item in subscribeNavList"
:key="item"
@click="onClickChangeSubscribeType(item)"
>
{{ item }}
</div>
</div>
</div>
<div class="content">
<BoatTable v-if="currentSubscribeNav === ''" />
<!-- <ManifestDetailTable v-if="currentSubscribeNav === ''" /> -->
<!-- <ReceiveTable v-if="currentSubscribeNav === ''" /> -->
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
// import ManifestDetailTable from './components/ManifestDetailTable.vue';
import BoatTable from './components/BoatTable.vue';
//
const subscribeNavList = ref(['船期信息', '船图查询', '船位跟踪']);
const currentSubscribeNav = ref('船期信息');
const onClickChangeSubscribeType = async (item: string) => {
if (item === currentSubscribeNav.value) return;
currentSubscribeNav.value = item;
};
</script>
<style lang="scss" scoped>
.table {
position: relative;
display: flex;
flex: 1;
flex-direction: column;
width: 100%;
height: 100%;
.header {
box-sizing: border-box;
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: space-between;
padding: 16px 16px 0;
margin-bottom: 15px;
color: #fff;
.title {
font-size: 28px;
font-weight: 700;
}
.api-btn {
display: flex;
flex: 1;
align-items: center;
justify-content: flex-end;
.btn {
display: flex;
align-items: center;
justify-content: center;
width: 150px;
height: 50px;
margin-left: 10px;
font-size: 18px;
color: #d7d7d7;
background-color: rgb(51 51 51 / 100%);
border-radius: 75px;
box-shadow: 5px 5px 5px rgb(0 0 0 / 34.9%);
}
.btn-active {
color: #000;
background-color: #f2f2f2;
box-shadow: none;
}
}
}
.content {
box-sizing: border-box;
flex: 1;
padding: 16px;
}
}
</style>

View File

@ -1,19 +0,0 @@
<template>
<div class="manage">
<CompanyRegister :isShowBackBtn="false" />
</div>
</template>
<script lang="ts" setup>
import CompanyRegister from '@/views/Login/register.vue';
</script>
<style lang="scss" scoped>
.manage {
box-sizing: border-box;
width: 100%;
height: 100%;
padding: 25px;
background-color: #fff;
}
</style>

View File

@ -1,18 +0,0 @@
<template>
<div class="manage">
<EmployeeTable />
</div>
</template>
<script lang="ts" setup>
import EmployeeTable from './components/EmployeeTable.vue';
</script>
<style lang="scss" scoped>
.manage {
display: flex;
flex-direction: row;
width: 100%;
height: 100%;
}
</style>

View File

@ -1,239 +0,0 @@
<template>
<el-dialog
v-model="dialogVisible"
:title="`${dialogProps.title}企业`"
width="80%"
@close="onCloseDialog"
>
<el-form
ref="ruleFormRef"
label-width="auto"
:rules="formRules"
:disabled="dialogProps.isView"
:model="dialogProps.row"
:hide-required-asterisk="dialogProps.isView"
>
<el-row>
<el-col :span="12">
<el-form-item label="企业类型" prop="enterpriseType" required>
<el-select
v-model="dialogProps.row!.enterpriseType"
placeholder="请选择企业类型"
style="width: 100%"
>
<el-option
v-for="item in companyTypeList"
:key="item.value"
:label="item.label"
:value="item.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="港口" prop="portId" required>
<RemoteSelect
v-model:value="dialogProps.row!.portId"
placeholder="请选择港口"
:api="postPortListAPI"
:disabled="dialogProps.isView"
/>
</el-form-item>
<el-form-item label="企业全称" prop="name">
<el-input v-model="dialogProps.row!.name" placeholder="请输入企业全称" />
</el-form-item>
<el-form-item label="企业统一社会信用代码" prop="code">
<el-input v-model="dialogProps.row!.code" placeholder="请输入企业统一社会信用代码" />
</el-form-item>
<el-form-item label="合同编号" prop="contractNo">
<el-input v-model="dialogProps.row!.contractNo" placeholder="请输入合同编号" />
</el-form-item>
<el-form-item label="法定代表人" prop="legalPerson">
<el-input v-model="dialogProps.row!.legalPerson" placeholder="请输入法定代表人" />
</el-form-item>
<el-form-item label="地址" prop="address">
<el-input v-model="dialogProps.row!.address" placeholder="请输入企业地址" />
</el-form-item>
<el-form-item label="企业状态">
<el-tag v-if="dialogProps.row!.enterpriseStatus === '正常'" type="success">
{{ dialogProps.row!.enterpriseStatus }}
</el-tag>
<el-tag v-else-if="dialogProps.row!.enterpriseStatus === ''" type="danger">
{{ dialogProps.row!.enterpriseStatus }}
</el-tag>
<el-tag v-else-if="dialogProps.row!.enterpriseStatus === ''" type="primary">
{{ dialogProps.row!.enterpriseStatus }}
</el-tag>
<el-tag v-else type="info">{{ dialogProps.row!.enterpriseStatus }}</el-tag>
</el-form-item>
<el-form-item label="营业执照" prop="licensePhoto">
<UploadImg
v-model:imageID="dialogProps.row!.licensePhoto"
width="135px"
height="135px"
:file-size="3"
:fileNo="dialogProps.row!.code"
:companyID="dialogProps.row!.id"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系人" prop="linkman">
<el-input v-model="dialogProps.row!.linkman" placeholder="请输入联系人姓名" />
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="dialogProps.row!.phone" placeholder="请填写手机号码" />
</el-form-item>
<el-form-item label="身份证" prop="idPhoto">
<UploadImg
v-model:imageID="dialogProps.row!.idPhoto"
width="135px"
height="135px"
:file-size="3"
:imgType="1"
:fileNo="dialogProps.row!.phone"
:companyID="dialogProps.row!.id"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button
v-if="dialogProps.row!.enterpriseStatus === '待审核'"
type="success"
@click="onClickRegPass(dialogProps.row.id!)"
>审核通过</el-button
>
<el-button v-show="!dialogProps.isView" type="primary" @click="onClickConfirm(ruleFormRef)"
>确认</el-button
>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ElMessage, ElMessageBox, FormInstance, FormRules } from 'element-plus';
import { reactive, ref } from 'vue';
import { postEnterpriseRegPassAPI } from '@/api/Enterprise/company';
import { postPortListAPI } from '@/api/Port';
import RemoteSelect from '@/components/RemoteSelect/index.vue';
import UploadImg from '@/components/Upload/img.vue';
import { EnterpriseType } from '@/types/boatInfo';
interface DialogPropsType {
title: string;
isView: boolean;
row: Partial<EnterpriseType>;
api?: (params: any) => Promise<any>;
getTableList?: () => void;
}
//
const companyTypeList = [
{
value: '港口码头',
label: '港口码头',
},
{
value: '船公司',
label: '船公司',
},
{
value: '货主',
label: '货主',
},
{
value: '船代',
label: '船代',
},
{
value: '货贷',
label: '货贷',
},
];
// dialog
const dialogVisible = ref(false);
//
const dialogProps = ref<DialogPropsType>({
isView: false,
title: '',
row: {},
});
// dialog/
const isShowDialog = (params: DialogPropsType) => {
dialogProps.value = params;
dialogVisible.value = true;
};
//
defineExpose({ isShowDialog });
//
const formRules = reactive<FormRules>({
name: [{ required: true, message: '请输入企业全称', trigger: 'blur' }],
code: [{ required: true, message: '请输入企业统一社会信用代码', trigger: 'blur' }],
address: [{ required: true, message: '请输入企业地址', trigger: 'blur' }],
legalPerson: [{ required: true, message: '请输入法定代表人', trigger: 'blur' }],
contractNo: [{ required: true, message: '请输入合同编号', trigger: 'blur' }],
linkman: [{ required: true, message: '请输入联系人', trigger: 'blur' }],
phone: [{ required: true, message: '请输入联系电话', trigger: 'blur' }],
portId: [{ required: true, message: '请选择港口', trigger: 'blur' }],
licensePhoto: [{ required: true, message: '请上传营业执照', trigger: 'blur' }],
idPhoto: [{ required: true, message: '请上传身份证照片', trigger: 'blur' }],
enterpriseType: [{ required: true, message: '请选择企业类型', trigger: 'blur' }],
});
//
const ruleFormRef = ref<FormInstance>();
// dialog
const onCloseDialog = () => {
//
ruleFormRef.value?.resetFields();
};
//
const onClickRegPass = (id: number) => {
ElMessageBox.confirm('确定审核通过吗?', '温馨提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'info',
}).then(async () => {
try {
await postEnterpriseRegPassAPI([id]);
ElMessage({
type: 'success',
message: '审核通过',
});
dialogProps.value.getTableList!();
} finally {
dialogVisible.value = false;
}
});
};
// /
const onClickConfirm = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
try {
await dialogProps.value.api!(dialogProps.value.row);
ElMessage({
message: `${dialogProps.value.title}成功`,
type: 'success',
});
dialogProps.value.getTableList!();
} finally {
dialogVisible.value = false;
}
});
};
</script>
<style lang="scss" scoped></style>

View File

@ -1,300 +0,0 @@
<template>
<div class="table">
<div class="header">
<el-form :inline="true" :model="searchTableForm" ref="tableFormRef">
<el-form-item prop="enterpriseStatus">
<el-select
v-model="searchTableForm.enterpriseStatus"
placeholder="请选择状态"
style="width: 120px"
>
<el-option
v-for="item in companyTypeList"
:key="item.value"
:label="item.label"
:value="item.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item prop="key">
<el-input v-model="searchTableForm.key" placeholder="单位名称或税号" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="onClickSearch"></el-button>
<el-button icon="Refresh" @click="onClickResetForm(tableFormRef)"></el-button>
</el-form-item>
</el-form>
</div>
<div class="footer">
<!-- <div class="footer-util">
<el-button type="primary" icon="Plus" @click="onClickOpenDrawer('新增')">
新增订阅
</el-button>
</div> -->
<!-- 表格 -->
<div class="footer-table">
<el-table
v-loading="tableLoading"
:data="tableState.tableData"
border
stripe
style="width: 100%; height: 100%"
>
<el-table-column prop="name" label="企业全称" align="center" width="180" />
<el-table-column prop="legalPerson" label="法定代表人" align="center" width="100" />
<el-table-column prop="code" label="企业统一社会信用代码" align="center" width="200" />
<el-table-column prop="address" label="地址" align="center" width="180" />
<el-table-column prop="enterpriseType" label="企业类型" align="center" width="180" />
<el-table-column prop="enterpriseStatus" label="企业状态" align="center" width="120">
<template #default="scope">
<el-tag v-if="scope.row.enterpriseStatus === '正常'" type="success">
{{ scope.row.enterpriseStatus }}
</el-tag>
<el-tag v-else-if="scope.row.enterpriseStatus === ''" type="danger">
{{ scope.row.enterpriseStatus }}
</el-tag>
<el-tag v-else-if="scope.row.enterpriseStatus === ''" type="primary">
{{ scope.row.enterpriseStatus }}
</el-tag>
<el-tag v-else type="info">{{ scope.row.enterpriseStatus }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="linkman" label="联系人" align="center" width="120" />
<el-table-column prop="phone" label="手机号" align="center" width="120" />
<el-table-column prop="createDate" label="创建时间" align="center" width="180" />
<el-table-column prop="operator" label="操作" width="200px" align="center" fixed="right">
<template #default="scope">
<el-button
type="primary"
size="small"
icon="View"
link
@click="onClickOpenDialog('查看', scope.row)"
>
查看
</el-button>
<el-button
type="primary"
size="small"
icon="Edit"
link
@click="onClickOpenDialog('编辑', scope.row)"
>
编辑
</el-button>
<el-button
type="success"
size="small"
icon="User"
link
@click="onClickOpenEmployee(scope.row)"
>
员工
</el-button>
<el-button
type="danger"
size="small"
icon="Delete"
link
@click="onClickDel(scope.row)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 分页 -->
<div class="footer-pagination">
<Pagination
:pageAble="tableState.pageAble"
:handle-size-change="handleSizeChange"
:handle-current-change="handleCurrentChange"
/>
</div>
</div>
<CompanyDialog ref="companyDialogRef" />
</div>
</template>
<script lang="ts" setup>
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
import { onMounted, reactive, ref } from 'vue';
import { useRouter } from 'vue-router';
import {
postEnterpriseDeleteAPI,
postEnterprisePageAPI,
postEnterpriseSaveAPI,
} from '@/api/Enterprise/company';
import Pagination from '@/components/Pagination/Pagination.vue';
import { useTable } from '@/hooks/useTable';
import { EnterpriseType } from '@/types/boatInfo';
import CompanyDialog from './CompanyDialog.vue';
//
const { getTableList, tableState, searchTable, resetTable, tableChangeCurrent, tableChangeSize } =
useTable({
api: postEnterprisePageAPI,
});
onMounted(async () => {
await getTableList();
});
//
const searchTableForm = reactive({
key: '',
enterpriseStatus: '',
});
//
const companyTypeList = [
{
value: '待审核',
label: '待审核',
},
{
value: '正常',
label: '正常',
},
{
value: '已关闭',
label: '已关闭',
},
{
value: '已注册',
label: '已注册',
},
];
//
const tableLoading = ref(false);
//
const tableFormRef = ref<FormInstance>();
//
const onClickSearch = async () => {
tableLoading.value = true;
//
tableState.value.searchParam = searchTableForm;
//
await searchTable();
tableLoading.value = false;
};
//
const onClickResetForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await resetTable();
formEl.resetFields();
};
// /
const companyDialogRef = ref<InstanceType<typeof CompanyDialog> | null>(null);
const onClickOpenDialog = (title: string, row: Partial<EnterpriseType> = {}) => {
const params = {
title,
isView: title === '查看',
row: { ...row },
api: postEnterpriseSaveAPI,
getTableList,
};
companyDialogRef.value?.isShowDialog(params);
};
//
const router = useRouter();
//
const onClickOpenEmployee = (row: EnterpriseType) => {
router.push({
path: '/enterprise/employee',
query: {
enterpriseId: row.id,
},
});
};
//
const onClickDel = (row: EnterpriseType) => {
ElMessageBox.confirm(`你确定要删除企业 ${row.name} 吗?`, '温馨提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
draggable: true,
})
.then(async () => {
await postEnterpriseDeleteAPI([row.id]);
//
await getTableList();
ElMessage({
message: '删除成功',
type: 'success',
});
})
.catch(() => {
console.log('用户点击了取消');
});
};
//
const handleSizeChange = async (val: number) => {
await tableChangeSize(val);
};
//
const handleCurrentChange = async (val: number) => {
await tableChangeCurrent(val);
};
</script>
<style lang="scss" scoped>
.table {
position: relative;
display: flex;
flex: 1;
flex-direction: column;
width: 100%;
.header {
display: flex;
padding: 16px 16px 0;
margin-bottom: 16px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
}
.footer {
position: relative;
box-sizing: border-box;
display: flex;
flex: 1;
flex-direction: column;
padding: 16px;
overflow: hidden;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
.footer-util {
display: flex;
flex-shrink: 0;
justify-content: flex-end;
margin-bottom: 15px;
}
.footer-table {
position: relative;
flex: 1;
}
.footer-pagination {
box-sizing: border-box;
display: flex;
flex-shrink: 0;
justify-content: flex-end;
width: 100%;
padding-top: 20px;
}
}
}
</style>

View File

@ -1,18 +0,0 @@
<template>
<div class="manage">
<CompanyTable />
</div>
</template>
<script lang="ts" setup>
import CompanyTable from './components/CompanyTable.vue';
</script>
<style lang="scss" scoped>
.manage {
display: flex;
flex-direction: row;
width: 100%;
height: 100%;
}
</style>

View File

@ -0,0 +1,103 @@
<template>
<div class="agree">
<div class="agree-content">
<div class="title">{{ agreeText?.textType }}</div>
<div class="content" v-html="agreeText?.content"></div>
</div>
<div class="agree-btn" v-if="textType === 'AGREEMENT' && !isAgree">
<el-checkbox v-model="checked" size="large">
<div class="btn-text">我已经完整阅读并同意用户协议</div>
</el-checkbox>
<el-button type="primary" @click="onClickAgree"></el-button>
</div>
</div>
</template>
<script lang="ts" setup>
import { ElMessage } from 'element-plus';
import { onMounted, ref } from 'vue';
import { postTextAgreeAPI, postTextGetAPI, postTextIsAgreeAPI } from '@/api/Help';
import { agreeTextType } from '@/types/help';
interface propsType {
textType: string;
}
const props = withDefaults(defineProps<propsType>(), {
textType: '',
});
//
const agreeText = ref<agreeTextType>();
//
const isAgree = ref(false);
const checked = ref(false);
//
const getAgreeText = async () => {
const { data } = await postTextGetAPI({ textType: props.textType });
agreeText.value = data;
//
if (props.textType === 'AGREEMENT') {
//
const { data: isAgreeData } = await postTextIsAgreeAPI();
isAgree.value = isAgreeData;
}
};
//
const onClickAgree = async () => {
if (checked.value) {
await postTextAgreeAPI();
isAgree.value = true;
} else {
ElMessage({
type: 'info',
message: '请勾选同意用户协议',
});
}
};
onMounted(async () => {
await getAgreeText();
});
</script>
<style lang="scss" scoped>
.agree {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
.agree-content {
flex: 1;
width: 100%;
background-color: #fff;
.title {
width: 100%;
height: 50px;
font-size: 20px;
font-weight: 700;
line-height: 50px;
color: #333;
text-align: center;
}
.content {
width: 100%;
height: calc(100% - 50px);
}
}
.agree-btn {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 50px;
.btn-text {
color: #fff;
}
}
}
</style>

View File

@ -1,116 +0,0 @@
<template>
<el-dialog
v-model="dialogVisible"
:title="`${dialogProps.title}`"
width="80%"
@close="onCloseDialog"
>
<el-form
ref="ruleFormRef"
label-width="auto"
:rules="formRules"
:disabled="dialogProps.isView"
:model="dialogProps.row"
:hide-required-asterisk="dialogProps.isView"
>
<el-form-item label="序号" prop="serial">
<el-input v-model="dialogProps.row!.serial" placeholder="请输入序号" type="number" />
</el-form-item>
<!-- 名称 -->
<el-form-item label="名称" prop="name">
<el-input v-model="dialogProps.row!.name" placeholder="请输入名称" />
</el-form-item>
<!-- 备注 -->
<el-form-item label="备注" prop="remark">
<el-input v-model="dialogProps.row!.remark" placeholder="请输入备注" type="textarea" />
</el-form-item>
<!-- 类型 -->
<el-form-item label="类型" prop="type">
<el-select v-model="dialogProps.row!.type" placeholder="请选择类型">
<el-option label="主题" value="主题" />
<el-option label="问题" value="问题" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button v-show="!dialogProps.isView" type="primary" @click="onClickConfirm(ruleFormRef)">
确认
</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ElMessage, FormInstance, FormRules } from 'element-plus';
import { reactive, ref } from 'vue';
import { HelpType } from '@/types/help';
interface DialogPropsType {
title: string;
isView: boolean;
row: Partial<HelpType>;
api?: (params: any) => Promise<any>;
getTableList?: () => void;
}
// dialog
const dialogVisible = ref(false);
//
const dialogProps = ref<DialogPropsType>({
isView: false,
title: '',
row: {},
});
// dialog/
const isShowDialog = (params: DialogPropsType) => {
dialogProps.value = params;
dialogVisible.value = true;
};
//
defineExpose({ isShowDialog });
const formRules = reactive<FormRules>({
serial: [{ required: true, message: '请输入序号', trigger: 'blur' }],
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
remark: [{ required: true, message: '请输入备注', trigger: 'blur' }],
type: [{ required: true, message: '请选择类型', trigger: 'change' }],
});
//
const ruleFormRef = ref<FormInstance>();
// dialog
const onCloseDialog = () => {
//
ruleFormRef.value?.resetFields();
};
// /
const onClickConfirm = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
try {
await dialogProps.value.api!(dialogProps.value.row);
ElMessage({
message: `${dialogProps.value.title}成功`,
type: 'success',
});
dialogProps.value.getTableList!();
} finally {
dialogVisible.value = false;
}
});
};
</script>
<style lang="scss" scoped></style>

View File

@ -1,135 +1,141 @@
<template>
<div class="table">
<div class="footer">
<!-- 表格 -->
<div class="footer-table">
<el-table
v-loading="tableLoading"
:data="tableState.tableData"
border
stripe
style="width: 100%; height: 100%"
<div class="notify">
<div class="list">
<ul
class="list-wrap"
v-infinite-scroll="helpListLoad"
:infinite-scroll-disabled="disabled"
:infinite-scroll-distance="100"
>
<el-table-column prop="serial" label="序号" align="center" width="80" />
<el-table-column prop="name" label="名称" align="center" width="180" />
<el-table-column prop="remark" label="备注" align="center" width="200" />
<el-table-column prop="type" label="状态" align="center" width="120" />
<el-table-column prop="createDate" label="创建时间" align="center" width="180" />
<el-table-column prop="operator" label="操作" width="200px" align="center" fixed="right">
<template #default="scope">
<el-button
type="primary"
size="small"
icon="View"
link
@click="onClickOpenDialog('查看', scope.row)"
<li
v-for="item in helpDocsList"
:key="item.id"
class="list-item"
@click="onClickOpenDialog(item)"
>
查看
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 分页 -->
<div class="footer-pagination">
<Pagination
:pageAble="tableState.pageAble"
:handle-size-change="handleSizeChange"
:handle-current-change="handleCurrentChange"
/>
<div class="item-icon"></div>
<div class="item-title">{{ item.name }}</div>
<div class="item-date">{{ item.createDate }}</div>
</li>
</ul>
<div class="content">
<div class="title">{{ helpDocsData?.name }}</div>
<div class="wrap" v-html="helpDocsData?.remark"></div>
</div>
</div>
<HelpDialog ref="helpDialogRef" />
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { postHelpPageAPI, postHelpSaveAPI } from '@/api/Help';
import Pagination from '@/components/Pagination/Pagination.vue';
import { useTable } from '@/hooks/useTable';
import { computed, ref } from 'vue';
import { postHelpGetAPI, postHelpPageAPI } from '@/api/Help';
import { HelpType } from '@/types/help';
import HelpDialog from './HelpDialog.vue';
const helpDocsList = ref<HelpType[]>([]);
const helpDocsData = ref<HelpType>();
// -
const currentPage = ref(1);
//
const loadMoreState = ref('loading');
const disabled = computed(() => loadMoreState.value === 'finished');
//
const { getTableList, tableState, tableChangeCurrent, tableChangeSize } = useTable({
api: postHelpPageAPI,
});
const getHelpDocsList = async () => {
const { data } = await postHelpPageAPI({
page: currentPage.value,
rows: 10,
});
onMounted(async () => {
await getTableList();
});
//
const tableLoading = ref(false);
// /
const helpDialogRef = ref<InstanceType<typeof HelpDialog> | null>(null);
const onClickOpenDialog = (title: string, row: Partial<HelpType> = {}) => {
const params = {
title,
isView: title === '查看',
row: { ...row },
api: postHelpSaveAPI,
getTableList,
};
helpDialogRef.value?.isShowDialog(params);
helpDocsList.value = [...helpDocsList.value, ...data.records];
currentPage.value++;
//
if (currentPage.value >= Number(data.pages) || data.records.length < 10) {
loadMoreState.value = 'finished';
} else {
loadMoreState.value = 'loading';
}
};
//
const handleSizeChange = async (val: number) => {
await tableChangeSize(val);
const helpListLoad = async () => {
await getHelpDocsList();
};
//
const handleCurrentChange = async (val: number) => {
await tableChangeCurrent(val);
const onClickOpenDialog = async (row: HelpType) => {
const { data } = await postHelpGetAPI({ id: row.id });
helpDocsData.value = data;
};
</script>
<style lang="scss" scoped>
.table {
position: relative;
display: flex;
flex: 1;
flex-direction: column;
.notify {
width: 100%;
.header {
display: flex;
padding: 16px 16px 0;
margin-bottom: 16px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
}
.footer {
position: relative;
height: 100%;
overflow-x: hidden;
color: #fff;
.list {
box-sizing: border-box;
display: flex;
flex: 1;
flex-direction: column;
padding: 16px;
justify-content: space-between;
width: 100%;
height: 100%;
.list-wrap {
box-sizing: border-box;
width: 30%;
height: 500px;
overflow: auto;
.list-item {
box-sizing: border-box;
display: flex;
align-items: center;
width: 100%;
padding: 10px 0;
overflow: hidden;
.item-icon {
width: 10px;
height: 10px;
margin-right: 10px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
.footer-util {
display: flex;
flex-shrink: 0;
justify-content: flex-end;
margin-bottom: 15px;
border-radius: 50%;
}
.footer-table {
position: relative;
.item-title {
flex: 1;
overflow: hidden;
font-size: 16px;
text-overflow: ellipsis;
white-space: nowrap;
}
.footer-pagination {
.item-date {
margin-left: 20px;
font-size: 14px;
}
}
}
.content {
box-sizing: border-box;
flex: 1;
height: 100%;
margin-left: 20px;
font-size: 21px;
font-style: normal;
font-weight: 700;
color: #333;
background-color: #fff;
.title {
display: flex;
flex-shrink: 0;
justify-content: flex-end;
justify-content: center;
padding: 16px;
font-size: 23px;
font-weight: 700;
}
.wrap {
box-sizing: border-box;
width: 100%;
padding-top: 20px;
height: calc(100% - 70px);
padding: 0 16px 20px;
}
}
}
}

View File

@ -1,18 +1,92 @@
<template>
<div class="manage">
<HelpTable />
<div class="table">
<div class="header">
<div class="title">帮助信息</div>
<div class="api-btn">
<div
class="btn"
:class="{ 'btn-active': currentNav === item }"
v-for="item in helpDocsList"
:key="item"
@click="onClickChangeNav(item)"
>
{{ item }}
</div>
</div>
</div>
<div class="content">
<HelpTable v-if="currentNav === ''" />
<AgreeText v-if="currentNav === ''" textType="AGREEMENT" />
<AgreeText v-if="currentNav === ''" textType="RULE" />
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import AgreeText from './components/AgreeText.vue';
import HelpTable from './components/HelpTable.vue';
//
const helpDocsList = ref(['分类主题', '用户协议', '共享规则']);
const currentNav = ref('分类主题');
const onClickChangeNav = (item: string) => {
if (item === currentNav.value) return;
currentNav.value = item;
};
</script>
<style lang="scss" scoped>
.manage {
.table {
position: relative;
display: flex;
flex-direction: row;
flex: 1;
flex-direction: column;
width: 100%;
height: 100%;
.header {
box-sizing: border-box;
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: space-between;
padding: 16px 16px 0;
margin-bottom: 15px;
color: #fff;
.title {
font-size: 28px;
font-weight: 700;
}
.api-btn {
display: flex;
flex: 1;
align-items: center;
justify-content: flex-end;
.btn {
display: flex;
align-items: center;
justify-content: center;
width: 150px;
height: 50px;
margin-left: 10px;
font-size: 18px;
color: #d7d7d7;
background-color: rgb(51 51 51 / 100%);
border-radius: 75px;
box-shadow: 5px 5px 5px rgb(0 0 0 / 34.9%);
}
.btn-active {
color: #000;
background-color: #f2f2f2;
box-shadow: none;
}
}
}
.content {
box-sizing: border-box;
flex: 1;
padding: 16px;
}
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<div class="table">
<div class="header">
<!-- <div class="header">
<el-form :inline="true" :model="searchTableForm" ref="tableFormRef">
<el-form-item>
<RemoteSelect
@ -18,7 +18,7 @@
<el-button icon="Refresh" @click="onClickResetForm(tableFormRef)"></el-button>
</el-form-item>
</el-form>
</div>
</div> -->
<div class="footer">
<!-- 表格 -->
<div class="footer-table">
@ -26,7 +26,6 @@
v-loading="tableLoading"
:data="tableState.tableData"
border
stripe
style="width: 100%; height: 100%"
>
<el-table-column prop="enterprise.name" label="企业" align="center" width="150" />
@ -76,55 +75,23 @@
</template>
<script lang="ts" setup>
import { FormInstance } from 'element-plus';
import { onMounted, reactive, ref } from 'vue';
import { getHistoryListAPI } from '@/api/Boat/info';
import { postShipListAPI } from '@/api/Ship';
import { onMounted, ref } from 'vue';
import { getPublishHistoryListAPI } from '@/api/Boat/info';
import Pagination from '@/components/Pagination/Pagination.vue';
import RemoteSelect from '@/components/RemoteSelect/index.vue';
import { useTable } from '@/hooks/useTable';
//
const { getTableList, tableState, searchTable, resetTable, tableChangeCurrent, tableChangeSize } =
useTable({
api: getHistoryListAPI,
});
const { getTableList, tableState, tableChangeCurrent, tableChangeSize } = useTable({
api: getPublishHistoryListAPI,
});
onMounted(async () => {
await getTableList();
});
//
const searchTableForm = reactive({
shipId: '',
voyage: '',
});
//
const tableLoading = ref(false);
//
const tableFormRef = ref<FormInstance>();
//
const onClickSearch = async () => {
tableLoading.value = true;
//
tableState.value.searchParam = searchTableForm;
//
await searchTable();
tableLoading.value = false;
};
//
const onClickResetForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await resetTable();
formEl.resetFields();
};
//
const handleSizeChange = async (val: number) => {
await tableChangeSize(val);
@ -147,9 +114,7 @@ const handleCurrentChange = async (val: number) => {
display: flex;
padding: 16px 16px 0;
margin-bottom: 16px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
}
.footer {
position: relative;
@ -159,9 +124,7 @@ const handleCurrentChange = async (val: number) => {
flex-direction: column;
padding: 16px;
overflow: hidden;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
.footer-util {
display: flex;
flex-shrink: 0;

View File

@ -0,0 +1,148 @@
<template>
<div class="table">
<!-- <div class="header">
<el-form :inline="true" :model="searchTableForm" ref="tableFormRef">
<el-form-item>
<RemoteSelect
v-model:value="searchTableForm.shipId"
placeholder="请选择船舶"
style="width: 120px"
:api="postShipListAPI"
/>
</el-form-item>
<el-form-item>
<el-input v-model="searchTableForm.voyage" placeholder="请输入航次" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="onClickSearch"></el-button>
<el-button icon="Refresh" @click="onClickResetForm(tableFormRef)"></el-button>
</el-form-item>
</el-form>
</div> -->
<div class="footer">
<!-- 表格 -->
<div class="footer-table">
<el-table
v-loading="tableLoading"
:data="tableState.tableData"
border
style="width: 100%; height: 100%"
>
<el-table-column prop="enterprise.name" label="企业" align="center" width="150" />
<el-table-column prop="ship.name" label="船舶" align="center" width="150" />
<el-table-column prop="voyage" label="航次" align="center" width="180" />
<el-table-column prop="shipRoute.name" label="航线" align="center" width="150" />
<el-table-column prop="loadWharf.name" label="装货码头" align="center" width="150" />
<el-table-column prop="dischargeWharf.name" label="卸货码头" align="center" width="150" />
<el-table-column prop="carNumPlan" label="计划商品车数量" align="center" width="180" />
<el-table-column prop="spareNumPlan" label="计划件杂货数量" align="center" width="180" />
<el-table-column prop="carNumActual" label="实际商品车数量" align="center" width="180" />
<el-table-column
prop="spareNumActual"
label="实际件杂货数量"
align="center"
width="180"
/>
<el-table-column
prop="departureDatePlan"
label="计划离泊时间"
align="center"
width="200"
/>
<el-table-column
prop="departureDateActual"
label="实际离泊时间"
align="center"
width="200"
/>
<el-table-column prop="shipStatus" label="船舶状态" align="center" width="150" />
<el-table-column prop="tradeType" label="贸易类型" align="center" width="150" />
<el-table-column prop="loadPort.name" label="装货港口" align="center" width="150" />
<el-table-column prop="dischargePort.name" label="卸货港口" align="center" width="150" />
<el-table-column prop="createDate" label="创建时间" align="center" width="180" />
</el-table>
</div>
<!-- 分页 -->
<div class="footer-pagination">
<Pagination
:pageAble="tableState.pageAble"
:handle-size-change="handleSizeChange"
:handle-current-change="handleCurrentChange"
/>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { getReceiveHistoryListAPI } from '@/api/Boat/info';
import Pagination from '@/components/Pagination/Pagination.vue';
import { useTable } from '@/hooks/useTable';
//
const { getTableList, tableState, tableChangeCurrent, tableChangeSize } = useTable({
api: getReceiveHistoryListAPI,
});
onMounted(async () => {
await getTableList();
});
//
const tableLoading = ref(false);
//
const handleSizeChange = async (val: number) => {
await tableChangeSize(val);
};
//
const handleCurrentChange = async (val: number) => {
await tableChangeCurrent(val);
};
</script>
<style lang="scss" scoped>
.table {
position: relative;
display: flex;
flex: 1;
flex-direction: column;
width: 100%;
.header {
display: flex;
padding: 16px 16px 0;
margin-bottom: 16px;
border-radius: 4px;
}
.footer {
position: relative;
box-sizing: border-box;
display: flex;
flex: 1;
flex-direction: column;
padding: 16px;
overflow: hidden;
border-radius: 4px;
.footer-util {
display: flex;
flex-shrink: 0;
justify-content: flex-end;
margin-bottom: 15px;
}
.footer-table {
position: relative;
flex: 1;
}
.footer-pagination {
box-sizing: border-box;
display: flex;
flex-shrink: 0;
justify-content: flex-end;
width: 100%;
padding-top: 20px;
}
}
}
</style>

View File

@ -1,18 +1,91 @@
<template>
<div class="manage">
<HistoryTable />
<div class="table">
<div class="header">
<div class="title">历史船货信息</div>
<div class="api-btn">
<div
class="btn"
:class="{ 'btn-active': currentHistoryNav === item }"
v-for="item in historyNavList"
:key="item"
@click="onClickChangeHistoryNav(item)"
>
{{ item }}
</div>
</div>
</div>
<div class="content">
<PublishTable v-if="currentHistoryNav === ''" />
<ReceiveTable v-if="currentHistoryNav === ''" />
</div>
</div>
</template>
<script lang="ts" setup>
import HistoryTable from './components/HistoryTable.vue';
import { ref } from 'vue';
import PublishTable from './components/PublishTable.vue';
import ReceiveTable from './components/ReceiveTable.vue';
//
const historyNavList = ref(['我发布的', '我收到的']);
const currentHistoryNav = ref('我发布的');
const onClickChangeHistoryNav = (item: string) => {
if (currentHistoryNav.value === item) return;
currentHistoryNav.value = item;
};
</script>
<style lang="scss" scoped>
.manage {
.table {
position: relative;
display: flex;
flex-direction: row;
flex: 1;
flex-direction: column;
width: 100%;
height: 100%;
.header {
box-sizing: border-box;
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: space-between;
padding: 16px 16px 0;
margin-bottom: 15px;
color: #fff;
.title {
font-size: 28px;
font-weight: 700;
}
.api-btn {
display: flex;
flex: 1;
align-items: center;
justify-content: flex-end;
.btn {
display: flex;
align-items: center;
justify-content: center;
width: 150px;
height: 50px;
margin-left: 10px;
font-size: 18px;
color: #d7d7d7;
background-color: rgb(51 51 51 / 100%);
border-radius: 75px;
box-shadow: 5px 5px 5px rgb(0 0 0 / 34.9%);
}
.btn-active {
color: #000;
background-color: #f2f2f2;
box-shadow: none;
}
}
}
.content {
box-sizing: border-box;
flex: 1;
padding: 16px;
}
}
</style>

View File

@ -1,11 +1,7 @@
<template>
<div class="card">
<div class="card-title">{{ title }}</div>
<div class="card-text number">{{ number }}</div>
<div class="card-text">
<div>{{ totalTitle }}</div>
<div>{{ totalNumber }}</div>
</div>
<div class="card-text">{{ number }}</div>
</div>
</template>
@ -13,38 +9,36 @@
defineProps<{
title: string;
number: number;
totalTitle: string;
totalNumber: number;
}>();
</script>
<style lang="scss" scoped>
.card {
position: relative;
width: 100%;
margin-bottom: 20px;
color: #020817;
background-color: #fff;
border: 1px solid #e4e4e7;
height: 88px;
margin: 20px 0;
font-size: 32px;
font-style: normal;
font-weight: 700;
border: 5px solid #d7d7d7;
border-radius: 8px;
.card-title {
box-sizing: border-box;
width: 100%;
padding: 10px;
font-size: 18px;
font-weight: 500;
position: absolute;
top: -14px; /* 使标题悬浮在边框上方 */
left: 20px; /* 根据需要调整水平位置 */
padding: 0 8px; /* 增加一点内边距 */
font-size: 15px;
color: #fff;
background-color: #555;
}
.card-text {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
justify-content: center;
width: 100%;
height: 40px;
padding: 0 10px 10px;
}
.number {
font-size: 24px;
font-weight: 700;
height: 100%;
color: #80ffff;
}
}
</style>

View File

@ -22,9 +22,6 @@ defineProps({
<style lang="scss" scoped>
.chart {
width: 100%;
height: 400px;
background-color: #fff;
border: 1px solid #e4e4e7;
border-radius: 8px;
height: 200px;
}
</style>

View File

@ -1,94 +1,151 @@
<template>
<div class="home">
<!-- <div class="home-search">
<div class="home-search">
<el-form :inline="true" :model="searchTableForm" ref="searchTableFormRef">
<el-form-item label="车架号" prop="name">
<el-input v-model="searchTableForm.name" placeholder="请输入车架号" />
<el-form-item
prop="vin"
:rules="[{ required: true, message: '请输入车架号', trigger: 'blur' }]"
>
<template #label>
<div class="search-label">车架号</div>
</template>
<el-input v-model="searchTableForm.vin" placeholder="请输入车架号" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search">查询</el-button>
<el-button type="primary" icon="Search" @click="onClickSearch(searchTableFormRef)"
>查询</el-button
>
</el-form-item>
</el-form>
</div> -->
</div>
<template v-if="!isShowVinDialog">
<div class="home-header">
<el-row :gutter="20">
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="2">
<Card
title="年度船舶艘次"
:number="tableData.yearVoyages"
totalTitle="累计船舶艘次"
:totalNumber="tableData.totalVoyages"
></Card>
<el-col :xs="24" :sm="12" :md="6" :lg="3" :xl="3">
<Card title="累计船舶艘次" :number="tableData.totalVoyages"></Card>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="2">
<Card
title="本港年度艘次"
:number="tableData.yearPortVoyages"
totalTitle="本港船舶艘次"
:totalNumber="tableData.totalPortVoyages"
></Card>
<el-col :xs="24" :sm="12" :md="6" :lg="3" :xl="3">
<Card title="年度船舶艘次" :number="tableData.yearVoyages"></Card>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="2">
<Card
title="年度货物数量"
:number="tableData.yearCargos"
totalTitle="累计货物数量"
:totalNumber="tableData.totalCargos"
></Card>
<el-col :xs="24" :sm="12" :md="6" :lg="3" :xl="3">
<Card title="本港船舶艘次" :number="tableData.totalPortVoyages"></Card>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="2">
<Card
title="本港年度货物"
:number="tableData.yearPortCargos"
totalTitle="本港货物累计"
:totalNumber="tableData.totalPortCargos"
></Card>
<el-col :xs="24" :sm="12" :md="6" :lg="3" :xl="3">
<Card title="本港年度艘次" :number="tableData.yearPortVoyages"></Card>
</el-col>
<el-col :xs="24" :sm="12" :md="6" :lg="3" :xl="3">
<Card title="累计货物数量" :number="tableData.totalCargos"></Card>
</el-col>
<el-col :xs="24" :sm="12" :md="6" :lg="3" :xl="3">
<Card title="年度货物数量" :number="tableData.yearCargos"></Card>
</el-col>
<el-col :xs="24" :sm="12" :md="6" :lg="3" :xl="3">
<Card title="本港货物累计" :number="tableData.totalPortCargos"></Card>
</el-col>
<el-col :xs="24" :sm="12" :md="6" :lg="3" :xl="3">
<Card title="本港年度货物" :number="tableData.yearPortCargos"></Card>
</el-col>
</el-row>
</div>
<div class="home-content">
<el-row :gutter="32">
<el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="4" class="charts">
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="4" class="charts">
<HomeChart :option="outImportVoyages" />
</el-col>
<el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="4" class="charts">
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="4" class="charts">
<HomeChart :option="outExportVoyages" />
</el-col>
<el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="4" class="charts">
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="4" class="charts">
<HomeChart :option="inTotalVoyages" />
</el-col>
<el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="4" class="charts">
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="4" class="charts">
<HomeChart :option="inYearVoyages" />
</el-col>
<el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="4" class="charts">
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="4" class="charts">
<HomeChart :option="outImportCargos" />
</el-col>
<el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="4" class="charts">
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="4" class="charts">
<HomeChart :option="outExportCargos" />
</el-col>
<el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="4" class="charts">
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="4" class="charts">
<HomeChart :option="inTotalCargos" />
</el-col>
<el-col :xs="24" :sm="24" :md="8" :lg="8" :xl="4" class="charts">
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="4" class="charts">
<HomeChart :option="inYearCargos" />
</el-col>
</el-row>
</div>
<div class="home-footer">
<div class="notice">最新公告{{ noticeData?.title }}</div>
</div>
</template>
<div class="home-vin" v-else>
<div class="vin-title">
<div class="text">{{ searchTableForm.vin }}</div>
的航运物流跟踪
</div>
<div class="vin-table" v-for="(item, index) in vinData" :key="index">
<div>{{ item.schedule.createDate }}</div>
<div>{{ item.schedule.loadPort.name }}</div>
<div>{{ item.shipStatus }}</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { ElMessage, FormInstance } from 'element-plus';
import { onMounted, reactive, ref } from 'vue';
import { getStatisticsAPI } from '@/api/ApiManage';
import { getVinSearchAPI } from '@/api/Boat/info';
import { noticePageAPI } from '@/api/Notice';
import { vinSearchType } from '@/types/boatInfo';
import { OutImportVoyages } from '@/types/help';
import { NoticeType } from '@/types/notice';
import Card from './components/Card.vue';
import HomeChart from './components/HomeChart.vue';
//
// const searchTableForm = reactive({
// name: '',
// });
const searchTableFormRef = ref();
const searchTableForm = reactive({
vin: '',
});
const vinData = ref<vinSearchType[]>([]);
const isShowVinDialog = ref(false);
//
const onClickSearch = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
const { data } = await getVinSearchAPI({ vin: searchTableForm.vin });
if (data.length > 0) {
vinData.value = data;
isShowVinDialog.value = true;
} else {
ElMessage({
type: 'warning',
message: '未查询到数据',
});
}
});
};
const noticeData = ref<NoticeType>();
//
const getNoticeList = async () => {
const { data } = await noticePageAPI({
page: 1,
rows: 10,
});
if (data.records.length > 0) {
noticeData.value = data.records[0];
}
};
//
const tableData = ref({
@ -107,6 +164,9 @@ const outImportVoyages = ref({
title: {
text: '外贸进口艘次',
left: 'center',
textStyle: {
color: '#fff',
},
},
legend: {
bottom: '2%',
@ -143,6 +203,9 @@ const outExportVoyages = ref({
title: {
text: '外贸出口艘次',
left: 'center',
textStyle: {
color: '#fff',
},
},
legend: {
bottom: '2%',
@ -174,11 +237,14 @@ const outExportVoyages = ref({
},
});
//
//
const inTotalVoyages = ref({
title: {
text: '贸累计艘次',
text: '贸累计艘次',
left: 'center',
textStyle: {
color: '#fff',
},
},
legend: {
bottom: '2%',
@ -210,11 +276,14 @@ const inTotalVoyages = ref({
},
});
//
//
const inYearVoyages = ref({
title: {
text: '贸年度艘次',
text: '贸年度艘次',
left: 'center',
textStyle: {
color: '#fff',
},
},
legend: {
bottom: '2%',
@ -251,6 +320,9 @@ const outImportCargos = ref({
title: {
text: '外贸进口货物',
left: 'center',
textStyle: {
color: '#fff',
},
},
legend: {
bottom: '2%',
@ -287,6 +359,9 @@ const outExportCargos = ref({
title: {
text: '外贸出口货物',
left: 'center',
textStyle: {
color: '#fff',
},
},
legend: {
bottom: '2%',
@ -323,6 +398,9 @@ const inTotalCargos = ref({
title: {
text: '内贸累计货物',
left: 'center',
textStyle: {
color: '#fff',
},
},
legend: {
bottom: '2%',
@ -359,6 +437,9 @@ const inYearCargos = ref({
title: {
text: '内贸年度货物',
left: 'center',
textStyle: {
color: '#fff',
},
},
legend: {
bottom: '2%',
@ -419,6 +500,7 @@ const handlePieData = (data: OutImportVoyages) => {
onMounted(async () => {
await getHomeStatistics();
await getNoticeList();
});
</script>
@ -428,11 +510,15 @@ onMounted(async () => {
height: 100%;
.home-search {
display: flex;
align-items: center;
justify-content: center;
padding: 16px 16px 0;
margin-bottom: 16px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
.search-label {
color: #fff;
}
}
.home-header {
width: 100%;
@ -444,5 +530,63 @@ onMounted(async () => {
margin-bottom: 20px;
}
}
.home-footer {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
.notice {
display: flex;
align-items: center;
justify-content: center;
width: 60%;
height: 40px;
overflow: hidden;
font-size: 16px;
color: #aaa;
text-overflow: ellipsis;
white-space: nowrap;
background-color: rgb(68 68 68 / 100%);
border-radius: 36px;
}
}
.home-vin {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
height: calc(100% - 70px);
.vin-title {
display: flex;
align-items: center;
justify-content: center;
width: 600px;
height: 60px;
margin-bottom: 40px;
font-size: 20px;
font-weight: 700;
color: #fff;
background-color: rgb(127 127 127 / 100%);
border-radius: 38px;
.text {
margin-right: 6px;
font-size: 32px;
font-style: italic;
text-decoration: underline;
}
}
.vin-table {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
width: 80%;
height: 50px;
font-size: 18px;
color: #fff;
background-color: rgb(255 255 255 / 0%);
border-bottom: 1px solid rgb(215 215 215 / 100%);
}
}
}
</style>

View File

@ -1,25 +1,33 @@
<template>
<div class="login-container">
<!-- 暗黑模式切换 开始 -->
<SwitchDark></SwitchDark>
<!-- 暗黑模式切换 结束 -->
<!-- 登录 -->
<div class="login-box">
<div class="login-welcome" v-if="currentPage === 0">
<img src="../../assets/images/welcome.png" alt="welcome" />
<div class="login">
<div class="login-header">
<img class="logo" src="@/assets/images/logo.png" />
<div class="left-title">
<div class="title">船货信息智慧共享服务平台</div>
<div class="text">Intelligent sharing service platform for ship and cargo information</div>
</div>
<div class="login-form" v-if="currentPage === 0">
<div class="login-logo">
<img class="logo-icon" src="../../assets/images/logo.png" alt="logo" />
<span class="logo-text">船货信息智慧共享服务平台</span>
</div>
<!-- 登录功能 开始 -->
<div class="login-content">
<div class="login-box" v-if="currentPage === 0">
<div class="login-welcome">
<div class="pc">
<div class="logo-title">滚装码头专业版</div>
<img src="@/assets/images/login-pc.png" />
</div>
<div class="mini">
<div class="logo-title">Mini APP</div>
<img src="@/assets/images/login.png" />
</div>
</div>
<div class="login-form">
<el-form ref="loginFormRef" :model="loginForm" :rules="loginRules">
<!-- 用户名 开始 -->
<div class="form-title">
<div class="form-title-left">欢迎使用</div>
<div class="form-title-right" @click="onClickChangePage(1)"></div>
</div>
<el-form-item prop="username">
<el-input
placeholder="请输入用户名"
placeholder="请输入登录账号(手机账号)"
autocomplete="on"
style="position: relative"
v-model="loginForm.username"
@ -30,9 +38,7 @@
</template>
</el-input>
</el-form-item>
<!-- 用户名 结束 -->
<!-- 密码 开始 -->
<el-form-item prop="password">
<el-input
placeholder="请输入密码"
@ -45,29 +51,139 @@
</template>
</el-input>
</el-form-item>
<!-- 密码 结束 -->
<div class="login-btn">
<el-button
type="primary"
type="success"
style="width: 100%; height: 100%"
:loading="loading"
@click="onClickSubmit(loginFormRef)"
>
登录
</el-button>
<el-button style="width: 100%; height: 100%" @click="onClickChangePage(1)">
企业入驻
</el-button>
</div>
</el-form>
<!-- 登录功能 结束 -->
</div>
</div>
<!-- 企业入驻 -->
<div class="login-form1" v-if="currentPage === 1">
<CompanyRegister @go-to-login="onClickChangePage(0)" />
<div class="register" v-if="currentPage === 1">
<div class="register-header">入驻企业登记信息</div>
<div class="register-box">
<div class="register-welcome">
<div class="pc">
<div class="logo-title">滚装码头专业版</div>
<img src="@/assets/images/login-pc.png" />
</div>
<div class="mini">
<div class="logo-title">Mini APP</div>
<img src="@/assets/images/login.png" />
</div>
</div>
<el-form ref="registerFormRef" :rules="formRules" :model="ruleForm" class="register-form">
<div class="form-left">
<div class="form-wrap">
<div class="form-title">基本信息</div>
<el-row :gutter="30">
<el-col :span="12">
<el-form-item prop="enterpriseType">
<el-select
v-model="ruleForm.enterpriseType"
placeholder="请选择企业类型"
style="width: 100%"
>
<el-option
v-for="item in companyTypeList"
:key="item.value"
:label="item.label"
:value="item.value"
></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="portId">
<RemoteSelect
v-model:value="ruleForm.portId"
placeholder="请选择港口"
:api="postPortListAPI"
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item prop="name">
<el-input v-model="ruleForm.name" placeholder="请输入企业全称" />
</el-form-item>
<el-row :gutter="30">
<el-col :span="12">
<el-form-item prop="code">
<el-input v-model="ruleForm.code" placeholder="请输入企业统一社会信用代码" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="legalPerson">
<el-input v-model="ruleForm.legalPerson" placeholder="请输入法定代表人" />
</el-form-item>
</el-col>
</el-row>
<el-form-item prop="contractNo">
<el-input v-model="ruleForm.contractNo" placeholder="请输入合同编号" />
</el-form-item>
<el-form-item prop="address">
<el-input v-model="ruleForm.address" placeholder="请输入企业地址" />
</el-form-item>
<el-form-item prop="licensePhoto">
<el-input v-model="ruleForm.licensePhoto" disabled placeholder="请上传营业执照">
<template #append>
<UploadImg v-model:imageID="ruleForm.licensePhoto" :file-no="ruleForm.code" />
</template>
</el-input>
</el-form-item>
</div>
<div class="form-submit">
<el-button
type="primary"
:loading="submitLoading"
@click="onClickSubmitRegister(registerFormRef)"
>
提交
</el-button>
</div>
</div>
<div class="form-right">
<div class="form-title">联系人 / 管理员</div>
<el-form-item prop="linkman">
<el-input v-model="ruleForm.linkman" placeholder="请输入联系人姓名" />
</el-form-item>
<el-form-item prop="phone">
<el-input v-model="ruleForm.phone" placeholder="请填写登录账号(手机号码)" />
</el-form-item>
<el-form-item prop="idPhoto">
<el-input v-model="ruleForm.idPhoto" disabled placeholder="请上传身份证照片">
<template #append>
<UploadImg
v-model:imageID="ruleForm.idPhoto"
:img-type="1"
:file-no="ruleForm.phone"
/>
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="ruleForm.password" placeholder="请输入登录密码" show-password />
</el-form-item>
<el-form-item prop="confirmPassword">
<el-input
v-model="ruleForm.confirmPassword"
placeholder="请再次输入密码"
show-password
/>
</el-form-item>
</div>
</el-form>
</div>
</div>
</div>
</div>
@ -75,13 +191,16 @@
<script lang="ts" setup>
import type { FormInstance, FormRules } from 'element-plus';
import { ElNotification } from 'element-plus';
import { ElMessage, ElNotification } from 'element-plus';
import { reactive, ref } from 'vue';
import { useRouter } from 'vue-router';
import SwitchDark from '@/components/SwitchDark/index.vue';
import { postEnterpriseRegAPI } from '@/api/Enterprise/company';
import { postPortListAPI } from '@/api/Port';
import RemoteSelect from '@/components/RemoteSelect/index.vue';
import { useUserStore } from '@/store/modules/user';
import { getTimeStateStr } from '@/utils';
import CompanyRegister from './register.vue';
import { validPhone } from '@/utils/validate';
import UploadImg from './components/Upload.vue';
//
const currentPage = ref(0);
@ -96,14 +215,8 @@ const loginFormRef = ref<FormInstance>();
//
const loginRules = reactive<FormRules>({
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 4, message: '用户名长度不能小于4位', trigger: 'blur' },
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 4, max: 16, message: '密码长度不符合规范,密码长度 4 - 16 位', trigger: 'blur' },
],
username: [{ required: true, message: '请填写登录账号', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
});
//
@ -143,26 +256,153 @@ const onClickSubmit = (formEl: FormInstance | undefined) => {
}
});
};
//
const companyTypeList = [
{
value: '港口码头',
label: '港口码头',
},
{
value: '船公司',
label: '船公司',
},
{
value: '货主',
label: '货主',
},
{
value: '船代',
label: '船代',
},
{
value: '货贷',
label: '货贷',
},
];
//
const ruleForm = reactive({
name: '',
code: '',
address: '',
legalPerson: '',
contractNo: '',
linkman: '',
phone: '',
password: '',
confirmPassword: '',
portId: '',
licensePhoto: '',
idPhoto: '',
enterpriseType: '',
});
//
const registerFormRef = ref<FormInstance>();
const validatePass2 = (_: any, value: any, callback: any) => {
if (value === '') {
callback(new Error('请再次输入密码'));
} else if (value !== ruleForm.password) {
callback(new Error('两次密码不一致!'));
} else {
callback();
}
};
const checkPhone = (_: any, value: string, callback: any) => {
if (!validPhone(value)) {
//
callback(new Error('请输入正确的手机号码'));
} else {
callback();
}
};
//
const formRules = reactive<FormRules>({
name: [{ required: true, message: '请输入企业全称', trigger: 'blur' }],
code: [{ required: true, message: '请输入企业统一社会信用代码', trigger: 'blur' }],
address: [{ required: true, message: '请输入企业地址', trigger: 'blur' }],
legalPerson: [{ required: true, message: '请输入法定代表人', trigger: 'blur' }],
contractNo: [{ required: true, message: '请输入合同编号', trigger: 'blur' }],
linkman: [{ required: true, message: '请输入联系人', trigger: 'blur' }],
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ validator: checkPhone, trigger: 'blur' },
],
portId: [{ required: true, message: '请选择港口', trigger: 'blur' }],
licensePhoto: [{ required: true, message: '请上传营业执照', trigger: 'blur' }],
idPhoto: [{ required: true, message: '请上传身份证照片', trigger: 'blur' }],
enterpriseType: [{ required: true, message: '请选择企业类型', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
confirmPassword: [{ required: true, validator: validatePass2, trigger: 'blur' }],
});
//
const submitLoading = ref(false);
//
const onClickSubmitRegister = (formEl: FormInstance | undefined) => {
//
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
try {
submitLoading.value = true; //
//
await postEnterpriseRegAPI(ruleForm);
ElMessage({
message: '企业注册成功',
type: 'success',
});
} finally {
submitLoading.value = false; //
}
});
};
</script>
<style lang="scss" scoped>
.login-container {
position: relative;
.login {
width: 100%;
height: 100%;
background: linear-gradient(180deg, rgb(51 51 51 / 100%) 0%, rgb(242 242 242 / 100%) 100%);
.login-header {
box-sizing: border-box;
display: flex;
align-items: center;
width: 100%;
height: 70px;
padding: 0 20px;
background-color: rgb(156 0 12 / 100%);
.logo {
width: 80px;
height: 50px;
margin-right: 16px;
}
.left-title {
display: flex;
flex-direction: column;
color: #fff;
.title {
font-size: 24px;
font-style: italic;
font-weight: 700;
}
.text {
font-size: 15px;
font-weight: 400;
}
}
}
.login-content {
display: flex;
align-items: center;
justify-content: center;
min-width: 550px;
height: 100%;
min-height: 500px;
background-image: url('@/assets/images/login_bg.svg');
background-position: 50%;
background-size: 100% 100%;
background-size: cover;
.dark {
position: absolute;
top: 5%;
right: 3.2%;
}
width: 100%;
height: calc(100% - 70px);
.login-box {
box-sizing: border-box;
display: flex;
@ -170,33 +410,68 @@ const onClickSubmit = (formEl: FormInstance | undefined) => {
justify-content: space-around;
width: 96%;
height: 94%;
padding: 0 4% 0 20px;
overflow: hidden;
border-radius: 10px;
.login-welcome {
width: 750px;
display: flex;
.pc {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.logo-title {
font-size: 32px;
font-style: normal;
font-weight: 700;
color: #fff;
}
img {
width: 100%;
height: 100%;
width: 500px;
height: 400px;
}
}
.mini {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
height: 445px;
margin-left: 10px;
.logo-title {
margin-bottom: 20px;
font-size: 32px;
font-style: normal;
font-weight: 700;
color: #fff;
}
img {
width: 160px;
height: 330px;
}
}
}
.login-form {
box-sizing: border-box;
padding: 40px 45px 25px;
border-radius: 10px;
.login-logo {
width: 400px;
padding: 60px 45px;
background-color: #fff;
border-radius: 7px;
box-shadow: 0 0 13px rgb(0 0 0 / 34.9%);
.form-title {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 40px;
.logo-icon {
width: 70px;
align-items: flex-end;
justify-content: space-between;
width: 100%;
margin-bottom: 20px;
font-weight: 700;
.form-title-left {
font-size: 38px;
}
.logo-text {
padding-left: 25px;
font-size: 48px;
font-weight: bold;
white-space: nowrap;
.form-title-right {
font-size: 20px;
color: #027db4;
cursor: pointer;
}
.form-title-right:hover {
color: rgb(217 0 27);
}
}
:deep(.el-form-item) {
@ -221,11 +496,111 @@ const onClickSubmit = (formEl: FormInstance | undefined) => {
margin: 20px 0;
}
}
.login-form1 {
}
.register {
box-sizing: border-box;
width: 70%;
padding: 40px 45px 25px;
border-radius: 10px;
width: 96%;
height: 94%;
background-color: #fff;
.register-header {
box-sizing: border-box;
width: 100%;
padding: 40px 0;
font-size: 39px;
font-weight: 700;
text-align: center;
}
.register-box {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 0 20px;
.register-welcome {
display: flex;
.pc {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
.logo-title {
font-size: 32px;
font-style: normal;
font-weight: 700;
}
img {
width: 320px;
height: 256px;
}
}
.mini {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
height: 445px;
margin-left: 10px;
.logo-title {
margin-bottom: 20px;
font-size: 32px;
font-style: normal;
font-weight: 700;
}
img {
width: 100px;
height: 200px;
}
}
}
.register-form {
box-sizing: border-box;
display: flex;
flex: 1;
justify-content: space-between;
padding: 40px;
.form-left {
width: 49%;
.form-wrap {
position: relative;
box-sizing: border-box;
width: 100%;
padding: 20px 30px;
border: 1px dashed #ddd;
border-radius: 8px;
.form-title {
position: absolute;
top: -14px; /* 使标题悬浮在边框上方 */
left: 20px; /* 根据需要调整水平位置 */
padding: 0 8px; /* 增加一点内边距 */
font-size: 18px;
color: #333;
background: #fff; /* 设置背景覆盖边框 */
}
}
.form-submit {
margin-top: 20px;
}
}
.form-right {
position: relative;
box-sizing: border-box;
width: 49%;
padding: 20px 30px;
border: 1px dashed #ddd;
border-radius: 8px;
.form-title {
position: absolute;
top: -14px; /* 使标题悬浮在边框上方 */
left: 20px; /* 根据需要调整水平位置 */
padding: 0 8px; /* 增加一点内边距 */
font-size: 18px;
color: #333;
background: #fff; /* 设置背景覆盖边框 */
}
}
}
}
}
}
}

View File

@ -1,18 +0,0 @@
<template>
<div class="manage">
<ManifestDetailTable />
</div>
</template>
<script lang="ts" setup>
import ManifestDetailTable from './components/ManifestDetailTable.vue';
</script>
<style lang="scss" scoped>
.manage {
display: flex;
flex-direction: row;
width: 100%;
height: 100%;
}
</style>

View File

@ -1,18 +0,0 @@
<template>
<div class="manage">
<ManifestTable />
</div>
</template>
<script lang="ts" setup>
import ManifestTable from './components/ManifestTable.vue';
</script>
<style lang="scss" scoped>
.manage {
display: flex;
flex-direction: row;
width: 100%;
height: 100%;
}
</style>

View File

@ -3,7 +3,11 @@
<div class="header">
<el-form :inline="true" :model="searchTableForm" ref="tableFormRef">
<el-form-item prop="name">
<el-input v-model="searchTableForm.billNo" placeholder="请输入提单号" />
<el-input
v-model="searchTableForm.billNo"
placeholder="请输入提单号"
style="width: 120px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="onClickSearch"></el-button>
@ -12,14 +16,12 @@
</el-form>
</div>
<div class="footer">
<div class="footer-util">舱单明细</div>
<!-- 表格 -->
<div class="footer-table">
<el-table
v-loading="tableLoading"
:data="tableState.tableData"
border
stripe
style="width: 100%; height: 100%"
>
<el-table-column prop="manifest.name" label="舱单" align="center" width="150" />
@ -56,24 +58,32 @@
<script lang="ts" setup>
import { FormInstance } from 'element-plus';
import { onMounted, reactive, ref } from 'vue';
import { useRoute } from 'vue-router';
import { getManifestDetailPageAPI } from '@/api/Manifest/detail';
import Pagination from '@/components/Pagination/Pagination.vue';
import { useTable } from '@/hooks/useTable';
//
const route = useRoute();
//
const props = withDefaults(
defineProps<{
manifestId: string;
}>(),
{
manifestId: '',
}
);
//
const { getTableList, tableState, searchTable, resetTable, tableChangeCurrent, tableChangeSize } =
useTable({
api: getManifestDetailPageAPI,
initParam: {
manifestId: [route.query.manifestId as string],
},
});
onMounted(async () => {
if (props.manifestId) {
tableState.value.searchInitParam = {
manifestId: [props.manifestId],
};
}
await getTableList();
});
@ -107,28 +117,6 @@ const onClickResetForm = async (formEl: FormInstance | undefined) => {
formEl.resetFields();
};
//
// const onClickDel = (row: EmployeeType) => {
// ElMessageBox.confirm(` ${row.name} ?`, '', {
// confirmButtonText: '',
// cancelButtonText: '',
// type: 'warning',
// draggable: true,
// })
// .then(async () => {
// await postEmployeeDeleteAPI([row.id]);
// //
// await getTableList();
// ElMessage({
// message: '',
// type: 'success',
// });
// })
// .catch(() => {
// console.log('');
// });
// };
//
const handleSizeChange = async (val: number) => {
await tableChangeSize(val);
@ -149,11 +137,8 @@ const handleCurrentChange = async (val: number) => {
width: 100%;
.header {
display: flex;
padding: 16px 16px 0;
margin-bottom: 16px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
justify-content: space-between;
padding: 0 16px;
}
.footer {
position: relative;
@ -163,9 +148,6 @@ const handleCurrentChange = async (val: number) => {
flex-direction: column;
padding: 16px;
overflow: hidden;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
.footer-util {
display: flex;
flex-shrink: 0;

View File

@ -5,33 +5,19 @@
<el-form-item>
<RemoteSelect
v-model:value="searchTableForm.shipId"
placeholder="请选择船"
placeholder="请选择船"
:api="postShipListAPI"
style="width: 120px"
/>
</el-form-item>
<el-form-item>
<el-input v-model="searchTableForm.voyage" placeholder="请输入航次" />
</el-form-item>
<el-form-item>
<el-select
v-model="searchTableForm.goodsStatus"
placeholder="货物状态"
style="width: 120px"
>
<el-option
v-for="item in goodsStatusList"
:key="item.value"
:label="item.label"
:value="item.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="onClickSearch"></el-button>
<el-button icon="Refresh" @click="onClickResetForm(tableFormRef)"></el-button>
</el-form-item>
</el-form>
<div>
<el-button type="success" @click="onClickExport"></el-button>
</div>
</div>
<div class="footer">
<!-- 表格 -->
@ -40,7 +26,6 @@
v-loading="tableLoading"
:data="tableState.tableData"
border
stripe
style="width: 100%; height: 100%"
>
<el-table-column prop="schedule.name" label="船期" align="center" width="120" />
@ -87,10 +72,9 @@
</template>
<script lang="ts" setup>
import { FormInstance } from 'element-plus';
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
import { onMounted, reactive, ref } from 'vue';
import { useRouter } from 'vue-router';
import { getManifestPageAPI } from '@/api/Manifest';
import { getManifestFileExportAPI, getManifestPageAPI } from '@/api/Manifest';
import { postShipListAPI } from '@/api/Ship';
import Pagination from '@/components/Pagination/Pagination.vue';
import RemoteSelect from '@/components/RemoteSelect/index.vue';
@ -107,25 +91,6 @@ onMounted(async () => {
await getTableList();
});
const goodsStatusList = [
{
value: '有计划',
label: '有计划',
},
{
value: '已靠港',
label: '已靠港',
},
{
value: '作业中',
label: '作业中',
},
{
value: '已离港',
label: '已离港',
},
];
//
const searchTableForm = reactive({
scheduleId: '',
@ -161,40 +126,54 @@ const onClickResetForm = async (formEl: FormInstance | undefined) => {
formEl.resetFields();
};
//
const router = useRouter();
//
const onClickOpenDetail = (row: ManifestType) => {
router.push({
path: '/Manifest/detail',
query: {
manifestId: row.id,
},
//
const onClickExport = () => {
ElMessageBox.confirm(`你确定要导出舱单信息吗?`, '温馨提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
draggable: true,
})
.then(async () => {
try {
let params: any = {
rows: tableState.value.pageAble.rows,
page: tableState.value.pageAble.page,
};
if (searchTableForm.shipId) {
params.shipId = searchTableForm.shipId;
}
const { data } = await getManifestFileExportAPI(params);
const blob = new Blob([data], {
type: 'application/vnd.ms-excel;charset=utf-8',
});
const downloadUrl = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = '舱单信息.xlsx';
link.click();
ElMessage({
message: '导出成功',
type: 'success',
});
} catch (error) {
ElMessage({
message: '导出失败',
type: 'error',
});
}
})
.catch(() => {
console.log('用户点击了取消');
});
};
//
// const onClickDel = (row: ManifestType) => {
// ElMessageBox.confirm(`?`, '', {
// confirmButtonText: '',
// cancelButtonText: '',
// type: 'warning',
// draggable: true,
// })
// .then(async () => {
// await postManifestDeleteAPI([row.id]);
// //
// await getTableList();
// ElMessage({
// message: '',
// type: 'success',
// });
// })
// .catch(() => {
// console.log('');
// });
// };
const emits = defineEmits(['ManifestID']);
//
const onClickOpenDetail = (row: ManifestType) => {
emits('ManifestID', row);
};
//
const handleSizeChange = async (val: number) => {
@ -216,11 +195,8 @@ const handleCurrentChange = async (val: number) => {
width: 100%;
.header {
display: flex;
padding: 16px 16px 0;
margin-bottom: 16px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
justify-content: space-between;
padding: 0 16px;
}
.footer {
position: relative;
@ -230,9 +206,6 @@ const handleCurrentChange = async (val: number) => {
flex-direction: column;
padding: 16px;
overflow: hidden;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
.footer-util {
display: flex;
flex-shrink: 0;

View File

@ -0,0 +1,103 @@
<template>
<div class="table">
<div class="header">
<div class="title">{{ currentSubscribeNav }}</div>
<div class="api-btn">
<div
class="btn"
:class="{ 'btn-active': currentSubscribeNav === item }"
v-for="item in subscribeNavList"
:key="item"
@click="onClickChangeSubscribeType(item)"
>
{{ item }}
</div>
</div>
</div>
<div class="content">
<ManifestTable v-if="currentSubscribeNav === ''" @ManifestID="onClickOpenDetail" />
<ManifestDetailTable :manifestId="manifestID" v-if="currentSubscribeNav === ''" />
<!-- <ReceiveTable v-if="currentSubscribeNav === ''" /> -->
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { ManifestType } from '@/types/manifest';
import ManifestDetailTable from './components/ManifestDetailTable.vue';
import ManifestTable from './components/ManifestTable.vue';
const manifestID = ref();
//
const subscribeNavList = ref(['舱单信息', '提单明细', '积载图']);
const currentSubscribeNav = ref('舱单信息');
const onClickChangeSubscribeType = async (item: string) => {
if (item === currentSubscribeNav.value) return;
currentSubscribeNav.value = item;
if (item === '提单明细') {
manifestID.value = '';
}
};
const onClickOpenDetail = (row: ManifestType) => {
manifestID.value = row.id;
currentSubscribeNav.value = '提单明细';
};
</script>
<style lang="scss" scoped>
.table {
position: relative;
display: flex;
flex: 1;
flex-direction: column;
width: 100%;
height: 100%;
.header {
box-sizing: border-box;
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: space-between;
padding: 16px 16px 0;
margin-bottom: 15px;
color: #fff;
.title {
font-size: 28px;
font-weight: 700;
}
.api-btn {
display: flex;
flex: 1;
align-items: center;
justify-content: flex-end;
.btn {
display: flex;
align-items: center;
justify-content: center;
width: 150px;
height: 50px;
margin-left: 10px;
font-size: 18px;
color: #d7d7d7;
background-color: rgb(51 51 51 / 100%);
border-radius: 75px;
box-shadow: 5px 5px 5px rgb(0 0 0 / 34.9%);
}
.btn-active {
color: #000;
background-color: #f2f2f2;
box-shadow: none;
}
}
}
.content {
box-sizing: border-box;
flex: 1;
padding: 16px;
}
}
</style>

View File

@ -1,7 +1,7 @@
<template>
<div class="notify">
<div class="title">平台公告</div>
<div class="list">
<div class="list-title">平台公告</div>
<ul
class="list-wrap"
v-infinite-scroll="noticeListLoad"
@ -14,17 +14,21 @@
class="list-item"
@click="onClickOpenDialog(item)"
>
<div class="item-icon"></div>
<div class="item-title">{{ item.title }}</div>
<div class="item-date">{{ item.createDate }}</div>
</li>
</ul>
</div>
<el-dialog v-model="isShowNotice" width="70%" top="50px">
<div class="content">
<div class="border1"></div>
<div class="border2"></div>
<div class="border3"></div>
<div class="border4"></div>
<div class="title">{{ noticeViewData?.title }}</div>
<div class="wrap" v-html="noticeViewData?.content"></div>
</div>
</el-dialog>
</div>
</div>
</template>
@ -79,16 +83,8 @@ const onClickOpenDialog = async (row: NoticeType) => {
width: 100%;
height: 100%;
overflow-x: hidden;
.list {
box-sizing: border-box;
width: 100%;
height: 100%;
padding: 20px;
color: #303133;
background-color: #fff;
border: 1px solid #e4ede7;
border-radius: 4px;
.list-title {
color: #fff;
.title {
display: flex;
flex-shrink: 0;
align-items: center;
@ -97,10 +93,16 @@ const onClickOpenDialog = async (row: NoticeType) => {
font-size: 30px;
font-weight: 700;
}
.list {
box-sizing: border-box;
display: flex;
justify-content: space-between;
width: 100%;
height: calc(100% - 100px);
.list-wrap {
box-sizing: border-box;
width: 100%;
height: calc(100% - 50px);
width: 30%;
height: 500px;
overflow: auto;
.list-item {
box-sizing: border-box;
@ -109,7 +111,13 @@ const onClickOpenDialog = async (row: NoticeType) => {
width: 100%;
padding: 10px 0;
overflow: hidden;
border-bottom: 1px dashed #e4e4e7;
.item-icon {
width: 10px;
height: 10px;
margin-right: 10px;
background-color: #fff;
border-radius: 50%;
}
.item-title {
flex: 1;
overflow: hidden;
@ -123,21 +131,63 @@ const onClickOpenDialog = async (row: NoticeType) => {
}
}
}
}
.content {
width: 100%;
position: relative;
box-sizing: border-box;
flex: 1;
height: 100%;
margin-left: 20px;
font-size: 21px;
font-style: normal;
font-weight: 700;
background-color: #333;
border: 5px solid #fff;
border-radius: 9px;
.title {
display: flex;
justify-content: center;
padding: 16px;
font-size: 23px;
font-weight: 700;
color: var(--el-text-color-regular);
}
.wrap {
box-sizing: border-box;
width: 100%;
height: calc(100% - 70px);
padding: 0 16px 20px;
}
.border1 {
position: absolute;
top: -5px;
left: 10%;
width: 80%;
height: 5px;
background-color: #333;
}
.border2 {
position: absolute;
top: 10%;
left: -5px;
width: 5px;
height: 80%;
background-color: #333;
}
.border3 {
position: absolute;
top: 10%;
left: 100%;
width: 5px;
height: 80%;
background-color: #333;
}
.border4 {
position: absolute;
top: 100%;
left: 10%;
width: 80%;
height: 5px;
background-color: #333;
}
}
}
}

View File

@ -0,0 +1,122 @@
<template>
<div class="send">
<el-steps class="header" :active="currentActive" finish-status="success">
<el-step title="发布船期信息" />
<el-step title="发布舱单信息" />
<el-step title="提交舱单明细" />
</el-steps>
<div class="content">
<div class="content-left">
<div class="left-icon">{{ currentActive }}</div>
<div class="left-title">{{ currentTitle[currentActive - 1] }}</div>
</div>
<div class="content-right">
<BoatInfoSend @updateBoatID="onUpdateBoatID" v-if="currentActive === 1" />
<ManifestSend
:scheduleId="manifestProps?.scheduleId"
:voyage="manifestProps?.voyage"
:loadPortId="manifestProps?.loadPortId"
:dischargePortId="manifestProps?.dischargePortId"
@updateBoatID="onUpdateManifest"
v-if="currentActive === 2"
/>
<ManifestDetailSend
:manifestId="manifestDetailProps?.manifestId"
:billNo="manifestDetailProps?.billNo"
:brandId="manifestDetailProps?.brandId"
:model="manifestDetailProps?.model"
v-if="currentActive === 3"
/>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import BoatInfoSend from './BoatInfoSend.vue';
import ManifestDetailSend from './ManifestDetailSend.vue';
import ManifestSend from './ManifestSend.vue';
//
const currentActive = ref(1);
const currentTitle = ref(['发布船期信息', '发布舱单信息', '提交舱单明细']);
interface manifestPropsType {
scheduleId: string;
voyage: string;
loadPortId: number | string;
dischargePortId: string | number;
}
const manifestProps = ref<manifestPropsType>();
//
const onUpdateBoatID = (params: manifestPropsType) => {
manifestProps.value = params;
currentActive.value = 2;
};
interface manifestDetailPropsType {
manifestId: string | number;
billNo?: string;
brandId?: string;
model?: string;
}
const manifestDetailProps = ref<manifestDetailPropsType>();
const onUpdateManifest = (params: manifestDetailPropsType) => {
manifestDetailProps.value = params;
currentActive.value = 3;
};
</script>
<style lang="scss" scoped>
.send {
width: 100%;
height: 100%;
.header {
width: 100%;
height: 70px;
}
.content {
box-sizing: border-box;
display: flex;
width: 100%;
height: calc(100% - 70px);
.content-left {
box-sizing: border-box;
display: flex;
flex-direction: row;
width: 30%;
height: 100%;
padding: 40px 0 0 20px;
background-color: #027db4;
.left-icon {
width: 64px;
height: 64px;
margin-right: 10px;
font-size: 32px;
font-weight: 600;
line-height: 64px;
color: #d9001b;
text-align: center;
background-color: #fff;
border-radius: 50%;
}
.left-title {
height: 64px;
font-size: 32px;
font-weight: 700;
line-height: 64px;
color: #fff;
}
}
.content-right {
box-sizing: border-box;
width: 70%;
height: 100%;
padding: 20px;
background-color: #fff;
}
}
}
</style>

View File

@ -0,0 +1,176 @@
<template>
<div class="table">
<div class="footer">
<div class="footer-util">
<div class="title">我最近发布的信息</div>
<el-button type="primary" icon="Plus" @click="onClickSendInfo"> </el-button>
</div>
<!-- 表格 -->
<div class="footer-table">
<el-table
v-loading="tableLoading"
:data="tableState.tableData"
border
style="width: 100%; height: 100%"
>
<el-table-column prop="enterprise.name" label="企业" align="center" width="150" />
<el-table-column prop="ship.name" label="船舶" align="center" width="150" />
<el-table-column prop="voyage" label="航次" align="center" width="180" />
<el-table-column prop="shipRoute.name" label="航线" align="center" width="150" />
<el-table-column prop="loadWharf.name" label="装货码头" align="center" width="150" />
<el-table-column prop="dischargeWharf.name" label="卸货码头" align="center" width="150" />
<el-table-column prop="carNumPlan" label="计划商品车数量" align="center" width="180" />
<el-table-column prop="spareNumPlan" label="计划件杂货数量" align="center" width="180" />
<el-table-column prop="carNumActual" label="实际商品车数量" align="center" width="180" />
<el-table-column
prop="spareNumActual"
label="实际件杂货数量"
align="center"
width="180"
/>
<el-table-column
prop="departureDatePlan"
label="计划离泊时间"
align="center"
width="200"
/>
<el-table-column
prop="departureDateActual"
label="实际离泊时间"
align="center"
width="200"
/>
<el-table-column prop="tradeType" label="贸易类型" align="center" width="150" />
<el-table-column prop="loadPort.name" label="装货港口" align="center" width="150" />
<el-table-column prop="dischargePort.name" label="卸货港口" align="center" width="150" />
<el-table-column prop="shipStatus" label="当前状态" align="center" width="150" />
<el-table-column prop="createDate" label="创建时间" align="center" width="180" />
<el-table-column prop="operator" label="操作" width="200px" align="center" fixed="right">
<template #default="scope">
<el-button
type="primary"
size="small"
icon="View"
link
@click="onClickOpenDetail(scope.row)"
>
查看
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 分页 -->
<div class="footer-pagination">
<Pagination
:pageAble="tableState.pageAble"
:handle-size-change="handleSizeChange"
:handle-current-change="handleCurrentChange"
/>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import dayjs from 'dayjs';
import { onMounted, ref } from 'vue';
import { getSailScheduleGetAPI, getSailSchedulePageAPI } from '@/api/Boat/info';
import Pagination from '@/components/Pagination/Pagination.vue';
import { useTable } from '@/hooks/useTable';
import { PageRowsResult } from '@/types';
import { BoatInfoType } from '@/types/boatInfo';
const handleTableData = (data: PageRowsResult<BoatInfoType>) => {
const { records } = data;
for (const item of records) {
item.departureDatePlan = dayjs(item.departureDatePlan).format('YYYY-MM-DD HH:mm:ss');
item.departureDateActual = dayjs(item.departureDateActual).format('YYYY-MM-DD HH:mm:ss');
}
return data;
};
//
const { getTableList, tableState, tableChangeCurrent, tableChangeSize } = useTable({
api: getSailSchedulePageAPI,
dataCallBack: handleTableData,
});
onMounted(async () => {
await getTableList();
});
//
const tableLoading = ref(false);
const emits = defineEmits(['sendInfo']);
//
const onClickSendInfo = () => {
emits('sendInfo');
};
//
const onClickOpenDetail = async (row: BoatInfoType) => {
const { data } = await getSailScheduleGetAPI({ id: row.id });
console.log('🚀 ~ file: BoatTable.vue:146 ~ onClickOpenDetail ~ data:', data);
};
//
const handleSizeChange = async (val: number) => {
await tableChangeSize(val);
};
//
const handleCurrentChange = async (val: number) => {
await tableChangeCurrent(val);
};
</script>
<style lang="scss" scoped>
.table {
position: relative;
display: flex;
flex: 1;
flex-direction: column;
width: 100%;
.header {
display: flex;
justify-content: space-between;
padding: 0 16px;
}
.footer {
position: relative;
box-sizing: border-box;
display: flex;
flex: 1;
flex-direction: column;
padding: 16px;
overflow: hidden;
.footer-util {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: space-between;
margin-bottom: 15px;
.title {
font-size: 28px;
font-weight: 400;
color: #80ffff;
}
}
.footer-table {
position: relative;
flex: 1;
}
.footer-pagination {
box-sizing: border-box;
display: flex;
flex-shrink: 0;
justify-content: flex-end;
width: 100%;
padding-top: 20px;
}
}
}
</style>

View File

@ -1,84 +1,80 @@
<template>
<div class="send">
<el-steps class="header" :active="currentActive" finish-status="success">
<el-step title="发布船期信息" />
<el-step title="发布舱单信息" />
<el-step title="提交舱单明细" />
</el-steps>
<div class="table">
<div class="header">
<div class="title">发布船货信息</div>
</div>
<div class="content">
<BoatInfoSend @updateBoatID="onUpdateBoatID" v-if="currentActive === 1" />
<ManifestSend
:scheduleId="manifestProps?.scheduleId"
:voyage="manifestProps?.voyage"
:loadPortId="manifestProps?.loadPortId"
:dischargePortId="manifestProps?.dischargePortId"
@updateBoatID="onUpdateManifest"
v-if="currentActive === 2"
/>
<ManifestDetailSend
:manifestId="manifestDetailProps?.manifestId"
:billNo="manifestDetailProps?.billNo"
:brandId="manifestDetailProps?.brandId"
:model="manifestDetailProps?.model"
v-if="currentActive === 3"
/>
<SendTable v-if="currentSubscribeNav === ''" @sendInfo="onClickSendInfo" />
<Send v-if="currentSubscribeNav === ''" />
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import BoatInfoSend from './components/BoatInfoSend.vue';
import ManifestDetailSend from './components/ManifestDetailSend.vue';
import ManifestSend from './components/ManifestSend.vue';
import Send from './components/Send.vue';
import SendTable from './components/SendTable.vue';
//
const currentActive = ref(1);
//
const currentSubscribeNav = ref('船货信息');
interface manifestPropsType {
scheduleId: string;
voyage: string;
loadPortId: number | string;
dischargePortId: string | number;
}
const manifestProps = ref<manifestPropsType>();
//
const onUpdateBoatID = (params: manifestPropsType) => {
manifestProps.value = params;
currentActive.value = 2;
};
interface manifestDetailPropsType {
manifestId: string | number;
billNo?: string;
brandId?: string;
model?: string;
}
const manifestDetailProps = ref<manifestDetailPropsType>();
const onUpdateManifest = (params: manifestDetailPropsType) => {
manifestDetailProps.value = params;
currentActive.value = 3;
//
const onClickSendInfo = () => {
currentSubscribeNav.value = '发布信息';
};
</script>
<style lang="scss" scoped>
.send {
box-sizing: border-box;
.table {
position: relative;
display: flex;
flex: 1;
flex-direction: column;
width: 100%;
height: 100%;
padding: 16px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
.header {
width: 100%;
height: 70px;
box-sizing: border-box;
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
padding: 16px 16px 0;
margin-bottom: 15px;
color: #fff;
.title {
font-size: 28px;
font-weight: 700;
}
.api-btn {
display: flex;
flex: 1;
align-items: center;
justify-content: flex-end;
.btn {
display: flex;
align-items: center;
justify-content: center;
width: 150px;
height: 50px;
margin-left: 10px;
font-size: 18px;
color: #d7d7d7;
background-color: rgb(51 51 51 / 100%);
border-radius: 75px;
box-shadow: 5px 5px 5px rgb(0 0 0 / 34.9%);
}
.btn-active {
color: #000;
background-color: #f2f2f2;
box-shadow: none;
}
}
}
.content {
box-sizing: border-box;
width: 100%;
height: calc(100% - 70px);
flex: 1;
padding: 16px;
}
}
</style>

View File

@ -21,34 +21,26 @@
:disabled="dialogProps.isView"
/>
</el-form-item>
<!-- <el-form-item label="码头">
<el-select
v-model="dialogProps.row!.wharfId"
placeholder="请选择港口"
style="width: 100%"
clearable
filterable
remote
reserve-keyword
remote-show-suffix
:remote-method="onRemoteWharf"
:loading="wharfLoading"
<el-form-item label="码头" prop="wharfId" required>
<RemoteSelect
v-model:value="dialogProps.row!.wharfId"
placeholder="请选择码头"
:api="postWharfListAPI"
:disabled="dialogProps.isView"
>
<el-option
v-for="item in wharfList"
:key="item.id"
:label="item.text"
:value="item.id"
/>
</el-select>
</el-form-item> -->
</el-form-item>
<el-form-item label="姓名" prop="name">
<el-input v-model="dialogProps.row!.name" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="昵称">
<el-input v-model="dialogProps.row!.nickname" placeholder="请输入昵称" />
</el-form-item>
<el-form-item label="部门" prop="dept">
<el-input v-model="dialogProps.row!.dept" placeholder="请输入部门" />
</el-form-item>
<el-form-item label="职位" prop="job">
<el-input v-model="dialogProps.row!.job" placeholder="请输入职位" />
</el-form-item>
<el-form-item label="用户名" prop="username">
<el-input v-model="dialogProps.row!.username" placeholder="请输入用户名" />
</el-form-item>
@ -74,6 +66,7 @@
import { ElMessage, FormInstance, FormRules } from 'element-plus';
import { reactive, ref } from 'vue';
import { postEnterpriseListAPI } from '@/api/Enterprise/company';
import { postWharfListAPI } from '@/api/Wharf';
import RemoteSelect from '@/components/RemoteSelect/index.vue';
import { EmployeeType } from '@/types/Enterprise';
@ -109,7 +102,9 @@ const formRules = reactive<FormRules>({
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
enterpriseId: [{ required: true, message: '请选择企业', trigger: 'blur' }],
// password: [{ required: true, message: '', trigger: 'blur' }],
wharfId: [{ required: true, message: '请选择码头', trigger: 'blur' }],
job: [{ required: true, message: '请输入职位', trigger: 'blur' }],
dept: [{ required: true, message: '请输入部门', trigger: 'blur' }],
phone: [{ required: true, message: '请输入联系电话', trigger: 'blur' }],
});

View File

@ -0,0 +1,237 @@
<template>
<div class="notify">
<div class="list">
<div class="list-left">
<div class="left-title">在册用户</div>
<div
class="list-wrap"
v-infinite-scroll="employeeListLoad"
:infinite-scroll-disabled="disabled"
:infinite-scroll-distance="100"
>
<div
v-for="item in employeeList"
:key="item.id"
class="list-item"
:class="{ 'list-item-active': currentUser.userId === item.id }"
@click="onClickOpenDialog(item)"
>
<div class="item-title">{{ item.name }}</div>
<div class="item-date">{{ item.dept }}/{{ item.job }}</div>
</div>
</div>
</div>
<div class="list-right">
<div class="right-title">操作权限</div>
<div class="content">
<div class="content-title" v-if="currentUser.name">{{ currentUser.name }}</div>
<el-checkbox-group v-model="currentUser.perms">
<div class="content-checkbox">
<div class="checkbox-title">查看船货信息</div>
<div class="checkbox">
<el-checkbox label="船期信息" value="/sail_schedule/page" />
<el-checkbox label="舱单信息" value="/manifest/page" />
<el-checkbox label="舱单明细" value="/manifest/detail/page" />
</div>
</div>
<div class="content-checkbox">
<div class="checkbox-title">发布船货信息</div>
<div class="checkbox">
<el-checkbox label="发布船货信息" value="sail_schedule/save" />
</div>
</div>
<div class="content-checkbox">
<div class="checkbox-title">订阅船货信息</div>
<div class="checkbox">
<el-checkbox label="我的订阅" value="/subscribe/publish/page" />
<el-checkbox label="收到订阅" value="/subscribe/receive/page" />
</div>
</div>
</el-checkbox-group>
<div class="content-btn">
<el-button type="primary" @click="onClickSubmit"> </el-button>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ElMessage } from 'element-plus';
import { computed, ref } from 'vue';
import { postEmployeePageAPI } from '@/api/Enterprise/employee';
import { getUserRoleMenuListAPI, postRolePremSaveAPI } from '@/api/System/role';
import { EmployeeType } from '@/types/Enterprise';
//
const employeeList = ref<EmployeeType[]>([]);
// -
const currentPage = ref(1);
//
const loadMoreState = ref('loading');
const disabled = computed(() => loadMoreState.value === 'finished');
//
const getEmployeeList = async () => {
const { data } = await postEmployeePageAPI({
page: currentPage.value,
rows: 10,
});
employeeList.value = [...employeeList.value, ...data.records];
currentPage.value++;
//
if (currentPage.value >= Number(data.pages) || data.records.length < 10) {
loadMoreState.value = 'finished';
} else {
loadMoreState.value = 'loading';
}
};
//
const currentUser = ref<{
name: string;
userId: number | string;
perms: string[];
}>({
name: '',
userId: '',
perms: [],
});
//
const onClickOpenDialog = async (row: EmployeeType) => {
const { data } = await getUserRoleMenuListAPI({ userId: row.id });
currentUser.value.name = row.name;
currentUser.value.userId = row.id;
currentUser.value.perms = data;
};
//
const onClickSubmit = async () => {
//
if (currentUser.value.userId) {
await postRolePremSaveAPI({
userId: currentUser.value.userId,
perms: currentUser.value.perms,
});
ElMessage({
message: '保存成功',
type: 'success',
});
} else {
ElMessage({
message: '请选择用户',
type: 'warning',
});
}
};
const employeeListLoad = async () => {
await getEmployeeList();
};
</script>
<style lang="scss" scoped>
.notify {
width: 100%;
height: 100%;
overflow-x: hidden;
color: #fff;
.list {
box-sizing: border-box;
display: flex;
justify-content: space-between;
width: 100%;
height: 100%;
.list-left {
box-sizing: border-box;
width: 30%;
.left-title {
font-size: 20px;
font-weight: 700;
line-height: 40px;
}
.list-wrap {
height: 500px;
overflow: auto;
.list-item {
box-sizing: border-box;
display: flex;
align-items: center;
width: 100%;
height: 50px;
padding: 0 6px;
overflow: hidden;
border-bottom: 1px solid #fff;
.item-title {
flex: 1;
overflow: hidden;
font-size: 16px;
text-overflow: ellipsis;
white-space: nowrap;
}
.item-date {
margin-left: 20px;
font-size: 14px;
}
}
.list-item:hover {
color: #333;
background-color: #ffffc9;
}
.list-item-active {
color: #333;
background-color: #ffffc9;
}
}
}
.list-right {
box-sizing: border-box;
flex: 1;
height: 100%;
margin-left: 20px;
.right-title {
font-size: 20px;
font-weight: 700;
line-height: 40px;
}
.content {
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: space-between;
width: 100%;
height: 400px;
padding: 40px;
color: #333;
background-color: #fff;
border-radius: 6px;
.content-title {
font-size: 20px;
font-weight: 700;
line-height: 40px;
}
.content-checkbox {
display: flex;
align-items: center;
width: 100%;
margin-bottom: 20px;
.checkbox-title {
font-size: 18px;
font-weight: 400;
}
.checkbox {
flex: 1;
}
}
}
}
}
}
</style>

View File

@ -2,51 +2,32 @@
<div class="table">
<div class="header">
<el-form :inline="true" :model="searchTableForm" ref="tableFormRef">
<!-- <el-form-item prop="enterpriseId">
<RemoteSelect
v-model:value="searchTableForm.enterpriseId"
placeholder="请选择企业"
style="width: 120px"
:api="postEnterpriseListAPI"
/>
</el-form-item> -->
<el-form-item prop="wharfId">
<RemoteSelect
v-model:value="searchTableForm.wharfId"
placeholder="请选择码头"
:api="postWharfListAPI"
style="width: 120px"
/>
</el-form-item>
<el-form-item prop="name">
<el-input v-model="searchTableForm.name" placeholder="姓名" />
</el-form-item>
<el-form-item prop="username">
<el-input v-model="searchTableForm.username" placeholder="用户名" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="onClickSearch"></el-button>
<el-button icon="Refresh" @click="onClickResetForm(tableFormRef)"></el-button>
</el-form-item>
</el-form>
</div>
<div class="footer">
<div class="footer-util">
<div>
<el-button type="primary" icon="Plus" @click="onClickOpenDialog('新增')">
新增员工
</el-button>
</div>
</div>
<div class="footer">
<!-- 表格 -->
<div class="footer-table">
<el-table
v-loading="tableLoading"
:data="tableState.tableData"
border
stripe
style="width: 100%; height: 100%"
>
<el-table-column prop="name" label="姓名" align="center" width="180" />
<el-table-column prop="enterprise.name" label="企业名称" align="center" width="100" />
<el-table-column prop="dept" label="部门" align="center" width="180" />
<el-table-column prop="job" label="职位" align="center" width="180" />
<el-table-column prop="nickname" label="昵称" align="center" width="180" />
<el-table-column prop="username" label="用户名" align="center" width="180" />
<el-table-column prop="wharf.name" label="码头" align="center" width="180" />
@ -71,7 +52,7 @@
>
查看
</el-button>
<!-- <el-button
<el-button
type="primary"
size="small"
icon="Edit"
@ -79,7 +60,7 @@
@click="onClickOpenDialog('编辑', scope.row)"
>
编辑
</el-button> -->
</el-button>
<el-button
type="danger"
size="small"
@ -110,13 +91,12 @@
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
import { onMounted, reactive, ref } from 'vue';
import {
postEmployeeAddAPI,
postEmployeeDeleteAPI,
postEmployeePageAPI,
postEmployeeSaveAPI,
} from '@/api/Enterprise/employee';
import { postWharfListAPI } from '@/api/Wharf';
import Pagination from '@/components/Pagination/Pagination.vue';
import RemoteSelect from '@/components/RemoteSelect/index.vue';
import { useTable } from '@/hooks/useTable';
import { EmployeeType } from '@/types/Enterprise';
import EmployeeDialog from './EmployeeDialog.vue';
@ -133,10 +113,7 @@ onMounted(async () => {
//
const searchTableForm = reactive({
// enterpriseId: route.query.enterpriseId as string,
name: '',
username: '',
wharfId: '',
});
//
@ -171,8 +148,8 @@ const onClickOpenDialog = (title: string, row: Partial<EmployeeType> = {}) => {
const params = {
title,
isView: title === '查看',
row: { ...row },
api: postEmployeeSaveAPI,
row: title === '新增' ? { password: '123456', ...row } : { ...row },
api: title === '新增' ? postEmployeeAddAPI : postEmployeeSaveAPI,
getTableList,
};
employeeDialogRef.value?.isShowDialog(params);
@ -220,11 +197,8 @@ const handleCurrentChange = async (val: number) => {
width: 100%;
.header {
display: flex;
padding: 16px 16px 0;
margin-bottom: 16px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
justify-content: space-between;
padding: 0 16px;
}
.footer {
position: relative;
@ -234,15 +208,6 @@ const handleCurrentChange = async (val: number) => {
flex-direction: column;
padding: 16px;
overflow: hidden;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
.footer-util {
display: flex;
flex-shrink: 0;
justify-content: flex-end;
margin-bottom: 15px;
}
.footer-table {
position: relative;
flex: 1;

View File

@ -0,0 +1,93 @@
<template>
<div class="notify">
<div class="list">
<div class="list-wrap">
<div class="title">{{ wharfInfo?.name }}</div>
<div class="list-item">地理位置{{ wharfInfo?.address }}</div>
<div class="list-item">通过能力{{ wharfInfo?.transitCapacity }}</div>
<div class="list-item">泊位数量{{ wharfInfo?.berthageNum }}</div>
<div class="list-item">堆存能力{{ wharfInfo?.storageCapacity }}</div>
<div class="list-item">接卸能力{{ wharfInfo?.handlingCapacity }}</div>
<div class="list-item">简介{{ wharfInfo?.intro }}</div>
</div>
<div class="content">
<img class="wharfInfo" :src="wharfPhoto" />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { getWharfPhotoAPI, postWharfInfoAPI } from '@/api/Wharf';
import { LoadInfoType } from '@/types/boatInfo';
//
const wharfInfo = ref<LoadInfoType>();
const wharfPhoto = ref('');
//
const getWharfInfo = async () => {
const { data } = await postWharfInfoAPI();
wharfInfo.value = data;
//
const { data: photo } = await getWharfPhotoAPI(data.portId);
const arrayBufferToBase64 = (buffer: ArrayBuffer): string => {
let binary = '';
const bytes = new Uint8Array(buffer);
const len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
};
const base64String = arrayBufferToBase64(photo);
wharfPhoto.value = `data:image/png;base64,${base64String}`;
};
onMounted(async () => {
await getWharfInfo();
});
</script>
<style lang="scss" scoped>
.notify {
width: 100%;
height: 100%;
overflow-x: hidden;
color: #fff;
.list {
box-sizing: border-box;
display: flex;
justify-content: space-between;
width: 100%;
height: 100%;
.list-wrap {
width: 50%;
height: 100%;
.title {
font-size: 26px;
font-weight: 700;
line-height: 40px;
}
.list-item {
font-size: 20px;
line-height: 40px;
}
}
.content {
box-sizing: border-box;
flex: 1;
height: 100%;
margin-left: 20px;
img {
width: 100%;
height: calc(100% - 40px);
}
}
}
}
</style>

View File

@ -0,0 +1,93 @@
<template>
<div class="table">
<div class="header">
<div class="title">用户设置</div>
<div class="api-btn">
<div
class="btn"
:class="{ 'btn-active': currentNav === item }"
v-for="item in helpDocsList"
:key="item"
@click="onClickChangeNav(item)"
>
{{ item }}
</div>
</div>
</div>
<div class="content">
<Wharf v-if="currentNav === ''" />
<EmployeeTable v-if="currentNav === ''" />
<employeeRole v-if="currentNav === ''" />
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import employeeRole from './components/EmployeeRole.vue';
import EmployeeTable from './components/EmployeeTable.vue';
import Wharf from './components/Wharf.vue';
//
const helpDocsList = ref(['码头信息', '用户管理', '权限管理']);
const currentNav = ref('码头信息');
const onClickChangeNav = (item: string) => {
if (item === currentNav.value) return;
currentNav.value = item;
};
</script>
<style lang="scss" scoped>
.table {
position: relative;
display: flex;
flex: 1;
flex-direction: column;
width: 100%;
height: 100%;
.header {
box-sizing: border-box;
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: space-between;
padding: 16px 16px 0;
margin-bottom: 15px;
color: #fff;
.title {
font-size: 28px;
font-weight: 700;
}
.api-btn {
display: flex;
flex: 1;
align-items: center;
justify-content: flex-end;
.btn {
display: flex;
align-items: center;
justify-content: center;
width: 150px;
height: 50px;
margin-left: 10px;
font-size: 18px;
color: #d7d7d7;
background-color: rgb(51 51 51 / 100%);
border-radius: 75px;
box-shadow: 5px 5px 5px rgb(0 0 0 / 34.9%);
}
.btn-active {
color: #000;
background-color: #f2f2f2;
box-shadow: none;
}
}
}
.content {
box-sizing: border-box;
flex: 1;
padding: 16px;
}
}
</style>

View File

@ -1,18 +0,0 @@
<template>
<div class="manage">
<PublishTable />
</div>
</template>
<script lang="ts" setup>
import PublishTable from './components/PublishTable.vue';
</script>
<style lang="scss" scoped>
.manage {
display: flex;
flex-direction: row;
width: 100%;
height: 100%;
}
</style>

View File

@ -1,18 +0,0 @@
<template>
<div class="manage">
<ReceiveTable />
</div>
</template>
<script lang="ts" setup>
import ReceiveTable from './components/ReceiveTable.vue';
</script>
<style lang="scss" scoped>
.manage {
display: flex;
flex-direction: row;
width: 100%;
height: 100%;
}
</style>

View File

@ -83,10 +83,10 @@
</el-form-item>
<el-form-item label="订阅状态" prop="subStatus">
<el-select v-model="dialogProps.row!.subStatus" placeholder="请选择订阅状态">
<el-option label="待接受" value="待接受" />
<el-option label="已关闭" value="已关闭" />
<el-option label="订阅中" value="订阅中" />
<el-option label="已取消" value="已取消" />
<el-option label="待接受" value="NO_ACCEPTED" />
<el-option label="已关闭" value="CLOSED" />
<el-option label="订阅中" value="SUBSCRIBE" />
<el-option label="已取消" value="CANCELLED" />
</el-select>
</el-form-item>
</el-form>

View File

@ -1,6 +1,6 @@
<template>
<div class="table">
<div class="header">
<!-- <div class="header">
<el-form :inline="true" :model="searchTableForm" ref="tableFormRef">
<el-form-item>
<RemoteSelect
@ -31,7 +31,7 @@
<el-button icon="Refresh" @click="onClickResetForm(tableFormRef)"></el-button>
</el-form-item>
</el-form>
</div>
</div> -->
<div class="footer">
<div class="footer-util">
<el-button type="primary" icon="Plus" @click="onClickOpenDialog('新增')">
@ -44,7 +44,6 @@
v-loading="tableLoading"
:data="tableState.tableData"
border
stripe
style="width: 100%; height: 100%"
>
<el-table-column prop="enterprise.name" label="订阅企业" align="center" width="150" />
@ -127,10 +126,10 @@
prop="status"
>
<el-select v-model="statusForm.status" placeholder="请选择订阅状态">
<el-option label="待接受" value="待接受" />
<el-option label="已关闭" value="已关闭" />
<el-option label="订阅中" value="订阅中" />
<el-option label="已取消" value="已取消" />
<el-option label="待接受" value="NO_ACCEPTED" />
<el-option label="已关闭" value="CLOSED" />
<el-option label="订阅中" value="SUBSCRIBE" />
<el-option label="已取消" value="CANCELLED" />
</el-select>
</el-form-item>
</el-form>
@ -148,17 +147,13 @@
import dayjs from 'dayjs';
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
import { onMounted, reactive, ref } from 'vue';
import { postEnterpriseListAPI } from '@/api/Enterprise/company';
import { postPortListAPI } from '@/api/Port';
import {
editStatusAPI,
publishPageAPI,
subscriptionDeleteAPI,
subscriptionSaveAPI,
} from '@/api/Subscription';
import { postWharfListAPI } from '@/api/Wharf';
import Pagination from '@/components/Pagination/Pagination.vue';
import RemoteSelect from '@/components/RemoteSelect/index.vue';
import { useTable } from '@/hooks/useTable';
import { PageRowsResult } from '@/types';
import { SubscriptionType } from '@/types/subscription';
@ -175,48 +170,25 @@ const handleTableData = (data: PageRowsResult<SubscriptionType>) => {
};
//
const { getTableList, tableState, searchTable, resetTable, tableChangeCurrent, tableChangeSize } =
useTable({
const { getTableList, tableState, tableChangeCurrent, tableChangeSize } = useTable({
api: publishPageAPI,
dataCallBack: handleTableData,
});
});
onMounted(async () => {
await getTableList();
});
//
const searchTableForm = reactive({
enterpriseId: '',
portId: '',
wharfId: '',
});
// const searchTableForm = reactive({
// enterpriseId: '',
// portId: '',
// wharfId: '',
// });
//
const tableLoading = ref(false);
//
const tableFormRef = ref<FormInstance>();
//
const onClickSearch = async () => {
tableLoading.value = true;
//
tableState.value.searchParam = searchTableForm;
//
await searchTable();
tableLoading.value = false;
};
//
const onClickResetForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await resetTable();
formEl.resetFields();
};
const publishDialogRef = ref<InstanceType<typeof PublishDialog> | null>(null);
const onClickOpenDialog = (title: string, row: Partial<SubscriptionType> = {}) => {
const params = {
@ -302,9 +274,7 @@ const handleCurrentChange = async (val: number) => {
display: flex;
padding: 16px 16px 0;
margin-bottom: 16px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
}
.footer {
position: relative;
@ -314,9 +284,7 @@ const handleCurrentChange = async (val: number) => {
flex-direction: column;
padding: 16px;
overflow: hidden;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
.footer-util {
display: flex;
flex-shrink: 0;

View File

@ -1,6 +1,6 @@
<template>
<div class="table">
<div class="header">
<!-- <div class="header">
<el-form :inline="true" :model="searchTableForm" ref="tableFormRef">
<el-form-item>
<RemoteSelect
@ -31,7 +31,7 @@
<el-button icon="Refresh" @click="onClickResetForm(tableFormRef)"></el-button>
</el-form-item>
</el-form>
</div>
</div> -->
<div class="footer">
<!-- 表格 -->
<div class="footer-table">
@ -75,14 +75,9 @@
<script lang="ts" setup>
import dayjs from 'dayjs';
import { FormInstance } from 'element-plus';
import { onMounted, reactive, ref } from 'vue';
import { postEnterpriseListAPI } from '@/api/Enterprise/company';
import { postPortListAPI } from '@/api/Port';
import { onMounted, ref } from 'vue';
import { receivePageAPI } from '@/api/Subscription';
import { postWharfListAPI } from '@/api/Wharf';
import Pagination from '@/components/Pagination/Pagination.vue';
import RemoteSelect from '@/components/RemoteSelect/index.vue';
import { useTable } from '@/hooks/useTable';
import { PageRowsResult } from '@/types';
import { SubscriptionType } from '@/types/subscription';
@ -98,48 +93,20 @@ const handleTableData = (data: PageRowsResult<SubscriptionType>) => {
};
//
const { getTableList, tableState, searchTable, resetTable, tableChangeCurrent, tableChangeSize } =
useTable({
const { getTableList, tableState, tableChangeCurrent, tableChangeSize } = useTable({
api: receivePageAPI,
dataCallBack: handleTableData,
});
});
onMounted(async () => {
await getTableList();
});
//
const searchTableForm = reactive({
enterpriseId: '',
portId: '',
wharfId: '',
});
//
const tableLoading = ref(false);
//
const tableFormRef = ref<FormInstance>();
//
const onClickSearch = async () => {
tableLoading.value = true;
//
tableState.value.searchParam = searchTableForm;
//
await searchTable();
tableLoading.value = false;
};
//
const onClickResetForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await resetTable();
formEl.resetFields();
};
//
const handleSizeChange = async (val: number) => {
await tableChangeSize(val);
@ -162,9 +129,7 @@ const handleCurrentChange = async (val: number) => {
display: flex;
padding: 16px 16px 0;
margin-bottom: 16px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
}
.footer {
position: relative;
@ -174,9 +139,7 @@ const handleCurrentChange = async (val: number) => {
flex-direction: column;
padding: 16px;
overflow: hidden;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
.footer-util {
display: flex;
flex-shrink: 0;

View File

@ -0,0 +1,91 @@
<template>
<div class="table">
<div class="header">
<div class="title">{{ currentSubscribeNav }}清单</div>
<div class="api-btn">
<div
class="btn"
:class="{ 'btn-active': currentSubscribeNav === item }"
v-for="item in subscribeNavList"
:key="item"
@click="onClickChangeSubscribeType(item)"
>
{{ item }}
</div>
</div>
</div>
<div class="content">
<PublishTable v-if="currentSubscribeNav === ''" />
<ReceiveTable v-if="currentSubscribeNav === ''" />
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import PublishTable from './components/PublishTable.vue';
import ReceiveTable from './components/ReceiveTable.vue';
//
const subscribeNavList = ref(['我的订阅', '收到订阅']);
const currentSubscribeNav = ref('我的订阅');
const onClickChangeSubscribeType = async (item: string) => {
if (item === currentSubscribeNav.value) return;
currentSubscribeNav.value = item;
};
</script>
<style lang="scss" scoped>
.table {
position: relative;
display: flex;
flex: 1;
flex-direction: column;
width: 100%;
height: 100%;
.header {
box-sizing: border-box;
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: space-between;
padding: 16px 16px 0;
margin-bottom: 15px;
color: #fff;
.title {
font-size: 28px;
font-weight: 700;
}
.api-btn {
display: flex;
flex: 1;
align-items: center;
justify-content: flex-end;
.btn {
display: flex;
align-items: center;
justify-content: center;
width: 150px;
height: 50px;
margin-left: 10px;
font-size: 18px;
color: #d7d7d7;
background-color: rgb(51 51 51 / 100%);
border-radius: 75px;
box-shadow: 5px 5px 5px rgb(0 0 0 / 34.9%);
}
.btn-active {
color: #000;
background-color: #f2f2f2;
box-shadow: none;
}
}
}
.content {
box-sizing: border-box;
flex: 1;
padding: 16px;
}
}
</style>

View File

@ -1,100 +0,0 @@
<template>
<el-drawer
v-model="drawerVisible"
:title="`${drawerProps.title}角色`"
size="450px"
:destroy-on-close="true"
>
<el-form
ref="roleFormRef"
label-width="100px"
label-suffix=" :"
:disabled="drawerProps.isView"
:model="drawerProps.row"
:hide-required-asterisk="drawerProps.isView"
:rules="roleDrawerRules"
>
<el-form-item label="角色名称" prop="name" required>
<el-input v-model="drawerProps.row!.name" placeholder="请输入角色名称" />
</el-form-item>
<el-form-item label="角色标识" prop="code" required>
<el-input v-model="drawerProps.row!.code" placeholder="请输入角色标识,标识唯一" />
</el-form-item>
<el-form-item label="角色类型" prop="type" required>
<el-select v-model="drawerProps.row!.type" placeholder="请选择角色类型" style="width: 100%">
<el-option label="系统管理员" value="系统管理员" />
<el-option label="普通角色" value="普通角色" />
</el-select>
</el-form-item>
<el-form-item label="角色描述">
<el-input v-model="drawerProps.row!.remark" type="textarea" placeholder="请输入角色描述" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisible = false">取消</el-button>
<el-button type="primary" @click="onClickConfirm(roleFormRef)"></el-button>
</span>
</template>
</el-drawer>
</template>
<script lang="ts" setup>
import type { FormInstance, FormRules } from 'element-plus';
import { ElMessage } from 'element-plus';
import { reactive, ref } from 'vue';
import { roleType } from '@/types/role';
interface DrawerPropsType {
title: string;
isView: boolean;
row: Partial<roleType>;
api?: (params: any) => Promise<any>;
getTableList?: () => void;
}
// drawer
const drawerVisible = ref(false);
//
const drawerProps = ref<DrawerPropsType>({
isView: false,
title: '',
row: {},
});
const isShowDrawer = async (item: DrawerPropsType) => {
drawerProps.value = item;
drawerVisible.value = true;
};
//
defineExpose({ isShowDrawer });
//
const roleFormRef = ref<FormInstance>();
//
const roleDrawerRules = reactive<FormRules>({
name: [{ required: true, message: '请输入角色名称', trigger: 'blur' }],
code: [{ required: true, message: '请输入角色标识', trigger: 'blur' }],
});
// /
const onClickConfirm = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
try {
//
await drawerProps.value.api!(drawerProps.value.row);
ElMessage({ message: `${drawerProps.value.title}成功`, type: 'success' });
drawerProps.value.getTableList!();
} finally {
drawerVisible.value = false;
}
});
};
</script>
<style lang="scss" scoped></style>

View File

@ -1,88 +0,0 @@
<template>
<div class="roleMenuTree">
<div class="title">
<span>角色名称</span>
<el-input v-model="row!" style="flex: 1" disabled />
</div>
<el-scrollbar max-height="100%">
<el-tree
ref="roleMenuTreeRef"
:data="menuTreeData"
:props="defaultMenuProps"
:default-expanded-keys="menuData"
show-checkbox
highlight-current
node-key="url"
@check-change="onCheckTreeChange"
/>
</el-scrollbar>
</div>
</template>
<script lang="ts" setup>
import { ElTree } from 'element-plus';
import { ref } from 'vue';
import { menuType } from '@/types/menu';
/**
* 父组件传递值类型
*/
interface MenuTreePropsType {
row: string;
menuTreeData: menuType[];
menuData: string[];
}
//
withDefaults(defineProps<MenuTreePropsType>(), {
row: '',
menuTreeData: () => [],
menuData: () => [],
});
//
const defaultMenuProps = {
children: 'children',
label: 'name',
};
//
const roleMenuTreeRef = ref<InstanceType<typeof ElTree>>();
// -
// watch(
// () => props.menuData,
// () => {
// // DOM
// // nextTick(() => {
// // //
// // roleMenuTreeRef.value?.setCheckedKeys(props.menuData, true);
// // });
// },
// { immediate: true }
// );
//
const emit = defineEmits(['updateMenuData']);
const onCheckTreeChange = () => {
emit('updateMenuData', roleMenuTreeRef.value?.getCheckedKeys());
};
</script>
<style lang="scss" scoped>
.roleMenuTree {
width: 100%;
height: 100%;
.title {
display: flex;
gap: 16px;
align-items: center;
margin-bottom: 16px;
}
.el-scrollbar {
border: var(--zb-border-light);
border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
}
}
</style>

View File

@ -1,319 +0,0 @@
<template>
<div class="role-table">
<div class="header">
<el-form :inline="true" :model="roleTableForm" ref="roleTableFormRef">
<el-form-item label="角色名" prop="name">
<el-input v-model="roleTableForm.name" placeholder="请输入角色名称" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="onClickSearch"></el-button>
<el-button icon="Refresh" @click="onClickResetForm(roleTableFormRef)"></el-button>
</el-form-item>
</el-form>
</div>
<div class="footer">
<div class="footer-util">
<el-button type="primary" icon="Plus" @click="onClickOpenDrawer('新增')">
新增角色
</el-button>
</div>
<!-- 表格 -->
<div class="footer-table">
<el-table
v-loading="tableLoading"
:data="tableState.tableData"
border
stripe
style="width: 100%; height: 100%"
>
<el-table-column prop="name" label="角色名称" align="center" width="100" />
<el-table-column
prop="type"
:show-overflow-tooltip="true"
width="180"
label="角色类型"
align="center"
/>
<el-table-column prop="code" label="角色标识" align="center" width="180" />
<el-table-column prop="dstatus" label="状态" align="center" width="100" />
<el-table-column prop="remark" label="备注" align="center" width="180" />
<el-table-column prop="createDate" label="创建时间" align="center" width="180" />
<el-table-column prop="operator" label="操作" width="200px" align="center" fixed="right">
<template #default="scope">
<el-button
type="primary"
size="small"
icon="Setting"
link
@click="onClickOpenMenuDrawer(scope.row)"
>
设置权限
</el-button>
<el-button
type="primary"
size="small"
icon="Edit"
link
@click="onClickOpenDrawer('编辑', scope.row)"
>
编辑
</el-button>
<el-button
type="danger"
size="small"
icon="Delete"
link
@click="onClickDel(scope.row)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 分页 -->
<div class="footer-pagination">
<Pagination
:pageAble="tableState.pageAble"
:handle-size-change="handleSizeChange"
:handle-current-change="handleCurrentChange"
/>
</div>
</div>
<RoleDrawer ref="roleDrawerRef" />
<el-drawer v-model="menuDrawer" :with-header="false" size="40%" title="角色配置">
<el-tabs type="border-card">
<el-tab-pane v-for="item in menuList" :key="item.name" :label="item.name">
<RoleMenuTree
:row="roleName"
:menu-tree-data="item.list"
:menuData="menuData"
@update-menu-data="onUpdateMenuData"
/>
</el-tab-pane>
</el-tabs>
<template #footer>
<div style="flex: auto">
<el-button @click="menuDrawer = false">取消</el-button>
<el-button type="primary" @click="onClickConfirmRoleMenu"></el-button>
</div>
</template>
</el-drawer>
</div>
</template>
<script lang="ts" setup>
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
import { onMounted, reactive, ref } from 'vue';
import { getAppListAPI, getMenuTreeAPI } from '@/api/System/menu';
import {
getRoleDeleteAPI,
getRoleMenuListAPI,
postRolePageAPI,
postRolePremSaveAPI,
postRoleSaveAPI,
} from '@/api/System/role';
import Pagination from '@/components/Pagination/Pagination.vue';
import { useTable } from '@/hooks/useTable';
import { menuType } from '@/types/menu';
import { rolePremType, roleType } from '@/types/role';
import RoleDrawer from './RoleDrawer.vue';
import RoleMenuTree from './RoleMenuTree.vue';
//
const { getTableList, tableState, searchTable, resetTable, tableChangeCurrent, tableChangeSize } =
useTable({
api: postRolePageAPI,
});
//
const menuList = ref<
{
name: string;
list: menuType[];
}[]
>([]);
//
const getMenuList = async () => {
try {
const { data } = await getAppListAPI();
if (data.length > 0) {
for (const item of data) {
const { data: list } = await getMenuTreeAPI({ appName: item });
menuList.value.push({ name: item, list });
}
}
} catch (error) {
console.log(error);
}
};
onMounted(async () => {
await getTableList();
await getMenuList();
});
//
const roleTableForm = reactive({
name: '',
});
//
const roleTableFormRef = ref<FormInstance>();
//
const tableLoading = ref(false);
//
const onClickSearch = async () => {
tableLoading.value = true;
//
tableState.value.searchParam = roleTableForm;
//
await searchTable();
tableLoading.value = false;
};
//
const onClickResetForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await resetTable();
formEl.resetFields();
};
// /
const roleDrawerRef = ref<InstanceType<typeof RoleDrawer> | null>(null);
const onClickOpenDrawer = (title: string, row: Partial<roleType> = {}) => {
const params = {
title,
isView: title === '查看',
row: { ...row },
api: postRoleSaveAPI,
getTableList: getTableList,
};
roleDrawerRef.value?.isShowDrawer(params);
};
//
const menuDrawer = ref(false);
//
const roleName = ref('');
const activeRow = ref<rolePremType>({
roleId: 0,
perms: [],
});
//
const menuData = ref<string[]>([]);
//
const getUserMenuList = async (roleId: number) => {
const { data } = await getRoleMenuListAPI({ roleId });
menuData.value = data;
};
//
const onClickOpenMenuDrawer = async (row: roleType) => {
roleName.value = row.name;
row.id && (await getUserMenuList(row.id));
menuDrawer.value = true;
};
const onUpdateMenuData = (data: string[]) => {
activeRow.value.perms = data;
};
//
const onClickConfirmRoleMenu = async () => {
await postRolePremSaveAPI(activeRow.value);
menuDrawer.value = false;
ElMessage({
message: '设置权限成功',
type: 'success',
});
};
//
const onClickDel = (row: roleType) => {
ElMessageBox.confirm(`确定要删除角色 ${row.name} 吗?`, '温馨提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
draggable: true,
})
.then(async () => {
await getRoleDeleteAPI([row.id]);
//
await getTableList();
ElMessage({
message: '删除角色成功',
type: 'success',
});
})
.catch(() => {
console.log('用户点击了取消');
});
};
//
const handleSizeChange = async (val: number) => {
await tableChangeSize(val);
};
//
const handleCurrentChange = async (val: number) => {
await tableChangeCurrent(val);
};
</script>
<style lang="scss" scoped>
.role-table {
position: relative;
display: flex;
flex: 1;
flex-direction: column;
width: 100%;
.header {
display: flex;
padding: 16px 16px 0;
margin-bottom: 16px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
}
.footer {
position: relative;
box-sizing: border-box;
display: flex;
flex: 1;
flex-direction: column;
padding: 16px;
overflow: hidden;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
.footer-util {
display: flex;
flex-shrink: 0;
justify-content: flex-end;
margin-bottom: 15px;
}
.footer-table {
position: relative;
flex: 1;
}
.footer-pagination {
box-sizing: border-box;
display: flex;
flex-shrink: 0;
justify-content: flex-end;
width: 100%;
padding-top: 20px;
}
}
}
</style>

View File

@ -1,18 +0,0 @@
<template>
<div class="roleManage">
<RoleTable />
</div>
</template>
<script lang="ts" setup>
import RoleTable from './components/RoleTable.vue';
</script>
<style lang="scss" scoped>
.roleManage {
display: flex;
flex-direction: row;
width: 100%;
height: 100%;
}
</style>

View File

@ -1,144 +0,0 @@
<template>
<el-drawer
v-model="drawerVisible"
:destroy-on-close="true"
size="450px"
:title="`${drawerProps.title}用户`"
>
<el-form
ref="ruleFormRef"
label-width="100px"
label-suffix=" :"
:rules="userDrawerRules"
:disabled="drawerProps.isView"
:model="drawerProps.row"
:hide-required-asterisk="drawerProps.isView"
>
<!-- <el-form-item label="用户头像" prop="avatar">
<UploadImg
v-model:image-url="drawerProps.row!.avatar"
width="135px"
height="135px"
:file-size="3"
>
<template #empty>
<el-icon><Avatar /></el-icon>
<span>请上传头像</span>
</template>
<template #tip> 头像大小不能超过 3M </template>
</UploadImg>
</el-form-item> -->
<el-form-item label="用户姓名" prop="name">
<el-input v-model="drawerProps.row!.name" placeholder="请填写用户姓名" clearable></el-input>
</el-form-item>
<el-form-item label="账号" prop="username">
<el-input v-model="drawerProps.row!.username" placeholder="请填写账号" clearable></el-input>
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="drawerProps.row!.phone" placeholder="请填写手机号" clearable></el-input>
</el-form-item>
<el-form-item label="邮箱">
<el-input v-model="drawerProps.row!.email" placeholder="请填写邮箱" clearable></el-input>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="drawerVisible = false">取消</el-button>
<el-button v-show="!drawerProps.isView" type="primary" @click="onClickConfirm(ruleFormRef)"
>确定</el-button
>
</template>
</el-drawer>
</template>
<script lang="ts" setup>
import type { FormInstance, FormRules } from 'element-plus';
import { ElMessage } from 'element-plus';
import { reactive, ref } from 'vue';
import { userType } from '@/types/user';
import { validPhone } from '@/utils/validate';
interface DrawerPropsType {
title: string;
isView: boolean;
row: Partial<userType>;
api?: (params: any) => Promise<any>;
getTableList?: () => void;
}
// drawer
const drawerVisible = ref(false);
//
const drawerProps = ref<DrawerPropsType>({
isView: false,
title: '',
row: {},
});
// drawer/
const isShowDrawer = (item: DrawerPropsType) => {
drawerProps.value = item;
drawerVisible.value = true;
};
//
defineExpose({ isShowDrawer });
/**
* 手机号校验规则
* @param {any} rule 校验规则
* @param {string} value 手机号
* @param {any} callback 回调函数
* @return 是否通过校验
*/
const checkPhone = (rule: any, value: string, callback: any) => {
if (!validPhone(value)) {
//
callback(new Error('请输入正确的手机号码'));
} else {
callback();
}
};
//
const userDrawerRules = reactive<FormRules>({
username: [
{ required: true, message: '请输入账号', trigger: 'blur' },
{ min: 4, message: '账号长度不能小于4位', trigger: 'blur' },
],
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ validator: checkPhone, trigger: 'blur' },
],
});
//
const ruleFormRef = ref<FormInstance>();
// /
const onClickConfirm = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
try {
// /
const row = { ...drawerProps.value.row };
if (row.password !== undefined) {
delete row.password;
}
await drawerProps.value.api!(row);
ElMessage({
message: `${drawerProps.value.title}成功`,
type: 'success',
});
drawerProps.value.getTableList!();
} finally {
drawerVisible.value = false;
}
});
};
</script>
<style lang="scss" scoped></style>

View File

@ -1,125 +0,0 @@
<template>
<el-dialog v-model="userRoleDialog" title="关联角色" width="50%" @close="onCloseDialog">
<el-form ref="userRoleFormRef" :model="userRoleForm" :rules="userRoleRules" label-width="100px">
<el-form-item label="用户名" required>
<el-input v-model="userRoleForm.username" disabled placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="用户角色" prop="roleIds" required>
<el-select
v-model="userRoleForm.roleIds"
multiple
collapse-tags
collapse-tags-tooltip
:max-collapse-tags="3"
placeholder="请选择角色"
style="width: 100%"
>
<el-option
v-for="item in roleList"
:key="item.id"
:label="item.text"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="userRoleDialog = false">取消</el-button>
<el-button type="primary" @click="onClickConfirm(userRoleFormRef)"></el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ElMessage, FormInstance, FormRules } from 'element-plus';
import { onMounted, reactive, ref } from 'vue';
import { getRoleListAPI } from '@/api/System/role';
import { getUserRoleListAPI, postUserRoleAPI } from '@/api/System/user';
import { dictionaryListType } from '@/types';
import { userType } from '@/types/user';
//
type userRoleFormType = {
username: string;
userId: number | string;
roleIds: number[];
};
//
const roleList = ref<dictionaryListType[]>([]);
//
const getRoleList = async () => {
const { data } = await getRoleListAPI({});
roleList.value = data;
};
onMounted(async () => {
await getRoleList();
});
// dialog
const userRoleDialog = ref(false);
//
const getUserRoleList = async (userId: string | number) => {
const { data } = await getUserRoleListAPI({ userId });
userRoleForm.roleIds = data.map((item) => item.id);
};
// dialog/
const isShowDialog = async (item: userType) => {
if (item) {
userRoleForm.userId = item.id;
userRoleForm.username = item.username;
item.id && (await getUserRoleList(item.id));
}
userRoleDialog.value = true;
};
//
defineExpose({ isShowDialog });
// /
const userRoleForm = reactive<userRoleFormType>({
username: '', //
userId: '',
roleIds: [], //
});
//
const userRoleFormRef = ref<FormInstance>();
// dialog
const onCloseDialog = () => {
//
userRoleFormRef.value?.resetFields();
};
//
const userRoleRules = reactive<FormRules>({
roleIds: [{ required: true, message: '请选择用户角色', trigger: 'change' }],
});
//
const onClickConfirm = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
try {
//
await postUserRoleAPI(userRoleForm);
ElMessage({
message: '设置用户角色成功',
type: 'success',
});
} finally {
userRoleDialog.value = false;
}
});
};
</script>
<style scoped></style>

View File

@ -1,245 +0,0 @@
<template>
<div class="user-table">
<div class="header">
<el-form :inline="true" :model="userTableForm" ref="userTableFormRef">
<el-form-item label="账号" prop="username">
<el-input v-model="userTableForm.username" placeholder="请输入账号" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="onClickSearch"></el-button>
<el-button icon="Refresh" @click="onClickResetForm(userTableFormRef)"></el-button>
</el-form-item>
</el-form>
</div>
<div class="footer">
<div class="footer-util">
<el-button type="primary" icon="Plus" @click="onClickOpenDrawer('新增')">
新增用户
</el-button>
</div>
<!-- 表格 -->
<div class="footer-table">
<el-table
v-loading="tableLoading"
:data="tableState.tableData"
border
stripe
style="width: 100%; height: 100%"
>
<el-table-column prop="username" label="账号" align="center" width="180" />
<el-table-column prop="name" label="姓名" align="center" />
<el-table-column prop="roleList" label="关联角色" align="center" width="120">
<template #default="scope">
<div style="display: flex; flex-wrap: wrap">
<el-tag
v-for="item in scope.row.roleList"
:key="item.id"
effect="dark"
disable-transitions
style="margin-right: 6px; margin-bottom: 6px"
>
{{ item.roleName }}
</el-tag>
</div>
</template>
</el-table-column>
<el-table-column prop="phone" label="手机号" align="center" width="120" />
<el-table-column prop="email" label="邮箱" align="center" width="120" />
<el-table-column prop="dstatus" label="用户状态" align="center" width="100" />
<el-table-column prop="createDate" label="创建时间" align="center" width="180" />
<el-table-column prop="operator" label="操作" width="200px" align="center" fixed="right">
<template #default="scope">
<el-button
type="primary"
size="small"
icon="User"
link
@click="onClickOpenUserRole(scope.row)"
>
角色
</el-button>
<el-button
type="primary"
size="small"
icon="Edit"
link
@click="onClickOpenDrawer('编辑', scope.row)"
>
编辑
</el-button>
<el-button
type="danger"
size="small"
icon="Delete"
link
@click="onClickDel(scope.row)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 分页 -->
<div class="footer-pagination">
<Pagination
:pageAble="tableState.pageAble"
:handle-size-change="handleSizeChange"
:handle-current-change="handleCurrentChange"
/>
</div>
</div>
<UserDrawer ref="userDrawerRef" />
<UserRoleDialog ref="userRoleDialogRef" />
</div>
</template>
<script lang="ts" setup>
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus';
import { onMounted, reactive, ref } from 'vue';
import { getUserDeleteAPI, postUserPageAPI, postUserSaveAPI } from '@/api/System/user';
import Pagination from '@/components/Pagination/Pagination.vue';
import { useTable } from '@/hooks/useTable';
import { userType } from '@/types/user';
import UserDrawer from './UserDrawer.vue';
import UserRoleDialog from './UserRoleDialog.vue';
//
const { getTableList, tableState, searchTable, resetTable, tableChangeCurrent, tableChangeSize } =
useTable({
api: postUserPageAPI,
});
onMounted(async () => {
await getTableList();
});
//
const userTableForm = reactive({
username: '',
});
//
const tableLoading = ref(false);
//
const userTableFormRef = ref<FormInstance>();
//
const onClickSearch = async () => {
tableLoading.value = true;
//
tableState.value.searchParam = userTableForm;
//
await searchTable();
tableLoading.value = false;
};
//
const onClickResetForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await resetTable();
formEl.resetFields();
};
//
const userRoleDialogRef = ref<InstanceType<typeof UserRoleDialog> | null>(null);
const onClickOpenUserRole = (row: userType) => {
userRoleDialogRef.value?.isShowDialog(row);
};
// // drawer
const userDrawerRef = ref<InstanceType<typeof UserDrawer> | null>(null);
const onClickOpenDrawer = (title: string, row: Partial<userType> = {}) => {
const params = {
title,
isView: title === '查看',
row: { ...row },
api: postUserSaveAPI,
getTableList: getTableList,
};
userDrawerRef.value?.isShowDrawer(params);
};
//
const onClickDel = (row: userType) => {
ElMessageBox.confirm(`你确定要删除用户 ${row.name} 吗?`, '温馨提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
draggable: true,
})
.then(async () => {
await getUserDeleteAPI([row.id]);
//
await getTableList();
ElMessage({
message: '删除用户成功',
type: 'success',
});
})
.catch(() => {
console.log('用户点击了取消');
});
};
//
const handleSizeChange = async (val: number) => {
await tableChangeSize(val);
};
//
const handleCurrentChange = async (val: number) => {
await tableChangeCurrent(val);
};
</script>
<style lang="scss" scoped>
.user-table {
position: relative;
display: flex;
flex: 1;
flex-direction: column;
width: 100%;
.header {
display: flex;
padding: 16px 16px 0;
margin-bottom: 16px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
}
.footer {
position: relative;
box-sizing: border-box;
display: flex;
flex: 1;
flex-direction: column;
padding: 16px;
overflow: hidden;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
.footer-util {
display: flex;
flex-shrink: 0;
justify-content: flex-end;
margin-bottom: 15px;
}
.footer-table {
position: relative;
flex: 1;
}
.footer-pagination {
box-sizing: border-box;
display: flex;
flex-shrink: 0;
justify-content: flex-end;
width: 100%;
padding-top: 20px;
}
}
}
</style>

View File

@ -1,18 +0,0 @@
<template>
<div class="userManage">
<UserTable />
</div>
</template>
<script lang="ts" setup>
import UserTable from './components/UserTable.vue';
</script>
<style lang="scss" scoped>
.userManage {
display: flex;
flex-direction: row;
width: 100%;
height: 100%;
}
</style>