|
@@ -1139,11 +1139,10 @@
|
|
<div class="form-item">
|
|
<div class="form-item">
|
|
<div class="form-label">胜任力标签</div>
|
|
<div class="form-label">胜任力标签</div>
|
|
<el-select
|
|
<el-select
|
|
- v-model="questionForm.competency_tags"
|
|
|
|
|
|
+ v-model="competencyTagsValue"
|
|
placeholder="请选择胜任力标签"
|
|
placeholder="请选择胜任力标签"
|
|
class="full-width"
|
|
class="full-width"
|
|
multiple
|
|
multiple
|
|
- @change="handleCompetencyTagsChange"
|
|
|
|
>
|
|
>
|
|
<el-option
|
|
<el-option
|
|
v-for="tag in competencyTagOptions"
|
|
v-for="tag in competencyTagOptions"
|
|
@@ -1163,15 +1162,40 @@
|
|
<div class="config-row">
|
|
<div class="config-row">
|
|
<span class="config-label">标签</span>
|
|
<span class="config-label">标签</span>
|
|
<span class="config-label">权重</span>
|
|
<span class="config-label">权重</span>
|
|
|
|
+ <!-- <el-button
|
|
|
|
+ type="text"
|
|
|
|
+ size="small"
|
|
|
|
+ @click="distributeWeightsEvenly"
|
|
|
|
+ style="color: #409eff; font-size: 12px;"
|
|
|
|
+ >
|
|
|
|
+ 平均分配
|
|
|
|
+ </el-button> -->
|
|
<!-- <span class="config-label">级别</span> -->
|
|
<!-- <span class="config-label">级别</span> -->
|
|
</div>
|
|
</div>
|
|
- <div class="config-row" v-for="tag in questionForm.competency_tags" :key="tag.id">
|
|
|
|
|
|
+ <div class="config-row" v-for="(tag, index) in questionForm.competency_tags" :key="tag.id">
|
|
<span class="tag-name">{{ tag.name }}</span>
|
|
<span class="tag-name">{{ tag.name }}</span>
|
|
- <el-input-number v-model="tag.weight" :min="0" :max="100" :step="1" controls-position="right" /> %
|
|
|
|
|
|
+ <el-input-number
|
|
|
|
+ v-model="tag.weight"
|
|
|
|
+ :min="0"
|
|
|
|
+ :max="100"
|
|
|
|
+ :step="1"
|
|
|
|
+ controls-position="right"
|
|
|
|
+ @change="(value) => handleWeightChange(index, value)"
|
|
|
|
+ /> %
|
|
<!-- <el-input-number v-model="tag.importance" :min="1" :max="5" :step="1" controls-position="right" /> -->
|
|
<!-- <el-input-number v-model="tag.importance" :min="1" :max="5" :step="1" controls-position="right" /> -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
- <div class="form-tip">配置各个胜任力标签的权重,总和需要100%</div>
|
|
|
|
|
|
+ <div class="form-tip">
|
|
|
|
+ 配置各个胜任力标签的权重,总和需要100%
|
|
|
|
+ <span class="weight-sum" :class="{ 'weight-error': getTotalWeight() !== 100 }">
|
|
|
|
+ (当前总计: {{ getTotalWeight() }}%)
|
|
|
|
+ </span>
|
|
|
|
+ <br/>
|
|
|
|
+ <el-icon style="color: #409eff; margin-right: 4px; margin-top: 4px;"><InfoFilled /></el-icon>
|
|
|
|
+ <span style="color: #909399; font-size: 11px;">
|
|
|
|
+ 修改任意权重时,其他权重会自动按比例调整以保持总和为100%。点击"平均分配"可快速平均分配所有权重。
|
|
|
|
+ </span>
|
|
|
|
+ </div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div class="form-item">
|
|
<div class="form-item">
|
|
@@ -2220,8 +2244,9 @@ import { GetList } from '../../questionBank/classList/api';
|
|
import { GetCompetencyList,GetQuestionList,GenerateQuestions,
|
|
import { GetCompetencyList,GetQuestionList,GenerateQuestions,
|
|
GetDraftList,SaveDraft,GetDigitalList,
|
|
GetDraftList,SaveDraft,GetDigitalList,
|
|
CreateConfig,GetConfig,UpdateConfig,AddDocument,
|
|
CreateConfig,GetConfig,UpdateConfig,AddDocument,
|
|
- GetOpeningSpeech,GetVideo,BatchBind,BatchUnbind,UpdateObj,GenerateCompetency,SaveCompetency,GetPositionTags,
|
|
|
|
- UpdateQuestion } from './api';
|
|
|
|
|
|
+ GetOpeningSpeech,GetVideo,BatchBind,BatchUnbind,UpdateObj,
|
|
|
|
+ GenerateCompetency,SaveCompetency,GetPositionTags,
|
|
|
|
+ UpdateQuestion,GetPositionQuestions } from './api';
|
|
import draggable from 'vuedraggable';
|
|
import draggable from 'vuedraggable';
|
|
import { updateFieldConfig } from './utils';
|
|
import { updateFieldConfig } from './utils';
|
|
import type { ProfileFieldsConfig } from './types';
|
|
import type { ProfileFieldsConfig } from './types';
|
|
@@ -2322,7 +2347,7 @@ const recruitmentProcess = reactive([
|
|
{ id: 6, name: '简历收集', description: '简历收集', active: true, type: 'resume_collection' },
|
|
{ id: 6, name: '简历收集', description: '简历收集', active: true, type: 'resume_collection' },
|
|
{ id: 5, name: '资料收集', description: '资料收集', active: true, type: 'data_collection' },
|
|
{ id: 5, name: '资料收集', description: '资料收集', active: true, type: 'data_collection' },
|
|
{ id: 2, name: 'AI考察', description: 'AI考察', active: true, type: 'ai_video' },
|
|
{ id: 2, name: 'AI考察', description: 'AI考察', active: true, type: 'ai_video' },
|
|
- { id: 7, name: '候选人提问', description: '时长5分钟,最多3个问题', active: true, type: 'candidate_questions',
|
|
|
|
|
|
+ /* { id: 7, name: '候选人提问', description: '时长5分钟,最多3个问题', active: true, type: 'candidate_questions',
|
|
config: {
|
|
config: {
|
|
title: '候选人提问',
|
|
title: '候选人提问',
|
|
duration: 5,
|
|
duration: 5,
|
|
@@ -2337,7 +2362,7 @@ const recruitmentProcess = reactive([
|
|
]
|
|
]
|
|
}
|
|
}
|
|
},
|
|
},
|
|
- /* { id: 8, name: '常识问题', description: '常识问题', active: true, type: 'common_sense' },
|
|
|
|
|
|
+ { id: 8, name: '常识问题', description: '常识问题', active: true, type: 'common_sense' },
|
|
{ id: 9, name: '心理问题', description: '心理问题', active: true, type: 'psychological_problem' }, */
|
|
{ id: 9, name: '心理问题', description: '心理问题', active: true, type: 'psychological_problem' }, */
|
|
|
|
|
|
/* { id: 1, name: '待核验', description: '待核验', active: true, type: 'pending_verification' }, */
|
|
/* { id: 1, name: '待核验', description: '待核验', active: true, type: 'pending_verification' }, */
|
|
@@ -2622,6 +2647,18 @@ const questionForm = reactive({
|
|
scoring_reference: '' // 评分标准
|
|
scoring_reference: '' // 评分标准
|
|
});
|
|
});
|
|
|
|
|
|
|
|
+// 定义胜任力标签类型
|
|
|
|
+interface CompetencyTagItem {
|
|
|
|
+ id: string;
|
|
|
|
+ name: string;
|
|
|
|
+ weight: number;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const getcompetency_tags = (value:any)=>{
|
|
|
|
+ return value.map((item:any)=>{
|
|
|
|
+ return item.id
|
|
|
|
+ })
|
|
|
|
+}
|
|
// 胜任力标签选项
|
|
// 胜任力标签选项
|
|
const competencyTagOptions = ref<CompetencyTag[]>([]);
|
|
const competencyTagOptions = ref<CompetencyTag[]>([]);
|
|
|
|
|
|
@@ -2918,6 +2955,7 @@ const cancelEditProcess = () => {
|
|
|
|
|
|
// 删除流程步骤 - 删除后允许重新添加相同类型的步骤
|
|
// 删除流程步骤 - 删除后允许重新添加相同类型的步骤
|
|
const deleteProcessStep = (index: number) => {
|
|
const deleteProcessStep = (index: number) => {
|
|
|
|
+
|
|
recruitmentProcess.splice(index, 1);
|
|
recruitmentProcess.splice(index, 1);
|
|
};
|
|
};
|
|
|
|
|
|
@@ -3662,6 +3700,7 @@ const editQuestion = (index: number) => {
|
|
console.log(question);
|
|
console.log(question);
|
|
Object.assign(questionForm, question);
|
|
Object.assign(questionForm, question);
|
|
showQuestionDialog.value = true;
|
|
showQuestionDialog.value = true;
|
|
|
|
+ distributeWeightsEvenly()
|
|
};
|
|
};
|
|
|
|
|
|
const deleteQuestion = (index: number) => {
|
|
const deleteQuestion = (index: number) => {
|
|
@@ -4401,6 +4440,12 @@ const editAIVideo = async (step: AIVideoStep) => {
|
|
}
|
|
}
|
|
|
|
|
|
if (step.name === 'AI考察') {
|
|
if (step.name === 'AI考察') {
|
|
|
|
+ // 如果是删除操作,先删除相关的AI考察问题
|
|
|
|
+ if (step.action === 'delete') {
|
|
|
|
+ await handleDeleteAIQuestions();
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
const response = await GetConfig(route.query.id);
|
|
const response = await GetConfig(route.query.id);
|
|
competencyLoading.value = true;
|
|
competencyLoading.value = true;
|
|
currentEditingStep.value = step;
|
|
currentEditingStep.value = step;
|
|
@@ -5282,8 +5327,8 @@ const fieldConfig = reactive({
|
|
},
|
|
},
|
|
|
|
|
|
// 专业技能
|
|
// 专业技能
|
|
- skills: { visible: true, label: '专业技能', required: true },
|
|
|
|
- training: { visible: true, label: '受过的培训', required: true },
|
|
|
|
|
|
+ require_professional_skills: { visible: true, label: '专业技能', required: true },
|
|
|
|
+ training_fields_config: { visible: true, label: '受过的培训', required: true },
|
|
|
|
|
|
// 工作经历
|
|
// 工作经历
|
|
work_experience: {
|
|
work_experience: {
|
|
@@ -5366,15 +5411,17 @@ const saveFieldConfig = async () => {
|
|
},
|
|
},
|
|
|
|
|
|
// 培训经历配置
|
|
// 培训经历配置
|
|
- require_training_info: false,
|
|
|
|
- training_fields_config: {
|
|
|
|
|
|
+ require_training_info: fieldConfig.training_fields_config.visible,
|
|
|
|
+ require_professional_skills: fieldConfig.require_professional_skills.visible,
|
|
|
|
+
|
|
|
|
+ /* training_fields_config: {
|
|
training_name: { required: true, visible: true },
|
|
training_name: { required: true, visible: true },
|
|
institution: { required: true, visible: true },
|
|
institution: { required: true, visible: true },
|
|
start_date: { required: true, visible: true },
|
|
start_date: { required: true, visible: true },
|
|
end_date: { required: false, visible: true },
|
|
end_date: { required: false, visible: true },
|
|
certificate: { required: false, visible: true }
|
|
certificate: { required: false, visible: true }
|
|
},
|
|
},
|
|
-
|
|
|
|
|
|
+ */
|
|
// 姿态配置
|
|
// 姿态配置
|
|
enable_posture_check: true,
|
|
enable_posture_check: true,
|
|
posture_check_config: {
|
|
posture_check_config: {
|
|
@@ -5460,8 +5507,8 @@ const getBasicFields = () => {
|
|
// 获取专业技能字段
|
|
// 获取专业技能字段
|
|
const getSkillFields = () => {
|
|
const getSkillFields = () => {
|
|
const skillFields = {
|
|
const skillFields = {
|
|
- skills: fieldConfig.skills,
|
|
|
|
- training: fieldConfig.training
|
|
|
|
|
|
+ skills: fieldConfig.require_professional_skills,
|
|
|
|
+ training: fieldConfig.training_fields_config
|
|
};
|
|
};
|
|
return skillFields;
|
|
return skillFields;
|
|
};
|
|
};
|
|
@@ -5880,6 +5927,132 @@ const editProcessStep = (step: any, index: number) => {
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+// 权重变化处理函数
|
|
|
|
+const handleWeightChange = (index: number, value: number | null) => {
|
|
|
|
+ if (value === null || value < 0) value = 0;
|
|
|
|
+ if (value > 100) value = 100;
|
|
|
|
+
|
|
|
|
+ const tags = questionForm.competency_tags as CompetencyTagItem[];
|
|
|
|
+ const oldWeight = tags[index].weight;
|
|
|
|
+ const newWeight = Math.round(value);
|
|
|
|
+
|
|
|
|
+ // 更新当前标签权重
|
|
|
|
+ tags[index].weight = newWeight;
|
|
|
|
+
|
|
|
|
+ // 如果只有一个标签,直接设置为100
|
|
|
|
+ if (tags.length === 1) {
|
|
|
|
+ tags[0].weight = 100;
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 计算权重差值
|
|
|
|
+ const weightDiff = newWeight - oldWeight;
|
|
|
|
+ const otherTags = tags.filter((_, i) => i !== index);
|
|
|
|
+ const totalOtherWeight = otherTags.reduce((sum, tag) => sum + tag.weight, 0);
|
|
|
|
+
|
|
|
|
+ // 如果其他标签总权重为0,则平均分配剩余权重
|
|
|
|
+ if (totalOtherWeight === 0) {
|
|
|
|
+ const remainingWeight = 100 - newWeight;
|
|
|
|
+ const averageWeight = Math.floor(remainingWeight / otherTags.length);
|
|
|
|
+ const remainder = remainingWeight % otherTags.length;
|
|
|
|
+
|
|
|
|
+ otherTags.forEach((tag, i) => {
|
|
|
|
+ const tagIndex = tags.findIndex(t => t.id === tag.id);
|
|
|
|
+ tags[tagIndex].weight = averageWeight + (i < remainder ? 1 : 0);
|
|
|
|
+ });
|
|
|
|
+ } else {
|
|
|
|
+ // 按比例调整其他标签权重
|
|
|
|
+ const targetTotalOther = 100 - newWeight;
|
|
|
|
+
|
|
|
|
+ if (targetTotalOther > 0) {
|
|
|
|
+ otherTags.forEach(tag => {
|
|
|
|
+ const tagIndex = tags.findIndex(t => t.id === tag.id);
|
|
|
|
+ const proportion = tag.weight / totalOtherWeight;
|
|
|
|
+ tags[tagIndex].weight = Math.round(targetTotalOther * proportion);
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 处理舍入误差
|
|
|
|
+ const currentTotal = getTotalWeight();
|
|
|
|
+ if (currentTotal !== 100) {
|
|
|
|
+ const diff = 100 - currentTotal;
|
|
|
|
+ // 找到最大权重的其他标签进行调整
|
|
|
|
+ const maxOtherTag = otherTags.reduce((max, tag) =>
|
|
|
|
+ tag.weight > max.weight ? tag : max
|
|
|
|
+ );
|
|
|
|
+ const maxTagIndex = tags.findIndex(t => t.id === maxOtherTag.id);
|
|
|
|
+ tags[maxTagIndex].weight += diff;
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ // 如果当前权重已经是100%,其他标签设为0
|
|
|
|
+ otherTags.forEach(tag => {
|
|
|
|
+ const tagIndex = tags.findIndex(t => t.id === tag.id);
|
|
|
|
+ tags[tagIndex].weight = 0;
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 获取总权重
|
|
|
|
+const getTotalWeight = (): number => {
|
|
|
|
+ const tags = questionForm.competency_tags as CompetencyTagItem[];
|
|
|
|
+ return tags.reduce((total, tag) => total + (tag.weight || 0), 0);
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 平均分配权重
|
|
|
|
+const distributeWeightsEvenly = () => {
|
|
|
|
+ const tags = questionForm.competency_tags as CompetencyTagItem[];
|
|
|
|
+ if (tags.length === 0) return;
|
|
|
|
+
|
|
|
|
+ const averageWeight = Math.floor(100 / tags.length);
|
|
|
|
+ const remainder = 100 % tags.length;
|
|
|
|
+
|
|
|
|
+ tags.forEach((tag, index) => {
|
|
|
|
+ tag.weight = averageWeight + (index < remainder ? 1 : 0);
|
|
|
|
+ });
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const competencyTagsValue = computed({
|
|
|
|
+ get: () => getcompetency_tags(questionForm.competency_tags),
|
|
|
|
+ set: (value) => {
|
|
|
|
+ questionForm.competency_tags = value;
|
|
|
|
+ handleCompetencyTagsChange(value);
|
|
|
|
+ }
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+// 删除AI考察相关问题
|
|
|
|
+const handleDeleteAIQuestions = async () => {
|
|
|
|
+ try {
|
|
|
|
+ // 获取当前职位下的所有AI生成的问题
|
|
|
|
+ const response = await GetPositionQuestions({
|
|
|
|
+ position_id: route.query.id,
|
|
|
|
+ tenant_id: 1,
|
|
|
|
+ is_ai_generated: true // 只获取AI生成的问题
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ if (response && response.data && response.data.length > 0) {
|
|
|
|
+ // 批量解绑AI生成的问题
|
|
|
|
+ await BatchUnbind({
|
|
|
|
+ position_id: route.query.id,
|
|
|
|
+ question_id: response.data.map((item: any) => item.id),
|
|
|
|
+ tenant_id: 1
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 更新配置
|
|
|
|
+ if (getConfigId.value > 0) {
|
|
|
|
+ await UpdateConfig({
|
|
|
|
+ id: getConfigId.value,
|
|
|
|
+ enable_ai_questions: false
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ ElMessage.success('AI考察问题已删除');
|
|
|
|
+ }
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('删除AI考察问题失败:', error);
|
|
|
|
+ ElMessage.error('删除AI考察问题失败');
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
</script>
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
<style scoped>
|
|
@@ -6492,4 +6665,126 @@ const editProcessStep = (step: any, index: number) => {
|
|
border-top: 1px solid #ebeef5;
|
|
border-top: 1px solid #ebeef5;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+/* 权重配置增强样式 */
|
|
|
|
+.competency-config {
|
|
|
|
+ .config-row {
|
|
|
|
+ &:first-child {
|
|
|
|
+ background-color: #f8f9fa;
|
|
|
|
+ border-radius: 4px 4px 0 0;
|
|
|
|
+ font-weight: 600;
|
|
|
|
+ color: #409eff;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* 平均分配按钮样式 */
|
|
|
|
+ .el-button--text {
|
|
|
|
+ padding: 4px 8px;
|
|
|
|
+ margin-left: auto;
|
|
|
|
+ font-size: 12px;
|
|
|
|
+
|
|
|
|
+ &:hover {
|
|
|
|
+ background-color: #ecf5ff;
|
|
|
|
+ color: #409eff;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .tag-name {
|
|
|
|
+ font-weight: 500;
|
|
|
|
+ color: #303133;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.weight-sum {
|
|
|
|
+ font-weight: bold;
|
|
|
|
+ color: #67c23a;
|
|
|
|
+ margin-left: 8px;
|
|
|
|
+ font-size: 13px;
|
|
|
|
+
|
|
|
|
+ &.weight-error {
|
|
|
|
+ color: #f56c6c;
|
|
|
|
+ animation: shake 0.5s ease-in-out;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 输入框优化样式 */
|
|
|
|
+:deep(.el-input-number) {
|
|
|
|
+ .el-input__inner {
|
|
|
|
+ text-align: center;
|
|
|
|
+ font-weight: 500;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ &.is-focus .el-input__inner {
|
|
|
|
+ border-color: #409eff;
|
|
|
|
+ box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 权重错误提示动画 */
|
|
|
|
+@keyframes shake {
|
|
|
|
+ 0%, 100% { transform: translateX(0); }
|
|
|
|
+ 25% { transform: translateX(-2px); }
|
|
|
|
+ 75% { transform: translateX(2px); }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 权重配置增强样式 */
|
|
|
|
+.competency-config {
|
|
|
|
+ .config-row {
|
|
|
|
+ &:first-child {
|
|
|
|
+ background-color: #f8f9fa;
|
|
|
|
+ border-radius: 4px 4px 0 0;
|
|
|
|
+ font-weight: 600;
|
|
|
|
+ color: #409eff;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* 平均分配按钮样式 */
|
|
|
|
+ .el-button--text {
|
|
|
|
+ padding: 4px 8px;
|
|
|
|
+ margin-left: auto;
|
|
|
|
+ font-size: 12px;
|
|
|
|
+
|
|
|
|
+ &:hover {
|
|
|
|
+ background-color: #ecf5ff;
|
|
|
|
+ color: #409eff;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .tag-name {
|
|
|
|
+ font-weight: 500;
|
|
|
|
+ color: #303133;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.weight-sum {
|
|
|
|
+ font-weight: bold;
|
|
|
|
+ color: #67c23a;
|
|
|
|
+ margin-left: 8px;
|
|
|
|
+ font-size: 13px;
|
|
|
|
+
|
|
|
|
+ &.weight-error {
|
|
|
|
+ color: #f56c6c;
|
|
|
|
+ animation: shake 0.5s ease-in-out;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 输入框优化样式 */
|
|
|
|
+:deep(.el-input-number) {
|
|
|
|
+ .el-input__inner {
|
|
|
|
+ text-align: center;
|
|
|
|
+ font-weight: 500;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ &.is-focus .el-input__inner {
|
|
|
|
+ border-color: #409eff;
|
|
|
|
+ box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* 权重错误提示动画 */
|
|
|
|
+@keyframes shake {
|
|
|
|
+ 0%, 100% { transform: translateX(0); }
|
|
|
|
+ 25% { transform: translateX(-2px); }
|
|
|
|
+ 75% { transform: translateX(2px); }
|
|
|
|
+}
|
|
</style>
|
|
</style>
|