Skip to content

Commit b87c7f1

Browse files
ice201508jiuling
andauthored
new-organization style no border (#1259)
* Draft MR * new-organization style no border * fix-new-organbization-update-style * fix-new-organbization-update-style --------- Co-authored-by: jiuling <[email protected]>
1 parent 15af65a commit b87c7f1

File tree

13 files changed

+254
-15
lines changed

13 files changed

+254
-15
lines changed

frontend/src/assets/stylesheets/element-plus/input.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
/* 所有的input 添加 */
33
.el-textarea__inner, .el-input__wrapper:not(.el-input-group--prepend *), .el-select__wrapper:not(.el-input-group--prepend *), .el-cascader {
44
border: 1px solid transparent;
5-
border-radius: var(--border-radius-md);-radius: var(--border-radius-md);
5+
border-radius: var(--border-radius-md);
66
}
77

88
/* default focus style */

frontend/src/components/__tests__/endpoints/EndpointDetail.spec.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,9 @@ vi.mock('@/packs/useFetchApi', () => {
8585

8686
vi.mock('@/stores/RepoDetailStore', () => ({
8787
default: () => ({
88-
initialize: vi.fn()
88+
initialize: vi.fn(),
89+
updateLikes: vi.fn(),
90+
updateUserLikes: vi.fn()
8991
})
9092
}));
9193

@@ -182,4 +184,4 @@ describe("EndpointDetail", () => {
182184
name: 'test-model'
183185
});
184186
});
185-
});
187+
});

