diff --git a/src/components/common/langs/en.js b/src/components/common/langs/en.js index fa1ee9f..a81d9e3 100644 --- a/src/components/common/langs/en.js +++ b/src/components/common/langs/en.js @@ -1069,6 +1069,8 @@ colTitle: 'Template title', autoSolicit: 'Auto Solicitation', editConfig: 'Edit Configuration', running: 'Running', + stopped: 'Stopped', + configure: 'Configure', emailTemplate: 'Email Template', emailStyle: 'Email Style', notStarted: 'Auto solicitation plan is not enabled', @@ -1179,6 +1181,7 @@ colTitle: 'Template title', factoryScenarioSolicit: 'Invite Submission', factoryScenarioPromoteCitation: 'Promote Citation', factoryScenarioGeneralThanks: 'General Thanks', + createdAt: 'Created at', noFactoryTask: 'No tasks', factoryCreateNow: 'Create now' } diff --git a/src/components/common/langs/zh.js b/src/components/common/langs/zh.js index 8280c7a..31d0d0e 100644 --- a/src/components/common/langs/zh.js +++ b/src/components/common/langs/zh.js @@ -1054,6 +1054,8 @@ const zh = { autoSolicit: '自动约稿', editConfig: '修改配置', running: '运行中', + stopped: '已停止', + configure: '配置', emailTemplate: '邮件模板', emailStyle: '邮件风格', notStarted: '未开启自动约稿计划', @@ -1164,6 +1166,7 @@ const zh = { factoryScenarioSolicit: '约稿', factoryScenarioPromoteCitation: '推广引用', factoryScenarioGeneralThanks: '常规感谢', + createdAt: '创建时间', noFactoryTask: '没有任务', factoryCreateNow: '立即创建' } diff --git a/src/components/page/autoPromotion.vue b/src/components/page/autoPromotion.vue index c258996..f015d67 100644 --- a/src/components/page/autoPromotion.vue +++ b/src/components/page/autoPromotion.vue @@ -62,13 +62,13 @@
- {{ taskCard.enabled ? $t('autoPromotion.running').toUpperCase() : 'STOPPED' }} + {{ taskCard.enabled ? $t('autoPromotion.running') : $t('autoPromotion.stopped') }}
@@ -76,21 +76,21 @@
-
+
Template : - {{ taskCard.templateName || 'No Template Configured' }} + {{ taskCard.templateName || 'No Template Configured' }}
-
+
Promotion Fields : {{ taskCard.fieldCountText }}
-
+
Country : @@ -112,18 +112,29 @@
- +
+ {{ $t('autoPromotion.createdAt') }}: {{ taskCard.createdAtText }} +
{{ $t('autoPromotion.noFactoryTask') }} - + + {{ $t('autoPromotion.factoryCreateNow') }}
@@ -196,6 +207,7 @@ export default { saving: false, promotionUpdating: false, promotionUpdatingJournalId: '', + promotionUpdatingTaskId: '', wizardJournal: null, wizardStartDate: '', wizardConfig: { @@ -215,6 +227,7 @@ export default { templateDialogInitialStyleId: '', templateDialogInitialTemplateId: '', templateNameMap: {}, + factoryTemplateNameMap: {}, allJournals: [], showFactoryTaskDialog: false, factoryDialogInitialJournalId: '', @@ -225,6 +238,9 @@ export default { this.fetchPromotionJournals(); }, methods: { + savePromotionCountriesNow(){ + + }, // --- 逻辑部分完全保留你原有的实现 --- openTemplateSelector() { this.templateDialogInitialStyleId = @@ -279,6 +295,38 @@ export default { this.$set(taskCard, 'switchLoading', false); } }, + getFactoryTaskActionText(taskCard) { + return taskCard && taskCard.enabled ? this.$t('autoPromotion.goManagePlan') : this.$t('autoPromotion.startPlan'); + }, + async handleFactoryTaskAction(journal, taskCard) { + if (!journal || !taskCard) return; + const taskId = taskCard.taskId != null ? String(taskCard.taskId) : ''; + this.promotionUpdating = true; + this.promotionUpdatingJournalId = journal.journal_id; + this.promotionUpdatingTaskId = taskId; + try { + if (!taskCard.enabled && taskId) { + const res = await this.$api.post('api/promotion_factory/changePromotionAct', { + promotion_factory_id: taskId, + start_promotion: '1' + }); + if (!res || Number(res.code) !== 0) { + this.$message.error((res && res.msg) || this.$t('autoPromotion.saveFailed')); + return; + } + this.$set(taskCard, 'enabled', true); + if (taskCard.rawTask) { + this.$set(taskCard.rawTask, 'state', '1'); + this.$set(taskCard.rawTask, 'start_promotion', '1'); + } + } + this.openDetail(journal, taskCard); + } finally { + this.promotionUpdating = false; + this.promotionUpdatingJournalId = ''; + this.promotionUpdatingTaskId = ''; + } + }, _parseJournalDetail(data) { const journalInfo = data.journal || data || {}; const tpl = journalInfo.template || data.template || {}; @@ -356,6 +404,7 @@ export default { async loadFactoryTaskSummaryByJournal(journal, userId) { if (!journal || !journal.journal_id) return; try { + await this.loadFactoryTemplateNamesByJournal(journal.journal_id); const res = await this.$api.post('api/promotion_factory/getList', { journal_id: String(journal.journal_id), user_id: String(userId || ''), @@ -375,7 +424,12 @@ export default { const normalizedTasks = (Array.isArray(list) ? list : []).map((task, idx) => { const type = task && task.type != null ? String(task.type) : ''; const fieldCount = this.getFactoryTaskFieldCount(task); - const enabled = String(task && task.state != null ? task.state : '0') === '1'; + const startPromotion = + task && task.start_promotion != null && String(task.start_promotion).trim() !== '' + ? String(task.start_promotion) + : null; + const state = task && task.state != null && String(task.state).trim() !== '' ? String(task.state) : null; + const enabled = String(startPromotion != null ? startPromotion : state != null ? state : '0') === '1'; return { cardKey: `${journal.journal_id}_${task.promotion_factory_id || idx}`, taskId: task.promotion_factory_id || '', @@ -384,10 +438,11 @@ export default { enabled: enabled, switchLoading: false, initialized: true, - templateName: task.template_name || (task.template_id ? `Template #${task.template_id}` : 'No Template Configured'), + templateName: this.getFactoryTemplateName(task, journal.journal_id), fieldCount: fieldCount, fieldCountText: String(fieldCount), countryScopeLabel: task.country_scope_label || '-', + createdAtText: this.formatTaskCreateTime(task), totalCount: total, showCount: idx === 0 && total > 0, rawTask: task @@ -411,6 +466,37 @@ export default { }); } }, + async loadFactoryTemplateNamesByJournal(journalId) { + const key = String(journalId || ''); + if (!key) return {}; + if (this.factoryTemplateNameMap[key]) { + return this.factoryTemplateNameMap[key]; + } + let map = {}; + try { + const res = await this.$api.post('api/mail_template/listTemplatesAll', { journal_id: key }); + const payload = (res && res.data) || {}; + const list = this.findArray(payload) || this.findArray(res) || []; + map = (Array.isArray(list) ? list : []).reduce((acc, item) => { + const id = item && (item.template_id != null ? item.template_id : item.id); + if (id == null) return acc; + const name = item.title || item.name || ''; + if (name) acc[String(id)] = name; + return acc; + }, {}); + } catch (e) { + map = {}; + } + this.$set(this.factoryTemplateNameMap, key, map); + return map; + }, + getFactoryTemplateName(task, journalId) { + const tplId = task && task.template_id != null ? String(task.template_id) : ''; + const journalMap = this.factoryTemplateNameMap[String(journalId || '')] || {}; + if (tplId && journalMap[tplId]) return journalMap[tplId]; + if (task && task.template_name) return task.template_name; + return tplId ? `Template #${tplId}` : 'No Template Configured'; + }, getFactoryTaskFieldCount(task) { if (!task) return 0; if (task.fetch_fields && typeof task.fetch_fields === 'object') { @@ -423,6 +509,23 @@ export default { .filter(Boolean); return fetchIds.length; }, + formatTaskCreateTime(task) { + if (!task || typeof task !== 'object') return ''; + const raw = task.ctime || task.create_time || task.created_at || task.time || ''; + if (raw == null || String(raw).trim() === '') return ''; + const n = Number(raw); + let dt = null; + if (!isNaN(n) && n > 0) { + dt = new Date(n > 1e12 ? n : n * 1000); + } else { + dt = new Date(String(raw)); + } + if (!dt || isNaN(dt.getTime())) return String(raw); + const pad = (v) => String(v).padStart(2, '0'); + return `${dt.getFullYear()}-${pad(dt.getMonth() + 1)}-${pad(dt.getDate())} ${pad(dt.getHours())}:${pad( + dt.getMinutes() + )}:${pad(dt.getSeconds())}`; + }, mapFactoryTaskTypeLabel(type) { const t = String(type || ''); if (t === '1') return this.$t('autoPromotion.factoryScenarioSolicit'); @@ -443,6 +546,7 @@ export default { }, async refreshAll() { this.templateNameMap = {}; + this.factoryTemplateNameMap = {}; this.allJournals = []; await this.fetchPromotionJournals(); }, @@ -512,34 +616,7 @@ export default { country_fetch_ids: (this.selectedCountryIds || []).join(',') }; }, - async handleSolicitAction(journal) { - if (!this.isSolicitConfigured(journal)) { - this.openWizardForJournal(journal); - return; - } - if (!journal.solicit.enabled) { - this.promotionUpdating = true; - this.promotionUpdatingJournalId = journal.journal_id; - try { - await this.$api.post('api/email_client/setDefaultPromotion', { - journal_id: String(journal.journal_id), - default_template_id: String(journal.solicit.templateId), - default_style_id: String(journal.solicit.styleId), - start_promotion: '1', - user_id: localStorage.getItem('U_id') - }); - await this.refreshJournalByDetail(journal); - this.openDetail(journal); - } catch (e) { - this.$message.error('Update failed'); - } finally { - this.promotionUpdating = false; - this.promotionUpdatingJournalId = ''; - } - } else { - this.openDetail(journal); - } - }, + openWizardForJournal(journal) { this.wizardJournal = journal; const s = journal.solicit || {}; @@ -575,8 +652,15 @@ export default { this.saving = false; } }, - openDetail(journal) { - this.$router.push({ path: '/autoPromotionLogs', query: { journal_id: String(journal.journal_id) } }); + openDetail(journal,taskCard) { + const taskId = taskCard && taskCard.taskId != null ? String(taskCard.taskId) : ''; + this.$router.push({ + path: '/autoPromotionLogs', + query: { + journal_id: String(journal.journal_id), + promotion_factory_id: taskId + } + }); }, async handleSwitch(journal, type, nextVal) { if (!this.isSolicitConfigured(journal)) { @@ -713,6 +797,13 @@ export default { background: #fafafa; } +.no-task-create-btn { + display: inline-flex; + align-items: center; + gap: 4px; + cursor: pointer; +} + /* 统计卡片基础样式 */ .stat-card { background: #ffffff; @@ -732,7 +823,7 @@ export default { } .stat-card.is-active { - border-top: 3px solid #409eff; + border-top: 3px solid #67c23a; } .stat-card.is-stopped { @@ -839,6 +930,12 @@ export default { box-sizing: border-box; } +.meta-row { + display: flex; + align-items: center; + min-width: 0; +} + .template-preview-box.is-empty { color: #909399; font-style: italic; @@ -852,6 +949,11 @@ export default { white-space: nowrap; } +.tpl-name-single-line { + flex: 1; + min-width: 0; +} + /* 统计项 */ .stats-container { display: grid; @@ -896,24 +998,46 @@ export default { /* 底部按钮区 */ .card-actions { display: flex; + flex-direction: column; gap: 8px; } +.task-create-time { + font-size: 11px; + color: #909399; + text-align: right; +} + .action-main-btn { flex: 1; - height: 30px; - background: #006699; - color: white; - border: none; - border-radius: 6px; - font-weight: 700; + height: 24px; + background: #eaf3ff; + color: #409eff; + border: 1px solid #d7e7ff; + border-radius: 4px; + font-weight: 600; font-size: 12px; + line-height: 22px; cursor: pointer; - transition: background 0.2s; + transition: all 0.2s; } .action-main-btn:hover { - background: #006699; + background: #dcecff; + border-color: #c4ddff; + color: #2f89ea; +} + +.is-active .action-main-btn { + background: #3dbb6a; + color: #ffffff; + border-color: #3dbb6a; +} + +.is-active .action-main-btn:hover { + background: #31a85b; + border-color: #31a85b; + color: #ffffff; } .action-main-btn.secondary { diff --git a/src/components/page/autoPromotionLogs.vue b/src/components/page/autoPromotionLogs.vue index d058d20..87cb326 100644 --- a/src/components/page/autoPromotionLogs.vue +++ b/src/components/page/autoPromotionLogs.vue @@ -363,6 +363,7 @@ export default { showFactoryTaskDialog: false, factoryDialogInitialJournalId: '', factoryDialogInitialTask: null, + routePromotionFactoryId: '', previewForm: { id: '', email: '', @@ -485,6 +486,11 @@ export default { async initPage() { this.hidePage = false; var journal_id = (this.$route.query && this.$route.query.journal_id) || ''; + var pfid = + (this.$route.query && this.$route.query.promotion_factory_id) || + (this.$route.query && this.$route.query.taskId) || + ''; + this.routePromotionFactoryId = String(pfid || ''); this.selectedJournalId = String(journal_id); this.loading = true; try { @@ -675,7 +681,29 @@ export default { }, openFactoryTaskDialogFromLogs() { this.factoryDialogInitialJournalId = this.selectedJournalId ? String(this.selectedJournalId) : ''; - this.factoryDialogInitialTask = this.list && this.list.length ? { ...this.list[0] } : null; + const targetTaskId = String(this.routePromotionFactoryId || '').trim(); + const first = this.list && this.list.length ? this.list[0] : null; + const matched = targetTaskId + ? (this.list || []).find((row) => { + const pid = row && row.promotion_factory_id != null ? String(row.promotion_factory_id) : ''; + const rid = row && row.id != null ? String(row.id) : ''; + const tid = row && row.task_id != null ? String(row.task_id) : ''; + return pid === targetTaskId || rid === targetTaskId || tid === targetTaskId; + }) || first + : first; + this.factoryDialogInitialTask = matched + ? { + ...matched, + promotion_factory_id: + matched.promotion_factory_id != null + ? String(matched.promotion_factory_id) + : matched.id != null + ? String(matched.id) + : matched.task_id != null + ? String(matched.task_id) + : '' + } + : null; this.showFactoryTaskDialog = true; }, @@ -811,8 +839,15 @@ export default { this.list = rawList.map((item, idx) => { const runAt = item.run_at || item.run_time || item.plan_time || item.execute_time || item.send_date || ''; const state = String(item.state != null ? item.state : ''); + const promotionFactoryId = + item.promotion_factory_id != null + ? item.promotion_factory_id + : item.id != null + ? item.id + : item.task_id; return { id: item.id || item.task_id || `task_${idx + 1}`, + promotion_factory_id: promotionFactoryId != null ? String(promotionFactoryId) : '', task_id: String(item.task_id != null ? item.task_id : item.id || ''), task_name: item.task_name || item.name || '', scene: item.scene || '', diff --git a/src/components/page/components/autoPromotion/PromotionFactoryTaskDialog.vue b/src/components/page/components/autoPromotion/PromotionFactoryTaskDialog.vue index d4ad0bc..7df0c78 100644 --- a/src/components/page/components/autoPromotion/PromotionFactoryTaskDialog.vue +++ b/src/components/page/components/autoPromotion/PromotionFactoryTaskDialog.vue @@ -44,6 +44,7 @@ v-model="selectedJournalId" filterable clearable + :disabled="isEditMode" style="width: 100%" :placeholder="$t('autoPromotion.factoryJournalPlaceholder')" @change="onJournalChange" @@ -111,7 +112,7 @@ -
+
- {{ sendCountMaxHint }} + {{ sendCountMaxHint }}
@@ -280,6 +281,9 @@ const API_GET_ACCOUNTS = 'api/email_client/getAccounts'; const API_AVAILABLE_FIELDS = 'api/email_client/getAvailableFields'; const API_FACTORY_ADD = 'api/promotion_factory/add'; + const API_FACTORY_EDIT = 'api/promotion_factory/edit'; + const API_FACTORY_DETAIL = 'api/promotion_factory/getDetail'; + const API_TEMPLATE_LIST_ALL = 'api/mail_template/listTemplatesAll'; const API_GET_COUNT_FOR_EMAIL_IDS = 'api/promotion_factory/getCountForPromotionEmailIds'; const PARTITION_TO_API = { @@ -327,6 +331,8 @@ factoryConfig: { defaultTemplateId: '', defaultStyleId: '' }, factoryTemplateName: '', factoryStyleName: '', + editingTaskId: '', + templateNameMap: {}, showFactoryTemplateDialog: false, _emailIdsSendLimitTimer: null }; @@ -393,6 +399,7 @@ }, sendCountMaxHint() { if (this.sendCountMaxLoading) return ''; + if (!this.selectedEmailIds || this.selectedEmailIds.length === 0) return ''; if (this.sendLimitFromApi) { return this.$t('autoPromotion.factorySendMaxFromApi', { max: this.sendCountMax }); } @@ -439,19 +446,96 @@ await this.loadJournalList(); if (this.journalList.length) { const preferred = this.initialJournalId != null && this.initialJournalId !== '' ? String(this.initialJournalId) : ''; + const initialTaskJournalId = this.getInitialTaskJournalId(this.initialTask); const matched = preferred ? this.journalList.find(function (j) { return String(j.journal_id) === preferred; }) : null; - this.selectedJournalId = matched ? matched.journal_id : this.journalList[0].journal_id; - await this.onJournalChange(this.selectedJournalId); - this.applyInitialTaskSeed(); + const matchedByTask = initialTaskJournalId + ? this.journalList.find(function (j) { + return String(j.journal_id) === initialTaskJournalId; + }) + : null; + this.selectedJournalId = matchedByTask + ? matchedByTask.journal_id + : matched + ? matched.journal_id + : this.journalList[0].journal_id; + if (this.isEditMode) { + await this.loadAccounts(this.selectedJournalId); + const initialSeed = await this.loadInitialTaskSeed(); + await this.applyInitialTaskSeed(initialSeed); + await Promise.all([this.loadAvailableFields(this.selectedJournalId), this.refreshSendCountMax()]); + this.syncActiveStep(); + } else { + await this.onJournalChange(this.selectedJournalId); + } } }, - applyInitialTaskSeed() { + getInitialTaskId(task) { + if (!task || typeof task !== 'object') return ''; + const id = task.promotion_factory_id != null ? task.promotion_factory_id : task.id != null ? task.id : ''; + return String(id || '').trim(); + }, + getInitialTaskJournalId(task) { + if (!task || typeof task !== 'object') return ''; + const id = task.journal_id != null ? task.journal_id : task.j_id != null ? task.j_id : ''; + return String(id || '').trim(); + }, + async loadInitialTaskSeed() { const task = this.initialTask && typeof this.initialTask === 'object' ? this.initialTask : null; + if (!task) return null; + const taskId = this.getInitialTaskId(task); + if (!taskId) return task; + try { + const res = await this.$api.post(API_FACTORY_DETAIL, { promotion_factory_id: taskId }); + if (!res || (res.code != null && Number(res.code) !== 0)) { + return task; + } + const payload = res.data != null ? res.data : res; + if (Array.isArray(payload)) return payload[0] || task; + if (payload && typeof payload === 'object') { + if (payload.detail && typeof payload.detail === 'object') return payload.detail; + if (payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data)) return payload.data; + const fromArray = this.findArray(payload); + if (Array.isArray(fromArray) && fromArray.length) return fromArray[0]; + return payload; + } + } catch (e) { + console.error(e); + } + return task; + }, + async loadTemplateNameMapByJournal(journalId) { + const key = String(journalId || ''); + if (!key) return {}; + if (this.templateNameMap[key]) return this.templateNameMap[key]; + let map = {}; + try { + const res = await this.$api.post(API_TEMPLATE_LIST_ALL, { journal_id: key }); + const payload = (res && res.data) || {}; + const list = this.findArray(payload) || this.findArray(res) || []; + map = (Array.isArray(list) ? list : []).reduce(function (acc, item) { + const id = item && (item.template_id != null ? item.template_id : item.id); + const name = item && (item.title || item.name || ''); + if (id != null && String(name).trim() !== '') { + acc[String(id)] = String(name); + } + return acc; + }, {}); + } catch (e) { + console.error(e); + map = {}; + } + this.$set(this.templateNameMap, key, map); + return map; + }, + async applyInitialTaskSeed(seedTask) { + const task = seedTask && typeof seedTask === 'object' ? seedTask : this.initialTask && typeof this.initialTask === 'object' ? this.initialTask : null; if (!task) return; + const taskId = this.getInitialTaskId(task); + this.editingTaskId = taskId || this.editingTaskId; if (task.type != null && String(task.type) !== '') { this.factoryType = String(task.type); } @@ -491,7 +575,10 @@ this.factoryZoneCountryIds = zoneKeys; if (task.template_id != null && String(task.template_id) !== '') { this.factoryConfig.defaultTemplateId = String(task.template_id); - this.factoryTemplateName = task.template_name || `Template #${task.template_id}`; + const tplId = String(task.template_id); + const fallbackTplName = task.template_name || ''; + const templateMap = await this.loadTemplateNameMapByJournal(this.selectedJournalId); + this.factoryTemplateName = templateMap[tplId] || fallbackTplName || `Template #${task.template_id}`; } if (task.style_id != null && String(task.style_id) !== '') { this.factoryConfig.defaultStyleId = String(task.style_id); @@ -896,21 +983,31 @@ return; } const pc = this.buildPartitionsAndCountries(); + const payload = { + journal_id: String(this.selectedJournalId), + type: String(this.factoryType), + expert_type: String(this.expertType || '').trim(), + email_ids: this.selectedEmailIds.join(','), + send_count: String(this.sendCount), + template_id: String(this.factoryConfig.defaultTemplateId), + style_id: String(this.factoryConfig.defaultStyleId), + fetch_ids: (this.factoryFieldIds || []).join(','), + target_partitions: pc.target_partitions, + target_country_ids: pc.target_country_ids, + start_promotion: '0' + }; + const endpoint = this.isEditMode ? API_FACTORY_EDIT : API_FACTORY_ADD; + if (this.isEditMode) { + const taskId = this.editingTaskId || this.getInitialTaskId(this.initialTask || {}); + if (!taskId) { + this.$message.error(this.$t('autoPromotion.factorySubmitFailed')); + return; + } + payload.promotion_factory_id = String(taskId); + } this.submitting = true; try { - const res = await this.$api.post(API_FACTORY_ADD, { - journal_id: String(this.selectedJournalId), - type: String(this.factoryType), - expert_type: String(this.expertType || '').trim(), - email_ids: this.selectedEmailIds.join(','), - send_count: String(this.sendCount), - template_id: String(this.factoryConfig.defaultTemplateId), - style_id: String(this.factoryConfig.defaultStyleId), - fetch_ids: (this.factoryFieldIds || []).join(','), - target_partitions: pc.target_partitions, - target_country_ids: pc.target_country_ids, - start_promotion: '0' - }); + const res = await this.$api.post(endpoint, payload); if (res && Number(res.code) === 0) { this.$message.success(this.$t('autoPromotion.factorySubmitSuccess')); this.innerVisible = false; @@ -1229,7 +1326,7 @@ display: flex; flex-direction: row; flex-wrap: wrap; - align-items: center; + align-items: flex-start; justify-content: flex-start; gap: 6px 14px; padding: 10px 16px 10px 18px; @@ -1244,7 +1341,7 @@ display: inline-flex; flex-direction: row; flex-wrap: nowrap; - align-items: center; + align-items: flex-start; gap: 8px; min-width: 0; } @@ -1267,6 +1364,13 @@ min-width: 0; } + .parameter-bar .param-inline-controls-send { + display: inline-flex; + flex-direction: column; + align-items: flex-start; + gap: 4px; + } + .parameter-bar .param-item-send { flex: 0 0 auto; } @@ -1284,6 +1388,11 @@ line-height: 32px; } + .parameter-bar .el-input-number--small .el-input__inner { + height: 32px; + line-height: 32px; + } + .max-tip { font-size: 11px; color: #e6a23c; @@ -1291,6 +1400,10 @@ max-width: 260px; } + .max-tip-under { + display: block; + } + .factory-required-star { color: #f56c6c; font-weight: 700; @@ -1346,7 +1459,7 @@ } .more-hint { font-size: 12px; color: #409eff; font-weight: bold; } -/deep/.el-dialog__body{ +::v-deep .el-dialog__body{ padding-top:15px !important; padding-bottom:15px !important; }