feat(全局): 部分组件调整&问题修复
This commit is contained in:
parent
2cbeb77956
commit
58f1dda566
|
@ -37,7 +37,7 @@
|
|||
"dependencies": {
|
||||
"@7polo/kity": "2.0.8",
|
||||
"@7polo/kityminder-core": "1.4.53",
|
||||
"@arco-design/web-vue": "^2.53.3",
|
||||
"@arco-design/web-vue": "^2.54.3",
|
||||
"@arco-themes/vue-ms-theme-default": "^0.0.30",
|
||||
"@form-create/arco-design": "^3.1.23",
|
||||
"@halo-dev/richtext-editor": "0.0.0-alpha.33",
|
||||
|
|
|
@ -181,8 +181,8 @@ export const getReviewDetailModuleCount = (data: ReviewDetailCaseListQueryParams
|
|||
};
|
||||
|
||||
// 评审详情-已关联用例模块树
|
||||
export const getReviewDetailModuleTree = (projectId: string, reviewId: string) => {
|
||||
return MSR.get({ url: `${GetReviewDetailModuleTreeUrl}/${projectId}/${reviewId}` });
|
||||
export const getReviewDetailModuleTree = (reviewId: string) => {
|
||||
return MSR.get({ url: `${GetReviewDetailModuleTreeUrl}/${reviewId}` });
|
||||
};
|
||||
|
||||
// 评审详情-获取用例评审历史
|
||||
|
|
|
@ -47,7 +47,7 @@ export function login(data: LoginData) {
|
|||
}
|
||||
|
||||
export function isLogin() {
|
||||
return MSR.get<LoginRes>({ url: isLoginUrl }, { ignoreCancelToken: true });
|
||||
return MSR.get<LoginRes>({ url: isLoginUrl }, { ignoreCancelToken: true, errorMessageMode: 'none' });
|
||||
}
|
||||
|
||||
export function logout() {
|
||||
|
@ -143,18 +143,18 @@ export function updatePsw(data: UpdatePswParams) {
|
|||
}
|
||||
|
||||
// 个人信息-校验第三方平台账号信息
|
||||
export function validatePlatform(id: string, data: Record<string, any>) {
|
||||
return MSR.post({ url: `${ValidatePlatformUrl}/${id}`, data });
|
||||
export function validatePlatform(id: string, orgId: string, data: Record<string, any>) {
|
||||
return MSR.post({ url: `${ValidatePlatformUrl}/${id}/${orgId}`, data });
|
||||
}
|
||||
|
||||
// 个人信息-保存第三方平台账号信息
|
||||
export function savePlatform(data: UpdatePswParams) {
|
||||
export function savePlatform(data: Record<string, any>) {
|
||||
return MSR.post({ url: SavePlatformUrl, data });
|
||||
}
|
||||
|
||||
// 个人信息-获取第三方平台账号信息
|
||||
export function getPlatform() {
|
||||
return MSR.get({ url: GetPlatformUrl });
|
||||
export function getPlatform(orgId: string) {
|
||||
return MSR.get({ url: GetPlatformUrl, params: orgId });
|
||||
}
|
||||
|
||||
// 个人信息-获取第三方平台账号信息-插件信息
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
@font-face {
|
||||
font-family: iconfont; /* Project id 3462279 */
|
||||
src: url('iconfont.woff2?t=1705549750803') format('woff2'), url('iconfont.woff?t=1705549750803') format('woff'),
|
||||
url('iconfont.ttf?t=1705549750803') format('truetype'), url('iconfont.svg?t=1705549750803#iconfont') format('svg');
|
||||
src: url('iconfont.woff2?t=1706424798592') format('woff2'), url('iconfont.woff?t=1706424798592') format('woff'),
|
||||
url('iconfont.ttf?t=1706424798592') format('truetype'), url('iconfont.svg?t=1706424798592#iconfont') format('svg');
|
||||
}
|
||||
.iconfont {
|
||||
font-size: 16px;
|
||||
|
@ -10,6 +10,12 @@
|
|||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
.icon-icon_swich::before {
|
||||
content: '\e79c';
|
||||
}
|
||||
.icon-icon_split_turn-down_arrow::before {
|
||||
content: '\e79b';
|
||||
}
|
||||
.icon-icon_carriage_return2::before {
|
||||
content: '\e79a';
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -5,6 +5,20 @@
|
|||
"css_prefix_text": "icon-",
|
||||
"description": "DE、MS项目icon管理",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "39108518",
|
||||
"name": "icon_swich",
|
||||
"font_class": "icon_swich",
|
||||
"unicode": "e79c",
|
||||
"unicode_decimal": 59292
|
||||
},
|
||||
{
|
||||
"icon_id": "39084088",
|
||||
"name": "icon_split_turn-down_arrow",
|
||||
"font_class": "icon_split_turn-down_arrow",
|
||||
"unicode": "e79b",
|
||||
"unicode_decimal": 59291
|
||||
},
|
||||
{
|
||||
"icon_id": "38923289",
|
||||
"name": "icon_carriage_return",
|
||||
|
|
|
@ -14,6 +14,10 @@
|
|||
/>
|
||||
<missing-glyph />
|
||||
|
||||
<glyph glyph-name="icon_swich" unicode="" d="M670.165333 798.165333l256-256c1.536-1.493333 2.901333-3.114667 4.138667-4.778666l3.029333-4.693334 2.304-4.864 1.493334-4.48 1.28-6.314666L938.666667 512l-0.128-3.2-0.725334-5.376-1.28-4.736-1.877333-4.736-2.218667-4.181333-2.858666-4.096-3.413334-3.84a43.050667 43.050667 0 0 0-4.778666-4.138667l-4.693334-3.029333-4.864-2.304-4.48-1.493334-6.357333-1.28L896 469.333333H128a42.666667 42.666667 0 1 0 0 85.333334h664.96l-183.125333 183.168a42.666667 42.666667 0 1 0 60.330666 60.330666zM913.066667 341.333333a42.666667 42.666667 0 0 0 0-85.333333H248.064l183.168-183.168a42.666667 42.666667 0 0 0-60.373333-60.330667l-256 256a43.008 43.008 0 0 0-4.096 4.778667l-3.072 4.693333-2.261334 4.864-1.237333 3.498667-1.152 4.864-0.597333 5.12L102.357333 298.666667l0.128 3.2 0.554667 4.266666 0.512 2.517334 1.877333 5.845333 1.578667 3.541333 1.578667 2.858667 2.858666 4.096 3.413334 3.84 2.346666 2.133333 4.010667 3.114667 3.157333 1.92 6.101334 2.773333 3.2 1.024 3.925333 0.853334 3.584 0.512L145.066667 341.333333h768z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="icon_split_turn-down_arrow" unicode="" d="M832-86.528a43.434667 43.434667 0 0 0-6.357333 0.426667l-2.218667 0.426666a40.106667 40.106667 0 0 0-13.653333 5.376 44.16 44.16 0 0 0-3.157334 2.133334l-0.938666 0.725333a43.349333 43.349333 0 0 0-3.84 3.413333l-106.666667 106.666667a42.666667 42.666667 0 0 0 60.330667 60.330667l33.834666-33.834667v195.669333a128 128 0 0 1-120.490666 127.786667l-7.509334 0.213333h-341.333333v-323.626666l33.834667 33.792a42.666667 42.666667 0 0 0 56.32 3.541333l4.010666-3.541333a42.666667 42.666667 0 0 0 0-60.330667l-106.666666-106.666667a42.666667 42.666667 0 0 0-60.330667 0l-106.666667 106.666667a42.666667 42.666667 0 0 0 60.330667 60.330667l33.834667-33.834667V560.853333A149.418667 149.418667 0 0 0 277.333333 853.333333a149.333333 149.333333 0 0 0 42.666667-292.48v-92.714666h341.333333a213.333333 213.333333 0 0 0 213.333334-213.333334v-195.626666l33.834666 33.792a42.666667 42.666667 0 0 0 56.32 3.541333l4.010667-3.541333a42.666667 42.666667 0 0 0 0-60.330667l-106.666667-106.666667-3.328-2.901333-1.450666-1.237333a43.946667 43.946667 0 0 0-16.810667-7.509334l-2.261333-0.426666-2.773334-0.256h-0.085333l-0.512-0.042667 0.469333 0.042667-3.413333-0.170667zM277.333333 768a64 64 0 1 1 0-128 64 64 0 0 1 0 128z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="icon_carriage_return2" unicode="" d="M311.168 499.498667a42.666667 42.666667 0 1 0 60.330667-60.330667L273.706667 341.333333H789.333333a21.333333 21.333333 0 0 1 21.333334 21.333334V640a42.666667 42.666667 0 0 0 85.333333 0v-277.333333a106.666667 106.666667 0 0 0-106.666667-106.666667H273.706667l97.792-97.834667a42.666667 42.666667 0 0 0 3.541333-56.32l-3.541333-4.010666a42.666667 42.666667 0 0 0-60.330667 0l-170.666667 170.666666-0.938666 0.938667a42.922667 42.922667 0 0 0-2.474667 2.901333l3.413333-3.84A43.008 43.008 0 0 0 128 297.813333V299.648c0 0.938667 0.085333 1.877333 0.170667 2.816L128 298.666667a43.008 43.008 0 0 0 9.088 26.325333c1.066667 1.322667 2.176 2.645333 3.413333 3.84l170.666667 170.666667z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="icon_carriage_return1" unicode="" d="M896 725.333333a85.333333 85.333333 0 0 0 85.333333-85.333333v-512a85.333333 85.333333 0 0 0-85.333333-85.333333H336.810667a85.333333 85.333333 0 0 0-58.24 22.954666l-4.608 5.077334-222.378667 287.146666a42.666667 42.666667 0 0 0 0 52.266667l222.378667 287.189333 4.608 5.077334A85.333333 85.333333 0 0 0 336.810667 725.333333H896z m0-85.333333H337.493333l-198.229333-256 198.272-256H896V640z m-396.501333-119.168l76.501333-76.458667 76.501333 76.458667a42.666667 42.666667 0 0 0 60.330667-60.330667L636.373333 384l76.458667-76.501333a42.666667 42.666667 0 0 0-60.330667-60.330667L576 323.626667l-76.501333-76.458667a42.666667 42.666667 0 0 0-60.330667 60.330667L515.626667 384l-76.458667 76.501333a42.666667 42.666667 0 0 0 60.330667 60.330667z" horiz-adv-x="1024" />
|
||||
|
|
Before Width: | Height: | Size: 427 KiB After Width: | Height: | Size: 430 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -575,12 +575,12 @@
|
|||
background-color: var(--color-text-input-border);
|
||||
}
|
||||
}
|
||||
.ms-card-container .arco-scrollbar .arco-scrollbar-track-direction-vertical {
|
||||
right: -10px;
|
||||
}
|
||||
.ms-card-container .arco-scrollbar .arco-scrollbar-track-direction-horizontal {
|
||||
bottom: -10px;
|
||||
}
|
||||
// .ms-card-container .arco-scrollbar .arco-scrollbar-track-direction-vertical {
|
||||
// right: -10px;
|
||||
// }
|
||||
// .ms-card-container .arco-scrollbar .arco-scrollbar-track-direction-horizontal {
|
||||
// bottom: -10px;
|
||||
// }
|
||||
.ms-base-table .arco-scrollbar .arco-scrollbar-track-direction-vertical {
|
||||
right: 0;
|
||||
}
|
||||
|
|
|
@ -215,10 +215,11 @@
|
|||
|
||||
// TODO: 临时解决 arco-design 的 cascader 组件绑定值只能是 path-mode 的问题,如果实际值也包含了 ‘-’,则不要取这个值,而是取绑定的 v-model 的值
|
||||
function getInputLabel(data: CascaderOption) {
|
||||
const isTagCount = data[props.labelKey].includes('+');
|
||||
if (!props.pathMode) {
|
||||
return t(data[props.labelKey].split('-').pop());
|
||||
return isTagCount ? data[props.labelKey] : t(data[props.labelKey].split('-').pop());
|
||||
}
|
||||
return t(data[props.labelKey]);
|
||||
return isTagCount ? data[props.labelKey] : t(data[props.labelKey]);
|
||||
}
|
||||
|
||||
function clearValues() {
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
<div
|
||||
class="mr-[4px] h-[8px] w-[8px] rounded-full"
|
||||
:style="{
|
||||
backgroundColor: caseLevelMap[props.caseLevel].bgColor,
|
||||
border: `1px solid ${caseLevelMap[props.caseLevel].borderColor}`,
|
||||
backgroundColor: caseLevel.bgColor,
|
||||
border: `1px solid ${caseLevel.borderColor}`,
|
||||
}"
|
||||
></div>
|
||||
{{ caseLevelMap[props.caseLevel].label }}
|
||||
{{ caseLevel.label }}
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -37,7 +37,9 @@
|
|||
bgColor: 'var(--color-text-n8)',
|
||||
borderColor: 'var(--color-text-brand)',
|
||||
},
|
||||
} as const;
|
||||
};
|
||||
|
||||
const caseLevel = computed(() => caseLevelMap[props.caseLevel] || {});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</a-button>
|
||||
</div>
|
||||
<div class="mb-[16px] flex items-center">
|
||||
<MsAvatar :avatar="userStore.avatar || 'default'" class="mb-[4px]" />
|
||||
<MsAvatar :avatar="userStore.avatar || 'default'" :size="58" class="mb-[4px]" />
|
||||
<a-button
|
||||
type="outline"
|
||||
class="arco-btn-outline--secondary ml-[8px] p-[2px_8px]"
|
||||
|
@ -304,4 +304,7 @@
|
|||
|
||||
bottom: 22px;
|
||||
}
|
||||
:deep(.ms-description-item-value) {
|
||||
-webkit-line-clamp: unset !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
<template>
|
||||
<div class="flex h-full flex-col overflow-hidden">
|
||||
<a-spin :loading="loading" class="flex h-full flex-col overflow-hidden">
|
||||
<div class="mb-[16px] flex items-center justify-between">
|
||||
<div class="font-medium text-[var(--color-text-1)]">{{ t('ms.personal.tripartite') }}</div>
|
||||
<MsSelect
|
||||
v-model:model-value="currentOrg"
|
||||
:options="orgOptions"
|
||||
:loading="orgLoading"
|
||||
class="w-[300px]"
|
||||
@change="handleOrgChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="platform-card-container">
|
||||
<div v-for="config of dynamicForm" :key="config.key" class="platform-card">
|
||||
|
@ -30,7 +37,7 @@
|
|||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -38,10 +45,14 @@
|
|||
|
||||
import MsFormCreate from '@/components/pure/ms-form-create/ms-form-create.vue';
|
||||
import MsTag, { TagType } from '@/components/pure/ms-tag/ms-tag.vue';
|
||||
import MsSelect from '@/components/business/ms-select';
|
||||
|
||||
import { getSystemOrgOption } from '@/api/modules/setting/organizationAndProject';
|
||||
import { getPlatform, getPlatformAccount, savePlatform, validatePlatform } from '@/api/modules/user/index';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
type Status = 0 | 1 | 2;
|
||||
|
@ -81,10 +92,33 @@
|
|||
'validate-trigger': ['change'],
|
||||
},
|
||||
});
|
||||
const currentOrg = ref(appStore.currentOrgId);
|
||||
const orgOptions = ref([]);
|
||||
const orgLoading = ref(false);
|
||||
|
||||
async function initOrgOptions() {
|
||||
try {
|
||||
orgLoading.value = true;
|
||||
const res = await getSystemOrgOption();
|
||||
orgOptions.value = res.map((e) => ({
|
||||
label: e.name,
|
||||
value: e.id,
|
||||
}));
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
orgLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const loading = ref(false);
|
||||
async function initPlatformAccountInfo() {
|
||||
try {
|
||||
loading.value = true;
|
||||
dynamicForm.value = {};
|
||||
const res = await getPlatformAccount();
|
||||
// 动态生成插件表单
|
||||
Object.keys(res).forEach((key) => {
|
||||
dynamicForm.value[key] = {
|
||||
key,
|
||||
|
@ -97,22 +131,48 @@
|
|||
};
|
||||
});
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function initPlatformInfo() {
|
||||
try {
|
||||
const res = await getPlatform();
|
||||
loading.value = true;
|
||||
const res = await getPlatform(currentOrg.value);
|
||||
// 遍历插件表单
|
||||
Object.keys(dynamicForm.value).forEach((configKey: any) => {
|
||||
const config = dynamicForm.value[configKey].formModel.form;
|
||||
// 遍历插件表单的表单项并赋值
|
||||
Object.keys(config).forEach((key) => {
|
||||
const value = res[configKey][key];
|
||||
config[key] = value || config[key];
|
||||
dynamicForm.value[configKey].status = value !== undefined ? 1 : 0;
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function validate(config: any) {
|
||||
try {
|
||||
config.validateLoading = true;
|
||||
await validatePlatform(config.key, config.formModel.form);
|
||||
const configForms: Record<string, any> = {};
|
||||
Object.keys(dynamicForm.value).forEach((key) => {
|
||||
configForms[key] = {
|
||||
...dynamicForm.value[key].formModel.form,
|
||||
};
|
||||
});
|
||||
await validatePlatform(config.key, currentOrg.value, config.formModel.form);
|
||||
await savePlatform({
|
||||
[currentOrg.value]: configForms,
|
||||
});
|
||||
Message.success(t('ms.personal.validPass'));
|
||||
config.status = 1;
|
||||
} catch (error) {
|
||||
|
@ -124,8 +184,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
initPlatformAccountInfo();
|
||||
async function handleOrgChange() {
|
||||
await initPlatformAccountInfo();
|
||||
initPlatformInfo();
|
||||
}
|
||||
|
||||
onBeforeMount(async () => {
|
||||
initOrgOptions();
|
||||
await initPlatformAccountInfo();
|
||||
initPlatformInfo();
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -72,7 +72,7 @@ export default {
|
|||
'ms.personal.validPass': 'Verification passed',
|
||||
'ms.personal.validFail': 'Verification failed',
|
||||
'ms.personal.unValid': 'Not verified',
|
||||
'ms.personal.valid': 'Verify',
|
||||
'ms.personal.valid': 'Verify and save',
|
||||
'ms.personal.authType': 'Authentication',
|
||||
'ms.personal.platformAccount': 'Account',
|
||||
'ms.personal.platformAccountPlaceholder': 'Please enter {type} account',
|
||||
|
|
|
@ -66,7 +66,7 @@ export default {
|
|||
'ms.personal.validPass': '校验通过',
|
||||
'ms.personal.validFail': '校验失败',
|
||||
'ms.personal.unValid': '未校验',
|
||||
'ms.personal.valid': '校验',
|
||||
'ms.personal.valid': '校验并保存',
|
||||
'ms.personal.authType': '认证方式',
|
||||
'ms.personal.platformAccount': '平台账号',
|
||||
'ms.personal.platformAccountPlaceholder': '请输入 {type} 账号',
|
||||
|
|
|
@ -69,7 +69,6 @@ export default defineComponent(
|
|||
selectRef,
|
||||
selectVal: innerValue,
|
||||
isCascade: true,
|
||||
options: props.options,
|
||||
valueKey: props.valueKey,
|
||||
labelKey: props.labelKey,
|
||||
});
|
||||
|
@ -79,7 +78,7 @@ export default defineComponent(
|
|||
(val) => {
|
||||
innerValue.value = val;
|
||||
if (Array.isArray(val) && val.length > 0 && props.multiple) {
|
||||
calculateMaxTag();
|
||||
calculateMaxTag(remoteOriginOptions.value);
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -239,6 +238,7 @@ export default defineComponent(
|
|||
handleSelectAllChange(true);
|
||||
}
|
||||
}
|
||||
calculateMaxTag(val);
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -357,9 +357,15 @@ export default defineComponent(
|
|||
popup-container={props.popupContainer || document.body}
|
||||
trigger-props={props.triggerProps}
|
||||
fallback-option={props.fallbackOption}
|
||||
onChange={(value: ModelType) => emit('update:modelValue', value)}
|
||||
onChange={(value: ModelType) => {
|
||||
emit('update:modelValue', value);
|
||||
emit('change', value);
|
||||
}}
|
||||
onSearch={handleSearch}
|
||||
onPopupVisibleChange={(val: boolean) => emit('popupVisibleChange', val)}
|
||||
onPopupVisibleChange={(val: boolean) => {
|
||||
handleSearch('', true);
|
||||
emit('popupVisibleChange', val);
|
||||
}}
|
||||
onRemove={(val: string | number | boolean | Record<string, any> | undefined) => emit('remove', val)}
|
||||
onKeyup={(e: KeyboardEvent) => {
|
||||
// 阻止组件在回车时自动触发的事件
|
||||
|
@ -416,6 +422,6 @@ export default defineComponent(
|
|||
'atLeastOne',
|
||||
'objectValue',
|
||||
],
|
||||
emits: ['update:modelValue', 'remoteSearch', 'popupVisibleChange', 'update:loading', 'remove'],
|
||||
emits: ['update:modelValue', 'remoteSearch', 'popupVisibleChange', 'update:loading', 'remove', 'change'],
|
||||
}
|
||||
);
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
:type="FileIconMap[fileType][UploadStatus.done]"
|
||||
class="absolute top-0 h-full w-full p-[24px] text-[var(--color-text-4)]"
|
||||
/>
|
||||
<a-tooltip :content="props.footerText" :mouse-enter-delay="300" position="bl" mini>
|
||||
<a-tooltip v-if="props.footerText" :content="props.footerText" :mouse-enter-delay="300" position="bl" mini>
|
||||
<div class="ms-thumbnail-card-footer one-line-text">
|
||||
{{ props.footerText }}
|
||||
</div>
|
||||
|
|
|
@ -113,4 +113,12 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style lang="less" scoped>
|
||||
:deep(.arco-menu-inner) {
|
||||
overflow-y: hidden;
|
||||
padding: 9px 20px;
|
||||
.arco-menu-selected-label {
|
||||
bottom: -8px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -66,6 +66,7 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { h, nextTick, onBeforeMount, Ref, ref, watch, watchEffect } from 'vue';
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
|
@ -302,28 +303,14 @@
|
|||
emit('check', checkedKeys);
|
||||
}
|
||||
|
||||
const innerFocusNodeKey = ref(props.focusNodeKey || ''); // 聚焦的节点,一般用于在操作扩展按钮时,高亮当前节点,保持扩展按钮持续显示
|
||||
|
||||
watch(
|
||||
() => props.focusNodeKey,
|
||||
(val) => {
|
||||
innerFocusNodeKey.value = val || '';
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => innerFocusNodeKey.value,
|
||||
(val) => {
|
||||
emit('update:focusNodeKey', val);
|
||||
}
|
||||
);
|
||||
const innerFocusNodeKey = useVModel(props, 'focusNodeKey', emit); // 聚焦的节点,一般用于在操作扩展按钮时,高亮当前节点,保持扩展按钮持续显示
|
||||
|
||||
const focusEl = ref<HTMLElement | null>(); // 存储聚焦的节点元素
|
||||
|
||||
watch(
|
||||
() => innerFocusNodeKey.value,
|
||||
(val) => {
|
||||
if (val.toString() !== '') {
|
||||
if (val?.toString() !== '') {
|
||||
focusEl.value = treeRef.value?.$el.querySelector(`[data-key="${val}"]`);
|
||||
if (focusEl.value) {
|
||||
focusEl.value.style.backgroundColor = 'rgb(var(--primary-1))';
|
||||
|
@ -355,21 +342,7 @@
|
|||
}
|
||||
);
|
||||
|
||||
const innerSelectedKeys = ref(props.selectedKeys || []);
|
||||
|
||||
watch(
|
||||
() => props.selectedKeys,
|
||||
(val) => {
|
||||
innerSelectedKeys.value = val || [];
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => innerSelectedKeys.value,
|
||||
(val) => {
|
||||
emit('update:selectedKeys', val);
|
||||
}
|
||||
);
|
||||
const innerSelectedKeys = useVModel(props, 'selectedKeys', emit);
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
|
@ -381,6 +354,14 @@
|
|||
border-radius: var(--border-radius-small);
|
||||
&:hover {
|
||||
background-color: rgb(var(--primary-1));
|
||||
.arco-tree-node-title {
|
||||
background-color: rgb(var(--primary-1));
|
||||
&:not([draggable='false']) {
|
||||
.arco-tree-node-title-text {
|
||||
width: calc(100% - 22px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.arco-tree-node-indent-block {
|
||||
width: 1px;
|
||||
|
@ -409,7 +390,7 @@
|
|||
&:hover {
|
||||
background-color: rgb(var(--primary-1));
|
||||
+ .ms-tree-node-extra {
|
||||
@apply block;
|
||||
@apply visible w-auto;
|
||||
}
|
||||
}
|
||||
.arco-tree-node-title-text {
|
||||
|
@ -418,7 +399,7 @@
|
|||
.arco-tree-node-drag-icon {
|
||||
@apply cursor-move;
|
||||
|
||||
right: 4px;
|
||||
right: 6px;
|
||||
.arco-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
@ -428,9 +409,9 @@
|
|||
width: 80%;
|
||||
}
|
||||
.ms-tree-node-extra {
|
||||
@apply relative hidden;
|
||||
@apply invisible relative w-0;
|
||||
&:hover {
|
||||
@apply block;
|
||||
@apply visible w-auto;
|
||||
}
|
||||
.ms-tree-node-extra__btn,
|
||||
.ms-tree-node-extra__more {
|
||||
|
@ -448,7 +429,7 @@
|
|||
}
|
||||
}
|
||||
.ms-tree-node-extra--focus {
|
||||
@apply block;
|
||||
@apply visible w-auto;
|
||||
}
|
||||
.arco-tree-node-custom-icon {
|
||||
@apply hidden;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<MsIcon
|
||||
v-if="props.avatar === 'default'"
|
||||
v-if="props.avatar === 'default' || props.avatar === null"
|
||||
type="icon-icon_that_person"
|
||||
:size="props.size"
|
||||
class="text-[var(--color-text-4)]"
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
<template>
|
||||
<a-spin class="!block h-full" :loading="props.loading" :size="28">
|
||||
<div
|
||||
ref="fullRef"
|
||||
:class="[
|
||||
'ms-card',
|
||||
'relative',
|
||||
'h-full',
|
||||
props.isFullscreen ? 'ms-card--no-radius' : '',
|
||||
props.isFullscreen || isFullScreen ? 'ms-card--no-radius' : '',
|
||||
props.autoHeight ? '' : 'min-h-[500px]',
|
||||
props.noContentPadding ? 'ms-card--noContentPadding' : 'p-[24px]',
|
||||
props.noBottomRadius ? 'ms-card--noBottomRadius' : '',
|
||||
]"
|
||||
>
|
||||
<a-scrollbar v-if="!props.simple" :style="{ overflow: 'auto' }">
|
||||
<div class="card-header" :style="props.minWidth ? { minWidth: `${props.minWidth}px` } : {}">
|
||||
<div class="ms-card-header" :style="props.headerMinWidth ? { minWidth: `${props.headerMinWidth}px` } : {}">
|
||||
<div v-if="!props.hideBack" class="back-btn" @click="back"><icon-arrow-left /></div>
|
||||
<slot name="headerLeft">
|
||||
<div class="font-medium text-[var(--color-text-000)]">{{ props.title }}</div>
|
||||
|
@ -20,6 +21,15 @@
|
|||
</slot>
|
||||
<div class="ml-auto flex items-center">
|
||||
<slot name="headerRight"></slot>
|
||||
<div
|
||||
v-if="props.showFullScreen"
|
||||
class="w-[96px] cursor-pointer text-right !text-[var(--color-text-4)]"
|
||||
@click="toggleFullScreen"
|
||||
>
|
||||
<MsIcon v-if="isFullScreen" type="icon-icon_minify_outlined" />
|
||||
<MsIcon v-else type="icon-icon_magnify_outlined" />
|
||||
{{ t('msCodeEditor.fullScreen') }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="$slots.subHeader" class="basis-full">
|
||||
<slot name="subHeader"></slot>
|
||||
|
@ -30,7 +40,7 @@
|
|||
<a-divider v-if="!props.simple && !props.hideDivider" class="mb-[16px] mt-0" />
|
||||
</div>
|
||||
<div class="ms-card-container">
|
||||
<a-scrollbar :class="props.noContentPadding ? '' : 'pr-[5px]'" :style="getComputedContentStyle">
|
||||
<a-scrollbar :class="['h-full', props.noContentPadding ? '' : 'pr-[5px]']" :style="getComputedContentStyle">
|
||||
<div class="relative h-full w-full" :style="{ minWidth: `${props.minWidth || 1000}px` }">
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
@ -38,7 +48,7 @@
|
|||
</div>
|
||||
<div
|
||||
v-if="!props.hideFooter && !props.simple"
|
||||
class="fixed bottom-0 right-[16px] z-10 flex items-center bg-white p-[24px] shadow-[0_-1px_4px_rgba(2,2,2,0.1)]"
|
||||
class="fixed bottom-0 right-[16px] z-[100] flex items-center bg-white p-[24px] shadow-[0_-1px_4px_rgba(2,2,2,0.1)]"
|
||||
:style="{ width: `calc(100% - ${menuWidth + 16}px)` }"
|
||||
>
|
||||
<div class="ml-0 mr-auto">
|
||||
|
@ -64,6 +74,7 @@
|
|||
import { computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import useFullScreen from '@/hooks/useFullScreen';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
|
@ -81,7 +92,8 @@
|
|||
hideBack: boolean; // 隐藏返回按钮
|
||||
autoHeight: boolean; // 内容区域高度是否自适应
|
||||
otherWidth: number; // 该宽度为卡片外部同级容器的宽度
|
||||
minWidth: number; // 卡片最小宽度
|
||||
headerMinWidth: number; // 卡片头部最小宽度
|
||||
minWidth: number; // 卡片内容最小宽度
|
||||
hasBreadcrumb: boolean; // 是否有面包屑,如果有面包屑,高度需要减去面包屑的高度
|
||||
noContentPadding: boolean; // 内容区域是否有padding
|
||||
noBottomRadius?: boolean; // 底部是否有圆角
|
||||
|
@ -89,6 +101,7 @@
|
|||
hideDivider?: boolean; // 是否隐藏分割线
|
||||
handleBack: () => void; // 自定义返回按钮触发事件
|
||||
dividerHasPX: boolean; // 分割线是否有左右padding;
|
||||
showFullScreen: boolean; // 是否显示全屏按钮
|
||||
}>
|
||||
>(),
|
||||
{
|
||||
|
@ -117,6 +130,10 @@
|
|||
return appStore.menuCollapse ? collapsedWidth : appStore.menuWidth;
|
||||
});
|
||||
|
||||
// 用于全屏的容器 ref
|
||||
const fullRef = ref<HTMLElement | null>();
|
||||
const { isFullScreen, toggleFullScreen } = useFullScreen(fullRef);
|
||||
|
||||
const _specialHeight = props.hasBreadcrumb ? 32 + props.specialHeight : props.specialHeight; // 有面包屑的话,默认面包屑高度32
|
||||
|
||||
const cardOverHeight = computed(() => {
|
||||
|
@ -132,6 +149,13 @@
|
|||
});
|
||||
|
||||
const getComputedContentStyle = computed(() => {
|
||||
if (props.isFullscreen || isFullScreen.value) {
|
||||
return {
|
||||
overflow: 'auto',
|
||||
width: 'auto',
|
||||
height: 'auto',
|
||||
};
|
||||
}
|
||||
if (props.noContentPadding) {
|
||||
return {
|
||||
overflow: 'auto',
|
||||
|
@ -139,13 +163,6 @@
|
|||
height: props.autoHeight ? 'auto' : `calc(100vh - ${cardOverHeight.value}px)`,
|
||||
};
|
||||
}
|
||||
if (props.isFullscreen) {
|
||||
return {
|
||||
overflow: 'auto',
|
||||
width: 'calc(100vw - 58px)',
|
||||
height: 'auto',
|
||||
};
|
||||
}
|
||||
return {
|
||||
overflow: 'auto',
|
||||
width: props.otherWidth
|
||||
|
@ -172,7 +189,7 @@
|
|||
box-shadow: 0 0 10px rgb(120 56 135 / 5%);
|
||||
&--noContentPadding {
|
||||
border-radius: var(--border-radius-large);
|
||||
.card-header {
|
||||
.ms-card-header {
|
||||
padding: 24px 24px 16px;
|
||||
}
|
||||
.arco-divider {
|
||||
|
@ -182,7 +199,7 @@
|
|||
&--noBottomRadius {
|
||||
border-radius: var(--border-radius-large) var(--border-radius-large) 0 0;
|
||||
}
|
||||
.card-header {
|
||||
.ms-card-header {
|
||||
@apply flex flex-wrap items-center;
|
||||
|
||||
padding-bottom: 16px;
|
||||
|
@ -200,6 +217,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.ms-card-container {
|
||||
@apply h-full;
|
||||
}
|
||||
}
|
||||
.ms-card--no-radius {
|
||||
border-radius: 0;
|
||||
|
|
|
@ -34,9 +34,9 @@
|
|||
<div
|
||||
v-if="showFullScreen"
|
||||
class="w-[96px] cursor-pointer text-right !text-[var(--color-text-4)]"
|
||||
@click="toggle"
|
||||
@click="toggleFullScreen"
|
||||
>
|
||||
<MsIcon v-if="isFullscreen" type="icon-icon_minify_outlined" />
|
||||
<MsIcon v-if="isFullScreen" type="icon-icon_minify_outlined" />
|
||||
<MsIcon v-else type="icon-icon_magnify_outlined" />
|
||||
{{ t('msCodeEditor.fullScreen') }}
|
||||
</div>
|
||||
|
@ -44,7 +44,7 @@
|
|||
</div>
|
||||
<!-- 这里的 40px 是顶部标题的 40px -->
|
||||
<div :class="`flex ${showTitleLine ? 'h-[calc(100%-40px)]' : 'h-full'} w-full flex-row`">
|
||||
<div ref="codeContainerRef" :class="['ms-code-editor', isFullscreen ? 'ms-code-editor-full-screen' : '']"></div>
|
||||
<div ref="codeContainerRef" :class="['ms-code-editor', isFullScreen ? 'ms-code-editor-full-screen' : '']"></div>
|
||||
<slot name="rightBox"> </slot>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -52,9 +52,9 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||
import { useFullscreen } from '@vueuse/core';
|
||||
|
||||
import { codeCharset } from '@/config/apiTest';
|
||||
import useFullScreen from '@/hooks/useFullScreen';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { decodeStringToCharset } from '@/utils';
|
||||
|
||||
|
@ -183,7 +183,7 @@
|
|||
}
|
||||
};
|
||||
|
||||
const { isFullscreen, toggle } = useFullscreen(fullRef);
|
||||
const { isFullScreen, toggleFullScreen } = useFullScreen(fullRef);
|
||||
|
||||
// 插入内容
|
||||
const insertContent = (text: string) => {
|
||||
|
@ -257,7 +257,7 @@
|
|||
return {
|
||||
codeContainerRef,
|
||||
fullRef,
|
||||
isFullscreen,
|
||||
isFullScreen,
|
||||
currentTheme,
|
||||
themeOptions,
|
||||
currentLanguage,
|
||||
|
@ -265,7 +265,7 @@
|
|||
currentCharset,
|
||||
charsetOptions,
|
||||
showTitleLine,
|
||||
toggle,
|
||||
toggleFullScreen,
|
||||
t,
|
||||
handleThemeChange,
|
||||
handleLanguageChange,
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
'ms-drawer',
|
||||
props.mask ? '' : 'ms-drawer-no-mask',
|
||||
props.noContentPadding ? 'ms-drawer-no-content-padding' : '',
|
||||
props.noTitle ? 'ms-drawer-no-title' : '',
|
||||
]"
|
||||
@cancel="handleCancel"
|
||||
@close="handleClose"
|
||||
|
@ -20,15 +21,15 @@
|
|||
<div class="flex items-center">
|
||||
{{ props.title }}
|
||||
<slot name="headerLeft"></slot>
|
||||
<a-tag v-if="titleTag" :color="props.titleTagColor" class="ml-[8px] mr-auto">{{
|
||||
props.titleTag
|
||||
}}</a-tag></div
|
||||
>
|
||||
<a-tag v-if="titleTag" :color="props.titleTagColor" class="ml-[8px] mr-auto">
|
||||
{{ props.titleTag }}
|
||||
</a-tag>
|
||||
</div>
|
||||
<slot name="tbutton"></slot>
|
||||
</div>
|
||||
</slot>
|
||||
</template>
|
||||
<div v-if="!props.disabledWidthDrag" class="handle" @mousedown="startResize">
|
||||
<div v-if="!props.disabledWidthDrag && typeof drawerWidth === 'number'" class="handle" @mousedown="startResize">
|
||||
<icon-drag-dot-vertical class="absolute left-[-3px] top-[50%] w-[14px]" size="14" />
|
||||
</div>
|
||||
<a-scrollbar class="h-full overflow-y-auto">
|
||||
|
@ -110,10 +111,13 @@
|
|||
cancelText?: string;
|
||||
saveContinueText?: string;
|
||||
showContinue?: boolean;
|
||||
width: number;
|
||||
width: string | number; // 抽屉宽度,为数值时才可拖拽改变宽度
|
||||
noContentPadding?: boolean; // 是否没有内容内边距
|
||||
popupContainer?: string;
|
||||
disabledWidthDrag?: boolean; // 是否禁止拖拽宽度
|
||||
closable?: boolean; // 是否显示右上角的关闭按钮
|
||||
noTitle?: boolean; // 是否不显示标题栏
|
||||
drawerStyle?: Record<string, string>; // 抽屉样式
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<DrawerProps>(), {
|
||||
|
@ -164,33 +168,35 @@
|
|||
* 鼠标单击开始监听拖拽移动
|
||||
*/
|
||||
const startResize = (event: MouseEvent) => {
|
||||
resizing.value = true;
|
||||
const startX = event.clientX;
|
||||
const initialWidth = drawerWidth.value;
|
||||
if (typeof drawerWidth.value === 'number') {
|
||||
resizing.value = true;
|
||||
const startX = event.clientX;
|
||||
const initialWidth = drawerWidth.value;
|
||||
|
||||
// 计算鼠标移动距离
|
||||
const handleMouseMove = (_event: MouseEvent) => {
|
||||
if (resizing.value) {
|
||||
const newWidth = initialWidth + (startX - _event.clientX); // 新的宽度等于当前抽屉宽度+鼠标移动的距离
|
||||
if (newWidth >= (props.width || 480) && newWidth <= window.innerWidth * 0.9) {
|
||||
// 最大最小宽度限制,最小宽度为传入的width或480,最大宽度为视图窗口宽度的90%
|
||||
drawerWidth.value = newWidth;
|
||||
// 计算鼠标移动距离
|
||||
const handleMouseMove = (_event: MouseEvent) => {
|
||||
if (resizing.value) {
|
||||
const newWidth = initialWidth + (startX - _event.clientX); // 新的宽度等于当前抽屉宽度+鼠标移动的距离
|
||||
if (newWidth >= (props.width || 480) && newWidth <= window.innerWidth * 0.9) {
|
||||
// 最大最小宽度限制,最小宽度为传入的width或480,最大宽度为视图窗口宽度的90%
|
||||
drawerWidth.value = newWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// 松开鼠标按键,拖拽结束
|
||||
const handleMouseUp = () => {
|
||||
if (resizing.value) {
|
||||
// 如果当前是在拖拽,则重置拖拽状态,且移除鼠标监听事件
|
||||
resizing.value = false;
|
||||
window.removeEventListener('mousemove', handleMouseMove);
|
||||
window.removeEventListener('mouseup', handleMouseUp);
|
||||
}
|
||||
};
|
||||
// 松开鼠标按键,拖拽结束
|
||||
const handleMouseUp = () => {
|
||||
if (resizing.value) {
|
||||
// 如果当前是在拖拽,则重置拖拽状态,且移除鼠标监听事件
|
||||
resizing.value = false;
|
||||
window.removeEventListener('mousemove', handleMouseMove);
|
||||
window.removeEventListener('mouseup', handleMouseUp);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('mousemove', handleMouseMove);
|
||||
window.addEventListener('mouseup', handleMouseUp);
|
||||
window.addEventListener('mousemove', handleMouseMove);
|
||||
window.addEventListener('mouseup', handleMouseUp);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -232,6 +238,11 @@
|
|||
right: -12px;
|
||||
}
|
||||
}
|
||||
.ms-drawer-no-title {
|
||||
.arco-drawer-header {
|
||||
@apply hidden;
|
||||
}
|
||||
}
|
||||
.ms-drawer-no-mask {
|
||||
left: auto;
|
||||
.arco-drawer {
|
||||
|
|
|
@ -19,11 +19,11 @@
|
|||
:class="{ active: innerActiveTab === tab.id }"
|
||||
@click="handleTabClick(tab)"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<div :draggable="!!tab.draggable" class="flex items-center">
|
||||
<slot name="label" :tab="tab">{{ tab.label }}</slot>
|
||||
<div v-if="tab.unSaved" class="ml-[8px] h-[8px] w-[8px] rounded-full bg-[rgb(var(--primary-5))]"></div>
|
||||
<MsButton
|
||||
v-if="props.atLeastOne ? props.tabs.length > 1 && tab.closable : tab.closable"
|
||||
v-if="props.atLeastOne ? props.tabs.length > 1 && tab.closable : tab.closable !== false"
|
||||
type="icon"
|
||||
status="secondary"
|
||||
class="ms-editable-tab-close-button"
|
||||
|
|
|
@ -3,5 +3,6 @@ export interface TabItem {
|
|||
label: string;
|
||||
closable?: boolean;
|
||||
unSaved?: boolean; // 未保存
|
||||
draggable?: boolean;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
|
|
@ -17,8 +17,9 @@
|
|||
:style="{
|
||||
'border-top': item.level === 1 && index !== 0 ? '1px solid var(--color-border-2)' : 'none',
|
||||
}"
|
||||
@click.stop="toggleMenu(item)"
|
||||
>
|
||||
<div @click="toggleMenu(item.name)">{{ item.title }}</div>
|
||||
<div>{{ item.title }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -28,15 +29,15 @@
|
|||
<script setup lang="ts">
|
||||
import { hasAnyPermission } from '@/utils/permission';
|
||||
|
||||
interface MenuItem {
|
||||
title: string;
|
||||
level: number;
|
||||
name: string;
|
||||
}
|
||||
const props = defineProps<{
|
||||
title?: string;
|
||||
defaultKey?: string;
|
||||
menuList: {
|
||||
title: string;
|
||||
level: number;
|
||||
name: string;
|
||||
permission?: string[];
|
||||
}[];
|
||||
menuList: MenuItem[];
|
||||
activeClass?: string;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
|
@ -56,10 +57,10 @@
|
|||
}
|
||||
);
|
||||
|
||||
const toggleMenu = (itemName: string) => {
|
||||
if (itemName) {
|
||||
currentKey.value = itemName;
|
||||
emit('toggleMenu', itemName);
|
||||
const toggleMenu = (item: MenuItem) => {
|
||||
if (item.level !== 1 && item.name !== currentKey.value) {
|
||||
currentKey.value = item.name;
|
||||
emit('toggleMenu', item.name);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -174,11 +174,13 @@
|
|||
</template>
|
||||
|
||||
<template #empty>
|
||||
<slot name="empty">
|
||||
<div class="flex h-[20px] flex-col items-center justify-center">
|
||||
<span class="text-[14px] text-[var(--color-text-4)]">{{ t('msTable.empty') }}</span>
|
||||
</div>
|
||||
</slot>
|
||||
<div class="w-full">
|
||||
<slot name="empty">
|
||||
<div class="flex h-[20px] flex-col items-center justify-center">
|
||||
<span class="text-[14px] text-[var(--color-text-4)]">{{ t('msTable.empty') }}</span>
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
<template #expand-icon="{ expanded }">
|
||||
<MsIcon v-if="!expanded" :size="8" type="icon-icon_right_outlined" class="text-[var(--color-text-4)]" />
|
||||
|
@ -258,6 +260,7 @@
|
|||
MsTableProps,
|
||||
} from './type';
|
||||
import type { TableColumnData, TableData } from '@arco-design/web-vue';
|
||||
import type { TableOperationColumn } from '@arco-design/web-vue/es/table/interface';
|
||||
|
||||
const batchLeft = ref('10px');
|
||||
const { t } = useI18n();
|
||||
|
@ -275,7 +278,15 @@
|
|||
noDisable?: boolean;
|
||||
showSetting?: boolean;
|
||||
columns: MsTableColumn;
|
||||
spanMethod?: (params: { record: TableData; rowIndex: number; columnIndex: number }) => void;
|
||||
spanMethod?: (params: {
|
||||
record: TableData;
|
||||
column: TableColumnData | TableOperationColumn;
|
||||
rowIndex: number;
|
||||
columnIndex: number;
|
||||
}) => void | {
|
||||
rowspan?: number | undefined;
|
||||
colspan?: number | undefined;
|
||||
};
|
||||
expandedKeys?: string[];
|
||||
rowClass?: string | any[] | Record<string, any> | ((record: TableData, rowIndex: number) => any);
|
||||
spanAll?: boolean;
|
||||
|
|
|
@ -276,6 +276,8 @@ export default function useTableProps<T>(
|
|||
} catch (err) {
|
||||
setTableErrorStatus('error');
|
||||
propsRes.value.data = [];
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
|
@ -73,8 +73,8 @@ export const FileIconMap: FileIconMapping = {
|
|||
[UploadStatus.done]: 'icon-icon_file-unknow_colorful1',
|
||||
},
|
||||
json: {
|
||||
[UploadStatus.init]: 'icon-icon_file-json_colorful_ash',
|
||||
[UploadStatus.done]: 'icon-icon_file-json_colorful1',
|
||||
[UploadStatus.init]: 'icon-a-icon_file-json',
|
||||
[UploadStatus.done]: 'icon-a-icon_file-json',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -34,12 +34,14 @@
|
|||
{{ t(props.mainText || 'ms.upload.importModalDragText') }}
|
||||
</div>
|
||||
<div v-if="showSubText" class="ms-upload-sub-text">
|
||||
{{
|
||||
t(props.subText || 'ms.upload.importModalFileTip', {
|
||||
type: UploadAcceptEnum[props.accept],
|
||||
size: props.maxSize || defaultMaxSize,
|
||||
})
|
||||
}}
|
||||
<slot name="subText">
|
||||
{{
|
||||
t(props.subText || 'ms.upload.importModalFileTip', {
|
||||
type: UploadAcceptEnum[props.accept],
|
||||
size: props.maxSize || defaultMaxSize,
|
||||
})
|
||||
}}
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
<div class="left-side">
|
||||
<a-space>
|
||||
<div class="one-line-text flex max-w-[145px] items-center">
|
||||
<img :src="props.logo" class="mr-[4px] h-[32px] w-[32px]" />
|
||||
{{ props.name }}
|
||||
<img :src="props.logo" class="mr-[4px] h-[34px] w-[32px]" />
|
||||
<div class="font-['Helvetica_Neue'] text-[16px] font-bold text-[rgb(var(--primary-5))]">{{ props.name }}</div>
|
||||
</div>
|
||||
</a-space>
|
||||
</div>
|
||||
|
@ -135,7 +135,7 @@
|
|||
|
||||
import type { ProjectListItem } from '@/models/setting/project';
|
||||
|
||||
import { IconCompass, IconFile, IconInfoCircle, IconQuestionCircle } from '@arco-design/web-vue/es/icon';
|
||||
import { IconInfoCircle, IconQuestionCircle } from '@arco-design/web-vue/es/icon';
|
||||
|
||||
const props = defineProps<{
|
||||
isPreview?: boolean;
|
||||
|
@ -207,21 +207,21 @@
|
|||
}
|
||||
|
||||
const helpCenterList = [
|
||||
{
|
||||
name: 'settings.help.guide',
|
||||
icon: IconCompass,
|
||||
route: '/help-center/guide',
|
||||
},
|
||||
// {
|
||||
// name: 'settings.help.guide',
|
||||
// icon: IconCompass,
|
||||
// route: '/help-center/guide',
|
||||
// },
|
||||
{
|
||||
name: 'settings.help.doc',
|
||||
icon: IconQuestionCircle,
|
||||
route: '/help-center/guide',
|
||||
},
|
||||
{
|
||||
name: 'settings.help.APIDoc',
|
||||
icon: IconFile,
|
||||
route: '/help-center/guide',
|
||||
},
|
||||
// {
|
||||
// name: 'settings.help.APIDoc',
|
||||
// icon: IconFile,
|
||||
// route: '/help-center/guide',
|
||||
// },
|
||||
{
|
||||
name: 'settings.help.version',
|
||||
icon: IconInfoCircle,
|
||||
|
|
|
@ -9,7 +9,7 @@ export type PathMapRoute = (typeof RouteEnum)[PathMapKey];
|
|||
export interface PathMapItem {
|
||||
key: PathMapKey | string; // 系统设置
|
||||
locale: string;
|
||||
route: PathMapRoute;
|
||||
route: PathMapRoute | string;
|
||||
permission?: [];
|
||||
level: (typeof MENU_LEVEL)[number]; // 系统设置里有系统级别也有组织级别,按最低权限级别配置
|
||||
children?: PathMapItem[];
|
||||
|
@ -35,12 +35,18 @@ export const pathMap: PathMapItem[] = [
|
|||
level: MENU_LEVEL[2],
|
||||
children: [
|
||||
{
|
||||
key: 'API_TEST_DEBUG', // 接口测试
|
||||
key: 'API_TEST_DEBUG', // 接口测试-接口调试
|
||||
locale: 'menu.apiTest.debug',
|
||||
route: RouteEnum.API_TEST_DEBUG,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
key: 'API_TEST_MANAGEMENT', // 接口测试-接口管理
|
||||
locale: 'menu.apiTest.management',
|
||||
route: RouteEnum.API_TEST_MANAGEMENT,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -87,13 +93,6 @@ export const pathMap: PathMapItem[] = [
|
|||
route: RouteEnum.CASE_MANAGEMENT_CASE,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
},
|
||||
{
|
||||
key: 'CASE_MANAGEMENT_REVIEW', // 功能测试-功能用例-用例评审
|
||||
locale: 'menu.caseManagement.caseManagementReview',
|
||||
route: RouteEnum.CASE_MANAGEMENT_REVIEW,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
children: [
|
||||
{
|
||||
key: 'CASE_MANAGEMENT_CASE_DETAIL', // 功能测试-功能用例详情
|
||||
|
@ -116,6 +115,22 @@ export const pathMap: PathMapItem[] = [
|
|||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'CASE_MANAGEMENT_REVIEW', // 功能测试-功能用例-用例评审
|
||||
locale: 'menu.caseManagement.caseManagementReview',
|
||||
route: RouteEnum.CASE_MANAGEMENT_REVIEW,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
children: [
|
||||
{
|
||||
key: 'CASE_MANAGEMENT_REVIEW_LIST', // 功能测试-功能用例-用例评审列表
|
||||
locale: 'menu.caseManagement.caseManagementReview',
|
||||
route: RouteEnum.CASE_MANAGEMENT_REVIEW,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
},
|
||||
{
|
||||
key: 'CASE_MANAGEMENT_REVIEW_CREATE', // 功能测试-功能用例-创建评审
|
||||
locale: 'menu.caseManagement.caseManagementReviewCreate',
|
||||
|
@ -123,6 +138,13 @@ export const pathMap: PathMapItem[] = [
|
|||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
},
|
||||
{
|
||||
key: 'CASE_MANAGEMENT_REVIEW_UPDATE', // 功能测试-功能用例-更新评审
|
||||
locale: 'menu.caseManagement.caseManagementCaseReviewEdit',
|
||||
route: RouteEnum.CASE_MANAGEMENT_REVIEW_CREATE,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
},
|
||||
{
|
||||
key: 'CASE_MANAGEMENT_REVIEW_DETAIL', // 功能测试-功能用例-评审详情
|
||||
locale: 'menu.caseManagement.caseManagementReviewDetail',
|
||||
|
@ -484,4 +506,48 @@ export const pathMap: PathMapItem[] = [
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'PERSONAL_INFORMATION', // 个人信息
|
||||
locale: 'ms.personal',
|
||||
route: '',
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
children: [
|
||||
{
|
||||
key: 'PERSONAL_INFORMATION_BASE_INFO', // 个人信息-基本信息
|
||||
locale: 'ms.personal.baseInfo',
|
||||
route: '',
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
},
|
||||
{
|
||||
key: 'PERSONAL_INFORMATION_PSW', // 个人信息-密码设置
|
||||
locale: 'ms.personal.setPsw',
|
||||
route: '',
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
},
|
||||
{
|
||||
key: 'PERSONAL_INFORMATION_APIKEYS', // 个人信息-ApiKeys
|
||||
locale: 'APIKEY',
|
||||
route: '',
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
},
|
||||
{
|
||||
key: 'PERSONAL_INFORMATION_LOCAL_EXECUTE', // 个人信息-本地执行
|
||||
locale: 'ms.personal.localExecution',
|
||||
route: '',
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
},
|
||||
{
|
||||
key: 'PERSONAL_INFORMATION_TRIPARTITE', // 个人信息-三方平台账号
|
||||
locale: 'ms.personal.tripartite',
|
||||
route: '',
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
@ -52,3 +52,14 @@ export enum ResponseComposition {
|
|||
EXTRACT = 'EXTRACT',
|
||||
ASSERTION = 'ASSERTION',
|
||||
}
|
||||
// 接口定义状态
|
||||
export enum RequestDefinitionStatus {
|
||||
DEPRECATED = 'DEPRECATED',
|
||||
PROCESSING = 'PROCESSING',
|
||||
DEBUGGING = 'DEBUGGING',
|
||||
DONE = 'DONE',
|
||||
}
|
||||
// 接口导入支持格式
|
||||
export enum RequestImportFormat {
|
||||
SWAGGER = 'SWAGGER',
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
export enum ApiTestRouteEnum {
|
||||
API_TEST = 'apiTest',
|
||||
API_TEST_DEBUG = 'apiTestDebug',
|
||||
API_TEST_MANAGEMENT = 'apiTestManagement',
|
||||
}
|
||||
|
||||
export enum BugManagementRouteEnum {
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* 全屏 hook
|
||||
* @param domRef dom ref
|
||||
*/
|
||||
export default function useFullScreen(domRef: Ref<HTMLElement | null | undefined>) {
|
||||
const isFullScreen = ref(false);
|
||||
|
||||
function enterFullScreen() {
|
||||
domRef.value?.setAttribute('style', 'position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 100;');
|
||||
isFullScreen.value = true;
|
||||
}
|
||||
|
||||
function exitFullscreen() {
|
||||
domRef.value?.setAttribute('style', '');
|
||||
isFullScreen.value = false;
|
||||
}
|
||||
|
||||
function toggleFullScreen() {
|
||||
if (isFullScreen.value) {
|
||||
exitFullscreen();
|
||||
} else {
|
||||
enterFullScreen();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isFullScreen,
|
||||
toggleFullScreen,
|
||||
};
|
||||
}
|
|
@ -28,8 +28,9 @@ export default function useSelect(config: UseSelectOption) {
|
|||
|
||||
/**
|
||||
* 计算最大标签数量
|
||||
* @param options 选择器的选项
|
||||
*/
|
||||
function calculateMaxTag() {
|
||||
function calculateMaxTag(options?: CascaderOption[] | SelectOptionData[]) {
|
||||
nextTick(() => {
|
||||
if (config.selectRef.value && selectViewInner.value && Array.isArray(config.selectVal.value)) {
|
||||
if (maxTagCount.value >= 1 && config.selectVal.value.length > maxTagCount.value) return; // 已经超过最大数量的展示,不需要再计算
|
||||
|
@ -38,7 +39,9 @@ export default function useSelect(config: UseSelectOption) {
|
|||
let tagCount = 0;
|
||||
const values = Object.values(config.selectVal.value);
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
const tagWidth = (values[i][config.labelKey || 'label']?.length || 0) * 12; // 计算每个标签渲染出来的宽度,文字大小在12px时宽度也是 12px
|
||||
const option = options?.find((e) => e[config.valueKey || 'value'] === values[i]);
|
||||
const tagWidth = (option ? option[config.labelKey || 'label']?.length || 0 : values[i].length) * 12; // 计算每个标签渲染出来的宽度,文字大小在12px时宽度也是 12px
|
||||
|
||||
if (lastWidth > tagWidth + 36) {
|
||||
tagCount += 1;
|
||||
lastWidth -= tagWidth + 36; // 36px是标签的边距、边框等宽度
|
||||
|
|
|
@ -6,11 +6,11 @@ import { useAppStore, useUserStore } from '@/store';
|
|||
|
||||
export default function useUser() {
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const logout = async (logoutTo?: string) => {
|
||||
const appStore = useAppStore();
|
||||
const userStore = useUserStore();
|
||||
await userStore.logout();
|
||||
const currentRoute = router.currentRoute.value;
|
||||
// 清空顶部菜单
|
||||
|
|
|
@ -102,4 +102,10 @@ export default {
|
|||
'common.noResource': 'No resource, please contact the administrator',
|
||||
'common.noSelectProject': 'No optional items available',
|
||||
'common.noPermission': 'No permission',
|
||||
'common.batchEdit': 'Batch Edit',
|
||||
'common.tagsInputPlaceholder': 'Enter the content and press Enter to directly add tags',
|
||||
'common.move': 'Move',
|
||||
'common.batchMove': 'Batch move',
|
||||
'common.batchMoveSuccess': 'Batch move successful',
|
||||
'common.importSuccess': 'Import successful',
|
||||
};
|
||||
|
|
|
@ -25,6 +25,7 @@ export default {
|
|||
'menu.caseManagement': 'Case Management',
|
||||
'menu.apiTest': 'API Test',
|
||||
'menu.apiTest.debug': 'API debug',
|
||||
'menu.apiTest.management': 'API Management',
|
||||
'menu.uiTest': 'UI Test',
|
||||
'menu.performanceTest': 'Performance Test',
|
||||
'menu.projectManagement': 'Project',
|
||||
|
@ -40,6 +41,7 @@ export default {
|
|||
'menu.caseManagement.caseManagementReview': 'Feature Case Review',
|
||||
'menu.caseManagement.caseManagementReviewCreate': 'Create Review',
|
||||
'menu.caseManagement.caseManagementReviewDetail': 'Review Detail',
|
||||
'menu.caseManagement.caseManagementReviewDetailCaseDetail': 'Review Case Detail',
|
||||
'menu.caseManagement.caseManagementCaseReviewEdit': 'Update Review',
|
||||
'menu.caseManagement.caseManagementCaseDetail': 'Case Detail',
|
||||
'menu.workstation': 'Workstation',
|
||||
|
|
|
@ -105,4 +105,10 @@ export default {
|
|||
'common.noResource': '暂无资源权限,请联系管理员',
|
||||
'common.noSelectProject': '无可选项目',
|
||||
'common.noPermission': '无权限',
|
||||
'common.batchEdit': '批量编辑',
|
||||
'common.tagsInputPlaceholder': '输入内容后回车可直接添加标签',
|
||||
'common.move': '移动',
|
||||
'common.batchMove': '批量移动',
|
||||
'common.batchMoveSuccess': '批量移动成功',
|
||||
'common.importSuccess': '导入成功',
|
||||
};
|
||||
|
|
|
@ -24,6 +24,7 @@ export default {
|
|||
'menu.caseManagement': '功能测试',
|
||||
'menu.apiTest': '接口测试',
|
||||
'menu.apiTest.debug': '接口调试',
|
||||
'menu.apiTest.management': '接口管理',
|
||||
'menu.uiTest': 'UI测试',
|
||||
'menu.workstation': '工作台',
|
||||
'menu.loadTest': '性能测试',
|
||||
|
@ -44,6 +45,7 @@ export default {
|
|||
'menu.caseManagement.caseManagementReview': '用例评审',
|
||||
'menu.caseManagement.caseManagementReviewCreate': '创建评审',
|
||||
'menu.caseManagement.caseManagementReviewDetail': '评审详情',
|
||||
'menu.caseManagement.caseManagementReviewDetailCaseDetail': '评审用例详情',
|
||||
'menu.caseManagement.caseManagementCaseReviewEdit': '更新评审',
|
||||
'menu.caseManagement.caseManagementCaseDetail': '用例详情',
|
||||
'menu.projectManagement.projectPermission': '项目与权限',
|
||||
|
|
|
@ -29,7 +29,7 @@ const defaultLoginConfig = {
|
|||
icon: [],
|
||||
loginLogo: [],
|
||||
loginImage: [],
|
||||
slogan: '一站式开源持续测试平台',
|
||||
slogan: 'login.form.title',
|
||||
};
|
||||
const defaultPlatformConfig = {
|
||||
logoPlatform: [],
|
||||
|
|
|
@ -10,9 +10,11 @@
|
|||
<script lang="ts" setup>
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { WorkbenchRouteEnum } from '@/enums/routeEnum';
|
||||
|
||||
const router = useRouter();
|
||||
const back = () => {
|
||||
// warning: Go to the node that has the permission
|
||||
router.push({ name: 'workstation' });
|
||||
router.push({ name: WorkbenchRouteEnum.WORKBENCH });
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -66,8 +66,13 @@
|
|||
</template>
|
||||
</a-dropdown>
|
||||
</MsButton>
|
||||
<MsButton type="icon" status="secondary" class="!rounded-[var(--border-radius-small)]" @click="toggle">
|
||||
<MsIcon :type="isFullscreen ? 'icon-icon_off_screen' : 'icon-icon_full_screen_one'" class="mr-1" size="16" />
|
||||
<MsButton
|
||||
type="icon"
|
||||
status="secondary"
|
||||
class="!rounded-[var(--border-radius-small)]"
|
||||
@click="toggleFullScreen"
|
||||
>
|
||||
<MsIcon :type="isFullScreen ? 'icon-icon_off_screen' : 'icon-icon_full_screen_one'" class="mr-1" size="16" />
|
||||
{{ t('caseManagement.featureCase.fullScreen') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
|
@ -150,7 +155,6 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useFullscreen } from '@vueuse/core';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
|
@ -169,6 +173,7 @@
|
|||
import CommentTab from './commentTab.vue';
|
||||
|
||||
import { createOrUpdateComment, deleteSingleBug, followBug, getBugDetail } from '@/api/modules/bug-management/index';
|
||||
import useFullScreen from '@/hooks/useFullScreen';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useModal from '@/hooks/useModal';
|
||||
import { useAppStore } from '@/store';
|
||||
|
@ -181,7 +186,7 @@
|
|||
const router = useRouter();
|
||||
const detailDrawerRef = ref<InstanceType<typeof MsDetailDrawer>>();
|
||||
const wrapperRef = ref();
|
||||
const { isFullscreen, toggle } = useFullscreen(wrapperRef);
|
||||
const { isFullScreen, toggleFullScreen } = useFullScreen(wrapperRef);
|
||||
const featureCaseStore = useFeatureCaseStore();
|
||||
const { t } = useI18n();
|
||||
const { openModal } = useModal();
|
||||
|
|
|
@ -77,8 +77,13 @@
|
|||
</template>
|
||||
</a-dropdown>
|
||||
</MsButton>
|
||||
<MsButton type="icon" status="secondary" class="!rounded-[var(--border-radius-small)]" @click="toggle">
|
||||
<MsIcon :type="isFullscreen ? 'icon-icon_off_screen' : 'icon-icon_full_screen_one'" class="mr-1" size="16" />
|
||||
<MsButton
|
||||
type="icon"
|
||||
status="secondary"
|
||||
class="!rounded-[var(--border-radius-small)]"
|
||||
@click="toggleFullScreen"
|
||||
>
|
||||
<MsIcon :type="isFullScreen ? 'icon-icon_off_screen' : 'icon-icon_full_screen_one'" class="mr-1" size="16" />
|
||||
{{ t('caseManagement.featureCase.fullScreen') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
|
@ -194,7 +199,6 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useFullscreen } from '@vueuse/core';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
|
@ -226,6 +230,7 @@
|
|||
getCaseDetail,
|
||||
getCaseModuleTree,
|
||||
} from '@/api/modules/case-management/featureCase';
|
||||
import useFullScreen from '@/hooks/useFullScreen';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useModal from '@/hooks/useModal';
|
||||
import { useAppStore } from '@/store';
|
||||
|
@ -244,7 +249,7 @@
|
|||
const router = useRouter();
|
||||
const detailDrawerRef = ref<InstanceType<typeof MsDetailDrawer>>();
|
||||
const wrapperRef = ref();
|
||||
const { isFullscreen, toggle } = useFullscreen(wrapperRef);
|
||||
const { isFullScreen, toggleFullScreen } = useFullScreen(wrapperRef);
|
||||
const featureCaseStore = useFeatureCaseStore();
|
||||
const userStore = useUserStore();
|
||||
const { t } = useI18n();
|
||||
|
|
|
@ -7,14 +7,14 @@
|
|||
>
|
||||
</template>
|
||||
<template #operation="{ record }">
|
||||
<MsButton v-if="record.demandPlatform !== pageConfig.platformName" @click="emit('update', record)">{{
|
||||
t('caseManagement.featureCase.cancelAssociation')
|
||||
}}</MsButton>
|
||||
<MsButton v-if="record.demandPlatform === pageConfig.platformName" @click="emit('update', record)">{{
|
||||
t('common.edit')
|
||||
}}</MsButton>
|
||||
<MsButton v-if="record.demandPlatform !== pageConfig.platformName" @click="emit('update', record)">
|
||||
{{ t('caseManagement.featureCase.cancelAssociation') }}
|
||||
</MsButton>
|
||||
<MsButton v-if="record.demandPlatform === pageConfig.platformName" @click="emit('update', record)">
|
||||
{{ t('common.edit') }}
|
||||
</MsButton>
|
||||
</template>
|
||||
<template v-if="(props.funParams.keyword || '').trim() === ''" #empty>
|
||||
<template v-if="(props.funParams.keyword || '').trim() === '' && props.showEmpty" #empty>
|
||||
<div class="flex w-full items-center justify-center">
|
||||
{{ t('caseManagement.caseReview.tableNoData') }}
|
||||
<MsButton class="ml-[8px]" @click="emit('create')">
|
||||
|
@ -53,6 +53,7 @@
|
|||
}; // 列表查询参数
|
||||
isShowOperation?: boolean; // 是否显示操作列
|
||||
highlightName?: boolean; // 是否高亮名称
|
||||
showEmpty?: boolean; // 是否显示自定义的空状态,否则显示表格的默认空状态
|
||||
}>(),
|
||||
{
|
||||
isShowOperation: true,
|
||||
|
|
|
@ -98,11 +98,8 @@
|
|||
{{ t('common.save') }}
|
||||
</a-button></div
|
||||
>
|
||||
<a-form-item
|
||||
field="attachment"
|
||||
:label="props.allowEdit ? t('caseManagement.featureCase.attachment') : '附件列表'"
|
||||
>
|
||||
<div v-if="props.allowEdit" class="flex flex-col">
|
||||
<a-form-item v-if="props.allowEdit" field="attachment" :label="t('caseManagement.featureCase.attachment')">
|
||||
<div class="flex flex-col">
|
||||
<div class="mb-1">
|
||||
<a-dropdown position="tr" trigger="hover">
|
||||
<a-button v-permission="['FUNCTIONAL_CASE:READ+UPDATE']" type="outline">
|
||||
|
@ -119,26 +116,28 @@
|
|||
>
|
||||
<template #upload-button>
|
||||
<a-button type="text" class="!text-[var(--color-text-1)]">
|
||||
<icon-upload />{{ t('caseManagement.featureCase.uploadFile') }}</a-button
|
||||
>
|
||||
<icon-upload />{{ t('caseManagement.featureCase.uploadFile') }}
|
||||
</a-button>
|
||||
</template>
|
||||
</a-upload>
|
||||
<a-button type="text" class="!text-[var(--color-text-1)]" @click="associatedFile">
|
||||
<MsIcon type="icon-icon_link-copy_outlined" size="16" />{{
|
||||
t('caseManagement.featureCase.associatedFile')
|
||||
}}</a-button
|
||||
>
|
||||
<MsIcon type="icon-icon_link-copy_outlined" size="16" />
|
||||
{{ t('caseManagement.featureCase.associatedFile') }}
|
||||
</a-button>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
<div class="!hover:bg-[rgb(var(--primary-1))] !text-[var(--color-text-4)]">{{
|
||||
t('system.orgTemplate.addAttachmentTip')
|
||||
}}</div>
|
||||
<div class="!hover:bg-[rgb(var(--primary-1))] !text-[var(--color-text-4)]">
|
||||
{{ t('system.orgTemplate.addAttachmentTip') }}
|
||||
</div>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<!-- 文件列表开始 -->
|
||||
<div class="w-[90%]">
|
||||
<div v-if="!props.allowEdit" class="mb-[16px] font-medium text-[var(--color-text-1)]">
|
||||
{{ t('caseManagement.featureCase.attachment') }}
|
||||
</div>
|
||||
<MsFileList
|
||||
ref="fileListRef"
|
||||
v-model:file-list="fileList"
|
||||
|
|
|
@ -153,7 +153,7 @@ export default {
|
|||
'caseManagement.featureCase.fullScreen': 'Full screen',
|
||||
'caseManagement.featureCase.more': 'More',
|
||||
'caseManagement.featureCase.basicInfo': 'Basic Info',
|
||||
'caseManagement.featureCase.attachment': 'attachment',
|
||||
'caseManagement.featureCase.attachment': 'Attachment',
|
||||
'caseManagement.featureCase.contentEdit': 'Content Edit',
|
||||
'caseManagement.featureCase.followSuccess': 'Followed Success',
|
||||
'caseManagement.featureCase.cancelFollowSuccess': 'Cancel success',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<MsCard :loading="loading" :min-width="1100" has-breadcrumb hide-footer no-content-padding hide-divider>
|
||||
<MsCard :min-width="1100" has-breadcrumb hide-footer no-content-padding hide-divider show-full-screen>
|
||||
<template #headerLeft>
|
||||
<a-tooltip :content="reviewDetail.name">
|
||||
<div class="one-line-text mr-[8px] max-w-[260px] font-medium text-[var(--color-text-000)]">
|
||||
|
@ -21,275 +21,258 @@
|
|||
{{ t('caseManagement.caseReview.myReview') }}
|
||||
</div>
|
||||
</template>
|
||||
<div class="h-full px-[24px]">
|
||||
<a-divider class="my-0" />
|
||||
<div class="flex h-[calc(100%-1px)] w-full">
|
||||
<div class="h-full w-[356px] border-r border-[var(--color-text-n8)] pr-[16px] pt-[16px]">
|
||||
<div class="mb-[16px] flex">
|
||||
<a-input-search
|
||||
v-model:model-value="keyword"
|
||||
:placeholder="t('caseManagement.caseReview.searchPlaceholder')"
|
||||
allow-clear
|
||||
class="mr-[8px] w-[240px]"
|
||||
@search="loadCaseList"
|
||||
@press-enter="loadCaseList"
|
||||
/>
|
||||
<a-select v-model:model-value="type" :options="typeOptions" class="w-[92px]" @change="loadCaseList">
|
||||
</a-select>
|
||||
</div>
|
||||
<a-spin :loading="caseListLoading" class="w-full">
|
||||
<div class="case-list">
|
||||
<div
|
||||
v-for="item of caseList"
|
||||
:key="item.caseId"
|
||||
:class="['case-item', caseDetail.id === item.caseId ? 'case-item--active' : '']"
|
||||
@click="changeActiveCase(item)"
|
||||
>
|
||||
<div class="mb-[4px] flex items-center justify-between">
|
||||
<div>{{ item.num }}</div>
|
||||
<div class="flex items-center gap-[4px] leading-[22px]">
|
||||
<MsIcon
|
||||
:type="reviewResultMap[item.status]?.icon"
|
||||
:style="{ color: reviewResultMap[item.status]?.color }"
|
||||
/>
|
||||
{{ t(reviewResultMap[item.status]?.label) }}
|
||||
</div>
|
||||
</div>
|
||||
<a-tooltip :content="item.name">
|
||||
<div class="one-line-text">{{ item.name }}</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<MsEmpty v-if="caseList.length === 0" />
|
||||
</div>
|
||||
<MsPagination
|
||||
v-model:page-size="pageNation.pageSize"
|
||||
v-model:current="pageNation.current"
|
||||
:total="pageNation.total"
|
||||
size="mini"
|
||||
simple
|
||||
@change="loadCaseList"
|
||||
@page-size-change="loadCaseList"
|
||||
/>
|
||||
</a-spin>
|
||||
<div class="flex h-full w-full border-t border-[var(--color-text-n8)]">
|
||||
<div class="h-full w-[356px] border-r border-[var(--color-text-n8)] py-[16px] pl-[24px] pr-[16px]">
|
||||
<div class="mb-[16px] flex">
|
||||
<a-input-search
|
||||
v-model:model-value="keyword"
|
||||
:placeholder="t('caseManagement.caseReview.searchPlaceholder')"
|
||||
allow-clear
|
||||
class="mr-[8px] w-[240px]"
|
||||
@search="loadCaseList"
|
||||
@press-enter="loadCaseList"
|
||||
/>
|
||||
<a-select v-model:model-value="type" :options="typeOptions" class="w-[92px]" @change="loadCaseList">
|
||||
</a-select>
|
||||
</div>
|
||||
<div class="relative flex w-[calc(100%-356px)] flex-col">
|
||||
<div class="pl-[16px] pt-[16px]">
|
||||
<div class="rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] p-[16px]">
|
||||
<div class="mb-[12px] flex items-center justify-between">
|
||||
<a-tooltip :content="`【${caseDetail.num}】${caseDetail.name}`">
|
||||
<div
|
||||
class="one-line-text cursor-pointer font-medium text-[rgb(var(--primary-5))]"
|
||||
@click="goCaseDetail"
|
||||
>
|
||||
【{{ caseDetail.num }}】{{ caseDetail.name }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<a-button
|
||||
type="outline"
|
||||
size="mini"
|
||||
class="arco-btn-outline--secondary"
|
||||
@click="editCaseVisible = true"
|
||||
>
|
||||
{{ t('common.edit') }}
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<MsIcon type="icon-icon_folder_filled1" class="mr-[4px] text-[var(--color-text-4)]" />
|
||||
<a-tooltip :content="caseDetail.moduleName || t('common.root')">
|
||||
<div class="one-line-text mr-[8px] max-w-[260px] font-medium text-[var(--color-text-000)]">
|
||||
{{ caseDetail.moduleName || t('common.root') }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<div class="case-detail-label">
|
||||
{{ t('caseManagement.caseReview.caseLevel') }}
|
||||
</div>
|
||||
<div class="case-detail-value">
|
||||
<caseLevel :case-level="caseDetailLevel" />
|
||||
</div>
|
||||
<div class="case-detail-label">
|
||||
{{ t('caseManagement.caseReview.caseVersion') }}
|
||||
</div>
|
||||
<div class="case-detail-value">
|
||||
<MsIcon type="icon-icon_version" size="13" class="mr-[4px]" />
|
||||
{{ caseDetail.versionName }}
|
||||
</div>
|
||||
<div class="case-detail-label">
|
||||
{{ t('caseManagement.caseReview.reviewResult') }}
|
||||
</div>
|
||||
<div class="case-detail-value">
|
||||
<div
|
||||
v-if="reviewResultMap[activeCaseReviewStatus as ReviewResult]"
|
||||
class="flex items-center gap-[4px]"
|
||||
>
|
||||
<MsIcon
|
||||
:type="reviewResultMap[activeCaseReviewStatus as ReviewResult].icon"
|
||||
:style="{
|
||||
color: reviewResultMap[activeCaseReviewStatus as ReviewResult].color,
|
||||
}"
|
||||
/>
|
||||
{{ t(reviewResultMap[activeCaseReviewStatus as ReviewResult].label) }}
|
||||
</div>
|
||||
<a-spin :loading="caseListLoading" class="h-[calc(100%-46px)] w-full">
|
||||
<div class="case-list">
|
||||
<div
|
||||
v-for="item of caseList"
|
||||
:key="item.caseId"
|
||||
:class="['case-item', caseDetail.id === item.caseId ? 'case-item--active' : '']"
|
||||
@click="changeActiveCase(item)"
|
||||
>
|
||||
<div class="mb-[4px] flex items-center justify-between">
|
||||
<div>{{ item.num }}</div>
|
||||
<div class="flex items-center gap-[4px] leading-[22px]">
|
||||
<MsIcon
|
||||
:type="reviewResultMap[item.status]?.icon"
|
||||
:style="{ color: reviewResultMap[item.status]?.color }"
|
||||
/>
|
||||
{{ t(reviewResultMap[item.status]?.label) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-tabs v-model:active-key="showTab" class="no-content">
|
||||
<a-tab-pane :key="tabList[0].key" :title="tabList[0].title" />
|
||||
<a-tab-pane :key="tabList[1].key" :title="tabList[1].title" />
|
||||
<a-tab-pane :key="tabList[2].key">
|
||||
<template #title>
|
||||
<div class="flex items-center">
|
||||
{{ tabList[2].title }}
|
||||
<div
|
||||
:class="`ml-[4px] rounded-full ${
|
||||
showTab === tabList[2].key ? 'bg-[rgb(var(--primary-5))]' : 'bg-[var(--color-text-brand)]'
|
||||
} px-[4px] text-[12px] text-white`"
|
||||
>
|
||||
{{ caseDetail.demandCount > 99 ? '99+' : caseDetail.demandCount }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
<a-divider class="my-0" />
|
||||
<div class="content-center">
|
||||
<MsDescription v-if="showTab === 'baseInfo'" :descriptions="descriptions" label-width="90px" />
|
||||
<div v-else-if="showTab === 'detail'" class="h-full">
|
||||
<MsSplitBox :size="0.8" direction="vertical" min="0" :max="0.99">
|
||||
<template #first>
|
||||
<caseTabDetail :form="caseDetail" :allow-edit="false" />
|
||||
</template>
|
||||
<template #second>
|
||||
<div class="flex h-full flex-col overflow-hidden">
|
||||
<div class="my-[8px] font-medium text-[var(--color-text-1)]">
|
||||
{{ t('caseManagement.caseReview.reviewHistory') }}
|
||||
</div>
|
||||
<div class="review-history-list">
|
||||
<a-spin :loading="reviewHistoryListLoading" class="h-full w-full">
|
||||
<div v-for="item of reviewHistoryList" :key="item.id" class="mb-[16px]">
|
||||
<div class="flex items-center">
|
||||
<MSAvatar :avatar="item.userLogo" />
|
||||
<div class="ml-[8px] flex items-center">
|
||||
<div class="font-medium text-[var(--color-text-1)]">{{ item.userName }}</div>
|
||||
<a-divider direction="vertical" margin="8px"></a-divider>
|
||||
<div v-if="item.status === 'PASS'" class="flex items-center">
|
||||
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
|
||||
{{ t('caseManagement.caseReview.pass') }}
|
||||
</div>
|
||||
<div v-else-if="item.status === 'UN_PASS'" class="flex items-center">
|
||||
<MsIcon type="icon-icon_close_filled" class="mr-[4px] text-[rgb(var(--danger-6))]" />
|
||||
{{ t('caseManagement.caseReview.fail') }}
|
||||
</div>
|
||||
<div v-else-if="item.status === 'UNDER_REVIEWED'" class="flex items-center">
|
||||
<MsIcon type="icon-icon_warning_filled" class="mr-[4px] text-[rgb(var(--warning-6))]" />
|
||||
{{ t('caseManagement.caseReview.suggestion') }}
|
||||
</div>
|
||||
<div v-else-if="item.status === 'RE_REVIEWED'" class="flex items-center">
|
||||
<MsIcon
|
||||
type="icon-icon_resubmit_filled"
|
||||
class="mr-[4px] text-[rgb(var(--warning-6))]"
|
||||
/>
|
||||
{{ t('caseManagement.caseReview.reReview') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-[48px] text-[var(--color-text-2)]" v-html="item.contentText"></div>
|
||||
<div class="ml-[48px] mt-[8px] text-[var(--color-text-4)]">
|
||||
{{ dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss') }}
|
||||
</div>
|
||||
</div>
|
||||
<MsEmpty v-if="reviewHistoryList.length === 0" />
|
||||
</a-spin>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MsSplitBox>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="flex items-center justify-between">
|
||||
{{ t('caseManagement.caseReview.demandCases') }}
|
||||
<a-input-search
|
||||
v-model="demandKeyword"
|
||||
:placeholder="t('caseManagement.caseReview.demandSearchPlaceholder')"
|
||||
allow-clear
|
||||
class="w-[300px]"
|
||||
@press-enter="searchDemand"
|
||||
@search="searchDemand"
|
||||
/>
|
||||
</div>
|
||||
<caseTabDemand
|
||||
ref="caseDemandRef"
|
||||
:fun-params="{ caseId: route.query.caseId as string, keyword: demandKeyword,projectId:appStore.currentProjectId }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-footer">
|
||||
<div class="mb-[16px] flex items-center">
|
||||
<div class="font-medium text-[var(--color-text-1)]">
|
||||
{{ t('caseManagement.caseReview.startReview') }}
|
||||
</div>
|
||||
<a-switch v-model:model-value="autoNext" class="mx-[8px]" size="small" type="line" />
|
||||
<div class="text-[var(--color-text-4)]">{{ t('caseManagement.caseReview.autoNext') }}</div>
|
||||
<a-tooltip position="right">
|
||||
<template #content>
|
||||
<div>{{ t('caseManagement.caseReview.autoNextTip1') }}</div>
|
||||
<div>{{ t('caseManagement.caseReview.autoNextTip2') }}</div>
|
||||
</template>
|
||||
<icon-question-circle
|
||||
class="mb-[2px] ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
<a-tooltip :content="item.name">
|
||||
<div class="one-line-text">{{ item.name }}</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<a-form ref="dialogFormRef" :model="caseResultForm" layout="vertical">
|
||||
<a-form-item field="reason" :label="t('caseManagement.caseReview.reviewResult')" class="mb-[8px]">
|
||||
<a-radio-group v-model:model-value="caseResultForm.result" @change="() => dialogFormRef?.resetFields()">
|
||||
<a-radio value="PASS">
|
||||
<div class="inline-flex items-center">
|
||||
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
|
||||
{{ t('caseManagement.caseReview.pass') }}
|
||||
<MsEmpty v-if="caseList.length === 0" />
|
||||
</div>
|
||||
<MsPagination
|
||||
v-model:page-size="pageNation.pageSize"
|
||||
v-model:current="pageNation.current"
|
||||
:total="pageNation.total"
|
||||
size="mini"
|
||||
simple
|
||||
@change="loadCaseList"
|
||||
@page-size-change="loadCaseList"
|
||||
/>
|
||||
</a-spin>
|
||||
</div>
|
||||
<a-spin :loading="caseDetailLoading" class="relative flex flex-1 flex-col">
|
||||
<div class="content-center">
|
||||
<div class="rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] p-[16px]">
|
||||
<div class="mb-[12px] flex items-center justify-between">
|
||||
<a-tooltip :content="`【${caseDetail.num}】${caseDetail.name}`">
|
||||
<div
|
||||
class="one-line-text cursor-pointer font-medium text-[rgb(var(--primary-5))]"
|
||||
@click="goCaseDetail"
|
||||
>
|
||||
【{{ caseDetail.num }}】{{ caseDetail.name }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<a-button type="outline" size="mini" class="arco-btn-outline--secondary" @click="editCaseVisible = true">
|
||||
{{ t('common.edit') }}
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<MsIcon type="icon-icon_folder_filled1" class="mr-[4px] text-[var(--color-text-4)]" />
|
||||
<a-tooltip :content="caseDetail.moduleName || t('common.root')">
|
||||
<div class="one-line-text mr-[8px] max-w-[260px] font-medium text-[var(--color-text-000)]">
|
||||
{{ caseDetail.moduleName || t('common.root') }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<div class="case-detail-label">
|
||||
{{ t('caseManagement.caseReview.caseLevel') }}
|
||||
</div>
|
||||
<div class="case-detail-value">
|
||||
<caseLevel :case-level="caseDetailLevel" />
|
||||
</div>
|
||||
<div class="case-detail-label">
|
||||
{{ t('caseManagement.caseReview.caseVersion') }}
|
||||
</div>
|
||||
<div class="case-detail-value">
|
||||
<MsIcon type="icon-icon_version" size="13" class="mr-[4px]" />
|
||||
{{ caseDetail.versionName }}
|
||||
</div>
|
||||
<div class="case-detail-label">
|
||||
{{ t('caseManagement.caseReview.reviewResult') }}
|
||||
</div>
|
||||
<div class="case-detail-value">
|
||||
<div v-if="reviewResultMap[activeCaseReviewStatus as ReviewResult]" class="flex items-center gap-[4px]">
|
||||
<MsIcon
|
||||
:type="reviewResultMap[activeCaseReviewStatus as ReviewResult].icon"
|
||||
:style="{
|
||||
color: reviewResultMap[activeCaseReviewStatus as ReviewResult].color,
|
||||
}"
|
||||
/>
|
||||
{{ t(reviewResultMap[activeCaseReviewStatus as ReviewResult].label) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-tabs v-model:active-key="showTab" class="no-content">
|
||||
<a-tab-pane :key="tabList[0].key" :title="tabList[0].title" />
|
||||
<a-tab-pane :key="tabList[1].key" :title="tabList[1].title" />
|
||||
<a-tab-pane :key="tabList[2].key">
|
||||
<template #title>
|
||||
<div class="flex items-center">
|
||||
{{ tabList[2].title }}
|
||||
<div
|
||||
v-if="caseDetail.demandCount > 0"
|
||||
:class="`ml-[4px] rounded-full ${
|
||||
showTab === tabList[2].key ? 'bg-[rgb(var(--primary-5))]' : 'bg-[var(--color-text-brand)]'
|
||||
} px-[4px] text-[12px] text-white`"
|
||||
>
|
||||
{{ caseDetail.demandCount > 99 ? '99+' : caseDetail.demandCount }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane :key="tabList[3].key" :title="tabList[3].title" />
|
||||
</a-tabs>
|
||||
<a-divider class="my-0" />
|
||||
<MsDescription
|
||||
v-if="showTab === 'baseInfo'"
|
||||
:descriptions="descriptions"
|
||||
label-width="90px"
|
||||
class="mt-[16px]"
|
||||
/>
|
||||
<div v-else-if="showTab === 'detail'" class="mt-[16px] h-full">
|
||||
<caseTabDetail :form="caseDetail" :allow-edit="false" />
|
||||
</div>
|
||||
<div v-else-if="showTab === 'demand'">
|
||||
<div class="mt-[16px] flex items-center justify-between">
|
||||
{{ t('caseManagement.caseReview.demandCases') }}
|
||||
<a-input-search
|
||||
v-model="demandKeyword"
|
||||
:placeholder="t('caseManagement.caseReview.demandSearchPlaceholder')"
|
||||
allow-clear
|
||||
class="w-[300px]"
|
||||
@press-enter="searchDemand"
|
||||
@search="searchDemand"
|
||||
/>
|
||||
</div>
|
||||
<caseTabDemand
|
||||
ref="caseDemandRef"
|
||||
:fun-params="{ projectId: appStore.currentProjectId, caseId: route.query.caseId as string, keyword: demandKeyword }"
|
||||
:show-empty="false"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="flex h-full flex-col overflow-hidden">
|
||||
<div class="review-history-list">
|
||||
<a-spin :loading="reviewHistoryListLoading" class="h-full w-full">
|
||||
<div v-for="item of reviewHistoryList" :key="item.id" class="mb-[16px]">
|
||||
<div class="flex items-center">
|
||||
<MSAvatar :avatar="item.userLogo" />
|
||||
<div class="ml-[8px] flex items-center">
|
||||
<div class="font-medium text-[var(--color-text-1)]">{{ item.userName }}</div>
|
||||
<a-divider direction="vertical" margin="8px"></a-divider>
|
||||
<div v-if="item.status === 'PASS'" class="flex items-center">
|
||||
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
|
||||
{{ t('caseManagement.caseReview.pass') }}
|
||||
</div>
|
||||
<div v-else-if="item.status === 'UN_PASS'" class="flex items-center">
|
||||
<MsIcon type="icon-icon_close_filled" class="mr-[4px] text-[rgb(var(--danger-6))]" />
|
||||
{{ t('caseManagement.caseReview.fail') }}
|
||||
</div>
|
||||
<div v-else-if="item.status === 'UNDER_REVIEWED'" class="flex items-center">
|
||||
<MsIcon type="icon-icon_warning_filled" class="mr-[4px] text-[rgb(var(--warning-6))]" />
|
||||
{{ t('caseManagement.caseReview.suggestion') }}
|
||||
</div>
|
||||
<div v-else-if="item.status === 'RE_REVIEWED'" class="flex items-center">
|
||||
<MsIcon type="icon-icon_resubmit_filled" class="mr-[4px] text-[rgb(var(--warning-6))]" />
|
||||
{{ t('caseManagement.caseReview.reReview') }}
|
||||
</div>
|
||||
</div>
|
||||
</a-radio>
|
||||
<a-radio value="UN_PASS">
|
||||
<div class="inline-flex items-center">
|
||||
<MsIcon type="icon-icon_close_filled" class="mr-[4px] text-[rgb(var(--danger-6))]" />
|
||||
{{ t('caseManagement.caseReview.fail') }}
|
||||
</div>
|
||||
</a-radio>
|
||||
<a-radio value="UNDER_REVIEWED">
|
||||
<div class="inline-flex items-center">
|
||||
<MsIcon type="icon-icon_warning_filled" class="mr-[4px] text-[rgb(var(--warning-6))]" />
|
||||
{{ t('caseManagement.caseReview.suggestion') }}
|
||||
</div>
|
||||
<a-tooltip :content="t('caseManagement.caseReview.suggestionTip')" position="right">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] mt-[2px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="reason"
|
||||
:label="t('caseManagement.caseReview.reason')"
|
||||
:rules="
|
||||
caseResultForm.result === 'UN_PASS'
|
||||
? [{ required: true, message: t('caseManagement.caseReview.reasonRequired') }]
|
||||
: []
|
||||
"
|
||||
asterisk-position="end"
|
||||
class="mb-0"
|
||||
>
|
||||
<MsRichText v-model:modelValue="caseResultForm.reason" class="w-full" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-button type="primary" class="mt-[16px]" :loading="submitReviewLoading" @click="submitReview">
|
||||
{{ t('caseManagement.caseReview.submitReview') }}
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="ml-[48px] text-[var(--color-text-2)]" v-html="item.contentText"></div>
|
||||
<div class="ml-[48px] mt-[8px] text-[var(--color-text-4)]">
|
||||
{{ dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss') }}
|
||||
</div>
|
||||
</div>
|
||||
<MsEmpty v-if="reviewHistoryList.length === 0" />
|
||||
</a-spin>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-footer">
|
||||
<div class="mb-[16px] flex items-center">
|
||||
<div class="font-medium text-[var(--color-text-1)]">
|
||||
{{ t('caseManagement.caseReview.startReview') }}
|
||||
</div>
|
||||
<a-switch v-model:model-value="autoNext" class="mx-[8px]" size="small" type="line" />
|
||||
<div class="text-[var(--color-text-4)]">{{ t('caseManagement.caseReview.autoNext') }}</div>
|
||||
<a-tooltip position="right">
|
||||
<template #content>
|
||||
<div>{{ t('caseManagement.caseReview.autoNextTip1') }}</div>
|
||||
<div>{{ t('caseManagement.caseReview.autoNextTip2') }}</div>
|
||||
</template>
|
||||
<icon-question-circle
|
||||
class="mb-[2px] ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<a-form ref="dialogFormRef" :model="caseResultForm" layout="vertical">
|
||||
<a-form-item field="reason" :label="t('caseManagement.caseReview.reviewResult')" class="mb-[8px]">
|
||||
<a-radio-group v-model:model-value="caseResultForm.result" @change="() => dialogFormRef?.resetFields()">
|
||||
<a-radio value="PASS">
|
||||
<div class="inline-flex items-center">
|
||||
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
|
||||
{{ t('caseManagement.caseReview.pass') }}
|
||||
</div>
|
||||
</a-radio>
|
||||
<a-radio value="UN_PASS">
|
||||
<div class="inline-flex items-center">
|
||||
<MsIcon type="icon-icon_close_filled" class="mr-[4px] text-[rgb(var(--danger-6))]" />
|
||||
{{ t('caseManagement.caseReview.fail') }}
|
||||
</div>
|
||||
</a-radio>
|
||||
<a-radio value="UNDER_REVIEWED">
|
||||
<div class="inline-flex items-center">
|
||||
<MsIcon type="icon-icon_warning_filled" class="mr-[4px] text-[rgb(var(--warning-6))]" />
|
||||
{{ t('caseManagement.caseReview.suggestion') }}
|
||||
</div>
|
||||
<a-tooltip :content="t('caseManagement.caseReview.suggestionTip')" position="right">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] mt-[2px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="reason"
|
||||
:label="t('caseManagement.caseReview.reason')"
|
||||
:rules="
|
||||
caseResultForm.result === 'UN_PASS' || caseResultForm.result === 'UNDER_REVIEWED'
|
||||
? [{ required: true, message: t('caseManagement.caseReview.reasonRequired') }]
|
||||
: []
|
||||
"
|
||||
asterisk-position="end"
|
||||
class="mb-0"
|
||||
>
|
||||
<MsRichText v-model:raw="caseResultForm.reason" class="w-full" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-button type="primary" class="mt-[16px]" :loading="submitReviewLoading" @click="submitReview">
|
||||
{{ t('caseManagement.caseReview.submitReview') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</a-spin>
|
||||
</div>
|
||||
</MsCard>
|
||||
<MsDrawer
|
||||
|
@ -455,9 +438,11 @@
|
|||
activeCaseId.value = item.caseId;
|
||||
}
|
||||
}
|
||||
const caseDetailLoading = ref(false);
|
||||
// 加载用例详情
|
||||
async function loadCaseDetail() {
|
||||
try {
|
||||
caseDetailLoading.value = true;
|
||||
const res = await getCaseDetail(activeCaseId.value);
|
||||
caseDetail.value = res;
|
||||
descriptions.value = [
|
||||
|
@ -467,12 +452,19 @@
|
|||
},
|
||||
// 解析用例模版的自定义字段
|
||||
...res.customFields.map((e) => {
|
||||
const val =
|
||||
typeof e.defaultValue === 'string' && e.defaultValue !== '' ? JSON.parse(e.defaultValue) : e.defaultValue;
|
||||
return {
|
||||
label: e.fieldName,
|
||||
value: Array.isArray(val) ? val.join('、') : val,
|
||||
};
|
||||
try {
|
||||
const val =
|
||||
typeof e.defaultValue === 'string' && e.defaultValue !== '' ? JSON.parse(e.defaultValue) : e.defaultValue;
|
||||
return {
|
||||
label: e.fieldName,
|
||||
value: Array.isArray(val) ? val.join('、') : val,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
label: e.fieldName,
|
||||
value: e.defaultValue,
|
||||
};
|
||||
}
|
||||
}),
|
||||
{
|
||||
label: t('caseManagement.caseReview.creator'),
|
||||
|
@ -486,6 +478,8 @@
|
|||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
caseDetailLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -503,6 +497,10 @@
|
|||
key: 'demand',
|
||||
title: t('caseManagement.caseReview.caseDemand'),
|
||||
},
|
||||
{
|
||||
key: 'reviewHistory',
|
||||
title: t('caseManagement.caseReview.reviewHistory'),
|
||||
},
|
||||
]);
|
||||
|
||||
const reviewHistoryListLoading = ref(false);
|
||||
|
@ -526,9 +524,7 @@
|
|||
() => activeCaseId.value,
|
||||
() => {
|
||||
loadCaseDetail();
|
||||
if (showTab.value === 'detail') {
|
||||
initReviewHistoryList();
|
||||
}
|
||||
initReviewHistoryList();
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -577,14 +573,21 @@
|
|||
reason: '',
|
||||
};
|
||||
if (autoNext.value) {
|
||||
// 自动下一个,更改激活的 id会刷新详情
|
||||
const index = caseList.value.findIndex((e) => e.caseId === activeCaseId.value);
|
||||
if (index < caseList.value.length - 1) {
|
||||
activeCaseId.value = caseList.value[index + 1].caseId;
|
||||
} else {
|
||||
// 当前是最后一个,刷新数据
|
||||
loadCaseDetail();
|
||||
initReviewHistoryList();
|
||||
}
|
||||
} else {
|
||||
// 不自动下一个才请求详情
|
||||
loadCaseDetail();
|
||||
initReviewHistoryList();
|
||||
}
|
||||
loadCaseList();
|
||||
loadCaseDetail();
|
||||
initReviewHistoryList();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
|
@ -654,8 +657,10 @@
|
|||
.case-list {
|
||||
.ms-scroll-bar();
|
||||
|
||||
overflow-y: auto;
|
||||
margin-bottom: 16px;
|
||||
padding: 16px;
|
||||
height: calc(100% - 46px);
|
||||
border-radius: var(--border-radius-small);
|
||||
background: var(--color-text-n9);
|
||||
.case-item {
|
||||
|
@ -691,21 +696,18 @@
|
|||
@apply flex-1 overflow-auto;
|
||||
.ms-scroll-bar();
|
||||
|
||||
padding: 16px 0 16px 16px;
|
||||
padding: 16px;
|
||||
.review-history-list {
|
||||
@apply overflow-auto;
|
||||
.ms-scroll-bar();
|
||||
@apply h-full;
|
||||
|
||||
padding: 16px 0 16px 16px;
|
||||
}
|
||||
}
|
||||
.content-footer {
|
||||
padding: 16px;
|
||||
width: calc(100% + 32px);
|
||||
box-shadow: 0 -1px 4px 0 rgb(31 35 41 / 10%);
|
||||
:deep(.arco-radio-label) {
|
||||
@apply inline-flex;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@/config/caseManagement
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
:get-modules-func="getCaseModuleTree"
|
||||
:get-table-func="getCaseList"
|
||||
:confirm-loading="confirmLoading"
|
||||
:associated-ids="associatedIds"
|
||||
:associated-ids="[]"
|
||||
:type="RequestModuleEnum.CASE_MANAGEMENT"
|
||||
@close="emit('close')"
|
||||
@save="saveHandler"
|
||||
|
@ -90,10 +90,12 @@
|
|||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
project: string;
|
||||
// associatedIds: string[];
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', val: boolean): void;
|
||||
(e: 'update:project', val: string): void;
|
||||
(e: 'update:associatedIds', val: string[]): void;
|
||||
(e: 'success', val: BaseAssociateCaseRequest & { reviewers: string[] }): void;
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
@ -136,7 +138,7 @@
|
|||
|
||||
const currentSelectCase = ref<string | number | Record<string, any> | undefined>('');
|
||||
|
||||
const associatedIds = ref<string[]>([]);
|
||||
// const associatedIds = useVModel(props, 'associatedIds', emit);
|
||||
const confirmLoading = ref<boolean>(false);
|
||||
|
||||
function saveHandler(params: BaseAssociateCaseRequest) {
|
||||
|
@ -144,7 +146,7 @@
|
|||
if (!errors) {
|
||||
try {
|
||||
confirmLoading.value = true;
|
||||
associatedIds.value = [...params.selectIds];
|
||||
// associatedIds.value = [...params.selectIds];
|
||||
emit('success', { ...params, reviewers: associateForm.value.reviewers });
|
||||
innerVisible.value = false;
|
||||
} catch (error) {
|
||||
|
|
|
@ -44,10 +44,10 @@
|
|||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<template #name="{ record }">
|
||||
<a-tooltip :content="record.name">
|
||||
<template #num="{ record }">
|
||||
<a-tooltip :content="record.num">
|
||||
<a-button type="text" class="px-0" @click="review(record)">
|
||||
<div class="one-line-text max-w-[168px]">{{ record.name }}</div>
|
||||
<div class="one-line-text max-w-[168px]">{{ record.num }}</div>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
@ -86,7 +86,7 @@
|
|||
</MsPopconfirm>
|
||||
</template>
|
||||
<template v-if="keyword.trim() === ''" #empty>
|
||||
<div class="flex items-center justify-center p-[8px] text-[var(--color-text-4)]">
|
||||
<div class="flex w-full items-center justify-center p-[8px] text-[var(--color-text-4)]">
|
||||
{{ t('caseManagement.caseReview.tableNoData') }}
|
||||
<MsButton class="ml-[8px]" @click="createCase">
|
||||
{{ t('caseManagement.caseReview.crateCase') }}
|
||||
|
@ -158,11 +158,7 @@
|
|||
asterisk-position="end"
|
||||
class="mb-0"
|
||||
>
|
||||
<!-- <a-input
|
||||
v-model:model-value="dialogForm.reason"
|
||||
:placeholder="t('caseManagement.caseReview.reasonPlaceholder')"
|
||||
/> -->
|
||||
<MsRichText v-model:modelValue="dialogForm.reason" class="w-full" />
|
||||
<MsRichText v-model:raw="dialogForm.reason" class="w-full" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
v-if="dialogShowType === 'changeReviewer'"
|
||||
|
@ -298,17 +294,19 @@
|
|||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'num',
|
||||
slotName: 'num',
|
||||
sortIndex: 1,
|
||||
showTooltip: true,
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.caseReview.caseName',
|
||||
slotName: 'name',
|
||||
dataIndex: 'name',
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
showTooltip: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
|
@ -317,6 +315,7 @@
|
|||
slotName: 'reviewNames',
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
width: 150,
|
||||
},
|
||||
|
@ -352,6 +351,7 @@
|
|||
{
|
||||
scroll: { x: '100%' },
|
||||
tableKey: TableKeyEnum.CASE_MANAGEMENT_REVIEW_CASE,
|
||||
heightUsed: 484,
|
||||
showSetting: true,
|
||||
selectable: true,
|
||||
showSelectAll: true,
|
||||
|
|
|
@ -56,7 +56,6 @@
|
|||
|
||||
import { getReviewDetailModuleTree } from '@/api/modules/case-management/caseReview';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { mapTree } from '@/utils';
|
||||
|
||||
import { ModuleTreeNode } from '@/models/projectManagement/file';
|
||||
|
@ -70,7 +69,6 @@
|
|||
const emit = defineEmits(['init', 'folderNodeSelect']);
|
||||
|
||||
const route = useRoute();
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const virtualListProps = computed(() => {
|
||||
|
@ -111,7 +109,7 @@
|
|||
async function initModules() {
|
||||
try {
|
||||
loading.value = true;
|
||||
const res = await getReviewDetailModuleTree(appStore.currentProjectId, route.query.id as string);
|
||||
const res = await getReviewDetailModuleTree(route.query.id as string);
|
||||
folderTree.value = mapTree<ModuleTreeNode>(res, (node) => {
|
||||
return {
|
||||
...node,
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
<a-button
|
||||
type="primary"
|
||||
status="danger"
|
||||
:disabled="confirmReviewName !== props.record.name"
|
||||
:disabled="confirmReviewName !== props.record.name || loading"
|
||||
class="ml-[12px]"
|
||||
@click="handleDeleteConfirm"
|
||||
>
|
||||
|
@ -39,6 +39,7 @@
|
|||
</a-button>
|
||||
<a-button
|
||||
v-if="props.record.status === 'COMPLETED'"
|
||||
:loading="loading"
|
||||
type="primary"
|
||||
class="ml-[12px]"
|
||||
@click="handleDeleteConfirm"
|
||||
|
@ -79,6 +80,7 @@
|
|||
|
||||
const dialogVisible = useVModel(props, 'visible', emit);
|
||||
const confirmReviewName = ref('');
|
||||
const loading = ref(false);
|
||||
|
||||
function handleDialogCancel() {
|
||||
dialogVisible.value = false;
|
||||
|
@ -90,6 +92,7 @@
|
|||
*/
|
||||
async function handleDeleteConfirm() {
|
||||
try {
|
||||
loading.value = true;
|
||||
await deleteReview(props.record.id, appStore.currentProjectId);
|
||||
Message.success(t('common.deleteSuccess'));
|
||||
dialogVisible.value = false;
|
||||
|
@ -97,9 +100,10 @@
|
|||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@/config/caseManagement
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
trigger="click"
|
||||
@popup-visible-change="handleFilterHidden"
|
||||
>
|
||||
<a-button type="text" class="arco-btn-text--secondary" @click="statusFilterVisible = true">
|
||||
<a-button type="text" class="arco-btn-text--secondary p-[8px_4px]" @click="statusFilterVisible = true">
|
||||
{{ t(columnConfig.title as string) }}
|
||||
<icon-down :class="statusFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
|
||||
</a-button>
|
||||
|
@ -67,10 +67,10 @@
|
|||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<template #name="{ record }">
|
||||
<a-tooltip :content="record.name">
|
||||
<template #num="{ record }">
|
||||
<a-tooltip :content="`${record.num}`">
|
||||
<a-button type="text" class="px-0" @click="openDetail(record.id)">
|
||||
<div class="one-line-text max-w-[168px]">{{ record.name }}</div>
|
||||
<div class="one-line-text max-w-[168px]">{{ record.num }}</div>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
@ -109,7 +109,7 @@
|
|||
<MsTableMoreAction :list="getMoreAction(record.status)" @select="handleMoreActionSelect($event, record)" />
|
||||
</template>
|
||||
<template v-if="keyword.trim() === ''" #empty>
|
||||
<div class="flex items-center justify-center p-[8px] text-[var(--color-text-4)]">
|
||||
<div class="flex w-full items-center justify-center p-[8px] text-[var(--color-text-4)]">
|
||||
{{ t('caseManagement.caseReview.tableNoData') }}
|
||||
<MsButton class="ml-[8px]" @click="() => emit('goCreate')">
|
||||
{{ t('caseManagement.caseReview.create') }}
|
||||
|
@ -339,17 +339,19 @@
|
|||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'num',
|
||||
slotName: 'num',
|
||||
sortIndex: 1,
|
||||
showTooltip: true,
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.caseReview.name',
|
||||
slotName: 'name',
|
||||
dataIndex: 'name',
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
showTooltip: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
|
@ -382,6 +384,7 @@
|
|||
dataIndex: 'reviewers',
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
width: 150,
|
||||
},
|
||||
|
@ -677,4 +680,3 @@
|
|||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@/config/caseManagement
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
<div>{{ t('caseManagement.caseReview.reReview') }}</div>
|
||||
</td>
|
||||
<td class="popover-value-td">
|
||||
{{ props.reviewDetail.reviewedCount }}
|
||||
{{ props.reviewDetail.reReviewedCount }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -66,7 +66,7 @@
|
|||
reviewDetail: {
|
||||
passCount: number;
|
||||
unPassCount: number;
|
||||
reviewedCount: number;
|
||||
reReviewedCount: number;
|
||||
underReviewedCount: number;
|
||||
caseCount: number;
|
||||
[key: string]: any;
|
||||
|
@ -81,7 +81,7 @@
|
|||
props.reviewDetail.status === 'PREPARED' ||
|
||||
(props.reviewDetail.passCount === 0 &&
|
||||
props.reviewDetail.unPassCount === 0 &&
|
||||
props.reviewDetail.reviewedCount === 0 &&
|
||||
props.reviewDetail.reReviewedCount === 0 &&
|
||||
props.reviewDetail.underReviewedCount === 0)
|
||||
) {
|
||||
return [
|
||||
|
@ -101,7 +101,7 @@
|
|||
color: 'rgb(var(--danger-6))',
|
||||
},
|
||||
{
|
||||
percentage: (props.reviewDetail.reviewedCount / props.reviewDetail.caseCount) * 100,
|
||||
percentage: (props.reviewDetail.reReviewedCount / props.reviewDetail.caseCount) * 100,
|
||||
color: 'rgb(var(--warning-6))',
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
<template>
|
||||
<MsCard :loading="loading" :min-width="1100" auto-height hide-footer no-bottom-radius no-content-padding hide-divider>
|
||||
<MsCard
|
||||
:loading="loading"
|
||||
:header-min-width="1100"
|
||||
:min-width="150"
|
||||
auto-height
|
||||
hide-footer
|
||||
no-bottom-radius
|
||||
no-content-padding
|
||||
hide-divider
|
||||
>
|
||||
<template #headerLeft>
|
||||
<a-tooltip :content="reviewDetail.name">
|
||||
<div class="one-line-text mr-[8px] max-w-[260px] font-medium text-[var(--color-text-000)]">
|
||||
|
@ -284,11 +293,11 @@
|
|||
eventTag: 'createCase',
|
||||
icon: 'icon-icon_add_outlined-1',
|
||||
},
|
||||
{
|
||||
label: t('caseManagement.caseReview.createTestPlan'),
|
||||
eventTag: 'createTestPlan',
|
||||
icon: 'icon-icon_add_outlined-1',
|
||||
},
|
||||
// {
|
||||
// label: t('caseManagement.caseReview.createTestPlan'),
|
||||
// eventTag: 'createTestPlan',
|
||||
// icon: 'icon-icon_add_outlined-1',
|
||||
// },
|
||||
{
|
||||
isDivider: true,
|
||||
},
|
||||
|
|
|
@ -13,10 +13,10 @@
|
|||
<!-- TOTO 第一版本暂时只考虑普通登录 -->
|
||||
<!-- <a-form-item class="login-form-item" field="radio" hide-label>
|
||||
<a-radio-group v-model="userInfo.authenticate" type="button">
|
||||
<a-radio value="LOCAL">普通登陆</a-radio>
|
||||
<a-radio v-xpack value="LDAP">LDAP</a-radio>
|
||||
<a-radio v-xpack value="OAuth2">OAuth2 测试</a-radio>
|
||||
<a-radio v-xpack value="OIDC 90">OIDC 90</a-radio>
|
||||
<a-radio value="LOCAL">{{ t('login.form.normalLogin') }}</a-radio>
|
||||
<a-radio value="LDAP">LDAP</a-radio>
|
||||
<a-radio value="OAuth2">{{ t('login.form.oauth2Test') }}</a-radio>
|
||||
<a-radio value="OIDC 90">OIDC 90</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item> -->
|
||||
<a-form-item
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export default {
|
||||
'login.form.title': 'Login to MeterSphere',
|
||||
'login.form.title': 'One-stop open source continuous testing platform',
|
||||
'login.form.userName.errMsg': 'Username cannot be empty',
|
||||
'login.form.password.errMsg': 'Password cannot be empty',
|
||||
'login.form.login.errMsg': 'Login error, refresh and try again',
|
||||
|
@ -10,5 +10,6 @@ export default {
|
|||
'login.form.forgetPassword': 'Forgot password',
|
||||
'login.form.login': 'login',
|
||||
'login.form.register': 'register account',
|
||||
'login.banner.slogan1': 'Out-of-the-box high-quality template',
|
||||
'login.form.normalLogin': 'Normal login',
|
||||
'login.form.oauth2Test': 'OAuth2 Test',
|
||||
};
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
export default {
|
||||
'login.form.title': '一站式开源持续测试平台',
|
||||
'login.form.userName.errMsg': '用户名不能为空',
|
||||
'login.form.userName.errMsg': '邮箱不能为空',
|
||||
'login.form.password.errMsg': '密码不能为空',
|
||||
'login.form.login.errMsg': '登录出错,请刷新重试',
|
||||
'login.form.login.success': '欢迎使用',
|
||||
'login.form.userName.placeholder': '用户名',
|
||||
'login.form.userName.placeholder': '请输入邮箱登录',
|
||||
'login.form.password.placeholder': '密码',
|
||||
'login.form.rememberPassword': '记住密码',
|
||||
'login.form.forgetPassword': '忘记密码',
|
||||
'login.form.login': '登录',
|
||||
'login.form.register': '注册账号',
|
||||
'login.banner.slogan': '开箱即用的高质量模板',
|
||||
'login.form.normalLogin': '普通登录',
|
||||
'login.form.oauth2Test': 'OAuth2 测试',
|
||||
};
|
||||
|
|
|
@ -67,11 +67,17 @@
|
|||
<div class="mb-[16px] h-[102px] w-[102px]">
|
||||
<a-spin class="h-full w-full" :loading="fileLoading">
|
||||
<MsThumbnailCard
|
||||
mode="hover"
|
||||
:mode="detail.storage === 'GIT' ? 'default' : 'hover'"
|
||||
:type="detail.fileType || ''"
|
||||
:url="`${CompressImgUrl}/${userStore.id}/${detail.id}`"
|
||||
:footer-text="t('project.fileManagement.replaceFile')"
|
||||
@click="handleFileIconClick"
|
||||
:footer-text="detail.storage === 'GIT' ? undefined : t('project.fileManagement.replaceFile')"
|
||||
@click="
|
||||
() => {
|
||||
if (detail.storage !== 'GIT') {
|
||||
handleFileIconClick();
|
||||
}
|
||||
}
|
||||
"
|
||||
/>
|
||||
</a-spin>
|
||||
</div>
|
||||
|
|
|
@ -31,8 +31,8 @@
|
|||
>
|
||||
<template #title="nodeData">
|
||||
<div class="inline-flex w-full">
|
||||
<div class="one-line-text w-[calc(100%-32px)] text-[var(--color-text-1)]">{{ nodeData.name }}</div>
|
||||
<div v-if="!props.isModal" class="ml-[4px] text-[var(--color-text-4)]">({{ nodeData.count || 0 }})</div>
|
||||
<div class="one-line-text text-[var(--color-text-1)]">{{ nodeData.name }}</div>
|
||||
<div v-if="!props.isModal" class="ml-auto text-[var(--color-text-4)]">({{ nodeData.count || 0 }})</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="!props.isModal" #extra="nodeData">
|
||||
|
|
|
@ -83,7 +83,7 @@
|
|||
/>
|
||||
</template>
|
||||
<template v-if="keyword.trim() === ''" #empty>
|
||||
<div class="flex items-center justify-center p-[8px] text-[var(--color-text-4)]">
|
||||
<div class="flex w-full items-center justify-center p-[8px] text-[var(--color-text-4)]">
|
||||
{{ t('project.fileManagement.tableNoFile') }}
|
||||
<MsButton class="ml-[8px]" @click="handleAddClick">
|
||||
{{ t('project.fileManagement.addFile') }}
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
<div class="folder-count">({{ myFileCount }})</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="folder">
|
||||
<div :class="getFolderClass('all')" @click="setActiveFolder('all')">
|
||||
<div class="folder" @click="setActiveFolder('all')">
|
||||
<div :class="getFolderClass('all')">
|
||||
<MsIcon type="icon-icon_folder_filled1" class="folder-icon" />
|
||||
<div class="folder-name">{{ t('project.fileManagement.allFile') }}</div>
|
||||
<div class="folder-count">({{ allFileCount }})</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<MsCard ref="fullRef" :special-height="132" :is-fullscreen="isFullscreen" simple>
|
||||
<MsCard ref="fullRef" :special-height="132" :is-fullscreen="isFullScreen" simple>
|
||||
<div id="mscard">
|
||||
<div class="mb-[16px] flex items-center justify-between">
|
||||
<div class="font-medium text-[var(--color-text-000)]">{{ t('project.messageManagement.config') }}</div>
|
||||
|
@ -14,7 +14,7 @@
|
|||
:multiple="true"
|
||||
:has-all-select="true"
|
||||
:default-all-select="true"
|
||||
:popup-container="isFullscreen ? '#mscard' : undefined"
|
||||
:popup-container="isFullScreen ? '#mscard' : undefined"
|
||||
>
|
||||
<template #footer>
|
||||
<div class="mb-[6px] mt-[4px] p-[3px_8px]">
|
||||
|
@ -25,15 +25,15 @@
|
|||
</div>
|
||||
</template>
|
||||
</MsSelect>
|
||||
<a-button type="outline" class="arco-btn-outline--secondary px-[5px]" @click="toggle">
|
||||
<a-button type="outline" class="arco-btn-outline--secondary px-[5px]" @click="toggleFullScreen">
|
||||
<template #icon>
|
||||
<MsIcon
|
||||
:type="isFullscreen ? 'icon-icon_off_screen' : 'icon-icon_full_screen_one'"
|
||||
:type="isFullScreen ? 'icon-icon_off_screen' : 'icon-icon_full_screen_one'"
|
||||
class="text-[var(--color-text-4)]"
|
||||
size="14"
|
||||
/>
|
||||
</template>
|
||||
{{ t(isFullscreen ? 'common.offFullScreen' : 'common.fullScreen') }}
|
||||
{{ t(isFullScreen ? 'common.offFullScreen' : 'common.fullScreen') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -42,7 +42,6 @@
|
|||
v-bind="propsRes"
|
||||
v-model:expandedKeys="expandedKeys"
|
||||
no-disable
|
||||
span-all
|
||||
:indent-size="0"
|
||||
v-on="propsEvent"
|
||||
>
|
||||
|
@ -71,7 +70,7 @@
|
|||
:remote-func="getMessageUserList"
|
||||
:remote-fields-map="{ label: 'name', value: 'id', id: 'id' }"
|
||||
:not-auto-init-search="true"
|
||||
:popup-container="isFullscreen ? '#mscard' : undefined"
|
||||
:popup-container="isFullScreen ? '#mscard' : undefined"
|
||||
:fallback-option="(val) => ({
|
||||
label: (val as Record<string, any>).name,
|
||||
value: val,
|
||||
|
@ -91,7 +90,7 @@
|
|||
size="small"
|
||||
type="line"
|
||||
/>
|
||||
<a-popover position="right" :popup-container="isFullscreen ? '#mscard' : undefined">
|
||||
<a-popover position="right" :popup-container="isFullScreen ? '#mscard' : undefined">
|
||||
<div
|
||||
class="ml-[8px] mr-[4px] cursor-pointer text-[var(--color-text-1)] hover:text-[rgb(var(--primary-6))]"
|
||||
>
|
||||
|
@ -123,7 +122,6 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, onBeforeMount, ref, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useFullscreen } from '@vueuse/core';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
|
@ -141,6 +139,7 @@
|
|||
getRobotList,
|
||||
saveMessageConfig,
|
||||
} from '@/api/modules/project-management/messageManagement';
|
||||
import useFullScreen from '@/hooks/useFullScreen';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
|
@ -160,7 +159,7 @@
|
|||
const robotOptions = ref<(SelectOptionData & RobotItem)[]>([]);
|
||||
const fullRef = ref<HTMLElement | null>();
|
||||
|
||||
const { isFullscreen, toggle } = useFullscreen(fullRef);
|
||||
const { isFullScreen, toggleFullScreen } = useFullScreen(fullRef);
|
||||
|
||||
const tableRef = ref<InstanceType<typeof MsBaseTable> | null>(null);
|
||||
const staticColumns: MsTableColumn = [
|
||||
|
@ -287,7 +286,7 @@
|
|||
functionName: (item as unknown as MessageItem).name,
|
||||
taskType: child.taskType,
|
||||
name: child.taskTypeName,
|
||||
rowspan: child.messageTaskDetailDTOList.length,
|
||||
rowspan: child.messageTaskDetailDTOList.length || 1,
|
||||
...grandson,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<div class="flex gap-[12px] p-[16px]">
|
||||
<a-avatar>MS</a-avatar>
|
||||
<div class="flex flex-1 flex-col">
|
||||
<div class="font-medium text-[var(--color-text-1)]">{{ subject || '-' }}</div>
|
||||
<div class="font-medium text-[var(--color-text-1)]" v-html="subject || '-'"></div>
|
||||
<div class="mt-[4px] text-[var(--color-text-2)]" v-html="template || '-'"></div>
|
||||
<div class="text-[var(--color-text-4)]">{{ dayjs().format('YYYY-MM-DD HH:mm:ss') }}</div>
|
||||
</div>
|
||||
|
@ -31,8 +31,7 @@
|
|||
v-else-if="props.robot.platform === 'MAIL'"
|
||||
class="preview-rounded w-[400px] bg-[var(--color-text-n9)] p-[12px] text-[14px]"
|
||||
>
|
||||
<div class="mb-[4px] text-[16px] font-medium leading-[24px] text-[var(--color-text-1)]">
|
||||
{{ subject || '-' }}
|
||||
<div class="mb-[4px] text-[16px] font-medium leading-[24px] text-[var(--color-text-1)]" v-html="subject || '-'">
|
||||
</div>
|
||||
<div class="mb-[8px] flex flex-col">
|
||||
<div class="text-[12px] leading-[16px] text-[var(--color-text-4)]">
|
||||
|
@ -216,9 +215,6 @@
|
|||
// 使用正则表达式替换 {{name}} 为高亮的关键字
|
||||
function replacePreviewName(str: string) {
|
||||
return str
|
||||
.replace(/<|>/g, (match) => {
|
||||
return match === '<' ? '<' : '>';
|
||||
})
|
||||
.replace(/{{(.*?)}}/g, `<span style='color: rgb(var(--primary-6))'><$1></span>`)
|
||||
.replace(/\n/g, '<br>');
|
||||
}
|
||||
|
|
|
@ -329,6 +329,7 @@
|
|||
dataIndex: 'publishTime',
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
width: 180,
|
||||
},
|
||||
|
@ -337,6 +338,7 @@
|
|||
dataIndex: 'createTime',
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
width: 180,
|
||||
},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<MsCard :loading="loading" simple>
|
||||
<MsCard :loading="loading" auto-height simple>
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<a-button v-permission="['SYSTEM_PARAMETER_SETTING_AUTH:READ+ADD']" type="primary" @click="createAuth">
|
||||
{{ t('system.config.auth.add') }}
|
||||
|
@ -100,7 +100,7 @@
|
|||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.casUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.serviceUrlPlaceholder')"
|
||||
:placeholder="t('system.config.auth.commonUrlPlaceholder', { url: 'http://<casurl>' })"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
|
@ -114,7 +114,7 @@
|
|||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.loginUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.loginUrlPlaceholder')"
|
||||
:placeholder="t('system.config.auth.commonUrlPlaceholder', { url: 'http://<casurl>/login' })"
|
||||
allow-clear
|
||||
></a-input>
|
||||
<MsFormItemSub :text="t('system.config.auth.loginUrlTip')" :show-fill-icon="false" />
|
||||
|
@ -129,7 +129,11 @@
|
|||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.redirectUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.callbackUrlPlaceholder')"
|
||||
:placeholder="
|
||||
t('system.config.auth.commonUrlPlaceholder', {
|
||||
url: 'http://<meteresphere-endpoint>/sso/callback/cas/suthld',
|
||||
})
|
||||
"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
|
@ -143,7 +147,7 @@
|
|||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.validateUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.verifyUrlPlaceholder')"
|
||||
:placeholder="t('system.config.auth.commonUrlPlaceholder', { url: 'http://<casurl>/serviceValidate' })"
|
||||
allow-clear
|
||||
></a-input>
|
||||
<MsFormItemSub :text="t('system.config.auth.verifyUrlTip')" :show-fill-icon="false" />
|
||||
|
@ -160,7 +164,11 @@
|
|||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.authUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.authUrlPlaceholder')"
|
||||
:placeholder="
|
||||
t('system.config.auth.commonUrlPlaceholder', {
|
||||
url: 'http://<keyclock>auth/realms/<metersphere>/protocol/openid-connect/auth',
|
||||
})
|
||||
"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
|
@ -174,7 +182,11 @@
|
|||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.tokenUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.tokenUrlPlaceholder')"
|
||||
:placeholder="
|
||||
t('system.config.auth.commonUrlPlaceholder', {
|
||||
url: 'http://<keyclock>auth/realms/<metersphere>/protocol/openid-connect/token',
|
||||
})
|
||||
"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
|
@ -188,7 +200,11 @@
|
|||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.userInfoUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.userInfoUrlPlaceholder')"
|
||||
:placeholder="
|
||||
t('system.config.auth.commonUrlPlaceholder', {
|
||||
url: 'http://<keyclock>auth/realms/<metersphere>/protocol/openid-connect/userinfo',
|
||||
})
|
||||
"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
|
@ -202,7 +218,11 @@
|
|||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.redirectUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.OIDCCallbackUrlPlaceholder')"
|
||||
:placeholder="
|
||||
t('system.config.auth.commonUrlPlaceholder', {
|
||||
url: 'http://<metersphere-endpoint>/sso/callback or http://<metersphere-endpoint>/sso/callback/authld',
|
||||
})
|
||||
"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
|
@ -244,7 +264,11 @@
|
|||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.logoutUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.logoutSessionUrlPlaceholder')"
|
||||
:placeholder="
|
||||
t('system.config.auth.commonUrlPlaceholder', {
|
||||
url: 'http://<keyclock>/auth/realms/<metersphere>/protocol/openid-connect/logout',
|
||||
})
|
||||
"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
|
@ -252,7 +276,7 @@
|
|||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.loginUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.loginUrlPlaceholder')"
|
||||
:placeholder="t('system.config.auth.commonUrlPlaceholder', { url: 'http://<casurl>/login' })"
|
||||
allow-clear
|
||||
></a-input>
|
||||
<MsFormItemSub :text="t('system.config.auth.loginUrlTip')" :show-fill-icon="false" />
|
||||
|
@ -269,7 +293,11 @@
|
|||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.authUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.authUrlPlaceholder')"
|
||||
:placeholder="
|
||||
t('system.config.auth.commonUrlPlaceholder', {
|
||||
url: 'http://<keyclock>auth/realms/<metersphere>/protocol/openid-connect/auth',
|
||||
})
|
||||
"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
|
@ -283,7 +311,11 @@
|
|||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.tokenUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.tokenUrlPlaceholder')"
|
||||
:placeholder="
|
||||
t('system.config.auth.commonUrlPlaceholder', {
|
||||
url: 'http://<keyclock>auth/realms/<metersphere>/protocol/openid-connect/token',
|
||||
})
|
||||
"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
|
@ -297,7 +329,11 @@
|
|||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.userInfoUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.userInfoUrlPlaceholder')"
|
||||
:placeholder="
|
||||
t('system.config.auth.commonUrlPlaceholder', {
|
||||
url: 'http://<keyclock>auth/realms/<metersphere>/protocol/openid-connect/userinfo',
|
||||
})
|
||||
"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
|
@ -311,7 +347,11 @@
|
|||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.redirectUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.OIDCCallbackUrlPlaceholder')"
|
||||
:placeholder="
|
||||
t('system.config.auth.commonUrlPlaceholder', {
|
||||
url: 'http://<meteresphere-endpoint>/sso/callback/cas/suthld',
|
||||
})
|
||||
"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
|
@ -365,7 +405,11 @@
|
|||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.logoutUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.logoutSessionUrlPlaceholder')"
|
||||
:placeholder="
|
||||
t('system.config.auth.commonUrlPlaceholder', {
|
||||
url: 'http://<keyclock>/auth/realms/<metersphere>/protocol/openid-connect/logout',
|
||||
})
|
||||
"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
|
@ -381,7 +425,7 @@
|
|||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.loginUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.loginUrlPlaceholder')"
|
||||
:placeholder="t('system.config.auth.commonUrlPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
<MsFormItemSub :text="t('system.config.auth.loginUrlTip')" :show-fill-icon="false" />
|
||||
|
|
|
@ -1,57 +1,61 @@
|
|||
<template>
|
||||
<MsCard class="mb-[16px]" :loading="loading" simple auto-height>
|
||||
<div class="mb-[16px] flex justify-between">
|
||||
<div class="font-medium text-[var(--color-text-000)]">{{ t('system.config.memoryCleanup') }}</div>
|
||||
</div>
|
||||
<a-radio-group v-model:model-value="activeType" type="button">
|
||||
<a-radio value="log">{{ t('system.config.memoryCleanup.log') }}</a-radio>
|
||||
<a-radio value="history">{{ t('system.config.memoryCleanup.history') }}</a-radio>
|
||||
</a-radio-group>
|
||||
<template v-if="activeType === 'log'">
|
||||
<div class="mb-[8px] mt-[16px] flex items-center">
|
||||
<div class="text-[var(--color-text-000)]">{{ t('system.config.memoryCleanup.keepTime') }}</div>
|
||||
<a-tooltip :content="t('system.config.memoryCleanup.keepTimeTip')" position="right">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<div>
|
||||
<MsCard class="mb-[16px]" :loading="loading" simple auto-height>
|
||||
<div class="mb-[16px] flex justify-between">
|
||||
<div class="font-medium text-[var(--color-text-000)]">{{ t('system.config.memoryCleanup') }}</div>
|
||||
</div>
|
||||
<a-input-number
|
||||
v-model:model-value="timeCount"
|
||||
class="w-[130px]"
|
||||
:disabled="saveLoading || !userStore.isAdmin"
|
||||
@blur="() => saveConfig()"
|
||||
>
|
||||
<template #append>
|
||||
<a-select
|
||||
v-model:model-value="activeTime"
|
||||
:options="timeOptions"
|
||||
class="select-input-append"
|
||||
:loading="saveLoading"
|
||||
@change="() => saveConfig()"
|
||||
/>
|
||||
</template>
|
||||
</a-input-number>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="mb-[8px] mt-[16px] flex items-center">
|
||||
<div class="text-[var(--color-text-000)]">{{ t('system.config.memoryCleanup.saveCount') }}</div>
|
||||
<a-tooltip :content="t('system.config.memoryCleanup.saveCountTip')" position="right">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<a-input-number
|
||||
v-model:model-value="historyCount"
|
||||
class="w-[130px]"
|
||||
:disabled="saveLoading"
|
||||
@blur="() => saveConfig()"
|
||||
/>
|
||||
</template>
|
||||
</MsCard>
|
||||
<a-radio-group v-model:model-value="activeType" type="button">
|
||||
<a-radio value="log">{{ t('system.config.memoryCleanup.log') }}</a-radio>
|
||||
<a-radio value="history">{{ t('system.config.memoryCleanup.history') }}</a-radio>
|
||||
</a-radio-group>
|
||||
<template v-if="activeType === 'log'">
|
||||
<div class="mb-[8px] mt-[16px] flex items-center">
|
||||
<div class="text-[var(--color-text-000)]">{{ t('system.config.memoryCleanup.keepTime') }}</div>
|
||||
<a-tooltip :content="t('system.config.memoryCleanup.keepTimeTip')" position="right">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<a-input-number
|
||||
v-model:model-value="timeCount"
|
||||
class="w-[130px]"
|
||||
:disabled="saveLoading"
|
||||
:min="0"
|
||||
@blur="() => saveConfig()"
|
||||
>
|
||||
<template #append>
|
||||
<a-select
|
||||
v-model:model-value="activeTime"
|
||||
:options="timeOptions"
|
||||
class="select-input-append"
|
||||
:loading="saveLoading"
|
||||
@change="() => saveConfig()"
|
||||
/>
|
||||
</template>
|
||||
</a-input-number>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="mb-[8px] mt-[16px] flex items-center">
|
||||
<div class="text-[var(--color-text-000)]">{{ t('system.config.memoryCleanup.saveCount') }}</div>
|
||||
<a-tooltip :content="t('system.config.memoryCleanup.saveCountTip')" position="right">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<a-input-number
|
||||
v-model:model-value="historyCount"
|
||||
class="w-[130px]"
|
||||
:disabled="saveLoading"
|
||||
:min="0"
|
||||
@blur="() => saveConfig()"
|
||||
/>
|
||||
</template>
|
||||
</MsCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -88,20 +92,26 @@
|
|||
const historyCount = ref(10);
|
||||
|
||||
onBeforeMount(async () => {
|
||||
loading.value = true;
|
||||
const res = await getCleanupConfig();
|
||||
if (res.operationLog) {
|
||||
const matches = res.operationLog.match(/(\d+)([MDY])$/);
|
||||
if (matches) {
|
||||
const [, number, letter] = matches;
|
||||
timeCount.value = Number(number);
|
||||
activeTime.value = letter;
|
||||
try {
|
||||
loading.value = true;
|
||||
const res = await getCleanupConfig();
|
||||
if (res.operationLog) {
|
||||
const matches = res.operationLog.match(/(\d+)([MDY])$/);
|
||||
if (matches) {
|
||||
const [, number, letter] = matches;
|
||||
timeCount.value = Number(number);
|
||||
activeTime.value = letter;
|
||||
}
|
||||
}
|
||||
if (res.operationHistory) {
|
||||
historyCount.value = Number(res.operationHistory);
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
if (res.operationHistory) {
|
||||
historyCount.value = Number(res.operationHistory);
|
||||
}
|
||||
loading.value = false;
|
||||
});
|
||||
|
||||
const saveLoading = ref(false);
|
||||
|
|
|
@ -230,7 +230,7 @@
|
|||
<div :class="['config-preview', '!h-[290px]', currentLocale === 'en-US' ? '!h-[340px]' : '']">
|
||||
<div ref="platformPageFullRef" class="login-preview">
|
||||
<div
|
||||
class="absolute right-[18px] top-[16px] z-[999] w-[96px] cursor-pointer text-right !text-[var(--color-text-4)]"
|
||||
class="absolute right-[12px] top-[8px] z-[999] w-[96px] cursor-pointer text-right !text-[var(--color-text-4)]"
|
||||
@click="platformFullscreenToggle"
|
||||
>
|
||||
<MsIcon v-if="isPlatformPageFullscreen" type="icon-icon_off_screen" />
|
||||
|
@ -341,7 +341,6 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, ref, watch } from 'vue';
|
||||
import { useFullscreen } from '@vueuse/core';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
|
@ -354,6 +353,7 @@
|
|||
import loginForm from '@/views/login/components/login-form.vue';
|
||||
|
||||
import { savePageConfig } from '@/api/modules/setting/config';
|
||||
import useFullScreen from '@/hooks/useFullScreen';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useLocale from '@/locale/useLocale';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
@ -377,9 +377,10 @@
|
|||
const pageConfig = ref({ ...appStore.pageConfig });
|
||||
const loginPageFullRef = ref<HTMLElement | null>(null);
|
||||
const platformPageFullRef = ref<HTMLElement | null>(null);
|
||||
const { isFullscreen: isLoginPageFullscreen, toggle: loginFullscreenToggle } = useFullscreen(loginPageFullRef);
|
||||
const { isFullscreen: isPlatformPageFullscreen, toggle: platformFullscreenToggle } =
|
||||
useFullscreen(platformPageFullRef);
|
||||
const { isFullScreen: isLoginPageFullscreen, toggleFullScreen: loginFullscreenToggle } =
|
||||
useFullScreen(loginPageFullRef);
|
||||
const { isFullScreen: isPlatformPageFullscreen, toggleFullScreen: platformFullscreenToggle } =
|
||||
useFullScreen(platformPageFullRef);
|
||||
const loginConfigFormRef = ref<FormInstance>();
|
||||
const platformConfigFormRef = ref<FormInstance>();
|
||||
|
||||
|
|
|
@ -130,34 +130,21 @@ export default {
|
|||
'system.config.auth.addResource': 'Add resource',
|
||||
'system.config.auth.serviceUrl': 'Server address',
|
||||
'system.config.auth.serviceUrlRequired': 'Server address cannot be empty',
|
||||
'system.config.auth.serviceUrlPlaceholder': 'eg: http://<casurl>',
|
||||
'system.config.auth.commonUrlPlaceholder': 'eg: {url}',
|
||||
'system.config.auth.loginUrl': 'Login address',
|
||||
'system.config.auth.loginUrlRequired': 'Login address cannot be empty',
|
||||
'system.config.auth.loginUrlPlaceholder': 'eg: http://<casurl>/login',
|
||||
'system.config.auth.loginUrlTip': 'When authentication fails, redirect to this login page',
|
||||
'system.config.auth.verifyUrl': 'Verify address',
|
||||
'system.config.auth.verifyUrlRequired': 'Verification address cannot be empty',
|
||||
'system.config.auth.verifyUrlPlaceholder': 'eg: http://<casurl>/serviceValidate',
|
||||
'system.config.auth.verifyUrlTip': 'The information used to verify the login is correct',
|
||||
'system.config.auth.callbackUrl': 'Callback address',
|
||||
'system.config.auth.callbackUrlRequired': 'Callback address cannot be empty',
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
'system.config.auth.callbackUrlPlaceholder': 'eg: http://<meteresphere-endpoint>/sso/callback/cas/suthld',
|
||||
'system.config.auth.OIDCCallbackUrlPlaceholder':
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
'eg: http://<metersphere-endpoint>/sso/callback or http://<metersphere-endpoint>/sso/callback/authld',
|
||||
'system.config.auth.authUrl': 'Authorized end address',
|
||||
'system.config.auth.authUrlRequired': 'Authorization end address cannot be empty',
|
||||
'system.config.auth.authUrlPlaceholder':
|
||||
'eg: http://<keyclock>auth/realms/<metersphere>/protocol/openid-connect/auth',
|
||||
'system.config.auth.tokenUrl': 'Token endpoint address',
|
||||
'system.config.auth.tokenUrlRequired': 'Token endpoint address cannot be empty',
|
||||
'system.config.auth.tokenUrlPlaceholder':
|
||||
'eg: http://<keyclock>auth/realms/<metersphere>/protocol/openid-connect/token',
|
||||
'system.config.auth.userInfoUrl': 'User information endpoint address',
|
||||
'system.config.auth.userInfoUrlRequired': 'User information endpoint address cannot be empty',
|
||||
'system.config.auth.userInfoUrlPlaceholder':
|
||||
'eg: http://<keyclock>auth/realms/<metersphere>/protocol/openid-connect/userinfo',
|
||||
'system.config.auth.clientId': 'Client ID',
|
||||
'system.config.auth.clientIdRequired': 'Client ID cannot be empty',
|
||||
'system.config.auth.clientIdPlaceholder': 'eg: metersphere',
|
||||
|
@ -166,8 +153,6 @@ export default {
|
|||
'system.config.auth.clientSecretPlaceholder': 'OIDC client secret',
|
||||
'system.config.auth.logoutSessionUrl': 'Logout session address',
|
||||
'system.config.auth.logoutSessionUrlRequired': 'Logout session address cannot be empty',
|
||||
'system.config.auth.logoutSessionUrlPlaceholder':
|
||||
'eg: http://<keyclock>/auth/realms/<metersphere>/protocol/openid-connect/logout',
|
||||
'system.config.auth.password': 'Password',
|
||||
'system.config.auth.passwordRequired': 'Password cannot be empty',
|
||||
'system.config.auth.passwordPlaceholder': 'OIDC client secret',
|
||||
|
|
|
@ -126,34 +126,21 @@ export default {
|
|||
'system.config.auth.addResource': '添加资源',
|
||||
'system.config.auth.serviceUrl': '服务端地址',
|
||||
'system.config.auth.serviceUrlRequired': '服务端地址不能为空',
|
||||
'system.config.auth.serviceUrlPlaceholder': '例如:http://<casurl>',
|
||||
'system.config.auth.commonUrlPlaceholder': '例如:{url}',
|
||||
'system.config.auth.loginUrl': '登录地址',
|
||||
'system.config.auth.loginUrlRequired': '登录地址不能为空',
|
||||
'system.config.auth.loginUrlPlaceholder': '例如:http://<casurl>/login',
|
||||
'system.config.auth.loginUrlTip': '当身份认证失败,会重新定向到该登录页',
|
||||
'system.config.auth.verifyUrl': '验证地址',
|
||||
'system.config.auth.verifyUrlRequired': '验证地址不能为空',
|
||||
'system.config.auth.verifyUrlPlaceholder': '例如:http://<casurl>/serviceValidate',
|
||||
'system.config.auth.verifyUrlTip': '用于验证登录的信息是否正确',
|
||||
'system.config.auth.callbackUrl': '回调地址',
|
||||
'system.config.auth.callbackUrlRequired': '回调地址不能为空',
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
'system.config.auth.callbackUrlPlaceholder': '例如:http://<meteresphere-endpoint>/sso/callback/cas/suthld',
|
||||
'system.config.auth.OIDCCallbackUrlPlaceholder':
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
'例如:http://<metersphere-endpoint>/sso/callback or http://<metersphere-endpoint>/sso/callback/authld',
|
||||
'system.config.auth.authUrl': '授权端地址',
|
||||
'system.config.auth.authUrlRequired': '授权端地址不能为空',
|
||||
'system.config.auth.authUrlPlaceholder':
|
||||
'例如:http://<keyclock>auth/realms/<metersphere>/protocol/openid-connect/auth',
|
||||
'system.config.auth.tokenUrl': 'Token 端点地址',
|
||||
'system.config.auth.tokenUrlRequired': 'Token 端点地址不能为空',
|
||||
'system.config.auth.tokenUrlPlaceholder':
|
||||
'例如:http://<keyclock>auth/realms/<metersphere>/protocol/openid-connect/token',
|
||||
'system.config.auth.userInfoUrl': '用户信息端点地址',
|
||||
'system.config.auth.userInfoUrlRequired': '用户信息端点地址不能为空',
|
||||
'system.config.auth.userInfoUrlPlaceholder':
|
||||
'例如:http://<keyclock>auth/realms/<metersphere>/protocol/openid-connect/userinfo',
|
||||
'system.config.auth.clientId': '客户端 ID',
|
||||
'system.config.auth.clientIdRequired': '客户端 ID不能为空',
|
||||
'system.config.auth.clientIdPlaceholder': '例如:metersphere',
|
||||
|
@ -162,8 +149,6 @@ export default {
|
|||
'system.config.auth.clientSecretPlaceholder': 'OIDC client secret',
|
||||
'system.config.auth.logoutSessionUrl': '注销会话端地址',
|
||||
'system.config.auth.logoutSessionUrlRequired': '注销会话端地址不能为空',
|
||||
'system.config.auth.logoutSessionUrlPlaceholder':
|
||||
'例如:http://<keyclock>/auth/realms/<metersphere>/protocol/openid-connect/logout',
|
||||
'system.config.auth.password': '密码',
|
||||
'system.config.auth.passwordRequired': '密码不能为空',
|
||||
'system.config.auth.passwordPlaceholder': 'OIDC client secret',
|
||||
|
|
|
@ -457,6 +457,7 @@
|
|||
width: 180,
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
Loading…
Reference in New Issue