frontend/src/components/__tests__/shared/RepoHeader.spec.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ const mockRepoDetailStore = {
3232
userLikes: false,
3333
updateLikes: vi.fn(),
3434
updateUserLikes: vi.fn(),
35-
// 添加其他可能需要的属性
3635
hfPath: null,
3736
msPath: null,
3837
csgPath: null,
@@ -82,6 +81,9 @@ describe('RepoHeader', () => {
8281
beforeEach(() => {
8382
vi.clearAllMocks()
8483
mockRepoDetailStore.isPrivate = false
84+
// Reset the mock call count to avoid interference from watchers
85+
mockRepoDetailStore.updateLikes.mockClear()
86+
mockRepoDetailStore.updateUserLikes.mockClear()
8587
})
8688

8789
it('mounts correctly', () => {
@@ -146,6 +148,10 @@ describe('RepoHeader', () => {
146148
expect(wrapper.text()).toContain('shared.likes')
147149
expect(wrapper.text()).toContain('10')
148150

151+
// 清除初始化时的调用记录
152+
mockRepoDetailStore.updateLikes.mockClear()
153+
mockRepoDetailStore.updateUserLikes.mockClear()
154+
149155
// Click to like
150156
await wrapper.find('.flex.cursor-pointer.gap-1').trigger('click')
151157
await flushPromises()

frontend/src/components/__tests__/user_settings/Profile.spec.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const createUserStore = (overrides = {}) =>
2929
],
3030
lastLoginTime: '2023-09-11',
3131
phone: '123456',
32+
fetchUserInfo: vi.fn(),
3233
...overrides
3334
})
3435

@@ -69,6 +70,12 @@ vi.mock('../../../packs/useFetchApi', () => ({
6970
})
7071
}))
7172

73+
vi.mock('vue-router', () => ({
74+
useRoute: () => ({
75+
fullPath: '/profile/current_user'
76+
})
77+
}))
78+
7279
describe('Profile', () => {
7380
let wrapper
7481
let originalLocalStorage

frontend/src/components/organizations/NewOrganization.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@
237237
}
238238
:deep(.el-select__wrapper) {
239239
height: 46px;
240-
box-shadow: none !important;
240+
/* box-shadow: none !important; */
241241
}
242242
:deep(.el-upload--picture) {
243243
border: 0px;

frontend/src/components/organizations/OrganizationEdit.vue

Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,46 @@
7979
<button class="btn btn-primary btn-md" @click="handleSubmit">{{ $t('all.saveSetting') }}</button>
8080
</el-form-item>
8181
</el-form>
82+
83+
<!-- 删除组织表单 -->
84+
<div v-if="isAdmin">
85+
<div class="font-semibold text-xl leading-[28px] text-gray-800 mb-4">{{ $t('organization.edit.deleteOrganization') }}</div>
86+
<div class="text-sm text-gray-700 mb-6">{{ $t('organization.edit.deleteOrganizationDesc') }}</div>
87+
<div class="text-sm text-gray-700 mb-4">{{ $t('organization.edit.deleteOrganizationConfirm', {orgName: organization.name}) }}</div>
88+
89+
<el-form
90+
ref="deleteFormRef"
91+
:model="deleteForm"
92+
label-position="top"
93+
class="text-left"
94+
style="--el-border-radius-base: 8px"
95+
>
96+
<el-form-item :label="$t('organization.edit.deleteOrganizationName')" prop="confirmName">
97+
<el-input
98+
v-model="deleteForm.confirmName"
99+
:placeholder="$t('organization.edit.deleteOrganizationNamePlaceholder')"
100+
class="w-full"
101+
></el-input>
102+
</el-form-item>
103+
104+
<el-form-item>
105+
<CsgButton
106+
class="btn btn-danger btn-md"
107+
:disabled="isDeleteButtonDisabled"
108+
:name="$t('organization.edit.deleteOrganizationButton')"
109+
@click="handleDeleteOrganization"
110+
/>
111+
</el-form-item>
112+
</el-form>
113+
</div>
114+
82115
</div>
83116
</template>
84117
<script setup>
85118
import useFetchApi from "../../packs/useFetchApi"
86119
import { ref, inject, computed, onMounted, watch } from 'vue'
87120
import { useI18n } from 'vue-i18n'
88-
import { ElMessage } from 'element-plus'
121+
import { ElMessage, ElMessageBox } from 'element-plus'
89122
import useUserStore from '../../stores/UserStore'
90123
const userStore = useUserStore()
91124
@@ -103,11 +136,25 @@
103136
const selectedProtocol = ref('https://')
104137
const org_types = ['企业', '高校', '非营利组织', '社区组织']
105138
const dataFormRef = ref(null)
139+
const deleteFormRef = ref(null)
106140
107141
const emit = defineEmits(['updateOrganization']);
108142
109143
const logo_images = ref([])
110144
145+
// 删除表单数据
146+
const deleteForm = ref({
147+
confirmName: ''
148+
})
149+
150+
// 用户角色状态
151+
const isAdmin = ref(false)
152+
153+
// 计算删除按钮是否禁用
154+
const isDeleteButtonDisabled = computed(() => {
155+
return !deleteForm.value.confirmName || deleteForm.value.confirmName.trim() === '' || deleteForm.value.confirmName !== organization.value.name
156+
})
157+
111158
watch(props.organizationRaw, (newVal) => {
112159
// set logo
113160
if (newVal.logo) {
@@ -220,16 +267,65 @@
220267
})
221268
}
222269
}
270+
271+
// 删除组织处理函数
272+
const handleDeleteOrganization = async () => {
273+
if (isDeleteButtonDisabled.value) {
274+
return
275+
}
276+
277+
try {
278+
await ElMessageBox.confirm(
279+
t('organization.edit.deleteOrganizationConfirmTitle'),
280+
t('organization.edit.deleteOrganization'),
281+
{
282+
confirmButtonText: t('organization.edit.deleteOrganizationConfirmButton'),
283+
cancelButtonText: t('organization.edit.deleteOrganizationCancelButton'),
284+
type: 'warning',
285+
}
286+
)
287+
288+
const deleteEndpoint = `/organization/${organization.value.name}`;
289+
const { error } = await useFetchApi(deleteEndpoint).delete().json()
290+
291+
if (error.value) {
292+
ElMessage({
293+
message: error.value.msg,
294+
type: 'error'
295+
});
296+
} else {
297+
ElMessage({
298+
message: t('organization.edit.deleteOrganizationSuccess'),
299+
type: 'success'
300+
});
301+
302+
// 更新用户信息以反映组织删除
303+
await userStore.fetchUserInfo()
304+
305+
// 删除成功后跳转到首页或组织列表
306+
window.location.href = '/'
307+
}
308+
} catch (err) {
309+
// 用户取消删除
310+
}
311+
}
312+
223313
const currentUserRole = async () => {
224-
const orgIsAdminEndpoint = `/organization/${props.name}/members/${userStore.username}`
314+
if (!organization.value.name) return
315+
316+
const orgIsAdminEndpoint = `/organization/${organization.value.name}/members/${userStore.username}`
225317
const { data, error } = await useFetchApi(orgIsAdminEndpoint).json()
226318
227319
if (error.value) {
228320
ElMessage({ message: error.value.msg, type: 'warning' })
321+
isAdmin.value = false
229322
} else {
230323
const body = data.value
231324
if(!body.data){
232325
window.location.href = '/'
326+
} else {
327+
// 检查用户是否为管理员
328+
isAdmin.value = body.data === 'admin'
233329
}
234330
}
235331
}
@@ -283,4 +379,4 @@
283379
:deep(.el-form-item .el-input__validateIcon) {
284380
display: none;
285381
}
286-
</style>
382+
</style>

frontend/src/components/organizations/OrganizationMembers.vue

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,20 @@
101101
>
102102
<Edit />
103103
</el-icon>
104+
<el-tooltip
105+
v-if="!canDeleteMember(scope.row)"
106+
:content="$t('organization.members.assignOtherAdmin')"
107+
placement="top"
108+
>
109+
<el-icon
110+
:size="16"
111+
class="cursor-not-allowed text-gray-400 delete-disabled"
112+
>
113+
<Delete />
114+
</el-icon>
115+
</el-tooltip>
104116
<el-popconfirm
117+
v-else
105118
:title="
106119
$t('organization.members.deleteConfirmTitle', {
107120
username: scope.row.nickname || scope.row.name,
@@ -160,7 +173,7 @@
160173
import InviteMember from './InviteMember.vue'
161174
import OrgMemberRoleEditDialog from './OrgMemberRoleEditDialog.vue'
162175
import dayjs from 'dayjs'
163-
import { ref, onMounted } from 'vue'
176+
import { ref, onMounted, computed } from 'vue'
164177
import { useCookies } from 'vue3-cookies'
165178
import { useI18n } from 'vue-i18n'
166179
import useUserStore from '../../stores/UserStore'
@@ -191,6 +204,23 @@
191204
per: 6
192205
})
193206
207+
// 计算当前admin用户数量
208+
const adminCount = computed(() => {
209+
return members.value.filter(member => member.role === 'admin').length
210+
})
211+
212+
// 检查是否可以删除某个成员
213+
const canDeleteMember = (member) => {
214+
// 如果是admin用户,需要检查删除后是否还有admin
215+
if (member.role === 'admin') {
216+
// 如果当前admin数量为1,则不能删除
217+
if (adminCount.value <= 1) {
218+
return false
219+
}
220+
}
221+
return true
222+
}
223+
194224
onMounted(() => {
195225
fetchMembers()
196226
})
@@ -219,6 +249,15 @@
219249
}
220250
221251
const handleDelete = async (member) => {
252+
// 再次检查是否可以删除
253+
if (!canDeleteMember(member)) {
254+
ElMessage({
255+
message: t('organization.members.assignOtherAdmin'),
256+
type: 'warning'
257+
})
258+
return
259+
}
260+
222261
const url = `/organization/${organization.value.name}/members/${member.username}`
223262
const options = {
224263
body: JSON.stringify({ role: member.role }),
@@ -239,6 +278,7 @@
239278
})
240279
}
241280
}
281+
242282
const currentUserRole = async () => {
243283
const orgIsAdminEndpoint = `/organization/${organization.value.name}/members/${userStore.username}`
244284
const { data, error } = await useFetchApi(orgIsAdminEndpoint).json()
@@ -257,4 +297,14 @@
257297
editRow.value = Object.assign({}, row)
258298
roleEditDialog.value = true
259299
}
260-
</script>
300+
</script>
301+
302+
<style scoped>
303+
.delete-disabled:hover {
304+
color: #9ca3af !important;
305+
}
306+
307+
.delete-disabled:hover :deep(svg) {
308+
color: #9ca3af !important;
309+
}
310+
</style>

frontend/src/components/shared/RepoHeader.vue

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,19 @@
509509
// 等待DOM更新后检查
510510
setTimeout(checkOverflow, 0)
511511
})
512+
513+
// 添加 watch 来监听 props 变化
514+
watch(() => props.totalLikes, (newValue) => {
515+
if (newValue !== undefined) {
516+
repoDetailStore.updateLikes(newValue)
517+
}
518+
}, { immediate: true })
519+
520+
watch(() => props.hasLike, (newValue) => {
521+
if (newValue !== undefined) {
522+
repoDetailStore.updateUserLikes(newValue)
523+
}
524+
}, { immediate: true })
512525
</script>
513526
514527
<style scoped>

0 commit comments

Comments
 (0)